aboutsummaryrefslogtreecommitdiff
path: root/internal/lsp
diff options
context:
space:
mode:
Diffstat (limited to 'internal/lsp')
-rw-r--r--internal/lsp/README.md7
-rw-r--r--internal/lsp/analysis/fillreturns/fillreturns.go276
-rw-r--r--internal/lsp/analysis/fillreturns/fillreturns_test.go22
-rw-r--r--internal/lsp/analysis/fillreturns/testdata/src/a/a.go139
-rw-r--r--internal/lsp/analysis/fillreturns/testdata/src/a/a.go.golden139
-rw-r--r--internal/lsp/analysis/fillreturns/testdata/src/a/typeparams/a.go5
-rw-r--r--internal/lsp/analysis/fillreturns/testdata/src/a/typeparams/a.go.golden5
-rw-r--r--internal/lsp/analysis/fillstruct/fillstruct.go495
-rw-r--r--internal/lsp/analysis/fillstruct/fillstruct_test.go22
-rw-r--r--internal/lsp/analysis/fillstruct/testdata/src/a/a.go106
-rw-r--r--internal/lsp/analysis/fillstruct/testdata/src/b/b.go6
-rw-r--r--internal/lsp/analysis/fillstruct/testdata/src/typeparams/typeparams.go41
-rw-r--r--internal/lsp/analysis/infertypeargs/infertypeargs.go31
-rw-r--r--internal/lsp/analysis/infertypeargs/infertypeargs_test.go23
-rw-r--r--internal/lsp/analysis/infertypeargs/run_go117.go16
-rw-r--r--internal/lsp/analysis/infertypeargs/run_go118.go111
-rw-r--r--internal/lsp/analysis/infertypeargs/testdata/src/a/basic.go20
-rw-r--r--internal/lsp/analysis/infertypeargs/testdata/src/a/basic.go.golden20
-rw-r--r--internal/lsp/analysis/infertypeargs/testdata/src/a/imported.go12
-rw-r--r--internal/lsp/analysis/infertypeargs/testdata/src/a/imported.go.golden12
-rw-r--r--internal/lsp/analysis/infertypeargs/testdata/src/a/imported/imported.go7
-rw-r--r--internal/lsp/analysis/infertypeargs/testdata/src/a/notypechange.go26
-rw-r--r--internal/lsp/analysis/infertypeargs/testdata/src/a/notypechange.go.golden26
-rw-r--r--internal/lsp/analysis/nonewvars/nonewvars.go93
-rw-r--r--internal/lsp/analysis/nonewvars/nonewvars_test.go22
-rw-r--r--internal/lsp/analysis/nonewvars/testdata/src/a/a.go16
-rw-r--r--internal/lsp/analysis/nonewvars/testdata/src/a/a.go.golden16
-rw-r--r--internal/lsp/analysis/nonewvars/testdata/src/typeparams/a.go6
-rw-r--r--internal/lsp/analysis/nonewvars/testdata/src/typeparams/a.go.golden6
-rw-r--r--internal/lsp/analysis/noresultvalues/noresultvalues.go90
-rw-r--r--internal/lsp/analysis/noresultvalues/noresultvalues_test.go22
-rw-r--r--internal/lsp/analysis/noresultvalues/testdata/src/a/a.go9
-rw-r--r--internal/lsp/analysis/noresultvalues/testdata/src/a/a.go.golden9
-rw-r--r--internal/lsp/analysis/noresultvalues/testdata/src/typeparams/a.go6
-rw-r--r--internal/lsp/analysis/noresultvalues/testdata/src/typeparams/a.go.golden6
-rw-r--r--internal/lsp/analysis/simplifycompositelit/simplifycompositelit.go196
-rw-r--r--internal/lsp/analysis/simplifycompositelit/simplifycompositelit_test.go17
-rw-r--r--internal/lsp/analysis/simplifycompositelit/testdata/src/a/a.go234
-rw-r--r--internal/lsp/analysis/simplifycompositelit/testdata/src/a/a.go.golden234
-rw-r--r--internal/lsp/analysis/simplifyrange/simplifyrange.go116
-rw-r--r--internal/lsp/analysis/simplifyrange/simplifyrange_test.go17
-rw-r--r--internal/lsp/analysis/simplifyrange/testdata/src/a/a.go16
-rw-r--r--internal/lsp/analysis/simplifyrange/testdata/src/a/a.go.golden16
-rw-r--r--internal/lsp/analysis/simplifyslice/simplifyslice.go94
-rw-r--r--internal/lsp/analysis/simplifyslice/simplifyslice_test.go22
-rw-r--r--internal/lsp/analysis/simplifyslice/testdata/src/a/a.go70
-rw-r--r--internal/lsp/analysis/simplifyslice/testdata/src/a/a.go.golden70
-rw-r--r--internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go39
-rw-r--r--internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden39
-rw-r--r--internal/lsp/analysis/stubmethods/stubmethods.go351
-rw-r--r--internal/lsp/analysis/undeclaredname/testdata/src/a/a.go28
-rw-r--r--internal/lsp/analysis/undeclaredname/testdata/src/a/channels.go13
-rw-r--r--internal/lsp/analysis/undeclaredname/testdata/src/a/consecutive_params.go10
-rw-r--r--internal/lsp/analysis/undeclaredname/testdata/src/a/error_param.go10
-rw-r--r--internal/lsp/analysis/undeclaredname/testdata/src/a/literals.go11
-rw-r--r--internal/lsp/analysis/undeclaredname/testdata/src/a/operation.go11
-rw-r--r--internal/lsp/analysis/undeclaredname/testdata/src/a/selector.go10
-rw-r--r--internal/lsp/analysis/undeclaredname/testdata/src/a/slice.go9
-rw-r--r--internal/lsp/analysis/undeclaredname/testdata/src/a/tuple.go13
-rw-r--r--internal/lsp/analysis/undeclaredname/testdata/src/a/unique_params.go11
-rw-r--r--internal/lsp/analysis/undeclaredname/undeclared.go340
-rw-r--r--internal/lsp/analysis/undeclaredname/undeclared_test.go17
-rw-r--r--internal/lsp/analysis/unusedparams/testdata/src/a/a.go55
-rw-r--r--internal/lsp/analysis/unusedparams/testdata/src/a/a.go.golden55
-rw-r--r--internal/lsp/analysis/unusedparams/testdata/src/typeparams/typeparams.go55
-rw-r--r--internal/lsp/analysis/unusedparams/testdata/src/typeparams/typeparams.go.golden55
-rw-r--r--internal/lsp/analysis/unusedparams/unusedparams.go152
-rw-r--r--internal/lsp/analysis/unusedparams/unusedparams_test.go22
-rw-r--r--internal/lsp/analysis/useany/testdata/src/a/a.go25
-rw-r--r--internal/lsp/analysis/useany/testdata/src/a/a.go.golden25
-rw-r--r--internal/lsp/analysis/useany/useany.go102
-rw-r--r--internal/lsp/analysis/useany/useany_test.go21
-rw-r--r--internal/lsp/browser/README.md1
-rw-r--r--internal/lsp/browser/browser.go67
-rw-r--r--internal/lsp/cache/analysis.go433
-rw-r--r--internal/lsp/cache/cache.go293
-rw-r--r--internal/lsp/cache/check.go863
-rw-r--r--internal/lsp/cache/error_test.go52
-rw-r--r--internal/lsp/cache/errors.go411
-rw-r--r--internal/lsp/cache/imports.go201
-rw-r--r--internal/lsp/cache/keys.go52
-rw-r--r--internal/lsp/cache/load.go507
-rw-r--r--internal/lsp/cache/metadata.go74
-rw-r--r--internal/lsp/cache/mod.go516
-rw-r--r--internal/lsp/cache/mod_tidy.go500
-rw-r--r--internal/lsp/cache/os_darwin.go59
-rw-r--r--internal/lsp/cache/os_windows.go55
-rw-r--r--internal/lsp/cache/parse.go1467
-rw-r--r--internal/lsp/cache/parse_test.go217
-rw-r--r--internal/lsp/cache/pkg.go149
-rw-r--r--internal/lsp/cache/session.go741
-rw-r--r--internal/lsp/cache/snapshot.go2479
-rw-r--r--internal/lsp/cache/symbols.go210
-rw-r--r--internal/lsp/cache/view.go1076
-rw-r--r--internal/lsp/cache/view_test.go218
-rw-r--r--internal/lsp/cache/workspace.go599
-rw-r--r--internal/lsp/cache/workspace_test.go425
-rw-r--r--internal/lsp/call_hierarchy.go42
-rw-r--r--internal/lsp/cmd/call_hierarchy.go146
-rw-r--r--internal/lsp/cmd/capabilities_test.go166
-rw-r--r--internal/lsp/cmd/check.go74
-rw-r--r--internal/lsp/cmd/cmd.go630
-rw-r--r--internal/lsp/cmd/cmd_test.go23
-rw-r--r--internal/lsp/cmd/definition.go137
-rw-r--r--internal/lsp/cmd/export_test.go11
-rw-r--r--internal/lsp/cmd/folding_range.go73
-rw-r--r--internal/lsp/cmd/format.go108
-rw-r--r--internal/lsp/cmd/help_test.go57
-rw-r--r--internal/lsp/cmd/highlight.go89
-rw-r--r--internal/lsp/cmd/implementation.go88
-rw-r--r--internal/lsp/cmd/imports.go102
-rw-r--r--internal/lsp/cmd/info.go197
-rw-r--r--internal/lsp/cmd/links.go78
-rw-r--r--internal/lsp/cmd/prepare_rename.go84
-rw-r--r--internal/lsp/cmd/references.go92
-rw-r--r--internal/lsp/cmd/remote.go164
-rw-r--r--internal/lsp/cmd/rename.go128
-rw-r--r--internal/lsp/cmd/semantictokens.go230
-rw-r--r--internal/lsp/cmd/serve.go130
-rw-r--r--internal/lsp/cmd/signature.go87
-rw-r--r--internal/lsp/cmd/subcommands.go44
-rw-r--r--internal/lsp/cmd/suggested_fix.go159
-rw-r--r--internal/lsp/cmd/symbols.go116
-rw-r--r--internal/lsp/cmd/test/call_hierarchy.go85
-rw-r--r--internal/lsp/cmd/test/check.go63
-rw-r--r--internal/lsp/cmd/test/cmdtest.go169
-rw-r--r--internal/lsp/cmd/test/definition.go61
-rw-r--r--internal/lsp/cmd/test/folding_range.go25
-rw-r--r--internal/lsp/cmd/test/format.go86
-rw-r--r--internal/lsp/cmd/test/highlight.go29
-rw-r--r--internal/lsp/cmd/test/implementation.go37
-rw-r--r--internal/lsp/cmd/test/imports.go29
-rw-r--r--internal/lsp/cmd/test/links.go30
-rw-r--r--internal/lsp/cmd/test/prepare_rename.go46
-rw-r--r--internal/lsp/cmd/test/references.go49
-rw-r--r--internal/lsp/cmd/test/rename.go29
-rw-r--r--internal/lsp/cmd/test/semanticdriver.go34
-rw-r--r--internal/lsp/cmd/test/signature.go34
-rw-r--r--internal/lsp/cmd/test/suggested_fix.go35
-rw-r--r--internal/lsp/cmd/test/symbols.go23
-rw-r--r--internal/lsp/cmd/test/workspace_symbol.go53
-rw-r--r--internal/lsp/cmd/usage/api-json.hlp4
-rw-r--r--internal/lsp/cmd/usage/bug.hlp4
-rw-r--r--internal/lsp/cmd/usage/call_hierarchy.hlp10
-rw-r--r--internal/lsp/cmd/usage/check.hlp8
-rw-r--r--internal/lsp/cmd/usage/definition.hlp15
-rw-r--r--internal/lsp/cmd/usage/fix.hlp15
-rw-r--r--internal/lsp/cmd/usage/folding_ranges.hlp8
-rw-r--r--internal/lsp/cmd/usage/format.hlp18
-rw-r--r--internal/lsp/cmd/usage/highlight.hlp10
-rw-r--r--internal/lsp/cmd/usage/implementation.hlp10
-rw-r--r--internal/lsp/cmd/usage/imports.hlp14
-rw-r--r--internal/lsp/cmd/usage/inspect.hlp8
-rw-r--r--internal/lsp/cmd/usage/licenses.hlp4
-rw-r--r--internal/lsp/cmd/usage/links.hlp12
-rw-r--r--internal/lsp/cmd/usage/prepare_rename.hlp10
-rw-r--r--internal/lsp/cmd/usage/references.hlp14
-rw-r--r--internal/lsp/cmd/usage/remote.hlp8
-rw-r--r--internal/lsp/cmd/usage/rename.hlp18
-rw-r--r--internal/lsp/cmd/usage/semtok.hlp8
-rw-r--r--internal/lsp/cmd/usage/serve.hlp30
-rw-r--r--internal/lsp/cmd/usage/signature.hlp10
-rw-r--r--internal/lsp/cmd/usage/symbols.hlp7
-rw-r--r--internal/lsp/cmd/usage/usage.hlp77
-rw-r--r--internal/lsp/cmd/usage/version.hlp6
-rw-r--r--internal/lsp/cmd/usage/vulncheck.hlp9
-rw-r--r--internal/lsp/cmd/usage/workspace.hlp7
-rw-r--r--internal/lsp/cmd/usage/workspace_symbol.hlp13
-rw-r--r--internal/lsp/cmd/vulncheck.go79
-rw-r--r--internal/lsp/cmd/workspace.go77
-rw-r--r--internal/lsp/cmd/workspace_symbol.go85
-rw-r--r--internal/lsp/code_action.go455
-rw-r--r--internal/lsp/code_lens.go57
-rw-r--r--internal/lsp/command.go819
-rw-r--r--internal/lsp/command/command_gen.go473
-rw-r--r--internal/lsp/command/commandmeta/meta.go258
-rw-r--r--internal/lsp/command/gen/gen.go155
-rw-r--r--internal/lsp/command/generate.go25
-rw-r--r--internal/lsp/command/interface.go384
-rw-r--r--internal/lsp/command/interface_test.go31
-rw-r--r--internal/lsp/command/util.go65
-rw-r--r--internal/lsp/completion.go178
-rw-r--r--internal/lsp/completion_test.go154
-rw-r--r--internal/lsp/debounce.go71
-rw-r--r--internal/lsp/debounce_test.go81
-rw-r--r--internal/lsp/debug/buildinfo_go1.12.go29
-rw-r--r--internal/lsp/debug/buildinfo_go1.18.go19
-rw-r--r--internal/lsp/debug/info.go265
-rw-r--r--internal/lsp/debug/info_test.go47
-rw-r--r--internal/lsp/debug/log/log.go43
-rw-r--r--internal/lsp/debug/metrics.go58
-rw-r--r--internal/lsp/debug/rpc.go239
-rw-r--r--internal/lsp/debug/serve.go954
-rw-r--r--internal/lsp/debug/tag/tag.go63
-rw-r--r--internal/lsp/debug/trace.go226
-rw-r--r--internal/lsp/definition.go67
-rw-r--r--internal/lsp/diagnostics.go649
-rw-r--r--internal/lsp/diff/diff.go159
-rw-r--r--internal/lsp/diff/diff_test.go80
-rw-r--r--internal/lsp/diff/difftest/difftest.go243
-rw-r--r--internal/lsp/diff/difftest/difftest_test.go84
-rw-r--r--internal/lsp/diff/myers/diff.go205
-rw-r--r--internal/lsp/diff/myers/diff_test.go16
-rw-r--r--internal/lsp/diff/unified.go210
-rw-r--r--internal/lsp/fake/client.go128
-rw-r--r--internal/lsp/fake/doc.go19
-rw-r--r--internal/lsp/fake/edit.go157
-rw-r--r--internal/lsp/fake/edit_test.go97
-rw-r--r--internal/lsp/fake/editor.go1258
-rw-r--r--internal/lsp/fake/editor_test.go82
-rw-r--r--internal/lsp/fake/proxy.go35
-rw-r--r--internal/lsp/fake/sandbox.go273
-rw-r--r--internal/lsp/fake/workdir.go365
-rw-r--r--internal/lsp/fake/workdir_test.go192
-rw-r--r--internal/lsp/fake/workdir_windows.go20
-rw-r--r--internal/lsp/folding_range.go44
-rw-r--r--internal/lsp/format.go31
-rw-r--r--internal/lsp/fuzzy/input.go183
-rw-r--r--internal/lsp/fuzzy/input_test.go141
-rw-r--r--internal/lsp/fuzzy/matcher.go407
-rw-r--r--internal/lsp/fuzzy/matcher_test.go295
-rw-r--r--internal/lsp/fuzzy/symbol.go236
-rw-r--r--internal/lsp/fuzzy/symbol_test.go79
-rw-r--r--internal/lsp/general.go510
-rw-r--r--internal/lsp/helper/README.md33
-rw-r--r--internal/lsp/helper/helper.go258
-rw-r--r--internal/lsp/highlight.go45
-rw-r--r--internal/lsp/hover.go34
-rw-r--r--internal/lsp/implementation.go21
-rw-r--r--internal/lsp/link.go280
-rw-r--r--internal/lsp/lsp_test.go1319
-rw-r--r--internal/lsp/lsppos/lsppos.go89
-rw-r--r--internal/lsp/lsprpc/autostart_default.go39
-rw-r--r--internal/lsp/lsprpc/autostart_posix.go99
-rw-r--r--internal/lsp/lsprpc/binder.go143
-rw-r--r--internal/lsp/lsprpc/binder_test.go154
-rw-r--r--internal/lsp/lsprpc/commandinterceptor.go47
-rw-r--r--internal/lsp/lsprpc/commandinterceptor_test.go42
-rw-r--r--internal/lsp/lsprpc/dialer.go115
-rw-r--r--internal/lsp/lsprpc/goenv.go89
-rw-r--r--internal/lsp/lsprpc/goenv_test.go68
-rw-r--r--internal/lsp/lsprpc/lsprpc.go530
-rw-r--r--internal/lsp/lsprpc/lsprpc_test.go349
-rw-r--r--internal/lsp/lsprpc/middleware.go145
-rw-r--r--internal/lsp/lsprpc/middleware_test.go93
-rw-r--r--internal/lsp/mod/code_lens.go153
-rw-r--r--internal/lsp/mod/diagnostics.go116
-rw-r--r--internal/lsp/mod/format.go33
-rw-r--r--internal/lsp/mod/hover.go163
-rw-r--r--internal/lsp/mod/mod_test.go60
-rw-r--r--internal/lsp/mod/testdata/unchanged/go.mod1
-rw-r--r--internal/lsp/mod/testdata/unchanged/main.go6
-rw-r--r--internal/lsp/progress/progress.go269
-rw-r--r--internal/lsp/progress/progress_test.go161
-rw-r--r--internal/lsp/protocol/codeactionkind.go11
-rw-r--r--internal/lsp/protocol/context.go43
-rw-r--r--internal/lsp/protocol/doc.go16
-rw-r--r--internal/lsp/protocol/enums.go246
-rw-r--r--internal/lsp/protocol/log.go136
-rw-r--r--internal/lsp/protocol/protocol.go277
-rw-r--r--internal/lsp/protocol/span.go151
-rw-r--r--internal/lsp/protocol/tsclient.go205
-rw-r--r--internal/lsp/protocol/tsprotocol.go6750
-rw-r--r--internal/lsp/protocol/tsserver.go1143
-rw-r--r--internal/lsp/protocol/typescript/README.md55
-rw-r--r--internal/lsp/protocol/typescript/code.ts1448
-rw-r--r--internal/lsp/protocol/typescript/tsconfig.json29
-rw-r--r--internal/lsp/protocol/typescript/util.ts254
-rw-r--r--internal/lsp/references.go40
-rw-r--r--internal/lsp/regtest/doc.go36
-rw-r--r--internal/lsp/regtest/env.go318
-rw-r--r--internal/lsp/regtest/env_test.go68
-rw-r--r--internal/lsp/regtest/expectation.go668
-rw-r--r--internal/lsp/regtest/regtest.go148
-rw-r--r--internal/lsp/regtest/runner.go533
-rw-r--r--internal/lsp/regtest/wrappers.go446
-rw-r--r--internal/lsp/rename.go56
-rwxr-xr-xinternal/lsp/reset_golden.sh6
-rw-r--r--internal/lsp/semantic.go958
-rw-r--r--internal/lsp/server.go168
-rw-r--r--internal/lsp/server_gen.go321
-rw-r--r--internal/lsp/signature_help.go31
-rw-r--r--internal/lsp/snippet/snippet_builder.go104
-rw-r--r--internal/lsp/snippet/snippet_builder_test.go62
-rw-r--r--internal/lsp/source/add_import.go26
-rwxr-xr-xinternal/lsp/source/api_json.go972
-rw-r--r--internal/lsp/source/call_hierarchy.go310
-rw-r--r--internal/lsp/source/code_lens.go244
-rw-r--r--internal/lsp/source/comment.go381
-rw-r--r--internal/lsp/source/comment_test.go368
-rw-r--r--internal/lsp/source/completion/builtin.go147
-rw-r--r--internal/lsp/source/completion/completion.go2967
-rw-r--r--internal/lsp/source/completion/deep_completion.go362
-rw-r--r--internal/lsp/source/completion/deep_completion_test.go33
-rw-r--r--internal/lsp/source/completion/definition.go127
-rw-r--r--internal/lsp/source/completion/format.go340
-rw-r--r--internal/lsp/source/completion/fuzz.go142
-rw-r--r--internal/lsp/source/completion/keywords.go154
-rw-r--r--internal/lsp/source/completion/labels.go112
-rw-r--r--internal/lsp/source/completion/literal.go440
-rw-r--r--internal/lsp/source/completion/package.go364
-rw-r--r--internal/lsp/source/completion/package_test.go77
-rw-r--r--internal/lsp/source/completion/postfix_snippets.go461
-rw-r--r--internal/lsp/source/completion/printf.go172
-rw-r--r--internal/lsp/source/completion/printf_test.go72
-rw-r--r--internal/lsp/source/completion/snippet.go115
-rw-r--r--internal/lsp/source/completion/statements.go360
-rw-r--r--internal/lsp/source/completion/util.go326
-rw-r--r--internal/lsp/source/completion/util_test.go28
-rw-r--r--internal/lsp/source/diagnostics.go84
-rw-r--r--internal/lsp/source/extract.go1307
-rw-r--r--internal/lsp/source/fix.go140
-rw-r--r--internal/lsp/source/folding_range.go185
-rw-r--r--internal/lsp/source/format.go387
-rw-r--r--internal/lsp/source/format_test.go91
-rw-r--r--internal/lsp/source/gc_annotations.go214
-rw-r--r--internal/lsp/source/highlight.go509
-rw-r--r--internal/lsp/source/hover.go870
-rw-r--r--internal/lsp/source/identifier.go576
-rw-r--r--internal/lsp/source/identifier_test.go128
-rw-r--r--internal/lsp/source/implementation.go446
-rw-r--r--internal/lsp/source/known_packages.go118
-rw-r--r--internal/lsp/source/offset_test.go71
-rw-r--r--internal/lsp/source/options.go1449
-rw-r--r--internal/lsp/source/options_test.go183
-rw-r--r--internal/lsp/source/references.go200
-rw-r--r--internal/lsp/source/rename.go371
-rw-r--r--internal/lsp/source/rename_check.go936
-rw-r--r--internal/lsp/source/signature_help.go181
-rw-r--r--internal/lsp/source/source_test.go984
-rw-r--r--internal/lsp/source/stub.go330
-rw-r--r--internal/lsp/source/symbols.go266
-rw-r--r--internal/lsp/source/types_format.go459
-rw-r--r--internal/lsp/source/util.go586
-rw-r--r--internal/lsp/source/view.go696
-rw-r--r--internal/lsp/source/workspace_symbol.go593
-rw-r--r--internal/lsp/source/workspace_symbol_test.go46
-rw-r--r--internal/lsp/symbols.go57
-rw-r--r--internal/lsp/template/completion.go301
-rw-r--r--internal/lsp/template/completion_test.go102
-rw-r--r--internal/lsp/template/highlight.go96
-rw-r--r--internal/lsp/template/implementations.go189
-rw-r--r--internal/lsp/template/parse.go520
-rw-r--r--internal/lsp/template/parse_test.go238
-rw-r--r--internal/lsp/template/symbols.go230
-rw-r--r--internal/lsp/testdata/%percent/perc%ent.go1
-rw-r--r--internal/lsp/testdata/addimport/addimport.go.golden7
-rw-r--r--internal/lsp/testdata/addimport/addimport.go.in3
-rw-r--r--internal/lsp/testdata/address/address.go78
-rw-r--r--internal/lsp/testdata/analyzer/bad_test.go18
-rw-r--r--internal/lsp/testdata/anon/anon.go.in23
-rw-r--r--internal/lsp/testdata/append/append.go38
-rw-r--r--internal/lsp/testdata/append/append2.go.in5
-rw-r--r--internal/lsp/testdata/arraytype/array_type.go.in48
-rw-r--r--internal/lsp/testdata/assign/assign.go.in26
-rw-r--r--internal/lsp/testdata/assign/internal/secret/secret.go3
-rw-r--r--internal/lsp/testdata/bad/bad0.go23
-rw-r--r--internal/lsp/testdata/bad/bad1.go33
-rw-r--r--internal/lsp/testdata/badstmt/badstmt.go.in26
-rw-r--r--internal/lsp/testdata/badstmt/badstmt_2.go.in9
-rw-r--r--internal/lsp/testdata/badstmt/badstmt_3.go.in9
-rw-r--r--internal/lsp/testdata/badstmt/badstmt_4.go.in11
-rw-r--r--internal/lsp/testdata/bar/bar.go.in47
-rw-r--r--internal/lsp/testdata/basiclit/basiclit.go56
-rw-r--r--internal/lsp/testdata/baz/baz.go.in33
-rw-r--r--internal/lsp/testdata/builtins/builtin_args.go62
-rw-r--r--internal/lsp/testdata/builtins/builtin_types.go11
-rw-r--r--internal/lsp/testdata/builtins/builtins.go46
-rw-r--r--internal/lsp/testdata/builtins/constants.go19
-rw-r--r--internal/lsp/testdata/callhierarchy/callhierarchy.go70
-rw-r--r--internal/lsp/testdata/callhierarchy/incoming/incoming.go12
-rw-r--r--internal/lsp/testdata/callhierarchy/outgoing/outgoing.go9
-rw-r--r--internal/lsp/testdata/casesensitive/casesensitive.go16
-rw-r--r--internal/lsp/testdata/cast/cast.go.in11
-rw-r--r--internal/lsp/testdata/cgo/declarecgo.go27
-rw-r--r--internal/lsp/testdata/cgo/declarecgo.go.golden30
-rw-r--r--internal/lsp/testdata/cgo/declarecgo_nocgo.go6
-rw-r--r--internal/lsp/testdata/cgoimport/usecgo.go.golden30
-rw-r--r--internal/lsp/testdata/cgoimport/usecgo.go.in9
-rw-r--r--internal/lsp/testdata/channel/channel.go25
-rw-r--r--internal/lsp/testdata/codelens/codelens_test.go16
-rw-r--r--internal/lsp/testdata/comment_completion/comment_completion.go.in70
-rw-r--r--internal/lsp/testdata/complit/complit.go.in90
-rw-r--r--internal/lsp/testdata/constant/constant.go14
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_for.go9
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_for_init.go9
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_for_init_cond.go9
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_for_init_cond_post.go9
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_if.go9
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_if_eof.go8
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_if_init.go9
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_if_init_cond.go9
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_multiline_if.go10
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_selector_1.go7
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_selector_2.go8
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_switch_init.go9
-rw-r--r--internal/lsp/testdata/danglingstmt/dangling_switch_init_tag.go9
-rw-r--r--internal/lsp/testdata/deep/deep.go135
-rw-r--r--internal/lsp/testdata/errors/errors.go10
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_args_returns.go11
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_args_returns.go.golden37
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_basic.go8
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_basic.go.golden30
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_basic_comment.go8
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_basic_comment.go.golden17
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_issue_44813.go13
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_issue_44813.go.golden21
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_redefine.go11
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_redefine.go.golden18
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_basic.go10
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_basic.go.golden21
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_basic_nonnested.go10
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_basic_nonnested.go.golden17
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_complex.go17
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_complex.go.golden28
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_complex_nonnested.go17
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_complex_nonnested.go.golden24
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_func_lit.go13
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_func_lit.go.golden24
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_func_lit_nonnested.go13
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_func_lit_nonnested.go.golden20
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_init.go12
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_init.go.golden23
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_init_nonnested.go12
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_return_init_nonnested.go.golden19
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_scope.go10
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_scope.go.golden16
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_smart_initialization.go9
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_smart_initialization.go.golden17
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_smart_return.go11
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_smart_return.go.golden19
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_unnecessary_param.go14
-rw-r--r--internal/lsp/testdata/extract/extract_function/extract_unnecessary_param.go.golden22
-rw-r--r--internal/lsp/testdata/extract/extract_method/extract_basic.go24
-rw-r--r--internal/lsp/testdata/extract/extract_method/extract_basic.go.golden728
-rw-r--r--internal/lsp/testdata/extract/extract_variable/extract_basic_lit.go6
-rw-r--r--internal/lsp/testdata/extract/extract_variable/extract_basic_lit.go.golden18
-rw-r--r--internal/lsp/testdata/extract/extract_variable/extract_func_call.go9
-rw-r--r--internal/lsp/testdata/extract/extract_variable/extract_func_call.go.golden36
-rw-r--r--internal/lsp/testdata/extract/extract_variable/extract_scope.go13
-rw-r--r--internal/lsp/testdata/extract/extract_variable/extract_scope.go.golden32
-rw-r--r--internal/lsp/testdata/fieldlist/field_list.go27
-rw-r--r--internal/lsp/testdata/fillstruct/a.go27
-rw-r--r--internal/lsp/testdata/fillstruct/a.go.golden126
-rw-r--r--internal/lsp/testdata/fillstruct/a2.go29
-rw-r--r--internal/lsp/testdata/fillstruct/a2.go.golden139
-rw-r--r--internal/lsp/testdata/fillstruct/a3.go42
-rw-r--r--internal/lsp/testdata/fillstruct/a3.go.golden243
-rw-r--r--internal/lsp/testdata/fillstruct/a4.go39
-rw-r--r--internal/lsp/testdata/fillstruct/a4.go.golden174
-rw-r--r--internal/lsp/testdata/fillstruct/data/a.go6
-rw-r--r--internal/lsp/testdata/fillstruct/fill_struct.go26
-rw-r--r--internal/lsp/testdata/fillstruct/fill_struct.go.golden124
-rw-r--r--internal/lsp/testdata/fillstruct/fill_struct_anon.go14
-rw-r--r--internal/lsp/testdata/fillstruct/fill_struct_anon.go.golden20
-rw-r--r--internal/lsp/testdata/fillstruct/fill_struct_nested.go15
-rw-r--r--internal/lsp/testdata/fillstruct/fill_struct_nested.go.golden19
-rw-r--r--internal/lsp/testdata/fillstruct/fill_struct_package.go12
-rw-r--r--internal/lsp/testdata/fillstruct/fill_struct_package.go.golden36
-rw-r--r--internal/lsp/testdata/fillstruct/fill_struct_partial.go24
-rw-r--r--internal/lsp/testdata/fillstruct/fill_struct_partial.go.golden52
-rw-r--r--internal/lsp/testdata/fillstruct/fill_struct_spaces.go9
-rw-r--r--internal/lsp/testdata/fillstruct/fill_struct_spaces.go.golden13
-rw-r--r--internal/lsp/testdata/folding/a.go75
-rw-r--r--internal/lsp/testdata/folding/a.go.golden759
-rw-r--r--internal/lsp/testdata/folding/bad.go.golden91
-rw-r--r--internal/lsp/testdata/folding/bad.go.in18
-rw-r--r--internal/lsp/testdata/foo/foo.go30
-rw-r--r--internal/lsp/testdata/format/bad_format.go.golden21
-rw-r--r--internal/lsp/testdata/format/bad_format.go.in22
-rw-r--r--internal/lsp/testdata/format/good_format.go9
-rw-r--r--internal/lsp/testdata/format/good_format.go.golden11
-rw-r--r--internal/lsp/testdata/format/newline_format.go.golden4
-rw-r--r--internal/lsp/testdata/format/newline_format.go.in2
-rw-r--r--internal/lsp/testdata/format/one_line.go.golden3
-rw-r--r--internal/lsp/testdata/format/one_line.go.in1
-rw-r--r--internal/lsp/testdata/func_rank/func_rank.go.in70
-rw-r--r--internal/lsp/testdata/funcsig/func_sig.go9
-rw-r--r--internal/lsp/testdata/funcvalue/func_value.go27
-rw-r--r--internal/lsp/testdata/fuzzymatch/fuzzymatch.go48
-rw-r--r--internal/lsp/testdata/generate/generate.go4
-rw-r--r--internal/lsp/testdata/generated/generated.go7
-rw-r--r--internal/lsp/testdata/generated/generator.go5
-rw-r--r--internal/lsp/testdata/godef/a/a.go105
-rw-r--r--internal/lsp/testdata/godef/a/a.go.golden190
-rw-r--r--internal/lsp/testdata/godef/a/a_test.go8
-rw-r--r--internal/lsp/testdata/godef/a/a_test.go.golden26
-rw-r--r--internal/lsp/testdata/godef/a/a_x_test.go9
-rw-r--r--internal/lsp/testdata/godef/a/a_x_test.go.golden26
-rw-r--r--internal/lsp/testdata/godef/a/d.go43
-rw-r--r--internal/lsp/testdata/godef/a/d.go.golden164
-rw-r--r--internal/lsp/testdata/godef/a/f.go15
-rw-r--r--internal/lsp/testdata/godef/a/f.go.golden34
-rw-r--r--internal/lsp/testdata/godef/a/g.go6
-rw-r--r--internal/lsp/testdata/godef/a/g.go.golden6
-rw-r--r--internal/lsp/testdata/godef/a/h.go147
-rw-r--r--internal/lsp/testdata/godef/a/h.go.golden136
-rw-r--r--internal/lsp/testdata/godef/a/random.go31
-rw-r--r--internal/lsp/testdata/godef/a/random.go.golden112
-rw-r--r--internal/lsp/testdata/godef/b/b.go57
-rw-r--r--internal/lsp/testdata/godef/b/b.go.golden454
-rw-r--r--internal/lsp/testdata/godef/b/c.go8
-rw-r--r--internal/lsp/testdata/godef/b/c.go.golden74
-rw-r--r--internal/lsp/testdata/godef/b/c.go.saved7
-rw-r--r--internal/lsp/testdata/godef/b/e.go31
-rw-r--r--internal/lsp/testdata/godef/b/e.go.golden144
-rw-r--r--internal/lsp/testdata/godef/b/h.go10
-rw-r--r--internal/lsp/testdata/godef/b/h.go.golden12
-rw-r--r--internal/lsp/testdata/godef/broken/unclosedIf.go.golden30
-rw-r--r--internal/lsp/testdata/godef/broken/unclosedIf.go.in9
-rw-r--r--internal/lsp/testdata/godef/hover_generics/hover.go15
-rw-r--r--internal/lsp/testdata/godef/hover_generics/hover.go.golden45
-rw-r--r--internal/lsp/testdata/godef/infer_generics/inferred.go12
-rw-r--r--internal/lsp/testdata/godef/infer_generics/inferred.go.golden20
-rw-r--r--internal/lsp/testdata/good/good0.go6
-rw-r--r--internal/lsp/testdata/good/good1.go20
-rw-r--r--internal/lsp/testdata/highlights/highlights.go151
-rw-r--r--internal/lsp/testdata/implementation/implementation.go31
-rw-r--r--internal/lsp/testdata/implementation/other/other.go27
-rw-r--r--internal/lsp/testdata/implementation/other/other_test.go10
-rw-r--r--internal/lsp/testdata/importedcomplit/imported_complit.go.in42
-rw-r--r--internal/lsp/testdata/imports/add_import.go.golden13
-rw-r--r--internal/lsp/testdata/imports/add_import.go.in10
-rw-r--r--internal/lsp/testdata/imports/good_imports.go.golden9
-rw-r--r--internal/lsp/testdata/imports/good_imports.go.in7
-rw-r--r--internal/lsp/testdata/imports/issue35458.go.golden20
-rw-r--r--internal/lsp/testdata/imports/issue35458.go.in23
-rw-r--r--internal/lsp/testdata/imports/multiple_blocks.go.golden9
-rw-r--r--internal/lsp/testdata/imports/multiple_blocks.go.in9
-rw-r--r--internal/lsp/testdata/imports/needs_imports.go.golden13
-rw-r--r--internal/lsp/testdata/imports/needs_imports.go.in6
-rw-r--r--internal/lsp/testdata/imports/remove_import.go.golden11
-rw-r--r--internal/lsp/testdata/imports/remove_import.go.in10
-rw-r--r--internal/lsp/testdata/imports/remove_imports.go.golden6
-rw-r--r--internal/lsp/testdata/imports/remove_imports.go.in9
-rw-r--r--internal/lsp/testdata/imports/two_lines.go.golden4
-rw-r--r--internal/lsp/testdata/imports/two_lines.go.in2
-rw-r--r--internal/lsp/testdata/index/index.go25
-rw-r--r--internal/lsp/testdata/interfacerank/interface_rank.go23
-rw-r--r--internal/lsp/testdata/keywords/accidental_keywords.go.in31
-rw-r--r--internal/lsp/testdata/keywords/empty_select.go7
-rw-r--r--internal/lsp/testdata/keywords/empty_switch.go11
-rw-r--r--internal/lsp/testdata/keywords/keywords.go100
-rw-r--r--internal/lsp/testdata/labels/labels.go49
-rw-r--r--internal/lsp/testdata/links/links.go26
-rw-r--r--internal/lsp/testdata/maps/maps.go.in18
-rw-r--r--internal/lsp/testdata/missingfunction/channels.go9
-rw-r--r--internal/lsp/testdata/missingfunction/channels.go.golden15
-rw-r--r--internal/lsp/testdata/missingfunction/consecutive_params.go6
-rw-r--r--internal/lsp/testdata/missingfunction/consecutive_params.go.golden12
-rw-r--r--internal/lsp/testdata/missingfunction/error_param.go6
-rw-r--r--internal/lsp/testdata/missingfunction/error_param.go.golden12
-rw-r--r--internal/lsp/testdata/missingfunction/literals.go7
-rw-r--r--internal/lsp/testdata/missingfunction/literals.go.golden29
-rw-r--r--internal/lsp/testdata/missingfunction/operation.go7
-rw-r--r--internal/lsp/testdata/missingfunction/operation.go.golden29
-rw-r--r--internal/lsp/testdata/missingfunction/selector.go6
-rw-r--r--internal/lsp/testdata/missingfunction/selector.go.golden12
-rw-r--r--internal/lsp/testdata/missingfunction/slice.go5
-rw-r--r--internal/lsp/testdata/missingfunction/slice.go.golden11
-rw-r--r--internal/lsp/testdata/missingfunction/tuple.go9
-rw-r--r--internal/lsp/testdata/missingfunction/tuple.go.golden15
-rw-r--r--internal/lsp/testdata/missingfunction/unique_params.go7
-rw-r--r--internal/lsp/testdata/missingfunction/unique_params.go.golden30
-rw-r--r--internal/lsp/testdata/multireturn/multi_return.go.in48
-rw-r--r--internal/lsp/testdata/nested_complit/nested_complit.go.in14
-rw-r--r--internal/lsp/testdata/nodisk/empty1
-rw-r--r--internal/lsp/testdata/nodisk/nodisk.overlay.go9
-rw-r--r--internal/lsp/testdata/noparse/noparse.go.in11
-rw-r--r--internal/lsp/testdata/noparse_format/noparse_format.go.golden2
-rw-r--r--internal/lsp/testdata/noparse_format/noparse_format.go.in9
-rw-r--r--internal/lsp/testdata/noparse_format/parse_format.go.golden7
-rw-r--r--internal/lsp/testdata/noparse_format/parse_format.go.in5
-rw-r--r--internal/lsp/testdata/printf/printf.go33
-rw-r--r--internal/lsp/testdata/rank/assign_rank.go.in19
-rw-r--r--internal/lsp/testdata/rank/binexpr_rank.go.in8
-rw-r--r--internal/lsp/testdata/rank/boolexpr_rank.go11
-rw-r--r--internal/lsp/testdata/rank/convert_rank.go.in54
-rw-r--r--internal/lsp/testdata/rank/struct/struct_rank.go11
-rw-r--r--internal/lsp/testdata/rank/switch_rank.go.in29
-rw-r--r--internal/lsp/testdata/rank/type_assert_rank.go.in8
-rw-r--r--internal/lsp/testdata/rank/type_switch_rank.go.in31
-rw-r--r--internal/lsp/testdata/references/another/another.go13
-rw-r--r--internal/lsp/testdata/references/interfaces/interfaces.go34
-rw-r--r--internal/lsp/testdata/references/other/other.go19
-rw-r--r--internal/lsp/testdata/references/refs.go38
-rw-r--r--internal/lsp/testdata/references/refs_test.go10
-rw-r--r--internal/lsp/testdata/rename/a/random.go.golden616
-rw-r--r--internal/lsp/testdata/rename/a/random.go.in42
-rw-r--r--internal/lsp/testdata/rename/b/b.go20
-rw-r--r--internal/lsp/testdata/rename/b/b.go.golden78
-rw-r--r--internal/lsp/testdata/rename/bad/bad.go.golden2
-rw-r--r--internal/lsp/testdata/rename/bad/bad.go.in8
-rw-r--r--internal/lsp/testdata/rename/bad/bad_test.go.in1
-rw-r--r--internal/lsp/testdata/rename/c/c.go7
-rw-r--r--internal/lsp/testdata/rename/c/c.go.golden32
-rw-r--r--internal/lsp/testdata/rename/c/c2.go4
-rw-r--r--internal/lsp/testdata/rename/c/c2.go.golden5
-rw-r--r--internal/lsp/testdata/rename/crosspkg/another/another.go13
-rw-r--r--internal/lsp/testdata/rename/crosspkg/another/another.go.golden15
-rw-r--r--internal/lsp/testdata/rename/crosspkg/crosspkg.go7
-rw-r--r--internal/lsp/testdata/rename/crosspkg/crosspkg.go.golden40
-rw-r--r--internal/lsp/testdata/rename/crosspkg/other/other.go8
-rw-r--r--internal/lsp/testdata/rename/crosspkg/other/other.go.golden20
-rw-r--r--internal/lsp/testdata/rename/generics/embedded.go10
-rw-r--r--internal/lsp/testdata/rename/generics/embedded.go.golden12
-rw-r--r--internal/lsp/testdata/rename/generics/generics.go25
-rw-r--r--internal/lsp/testdata/rename/generics/generics.go.golden108
-rw-r--r--internal/lsp/testdata/rename/generics/unions.go10
-rw-r--r--internal/lsp/testdata/rename/generics/unions.go.golden24
-rw-r--r--internal/lsp/testdata/rename/issue39614/issue39614.go.golden10
-rw-r--r--internal/lsp/testdata/rename/issue39614/issue39614.go.in8
-rw-r--r--internal/lsp/testdata/rename/issue42134/1.go8
-rw-r--r--internal/lsp/testdata/rename/issue42134/1.go.golden10
-rw-r--r--internal/lsp/testdata/rename/issue42134/2.go12
-rw-r--r--internal/lsp/testdata/rename/issue42134/2.go.golden14
-rw-r--r--internal/lsp/testdata/rename/issue42134/3.go11
-rw-r--r--internal/lsp/testdata/rename/issue42134/3.go.golden13
-rw-r--r--internal/lsp/testdata/rename/issue42134/4.go8
-rw-r--r--internal/lsp/testdata/rename/issue42134/4.go.golden10
-rw-r--r--internal/lsp/testdata/rename/issue43616/issue43616.go.golden13
-rw-r--r--internal/lsp/testdata/rename/issue43616/issue43616.go.in7
-rw-r--r--internal/lsp/testdata/rename/shadow/shadow.go20
-rw-r--r--internal/lsp/testdata/rename/shadow/shadow.go.golden48
-rw-r--r--internal/lsp/testdata/rename/testy/testy.go7
-rw-r--r--internal/lsp/testdata/rename/testy/testy.go.golden18
-rw-r--r--internal/lsp/testdata/rename/testy/testy_test.go8
-rw-r--r--internal/lsp/testdata/rename/testy/testy_test.go.golden30
-rw-r--r--internal/lsp/testdata/selector/selector.go.in66
-rw-r--r--internal/lsp/testdata/semantic/README.md2
-rw-r--r--internal/lsp/testdata/semantic/a.go81
-rw-r--r--internal/lsp/testdata/semantic/a.go.golden83
-rw-r--r--internal/lsp/testdata/semantic/b.go34
-rw-r--r--internal/lsp/testdata/semantic/b.go.golden36
-rw-r--r--internal/lsp/testdata/semantic/semantic_test.go13
-rw-r--r--internal/lsp/testdata/signature/signature.go85
-rw-r--r--internal/lsp/testdata/signature/signature.go.golden65
-rw-r--r--internal/lsp/testdata/signature/signature2.go.golden3
-rw-r--r--internal/lsp/testdata/signature/signature2.go.in5
-rw-r--r--internal/lsp/testdata/signature/signature3.go.golden3
-rw-r--r--internal/lsp/testdata/signature/signature3.go.in5
-rw-r--r--internal/lsp/testdata/signature/signature_test.go13
-rw-r--r--internal/lsp/testdata/signature/signature_test.go.golden30
-rw-r--r--internal/lsp/testdata/snippets/func_snippets118.go.in19
-rw-r--r--internal/lsp/testdata/snippets/literal.go22
-rw-r--r--internal/lsp/testdata/snippets/literal.go.golden6
-rw-r--r--internal/lsp/testdata/snippets/literal_snippets.go.in233
-rw-r--r--internal/lsp/testdata/snippets/literal_snippets118.go.in14
-rw-r--r--internal/lsp/testdata/snippets/postfix.go42
-rw-r--r--internal/lsp/testdata/snippets/snippets.go.golden3
-rw-r--r--internal/lsp/testdata/snippets/snippets.go.in61
-rw-r--r--internal/lsp/testdata/statements/append.go42
-rw-r--r--internal/lsp/testdata/statements/if_err_check_return.go27
-rw-r--r--internal/lsp/testdata/statements/if_err_check_return_2.go12
-rw-r--r--internal/lsp/testdata/statements/if_err_check_test.go20
-rw-r--r--internal/lsp/testdata/stub/other/other.go10
-rw-r--r--internal/lsp/testdata/stub/stub_add_selector.go12
-rw-r--r--internal/lsp/testdata/stub/stub_add_selector.go.golden19
-rw-r--r--internal/lsp/testdata/stub/stub_assign.go10
-rw-r--r--internal/lsp/testdata/stub/stub_assign.go.golden17
-rw-r--r--internal/lsp/testdata/stub/stub_assign_multivars.go11
-rw-r--r--internal/lsp/testdata/stub/stub_assign_multivars.go.golden18
-rw-r--r--internal/lsp/testdata/stub/stub_embedded.go15
-rw-r--r--internal/lsp/testdata/stub/stub_embedded.go.golden37
-rw-r--r--internal/lsp/testdata/stub/stub_err.go7
-rw-r--r--internal/lsp/testdata/stub/stub_err.go.golden14
-rw-r--r--internal/lsp/testdata/stub/stub_function_return.go11
-rw-r--r--internal/lsp/testdata/stub/stub_function_return.go.golden18
-rw-r--r--internal/lsp/testdata/stub/stub_generic_receiver.go15
-rw-r--r--internal/lsp/testdata/stub/stub_generic_receiver.go.golden22
-rw-r--r--internal/lsp/testdata/stub/stub_ignored_imports.go18
-rw-r--r--internal/lsp/testdata/stub/stub_ignored_imports.go.golden26
-rw-r--r--internal/lsp/testdata/stub/stub_multi_var.go11
-rw-r--r--internal/lsp/testdata/stub/stub_multi_var.go.golden18
-rw-r--r--internal/lsp/testdata/stub/stub_pointer.go9
-rw-r--r--internal/lsp/testdata/stub/stub_pointer.go.golden16
-rw-r--r--internal/lsp/testdata/stub/stub_renamed_import.go11
-rw-r--r--internal/lsp/testdata/stub/stub_renamed_import.go.golden18
-rw-r--r--internal/lsp/testdata/stub/stub_renamed_import_iface.go13
-rw-r--r--internal/lsp/testdata/stub/stub_renamed_import_iface.go.golden22
-rw-r--r--internal/lsp/testdata/stub/stub_stdlib.go9
-rw-r--r--internal/lsp/testdata/stub/stub_stdlib.go.golden16
-rw-r--r--internal/lsp/testdata/suggestedfix/has_suggested_fix.go11
-rw-r--r--internal/lsp/testdata/suggestedfix/has_suggested_fix.go.golden13
-rw-r--r--internal/lsp/testdata/summary.txt.golden30
-rw-r--r--internal/lsp/testdata/summary_go1.18.txt.golden30
-rw-r--r--internal/lsp/testdata/symbols/main.go64
-rw-r--r--internal/lsp/testdata/symbols/main.go.golden31
-rw-r--r--internal/lsp/testdata/testy/testy.go5
-rw-r--r--internal/lsp/testdata/testy/testy_test.go18
-rw-r--r--internal/lsp/testdata/testy/testy_test.go.golden3
-rw-r--r--internal/lsp/testdata/typdef/typdef.go65
-rw-r--r--internal/lsp/testdata/typeassert/type_assert.go24
-rw-r--r--internal/lsp/testdata/typeerrors/noresultvalues.go5
-rw-r--r--internal/lsp/testdata/typeerrors/noresultvalues.go.golden14
-rw-r--r--internal/lsp/testdata/typemods/type_mods.go21
-rw-r--r--internal/lsp/testdata/typeparams/type_params.go33
-rw-r--r--internal/lsp/testdata/types/types.go18
-rw-r--r--internal/lsp/testdata/undeclared/var.go14
-rw-r--r--internal/lsp/testdata/undeclared/var.go.golden51
-rw-r--r--internal/lsp/testdata/unimported/export_test.go3
-rw-r--r--internal/lsp/testdata/unimported/unimported.go.in20
-rw-r--r--internal/lsp/testdata/unimported/unimported_cand_type.go16
-rw-r--r--internal/lsp/testdata/unimported/x_test.go9
-rw-r--r--internal/lsp/testdata/unresolved/unresolved.go.in6
-rw-r--r--internal/lsp/testdata/unsafe/unsafe.go13
-rw-r--r--internal/lsp/testdata/variadic/variadic.go.in38
-rw-r--r--internal/lsp/testdata/variadic/variadic_intf.go21
-rw-r--r--internal/lsp/testdata/workspacesymbol/a/a.go9
-rw-r--r--internal/lsp/testdata/workspacesymbol/a/a.go.golden5
-rw-r--r--internal/lsp/testdata/workspacesymbol/a/a_test.go3
-rw-r--r--internal/lsp/testdata/workspacesymbol/a/a_test.go.golden3
-rw-r--r--internal/lsp/testdata/workspacesymbol/a/a_x_test.go3
-rw-r--r--internal/lsp/testdata/workspacesymbol/a/a_x_test.go.golden3
-rw-r--r--internal/lsp/testdata/workspacesymbol/b/b.go7
-rw-r--r--internal/lsp/testdata/workspacesymbol/b/b.go.golden5
-rw-r--r--internal/lsp/testdata/workspacesymbol/issue44806.go10
-rw-r--r--internal/lsp/testdata/workspacesymbol/main.go47
-rw-r--r--internal/lsp/testdata/workspacesymbol/p/p.go3
-rw-r--r--internal/lsp/testdata/workspacesymbol/query.go29
-rw-r--r--internal/lsp/testdata/workspacesymbol/query.go.golden83
-rw-r--r--internal/lsp/tests/README.md66
-rw-r--r--internal/lsp/tests/normalizer.go129
-rw-r--r--internal/lsp/tests/tests.go1458
-rw-r--r--internal/lsp/tests/util.go580
-rw-r--r--internal/lsp/text_synchronization.go382
-rw-r--r--internal/lsp/work/completion.go159
-rw-r--r--internal/lsp/work/diagnostics.go93
-rw-r--r--internal/lsp/work/format.go31
-rw-r--r--internal/lsp/work/hover.go94
-rw-r--r--internal/lsp/workspace.go108
-rw-r--r--internal/lsp/workspace_symbol.go23
732 files changed, 0 insertions, 90511 deletions
diff --git a/internal/lsp/README.md b/internal/lsp/README.md
deleted file mode 100644
index 34a142cbb..000000000
--- a/internal/lsp/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# lsp
-
-internal/lsp provides much of the Language Server Protocol (lsp) implementation
-for gopls.
-
-Documentation for users and contributors can be found in the
-[`gopls/doc`](../../gopls/doc) directory.
diff --git a/internal/lsp/analysis/fillreturns/fillreturns.go b/internal/lsp/analysis/fillreturns/fillreturns.go
deleted file mode 100644
index 4607f37c0..000000000
--- a/internal/lsp/analysis/fillreturns/fillreturns.go
+++ /dev/null
@@ -1,276 +0,0 @@
-// 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 defines an Analyzer that will attempt to
-// automatically fill in a return statement that has missing
-// values with zero value elements.
-package fillreturns
-
-import (
- "bytes"
- "fmt"
- "go/ast"
- "go/format"
- "go/types"
- "regexp"
- "strings"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/typeparams"
-)
-
-const Doc = `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.
-`
-
-var Analyzer = &analysis.Analyzer{
- Name: "fillreturns",
- Doc: Doc,
- Requires: []*analysis.Analyzer{},
- Run: run,
- RunDespiteErrors: true,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- info := pass.TypesInfo
- if info == nil {
- return nil, fmt.Errorf("nil TypeInfo")
- }
-
- errors := analysisinternal.GetTypeErrors(pass)
-outer:
- for _, typeErr := range errors {
- // 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.
- 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 {
- 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 := typeparams.ForFuncType(enclosingFunc); 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 exits,
- // fill in the missing value with the appropriate "zero" value.
- var retTyps []types.Type
- for _, ret := range enclosingFunc.Results.List {
- retTyps = append(retTyps, info.TypeOf(ret.Type))
- }
- matches :=
- analysisinternal.FindMatchingIdents(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 !matchingTypes(info.TypeOf(val), 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 {
- idents, ok := matches[retTyp]
- if !ok {
- return nil, fmt.Errorf("invalid return type: %v", retTyp)
- }
- // Find the identifer whose name is most similar to the return type.
- // If we do not find any identifer that matches the pattern,
- // generate a zero value.
- value := analysisinternal.FindBestMatch(retTyp.String(), idents)
- if value == nil {
- value = analysisinternal.ZeroValue(
- pass.Fset, file, pass.Pkg, retTyp)
- }
- if value == nil {
- return nil, nil
- }
- fixed[i] = value
- }
- }
-
- // 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/internal/lsp/analysis/fillreturns/fillreturns_test.go b/internal/lsp/analysis/fillreturns/fillreturns_test.go
deleted file mode 100644
index 7ef0d4679..000000000
--- a/internal/lsp/analysis/fillreturns/fillreturns_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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/internal/lsp/analysis/fillreturns"
- "golang.org/x/tools/internal/typeparams"
-)
-
-func Test(t *testing.T) {
- testdata := analysistest.TestData()
- tests := []string{"a"}
- if typeparams.Enabled {
- tests = append(tests, "typeparams")
- }
- analysistest.RunWithSuggestedFixes(t, testdata, fillreturns.Analyzer, tests...)
-}
diff --git a/internal/lsp/analysis/fillreturns/testdata/src/a/a.go b/internal/lsp/analysis/fillreturns/testdata/src/a/a.go
deleted file mode 100644
index 7ab0ff167..000000000
--- a/internal/lsp/analysis/fillreturns/testdata/src/a/a.go
+++ /dev/null
@@ -1,139 +0,0 @@
-// 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/internal/lsp/analysis/fillreturns/testdata/src/a/a.go.golden b/internal/lsp/analysis/fillreturns/testdata/src/a/a.go.golden
deleted file mode 100644
index f007a5f37..000000000
--- a/internal/lsp/analysis/fillreturns/testdata/src/a/a.go.golden
+++ /dev/null
@@ -1,139 +0,0 @@
-// 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/internal/lsp/analysis/fillreturns/testdata/src/a/typeparams/a.go b/internal/lsp/analysis/fillreturns/testdata/src/a/typeparams/a.go
deleted file mode 100644
index 8454bd2ce..000000000
--- a/internal/lsp/analysis/fillreturns/testdata/src/a/typeparams/a.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package fillreturns
-
-func hello[T any]() int {
- return
-}
diff --git a/internal/lsp/analysis/fillreturns/testdata/src/a/typeparams/a.go.golden b/internal/lsp/analysis/fillreturns/testdata/src/a/typeparams/a.go.golden
deleted file mode 100644
index 8454bd2ce..000000000
--- a/internal/lsp/analysis/fillreturns/testdata/src/a/typeparams/a.go.golden
+++ /dev/null
@@ -1,5 +0,0 @@
-package fillreturns
-
-func hello[T any]() int {
- return
-}
diff --git a/internal/lsp/analysis/fillstruct/fillstruct.go b/internal/lsp/analysis/fillstruct/fillstruct.go
deleted file mode 100644
index a4dd8ccb8..000000000
--- a/internal/lsp/analysis/fillstruct/fillstruct.go
+++ /dev/null
@@ -1,495 +0,0 @@
-// 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.
-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/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/typeparams"
-)
-
-const Doc = `note incomplete struct initializations
-
-This analyzer provides diagnostics for any struct literals that do not have
-any fields initialized. Because the suggested fix for this analysis is
-expensive to compute, callers should compute it separately, using the
-SuggestedFix function below.
-`
-
-var Analyzer = &analysis.Analyzer{
- Name: "fillstruct",
- Doc: Doc,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
- Run: run,
- RunDespiteErrors: true,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)}
- inspect.Preorder(nodeFilter, func(n ast.Node) {
- info := pass.TypesInfo
- if info == nil {
- return
- }
- expr := n.(*ast.CompositeLit)
-
- var file *ast.File
- for _, f := range pass.Files {
- if f.Pos() <= expr.Pos() && expr.Pos() <= f.End() {
- file = f
- break
- }
- }
- if file == nil {
- return
- }
-
- typ := info.TypeOf(expr)
- if typ == nil {
- return
- }
-
- // Ignore types that have type parameters for now.
- // TODO: support type params.
- if typ, ok := typ.(*types.Named); ok {
- if tparams := typeparams.ForNamed(typ); tparams != nil && tparams.Len() > 0 {
- return
- }
- }
-
- // Find reference to the type declaration of the struct being initialized.
- for {
- p, ok := typ.Underlying().(*types.Pointer)
- if !ok {
- break
- }
- typ = p.Elem()
- }
- typ = typ.Underlying()
-
- obj, ok := typ.(*types.Struct)
- if !ok {
- return
- }
- fieldCount := obj.NumFields()
-
- // Skip any struct that is already populated or that has no fields.
- if fieldCount == 0 || fieldCount == len(expr.Elts) {
- return
- }
-
- var fillable bool
- var fillableFields []string
- for i := 0; i < fieldCount; i++ {
- field := obj.Field(i)
- // Ignore fields that are not accessible in the current package.
- if field.Pkg() != nil && field.Pkg() != pass.Pkg && !field.Exported() {
- continue
- }
- // Ignore structs containing fields that have type parameters for now.
- // TODO: support type params.
- if typ, ok := field.Type().(*types.Named); ok {
- if tparams := typeparams.ForNamed(typ); tparams != nil && tparams.Len() > 0 {
- return
- }
- }
- if _, ok := field.Type().(*typeparams.TypeParam); ok {
- return
- }
- fillable = true
- fillableFields = append(fillableFields, fmt.Sprintf("%s: %s", field.Name(), field.Type().String()))
- }
- if !fillable {
- return
- }
- var name string
- switch typ := expr.Type.(type) {
- case *ast.Ident:
- name = typ.Name
- case *ast.SelectorExpr:
- name = fmt.Sprintf("%s.%s", typ.X, typ.Sel.Name)
- default:
- totalFields := len(fillableFields)
- 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, ", "))
- }
- pass.Report(analysis.Diagnostic{
- Message: fmt.Sprintf("Fill %s", name),
- Pos: expr.Pos(),
- End: expr.End(),
- })
- })
- return nil, nil
-}
-
-func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
- pos := rng.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, fmt.Errorf("no enclosing ast.Node")
- }
- var expr *ast.CompositeLit
- for _, n := range path {
- if node, ok := n.(*ast.CompositeLit); ok {
- expr = node
- break
- }
- }
-
- if info == nil {
- return nil, fmt.Errorf("nil types.Info")
- }
- typ := info.TypeOf(expr)
- if typ == nil {
- return nil, fmt.Errorf("no composite literal")
- }
-
- // Find reference to the type declaration of the struct being initialized.
- for {
- p, ok := typ.Underlying().(*types.Pointer)
- if !ok {
- break
- }
- typ = p.Elem()
- }
- typ = typ.Underlying()
-
- obj, ok := typ.(*types.Struct)
- if !ok {
- return nil, fmt.Errorf("unexpected type %v (%T), expected *types.Struct", typ, typ)
- }
- fieldCount := obj.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)
- prefilledTypes := map[string]ast.Expr{}
- for _, e := range expr.Elts {
- if kv, ok := e.(*ast.KeyValueExpr); ok {
- if key, ok := kv.Key.(*ast.Ident); ok {
- prefilledTypes[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.
- fakeFset := token.NewFileSet()
- tok := fakeFset.AddFile("", -1, fieldCount+2)
-
- line := 2 // account for 1-based lines and the left brace
- var elts []ast.Expr
- var fieldTyps []types.Type
- for i := 0; i < fieldCount; i++ {
- field := obj.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.FindMatchingIdents(fieldTyps, file, rng.Start, info, pkg)
- for i, fieldTyp := range fieldTyps {
- if fieldTyp == nil {
- continue
- }
-
- 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: obj.Field(i).Name(),
- },
- Colon: pos,
- }
- if expr, ok := prefilledTypes[obj.Field(i).Name()]; ok {
- kv.Value = expr
- } else {
- idents, ok := matches[fieldTyp]
- if !ok {
- return nil, fmt.Errorf("invalid struct field type: %v", fieldTyp)
- }
-
- // Find the identifer whose name is most similar to the name of the field's key.
- // If we do not find any identifer that matches the pattern, generate a new value.
- // NOTE: We currently match on the name of the field key rather than the field type.
- value := analysisinternal.FindBestMatch(obj.Field(i).Name(), idents)
- if value == nil {
- value = populateValue(fset, file, pkg, fieldTyp)
- }
- if value == nil {
- return nil, nil
- }
-
- kv.Value = value
- }
- 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, 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 := fset.Position(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, fmt.Errorf("failed to run first format on:\n%s\ngot err: %v", cl.Type, err)
- }
- sug := indent(formatBuf.Bytes(), whitespace)
-
- if len(prefilledTypes) > 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 &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
-// expressions 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.
-func populateValue(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
- under := typ
- if n, ok := typ.(*types.Named); ok {
- under = n.Underlying()
- }
- switch u := under.(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: `""`}
- default:
- panic("unknown basic type")
- }
- case *types.Map:
- k := analysisinternal.TypeExpr(fset, f, pkg, u.Key())
- v := analysisinternal.TypeExpr(fset, 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(fset, f, pkg, u.Elem())
- if s == nil {
- return nil
- }
- return &ast.CompositeLit{
- Type: &ast.ArrayType{
- Elt: s,
- },
- }
- case *types.Array:
- a := analysisinternal.TypeExpr(fset, 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(fset, 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(fset, 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(fset, 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(fset, 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 u.Elem().(type) {
- case *types.Basic:
- return &ast.CallExpr{
- Fun: &ast.Ident{
- Name: "new",
- },
- Args: []ast.Expr{
- &ast.Ident{
- Name: u.Elem().String(),
- },
- },
- }
- default:
- return &ast.UnaryExpr{
- Op: token.AND,
- X: populateValue(fset, f, pkg, u.Elem()),
- }
- }
- case *types.Interface:
- return ast.NewIdent("nil")
- }
- return nil
-}
diff --git a/internal/lsp/analysis/fillstruct/fillstruct_test.go b/internal/lsp/analysis/fillstruct/fillstruct_test.go
deleted file mode 100644
index 51a516cdf..000000000
--- a/internal/lsp/analysis/fillstruct/fillstruct_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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 (
- "testing"
-
- "golang.org/x/tools/go/analysis/analysistest"
- "golang.org/x/tools/internal/lsp/analysis/fillstruct"
- "golang.org/x/tools/internal/typeparams"
-)
-
-func Test(t *testing.T) {
- testdata := analysistest.TestData()
- tests := []string{"a"}
- if typeparams.Enabled {
- tests = append(tests, "typeparams")
- }
- analysistest.Run(t, testdata, fillstruct.Analyzer, tests...)
-}
diff --git a/internal/lsp/analysis/fillstruct/testdata/src/a/a.go b/internal/lsp/analysis/fillstruct/testdata/src/a/a.go
deleted file mode 100644
index f69fe8339..000000000
--- a/internal/lsp/analysis/fillstruct/testdata/src/a/a.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// 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"
-)
-
-type emptyStruct struct{}
-
-var _ = emptyStruct{}
-
-type basicStruct struct {
- foo int
-}
-
-var _ = basicStruct{} // want ""
-
-type twoArgStruct struct {
- foo int
- bar string
-}
-
-var _ = twoArgStruct{} // want ""
-
-var _ = twoArgStruct{ // want ""
- bar: "bar",
-}
-
-type nestedStruct struct {
- bar string
- basic basicStruct
-}
-
-var _ = nestedStruct{} // want ""
-
-var _ = data.B{} // want ""
-
-type typedStruct struct {
- m map[string]int
- s []int
- c chan int
- c1 <-chan int
- a [2]string
-}
-
-var _ = typedStruct{} // want ""
-
-type funStruct struct {
- fn func(i int) int
-}
-
-var _ = funStruct{} // want ""
-
-type funStructCompex struct {
- fn func(i int, s string) (string, int)
-}
-
-var _ = funStructCompex{} // want ""
-
-type funStructEmpty struct {
- fn func()
-}
-
-var _ = funStructEmpty{} // want ""
-
-type Foo struct {
- A int
-}
-
-type Bar struct {
- X *Foo
- Y *Foo
-}
-
-var _ = Bar{} // want ""
-
-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 ""
-
-type pointerBuiltinStruct struct {
- b *bool
- s *string
- i *int
-}
-
-var _ = pointerBuiltinStruct{} // want ""
-
-var _ = []ast.BasicLit{
- {}, // want ""
-}
-
-var _ = []ast.BasicLit{{}, // want ""
-}
diff --git a/internal/lsp/analysis/fillstruct/testdata/src/b/b.go b/internal/lsp/analysis/fillstruct/testdata/src/b/b.go
deleted file mode 100644
index a4b394605..000000000
--- a/internal/lsp/analysis/fillstruct/testdata/src/b/b.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package fillstruct
-
-type B struct {
- ExportedInt int
- unexportedInt int
-}
diff --git a/internal/lsp/analysis/fillstruct/testdata/src/typeparams/typeparams.go b/internal/lsp/analysis/fillstruct/testdata/src/typeparams/typeparams.go
deleted file mode 100644
index 90290613d..000000000
--- a/internal/lsp/analysis/fillstruct/testdata/src/typeparams/typeparams.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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]{}
-
-type fooType[T any] T
-
-type twoArgStruct[F, B any] struct {
- foo fooType[F]
- bar fooType[B]
-}
-
-var _ = twoArgStruct[string, int]{}
-
-var _ = twoArgStruct[int, string]{
- bar: "bar",
-}
-
-type nestedStruct struct {
- bar string
- basic basicStruct[int]
-}
-
-var _ = nestedStruct{}
-
-func _[T any]() {
- type S struct{ t T }
- x := S{}
- _ = x
-}
diff --git a/internal/lsp/analysis/infertypeargs/infertypeargs.go b/internal/lsp/analysis/infertypeargs/infertypeargs.go
deleted file mode 100644
index 119de50ce..000000000
--- a/internal/lsp/analysis/infertypeargs/infertypeargs.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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 defines an analyzer that checks for explicit function
-// arguments that could be inferred.
-package infertypeargs
-
-import (
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/inspect"
-)
-
-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,
-}
diff --git a/internal/lsp/analysis/infertypeargs/infertypeargs_test.go b/internal/lsp/analysis/infertypeargs/infertypeargs_test.go
deleted file mode 100644
index 2957f46e3..000000000
--- a/internal/lsp/analysis/infertypeargs/infertypeargs_test.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// 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/internal/lsp/analysis/infertypeargs"
- "golang.org/x/tools/internal/testenv"
- "golang.org/x/tools/internal/typeparams"
-)
-
-func Test(t *testing.T) {
- testenv.NeedsGo1Point(t, 13)
- if !typeparams.Enabled {
- t.Skip("type params are not enabled")
- }
- testdata := analysistest.TestData()
- analysistest.RunWithSuggestedFixes(t, testdata, infertypeargs.Analyzer, "a")
-}
diff --git a/internal/lsp/analysis/infertypeargs/run_go117.go b/internal/lsp/analysis/infertypeargs/run_go117.go
deleted file mode 100644
index bc5c29b51..000000000
--- a/internal/lsp/analysis/infertypeargs/run_go117.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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 !go1.18
-// +build !go1.18
-
-package infertypeargs
-
-import "golang.org/x/tools/go/analysis"
-
-// This analyzer only relates to go1.18+, and uses the types.CheckExpr API that
-// was added in Go 1.13.
-func run(pass *analysis.Pass) (interface{}, error) {
- return nil, nil
-}
diff --git a/internal/lsp/analysis/infertypeargs/run_go118.go b/internal/lsp/analysis/infertypeargs/run_go118.go
deleted file mode 100644
index 66457429a..000000000
--- a/internal/lsp/analysis/infertypeargs/run_go118.go
+++ /dev/null
@@ -1,111 +0,0 @@
-// 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 go1.18
-// +build go1.18
-
-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"
-)
-
-func run(pass *analysis.Pass) (interface{}, error) {
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
-
- 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
- }
-
- // Confirm that instantiation actually occurred at this ident.
- idata, ok := typeparams.GetInstances(pass.TypesInfo)[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 := new(types.Info)
- typeparams.InitInstanceInfo(info)
- if err := types.CheckExpr(pass.Fset, pass.Pkg, call.Pos(), newCall, info); err != nil {
- // Most likely inference failed.
- break
- }
- newIData := typeparams.GetInstances(info)[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 start, end token.Pos
- var edit analysis.TextEdit
- if required == 0 {
- start, end = lbrack, rbrack+1 // erase the entire index
- edit = analysis.TextEdit{Pos: start, End: end}
- } else {
- start = indices[required].Pos()
- end = rbrack
- // erase from end of last arg to include last comma & white-spaces
- edit = analysis.TextEdit{Pos: indices[required-1].End(), End: end}
- }
- pass.Report(analysis.Diagnostic{
- Pos: start,
- End: end,
- Message: "unnecessary type arguments",
- SuggestedFixes: []analysis.SuggestedFix{{
- Message: "simplify type arguments",
- TextEdits: []analysis.TextEdit{edit},
- }},
- })
- }
- })
-
- return nil, nil
-}
-
-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/internal/lsp/analysis/infertypeargs/testdata/src/a/basic.go b/internal/lsp/analysis/infertypeargs/testdata/src/a/basic.go
deleted file mode 100644
index 1c3d88ba1..000000000
--- a/internal/lsp/analysis/infertypeargs/testdata/src/a/basic.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// 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/internal/lsp/analysis/infertypeargs/testdata/src/a/basic.go.golden b/internal/lsp/analysis/infertypeargs/testdata/src/a/basic.go.golden
deleted file mode 100644
index 72348ff77..000000000
--- a/internal/lsp/analysis/infertypeargs/testdata/src/a/basic.go.golden
+++ /dev/null
@@ -1,20 +0,0 @@
-// 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/internal/lsp/analysis/infertypeargs/testdata/src/a/imported.go b/internal/lsp/analysis/infertypeargs/testdata/src/a/imported.go
deleted file mode 100644
index fc1f763df..000000000
--- a/internal/lsp/analysis/infertypeargs/testdata/src/a/imported.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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/internal/lsp/analysis/infertypeargs/testdata/src/a/imported.go.golden b/internal/lsp/analysis/infertypeargs/testdata/src/a/imported.go.golden
deleted file mode 100644
index 6099545bb..000000000
--- a/internal/lsp/analysis/infertypeargs/testdata/src/a/imported.go.golden
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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/internal/lsp/analysis/infertypeargs/testdata/src/a/imported/imported.go b/internal/lsp/analysis/infertypeargs/testdata/src/a/imported/imported.go
deleted file mode 100644
index f0610a8b4..000000000
--- a/internal/lsp/analysis/infertypeargs/testdata/src/a/imported/imported.go
+++ /dev/null
@@ -1,7 +0,0 @@
-// 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/internal/lsp/analysis/infertypeargs/testdata/src/a/notypechange.go b/internal/lsp/analysis/infertypeargs/testdata/src/a/notypechange.go
deleted file mode 100644
index c304f1d0d..000000000
--- a/internal/lsp/analysis/infertypeargs/testdata/src/a/notypechange.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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/internal/lsp/analysis/infertypeargs/testdata/src/a/notypechange.go.golden b/internal/lsp/analysis/infertypeargs/testdata/src/a/notypechange.go.golden
deleted file mode 100644
index 93c6f707c..000000000
--- a/internal/lsp/analysis/infertypeargs/testdata/src/a/notypechange.go.golden
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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/internal/lsp/analysis/nonewvars/nonewvars.go b/internal/lsp/analysis/nonewvars/nonewvars.go
deleted file mode 100644
index e7fa430cc..000000000
--- a/internal/lsp/analysis/nonewvars/nonewvars.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// 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"
- "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"
-)
-
-const Doc = `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
-`
-
-var Analyzer = &analysis.Analyzer{
- Name: string(analysisinternal.NoNewVars),
- Doc: Doc,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
- Run: run,
- RunDespiteErrors: true,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- errors := analysisinternal.GetTypeErrors(pass)
-
- 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 errors {
- 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/internal/lsp/analysis/nonewvars/nonewvars_test.go b/internal/lsp/analysis/nonewvars/nonewvars_test.go
deleted file mode 100644
index dc58ab0ff..000000000
--- a/internal/lsp/analysis/nonewvars/nonewvars_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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/internal/lsp/analysis/nonewvars"
- "golang.org/x/tools/internal/typeparams"
-)
-
-func Test(t *testing.T) {
- testdata := analysistest.TestData()
- tests := []string{"a"}
- if typeparams.Enabled {
- tests = append(tests, "typeparams")
- }
- analysistest.RunWithSuggestedFixes(t, testdata, nonewvars.Analyzer, tests...)
-}
diff --git a/internal/lsp/analysis/nonewvars/testdata/src/a/a.go b/internal/lsp/analysis/nonewvars/testdata/src/a/a.go
deleted file mode 100644
index 97d8fcde1..000000000
--- a/internal/lsp/analysis/nonewvars/testdata/src/a/a.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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/internal/lsp/analysis/nonewvars/testdata/src/a/a.go.golden b/internal/lsp/analysis/nonewvars/testdata/src/a/a.go.golden
deleted file mode 100644
index 17197e564..000000000
--- a/internal/lsp/analysis/nonewvars/testdata/src/a/a.go.golden
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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/internal/lsp/analysis/nonewvars/testdata/src/typeparams/a.go b/internal/lsp/analysis/nonewvars/testdata/src/typeparams/a.go
deleted file mode 100644
index b381c9c09..000000000
--- a/internal/lsp/analysis/nonewvars/testdata/src/typeparams/a.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package nonewvars
-
-func hello[T any]() int {
- var z T
- z := 1 // want "no new variables on left side of :="
-}
diff --git a/internal/lsp/analysis/nonewvars/testdata/src/typeparams/a.go.golden b/internal/lsp/analysis/nonewvars/testdata/src/typeparams/a.go.golden
deleted file mode 100644
index 3a5117301..000000000
--- a/internal/lsp/analysis/nonewvars/testdata/src/typeparams/a.go.golden
+++ /dev/null
@@ -1,6 +0,0 @@
-package nonewvars
-
-func hello[T any]() int {
- var z T
- z = 1 // want "no new variables on left side of :="
-}
diff --git a/internal/lsp/analysis/noresultvalues/noresultvalues.go b/internal/lsp/analysis/noresultvalues/noresultvalues.go
deleted file mode 100644
index b9f21f313..000000000
--- a/internal/lsp/analysis/noresultvalues/noresultvalues.go
+++ /dev/null
@@ -1,90 +0,0 @@
-// 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 defines an Analyzer that applies suggested fixes
-// to errors of the type "no result values expected".
-package noresultvalues
-
-import (
- "bytes"
- "go/ast"
- "go/format"
- "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/internal/analysisinternal"
-)
-
-const Doc = `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 }
-`
-
-var Analyzer = &analysis.Analyzer{
- Name: string(analysisinternal.NoResultValues),
- Doc: Doc,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
- Run: run,
- RunDespiteErrors: true,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- errors := analysisinternal.GetTypeErrors(pass)
-
- 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 errors {
- 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/internal/lsp/analysis/noresultvalues/noresultvalues_test.go b/internal/lsp/analysis/noresultvalues/noresultvalues_test.go
deleted file mode 100644
index 12198a1c1..000000000
--- a/internal/lsp/analysis/noresultvalues/noresultvalues_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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/internal/lsp/analysis/noresultvalues"
- "golang.org/x/tools/internal/typeparams"
-)
-
-func Test(t *testing.T) {
- testdata := analysistest.TestData()
- tests := []string{"a"}
- if typeparams.Enabled {
- tests = append(tests, "typeparams")
- }
- analysistest.RunWithSuggestedFixes(t, testdata, noresultvalues.Analyzer, tests...)
-}
diff --git a/internal/lsp/analysis/noresultvalues/testdata/src/a/a.go b/internal/lsp/analysis/noresultvalues/testdata/src/a/a.go
deleted file mode 100644
index 3daa7f7c7..000000000
--- a/internal/lsp/analysis/noresultvalues/testdata/src/a/a.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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/internal/lsp/analysis/noresultvalues/testdata/src/a/a.go.golden b/internal/lsp/analysis/noresultvalues/testdata/src/a/a.go.golden
deleted file mode 100644
index 5e93aa413..000000000
--- a/internal/lsp/analysis/noresultvalues/testdata/src/a/a.go.golden
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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/internal/lsp/analysis/noresultvalues/testdata/src/typeparams/a.go b/internal/lsp/analysis/noresultvalues/testdata/src/typeparams/a.go
deleted file mode 100644
index f8aa43665..000000000
--- a/internal/lsp/analysis/noresultvalues/testdata/src/typeparams/a.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package noresult
-
-func hello[T any]() {
- var z T
- return z // want `no result values expected|too many return values`
-}
diff --git a/internal/lsp/analysis/noresultvalues/testdata/src/typeparams/a.go.golden b/internal/lsp/analysis/noresultvalues/testdata/src/typeparams/a.go.golden
deleted file mode 100644
index 963e3f4e1..000000000
--- a/internal/lsp/analysis/noresultvalues/testdata/src/typeparams/a.go.golden
+++ /dev/null
@@ -1,6 +0,0 @@
-package noresult
-
-func hello[T any]() {
- var z T
- return // want `no result values expected|too many return values`
-}
diff --git a/internal/lsp/analysis/simplifycompositelit/simplifycompositelit.go b/internal/lsp/analysis/simplifycompositelit/simplifycompositelit.go
deleted file mode 100644
index c91fc7577..000000000
--- a/internal/lsp/analysis/simplifycompositelit/simplifycompositelit.go
+++ /dev/null
@@ -1,196 +0,0 @@
-// 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"
- "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"
-)
-
-const Doc = `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.`
-
-var Analyzer = &analysis.Analyzer{
- Name: "simplifycompositelit",
- Doc: Doc,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
- Run: run,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)}
- inspect.Preorder(nodeFilter, func(n ast.Node) {
- expr := n.(*ast.CompositeLit)
-
- outer := expr
- var keyType, eltType ast.Expr
- switch typ := outer.Type.(type) {
- case *ast.ArrayType:
- eltType = typ.Elt
- case *ast.MapType:
- keyType = typ.Key
- eltType = typ.Value
- }
-
- if eltType == nil {
- return
- }
- var ktyp reflect.Value
- if keyType != nil {
- ktyp = reflect.ValueOf(keyType)
- }
- typ := reflect.ValueOf(eltType)
- for _, x := range outer.Elts {
- // look at value of indexed/named elements
- if t, ok := x.(*ast.KeyValueExpr); ok {
- if keyType != nil {
- simplifyLiteral(pass, ktyp, keyType, t.Key)
- }
- x = t.Value
- }
- simplifyLiteral(pass, typ, eltType, x)
- }
- })
- return nil, nil
-}
-
-func simplifyLiteral(pass *analysis.Pass, typ reflect.Value, astType, x ast.Expr) {
- // if the element is a composite literal and its literal type
- // matches the outer literal's element type exactly, the inner
- // literal type may be omitted
- if inner, ok := x.(*ast.CompositeLit); ok && match(typ, reflect.ValueOf(inner.Type)) {
- var b bytes.Buffer
- printer.Fprint(&b, pass.Fset, inner.Type)
- createDiagnostic(pass, inner.Type.Pos(), inner.Type.End(), b.String())
- }
- // if the outer literal's element type is a pointer type *T
- // and the element is & of a composite literal of type T,
- // the inner &T may be omitted.
- if ptr, ok := astType.(*ast.StarExpr); ok {
- if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND {
- if inner, ok := addr.X.(*ast.CompositeLit); ok {
- if match(reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) {
- var b bytes.Buffer
- printer.Fprint(&b, pass.Fset, inner.Type)
- // Account for the & by subtracting 1 from typ.Pos().
- createDiagnostic(pass, inner.Type.Pos()-1, inner.Type.End(), "&"+b.String())
- }
- }
- }
- }
-}
-
-func createDiagnostic(pass *analysis.Pass, start, end token.Pos, typ string) {
- pass.Report(analysis.Diagnostic{
- Pos: start,
- End: end,
- Message: "redundant type from array, slice, or map composite literal",
- SuggestedFixes: []analysis.SuggestedFix{{
- Message: fmt.Sprintf("Remove '%s'", typ),
- TextEdits: []analysis.TextEdit{{
- Pos: start,
- End: end,
- NewText: []byte{},
- }},
- }},
- })
-}
-
-// match reports whether pattern matches val,
-// recording wildcard submatches in m.
-// If m == nil, match checks whether pattern == val.
-// from https://github.com/golang/go/blob/26154f31ad6c801d8bad5ef58df1e9263c6beec7/src/cmd/gofmt/rewrite.go#L160
-func match(pattern, val reflect.Value) bool {
- // Otherwise, pattern and val must match recursively.
- if !pattern.IsValid() || !val.IsValid() {
- return !pattern.IsValid() && !val.IsValid()
- }
- if pattern.Type() != val.Type() {
- return false
- }
-
- // Special cases.
- switch pattern.Type() {
- case identType:
- // For identifiers, only the names need to match
- // (and none of the other *ast.Object information).
- // This is a common case, handle it all here instead
- // of recursing down any further via reflection.
- p := pattern.Interface().(*ast.Ident)
- v := val.Interface().(*ast.Ident)
- return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name
- case objectPtrType, positionType:
- // object pointers and token positions always match
- return true
- case callExprType:
- // For calls, the Ellipsis fields (token.Position) must
- // match since that is how f(x) and f(x...) are different.
- // Check them here but fall through for the remaining fields.
- p := pattern.Interface().(*ast.CallExpr)
- v := val.Interface().(*ast.CallExpr)
- if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() {
- return false
- }
- }
-
- p := reflect.Indirect(pattern)
- v := reflect.Indirect(val)
- if !p.IsValid() || !v.IsValid() {
- return !p.IsValid() && !v.IsValid()
- }
-
- switch p.Kind() {
- case reflect.Slice:
- if p.Len() != v.Len() {
- return false
- }
- for i := 0; i < p.Len(); i++ {
- if !match(p.Index(i), v.Index(i)) {
- return false
- }
- }
- return true
-
- case reflect.Struct:
- for i := 0; i < p.NumField(); i++ {
- if !match(p.Field(i), v.Field(i)) {
- return false
- }
- }
- return true
-
- case reflect.Interface:
- return match(p.Elem(), v.Elem())
- }
-
- // Handle token integers, etc.
- return p.Interface() == v.Interface()
-}
-
-// Values/types for special cases.
-var (
- identType = reflect.TypeOf((*ast.Ident)(nil))
- objectPtrType = reflect.TypeOf((*ast.Object)(nil))
- positionType = reflect.TypeOf(token.NoPos)
- callExprType = reflect.TypeOf((*ast.CallExpr)(nil))
-)
diff --git a/internal/lsp/analysis/simplifycompositelit/simplifycompositelit_test.go b/internal/lsp/analysis/simplifycompositelit/simplifycompositelit_test.go
deleted file mode 100644
index e60f7d6b0..000000000
--- a/internal/lsp/analysis/simplifycompositelit/simplifycompositelit_test.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// 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/internal/lsp/analysis/simplifycompositelit"
-)
-
-func Test(t *testing.T) {
- testdata := analysistest.TestData()
- analysistest.RunWithSuggestedFixes(t, testdata, simplifycompositelit.Analyzer, "a")
-}
diff --git a/internal/lsp/analysis/simplifycompositelit/testdata/src/a/a.go b/internal/lsp/analysis/simplifycompositelit/testdata/src/a/a.go
deleted file mode 100644
index 14e0fa3ae..000000000
--- a/internal/lsp/analysis/simplifycompositelit/testdata/src/a/a.go
+++ /dev/null
@@ -1,234 +0,0 @@
-// 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/internal/lsp/analysis/simplifycompositelit/testdata/src/a/a.go.golden b/internal/lsp/analysis/simplifycompositelit/testdata/src/a/a.go.golden
deleted file mode 100644
index 6bfed45a5..000000000
--- a/internal/lsp/analysis/simplifycompositelit/testdata/src/a/a.go.golden
+++ /dev/null
@@ -1,234 +0,0 @@
-// 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/internal/lsp/analysis/simplifyrange/simplifyrange.go b/internal/lsp/analysis/simplifyrange/simplifyrange.go
deleted file mode 100644
index c9cb38798..000000000
--- a/internal/lsp/analysis/simplifyrange/simplifyrange.go
+++ /dev/null
@@ -1,116 +0,0 @@
-// 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 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
-package simplifyrange
-
-import (
- "bytes"
- "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"
-)
-
-const Doc = `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.`
-
-var Analyzer = &analysis.Analyzer{
- Name: "simplifyrange",
- Doc: Doc,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
- Run: run,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- nodeFilter := []ast.Node{
- (*ast.RangeStmt)(nil),
- }
- inspect.Preorder(nodeFilter, func(n ast.Node) {
- var copy *ast.RangeStmt
- if stmt, ok := n.(*ast.RangeStmt); ok {
- x := *stmt
- copy = &x
- }
- if copy == nil {
- return
- }
- 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/internal/lsp/analysis/simplifyrange/simplifyrange_test.go b/internal/lsp/analysis/simplifyrange/simplifyrange_test.go
deleted file mode 100644
index ecc7a9692..000000000
--- a/internal/lsp/analysis/simplifyrange/simplifyrange_test.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// 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 (
- "testing"
-
- "golang.org/x/tools/go/analysis/analysistest"
- "golang.org/x/tools/internal/lsp/analysis/simplifyrange"
-)
-
-func Test(t *testing.T) {
- testdata := analysistest.TestData()
- analysistest.RunWithSuggestedFixes(t, testdata, simplifyrange.Analyzer, "a")
-}
diff --git a/internal/lsp/analysis/simplifyrange/testdata/src/a/a.go b/internal/lsp/analysis/simplifyrange/testdata/src/a/a.go
deleted file mode 100644
index 49face1e9..000000000
--- a/internal/lsp/analysis/simplifyrange/testdata/src/a/a.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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/internal/lsp/analysis/simplifyrange/testdata/src/a/a.go.golden b/internal/lsp/analysis/simplifyrange/testdata/src/a/a.go.golden
deleted file mode 100644
index ec8490ab3..000000000
--- a/internal/lsp/analysis/simplifyrange/testdata/src/a/a.go.golden
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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/internal/lsp/analysis/simplifyslice/simplifyslice.go b/internal/lsp/analysis/simplifyslice/simplifyslice.go
deleted file mode 100644
index da1728e6f..000000000
--- a/internal/lsp/analysis/simplifyslice/simplifyslice.go
+++ /dev/null
@@ -1,94 +0,0 @@
-// 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 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
-package simplifyslice
-
-import (
- "bytes"
- "fmt"
- "go/ast"
- "go/printer"
-
- "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 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.`
-
-var Analyzer = &analysis.Analyzer{
- Name: "simplifyslice",
- Doc: Doc,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
- Run: run,
-}
-
-// 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) {
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- nodeFilter := []ast.Node{
- (*ast.SliceExpr)(nil),
- }
- inspect.Preorder(nodeFilter, func(n ast.Node) {
- 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/internal/lsp/analysis/simplifyslice/simplifyslice_test.go b/internal/lsp/analysis/simplifyslice/simplifyslice_test.go
deleted file mode 100644
index cff6267c6..000000000
--- a/internal/lsp/analysis/simplifyslice/simplifyslice_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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/internal/lsp/analysis/simplifyslice"
- "golang.org/x/tools/internal/typeparams"
-)
-
-func Test(t *testing.T) {
- testdata := analysistest.TestData()
- tests := []string{"a"}
- if typeparams.Enabled {
- tests = append(tests, "typeparams")
- }
- analysistest.RunWithSuggestedFixes(t, testdata, simplifyslice.Analyzer, tests...)
-}
diff --git a/internal/lsp/analysis/simplifyslice/testdata/src/a/a.go b/internal/lsp/analysis/simplifyslice/testdata/src/a/a.go
deleted file mode 100644
index 20792105d..000000000
--- a/internal/lsp/analysis/simplifyslice/testdata/src/a/a.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// 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/internal/lsp/analysis/simplifyslice/testdata/src/a/a.go.golden b/internal/lsp/analysis/simplifyslice/testdata/src/a/a.go.golden
deleted file mode 100644
index 45c791421..000000000
--- a/internal/lsp/analysis/simplifyslice/testdata/src/a/a.go.golden
+++ /dev/null
@@ -1,70 +0,0 @@
-// 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/internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go b/internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go
deleted file mode 100644
index 69db3100a..000000000
--- a/internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// 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 go1.18
-// +build go1.18
-
-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/internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden b/internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden
deleted file mode 100644
index 99ca9e447..000000000
--- a/internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden
+++ /dev/null
@@ -1,39 +0,0 @@
-// 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 go1.18
-// +build go1.18
-
-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/internal/lsp/analysis/stubmethods/stubmethods.go b/internal/lsp/analysis/stubmethods/stubmethods.go
deleted file mode 100644
index c2a4138fa..000000000
--- a/internal/lsp/analysis/stubmethods/stubmethods.go
+++ /dev/null
@@ -1,351 +0,0 @@
-// 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"
- "fmt"
- "go/ast"
- "go/format"
- "go/token"
- "go/types"
- "strconv"
- "strings"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/typesinternal"
-)
-
-const Doc = `stub methods analyzer
-
-This analyzer generates method stubs for concrete types
-in order to implement a target interface`
-
-var Analyzer = &analysis.Analyzer{
- Name: "stubmethods",
- Doc: Doc,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
- Run: run,
- RunDespiteErrors: true,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- for _, err := range analysisinternal.GetTypeErrors(pass) {
- ifaceErr := strings.Contains(err.Msg, "missing method") || strings.HasPrefix(err.Msg, "cannot convert")
- if !ifaceErr {
- continue
- }
- var file *ast.File
- for _, f := range pass.Files {
- if f.Pos() <= err.Pos && err.Pos < f.End() {
- file = f
- break
- }
- }
- if file == nil {
- continue
- }
- // Get the end position of the error.
- _, _, endPos, ok := typesinternal.ReadGo116ErrorData(err)
- if !ok {
- var buf bytes.Buffer
- if err := format.Node(&buf, pass.Fset, file); err != nil {
- continue
- }
- endPos = analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos)
- }
- path, _ := astutil.PathEnclosingInterval(file, err.Pos, endPos)
- si := GetStubInfo(pass.TypesInfo, path, err.Pos)
- if si == nil {
- continue
- }
- qf := RelativeToFiles(si.Concrete.Obj().Pkg(), file, nil, nil)
- pass.Report(analysis.Diagnostic{
- Pos: err.Pos,
- End: endPos,
- Message: fmt.Sprintf("Implement %s", types.TypeString(si.Interface.Type(), qf)),
- })
- }
- return nil, nil
-}
-
-// 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.
- Interface types.Object
- Concrete *types.Named
- Pointer bool
-}
-
-// GetStubInfo determines whether the "missing method error"
-// can be used to deduced what the concrete and interface types are.
-func GetStubInfo(ti *types.Info, path []ast.Node, pos token.Pos) *StubInfo {
- for _, n := range path {
- switch n := n.(type) {
- case *ast.ValueSpec:
- return fromValueSpec(ti, 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(ti, pos, path, n)
- return si
- case *ast.AssignStmt:
- return fromAssignStmt(ti, n, pos)
- }
- }
- return nil
-}
-
-// 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(ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.ReturnStmt) (*StubInfo, error) {
- returnIdx := -1
- for i, r := range rs.Results {
- if pos >= r.Pos() && pos <= r.End() {
- returnIdx = i
- }
- }
- if returnIdx == -1 {
- return nil, fmt.Errorf("pos %d not within return statement bounds: [%d-%d]", pos, rs.Pos(), rs.End())
- }
- concObj, pointer := concreteType(rs.Results[returnIdx], ti)
- if concObj == nil || concObj.Obj().Pkg() == nil {
- return nil, nil
- }
- ef := enclosingFunction(path, ti)
- if ef == nil {
- return nil, fmt.Errorf("could not find the enclosing function of the return statement")
- }
- iface := ifaceType(ef.Results.List[returnIdx].Type, ti)
- if iface == nil {
- return nil, nil
- }
- return &StubInfo{
- Concrete: concObj,
- Pointer: pointer,
- Interface: iface,
- }, nil
-}
-
-// fromValueSpec returns *StubInfo from a variable declaration such as
-// var x io.Writer = &T{}
-func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pos token.Pos) *StubInfo {
- var idx int
- for i, vs := range vs.Values {
- if pos >= vs.Pos() && pos <= vs.End() {
- idx = i
- break
- }
- }
-
- valueNode := vs.Values[idx]
- ifaceNode := vs.Type
- callExp, ok := valueNode.(*ast.CallExpr)
- // if the ValueSpec is `var _ = myInterface(...)`
- // as opposed to `var _ myInterface = ...`
- if ifaceNode == nil && ok && len(callExp.Args) == 1 {
- ifaceNode = callExp.Fun
- valueNode = callExp.Args[0]
- }
- concObj, pointer := concreteType(valueNode, ti)
- if concObj == nil || concObj.Obj().Pkg() == nil {
- return nil
- }
- ifaceObj := ifaceType(ifaceNode, ti)
- if ifaceObj == nil {
- return nil
- }
- return &StubInfo{
- Concrete: concObj,
- Interface: ifaceObj,
- Pointer: pointer,
- }
-}
-
-// fromAssignStmt returns *StubInfo from a variable re-assignment such as
-// var x io.Writer
-// x = &T{}
-func fromAssignStmt(ti *types.Info, as *ast.AssignStmt, pos token.Pos) *StubInfo {
- idx := -1
- var lhs, rhs ast.Expr
- // Given a re-assignment interface conversion error,
- // the compiler error shows up on the right hand side of the expression.
- // For example, x = &T{} where x is io.Writer highlights the error
- // under "&T{}" and not "x".
- for i, hs := range as.Rhs {
- if pos >= hs.Pos() && pos <= hs.End() {
- idx = i
- break
- }
- }
- if idx == -1 {
- return nil
- }
- // Technically, this should never happen as
- // we would get a "cannot assign N values to M variables"
- // before we get an interface conversion error. Nonetheless,
- // guard against out of range index errors.
- if idx >= len(as.Lhs) {
- return nil
- }
- lhs, rhs = as.Lhs[idx], as.Rhs[idx]
- ifaceObj := ifaceType(lhs, ti)
- if ifaceObj == nil {
- return nil
- }
- concType, pointer := concreteType(rhs, ti)
- if concType == nil || concType.Obj().Pkg() == nil {
- return nil
- }
- return &StubInfo{
- Concrete: concType,
- Interface: ifaceObj,
- Pointer: pointer,
- }
-}
-
-// RelativeToFiles returns a types.Qualifier that formats package names
-// according to the files where the concrete and interface types are defined.
-//
-// This is similar to types.RelativeTo except if a file imports the package with a different name,
-// then it will use it. And if the file does import the package but it is ignored,
-// then it will return the original name. It also prefers package names in ifaceFile in case
-// an import is missing from concFile but is present in ifaceFile.
-//
-// Additionally, if missingImport is not nil, the function will be called whenever the concFile
-// is presented with a package that is not imported. This is useful so that as types.TypeString is
-// formatting a function signature, it is identifying packages that will need to be imported when
-// stubbing an interface.
-func RelativeToFiles(concPkg *types.Package, concFile, ifaceFile *ast.File, missingImport func(name, path string)) types.Qualifier {
- return func(other *types.Package) string {
- if other == concPkg {
- return ""
- }
-
- // Check if the concrete file already has the given import,
- // if so return the default package name or the renamed import statement.
- for _, imp := range concFile.Imports {
- impPath, _ := strconv.Unquote(imp.Path.Value)
- isIgnored := imp.Name != nil && (imp.Name.Name == "." || imp.Name.Name == "_")
- if impPath == other.Path() && !isIgnored {
- importName := other.Name()
- if imp.Name != nil {
- importName = imp.Name.Name
- }
- return importName
- }
- }
-
- // If the concrete file does not have the import, check if the package
- // is renamed in the interface file and prefer that.
- var importName string
- if ifaceFile != nil {
- for _, imp := range ifaceFile.Imports {
- impPath, _ := strconv.Unquote(imp.Path.Value)
- isIgnored := imp.Name != nil && (imp.Name.Name == "." || imp.Name.Name == "_")
- if impPath == other.Path() && !isIgnored {
- if imp.Name != nil && imp.Name.Name != concPkg.Name() {
- importName = imp.Name.Name
- }
- break
- }
- }
- }
-
- if missingImport != nil {
- missingImport(importName, other.Path())
- }
-
- // Up until this point, importName must stay empty when calling missingImport,
- // otherwise we'd end up with `import time "time"` which doesn't look idiomatic.
- if importName == "" {
- importName = other.Name()
- }
- return importName
- }
-}
-
-// ifaceType will try to extract the types.Object that defines
-// the interface given the ast.Expr where the "missing method"
-// or "conversion" errors happen.
-func ifaceType(n ast.Expr, ti *types.Info) types.Object {
- tv, ok := ti.Types[n]
- if !ok {
- return nil
- }
- typ := tv.Type
- named, ok := typ.(*types.Named)
- if !ok {
- return nil
- }
- _, ok = named.Underlying().(*types.Interface)
- if !ok {
- 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(n ast.Expr, ti *types.Info) (*types.Named, bool) {
- tv, ok := ti.Types[n]
- if !ok {
- return nil, false
- }
- typ := tv.Type
- ptr, isPtr := typ.(*types.Pointer)
- if isPtr {
- typ = ptr.Elem()
- }
- named, ok := 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/internal/lsp/analysis/undeclaredname/testdata/src/a/a.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/a.go
deleted file mode 100644
index 81c732001..000000000
--- a/internal/lsp/analysis/undeclaredname/testdata/src/a/a.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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: y"
-
- if z == m { // want "undeclared name: m"
- z = 1
- }
-
- if z == 1 {
- z = 1
- } else if z == n+1 { // want "undeclared name: n"
- z = 1
- }
-
- switch z {
- case 10:
- z = 1
- case a: // want "undeclared name: a"
- z = 1
- }
- return z
-}
diff --git a/internal/lsp/analysis/undeclaredname/testdata/src/a/channels.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/channels.go
deleted file mode 100644
index ecf00ecfc..000000000
--- a/internal/lsp/analysis/undeclaredname/testdata/src/a/channels.go
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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: undefinedChannels"
-}
-
-func c() (<-chan string, chan string) {
- return make(<-chan string), make(chan string)
-}
diff --git a/internal/lsp/analysis/undeclaredname/testdata/src/a/consecutive_params.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/consecutive_params.go
deleted file mode 100644
index ab7b2ba5c..000000000
--- a/internal/lsp/analysis/undeclaredname/testdata/src/a/consecutive_params.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// 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: undefinedConsecutiveParams"
-}
diff --git a/internal/lsp/analysis/undeclaredname/testdata/src/a/error_param.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/error_param.go
deleted file mode 100644
index 341a9d2a4..000000000
--- a/internal/lsp/analysis/undeclaredname/testdata/src/a/error_param.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// 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: undefinedErrorParam"
-}
diff --git a/internal/lsp/analysis/undeclaredname/testdata/src/a/literals.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/literals.go
deleted file mode 100644
index ab82463d0..000000000
--- a/internal/lsp/analysis/undeclaredname/testdata/src/a/literals.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// 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: undefinedLiterals"
-}
diff --git a/internal/lsp/analysis/undeclaredname/testdata/src/a/operation.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/operation.go
deleted file mode 100644
index 9a543821e..000000000
--- a/internal/lsp/analysis/undeclaredname/testdata/src/a/operation.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// 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: undefinedOperation"
-}
diff --git a/internal/lsp/analysis/undeclaredname/testdata/src/a/selector.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/selector.go
deleted file mode 100644
index 9ed09a27f..000000000
--- a/internal/lsp/analysis/undeclaredname/testdata/src/a/selector.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// 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: undefinedSelector"
-}
diff --git a/internal/lsp/analysis/undeclaredname/testdata/src/a/slice.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/slice.go
deleted file mode 100644
index d741c68f6..000000000
--- a/internal/lsp/analysis/undeclaredname/testdata/src/a/slice.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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: undefinedSlice"
-}
diff --git a/internal/lsp/analysis/undeclaredname/testdata/src/a/tuple.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/tuple.go
deleted file mode 100644
index 3148e8f4d..000000000
--- a/internal/lsp/analysis/undeclaredname/testdata/src/a/tuple.go
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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: undefinedTuple"
-}
-
-func b() (string, error) {
- return "", nil
-}
diff --git a/internal/lsp/analysis/undeclaredname/testdata/src/a/unique_params.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/unique_params.go
deleted file mode 100644
index 98f77a43c..000000000
--- a/internal/lsp/analysis/undeclaredname/testdata/src/a/unique_params.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// 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: undefinedUniqueArguments"
-}
diff --git a/internal/lsp/analysis/undeclaredname/undeclared.go b/internal/lsp/analysis/undeclaredname/undeclared.go
deleted file mode 100644
index 22b552c37..000000000
--- a/internal/lsp/analysis/undeclaredname/undeclared.go
+++ /dev/null
@@ -1,340 +0,0 @@
-// 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 defines an Analyzer that applies suggested fixes
-// to errors of the type "undeclared name: %s".
-package undeclaredname
-
-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/internal/analysisinternal"
- "golang.org/x/tools/internal/span"
-)
-
-const Doc = `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!")
-}
-`
-
-var Analyzer = &analysis.Analyzer{
- Name: string(analysisinternal.UndeclaredName),
- Doc: Doc,
- Requires: []*analysis.Analyzer{},
- Run: run,
- RunDespiteErrors: true,
-}
-
-const undeclaredNamePrefix = "undeclared name: "
-
-func run(pass *analysis.Pass) (interface{}, error) {
- for _, err := range analysisinternal.GetTypeErrors(pass) {
- runForError(pass, err)
- }
- return nil, nil
-}
-
-func runForError(pass *analysis.Pass, err types.Error) {
- if !strings.HasPrefix(err.Msg, undeclaredNamePrefix) {
- return
- }
- name := strings.TrimPrefix(err.Msg, undeclaredNamePrefix)
- 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
- }
-
- // Get the path for the relevant range.
- 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
- }
-
- // 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
- }
- // 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
- }
- tok := pass.Fset.File(file.Pos())
- if tok == nil {
- return
- }
- offset := pass.Fset.Position(err.Pos).Offset
- end := tok.Pos(offset + len(name))
- pass.Report(analysis.Diagnostic{
- Pos: err.Pos,
- End: end,
- Message: err.Msg,
- })
-}
-
-func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
- pos := rng.Start // don't use the end
- path, _ := astutil.PathEnclosingInterval(file, pos, pos)
- if len(path) < 2 {
- return nil, fmt.Errorf("no expression found")
- }
- ident, ok := path[0].(*ast.Ident)
- if !ok {
- return nil, fmt.Errorf("no identifier found")
- }
-
- // Check for a possible call expression, in which case we should add a
- // new function declaration.
- if len(path) > 1 {
- if _, ok := path[1].(*ast.CallExpr); ok {
- return newFunctionDeclaration(path, file, pkg, info, fset)
- }
- }
-
- // Get the place to insert the new statement.
- insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
- if insertBeforeStmt == nil {
- return nil, fmt.Errorf("could not locate insertion point")
- }
-
- insertBefore := fset.Position(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 &analysis.SuggestedFix{
- Message: fmt.Sprintf("Create variable \"%s\"", 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) (*analysis.SuggestedFix, error) {
- if len(path) < 3 {
- return nil, fmt.Errorf("unexpected set of enclosing nodes: %v", path)
- }
- ident, ok := path[0].(*ast.Ident)
- if !ok {
- return nil, fmt.Errorf("no name for function declaration %v (%T)", path[0], path[0])
- }
- call, ok := path[1].(*ast.CallExpr)
- if !ok {
- return 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, 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, 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(fset, file, pkg, paramTypes[i]),
- })
- }
-
- decl := &ast.FuncDecl{
- Name: ast.NewIdent(ident.Name),
- Type: &ast.FuncType{
- Params: params,
- // TODO(rstambler): Also handle result parameters here.
- },
- 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, err
- }
- return &analysis.SuggestedFix{
- Message: fmt.Sprintf("Create function \"%s\"", 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 := 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)
-}
diff --git a/internal/lsp/analysis/undeclaredname/undeclared_test.go b/internal/lsp/analysis/undeclaredname/undeclared_test.go
deleted file mode 100644
index b71543937..000000000
--- a/internal/lsp/analysis/undeclaredname/undeclared_test.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// 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/internal/lsp/analysis/undeclaredname"
-)
-
-func Test(t *testing.T) {
- testdata := analysistest.TestData()
- analysistest.Run(t, testdata, undeclaredname.Analyzer, "a")
-}
diff --git a/internal/lsp/analysis/unusedparams/testdata/src/a/a.go b/internal/lsp/analysis/unusedparams/testdata/src/a/a.go
deleted file mode 100644
index 23e4122c4..000000000
--- a/internal/lsp/analysis/unusedparams/testdata/src/a/a.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// 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 "potentially unused parameter: 'i2'"
- i3 += i1
- _ = func(z int) int { // want "potentially unused parameter: 'z'"
- _ = 1
- return 1
- }
- return i3
-}
-
-func b(c bytes.Buffer) { // want "potentially unused parameter: 'c'"
- _ = 1
-}
-
-func z(h http.ResponseWriter, _ *http.Request) { // want "potentially unused parameter: 'h'"
- fmt.Println("Before")
-}
-
-func l(h http.Handler) http.Handler {
- return http.HandlerFunc(z)
-}
-
-func mult(a, b int) int { // want "potentially unused parameter: 'b'"
- a += 1
- return a
-}
-
-func y(a int) {
- panic("yo")
-}
diff --git a/internal/lsp/analysis/unusedparams/testdata/src/a/a.go.golden b/internal/lsp/analysis/unusedparams/testdata/src/a/a.go.golden
deleted file mode 100644
index e28a6bdea..000000000
--- a/internal/lsp/analysis/unusedparams/testdata/src/a/a.go.golden
+++ /dev/null
@@ -1,55 +0,0 @@
-// 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 "potentially unused parameter: 'i2'"
- i3 += i1
- _ = func(_ int) int { // want "potentially unused parameter: 'z'"
- _ = 1
- return 1
- }
- return i3
-}
-
-func b(_ bytes.Buffer) { // want "potentially unused parameter: 'c'"
- _ = 1
-}
-
-func z(_ http.ResponseWriter, _ *http.Request) { // want "potentially unused parameter: 'h'"
- fmt.Println("Before")
-}
-
-func l(h http.Handler) http.Handler {
- return http.HandlerFunc(z)
-}
-
-func mult(a, _ int) int { // want "potentially unused parameter: 'b'"
- a += 1
- return a
-}
-
-func y(a int) {
- panic("yo")
-}
diff --git a/internal/lsp/analysis/unusedparams/testdata/src/typeparams/typeparams.go b/internal/lsp/analysis/unusedparams/testdata/src/typeparams/typeparams.go
deleted file mode 100644
index 93af2681b..000000000
--- a/internal/lsp/analysis/unusedparams/testdata/src/typeparams/typeparams.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// 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 "potentially unused parameter: 'i2'"
- i3 += i1
- _ = func(z int) int { // want "potentially unused parameter: 'z'"
- _ = 1
- return 1
- }
- return i3
-}
-
-func b[T any](c bytes.Buffer) { // want "potentially unused parameter: 'c'"
- _ = 1
-}
-
-func z[T http.ResponseWriter](h T, _ *http.Request) { // want "potentially unused parameter: 'h'"
- fmt.Println("Before")
-}
-
-func l(h http.Handler) http.Handler {
- return http.HandlerFunc(z[http.ResponseWriter])
-}
-
-func mult(a, b int) int { // want "potentially unused parameter: 'b'"
- a += 1
- return a
-}
-
-func y[T any](a T) {
- panic("yo")
-}
diff --git a/internal/lsp/analysis/unusedparams/testdata/src/typeparams/typeparams.go.golden b/internal/lsp/analysis/unusedparams/testdata/src/typeparams/typeparams.go.golden
deleted file mode 100644
index c86bf289a..000000000
--- a/internal/lsp/analysis/unusedparams/testdata/src/typeparams/typeparams.go.golden
+++ /dev/null
@@ -1,55 +0,0 @@
-// 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 "potentially unused parameter: 'i2'"
- i3 += i1
- _ = func(_ int) int { // want "potentially unused parameter: 'z'"
- _ = 1
- return 1
- }
- return i3
-}
-
-func b[T any](_ bytes.Buffer) { // want "potentially unused parameter: 'c'"
- _ = 1
-}
-
-func z[T http.ResponseWriter](_ T, _ *http.Request) { // want "potentially unused parameter: 'h'"
- fmt.Println("Before")
-}
-
-func l(h http.Handler) http.Handler {
- return http.HandlerFunc(z[http.ResponseWriter])
-}
-
-func mult(a, _ int) int { // want "potentially unused parameter: 'b'"
- a += 1
- return a
-}
-
-func y[T any](a T) {
- panic("yo")
-}
diff --git a/internal/lsp/analysis/unusedparams/unusedparams.go b/internal/lsp/analysis/unusedparams/unusedparams.go
deleted file mode 100644
index 4c933c8fb..000000000
--- a/internal/lsp/analysis/unusedparams/unusedparams.go
+++ /dev/null
@@ -1,152 +0,0 @@
-// 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 defines an analyzer that checks for unused
-// parameters of functions.
-package unusedparams
-
-import (
- "fmt"
- "go/ast"
- "go/types"
- "strings"
-
- "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 unused parameters of functions
-
-The unusedparams analyzer checks functions to see if there are
-any parameters that are not being used.
-
-To reduce false positives it ignores:
-- methods
-- parameters that do not have a name or are underscored
-- functions in test files
-- functions with empty bodies or those with just a return stmt`
-
-var Analyzer = &analysis.Analyzer{
- Name: "unusedparams",
- Doc: Doc,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
- Run: run,
-}
-
-type paramData struct {
- field *ast.Field
- ident *ast.Ident
- typObj types.Object
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- nodeFilter := []ast.Node{
- (*ast.FuncDecl)(nil),
- (*ast.FuncLit)(nil),
- }
-
- inspect.Preorder(nodeFilter, func(n ast.Node) {
- var fieldList *ast.FieldList
- var body *ast.BlockStmt
-
- // Get the fieldList and body from the function node.
- switch f := n.(type) {
- case *ast.FuncDecl:
- fieldList, body = f.Type.Params, f.Body
- // TODO(golang/go#36602): add better handling for methods, if we enable methods
- // we will get false positives if a struct is potentially implementing
- // an interface.
- if f.Recv != nil {
- return
- }
- // Ignore functions in _test.go files to reduce false positives.
- if file := pass.Fset.File(n.Pos()); file != nil && strings.HasSuffix(file.Name(), "_test.go") {
- return
- }
- case *ast.FuncLit:
- fieldList, body = f.Type.Params, f.Body
- }
- // If there are no arguments or the function is empty, then return.
- if fieldList.NumFields() == 0 || body == nil || len(body.List) == 0 {
- return
- }
-
- switch expr := body.List[0].(type) {
- case *ast.ReturnStmt:
- // Ignore functions that only contain a return statement to reduce false positives.
- return
- case *ast.ExprStmt:
- callExpr, ok := expr.X.(*ast.CallExpr)
- if !ok || len(body.List) > 1 {
- break
- }
- // Ignore functions that only contain a panic statement to reduce false positives.
- if fun, ok := callExpr.Fun.(*ast.Ident); ok && fun.Name == "panic" {
- return
- }
- }
-
- // Get the useful data from each field.
- params := make(map[string]*paramData)
- unused := make(map[*paramData]bool)
- for _, f := range fieldList.List {
- for _, i := range f.Names {
- if i.Name == "_" {
- continue
- }
- params[i.Name] = &paramData{
- field: f,
- ident: i,
- typObj: pass.TypesInfo.ObjectOf(i),
- }
- unused[params[i.Name]] = true
- }
- }
-
- // Traverse through the body of the function and
- // check to see which parameters are unused.
- ast.Inspect(body, func(node ast.Node) bool {
- n, ok := node.(*ast.Ident)
- if !ok {
- return true
- }
- param, ok := params[n.Name]
- if !ok {
- return false
- }
- if nObj := pass.TypesInfo.ObjectOf(n); nObj != param.typObj {
- return false
- }
- delete(unused, param)
- return false
- })
-
- // Create the reports for the unused parameters.
- for u := range unused {
- start, end := u.field.Pos(), u.field.End()
- if len(u.field.Names) > 1 {
- start, end = u.ident.Pos(), u.ident.End()
- }
- // TODO(golang/go#36602): Add suggested fixes to automatically
- // remove the unused parameter from every use of this
- // function.
- pass.Report(analysis.Diagnostic{
- Pos: start,
- End: end,
- Message: fmt.Sprintf("potentially unused parameter: '%s'", u.ident.Name),
- SuggestedFixes: []analysis.SuggestedFix{{
- Message: `Replace with "_"`,
- TextEdits: []analysis.TextEdit{{
- Pos: u.ident.Pos(),
- End: u.ident.End(),
- NewText: []byte("_"),
- }},
- }},
- })
- }
- })
- return nil, nil
-}
diff --git a/internal/lsp/analysis/unusedparams/unusedparams_test.go b/internal/lsp/analysis/unusedparams/unusedparams_test.go
deleted file mode 100644
index dff17c95e..000000000
--- a/internal/lsp/analysis/unusedparams/unusedparams_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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/internal/lsp/analysis/unusedparams"
- "golang.org/x/tools/internal/typeparams"
-)
-
-func Test(t *testing.T) {
- testdata := analysistest.TestData()
- tests := []string{"a"}
- if typeparams.Enabled {
- tests = append(tests, "typeparams")
- }
- analysistest.RunWithSuggestedFixes(t, testdata, unusedparams.Analyzer, tests...)
-}
diff --git a/internal/lsp/analysis/useany/testdata/src/a/a.go b/internal/lsp/analysis/useany/testdata/src/a/a.go
deleted file mode 100644
index 22d693150..000000000
--- a/internal/lsp/analysis/useany/testdata/src/a/a.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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/internal/lsp/analysis/useany/testdata/src/a/a.go.golden b/internal/lsp/analysis/useany/testdata/src/a/a.go.golden
deleted file mode 100644
index efd8fd640..000000000
--- a/internal/lsp/analysis/useany/testdata/src/a/a.go.golden
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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/internal/lsp/analysis/useany/useany.go b/internal/lsp/analysis/useany/useany.go
deleted file mode 100644
index 73e2f7633..000000000
--- a/internal/lsp/analysis/useany/useany.go
+++ /dev/null
@@ -1,102 +0,0 @@
-// 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"
- "golang.org/x/tools/internal/typeparams"
-)
-
-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,
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
-
- universeAny := types.Universe.Lookup("any")
- if universeAny == nil {
- // Go <= 1.17. Nothing to check.
- return nil, nil
- }
-
- 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 = typeparams.ForTypeSpec(node)
- case *ast.FuncType:
- tparams = typeparams.ForFuncType(node)
- 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/internal/lsp/analysis/useany/useany_test.go b/internal/lsp/analysis/useany/useany_test.go
deleted file mode 100644
index 535d91526..000000000
--- a/internal/lsp/analysis/useany/useany_test.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// 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/internal/lsp/analysis/useany"
- "golang.org/x/tools/internal/typeparams"
-)
-
-func Test(t *testing.T) {
- if !typeparams.Enabled {
- t.Skip("type params are not enabled")
- }
- testdata := analysistest.TestData()
- analysistest.RunWithSuggestedFixes(t, testdata, useany.Analyzer, "a")
-}
diff --git a/internal/lsp/browser/README.md b/internal/lsp/browser/README.md
deleted file mode 100644
index e5f04df4d..000000000
--- a/internal/lsp/browser/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This package is a copy of cmd/internal/browser from the go distribution \ No newline at end of file
diff --git a/internal/lsp/browser/browser.go b/internal/lsp/browser/browser.go
deleted file mode 100644
index 0ac4f20f0..000000000
--- a/internal/lsp/browser/browser.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// 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 (
- exec "golang.org/x/sys/execabs"
- "os"
- "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/internal/lsp/cache/analysis.go b/internal/lsp/cache/analysis.go
deleted file mode 100644
index d66a3ed37..000000000
--- a/internal/lsp/cache/analysis.go
+++ /dev/null
@@ -1,433 +0,0 @@
-// 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/ast"
- "go/types"
- "reflect"
- "sort"
- "sync"
-
- "golang.org/x/sync/errgroup"
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/memoize"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-func (s *snapshot) Analyze(ctx context.Context, id string, analyzers []*source.Analyzer) ([]*source.Diagnostic, error) {
- var roots []*actionHandle
- for _, a := range analyzers {
- if !a.IsEnabled(s.view) {
- continue
- }
- ah, err := s.actionHandle(ctx, PackageID(id), a.Analyzer)
- if err != nil {
- return nil, err
- }
- roots = append(roots, ah)
- }
-
- // Check if the context has been canceled before running the analyses.
- if ctx.Err() != nil {
- return nil, ctx.Err()
- }
-
- var results []*source.Diagnostic
- for _, ah := range roots {
- diagnostics, _, err := ah.analyze(ctx, s)
- if err != nil {
- // Keep going if a single analyzer failed.
- event.Error(ctx, fmt.Sprintf("analyzer %q failed", ah.analyzer.Name), err)
- continue
- }
- results = append(results, diagnostics...)
- }
- return results, nil
-}
-
-type actionHandleKey string
-
-// 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 actionHandle struct {
- handle *memoize.Handle
-
- analyzer *analysis.Analyzer
- pkg *pkg
-}
-
-type actionData struct {
- diagnostics []*source.Diagnostic
- result interface{}
- objectFacts map[objectFactKey]analysis.Fact
- packageFacts map[packageFactKey]analysis.Fact
- err error
-}
-
-type objectFactKey struct {
- obj types.Object
- typ reflect.Type
-}
-
-type packageFactKey struct {
- pkg *types.Package
- typ reflect.Type
-}
-
-func (s *snapshot) actionHandle(ctx context.Context, id PackageID, a *analysis.Analyzer) (*actionHandle, error) {
- ph, err := s.buildPackageHandle(ctx, id, source.ParseFull)
- if err != nil {
- return nil, err
- }
- act := s.getActionHandle(id, ph.mode, a)
- if act != nil {
- return act, nil
- }
- if len(ph.key) == 0 {
- return nil, errors.Errorf("actionHandle: no key for package %s", id)
- }
- pkg, err := ph.check(ctx, s)
- if err != nil {
- return nil, err
- }
- act = &actionHandle{
- analyzer: a,
- pkg: pkg,
- }
- var deps []*actionHandle
- // Add a dependency on each required analyzers.
- for _, req := range a.Requires {
- reqActionHandle, err := s.actionHandle(ctx, id, req)
- if err != nil {
- return nil, err
- }
- deps = append(deps, reqActionHandle)
- }
-
- // TODO(golang/go#35089): Re-enable this when we doesn't use ParseExported
- // mode for dependencies. In the meantime, disable analysis for dependencies,
- // since we don't get anything useful out of it.
- if false {
- // An analysis that consumes/produces facts
- // must run on the package's dependencies too.
- if len(a.FactTypes) > 0 {
- importIDs := make([]string, 0, len(ph.m.Deps))
- for _, importID := range ph.m.Deps {
- importIDs = append(importIDs, string(importID))
- }
- sort.Strings(importIDs) // for determinism
- for _, importID := range importIDs {
- depActionHandle, err := s.actionHandle(ctx, PackageID(importID), a)
- if err != nil {
- return nil, err
- }
- deps = append(deps, depActionHandle)
- }
- }
- }
-
- h := s.generation.Bind(buildActionKey(a, ph), func(ctx context.Context, arg memoize.Arg) interface{} {
- snapshot := arg.(*snapshot)
- // Analyze dependencies first.
- results, err := execAll(ctx, snapshot, deps)
- if err != nil {
- return &actionData{
- err: err,
- }
- }
- return runAnalysis(ctx, snapshot, a, pkg, results)
- }, nil)
- act.handle = h
-
- act = s.addActionHandle(act)
- return act, nil
-}
-
-func (act *actionHandle) analyze(ctx context.Context, snapshot *snapshot) ([]*source.Diagnostic, interface{}, error) {
- d, err := act.handle.Get(ctx, snapshot.generation, snapshot)
- if err != nil {
- return nil, nil, err
- }
- data, ok := d.(*actionData)
- if !ok {
- return nil, nil, errors.Errorf("unexpected type for %s:%s", act.pkg.ID(), act.analyzer.Name)
- }
- if data == nil {
- return nil, nil, errors.Errorf("unexpected nil analysis for %s:%s", act.pkg.ID(), act.analyzer.Name)
- }
- return data.diagnostics, data.result, data.err
-}
-
-func buildActionKey(a *analysis.Analyzer, ph *packageHandle) actionHandleKey {
- return actionHandleKey(hashContents([]byte(fmt.Sprintf("%p %s", a, string(ph.key)))))
-}
-
-func (act *actionHandle) String() string {
- return fmt.Sprintf("%s@%s", act.analyzer, act.pkg.PkgPath())
-}
-
-func execAll(ctx context.Context, snapshot *snapshot, actions []*actionHandle) (map[*actionHandle]*actionData, error) {
- var mu sync.Mutex
- results := make(map[*actionHandle]*actionData)
-
- g, ctx := errgroup.WithContext(ctx)
- for _, act := range actions {
- act := act
- g.Go(func() error {
- v, err := act.handle.Get(ctx, snapshot.generation, snapshot)
- if err != nil {
- return err
- }
- data, ok := v.(*actionData)
- if !ok {
- return errors.Errorf("unexpected type for %s: %T", act, v)
- }
-
- mu.Lock()
- defer mu.Unlock()
- results[act] = data
-
- return nil
- })
- }
- return results, g.Wait()
-}
-
-func runAnalysis(ctx context.Context, snapshot *snapshot, analyzer *analysis.Analyzer, pkg *pkg, deps map[*actionHandle]*actionData) (data *actionData) {
- data = &actionData{
- objectFacts: make(map[objectFactKey]analysis.Fact),
- packageFacts: make(map[packageFactKey]analysis.Fact),
- }
- defer func() {
- if r := recover(); r != nil {
- data.err = errors.Errorf("analysis %s for package %s panicked: %v", analyzer.Name, pkg.PkgPath(), r)
- }
- }()
-
- // Plumb the output values of the dependencies
- // into the inputs of this action. Also facts.
- inputs := make(map[*analysis.Analyzer]interface{})
-
- for depHandle, depData := range deps {
- if depHandle.pkg == pkg {
- // Same package, different analysis (horizontal edge):
- // in-memory outputs of prerequisite analyzers
- // become inputs to this analysis pass.
- inputs[depHandle.analyzer] = depData.result
- } else if depHandle.analyzer == analyzer { // (always true)
- // Same analysis, different package (vertical edge):
- // serialized facts produced by prerequisite analysis
- // become available to this analysis pass.
- for key, fact := range depData.objectFacts {
- // Filter out facts related to objects
- // that are irrelevant downstream
- // (equivalently: not in the compiler export data).
- if !exportedFrom(key.obj, depHandle.pkg.types) {
- continue
- }
- data.objectFacts[key] = fact
- }
- for key, fact := range depData.packageFacts {
- // TODO: filter out facts that belong to
- // packages not mentioned in the export data
- // to prevent side channels.
-
- data.packageFacts[key] = fact
- }
- }
- }
-
- var syntax []*ast.File
- for _, cgf := range pkg.compiledGoFiles {
- syntax = append(syntax, cgf.File)
- }
-
- var diagnostics []*analysis.Diagnostic
-
- // Run the analysis.
- pass := &analysis.Pass{
- Analyzer: analyzer,
- Fset: snapshot.FileSet(),
- Files: syntax,
- Pkg: pkg.GetTypes(),
- TypesInfo: pkg.GetTypesInfo(),
- TypesSizes: pkg.GetTypesSizes(),
- ResultOf: inputs,
- Report: func(d analysis.Diagnostic) {
- // Prefix the diagnostic category with the analyzer's name.
- if d.Category == "" {
- d.Category = analyzer.Name
- } else {
- d.Category = analyzer.Name + "." + d.Category
- }
- diagnostics = append(diagnostics, &d)
- },
- ImportObjectFact: func(obj types.Object, ptr analysis.Fact) bool {
- if obj == nil {
- panic("nil object")
- }
- key := objectFactKey{obj, factType(ptr)}
-
- if v, ok := data.objectFacts[key]; ok {
- reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem())
- return true
- }
- return false
- },
- ExportObjectFact: func(obj types.Object, fact analysis.Fact) {
- if obj.Pkg() != pkg.types {
- panic(fmt.Sprintf("internal error: in analysis %s of package %s: Fact.Set(%s, %T): can't set facts on objects belonging another package",
- analyzer, pkg.ID(), obj, fact))
- }
- key := objectFactKey{obj, factType(fact)}
- data.objectFacts[key] = fact // clobber any existing entry
- },
- ImportPackageFact: func(pkg *types.Package, ptr analysis.Fact) bool {
- if pkg == nil {
- panic("nil package")
- }
- key := packageFactKey{pkg, factType(ptr)}
- if v, ok := data.packageFacts[key]; ok {
- reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem())
- return true
- }
- return false
- },
- ExportPackageFact: func(fact analysis.Fact) {
- key := packageFactKey{pkg.types, factType(fact)}
- data.packageFacts[key] = fact // clobber any existing entry
- },
- AllObjectFacts: func() []analysis.ObjectFact {
- facts := make([]analysis.ObjectFact, 0, len(data.objectFacts))
- for k := range data.objectFacts {
- facts = append(facts, analysis.ObjectFact{Object: k.obj, Fact: data.objectFacts[k]})
- }
- return facts
- },
- AllPackageFacts: func() []analysis.PackageFact {
- facts := make([]analysis.PackageFact, 0, len(data.packageFacts))
- for k := range data.packageFacts {
- facts = append(facts, analysis.PackageFact{Package: k.pkg, Fact: data.packageFacts[k]})
- }
- return facts
- },
- }
- analysisinternal.SetTypeErrors(pass, pkg.typeErrors)
-
- if pkg.IsIllTyped() {
- data.err = errors.Errorf("analysis skipped due to errors in package")
- return data
- }
- data.result, data.err = pass.Analyzer.Run(pass)
- if data.err != nil {
- return data
- }
-
- if got, want := reflect.TypeOf(data.result), pass.Analyzer.ResultType; got != want {
- data.err = errors.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)
- return data
- }
-
- // disallow calls after Run
- pass.ExportObjectFact = func(obj types.Object, fact analysis.Fact) {
- panic(fmt.Sprintf("%s:%s: Pass.ExportObjectFact(%s, %T) called after Run", analyzer.Name, pkg.PkgPath(), obj, fact))
- }
- pass.ExportPackageFact = func(fact analysis.Fact) {
- panic(fmt.Sprintf("%s:%s: Pass.ExportPackageFact(%T) called after Run", analyzer.Name, pkg.PkgPath(), fact))
- }
-
- for _, diag := range diagnostics {
- srcDiags, err := analysisDiagnosticDiagnostics(snapshot, pkg, analyzer, diag)
- if err != nil {
- event.Error(ctx, "unable to compute analysis error position", err, tag.Category.Of(diag.Category), tag.Package.Of(pkg.ID()))
- continue
- }
- if ctx.Err() != nil {
- data.err = ctx.Err()
- return data
- }
- data.diagnostics = append(data.diagnostics, srcDiags...)
- }
- return data
-}
-
-// exportedFrom reports whether obj may be visible to a package that imports pkg.
-// This includes not just the exported members of pkg, but also unexported
-// constants, types, fields, and methods, perhaps belonging to oether packages,
-// that find there way into the API.
-// This is an overapproximation of the more accurate approach used by
-// gc export data, which walks the type graph, but it's much simpler.
-//
-// TODO(adonovan): do more accurate filtering by walking the type graph.
-func exportedFrom(obj types.Object, pkg *types.Package) bool {
- switch obj := obj.(type) {
- case *types.Func:
- return obj.Exported() && obj.Pkg() == pkg ||
- obj.Type().(*types.Signature).Recv() != nil
- case *types.Var:
- return obj.Exported() && obj.Pkg() == pkg ||
- obj.IsField()
- case *types.TypeName, *types.Const:
- return true
- }
- return false // Nil, Builtin, Label, or PkgName
-}
-
-func factType(fact analysis.Fact) reflect.Type {
- t := reflect.TypeOf(fact)
- if t.Kind() != reflect.Ptr {
- panic(fmt.Sprintf("invalid Fact type: got %T, want pointer", fact))
- }
- return t
-}
-
-func (s *snapshot) DiagnosePackage(ctx context.Context, spkg source.Package) (map[span.URI][]*source.Diagnostic, error) {
- pkg := spkg.(*pkg)
- // Apply type error analyzers. They augment type error diagnostics with their own fixes.
- var analyzers []*source.Analyzer
- for _, a := range s.View().Options().TypeErrorAnalyzers {
- analyzers = append(analyzers, a)
- }
- var errorAnalyzerDiag []*source.Diagnostic
- if pkg.HasTypeErrors() {
- var err error
- errorAnalyzerDiag, err = s.Analyze(ctx, pkg.ID(), analyzers)
- if err != nil {
- // Keep going: analysis failures should not block diagnostics.
- event.Error(ctx, "type error analysis failed", err, tag.Package.Of(pkg.ID()))
- }
- }
- diags := map[span.URI][]*source.Diagnostic{}
- for _, diag := range pkg.diagnostics {
- for _, eaDiag := range errorAnalyzerDiag {
- if eaDiag.URI == diag.URI && eaDiag.Range == diag.Range && eaDiag.Message == diag.Message {
- // Type error analyzers just add fixes and tags. Make a copy,
- // since we don't own either, and overwrite.
- // The analyzer itself can't do this merge because
- // analysis.Diagnostic doesn't have all the fields, and Analyze
- // can't because it doesn't have the type error, notably its code.
- clone := *diag
- clone.SuggestedFixes = eaDiag.SuggestedFixes
- clone.Tags = eaDiag.Tags
- clone.Analyzer = eaDiag.Analyzer
- diag = &clone
- }
- }
- diags[diag.URI] = append(diags[diag.URI], diag)
- }
- return diags, nil
-}
diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go
deleted file mode 100644
index ac670b573..000000000
--- a/internal/lsp/cache/cache.go
+++ /dev/null
@@ -1,293 +0,0 @@
-// 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/token"
- "go/types"
- "html/template"
- "io/ioutil"
- "os"
- "reflect"
- "sort"
- "strconv"
- "sync"
- "sync/atomic"
- "time"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/gocommand"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/memoize"
- "golang.org/x/tools/internal/span"
-)
-
-func New(options func(*source.Options)) *Cache {
- index := atomic.AddInt64(&cacheIndex, 1)
- c := &Cache{
- id: strconv.FormatInt(index, 10),
- fset: token.NewFileSet(),
- options: options,
- fileContent: map[span.URI]*fileHandle{},
- }
- return c
-}
-
-type Cache struct {
- id string
- fset *token.FileSet
- options func(*source.Options)
-
- store memoize.Store
-
- fileMu sync.Mutex
- fileContent map[span.URI]*fileHandle
-}
-
-type fileHandle struct {
- modTime time.Time
- uri span.URI
- bytes []byte
- hash string
- err error
-
- // size is the file length as reported by Stat, for the purpose of
- // invalidation. Probably we could just use len(bytes), but this is done
- // defensively in case the definition of file size in the file system
- // differs.
- size int64
-}
-
-func (h *fileHandle) Saved() bool {
- return true
-}
-
-func (c *Cache) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
- return c.getFile(ctx, uri)
-}
-
-func (c *Cache) getFile(ctx context.Context, uri span.URI) (*fileHandle, error) {
- fi, statErr := os.Stat(uri.Filename())
- if statErr != nil {
- return &fileHandle{
- err: statErr,
- uri: uri,
- }, nil
- }
-
- c.fileMu.Lock()
- fh, ok := c.fileContent[uri]
- c.fileMu.Unlock()
-
- // Check mtime and file size to infer whether the file has changed. This is
- // an imperfect heuristic. Notably on some real systems (such as WSL) the
- // filesystem clock resolution can be large -- 1/64s was observed. Therefore
- // it's quite possible for multiple file modifications to occur within a
- // single logical 'tick'. This can leave the cache in an incorrect state, but
- // unfortunately we can't afford to pay the price of reading the actual file
- // content here. Or to be more precise, reading would be a risky change and
- // we don't know if we can afford it.
- //
- // We check file size in an attempt to reduce the probability of false cache
- // hits.
- if ok && fh.modTime.Equal(fi.ModTime()) && fh.size == fi.Size() {
- return fh, nil
- }
-
- fh, err := readFile(ctx, uri, fi)
- if err != nil {
- return nil, err
- }
- c.fileMu.Lock()
- c.fileContent[uri] = fh
- c.fileMu.Unlock()
- return fh, nil
-}
-
-// ioLimit limits the number of parallel file reads per process.
-var ioLimit = make(chan struct{}, 128)
-
-func readFile(ctx context.Context, uri span.URI, fi os.FileInfo) (*fileHandle, error) {
- select {
- case ioLimit <- struct{}{}:
- case <-ctx.Done():
- return nil, ctx.Err()
- }
- defer func() { <-ioLimit }()
-
- ctx, done := event.Start(ctx, "cache.readFile", tag.File.Of(uri.Filename()))
- _ = ctx
- defer done()
-
- data, err := ioutil.ReadFile(uri.Filename())
- if err != nil {
- return &fileHandle{
- modTime: fi.ModTime(),
- size: fi.Size(),
- err: err,
- }, nil
- }
- return &fileHandle{
- modTime: fi.ModTime(),
- size: fi.Size(),
- uri: uri,
- bytes: data,
- hash: hashContents(data),
- }, nil
-}
-
-func (c *Cache) NewSession(ctx context.Context) *Session {
- index := atomic.AddInt64(&sessionIndex, 1)
- options := source.DefaultOptions().Clone()
- if c.options != nil {
- c.options(options)
- }
- s := &Session{
- cache: c,
- id: strconv.FormatInt(index, 10),
- options: options,
- overlays: make(map[span.URI]*overlay),
- gocmdRunner: &gocommand.Runner{},
- }
- event.Log(ctx, "New session", KeyCreateSession.Of(s))
- return s
-}
-
-func (c *Cache) FileSet() *token.FileSet {
- return c.fset
-}
-
-func (h *fileHandle) URI() span.URI {
- return h.uri
-}
-
-func (h *fileHandle) Hash() string {
- return h.hash
-}
-
-func (h *fileHandle) FileIdentity() source.FileIdentity {
- return source.FileIdentity{
- URI: h.uri,
- Hash: h.hash,
- }
-}
-
-func (h *fileHandle) Read() ([]byte, error) {
- return h.bytes, h.err
-}
-
-func hashContents(contents []byte) string {
- return fmt.Sprintf("%x", sha256.Sum256(contents))
-}
-
-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() }
-
-type packageStat struct {
- id PackageID
- mode source.ParseMode
- file int64
- ast int64
- types int64
- typesInfo int64
- total int64
-}
-
-func (c *Cache) PackageStats(withNames bool) template.HTML {
- var packageStats []packageStat
- c.store.DebugOnlyIterate(func(k, v interface{}) {
- switch k.(type) {
- case packageHandleKey:
- v := v.(*packageData)
- if v.pkg == nil {
- break
- }
- var typsCost, typInfoCost int64
- if v.pkg.types != nil {
- typsCost = typesCost(v.pkg.types.Scope())
- }
- if v.pkg.typesInfo != nil {
- typInfoCost = typesInfoCost(v.pkg.typesInfo)
- }
- stat := packageStat{
- id: v.pkg.m.ID,
- mode: v.pkg.mode,
- types: typsCost,
- typesInfo: typInfoCost,
- }
- for _, f := range v.pkg.compiledGoFiles {
- stat.file += int64(len(f.Src))
- stat.ast += astCost(f.File)
- }
- stat.total = stat.file + stat.ast + stat.types + stat.typesInfo
- packageStats = append(packageStats, stat)
- }
- })
- var totalCost int64
- for _, stat := range packageStats {
- totalCost += stat.total
- }
- sort.Slice(packageStats, func(i, j int) bool {
- return packageStats[i].total > packageStats[j].total
- })
- html := "<table><thead><td>Name</td><td>total = file + ast + types + types info</td></thead>\n"
- human := func(n int64) string {
- return fmt.Sprintf("%.2f", float64(n)/(1024*1024))
- }
- var printedCost int64
- for _, stat := range packageStats {
- name := stat.id
- if !withNames {
- name = "-"
- }
- html += fmt.Sprintf("<tr><td>%v (%v)</td><td>%v = %v + %v + %v + %v</td></tr>\n", name, stat.mode,
- human(stat.total), human(stat.file), human(stat.ast), human(stat.types), human(stat.typesInfo))
- printedCost += stat.total
- if float64(printedCost) > float64(totalCost)*.9 {
- break
- }
- }
- html += "</table>\n"
- return template.HTML(html)
-}
-
-func astCost(f *ast.File) int64 {
- if f == nil {
- return 0
- }
- var count int64
- ast.Inspect(f, func(_ ast.Node) bool {
- count += 32 // nodes are pretty small.
- return true
- })
- return count
-}
-
-func typesCost(scope *types.Scope) int64 {
- cost := 64 + int64(scope.Len())*128 // types.object looks pretty big
- for i := 0; i < scope.NumChildren(); i++ {
- cost += typesCost(scope.Child(i))
- }
- return cost
-}
-
-func typesInfoCost(info *types.Info) int64 {
- // Most of these refer to existing objects, with the exception of InitOrder, Selections, and Types.
- cost := 24*len(info.Defs) +
- 32*len(info.Implicits) +
- 256*len(info.InitOrder) + // these are big, but there aren't many of them.
- 32*len(info.Scopes) +
- 128*len(info.Selections) + // wild guess
- 128*len(info.Types) + // wild guess
- 32*len(info.Uses)
- return int64(cost)
-}
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
deleted file mode 100644
index f16686354..000000000
--- a/internal/lsp/cache/check.go
+++ /dev/null
@@ -1,863 +0,0 @@
-// 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"
- "fmt"
- "go/ast"
- "go/types"
- "path"
- "path/filepath"
- "regexp"
- "sort"
- "strings"
- "sync"
-
- "golang.org/x/mod/module"
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/go/packages"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/memoize"
- "golang.org/x/tools/internal/packagesinternal"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/typeparams"
- "golang.org/x/tools/internal/typesinternal"
- errors "golang.org/x/xerrors"
-)
-
-type packageHandleKey string
-
-type packageHandle struct {
- handle *memoize.Handle
-
- goFiles, compiledGoFiles []*parseGoHandle
-
- // mode is the mode the files were parsed in.
- mode source.ParseMode
-
- // m is the metadata associated with the package.
- m *KnownMetadata
-
- // key is the hashed key for the package.
- key packageHandleKey
-}
-
-func (ph *packageHandle) packageKey() packageKey {
- return packageKey{
- id: ph.m.ID,
- mode: ph.mode,
- }
-}
-
-func (ph *packageHandle) imports(ctx context.Context, s source.Snapshot) (result []string) {
- for _, pgh := range ph.goFiles {
- f, err := s.ParseGo(ctx, pgh.file, source.ParseHeader)
- if err != nil {
- continue
- }
- seen := map[string]struct{}{}
- for _, impSpec := range f.File.Imports {
- imp := strings.Trim(impSpec.Path.Value, `"`)
- if _, ok := seen[imp]; !ok {
- seen[imp] = struct{}{}
- result = append(result, imp)
- }
- }
- }
-
- sort.Strings(result)
- return result
-}
-
-// packageData contains the data produced by type-checking a package.
-type packageData struct {
- pkg *pkg
- err error
-}
-
-// buildPackageHandle returns a packageHandle for a given package and mode.
-// It assumes that the given ID already has metadata available, so it does not
-// attempt to reload missing or invalid metadata. The caller must reload
-// metadata if needed.
-func (s *snapshot) buildPackageHandle(ctx context.Context, id PackageID, mode source.ParseMode) (*packageHandle, error) {
- if ph := s.getPackage(id, mode); ph != nil {
- return ph, nil
- }
-
- // Build the packageHandle for this ID and its dependencies.
- ph, deps, err := s.buildKey(ctx, id, mode)
- if err != nil {
- return nil, err
- }
-
- // Do not close over the packageHandle or the snapshot in the Bind function.
- // This creates a cycle, which causes the finalizers to never run on the handles.
- // The possible cycles are:
- //
- // packageHandle.h.function -> packageHandle
- // packageHandle.h.function -> snapshot -> packageHandle
- //
-
- m := ph.m
- key := ph.key
-
- h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
- snapshot := arg.(*snapshot)
-
- // Begin loading the direct dependencies, in parallel.
- var wg sync.WaitGroup
- for _, dep := range deps {
- wg.Add(1)
- go func(dep *packageHandle) {
- dep.check(ctx, snapshot)
- wg.Done()
- }(dep)
- }
-
- data := &packageData{}
- data.pkg, data.err = typeCheck(ctx, snapshot, m.Metadata, mode, deps)
- // Make sure that the workers above have finished before we return,
- // especially in case of cancellation.
- wg.Wait()
-
- return data
- }, nil)
- ph.handle = h
-
- // Cache the handle in the snapshot. If a package handle has already
- // been cached, addPackage will return the cached value. This is fine,
- // since the original package handle above will have no references and be
- // garbage collected.
- ph = s.addPackageHandle(ph)
-
- return ph, nil
-}
-
-// buildKey computes the key for a given packageHandle.
-func (s *snapshot) buildKey(ctx context.Context, id PackageID, mode source.ParseMode) (*packageHandle, map[PackagePath]*packageHandle, error) {
- m := s.getMetadata(id)
- if m == nil {
- return nil, nil, errors.Errorf("no metadata for %s", id)
- }
- goFiles, err := s.parseGoHandles(ctx, m.GoFiles, mode)
- if err != nil {
- return nil, nil, err
- }
- compiledGoFiles, err := s.parseGoHandles(ctx, m.CompiledGoFiles, mode)
- if err != nil {
- return nil, nil, err
- }
- ph := &packageHandle{
- m: m,
- goFiles: goFiles,
- compiledGoFiles: compiledGoFiles,
- mode: mode,
- }
- // Make sure all of the depList are sorted.
- depList := append([]PackageID{}, m.Deps...)
- sort.Slice(depList, func(i, j int) bool {
- return depList[i] < depList[j]
- })
-
- deps := make(map[PackagePath]*packageHandle)
-
- // Begin computing the key by getting the depKeys for all dependencies.
- var depKeys []packageHandleKey
- for _, depID := range depList {
- depHandle, err := s.buildPackageHandle(ctx, depID, s.workspaceParseMode(depID))
- // Don't use invalid metadata for dependencies if the top-level
- // metadata is valid. We only load top-level packages, so if the
- // top-level is valid, all of its dependencies should be as well.
- if err != nil || m.Valid && !depHandle.m.Valid {
- if err != nil {
- event.Error(ctx, fmt.Sprintf("%s: no dep handle for %s", id, depID), err, tag.Snapshot.Of(s.id))
- } else {
- event.Log(ctx, fmt.Sprintf("%s: invalid dep handle for %s", id, depID), tag.Snapshot.Of(s.id))
- }
-
- if ctx.Err() != nil {
- return nil, nil, ctx.Err()
- }
- // One bad dependency should not prevent us from checking the entire package.
- // Add a special key to mark a bad dependency.
- depKeys = append(depKeys, packageHandleKey(fmt.Sprintf("%s import not found", depID)))
- continue
- }
- deps[depHandle.m.PkgPath] = depHandle
- depKeys = append(depKeys, depHandle.key)
- }
- experimentalKey := s.View().Options().ExperimentalPackageCacheKey
- ph.key = checkPackageKey(ph.m.ID, compiledGoFiles, m, depKeys, mode, experimentalKey)
- return ph, deps, nil
-}
-
-func (s *snapshot) workspaceParseMode(id PackageID) source.ParseMode {
- s.mu.Lock()
- defer s.mu.Unlock()
- _, ws := s.workspacePackages[id]
- if !ws {
- return source.ParseExported
- }
- if s.view.Options().MemoryMode == source.ModeNormal {
- return source.ParseFull
- }
- if s.isActiveLocked(id, nil) {
- return source.ParseFull
- }
- return source.ParseExported
-}
-
-func checkPackageKey(id PackageID, pghs []*parseGoHandle, m *KnownMetadata, deps []packageHandleKey, mode source.ParseMode, experimentalKey bool) packageHandleKey {
- b := bytes.NewBuffer(nil)
- b.WriteString(string(id))
- if m.Module != nil {
- b.WriteString(m.Module.GoVersion) // go version affects type check errors.
- }
- if !experimentalKey {
- // cfg was used to produce the other hashed inputs (package ID, parsed Go
- // files, and deps). It should not otherwise affect the inputs to the type
- // checker, so this experiment omits it. This should increase cache hits on
- // the daemon as cfg contains the environment and working directory.
- b.WriteString(hashConfig(m.Config))
- }
- b.WriteByte(byte(mode))
- for _, dep := range deps {
- b.WriteString(string(dep))
- }
- for _, cgf := range pghs {
- b.WriteString(cgf.file.FileIdentity().String())
- }
- return packageHandleKey(hashContents(b.Bytes()))
-}
-
-// hashEnv returns a hash of the snapshot's configuration.
-func hashEnv(s *snapshot) string {
- s.view.optionsMu.Lock()
- env := s.view.options.EnvSlice()
- s.view.optionsMu.Unlock()
-
- b := &bytes.Buffer{}
- for _, e := range env {
- b.WriteString(e)
- }
- return hashContents(b.Bytes())
-}
-
-// hashConfig returns the hash for the *packages.Config.
-func hashConfig(config *packages.Config) string {
- b := bytes.NewBuffer(nil)
-
- // Dir, Mode, Env, BuildFlags are the parts of the config that can change.
- b.WriteString(config.Dir)
- b.WriteString(string(rune(config.Mode)))
-
- for _, e := range config.Env {
- b.WriteString(e)
- }
- for _, f := range config.BuildFlags {
- b.WriteString(f)
- }
- return hashContents(b.Bytes())
-}
-
-func (ph *packageHandle) Check(ctx context.Context, s source.Snapshot) (source.Package, error) {
- return ph.check(ctx, s.(*snapshot))
-}
-
-func (ph *packageHandle) check(ctx context.Context, s *snapshot) (*pkg, error) {
- v, err := ph.handle.Get(ctx, s.generation, s)
- if err != nil {
- return nil, err
- }
- data := v.(*packageData)
- return data.pkg, data.err
-}
-
-func (ph *packageHandle) CompiledGoFiles() []span.URI {
- return ph.m.CompiledGoFiles
-}
-
-func (ph *packageHandle) ID() string {
- return string(ph.m.ID)
-}
-
-func (ph *packageHandle) cached(g *memoize.Generation) (*pkg, error) {
- v := ph.handle.Cached(g)
- if v == nil {
- return nil, errors.Errorf("no cached type information for %s", ph.m.PkgPath)
- }
- data := v.(*packageData)
- return data.pkg, data.err
-}
-
-func (s *snapshot) parseGoHandles(ctx context.Context, files []span.URI, mode source.ParseMode) ([]*parseGoHandle, error) {
- pghs := make([]*parseGoHandle, 0, len(files))
- for _, uri := range files {
- fh, err := s.GetFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- pghs = append(pghs, s.parseGoHandle(ctx, fh, mode))
- }
- return pghs, nil
-}
-
-func typeCheck(ctx context.Context, snapshot *snapshot, m *Metadata, mode source.ParseMode, deps map[PackagePath]*packageHandle) (*pkg, error) {
- var filter *unexportedFilter
- if mode == source.ParseExported {
- filter = &unexportedFilter{uses: map[string]bool{}}
- }
- pkg, err := doTypeCheck(ctx, snapshot, m, mode, deps, filter)
- if err != nil {
- return nil, err
- }
-
- if mode == source.ParseExported {
- // The AST filtering is a little buggy and may remove things it
- // shouldn't. If we only got undeclared name errors, try one more
- // time keeping those names.
- missing, unexpected := filter.ProcessErrors(pkg.typeErrors)
- if len(unexpected) == 0 && len(missing) != 0 {
- event.Log(ctx, fmt.Sprintf("discovered missing identifiers: %v", missing), tag.Package.Of(string(m.ID)))
- pkg, err = doTypeCheck(ctx, snapshot, m, mode, deps, filter)
- if err != nil {
- return nil, err
- }
- missing, unexpected = filter.ProcessErrors(pkg.typeErrors)
- }
- if len(unexpected) != 0 || len(missing) != 0 {
- event.Log(ctx, fmt.Sprintf("falling back to safe trimming due to type errors: %v or still-missing identifiers: %v", unexpected, missing), tag.Package.Of(string(m.ID)))
- pkg, err = doTypeCheck(ctx, snapshot, m, mode, deps, nil)
- if err != nil {
- return nil, err
- }
- }
- }
- // If this is a replaced module in the workspace, the version is
- // meaningless, and we don't want clients to access it.
- if m.Module != nil {
- version := m.Module.Version
- if source.IsWorkspaceModuleVersion(version) {
- version = ""
- }
- pkg.version = &module.Version{
- Path: m.Module.Path,
- Version: version,
- }
- }
-
- // We don't care about a package's errors unless we have parsed it in full.
- if mode != source.ParseFull {
- return pkg, nil
- }
-
- for _, e := range m.Errors {
- diags, err := goPackagesErrorDiagnostics(snapshot, pkg, e)
- if err != nil {
- event.Error(ctx, "unable to compute positions for list errors", err, tag.Package.Of(pkg.ID()))
- continue
- }
- pkg.diagnostics = append(pkg.diagnostics, diags...)
- }
-
- // Our heuristic for whether to show type checking errors is:
- // + If any file was 'fixed', don't show type checking errors as we
- // can't guarantee that they reference accurate locations in the source.
- // + 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[span.URI]bool{}
- for _, e := range pkg.parseErrors {
- diags, err := parseErrorDiagnostics(snapshot, pkg, e)
- if err != nil {
- event.Error(ctx, "unable to compute positions for parse errors", err, tag.Package.Of(pkg.ID()))
- continue
- }
- for _, diag := range diags {
- unparseable[diag.URI] = true
- pkg.diagnostics = append(pkg.diagnostics, diag)
- }
- }
-
- if pkg.hasFixedFiles {
- return pkg, nil
- }
-
- unexpanded := pkg.typeErrors
- pkg.typeErrors = nil
- for _, e := range expandErrors(unexpanded, snapshot.View().Options().RelatedInformationSupported) {
- diags, err := typeErrorDiagnostics(snapshot, pkg, e)
- if err != nil {
- event.Error(ctx, "unable to compute positions for type errors", err, tag.Package.Of(pkg.ID()))
- continue
- }
- pkg.typeErrors = append(pkg.typeErrors, e.primary)
- 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)
- }
- }
- }
-
- depsErrors, err := snapshot.depsErrors(ctx, pkg)
- if err != nil {
- return nil, err
- }
- pkg.diagnostics = append(pkg.diagnostics, depsErrors...)
-
- return pkg, nil
-}
-
-var goVersionRx = regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`)
-
-func doTypeCheck(ctx context.Context, snapshot *snapshot, m *Metadata, mode source.ParseMode, deps map[PackagePath]*packageHandle, astFilter *unexportedFilter) (*pkg, error) {
- ctx, done := event.Start(ctx, "cache.typeCheck", tag.Package.Of(string(m.ID)))
- defer done()
-
- pkg := &pkg{
- m: m,
- mode: mode,
- imports: make(map[PackagePath]*pkg),
- types: types.NewPackage(string(m.PkgPath), string(m.Name)),
- 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),
- Selections: make(map[*ast.SelectorExpr]*types.Selection),
- Scopes: make(map[ast.Node]*types.Scope),
- },
- typesSizes: m.TypesSizes,
- }
- typeparams.InitInstanceInfo(pkg.typesInfo)
-
- for _, gf := range pkg.m.GoFiles {
- // In the presence of line directives, we may need to report errors in
- // non-compiled Go files, so we need to register them on the package.
- // However, we only need to really parse them in ParseFull mode, when
- // the user might actually be looking at the file.
- fh, err := snapshot.GetFile(ctx, gf)
- if err != nil {
- return nil, err
- }
- goMode := source.ParseFull
- if mode != source.ParseFull {
- goMode = source.ParseHeader
- }
- pgf, err := snapshot.ParseGo(ctx, fh, goMode)
- if err != nil {
- return nil, err
- }
- pkg.goFiles = append(pkg.goFiles, pgf)
- }
-
- if err := parseCompiledGoFiles(ctx, snapshot, mode, pkg, astFilter); err != nil {
- return nil, err
- }
-
- // Use the default type information for the unsafe package.
- if m.PkgPath == "unsafe" {
- // Don't type check Unsafe: it's unnecessary, and doing so exposes a data
- // race to Unsafe.completed.
- pkg.types = types.Unsafe
- return pkg, nil
- }
-
- if len(m.CompiledGoFiles) == 0 {
- // No files most likely means go/packages failed. Try to attach error
- // messages to the file as much as possible.
- var found bool
- for _, e := range m.Errors {
- srcDiags, err := goPackagesErrorDiagnostics(snapshot, pkg, e)
- if err != nil {
- continue
- }
- found = true
- pkg.diagnostics = append(pkg.diagnostics, srcDiags...)
- }
- if found {
- return pkg, nil
- }
- return nil, errors.Errorf("no parsed files for package %s, expected: %v, errors: %v", pkg.m.PkgPath, pkg.compiledGoFiles, m.Errors)
- }
-
- cfg := &types.Config{
- Error: func(e error) {
- pkg.typeErrors = append(pkg.typeErrors, e.(types.Error))
- },
- Importer: importerFunc(func(pkgPath string) (*types.Package, error) {
- // If the context was cancelled, we should abort.
- if ctx.Err() != nil {
- return nil, ctx.Err()
- }
- dep := resolveImportPath(pkgPath, pkg, deps)
- if dep == nil {
- return nil, snapshot.missingPkgError(ctx, pkgPath)
- }
- if !source.IsValidImport(string(m.PkgPath), string(dep.m.PkgPath)) {
- return nil, errors.Errorf("invalid use of internal package %s", pkgPath)
- }
- depPkg, err := dep.check(ctx, snapshot)
- if err != nil {
- return nil, err
- }
- pkg.imports[depPkg.m.PkgPath] = depPkg
- return depPkg.types, nil
- }),
- }
- if pkg.m.Module != nil && pkg.m.Module.GoVersion != "" {
- goVersion := "go" + pkg.m.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) {
- typesinternal.SetGoVersion(cfg, goVersion)
- }
- }
-
- if mode != source.ParseFull {
- cfg.DisableUnusedImportCheck = true
- cfg.IgnoreFuncBodies = true
- }
-
- // We want to type check cgo code if go/types supports it.
- // We passed typecheckCgo to go/packages when we Loaded.
- typesinternal.SetUsesCgo(cfg)
-
- check := types.NewChecker(cfg, snapshot.FileSet(), pkg.types, pkg.typesInfo)
-
- var files []*ast.File
- for _, cgf := range pkg.compiledGoFiles {
- files = append(files, cgf.File)
- }
-
- // Type checking errors are handled via the config, so ignore them here.
- _ = check.Files(files)
-
- // 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()
- }
- return pkg, nil
-}
-
-func parseCompiledGoFiles(ctx context.Context, snapshot *snapshot, mode source.ParseMode, pkg *pkg, astFilter *unexportedFilter) error {
- for _, cgf := range pkg.m.CompiledGoFiles {
- fh, err := snapshot.GetFile(ctx, cgf)
- if err != nil {
- return err
- }
-
- var pgf *source.ParsedGoFile
- var fixed bool
- // Only parse Full through the cache -- we need to own Exported ASTs
- // to prune them.
- if mode == source.ParseFull {
- pgh := snapshot.parseGoHandle(ctx, fh, mode)
- pgf, fixed, err = snapshot.parseGo(ctx, pgh)
- } else {
- d := parseGo(ctx, snapshot.FileSet(), fh, mode)
- pgf, fixed, err = d.parsed, d.fixed, d.err
- }
- if err != nil {
- return err
- }
- pkg.compiledGoFiles = append(pkg.compiledGoFiles, pgf)
- if pgf.ParseErr != nil {
- pkg.parseErrors = append(pkg.parseErrors, pgf.ParseErr)
- }
- // If we have fixed parse errors in any of the files, we should hide type
- // errors, as they may be completely nonsensical.
- pkg.hasFixedFiles = pkg.hasFixedFiles || fixed
- }
- if mode != source.ParseExported {
- return nil
- }
- if astFilter != nil {
- var files []*ast.File
- for _, cgf := range pkg.compiledGoFiles {
- files = append(files, cgf.File)
- }
- astFilter.Filter(files)
- } else {
- for _, cgf := range pkg.compiledGoFiles {
- trimAST(cgf.File)
- }
- }
- return nil
-}
-
-func (s *snapshot) depsErrors(ctx context.Context, pkg *pkg) ([]*source.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 pkg.m.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 s.view.goversion < 15 {
- directImporterIdx = len(depsError.ImportStack) - 2
- }
- if directImporterIdx < 0 {
- continue
- }
-
- directImporter := depsError.ImportStack[directImporterIdx]
- if s.isWorkspacePackage(PackageID(directImporter)) {
- continue
- }
- relevantErrors = append(relevantErrors, depsError)
- }
-
- // Don't build the import index for nothing.
- if len(relevantErrors) == 0 {
- return nil, nil
- }
-
- // Build an index of all imports in the package.
- type fileImport struct {
- cgf *source.ParsedGoFile
- imp *ast.ImportSpec
- }
- allImports := map[string][]fileImport{}
- for _, cgf := range pkg.compiledGoFiles {
- for _, group := range astutil.Imports(s.FileSet(), cgf.File) {
- for _, imp := range group {
- if imp.Path == nil {
- continue
- }
- path := strings.Trim(imp.Path.Value, `"`)
- allImports[path] = append(allImports[path], fileImport{cgf, imp})
- }
- }
- }
-
- // Apply a diagnostic to any import involved in the error, stopping once
- // we reach the workspace.
- var errors []*source.Diagnostic
- for _, depErr := range relevantErrors {
- for i := len(depErr.ImportStack) - 1; i >= 0; i-- {
- item := depErr.ImportStack[i]
- if s.isWorkspacePackage(PackageID(item)) {
- break
- }
-
- for _, imp := range allImports[item] {
- rng, err := source.NewMappedRange(s.FileSet(), imp.cgf.Mapper, imp.imp.Pos(), imp.imp.End()).Range()
- if err != nil {
- return nil, err
- }
- fixes, err := goGetQuickFixes(s, imp.cgf.URI, item)
- if err != nil {
- return nil, err
- }
- errors = append(errors, &source.Diagnostic{
- URI: imp.cgf.URI,
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.TypeError,
- Message: fmt.Sprintf("error while importing %v: %v", item, depErr.Err),
- SuggestedFixes: fixes,
- })
- }
- }
- }
-
- if len(pkg.compiledGoFiles) == 0 {
- return errors, nil
- }
- mod := s.GoModForFile(pkg.compiledGoFiles[0].URI)
- if mod == "" {
- return errors, nil
- }
- fh, err := s.GetFile(ctx, mod)
- if err != nil {
- return nil, err
- }
- pm, err := s.ParseMod(ctx, fh)
- 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]
- m := s.getMetadata(PackageID(item))
- if m == nil || m.Module == nil {
- continue
- }
- modVer := module.Version{Path: m.Module.Path, Version: m.Module.Version}
- reference := findModuleReference(pm.File, modVer)
- if reference == nil {
- continue
- }
- rng, err := rangeFromPositions(pm.Mapper, reference.Start, reference.End)
- if err != nil {
- return nil, err
- }
- fixes, err := goGetQuickFixes(s, pm.URI, item)
- if err != nil {
- return nil, err
- }
- errors = append(errors, &source.Diagnostic{
- URI: pm.URI,
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.TypeError,
- Message: fmt.Sprintf("error while importing %v: %v", item, depErr.Err),
- SuggestedFixes: fixes,
- })
- break
- }
- }
- return errors, nil
-}
-
-// missingPkgError returns an error message for a missing package that varies
-// based on the user's workspace mode.
-func (s *snapshot) missingPkgError(ctx context.Context, pkgPath string) error {
- var b strings.Builder
- if s.workspaceMode()&moduleMode == 0 {
- gorootSrcPkg := filepath.FromSlash(filepath.Join(s.view.goroot, "src", pkgPath))
-
- b.WriteString(fmt.Sprintf("cannot find package %q in any of \n\t%s (from $GOROOT)", pkgPath, gorootSrcPkg))
-
- for _, gopath := range filepath.SplitList(s.view.gopath) {
- gopathSrcPkg := filepath.FromSlash(filepath.Join(gopath, "src", pkgPath))
- b.WriteString(fmt.Sprintf("\n\t%s (from $GOPATH)", gopathSrcPkg))
- }
- } else {
- b.WriteString(fmt.Sprintf("no required module provides package %q", pkgPath))
- if err := s.getInitializationError(ctx); err != nil {
- b.WriteString(fmt.Sprintf("(workspace configuration error: %s)", err.MainError))
- }
- }
- return errors.New(b.String())
-}
-
-type extendedError struct {
- primary types.Error
- secondaries []types.Error
-}
-
-func (e extendedError) Error() string {
- return e.primary.Error()
-}
-
-// expandErrors duplicates "secondary" errors by mapping them to their main
-// error. Some errors returned by the type checker are followed by secondary
-// errors which give more information about the error. These are errors in
-// their own right, and they are marked by starting with \t. For instance, when
-// there is a multiply-defined function, the secondary error points back to the
-// definition first noticed.
-//
-// This function associates the secondary error with its primary error, which can
-// then be used as RelatedInformation when the error becomes a diagnostic.
-//
-// If supportsRelatedInformation is false, the secondary is instead embedded as
-// additional context in the primary error.
-func expandErrors(errs []types.Error, supportsRelatedInformation bool) []extendedError {
- var result []extendedError
- for i := 0; i < len(errs); {
- original := extendedError{
- primary: errs[i],
- }
- for i++; i < len(errs); i++ {
- spl := errs[i]
- if len(spl.Msg) == 0 || spl.Msg[0] != '\t' {
- break
- }
- spl.Msg = spl.Msg[1:]
- original.secondaries = append(original.secondaries, spl)
- }
-
- // Clone the error to all its related locations -- VS Code, at least,
- // doesn't do it for us.
- result = append(result, original)
- for i, mainSecondary := range original.secondaries {
- // Create the new primary error, with a tweaked message, in the
- // secondary's location. We need to start from the secondary to
- // capture its unexported location fields.
- relocatedSecondary := mainSecondary
- if supportsRelatedInformation {
- relocatedSecondary.Msg = fmt.Sprintf("%v (see details)", original.primary.Msg)
- } else {
- relocatedSecondary.Msg = fmt.Sprintf("%v (this error: %v)", original.primary.Msg, mainSecondary.Msg)
- }
- relocatedSecondary.Soft = original.primary.Soft
-
- // Copy over the secondary errors, noting the location of the
- // current error we're cloning.
- clonedError := extendedError{primary: relocatedSecondary, secondaries: []types.Error{original.primary}}
- for j, secondary := range original.secondaries {
- if i == j {
- secondary.Msg += " (this error)"
- }
- clonedError.secondaries = append(clonedError.secondaries, secondary)
- }
- result = append(result, clonedError)
- }
-
- }
- return result
-}
-
-// resolveImportPath resolves an import path in pkg to a package from deps.
-// It should produce the same results as resolveImportPath:
-// https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/load/pkg.go;drc=641918ee09cb44d282a30ee8b66f99a0b63eaef9;l=990.
-func resolveImportPath(importPath string, pkg *pkg, deps map[PackagePath]*packageHandle) *packageHandle {
- if dep := deps[PackagePath(importPath)]; dep != nil {
- return dep
- }
- // We may be in GOPATH mode, in which case we need to check vendor dirs.
- searchDir := path.Dir(pkg.PkgPath())
- for {
- vdir := PackagePath(path.Join(searchDir, "vendor", importPath))
- if vdep := deps[vdir]; vdep != nil {
- return vdep
- }
-
- // Search until Dir doesn't take us anywhere new, e.g. "." or "/".
- next := path.Dir(searchDir)
- if searchDir == next {
- break
- }
- searchDir = next
- }
-
- // Vendor didn't work. Let's try minimal module compatibility mode.
- // In MMC, the packagePath is the canonical (.../vN/...) path, which
- // is hard to calculate. But the go command has already resolved the ID
- // to the non-versioned path, and we can take advantage of that.
- for _, dep := range deps {
- if dep.ID() == importPath {
- return dep
- }
- }
- 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) }
diff --git a/internal/lsp/cache/error_test.go b/internal/lsp/cache/error_test.go
deleted file mode 100644
index 43cc03f78..000000000
--- a/internal/lsp/cache/error_test.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// 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 (
- "strings"
- "testing"
-)
-
-func TestParseErrorMessage(t *testing.T) {
- tests := []struct {
- name string
- in string
- expectedFileName string
- expectedLine int
- expectedColumn int
- }{
- {
- name: "from go list output",
- in: "\nattributes.go:13:1: expected 'package', found 'type'",
- expectedFileName: "attributes.go",
- expectedLine: 13,
- expectedColumn: 1,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- spn := parseGoListError(tt.in, ".")
- fn := spn.URI().Filename()
-
- if !strings.HasSuffix(fn, tt.expectedFileName) {
- t.Errorf("expected filename with suffix %v but got %v", tt.expectedFileName, fn)
- }
-
- if !spn.HasPosition() {
- t.Fatalf("expected span to have position")
- }
-
- pos := spn.Start()
- if pos.Line() != tt.expectedLine {
- t.Errorf("expected line %v but got %v", tt.expectedLine, pos.Line())
- }
-
- if pos.Column() != tt.expectedColumn {
- t.Errorf("expected line %v but got %v", tt.expectedLine, pos.Line())
- }
- })
- }
-}
diff --git a/internal/lsp/cache/errors.go b/internal/lsp/cache/errors.go
deleted file mode 100644
index e9a86de35..000000000
--- a/internal/lsp/cache/errors.go
+++ /dev/null
@@ -1,411 +0,0 @@
-// 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/scanner"
- "go/token"
- "go/types"
- "regexp"
- "strconv"
- "strings"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/packages"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/typesinternal"
- errors "golang.org/x/xerrors"
-)
-
-func goPackagesErrorDiagnostics(snapshot *snapshot, pkg *pkg, e packages.Error) ([]*source.Diagnostic, error) {
- if msg, spn, ok := parseGoListImportCycleError(snapshot, e, pkg); ok {
- rng, err := spanToRange(pkg, spn)
- if err != nil {
- return nil, err
- }
- return []*source.Diagnostic{{
- URI: spn.URI(),
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.ListError,
- Message: msg,
- }}, nil
- }
-
- var spn span.Span
- if e.Pos == "" {
- spn = parseGoListError(e.Msg, pkg.m.Config.Dir)
- // We may not have been able to parse a valid span. Apply the errors to all files.
- if _, err := spanToRange(pkg, spn); err != nil {
- var diags []*source.Diagnostic
- for _, cgf := range pkg.compiledGoFiles {
- diags = append(diags, &source.Diagnostic{
- URI: cgf.URI,
- Severity: protocol.SeverityError,
- Source: source.ListError,
- Message: e.Msg,
- })
- }
- return diags, nil
- }
- } else {
- spn = span.ParseInDir(e.Pos, pkg.m.Config.Dir)
- }
-
- rng, err := spanToRange(pkg, spn)
- if err != nil {
- return nil, err
- }
- return []*source.Diagnostic{{
- URI: spn.URI(),
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.ListError,
- Message: e.Msg,
- }}, nil
-}
-
-func parseErrorDiagnostics(snapshot *snapshot, pkg *pkg, errList scanner.ErrorList) ([]*source.Diagnostic, error) {
- // The first parser error is likely the root cause of the problem.
- if errList.Len() <= 0 {
- return nil, errors.Errorf("no errors in %v", errList)
- }
- e := errList[0]
- pgf, err := pkg.File(span.URIFromPath(e.Pos.Filename))
- if err != nil {
- return nil, err
- }
- pos := pgf.Tok.Pos(e.Pos.Offset)
- spn, err := span.NewRange(snapshot.FileSet(), pos, pos).Span()
- if err != nil {
- return nil, err
- }
- rng, err := spanToRange(pkg, spn)
- if err != nil {
- return nil, err
- }
- return []*source.Diagnostic{{
- URI: spn.URI(),
- Range: rng,
- Severity: protocol.SeverityError,
- Source: 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 typeErrorDiagnostics(snapshot *snapshot, pkg *pkg, e extendedError) ([]*source.Diagnostic, error) {
- code, spn, err := typeErrorData(snapshot.FileSet(), pkg, e.primary)
- if err != nil {
- return nil, err
- }
- rng, err := spanToRange(pkg, spn)
- if err != nil {
- return nil, err
- }
- diag := &source.Diagnostic{
- URI: spn.URI(),
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.TypeError,
- Message: e.primary.Msg,
- }
- if code != 0 {
- diag.Code = code.String()
- diag.CodeHref = typesCodeHref(snapshot, code)
- }
-
- for _, secondary := range e.secondaries {
- _, secondarySpan, err := typeErrorData(snapshot.FileSet(), pkg, secondary)
- if err != nil {
- return nil, err
- }
- rng, err := spanToRange(pkg, secondarySpan)
- if err != nil {
- return nil, err
- }
- diag.Related = append(diag.Related, source.RelatedInformation{
- URI: secondarySpan.URI(),
- Range: rng,
- Message: secondary.Msg,
- })
- }
-
- if match := importErrorRe.FindStringSubmatch(e.primary.Msg); match != nil {
- diag.SuggestedFixes, err = goGetQuickFixes(snapshot, spn.URI(), match[1])
- if err != nil {
- return nil, err
- }
- }
- if match := unsupportedFeatureRe.FindStringSubmatch(e.primary.Msg); match != nil {
- diag.SuggestedFixes, err = editGoDirectiveQuickFix(snapshot, spn.URI(), match[1])
- if err != nil {
- return nil, err
- }
- }
- return []*source.Diagnostic{diag}, nil
-}
-
-func goGetQuickFixes(snapshot *snapshot, uri span.URI, pkg string) ([]source.SuggestedFix, error) {
- // Go get only supports module mode for now.
- if snapshot.workspaceMode()&moduleMode == 0 {
- return nil, nil
- }
- title := fmt.Sprintf("go get package %v", pkg)
- cmd, err := command.NewGoGetPackageCommand(title, command.GoGetPackageArgs{
- URI: protocol.URIFromSpanURI(uri),
- AddRequire: true,
- Pkg: pkg,
- })
- if err != nil {
- return nil, err
- }
- return []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)}, nil
-}
-
-func editGoDirectiveQuickFix(snapshot *snapshot, uri span.URI, version string) ([]source.SuggestedFix, error) {
- // Go mod edit only supports module mode.
- if snapshot.workspaceMode()&moduleMode == 0 {
- return nil, nil
- }
- title := fmt.Sprintf("go mod edit -go=%s", version)
- cmd, err := command.NewEditGoDirectiveCommand(title, command.EditGoDirectiveArgs{
- URI: protocol.URIFromSpanURI(uri),
- Version: version,
- })
- if err != nil {
- return nil, err
- }
- return []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)}, nil
-}
-
-func analysisDiagnosticDiagnostics(snapshot *snapshot, pkg *pkg, a *analysis.Analyzer, e *analysis.Diagnostic) ([]*source.Diagnostic, error) {
- var srcAnalyzer *source.Analyzer
- // Find the analyzer that generated this diagnostic.
- for _, sa := range source.EnabledAnalyzers(snapshot) {
- if a == sa.Analyzer {
- srcAnalyzer = sa
- break
- }
- }
-
- spn, err := span.NewRange(snapshot.FileSet(), e.Pos, e.End).Span()
- if err != nil {
- return nil, err
- }
- rng, err := spanToRange(pkg, spn)
- if err != nil {
- return nil, err
- }
- kinds := srcAnalyzer.ActionKind
- if len(srcAnalyzer.ActionKind) == 0 {
- kinds = append(kinds, protocol.QuickFix)
- }
- fixes, err := suggestedAnalysisFixes(snapshot, pkg, e, kinds)
- if err != nil {
- return nil, err
- }
- if srcAnalyzer.Fix != "" {
- cmd, err := command.NewApplyFixCommand(e.Message, command.ApplyFixArgs{
- URI: protocol.URIFromSpanURI(spn.URI()),
- Range: rng,
- Fix: srcAnalyzer.Fix,
- })
- if err != nil {
- return nil, err
- }
- for _, kind := range kinds {
- fixes = append(fixes, source.SuggestedFixFromCommand(cmd, kind))
- }
- }
- related, err := relatedInformation(pkg, snapshot.FileSet(), e)
- if err != nil {
- return nil, err
- }
-
- severity := srcAnalyzer.Severity
- if severity == 0 {
- severity = protocol.SeverityWarning
- }
- diag := &source.Diagnostic{
- URI: spn.URI(),
- Range: rng,
- Severity: severity,
- Source: source.AnalyzerErrorKind(e.Category),
- Message: e.Message,
- Related: related,
- SuggestedFixes: fixes,
- Analyzer: srcAnalyzer,
- }
- // If the fixes only delete code, assume that the diagnostic is reporting dead code.
- if onlyDeletions(fixes) {
- diag.Tags = []protocol.DiagnosticTag{protocol.Unnecessary}
- }
- return []*source.Diagnostic{diag}, nil
-}
-
-// onlyDeletions returns true if all of the suggested fixes are deletions.
-func onlyDeletions(fixes []source.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(snapshot *snapshot, code typesinternal.ErrorCode) string {
- target := snapshot.View().Options().LinkTarget
- return source.BuildLink(target, "golang.org/x/tools/internal/typesinternal", code.String())
-}
-
-func suggestedAnalysisFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic, kinds []protocol.CodeActionKind) ([]source.SuggestedFix, error) {
- var fixes []source.SuggestedFix
- for _, fix := range diag.SuggestedFixes {
- edits := make(map[span.URI][]protocol.TextEdit)
- for _, e := range fix.TextEdits {
- spn, err := span.NewRange(snapshot.FileSet(), e.Pos, e.End).Span()
- if err != nil {
- return nil, err
- }
- rng, err := spanToRange(pkg, spn)
- if err != nil {
- return nil, err
- }
- edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{
- Range: rng,
- NewText: string(e.NewText),
- })
- }
- for _, kind := range kinds {
- fixes = append(fixes, source.SuggestedFix{
- Title: fix.Message,
- Edits: edits,
- ActionKind: kind,
- })
- }
-
- }
- return fixes, nil
-}
-
-func relatedInformation(pkg *pkg, fset *token.FileSet, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) {
- var out []source.RelatedInformation
- for _, related := range diag.Related {
- spn, err := span.NewRange(fset, related.Pos, related.End).Span()
- if err != nil {
- return nil, err
- }
- rng, err := spanToRange(pkg, spn)
- if err != nil {
- return nil, err
- }
- out = append(out, source.RelatedInformation{
- URI: spn.URI(),
- Range: rng,
- Message: related.Message,
- })
- }
- return out, nil
-}
-
-func typeErrorData(fset *token.FileSet, pkg *pkg, terr types.Error) (typesinternal.ErrorCode, span.Span, error) {
- ecode, start, end, ok := typesinternal.ReadGo116ErrorData(terr)
- if !ok {
- start, end = terr.Pos, terr.Pos
- ecode = 0
- }
- posn := fset.Position(start)
- pgf, err := pkg.File(span.URIFromPath(posn.Filename))
- if err != nil {
- return 0, span.Span{}, err
- }
- if !end.IsValid() || end == start {
- end = analysisinternal.TypeErrorEndPos(fset, pgf.Src, start)
- }
- spn, err := parsedGoSpan(pgf, start, end)
- if err != nil {
- return 0, span.Span{}, err
- }
- return ecode, spn, nil
-}
-
-func parsedGoSpan(pgf *source.ParsedGoFile, start, end token.Pos) (span.Span, error) {
- return span.FileSpan(pgf.Tok, pgf.Mapper.Converter, start, end)
-}
-
-// spanToRange converts a span.Span to a protocol.Range,
-// assuming that the span belongs to the package whose diagnostics are being computed.
-func spanToRange(pkg *pkg, spn span.Span) (protocol.Range, error) {
- pgf, err := pkg.File(spn.URI())
- if err != nil {
- return protocol.Range{}, err
- }
- return pgf.Mapper.Range(spn)
-}
-
-// parseGoListError attempts to parse a standard `go list` error message
-// by stripping off the trailing error message.
-//
-// It works only on errors whose message is prefixed by colon,
-// followed by a space (": "). For example:
-//
-// attributes.go:13:1: expected 'package', found 'type'
-//
-func parseGoListError(input, wd string) span.Span {
- input = strings.TrimSpace(input)
- msgIndex := strings.Index(input, ": ")
- if msgIndex < 0 {
- return span.Parse(input)
- }
- return span.ParseInDir(input[:msgIndex], wd)
-}
-
-func parseGoListImportCycleError(snapshot *snapshot, e packages.Error, pkg *pkg) (string, span.Span, bool) {
- re := regexp.MustCompile(`(.*): import stack: \[(.+)\]`)
- matches := re.FindStringSubmatch(strings.TrimSpace(e.Msg))
- if len(matches) < 3 {
- return e.Msg, span.Span{}, false
- }
- 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 msg, span.Span{}, false
- }
- // Imports have quotation marks around them.
- circImp := strconv.Quote(importList[1])
- for _, cgf := range pkg.compiledGoFiles {
- // Search file imports for the import that is causing the import cycle.
- for _, imp := range cgf.File.Imports {
- if imp.Path.Value == circImp {
- spn, err := span.NewRange(snapshot.FileSet(), imp.Pos(), imp.End()).Span()
- if err != nil {
- return msg, span.Span{}, false
- }
- return msg, spn, true
- }
- }
- }
- return msg, span.Span{}, false
-}
diff --git a/internal/lsp/cache/imports.go b/internal/lsp/cache/imports.go
deleted file mode 100644
index 01a2468ef..000000000
--- a/internal/lsp/cache/imports.go
+++ /dev/null
@@ -1,201 +0,0 @@
-// 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"
- "reflect"
- "strings"
- "sync"
- "time"
-
- "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/lsp/source"
-)
-
-type importsState struct {
- ctx context.Context
-
- mu sync.Mutex
- processEnv *imports.ProcessEnv
- cleanupProcessEnv func()
- cacheRefreshDuration time.Duration
- cacheRefreshTimer *time.Timer
- cachedModFileHash string
- cachedBuildFlags []string
-}
-
-func (s *importsState) runProcessEnvFunc(ctx context.Context, snapshot *snapshot, fn func(*imports.Options) error) error {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- // Find the hash of the active mod file, 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.
- var modFileHash string
- // If we are using 'legacyWorkspace' mode, we can just read the modfile from
- // the snapshot. Otherwise, we need to get the synthetic workspace mod file.
- //
- // TODO(rfindley): we should be able to just always use the synthetic
- // workspace module, or alternatively use the go.work file.
- if snapshot.workspace.moduleSource == legacyWorkspace {
- for m := range snapshot.workspace.getActiveModFiles() { // range to access the only element
- modFH, err := snapshot.GetFile(ctx, m)
- if err != nil {
- return err
- }
- modFileHash = modFH.FileIdentity().Hash
- }
- } else {
- modFile, err := snapshot.workspace.modFile(ctx, snapshot)
- if err != nil {
- return err
- }
- modBytes, err := modFile.Format()
- if err != nil {
- return err
- }
- modFileHash = hashContents(modBytes)
- }
-
- // view.goEnv is immutable -- changes make a new view. Options can change.
- // We can't compare build flags directly because we may add -modfile.
- snapshot.view.optionsMu.Lock()
- localPrefix := snapshot.view.options.Local
- currentBuildFlags := snapshot.view.options.BuildFlags
- changed := !reflect.DeepEqual(currentBuildFlags, s.cachedBuildFlags) ||
- snapshot.view.options.VerboseOutput != (s.processEnv.Logf != nil) ||
- modFileHash != s.cachedModFileHash
- snapshot.view.optionsMu.Unlock()
-
- // If anything relevant to imports has changed, clear caches and
- // update the processEnv. Clearing caches blocks on any background
- // scans.
- if changed {
- // As a special case, skip cleanup the first time -- we haven't fully
- // initialized the environment yet and calling GetResolver will do
- // unnecessary work and potentially mess up the go.mod file.
- if s.cleanupProcessEnv != nil {
- if resolver, err := s.processEnv.GetResolver(); err == nil {
- if modResolver, ok := resolver.(*imports.ModuleResolver); ok {
- modResolver.ClearForNewMod()
- }
- }
- s.cleanupProcessEnv()
- }
- s.cachedModFileHash = modFileHash
- s.cachedBuildFlags = currentBuildFlags
- var err error
- s.cleanupProcessEnv, err = s.populateProcessEnv(ctx, snapshot)
- if err != nil {
- return err
- }
- }
-
- // Run the user function.
- opts := &imports.Options{
- // Defaults.
- AllErrors: true,
- Comments: true,
- Fragment: true,
- FormatOnly: false,
- TabIndent: true,
- TabWidth: 8,
- Env: s.processEnv,
- LocalPrefix: localPrefix,
- }
-
- if err := fn(opts); err != nil {
- return err
- }
-
- if s.cacheRefreshTimer == nil {
- // Don't refresh more than twice per minute.
- delay := 30 * time.Second
- // Don't spend more than a couple percent of the time refreshing.
- if adaptive := 50 * s.cacheRefreshDuration; adaptive > delay {
- delay = adaptive
- }
- s.cacheRefreshTimer = time.AfterFunc(delay, s.refreshProcessEnv)
- }
-
- return nil
-}
-
-// populateProcessEnv sets the dynamically configurable fields for the view's
-// process environment. Assumes that the caller is holding the s.view.importsMu.
-func (s *importsState) populateProcessEnv(ctx context.Context, snapshot *snapshot) (cleanup func(), err error) {
- pe := s.processEnv
-
- if snapshot.view.Options().VerboseOutput {
- pe.Logf = func(format string, args ...interface{}) {
- event.Log(ctx, fmt.Sprintf(format, args...))
- }
- } else {
- pe.Logf = nil
- }
-
- // Take an extra reference to the snapshot so that its workspace directory
- // (if any) isn't destroyed while we're using it.
- release := snapshot.generation.Acquire()
- _, inv, cleanupInvocation, err := snapshot.goCommandInvocation(ctx, source.LoadWorkspace, &gocommand.Invocation{
- WorkingDir: snapshot.view.rootURI.Filename(),
- })
- if err != nil {
- return nil, err
- }
- pe.WorkingDir = inv.WorkingDir
- pe.BuildFlags = inv.BuildFlags
- pe.WorkingDir = inv.WorkingDir
- pe.ModFile = inv.ModFile
- pe.ModFlag = inv.ModFlag
- pe.Env = map[string]string{}
- for _, kv := range inv.Env {
- split := strings.SplitN(kv, "=", 2)
- if len(split) != 2 {
- continue
- }
- pe.Env[split[0]] = split[1]
- }
-
- return func() {
- cleanupInvocation()
- release()
- }, nil
-}
-
-func (s *importsState) refreshProcessEnv() {
- start := time.Now()
-
- s.mu.Lock()
- env := s.processEnv
- if resolver, err := s.processEnv.GetResolver(); err == nil {
- resolver.ClearForNewScan()
- }
- s.mu.Unlock()
-
- event.Log(s.ctx, "background imports cache refresh starting")
- if err := imports.PrimeCache(context.Background(), env); err == nil {
- event.Log(s.ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)))
- } else {
- event.Log(s.ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)), keys.Err.Of(err))
- }
- s.mu.Lock()
- s.cacheRefreshDuration = time.Since(start)
- s.cacheRefreshTimer = nil
- s.mu.Unlock()
-}
-
-func (s *importsState) destroy() {
- s.mu.Lock()
- if s.cleanupProcessEnv != nil {
- s.cleanupProcessEnv()
- }
- s.mu.Unlock()
-}
diff --git a/internal/lsp/cache/keys.go b/internal/lsp/cache/keys.go
deleted file mode 100644
index 449daba3a..000000000
--- a/internal/lsp/cache/keys.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// 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 (
- "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/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
deleted file mode 100644
index 17b7acae0..000000000
--- a/internal/lsp/cache/load.go
+++ /dev/null
@@ -1,507 +0,0 @@
-// 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"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "strings"
- "time"
-
- "golang.org/x/tools/go/packages"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/gocommand"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/memoize"
- "golang.org/x/tools/internal/packagesinternal"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// load calls packages.Load for the given scopes, updating package metadata,
-// import graph, and mapped files with the result.
-func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...interface{}) (err error) {
- var query []string
- var containsDir bool // for logging
- for _, scope := range scopes {
- if !s.shouldLoad(scope) {
- continue
- }
- // Unless the context was canceled, set "shouldLoad" to false for all
- // of the metadata we attempted to load.
- defer func() {
- if errors.Is(err, context.Canceled) {
- return
- }
- s.clearShouldLoad(scope)
- }()
- switch scope := scope.(type) {
- case PackagePath:
- if source.IsCommandLineArguments(string(scope)) {
- panic("attempted to load command-line-arguments")
- }
- // 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 fileURI:
- uri := span.URI(scope)
- // Don't try to load a file that doesn't exist.
- fh := s.FindFile(uri)
- if fh == nil || s.View().FileKind(fh) != source.Go {
- continue
- }
- query = append(query, fmt.Sprintf("file=%s", uri.Filename()))
- case moduleLoadScope:
- switch scope {
- case "std", "cmd":
- query = append(query, string(scope))
- default:
- query = append(query, fmt.Sprintf("%s/...", scope))
- }
- case viewLoadScope:
- // If we are outside of GOPATH, a module, or some other known
- // build system, don't load subdirectories.
- if !s.ValidBuildConfiguration() {
- query = append(query, "./")
- } else {
- query = append(query, "./...")
- }
- default:
- panic(fmt.Sprintf("unknown scope type %T", scope))
- }
- switch scope.(type) {
- case viewLoadScope, moduleLoadScope:
- containsDir = true
- }
- }
- if len(query) == 0 {
- return nil
- }
- sort.Strings(query) // for determinism
-
- if s.view.Options().VerboseWorkDoneProgress {
- work := s.view.session.progress.Start(ctx, "Load", fmt.Sprintf("Loading query=%s", query), nil, nil)
- defer func() {
- work.End("Done.")
- }()
- }
-
- ctx, done := event.Start(ctx, "cache.view.load", tag.Query.Of(query))
- defer done()
-
- flags := source.LoadWorkspace
- if allowNetwork {
- flags |= source.AllowNetwork
- }
- _, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{
- WorkingDir: s.view.rootURI.Filename(),
- })
- if err != nil {
- return err
- }
-
- // 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...)
- cleanup()
-
- // 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()
- }
- if err != nil {
- event.Error(ctx, "go/packages.Load", err, tag.Snapshot.Of(s.ID()), tag.Directory.Of(cfg.Dir), tag.Query.Of(query), tag.PackageCount.Of(len(pkgs)))
- } else {
- event.Log(ctx, "go/packages.Load", tag.Snapshot.Of(s.ID()), tag.Directory.Of(cfg.Dir), tag.Query.Of(query), tag.PackageCount.Of(len(pkgs)))
- }
- if len(pkgs) == 0 {
- if err == nil {
- err = fmt.Errorf("no packages returned")
- }
- return errors.Errorf("%v: %w", err, source.PackagesLoadError)
- }
- for _, pkg := range pkgs {
- if !containsDir || s.view.Options().VerboseOutput {
- event.Log(ctx, "go/packages.Load",
- tag.Snapshot.Of(s.ID()),
- tag.Package.Of(pkg.ID),
- tag.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 errors.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.gocache) {
- continue
- }
- // Skip filtered packages. They may be added anyway if they're
- // dependencies of non-filtered packages.
- if s.view.allFilesExcluded(pkg) {
- continue
- }
- // Set the metadata for this package.
- s.mu.Lock()
- m, err := s.setMetadataLocked(ctx, PackagePath(pkg.PkgPath), pkg, cfg, query, map[PackageID]struct{}{})
- s.mu.Unlock()
- if err != nil {
- return err
- }
- if _, err := s.buildPackageHandle(ctx, m.ID, s.workspaceParseMode(m.ID)); err != nil {
- return err
- }
- }
- // Rebuild the import graph when the metadata is updated.
- s.clearAndRebuildImportGraph()
-
- return nil
-}
-
-// workspaceLayoutErrors returns a diagnostic for every open file, as well as
-// an error message if there are no open files.
-func (s *snapshot) workspaceLayoutError(ctx context.Context) *source.CriticalError {
- if len(s.workspace.getKnownModFiles()) == 0 {
- return nil
- }
- if s.view.userGo111Module == off {
- return nil
- }
- if s.workspace.moduleSource != legacyWorkspace {
- return nil
- }
- // If the user has one module per view, there is nothing to warn about.
- if s.ValidBuildConfiguration() && len(s.workspace.getKnownModFiles()) == 1 {
- return nil
- }
-
- // Apply diagnostics about the workspace configuration to relevant open
- // files.
- openFiles := s.openFiles()
-
- // If the snapshot does not have a valid build configuration, it may be
- // that the user has opened a directory that contains multiple modules.
- // Check for that an warn about it.
- if !s.ValidBuildConfiguration() {
- msg := `gopls requires a module at the root of your workspace.
-You can work with multiple modules by opening each one as a workspace folder.
-Improvements to this workflow will be coming soon, and you can learn more here:
-https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.`
- return &source.CriticalError{
- MainError: errors.Errorf(msg),
- DiagList: s.applyCriticalErrorToFiles(ctx, msg, openFiles),
- }
- }
-
- // If the user has one active go.mod file, they may still be editing files
- // in nested modules. Check the module of each open file and add warnings
- // that the nested module must be opened as a workspace folder.
- if len(s.workspace.getActiveModFiles()) == 1 {
- // Get the active root go.mod file to compare against.
- var rootModURI span.URI
- for uri := range s.workspace.getActiveModFiles() {
- rootModURI = uri
- }
- nestedModules := map[string][]source.VersionedFileHandle{}
- for _, fh := range openFiles {
- modURI := moduleForURI(s.workspace.knownModFiles, fh.URI())
- if modURI != rootModURI {
- modDir := filepath.Dir(modURI.Filename())
- nestedModules[modDir] = append(nestedModules[modDir], fh)
- }
- }
- // Add a diagnostic to each file in a nested module to mark it as
- // "orphaned". Don't show a general diagnostic in the progress bar,
- // because the user may still want to edit a file in a nested module.
- var srcDiags []*source.Diagnostic
- for modDir, uris := range nestedModules {
- msg := fmt.Sprintf(`This file is in %s, which is a nested module in the %s module.
-gopls currently requires one module per workspace folder.
-Please open %s as a separate workspace folder.
-You can learn more here: https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.
-`, modDir, filepath.Dir(rootModURI.Filename()), modDir)
- srcDiags = append(srcDiags, s.applyCriticalErrorToFiles(ctx, msg, uris)...)
- }
- if len(srcDiags) != 0 {
- return &source.CriticalError{
- MainError: errors.Errorf(`You are working in a nested module.
-Please open it as a separate workspace folder. Learn more:
-https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.`),
- DiagList: srcDiags,
- }
- }
- }
- return nil
-}
-
-func (s *snapshot) applyCriticalErrorToFiles(ctx context.Context, msg string, files []source.VersionedFileHandle) []*source.Diagnostic {
- var srcDiags []*source.Diagnostic
- for _, fh := range files {
- // Place the diagnostics on the package or module declarations.
- var rng protocol.Range
- switch s.view.FileKind(fh) {
- case source.Go:
- if pgf, err := s.ParseGo(ctx, fh, source.ParseHeader); err == nil {
- pkgDecl := span.NewRange(s.FileSet(), pgf.File.Package, pgf.File.Name.End())
- if spn, err := pkgDecl.Span(); err == nil {
- rng, _ = pgf.Mapper.Range(spn)
- }
- }
- case source.Mod:
- if pmf, err := s.ParseMod(ctx, fh); err == nil {
- if pmf.File.Module != nil && pmf.File.Module.Syntax != nil {
- rng, _ = rangeFromPositions(pmf.Mapper, pmf.File.Module.Syntax.Start, pmf.File.Module.Syntax.End)
- }
- }
- }
- srcDiags = append(srcDiags, &source.Diagnostic{
- URI: fh.URI(),
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.ListError,
- Message: msg,
- })
- }
- return srcDiags
-}
-
-type workspaceDirKey string
-
-type workspaceDirData struct {
- dir string
- err error
-}
-
-// getWorkspaceDir gets the URI for the workspace directory associated with
-// this snapshot. The workspace directory is a temp directory containing the
-// go.mod file computed from all active modules.
-func (s *snapshot) getWorkspaceDir(ctx context.Context) (span.URI, error) {
- s.mu.Lock()
- h := s.workspaceDirHandle
- s.mu.Unlock()
- if h != nil {
- return getWorkspaceDir(ctx, h, s.generation)
- }
- file, err := s.workspace.modFile(ctx, s)
- if err != nil {
- return "", err
- }
- hash := sha256.New()
- modContent, err := file.Format()
- if err != nil {
- return "", err
- }
- sumContent, err := s.workspace.sumFile(ctx, s)
- if err != nil {
- return "", err
- }
- hash.Write(modContent)
- hash.Write(sumContent)
- key := workspaceDirKey(hash.Sum(nil))
- s.mu.Lock()
- h = s.generation.Bind(key, func(context.Context, memoize.Arg) interface{} {
- tmpdir, err := ioutil.TempDir("", "gopls-workspace-mod")
- if err != nil {
- return &workspaceDirData{err: err}
- }
-
- for name, content := range map[string][]byte{
- "go.mod": modContent,
- "go.sum": sumContent,
- } {
- filename := filepath.Join(tmpdir, name)
- if err := ioutil.WriteFile(filename, content, 0644); err != nil {
- os.RemoveAll(tmpdir)
- return &workspaceDirData{err: err}
- }
- }
-
- return &workspaceDirData{dir: tmpdir}
- }, func(v interface{}) {
- d := v.(*workspaceDirData)
- if d.dir != "" {
- if err := os.RemoveAll(d.dir); err != nil {
- event.Error(context.Background(), "cleaning workspace dir", err)
- }
- }
- })
- s.workspaceDirHandle = h
- s.mu.Unlock()
- return getWorkspaceDir(ctx, h, s.generation)
-}
-
-func getWorkspaceDir(ctx context.Context, h *memoize.Handle, g *memoize.Generation) (span.URI, error) {
- v, err := h.Get(ctx, g, nil)
- if err != nil {
- return "", err
- }
- return span.URIFromPath(v.(*workspaceDirData).dir), nil
-}
-
-// setMetadataLocked extracts metadata from pkg and records it in s. It
-// recurses through pkg.Imports to ensure that metadata exists for all
-// dependencies.
-func (s *snapshot) setMetadataLocked(ctx context.Context, pkgPath PackagePath, pkg *packages.Package, cfg *packages.Config, query []string, seen map[PackageID]struct{}) (*Metadata, error) {
- id := PackageID(pkg.ID)
- if source.IsCommandLineArguments(pkg.ID) {
- suffix := ":" + strings.Join(query, ",")
- id = PackageID(string(id) + suffix)
- pkgPath = PackagePath(string(pkgPath) + suffix)
- }
- if _, ok := seen[id]; ok {
- return nil, errors.Errorf("import cycle detected: %q", id)
- }
- // Recreate the metadata rather than reusing it to avoid locking.
- m := &Metadata{
- ID: id,
- PkgPath: pkgPath,
- Name: PackageName(pkg.Name),
- ForTest: PackagePath(packagesinternal.GetForTest(pkg)),
- TypesSizes: pkg.TypesSizes,
- Config: cfg,
- Module: pkg.Module,
- depsErrors: packagesinternal.GetDepsErrors(pkg),
- }
-
- for _, err := range pkg.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(err.Msg, "expected '") {
- continue
- }
- m.Errors = append(m.Errors, err)
- }
-
- uris := map[span.URI]struct{}{}
- for _, filename := range pkg.CompiledGoFiles {
- uri := span.URIFromPath(filename)
- m.CompiledGoFiles = append(m.CompiledGoFiles, uri)
- uris[uri] = struct{}{}
- }
- for _, filename := range pkg.GoFiles {
- uri := span.URIFromPath(filename)
- m.GoFiles = append(m.GoFiles, uri)
- uris[uri] = struct{}{}
- }
- s.updateIDForURIsLocked(id, uris)
-
- // TODO(rstambler): is this still necessary?
- copied := map[PackageID]struct{}{
- id: {},
- }
- for k, v := range seen {
- copied[k] = v
- }
- for importPath, importPkg := range pkg.Imports {
- importPkgPath := PackagePath(importPath)
- importID := PackageID(importPkg.ID)
-
- m.Deps = append(m.Deps, importID)
-
- // Don't remember any imports with significant errors.
- if importPkgPath != "unsafe" && len(importPkg.CompiledGoFiles) == 0 {
- if m.MissingDeps == nil {
- m.MissingDeps = make(map[PackagePath]struct{})
- }
- m.MissingDeps[importPkgPath] = struct{}{}
- continue
- }
- if s.noValidMetadataForIDLocked(importID) {
- if _, err := s.setMetadataLocked(ctx, importPkgPath, importPkg, cfg, query, copied); err != nil {
- event.Error(ctx, "error in dependency", err)
- }
- }
- }
-
- // Add the metadata to the cache.
-
- // If we've already set the metadata for this snapshot, reuse it.
- if original, ok := s.metadata[m.ID]; ok && original.Valid {
- // Since we've just reloaded, clear out shouldLoad.
- original.ShouldLoad = false
- m = original.Metadata
- } else {
- s.metadata[m.ID] = &KnownMetadata{
- Metadata: m,
- Valid: true,
- }
- // Invalidate any packages we may have associated with this metadata.
- for _, mode := range []source.ParseMode{source.ParseHeader, source.ParseExported, source.ParseFull} {
- key := packageKey{mode, m.ID}
- delete(s.packages, key)
- }
- }
-
- // Set the workspace packages. If any of the package's files belong to the
- // view, then the package may be a workspace package.
- for _, uri := range append(m.CompiledGoFiles, m.GoFiles...) {
- if !s.view.contains(uri) {
- continue
- }
-
- // The package's files are in this view. It may be a workspace package.
- if strings.Contains(string(uri), "/vendor/") {
- // Vendored packages are not likely to be interesting to the user.
- continue
- }
-
- switch {
- case m.ForTest == "":
- // A normal package.
- s.workspacePackages[m.ID] = pkgPath
- case m.ForTest == m.PkgPath, m.ForTest+"_test" == m.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.
- s.workspacePackages[m.ID] = m.ForTest
- default:
- // A test variant of some intermediate package. We don't care about it.
- m.IsIntermediateTestVariant = true
- }
- }
- return m, nil
-}
-
-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 !source.InDir(gocache, pkg.GoFiles[0]) {
- return false
- }
- return true
-}
diff --git a/internal/lsp/cache/metadata.go b/internal/lsp/cache/metadata.go
deleted file mode 100644
index 618578dd8..000000000
--- a/internal/lsp/cache/metadata.go
+++ /dev/null
@@ -1,74 +0,0 @@
-// 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 (
- "go/types"
-
- "golang.org/x/tools/go/packages"
- "golang.org/x/tools/internal/packagesinternal"
- "golang.org/x/tools/internal/span"
-)
-
-// 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
- PackagePath string
- PackageName string
-)
-
-// Metadata holds package Metadata extracted from a call to packages.Load.
-type Metadata struct {
- ID PackageID
- PkgPath PackagePath
- Name PackageName
- GoFiles []span.URI
- CompiledGoFiles []span.URI
- ForTest PackagePath
- TypesSizes types.Sizes
- Errors []packages.Error
- Deps []PackageID
- MissingDeps map[PackagePath]struct{}
- Module *packages.Module
- depsErrors []*packagesinternal.PackageError
-
- // Config is the *packages.Config associated with the loaded package.
- Config *packages.Config
-
- // IsIntermediateTestVariant reports whether the given package is an
- // intermediate test variant, e.g.
- // "golang.org/x/tools/internal/lsp/cache [golang.org/x/tools/internal/lsp/source.test]".
- IsIntermediateTestVariant bool
-}
-
-// Name implements the source.Metadata interface.
-func (m *Metadata) PackageName() string {
- return string(m.Name)
-}
-
-// PkgPath implements the source.Metadata interface.
-func (m *Metadata) PackagePath() string {
- return string(m.PkgPath)
-}
-
-// ModuleInfo implements the source.Metadata interface.
-func (m *Metadata) ModuleInfo() *packages.Module {
- return m.Module
-}
-
-// KnownMetadata is a wrapper around metadata that tracks its validity.
-type KnownMetadata struct {
- *Metadata
-
- // Valid is true if the given metadata is Valid.
- // Invalid metadata can still be used if a metadata reload fails.
- Valid bool
-
- // ShouldLoad is true if the given metadata should be reloaded.
- ShouldLoad bool
-}
diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go
deleted file mode 100644
index 8a2d42abc..000000000
--- a/internal/lsp/cache/mod.go
+++ /dev/null
@@ -1,516 +0,0 @@
-// 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"
- "path/filepath"
- "regexp"
- "strings"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/mod/module"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/gocommand"
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/memoize"
- "golang.org/x/tools/internal/span"
-)
-
-type parseModHandle struct {
- handle *memoize.Handle
-}
-
-type parseModData struct {
- parsed *source.ParsedModule
-
- // err is any error encountered while parsing the file.
- err error
-}
-
-func (mh *parseModHandle) parse(ctx context.Context, snapshot *snapshot) (*source.ParsedModule, error) {
- v, err := mh.handle.Get(ctx, snapshot.generation, snapshot)
- if err != nil {
- return nil, err
- }
- data := v.(*parseModData)
- return data.parsed, data.err
-}
-
-func (s *snapshot) ParseMod(ctx context.Context, modFH source.FileHandle) (*source.ParsedModule, error) {
- if handle := s.getParseModHandle(modFH.URI()); handle != nil {
- return handle.parse(ctx, s)
- }
- h := s.generation.Bind(modFH.FileIdentity(), func(ctx context.Context, _ memoize.Arg) interface{} {
- _, done := event.Start(ctx, "cache.ParseModHandle", tag.URI.Of(modFH.URI()))
- defer done()
-
- contents, err := modFH.Read()
- if err != nil {
- return &parseModData{err: err}
- }
- m := &protocol.ColumnMapper{
- URI: modFH.URI(),
- Converter: span.NewContentConverter(modFH.URI().Filename(), contents),
- Content: contents,
- }
- file, parseErr := modfile.Parse(modFH.URI().Filename(), contents, nil)
- // Attempt to convert the error to a standardized parse error.
- var parseErrors []*source.Diagnostic
- if parseErr != nil {
- mfErrList, ok := parseErr.(modfile.ErrorList)
- if !ok {
- return &parseModData{err: fmt.Errorf("unexpected parse error type %v", parseErr)}
- }
- for _, mfErr := range mfErrList {
- rng, err := rangeFromPositions(m, mfErr.Pos, mfErr.Pos)
- if err != nil {
- return &parseModData{err: err}
- }
- parseErrors = append(parseErrors, &source.Diagnostic{
- URI: modFH.URI(),
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.ParseError,
- Message: mfErr.Err.Error(),
- })
- }
- }
- return &parseModData{
- parsed: &source.ParsedModule{
- URI: modFH.URI(),
- Mapper: m,
- File: file,
- ParseErrors: parseErrors,
- },
- err: parseErr,
- }
- }, nil)
-
- pmh := &parseModHandle{handle: h}
- s.mu.Lock()
- s.parseModHandles[modFH.URI()] = pmh
- s.mu.Unlock()
-
- return pmh.parse(ctx, s)
-}
-
-type parseWorkHandle struct {
- handle *memoize.Handle
-}
-
-type parseWorkData struct {
- parsed *source.ParsedWorkFile
-
- // err is any error encountered while parsing the file.
- err error
-}
-
-func (mh *parseWorkHandle) parse(ctx context.Context, snapshot *snapshot) (*source.ParsedWorkFile, error) {
- v, err := mh.handle.Get(ctx, snapshot.generation, snapshot)
- if err != nil {
- return nil, err
- }
- data := v.(*parseWorkData)
- return data.parsed, data.err
-}
-
-func (s *snapshot) ParseWork(ctx context.Context, modFH source.FileHandle) (*source.ParsedWorkFile, error) {
- if handle := s.getParseWorkHandle(modFH.URI()); handle != nil {
- return handle.parse(ctx, s)
- }
- h := s.generation.Bind(modFH.FileIdentity(), func(ctx context.Context, _ memoize.Arg) interface{} {
- _, done := event.Start(ctx, "cache.ParseModHandle", tag.URI.Of(modFH.URI()))
- defer done()
-
- contents, err := modFH.Read()
- if err != nil {
- return &parseWorkData{err: err}
- }
- m := &protocol.ColumnMapper{
- URI: modFH.URI(),
- Converter: span.NewContentConverter(modFH.URI().Filename(), contents),
- Content: contents,
- }
- file, parseErr := modfile.ParseWork(modFH.URI().Filename(), contents, nil)
- // Attempt to convert the error to a standardized parse error.
- var parseErrors []*source.Diagnostic
- if parseErr != nil {
- mfErrList, ok := parseErr.(modfile.ErrorList)
- if !ok {
- return &parseWorkData{err: fmt.Errorf("unexpected parse error type %v", parseErr)}
- }
- for _, mfErr := range mfErrList {
- rng, err := rangeFromPositions(m, mfErr.Pos, mfErr.Pos)
- if err != nil {
- return &parseWorkData{err: err}
- }
- parseErrors = append(parseErrors, &source.Diagnostic{
- URI: modFH.URI(),
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.ParseError,
- Message: mfErr.Err.Error(),
- })
- }
- }
- return &parseWorkData{
- parsed: &source.ParsedWorkFile{
- URI: modFH.URI(),
- Mapper: m,
- File: file,
- ParseErrors: parseErrors,
- },
- err: parseErr,
- }
- }, nil)
-
- pwh := &parseWorkHandle{handle: h}
- s.mu.Lock()
- s.parseWorkHandles[modFH.URI()] = pwh
- s.mu.Unlock()
-
- return pwh.parse(ctx, s)
-}
-
-// goSum reads the go.sum file for the go.mod file at modURI, if it exists. If
-// it doesn't exist, it returns nil.
-func (s *snapshot) goSum(ctx context.Context, modURI span.URI) []byte {
- // Get the go.sum file, either from the snapshot or directly from the
- // cache. Avoid (*snapshot).GetFile here, as we don't want to add
- // nonexistent file handles to the snapshot if the file does not exist.
- sumURI := span.URIFromPath(sumFilename(modURI))
- var sumFH source.FileHandle = s.FindFile(sumURI)
- if sumFH == nil {
- var err error
- sumFH, err = s.view.session.cache.getFile(ctx, sumURI)
- if err != nil {
- return nil
- }
- }
- content, err := sumFH.Read()
- if err != nil {
- return nil
- }
- return content
-}
-
-func sumFilename(modURI span.URI) string {
- return strings.TrimSuffix(modURI.Filename(), ".mod") + ".sum"
-}
-
-// modKey is uniquely identifies cached data for `go mod why` or dependencies
-// to upgrade.
-type modKey struct {
- sessionID, env, view string
- mod source.FileIdentity
- verb modAction
-}
-
-type modAction int
-
-const (
- why modAction = iota
- upgrade
-)
-
-type modWhyHandle struct {
- handle *memoize.Handle
-}
-
-type modWhyData struct {
- // why keeps track of the `go mod why` results for each require statement
- // in the go.mod file.
- why map[string]string
-
- err error
-}
-
-func (mwh *modWhyHandle) why(ctx context.Context, snapshot *snapshot) (map[string]string, error) {
- v, err := mwh.handle.Get(ctx, snapshot.generation, snapshot)
- if err != nil {
- return nil, err
- }
- data := v.(*modWhyData)
- return data.why, data.err
-}
-
-func (s *snapshot) ModWhy(ctx context.Context, fh source.FileHandle) (map[string]string, error) {
- if s.View().FileKind(fh) != source.Mod {
- return nil, fmt.Errorf("%s is not a go.mod file", fh.URI())
- }
- if handle := s.getModWhyHandle(fh.URI()); handle != nil {
- return handle.why(ctx, s)
- }
- key := modKey{
- sessionID: s.view.session.id,
- env: hashEnv(s),
- mod: fh.FileIdentity(),
- view: s.view.rootURI.Filename(),
- verb: why,
- }
- h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
- ctx, done := event.Start(ctx, "cache.ModWhyHandle", tag.URI.Of(fh.URI()))
- defer done()
-
- snapshot := arg.(*snapshot)
-
- pm, err := snapshot.ParseMod(ctx, fh)
- if err != nil {
- return &modWhyData{err: err}
- }
- // No requires to explain.
- if len(pm.File.Require) == 0 {
- return &modWhyData{}
- }
- // Run `go mod why` on all the dependencies.
- inv := &gocommand.Invocation{
- Verb: "mod",
- Args: []string{"why", "-m"},
- WorkingDir: filepath.Dir(fh.URI().Filename()),
- }
- for _, req := range pm.File.Require {
- inv.Args = append(inv.Args, req.Mod.Path)
- }
- stdout, err := snapshot.RunGoCommandDirect(ctx, source.Normal, inv)
- if err != nil {
- return &modWhyData{err: err}
- }
- whyList := strings.Split(stdout.String(), "\n\n")
- if len(whyList) != len(pm.File.Require) {
- return &modWhyData{
- err: 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 &modWhyData{why: why}
- }, nil)
-
- mwh := &modWhyHandle{handle: h}
- s.mu.Lock()
- s.modWhyHandles[fh.URI()] = mwh
- s.mu.Unlock()
-
- return mwh.why(ctx, s)
-}
-
-// extractGoCommandError tries to parse errors that come from the go command
-// and shape them into go.mod diagnostics.
-func (s *snapshot) extractGoCommandErrors(ctx context.Context, goCmdError string) ([]*source.Diagnostic, error) {
- diagLocations := map[*source.ParsedModule]span.Span{}
- backupDiagLocations := map[*source.ParsedModule]span.Span{}
-
- // 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.
- if strings.Contains(goCmdError, "errors parsing go.mod") {
- return nil, nil
- }
-
- // Match the error against all the mod files in the workspace.
- for _, uri := range s.ModFiles() {
- fh, err := s.GetFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- pm, err := s.ParseMod(ctx, fh)
- if err != nil {
- return nil, err
- }
- spn, found, err := s.matchErrorToModule(ctx, pm, goCmdError)
- if err != nil {
- return nil, err
- }
- if found {
- diagLocations[pm] = spn
- } else {
- backupDiagLocations[pm] = spn
- }
- }
-
- // If we didn't find any good matches, assign diagnostics to all go.mod files.
- if len(diagLocations) == 0 {
- diagLocations = backupDiagLocations
- }
-
- var srcErrs []*source.Diagnostic
- for pm, spn := range diagLocations {
- diag, err := s.goCommandDiagnostic(pm, spn, goCmdError)
- if err != nil {
- return nil, err
- }
- srcErrs = append(srcErrs, diag)
- }
- return srcErrs, nil
-}
-
-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(ctx context.Context, pm *source.ParsedModule, goCmdError string) (span.Span, 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]}
- // Any module versions that come from the workspace module should not
- // be shown to the user.
- if source.IsWorkspaceModuleVersion(ver.Version) {
- continue
- }
- 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.
- if pm.File.Module == nil {
- return span.New(pm.URI, span.NewPoint(1, 1, 0), span.Point{}), false, nil
- }
- spn, err := spanFromPositions(pm.Mapper, pm.File.Module.Syntax.Start, pm.File.Module.Syntax.End)
- return spn, false, err
- }
-
- spn, err := spanFromPositions(pm.Mapper, reference.Start, reference.End)
- return spn, true, err
-}
-
-// goCommandDiagnostic creates a diagnostic for a given go command error.
-func (s *snapshot) goCommandDiagnostic(pm *source.ParsedModule, spn span.Span, goCmdError string) (*source.Diagnostic, error) {
- rng, err := pm.Mapper.Range(spn)
- if err != nil {
- return nil, err
- }
-
- 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]}
- // Any module versions that come from the workspace module should not
- // be shown to the user.
- if source.IsWorkspaceModuleVersion(ver.Version) {
- continue
- }
- if err := module.Check(ver.Path, ver.Version); err != nil {
- continue
- }
- innermost = &ver
- break
- }
-
- switch {
- case strings.Contains(goCmdError, "inconsistent vendoring"):
- cmd, err := command.NewVendorCommand("Run go mod vendor", command.URIArg{URI: protocol.URIFromSpanURI(pm.URI)})
- if err != nil {
- return nil, err
- }
- return &source.Diagnostic{
- URI: pm.URI,
- Range: rng,
- Severity: protocol.SeverityError,
- Source: 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: []source.SuggestedFix{source.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
- for _, uri := range s.ModFiles() {
- args = append(args, protocol.URIFromSpanURI(uri))
- }
- tidyCmd, err := command.NewTidyCommand("Run go mod tidy", command.URIArgs{URIs: args})
- if err != nil {
- return nil, err
- }
- updateCmd, err := command.NewUpdateGoSumCommand("Update go.sum", command.URIArgs{URIs: args})
- if err != nil {
- return nil, err
- }
- 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 &source.Diagnostic{
- URI: pm.URI,
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.ListError,
- Message: msg,
- SuggestedFixes: []source.SuggestedFix{
- source.SuggestedFixFromCommand(tidyCmd, protocol.QuickFix),
- source.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, err := command.NewAddDependencyCommand(title, command.DependencyArgs{
- URI: protocol.URIFromSpanURI(pm.URI),
- AddRequire: false,
- GoCmdArgs: []string{fmt.Sprintf("%v@%v", innermost.Path, innermost.Version)},
- })
- if err != nil {
- return nil, err
- }
- return &source.Diagnostic{
- URI: pm.URI,
- Range: rng,
- Severity: protocol.SeverityError,
- Message: fmt.Sprintf("%v@%v has not been downloaded", innermost.Path, innermost.Version),
- Source: source.ListError,
- SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)},
- }, nil
- default:
- return &source.Diagnostic{
- URI: pm.URI,
- Range: rng,
- Severity: protocol.SeverityError,
- Source: 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/internal/lsp/cache/mod_tidy.go b/internal/lsp/cache/mod_tidy.go
deleted file mode 100644
index e85f6510b..000000000
--- a/internal/lsp/cache/mod_tidy.go
+++ /dev/null
@@ -1,500 +0,0 @@
-// 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"
- "go/ast"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/gocommand"
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/memoize"
- "golang.org/x/tools/internal/span"
-)
-
-type modTidyKey struct {
- sessionID string
- env string
- gomod source.FileIdentity
- imports string
- unsavedOverlays string
- view string
-}
-
-type modTidyHandle struct {
- handle *memoize.Handle
-}
-
-type modTidyData struct {
- tidied *source.TidiedModule
- err error
-}
-
-func (mth *modTidyHandle) tidy(ctx context.Context, snapshot *snapshot) (*source.TidiedModule, error) {
- v, err := mth.handle.Get(ctx, snapshot.generation, snapshot)
- if err != nil {
- return nil, err
- }
- data := v.(*modTidyData)
- return data.tidied, data.err
-}
-
-func (s *snapshot) ModTidy(ctx context.Context, pm *source.ParsedModule) (*source.TidiedModule, error) {
- if pm.File == nil {
- return nil, fmt.Errorf("cannot tidy unparseable go.mod file: %v", pm.URI)
- }
- if handle := s.getModTidyHandle(pm.URI); handle != nil {
- return handle.tidy(ctx, s)
- }
- fh, err := s.GetFile(ctx, pm.URI)
- if err != nil {
- return nil, err
- }
- // 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.
- if _, ok := fh.(*overlay); ok {
- if info, _ := os.Stat(fh.URI().Filename()); info == nil {
- return nil, source.ErrNoModOnDisk
- }
- }
- if criticalErr := s.GetCriticalError(ctx); criticalErr != nil {
- return &source.TidiedModule{
- Diagnostics: criticalErr.DiagList,
- }, nil
- }
- workspacePkgs, err := s.workspacePackageHandles(ctx)
- if err != nil {
- return nil, err
- }
- importHash, err := s.hashImports(ctx, workspacePkgs)
- if err != nil {
- return nil, err
- }
-
- s.mu.Lock()
- overlayHash := hashUnsavedOverlays(s.files)
- s.mu.Unlock()
-
- key := modTidyKey{
- sessionID: s.view.session.id,
- view: s.view.folder.Filename(),
- imports: importHash,
- unsavedOverlays: overlayHash,
- gomod: fh.FileIdentity(),
- env: hashEnv(s),
- }
- h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
- ctx, done := event.Start(ctx, "cache.ModTidyHandle", tag.URI.Of(fh.URI()))
- defer done()
-
- snapshot := arg.(*snapshot)
- inv := &gocommand.Invocation{
- Verb: "mod",
- Args: []string{"tidy"},
- WorkingDir: filepath.Dir(fh.URI().Filename()),
- }
- tmpURI, inv, cleanup, err := snapshot.goCommandInvocation(ctx, source.WriteTemporaryModFile, inv)
- if err != nil {
- return &modTidyData{err: err}
- }
- // Keep the temporary go.mod file around long enough to parse it.
- defer cleanup()
-
- if _, err := s.view.session.gocmdRunner.Run(ctx, *inv); err != nil {
- return &modTidyData{err: err}
- }
- // Go directly to disk to get the temporary mod file, since it is
- // always on disk.
- tempContents, err := ioutil.ReadFile(tmpURI.Filename())
- if err != nil {
- return &modTidyData{err: err}
- }
- ideal, err := modfile.Parse(tmpURI.Filename(), tempContents, nil)
- if err != nil {
- // We do not need to worry about the temporary file's parse errors
- // since it has been "tidied".
- return &modTidyData{err: err}
- }
- // Compare the original and tidied go.mod files to compute errors and
- // suggested fixes.
- diagnostics, err := modTidyDiagnostics(ctx, snapshot, pm, ideal, workspacePkgs)
- if err != nil {
- return &modTidyData{err: err}
- }
- return &modTidyData{
- tidied: &source.TidiedModule{
- Diagnostics: diagnostics,
- TidiedContent: tempContents,
- },
- }
- }, nil)
-
- mth := &modTidyHandle{handle: h}
- s.mu.Lock()
- s.modTidyHandles[fh.URI()] = mth
- s.mu.Unlock()
-
- return mth.tidy(ctx, s)
-}
-
-func (s *snapshot) hashImports(ctx context.Context, wsPackages []*packageHandle) (string, error) {
- seen := map[string]struct{}{}
- var imports []string
- for _, ph := range wsPackages {
- for _, imp := range ph.imports(ctx, s) {
- if _, ok := seen[imp]; !ok {
- imports = append(imports, imp)
- seen[imp] = struct{}{}
- }
- }
- }
- sort.Strings(imports)
- hashed := strings.Join(imports, ",")
- return hashContents([]byte(hashed)), 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 source.Snapshot, pm *source.ParsedModule, ideal *modfile.File, workspacePkgs []*packageHandle) (diagnostics []*source.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, snapshot.View().Options().ComputeEdits)
- 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.
- missingModuleFixes := map[*modfile.Require][]source.SuggestedFix{}
- 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.
- for _, ph := range workspacePkgs {
- 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.
- importedPkgs := ph.imports(ctx, snapshot)
-
- for _, imp := range importedPkgs {
- 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 _, pgh := range ph.compiledGoFiles {
- pgf, err := snapshot.ParseGo(ctx, pgh.file, source.ParseHeader)
- 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(snapshot, m, imp, req, fixes)
- if err != nil {
- return nil, err
- }
- diagnostics = append(diagnostics, srcErr)
- }
- }
- }
- // Finally, add errors for any unused dependencies.
- 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
-}
-
-// unusedDiagnostic returns a source.Diagnostic for an unused require.
-func unusedDiagnostic(m *protocol.ColumnMapper, req *modfile.Require, onlyDiagnostic bool) (*source.Diagnostic, error) {
- rng, err := rangeFromPositions(m, req.Syntax.Start, req.Syntax.End)
- if err != nil {
- return nil, err
- }
- title := fmt.Sprintf("Remove dependency: %s", req.Mod.Path)
- cmd, err := command.NewRemoveDependencyCommand(title, command.RemoveDependencyArgs{
- URI: protocol.URIFromSpanURI(m.URI),
- OnlyDiagnostic: onlyDiagnostic,
- ModulePath: req.Mod.Path,
- })
- if err != nil {
- return nil, err
- }
- return &source.Diagnostic{
- URI: m.URI,
- Range: rng,
- Severity: protocol.SeverityWarning,
- Source: source.ModTidyError,
- Message: fmt.Sprintf("%s is not used in this module", req.Mod.Path),
- SuggestedFixes: []source.SuggestedFix{source.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.ColumnMapper, req *modfile.Require, computeEdits diff.ComputeEdits) (*source.Diagnostic, error) {
- rng, err := rangeFromPositions(m, req.Syntax.Start, req.Syntax.End)
- 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([]byte(comments.Suffix[0].Token))
- rng, err = rangeFromPositions(m, comments.Suffix[0].Start, end)
- if err != nil {
- return nil, err
- }
- }
- }
- // If the dependency should be indirect, add the // indirect.
- edits, err := switchDirectness(req, m, computeEdits)
- if err != nil {
- return nil, err
- }
- return &source.Diagnostic{
- URI: m.URI,
- Range: rng,
- Severity: protocol.SeverityWarning,
- Source: source.ModTidyError,
- Message: fmt.Sprintf("%s should be %s", req.Mod.Path, direction),
- SuggestedFixes: []source.SuggestedFix{{
- Title: fmt.Sprintf("Change %s to %s", req.Mod.Path, direction),
- Edits: map[span.URI][]protocol.TextEdit{
- m.URI: edits,
- },
- ActionKind: protocol.QuickFix,
- }},
- }, nil
-}
-
-func missingModuleDiagnostic(pm *source.ParsedModule, req *modfile.Require) (*source.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 = rangeFromPositions(pm.Mapper, start, end)
- if err != nil {
- return nil, err
- }
- }
- title := fmt.Sprintf("Add %s to your go.mod file", req.Mod.Path)
- cmd, err := command.NewAddDependencyCommand(title, command.DependencyArgs{
- URI: protocol.URIFromSpanURI(pm.Mapper.URI),
- AddRequire: !req.Indirect,
- GoCmdArgs: []string{req.Mod.Path + "@" + req.Mod.Version},
- })
- if err != nil {
- return nil, err
- }
- return &source.Diagnostic{
- URI: pm.Mapper.URI,
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.ModTidyError,
- Message: fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path),
- SuggestedFixes: []source.SuggestedFix{source.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.ColumnMapper, computeEdits diff.ComputeEdits) ([]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.
- diff, err := computeEdits(m.URI, string(m.Content), string(newContent))
- if err != nil {
- return nil, err
- }
- return source.ToProtocolEdits(m, diff)
-}
-
-// missingModuleForImport creates an error for a given import path that comes
-// from a missing module.
-func missingModuleForImport(snapshot source.Snapshot, m *protocol.ColumnMapper, imp *ast.ImportSpec, req *modfile.Require, fixes []source.SuggestedFix) (*source.Diagnostic, error) {
- if req.Syntax == nil {
- return nil, fmt.Errorf("no syntax for %v", req)
- }
- spn, err := span.NewRange(snapshot.FileSet(), imp.Path.Pos(), imp.Path.End()).Span()
- if err != nil {
- return nil, err
- }
- rng, err := m.Range(spn)
- if err != nil {
- return nil, err
- }
- return &source.Diagnostic{
- URI: m.URI,
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.ModTidyError,
- Message: fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path),
- SuggestedFixes: fixes,
- }, nil
-}
-
-func rangeFromPositions(m *protocol.ColumnMapper, s, e modfile.Position) (protocol.Range, error) {
- spn, err := spanFromPositions(m, s, e)
- if err != nil {
- return protocol.Range{}, err
- }
- return m.Range(spn)
-}
-
-func spanFromPositions(m *protocol.ColumnMapper, s, e modfile.Position) (span.Span, error) {
- toPoint := func(offset int) (span.Point, error) {
- l, c, err := m.Converter.ToPosition(offset)
- if err != nil {
- return span.Point{}, err
- }
- return span.NewPoint(l, c, offset), nil
- }
- start, err := toPoint(s.Byte)
- if err != nil {
- return span.Span{}, err
- }
- end, err := toPoint(e.Byte)
- if err != nil {
- return span.Span{}, err
- }
- return span.New(m.URI, start, end), nil
-}
diff --git a/internal/lsp/cache/os_darwin.go b/internal/lsp/cache/os_darwin.go
deleted file mode 100644
index 2c88be1fc..000000000
--- a/internal/lsp/cache/os_darwin.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// 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() {
- checkPathCase = darwinCheckPathCase
-}
-
-func darwinCheckPathCase(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("case mismatch in path %q: component %q is listed by macOS as %q", path, g, w)
- }
- }
- return nil
-}
diff --git a/internal/lsp/cache/os_windows.go b/internal/lsp/cache/os_windows.go
deleted file mode 100644
index 7ff1cce74..000000000
--- a/internal/lsp/cache/os_windows.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// 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() {
- checkPathCase = windowsCheckPathCase
-}
-
-func windowsCheckPathCase(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("case mismatch in path %q: component %q is listed by Windows as %q", path, g, w)
- }
- }
- return nil
-}
diff --git a/internal/lsp/cache/parse.go b/internal/lsp/cache/parse.go
deleted file mode 100644
index e761373fa..000000000
--- a/internal/lsp/cache/parse.go
+++ /dev/null
@@ -1,1467 +0,0 @@
-// 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"
- "fmt"
- "go/ast"
- "go/parser"
- "go/scanner"
- "go/token"
- "go/types"
- "path/filepath"
- "reflect"
- "strconv"
- "strings"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/diff/myers"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/memoize"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// parseKey uniquely identifies a parsed Go file.
-type parseKey struct {
- file source.FileIdentity
- mode source.ParseMode
-}
-
-type parseGoHandle struct {
- handle *memoize.Handle
- file source.FileHandle
- mode source.ParseMode
-}
-
-type parseGoData struct {
- parsed *source.ParsedGoFile
-
- // If true, we adjusted the AST to make it type check better, and
- // it may not match the source code.
- fixed bool
- err error // any other errors
-}
-
-func (s *snapshot) parseGoHandle(ctx context.Context, fh source.FileHandle, mode source.ParseMode) *parseGoHandle {
- key := parseKey{
- file: fh.FileIdentity(),
- mode: mode,
- }
- if pgh := s.getGoFile(key); pgh != nil {
- return pgh
- }
- parseHandle := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
- snapshot := arg.(*snapshot)
- return parseGo(ctx, snapshot.FileSet(), fh, mode)
- }, nil)
-
- pgh := &parseGoHandle{
- handle: parseHandle,
- file: fh,
- mode: mode,
- }
- return s.addGoFile(key, pgh)
-}
-
-func (pgh *parseGoHandle) String() string {
- return pgh.File().URI().Filename()
-}
-
-func (pgh *parseGoHandle) File() source.FileHandle {
- return pgh.file
-}
-
-func (pgh *parseGoHandle) Mode() source.ParseMode {
- return pgh.mode
-}
-
-func (s *snapshot) ParseGo(ctx context.Context, fh source.FileHandle, mode source.ParseMode) (*source.ParsedGoFile, error) {
- pgh := s.parseGoHandle(ctx, fh, mode)
- pgf, _, err := s.parseGo(ctx, pgh)
- return pgf, err
-}
-
-func (s *snapshot) parseGo(ctx context.Context, pgh *parseGoHandle) (*source.ParsedGoFile, bool, error) {
- if pgh.mode == source.ParseExported {
- panic("only type checking should use Exported")
- }
- d, err := pgh.handle.Get(ctx, s.generation, s)
- if err != nil {
- return nil, false, err
- }
- data := d.(*parseGoData)
- return data.parsed, data.fixed, data.err
-}
-
-type astCacheKey struct {
- pkg packageHandleKey
- uri span.URI
-}
-
-func (s *snapshot) astCacheData(ctx context.Context, spkg source.Package, pos token.Pos) (*astCacheData, error) {
- pkg := spkg.(*pkg)
- pkgHandle := s.getPackage(pkg.m.ID, pkg.mode)
- if pkgHandle == nil {
- return nil, fmt.Errorf("could not reconstruct package handle for %v", pkg.m.ID)
- }
- tok := s.FileSet().File(pos)
- if tok == nil {
- return nil, fmt.Errorf("no file for pos %v", pos)
- }
- pgf, err := pkg.File(span.URIFromPath(tok.Name()))
- if err != nil {
- return nil, err
- }
- astHandle := s.generation.Bind(astCacheKey{pkgHandle.key, pgf.URI}, func(ctx context.Context, arg memoize.Arg) interface{} {
- return buildASTCache(pgf)
- }, nil)
-
- d, err := astHandle.Get(ctx, s.generation, s)
- if err != nil {
- return nil, err
- }
- data := d.(*astCacheData)
- if data.err != nil {
- return nil, data.err
- }
- return data, nil
-}
-
-func (s *snapshot) PosToDecl(ctx context.Context, spkg source.Package, pos token.Pos) (ast.Decl, error) {
- data, err := s.astCacheData(ctx, spkg, pos)
- if err != nil {
- return nil, err
- }
- return data.posToDecl[pos], nil
-}
-
-func (s *snapshot) PosToField(ctx context.Context, spkg source.Package, pos token.Pos) (*ast.Field, error) {
- data, err := s.astCacheData(ctx, spkg, pos)
- if err != nil {
- return nil, err
- }
- return data.posToField[pos], nil
-}
-
-type astCacheData struct {
- err error
-
- posToDecl map[token.Pos]ast.Decl
- posToField map[token.Pos]*ast.Field
-}
-
-// buildASTCache builds caches to aid in quickly going from the typed
-// world to the syntactic world.
-func buildASTCache(pgf *source.ParsedGoFile) *astCacheData {
- var (
- // path contains all ancestors, including n.
- path []ast.Node
- // decls contains all ancestors that are decls.
- decls []ast.Decl
- )
-
- data := &astCacheData{
- posToDecl: make(map[token.Pos]ast.Decl),
- posToField: make(map[token.Pos]*ast.Field),
- }
-
- ast.Inspect(pgf.File, func(n ast.Node) bool {
- if n == nil {
- lastP := path[len(path)-1]
- path = path[:len(path)-1]
- if len(decls) > 0 && decls[len(decls)-1] == lastP {
- decls = decls[:len(decls)-1]
- }
- return false
- }
-
- path = append(path, n)
-
- switch n := n.(type) {
- case *ast.Field:
- addField := func(f ast.Node) {
- if f.Pos().IsValid() {
- data.posToField[f.Pos()] = n
- if len(decls) > 0 {
- data.posToDecl[f.Pos()] = decls[len(decls)-1]
- }
- }
- }
-
- // Add mapping for *ast.Field itself. This handles embedded
- // fields which have no associated *ast.Ident name.
- addField(n)
-
- // Add mapping for each field name since you can have
- // multiple names for the same type expression.
- for _, name := range n.Names {
- addField(name)
- }
-
- // Also map "X" in "...X" to the containing *ast.Field. This
- // makes it easy to format variadic signature params
- // properly.
- if elips, ok := n.Type.(*ast.Ellipsis); ok && elips.Elt != nil {
- addField(elips.Elt)
- }
- case *ast.FuncDecl:
- decls = append(decls, n)
-
- if n.Name != nil && n.Name.Pos().IsValid() {
- data.posToDecl[n.Name.Pos()] = n
- }
- case *ast.GenDecl:
- decls = append(decls, n)
-
- for _, spec := range n.Specs {
- switch spec := spec.(type) {
- case *ast.TypeSpec:
- if spec.Name != nil && spec.Name.Pos().IsValid() {
- data.posToDecl[spec.Name.Pos()] = n
- }
- case *ast.ValueSpec:
- for _, id := range spec.Names {
- if id != nil && id.Pos().IsValid() {
- data.posToDecl[id.Pos()] = n
- }
- }
- }
- }
- }
-
- return true
- })
-
- return data
-}
-
-func parseGo(ctx context.Context, fset *token.FileSet, fh source.FileHandle, mode source.ParseMode) *parseGoData {
- ctx, done := event.Start(ctx, "cache.parseGo", tag.File.Of(fh.URI().Filename()))
- defer done()
-
- ext := filepath.Ext(fh.URI().Filename())
- if ext != ".go" && ext != "" { // files generated by cgo have no extension
- return &parseGoData{err: errors.Errorf("cannot parse non-Go file %s", fh.URI())}
- }
- src, err := fh.Read()
- if err != nil {
- return &parseGoData{err: err}
- }
-
- parserMode := parser.AllErrors | parser.ParseComments
- if mode == source.ParseHeader {
- parserMode = parser.ImportsOnly | parser.ParseComments
- }
-
- file, err := parser.ParseFile(fset, fh.URI().Filename(), src, parserMode)
- 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. 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(fh.URI().Filename(), -1, len(src))
- tok.SetLinesForContent(src)
- }
-
- fixed := false
- // If there were parse errors, attempt to fix them up.
- if parseErr != nil {
- // Fix any badly parsed parts of the AST.
- fixed = fixAST(ctx, file, tok, src)
-
- for i := 0; i < 10; i++ {
- // Fix certain syntax errors that render the file unparseable.
- newSrc := 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 {
- edits, err := myers.ComputeEdits(fh.URI(), string(src), string(newSrc))
- if err != nil {
- event.Error(ctx, "error generating fixSrc diff", err, tag.File.Of(tok.Name()))
- } else {
- unified := diff.ToUnified("before", "after", string(src), edits)
- event.Log(ctx, fmt.Sprintf("fixSrc loop - last diff:\n%v", unified), tag.File.Of(tok.Name()))
- }
- }
-
- newFile, _ := parser.ParseFile(fset, fh.URI().Filename(), newSrc, parserMode)
- if newFile != nil {
- // Maintain the original parseError so we don't try formatting the doctored file.
- file = newFile
- src = newSrc
- tok = fset.File(file.Pos())
-
- fixed = fixAST(ctx, file, tok, src)
- }
- }
- }
-
- return &parseGoData{
- parsed: &source.ParsedGoFile{
- URI: fh.URI(),
- Mode: mode,
- Src: src,
- File: file,
- Tok: tok,
- Mapper: &protocol.ColumnMapper{
- URI: fh.URI(),
- Converter: span.NewTokenConverter(fset, tok),
- Content: src,
- },
- ParseErr: parseErr,
- },
- fixed: fixed,
- }
-}
-
-// An unexportedFilter removes as much unexported AST from a set of Files as possible.
-type unexportedFilter struct {
- uses map[string]bool
-}
-
-// Filter records uses of unexported identifiers and filters out all other
-// unexported declarations.
-func (f *unexportedFilter) Filter(files []*ast.File) {
- // Iterate to fixed point -- unexported types can include other unexported types.
- oldLen := len(f.uses)
- for {
- for _, file := range files {
- f.recordUses(file)
- }
- if len(f.uses) == oldLen {
- break
- }
- oldLen = len(f.uses)
- }
-
- for _, file := range files {
- var newDecls []ast.Decl
- for _, decl := range file.Decls {
- if f.filterDecl(decl) {
- newDecls = append(newDecls, decl)
- }
- }
- file.Decls = newDecls
- file.Scope = nil
- file.Unresolved = nil
- file.Comments = nil
- trimAST(file)
- }
-}
-
-func (f *unexportedFilter) keep(ident *ast.Ident) bool {
- return ast.IsExported(ident.Name) || f.uses[ident.Name]
-}
-
-func (f *unexportedFilter) filterDecl(decl ast.Decl) bool {
- switch decl := decl.(type) {
- case *ast.FuncDecl:
- if ident := recvIdent(decl); ident != nil && !f.keep(ident) {
- return false
- }
- return f.keep(decl.Name)
- case *ast.GenDecl:
- if decl.Tok == token.CONST {
- // Constants can involve iota, and iota is hard to deal with.
- return true
- }
- var newSpecs []ast.Spec
- for _, spec := range decl.Specs {
- if f.filterSpec(spec) {
- newSpecs = append(newSpecs, spec)
- }
- }
- decl.Specs = newSpecs
- return len(newSpecs) != 0
- case *ast.BadDecl:
- return false
- }
- panic(fmt.Sprintf("unknown ast.Decl %T", decl))
-}
-
-func (f *unexportedFilter) filterSpec(spec ast.Spec) bool {
- switch spec := spec.(type) {
- case *ast.ImportSpec:
- return true
- case *ast.ValueSpec:
- var newNames []*ast.Ident
- for _, name := range spec.Names {
- if f.keep(name) {
- newNames = append(newNames, name)
- }
- }
- spec.Names = newNames
- return len(spec.Names) != 0
- case *ast.TypeSpec:
- if !f.keep(spec.Name) {
- return false
- }
- switch typ := spec.Type.(type) {
- case *ast.StructType:
- f.filterFieldList(typ.Fields)
- case *ast.InterfaceType:
- f.filterFieldList(typ.Methods)
- }
- return true
- }
- panic(fmt.Sprintf("unknown ast.Spec %T", spec))
-}
-
-func (f *unexportedFilter) filterFieldList(fields *ast.FieldList) {
- var newFields []*ast.Field
- for _, field := range fields.List {
- if len(field.Names) == 0 {
- // Keep embedded fields: they can export methods and fields.
- newFields = append(newFields, field)
- }
- for _, name := range field.Names {
- if f.keep(name) {
- newFields = append(newFields, field)
- break
- }
- }
- }
- fields.List = newFields
-}
-
-func (f *unexportedFilter) recordUses(file *ast.File) {
- for _, decl := range file.Decls {
- switch decl := decl.(type) {
- case *ast.FuncDecl:
- // Ignore methods on dropped types.
- if ident := recvIdent(decl); ident != nil && !f.keep(ident) {
- break
- }
- // Ignore functions with dropped names.
- if !f.keep(decl.Name) {
- break
- }
- f.recordFuncType(decl.Type)
- case *ast.GenDecl:
- for _, spec := range decl.Specs {
- switch spec := spec.(type) {
- case *ast.ValueSpec:
- for i, name := range spec.Names {
- // Don't mess with constants -- iota is hard.
- if f.keep(name) || decl.Tok == token.CONST {
- f.recordIdents(spec.Type)
- if len(spec.Values) > i {
- f.recordIdents(spec.Values[i])
- }
- }
- }
- case *ast.TypeSpec:
- switch typ := spec.Type.(type) {
- case *ast.StructType:
- f.recordFieldUses(false, typ.Fields)
- case *ast.InterfaceType:
- f.recordFieldUses(false, typ.Methods)
- }
- }
- }
- }
- }
-}
-
-// recvIdent returns the identifier of a method receiver, e.g. *int.
-func recvIdent(decl *ast.FuncDecl) *ast.Ident {
- if decl.Recv == nil || len(decl.Recv.List) == 0 {
- return nil
- }
- x := decl.Recv.List[0].Type
- if star, ok := x.(*ast.StarExpr); ok {
- x = star.X
- }
- if ident, ok := x.(*ast.Ident); ok {
- return ident
- }
- return nil
-}
-
-// recordIdents records unexported identifiers in an Expr in uses.
-// These may be types, e.g. in map[key]value, function names, e.g. in foo(),
-// or simple variable references. References that will be discarded, such
-// as those in function literal bodies, are ignored.
-func (f *unexportedFilter) recordIdents(x ast.Expr) {
- ast.Inspect(x, func(n ast.Node) bool {
- if n == nil {
- return false
- }
- if complit, ok := n.(*ast.CompositeLit); ok {
- // We clear out composite literal contents; just record their type.
- f.recordIdents(complit.Type)
- return false
- }
- if flit, ok := n.(*ast.FuncLit); ok {
- f.recordFuncType(flit.Type)
- return false
- }
- if ident, ok := n.(*ast.Ident); ok && !ast.IsExported(ident.Name) {
- f.uses[ident.Name] = true
- }
- return true
- })
-}
-
-// recordFuncType records the types mentioned by a function type.
-func (f *unexportedFilter) recordFuncType(x *ast.FuncType) {
- f.recordFieldUses(true, x.Params)
- f.recordFieldUses(true, x.Results)
-}
-
-// recordFieldUses records unexported identifiers used in fields, which may be
-// struct members, interface members, or function parameter/results.
-func (f *unexportedFilter) recordFieldUses(isParams bool, fields *ast.FieldList) {
- if fields == nil {
- return
- }
- for _, field := range fields.List {
- if isParams {
- // Parameter types of retained functions need to be retained.
- f.recordIdents(field.Type)
- continue
- }
- if ft, ok := field.Type.(*ast.FuncType); ok {
- // Function declarations in interfaces need all their types retained.
- f.recordFuncType(ft)
- continue
- }
- if len(field.Names) == 0 {
- // Embedded fields might contribute exported names.
- f.recordIdents(field.Type)
- }
- for _, name := range field.Names {
- // We only need normal fields if they're exported.
- if ast.IsExported(name.Name) {
- f.recordIdents(field.Type)
- break
- }
- }
- }
-}
-
-// ProcessErrors records additional uses from errors, returning the new uses
-// and any unexpected errors.
-func (f *unexportedFilter) ProcessErrors(errors []types.Error) (map[string]bool, []types.Error) {
- var unexpected []types.Error
- missing := map[string]bool{}
- for _, err := range errors {
- if strings.Contains(err.Msg, "missing return") {
- continue
- }
- const undeclared = "undeclared name: "
- if strings.HasPrefix(err.Msg, undeclared) {
- missing[strings.TrimPrefix(err.Msg, undeclared)] = true
- f.uses[strings.TrimPrefix(err.Msg, undeclared)] = true
- continue
- }
- unexpected = append(unexpected, err)
- }
- return missing, unexpected
-}
-
-// trimAST clears any part of the AST not relevant to type checking
-// expressions at pos.
-func trimAST(file *ast.File) {
- ast.Inspect(file, func(n ast.Node) bool {
- if n == nil {
- return false
- }
- switch n := n.(type) {
- case *ast.FuncDecl:
- n.Body = nil
- case *ast.BlockStmt:
- n.List = nil
- case *ast.CaseClause:
- n.Body = nil
- case *ast.CommClause:
- n.Body = nil
- case *ast.CompositeLit:
- // types.Info.Types for long slice/array literals are particularly
- // expensive. Try to clear them out.
- at, ok := n.Type.(*ast.ArrayType)
- if !ok {
- // Composite literal. No harm removing all its fields.
- n.Elts = nil
- break
- }
- // Removing the elements from an ellipsis array changes its type.
- // Try to set the length explicitly so we can continue.
- if _, ok := at.Len.(*ast.Ellipsis); ok {
- length, ok := arrayLength(n)
- if !ok {
- break
- }
- at.Len = &ast.BasicLit{
- Kind: token.INT,
- Value: fmt.Sprint(length),
- ValuePos: at.Len.Pos(),
- }
- }
- n.Elts = nil
- }
- return true
- })
-}
-
-// arrayLength returns the length of some simple forms of ellipsis array literal.
-// Notably, it handles the tables in golang.org/x/text.
-func arrayLength(array *ast.CompositeLit) (int, bool) {
- litVal := func(expr ast.Expr) (int, bool) {
- lit, ok := expr.(*ast.BasicLit)
- if !ok {
- return 0, false
- }
- val, err := strconv.ParseInt(lit.Value, 10, 64)
- if err != nil {
- return 0, false
- }
- return int(val), true
- }
- largestKey := -1
- for _, elt := range array.Elts {
- kve, ok := elt.(*ast.KeyValueExpr)
- if !ok {
- continue
- }
- switch key := kve.Key.(type) {
- case *ast.BasicLit:
- if val, ok := litVal(key); ok && largestKey < val {
- largestKey = val
- }
- case *ast.BinaryExpr:
- // golang.org/x/text uses subtraction (and only subtraction) in its indices.
- if key.Op != token.SUB {
- break
- }
- x, ok := litVal(key.X)
- if !ok {
- break
- }
- y, ok := litVal(key.Y)
- if !ok {
- break
- }
- if val := x - y; largestKey < val {
- largestKey = val
- }
- }
- }
- if largestKey != -1 {
- return largestKey + 1, true
- }
- return len(array.Elts), true
-}
-
-// 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(ctx context.Context, n ast.Node, tok *token.File, src []byte) (fixed bool) {
- var err error
- walkASTWithParent(n, func(n, parent ast.Node) bool {
- switch n := n.(type) {
- case *ast.BadStmt:
- if fixed = fixDeferOrGoStmt(n, parent, tok, src); fixed {
- // Recursively fix in our fixed node.
- _ = fixAST(ctx, parent, tok, src)
- } else {
- err = errors.Errorf("unable to parse defer or go from *ast.BadStmt: %v", err)
- }
- return false
- case *ast.BadExpr:
- if fixed = fixArrayType(n, parent, tok, src); fixed {
- // Recursively fix in our fixed node.
- _ = fixAST(ctx, parent, tok, src)
- 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
- //
- fixInitStmt(n, parent, tok, src)
-
- 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"
- //
- fixPhantomSelector(n, tok, src)
- 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.
- fixEmptySwitch(n, tok, src)
- }
-
- return true
- default:
- return true
- }
- })
- return fixed
-}
-
-// 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)
- })
-}
-
-// fixSrc attempts to modify the file's source code to fix certain
-// syntax errors that leave the rest of the file unparsed.
-func fixSrc(f *ast.File, tok *token.File, src []byte) (newSrc []byte) {
- 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, tok, src)
- case *ast.SelectorExpr:
- newSrc = fixDanglingSelector(n, tok, src)
- }
-
- return newSrc == nil
- })
-
- return newSrc
-}
-
-// 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 := source.Offset(tok, b.Lbrace)
- if err != nil {
- return nil
- }
- if braceOffset < len(src) && src[braceOffset] == '{' {
- return nil
- }
- }
-
- parentLine := tok.Line(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 := source.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 {
-//
-// }
-//
-func fixEmptySwitch(body *ast.BlockStmt, tok *token.File, src []byte) {
- // We only care about empty switch statements.
- if len(body.List) > 0 || !body.Rbrace.IsValid() {
- return
- }
-
- // If the right brace is actually in the source code at the
- // specified position, don't mess with it.
- braceOffset, err := source.Offset(tok, body.Rbrace)
- if err != nil {
- return
- }
- if braceOffset < len(src) && src[braceOffset] == '}' {
- return
- }
-
- braceLine := tok.Line(body.Rbrace)
- if braceLine >= tok.LineCount() {
- // If we are the last line in the file, no need to fix anything.
- return
- }
-
- // Move the right brace down one line.
- body.Rbrace = tok.LineStart(braceLine + 1)
-}
-
-// 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, tok *token.File, src []byte) []byte {
- if !isPhantomUnderscore(s.Sel, tok, src) {
- return nil
- }
-
- if !s.X.End().IsValid() {
- return nil
- }
-
- insertOffset, err := source.Offset(tok, 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'?
-func fixPhantomSelector(sel *ast.SelectorExpr, tok *token.File, src []byte) {
- if !isPhantomUnderscore(sel.Sel, tok, src) {
- return
- }
-
- // 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
- }
-
- maybeKeyword := readKeyword(sel.Sel.Pos(), tok, src)
- if maybeKeyword == "" {
- 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 := source.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.
-//
-// fixInitStmt returns valid AST for the original source.
-func fixInitStmt(bad *ast.BadExpr, parent ast.Node, tok *token.File, src []byte) {
- if !bad.Pos().IsValid() || !bad.End().IsValid() {
- return
- }
-
- // Try to extract a statement from the BadExpr.
- start, err := source.Offset(tok, bad.Pos())
- if err != nil {
- return
- }
- end, err := source.Offset(tok, bad.End()-1)
- if err != nil {
- return
- }
- stmtBytes := src[start : end+1]
- stmt, err := parseStmt(bad.Pos(), stmtBytes)
- if err != nil {
- return
- }
-
- // 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
- }
- p.Init = stmt
- p.Cond = &ast.Ident{
- Name: "_",
- NamePos: stmt.End(),
- }
- case *ast.ForStmt:
- if p.Init != nil {
- return
- }
- p.Init = stmt
- p.Cond = &ast.Ident{
- Name: "_",
- NamePos: stmt.End(),
- }
- case *ast.SwitchStmt:
- if p.Init != nil {
- return
- }
- p.Init = stmt
- p.Tag = nil
- }
-}
-
-// 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 := source.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, err := source.Offset(tok, from)
- if err != nil {
- return false
- }
- toOffset, err := source.Offset(tok, 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(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 || tok.Line(to) > tok.Line(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, err := source.Offset(tok, from)
- if err != nil {
- return false
- }
- if !from.IsValid() || fromOffset >= len(src) {
- return false
- }
-
- toOffset, err := source.Offset(tok, to)
- if err != nil {
- 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(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.
-func parseStmt(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, errors.Errorf("error reading fake file source: %v", err)
- }
-
- // Extract our expression node from inside the fake file.
- if len(fakeFile.Decls) == 0 {
- return nil, errors.Errorf("error parsing fake file: %v", err)
- }
-
- fakeDecl, _ := fakeFile.Decls[0].(*ast.FuncDecl)
- if fakeDecl == nil || len(fakeDecl.Body.List) == 0 {
- return nil, errors.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(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(pos token.Pos, src []byte) (ast.Expr, error) {
- stmt, err := parseStmt(pos, src)
- if err != nil {
- return nil, err
- }
-
- exprStmt, ok := stmt.(*ast.ExprStmt)
- if !ok {
- return nil, errors.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(n ast.Node, offset token.Pos) {
- 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
- }
-
- f.SetInt(f.Int() + int64(offset))
- }
- }
-
- 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/internal/lsp/cache/parse_test.go b/internal/lsp/cache/parse_test.go
deleted file mode 100644
index cb620f274..000000000
--- a/internal/lsp/cache/parse_test.go
+++ /dev/null
@@ -1,217 +0,0 @@
-// 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"
- "go/ast"
- "go/format"
- "go/parser"
- "go/token"
- "go/types"
- "reflect"
- "sort"
- "testing"
-
- "golang.org/x/tools/go/packages"
-)
-
-func TestArrayLength(t *testing.T) {
- tests := []struct {
- expr string
- length int
- }{
- {`[...]int{0,1,2,3,4,5,6,7,8,9}`, 10},
- {`[...]int{9:0}`, 10},
- {`[...]int{19-10:0}`, 10},
- {`[...]int{19-10:0, 17-10:0, 18-10:0}`, 10},
- }
-
- for _, tt := range tests {
- expr, err := parser.ParseExpr(tt.expr)
- if err != nil {
- t.Fatal(err)
- }
- l, ok := arrayLength(expr.(*ast.CompositeLit))
- if !ok {
- t.Errorf("arrayLength did not recognize expression %#v", expr)
- }
- if l != tt.length {
- t.Errorf("arrayLength(%#v) = %v, want %v", expr, l, tt.length)
- }
- }
-}
-
-func TestTrim(t *testing.T) {
- tests := []struct {
- name string
- file string
- kept []string
- }{
- {
- name: "delete_unused",
- file: `
-type x struct{}
-func y()
-var z int
-`,
- kept: []string{},
- },
- {
- // From the common type in testing.
- name: "unexported_embedded",
- file: `
-type x struct {}
-type Exported struct { x }
-`,
- kept: []string{"Exported", "x"},
- },
- {
- // From the d type in unicode.
- name: "exported_field_unexported_type",
- file: `
-type x struct {}
-type Exported struct {
- X x
-}
-`,
- kept: []string{"Exported", "x"},
- },
- {
- // From errNotExist in io/fs.
- name: "exported_var_function_call",
- file: `
-func x() int { return 0 }
-var Exported = x()
-`,
- kept: []string{"Exported", "x"},
- },
- {
- // From DefaultServeMux in net/http.
- name: "exported_pointer_to_unexported_var",
- file: `
-var Exported = &x
-var x int
-`,
- kept: []string{"Exported", "x"},
- },
- {
- // From DefaultWriter in goldmark/renderer/html.
- name: "exported_pointer_to_composite_lit",
- file: `
-var Exported = &x{}
-type x struct{}
-`,
- kept: []string{"Exported", "x"},
- },
- {
- // From SelectDir in reflect.
- name: "leave_constants",
- file: `
-type Enum int
-const (
- _ Enum = iota
- EnumOne
-)
-`,
- kept: []string{"Enum", "EnumOne"},
- },
- {
- name: "constant_conversion",
- file: `
-type x int
-const (
- foo x = 0
-)
-`,
- kept: []string{"x", "foo"},
- },
- {
- name: "unexported_return",
- file: `
-type x int
-func Exported() x {}
-type y int
-type Interface interface {
- Exported() y
-}
-`,
- kept: []string{"Exported", "Interface", "x", "y"},
- },
- {
- name: "drop_composite_literals",
- file: `
-type x int
-type Exported struct {
- foo x
-}
-var Var = Exported{foo:1}
-`,
- kept: []string{"Exported", "Var"},
- },
- {
- name: "drop_function_literals",
- file: `
-type x int
-var Exported = func() { return x(0) }
-`,
- kept: []string{"Exported"},
- },
- {
- name: "missing_receiver_panic",
- file: `
- func() foo() {}
-`,
- kept: []string{},
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- fset := token.NewFileSet()
- file, err := parser.ParseFile(fset, "main.go", "package main\n\n"+tt.file, parser.AllErrors)
- if err != nil {
- t.Fatal(err)
- }
- filter := &unexportedFilter{uses: map[string]bool{}}
- filter.Filter([]*ast.File{file})
- pkg := types.NewPackage("main", "main")
- checker := types.NewChecker(&types.Config{
- DisableUnusedImportCheck: true,
- }, fset, pkg, nil)
- if err := checker.Files([]*ast.File{file}); err != nil {
- t.Error(err)
- }
- names := pkg.Scope().Names()
- sort.Strings(names)
- sort.Strings(tt.kept)
- if !reflect.DeepEqual(names, tt.kept) {
- t.Errorf("package contains names %v, wanted %v", names, tt.kept)
- }
- })
- }
-}
-
-func TestPkg(t *testing.T) {
- t.Skip("for manual debugging")
- fset := token.NewFileSet()
- pkgs, err := packages.Load(&packages.Config{
- Mode: packages.NeedSyntax | packages.NeedFiles,
- Fset: fset,
- }, "io")
- if err != nil {
- t.Fatal(err)
- }
- if len(pkgs[0].Errors) != 0 {
- t.Fatal(pkgs[0].Errors)
- }
- filter := &unexportedFilter{uses: map[string]bool{}}
- filter.Filter(pkgs[0].Syntax)
- for _, file := range pkgs[0].Syntax {
- buf := &bytes.Buffer{}
- format.Node(buf, fset, file)
- t.Log(buf.String())
- }
-}
diff --git a/internal/lsp/cache/pkg.go b/internal/lsp/cache/pkg.go
deleted file mode 100644
index 0c7bf74d3..000000000
--- a/internal/lsp/cache/pkg.go
+++ /dev/null
@@ -1,149 +0,0 @@
-// 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 (
- "go/ast"
- "go/scanner"
- "go/types"
-
- "golang.org/x/mod/module"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// pkg contains the type information needed by the source package.
-type pkg struct {
- m *Metadata
- mode source.ParseMode
- goFiles []*source.ParsedGoFile
- compiledGoFiles []*source.ParsedGoFile
- diagnostics []*source.Diagnostic
- imports map[PackagePath]*pkg
- version *module.Version
- parseErrors []scanner.ErrorList
- typeErrors []types.Error
- types *types.Package
- typesInfo *types.Info
- typesSizes types.Sizes
- hasFixedFiles bool // if true, AST was sufficiently mangled that we should hide type errors
-}
-
-// Declare explicit types for files and directories to distinguish between the two.
-type (
- fileURI span.URI
- moduleLoadScope string
- viewLoadScope span.URI
-)
-
-func (p *pkg) ID() string {
- return string(p.m.ID)
-}
-
-func (p *pkg) Name() string {
- return string(p.m.Name)
-}
-
-func (p *pkg) PkgPath() string {
- return string(p.m.PkgPath)
-}
-
-func (p *pkg) ParseMode() source.ParseMode {
- return p.mode
-}
-
-func (p *pkg) CompiledGoFiles() []*source.ParsedGoFile {
- return p.compiledGoFiles
-}
-
-func (p *pkg) File(uri span.URI) (*source.ParsedGoFile, error) {
- for _, cgf := range p.compiledGoFiles {
- if cgf.URI == uri {
- return cgf, nil
- }
- }
- for _, gf := range p.goFiles {
- if gf.URI == uri {
- return gf, nil
- }
- }
- return nil, errors.Errorf("no parsed file for %s in %v", uri, p.m.ID)
-}
-
-func (p *pkg) GetSyntax() []*ast.File {
- var syntax []*ast.File
- for _, pgf := range p.compiledGoFiles {
- syntax = append(syntax, pgf.File)
- }
- return syntax
-}
-
-func (p *pkg) GetTypes() *types.Package {
- return p.types
-}
-
-func (p *pkg) GetTypesInfo() *types.Info {
- return p.typesInfo
-}
-
-func (p *pkg) GetTypesSizes() types.Sizes {
- return p.typesSizes
-}
-
-func (p *pkg) IsIllTyped() bool {
- return p.types == nil || p.typesInfo == nil || p.typesSizes == nil
-}
-
-func (p *pkg) ForTest() string {
- return string(p.m.ForTest)
-}
-
-func (p *pkg) GetImport(pkgPath string) (source.Package, error) {
- if imp := p.imports[PackagePath(pkgPath)]; imp != nil {
- return imp, nil
- }
- // Don't return a nil pointer because that still satisfies the interface.
- return nil, errors.Errorf("no imported package for %s", pkgPath)
-}
-
-func (p *pkg) MissingDependencies() []string {
- // We don't invalidate metadata for import deletions, so check the package
- // imports via the *types.Package. Only use metadata if p.types is nil.
- if p.types == nil {
- var md []string
- for i := range p.m.MissingDeps {
- md = append(md, string(i))
- }
- return md
- }
- var md []string
- for _, pkg := range p.types.Imports() {
- if _, ok := p.m.MissingDeps[PackagePath(pkg.Path())]; ok {
- md = append(md, pkg.Path())
- }
- }
- return md
-}
-
-func (p *pkg) Imports() []source.Package {
- var result []source.Package
- for _, imp := range p.imports {
- result = append(result, imp)
- }
- return result
-}
-
-func (p *pkg) Version() *module.Version {
- return p.version
-}
-
-func (p *pkg) HasListOrParseErrors() bool {
- return len(p.m.Errors) != 0 || len(p.parseErrors) != 0
-}
-
-func (p *pkg) HasTypeErrors() bool {
- return len(p.typeErrors) != 0
-}
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
deleted file mode 100644
index e86ed25cb..000000000
--- a/internal/lsp/cache/session.go
+++ /dev/null
@@ -1,741 +0,0 @@
-// 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"
- "strconv"
- "sync"
- "sync/atomic"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/gocommand"
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/progress"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/xcontext"
- errors "golang.org/x/xerrors"
-)
-
-type Session struct {
- cache *Cache
- id string
-
- optionsMu sync.Mutex
- options *source.Options
-
- viewMu sync.RWMutex
- views []*View
- viewMap map[span.URI]*View // map of URI->best view
-
- overlayMu sync.Mutex
- overlays map[span.URI]*overlay
-
- // gocmdRunner guards go command calls from concurrency errors.
- gocmdRunner *gocommand.Runner
-
- progress *progress.Tracker
-}
-
-type overlay struct {
- session *Session
- uri span.URI
- text []byte
- hash string
- version int32
- kind source.FileKind
-
- // 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) Read() ([]byte, error) {
- return o.text, nil
-}
-
-func (o *overlay) FileIdentity() source.FileIdentity {
- return source.FileIdentity{
- URI: o.uri,
- Hash: o.hash,
- }
-}
-
-func (o *overlay) VersionedFileIdentity() source.VersionedFileIdentity {
- return source.VersionedFileIdentity{
- URI: o.uri,
- SessionID: o.session.id,
- Version: o.version,
- }
-}
-
-func (o *overlay) Kind() source.FileKind {
- return o.kind
-}
-
-func (o *overlay) URI() span.URI {
- return o.uri
-}
-
-func (o *overlay) Version() int32 {
- return o.version
-}
-
-func (o *overlay) Session() string {
- return o.session.id
-}
-
-func (o *overlay) Saved() bool {
- return o.saved
-}
-
-// closedFile implements LSPFile for a file that the editor hasn't told us about.
-type closedFile struct {
- source.FileHandle
-}
-
-func (c *closedFile) VersionedFileIdentity() source.VersionedFileIdentity {
- return source.VersionedFileIdentity{
- URI: c.FileHandle.URI(),
- SessionID: "",
- Version: 0,
- }
-}
-
-func (c *closedFile) Saved() bool {
- return true
-}
-
-func (c *closedFile) Session() string {
- return ""
-}
-
-func (c *closedFile) Version() int32 {
- return 0
-}
-
-func (s *Session) ID() string { return s.id }
-func (s *Session) String() string { return s.id }
-
-func (s *Session) Options() *source.Options {
- s.optionsMu.Lock()
- defer s.optionsMu.Unlock()
- return s.options
-}
-
-func (s *Session) SetOptions(options *source.Options) {
- s.optionsMu.Lock()
- defer s.optionsMu.Unlock()
- s.options = options
-}
-
-func (s *Session) SetProgressTracker(tracker *progress.Tracker) {
- // The progress tracker should be set before any view is initialized.
- s.progress = tracker
-}
-
-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(ctx)
- }
- event.Log(ctx, "Shutdown session", KeyShutdownSession.Of(s))
-}
-
-func (s *Session) Cache() interface{} {
- return s.cache
-}
-
-func (s *Session) NewView(ctx context.Context, name string, folder span.URI, options *source.Options) (source.View, source.Snapshot, func(), error) {
- s.viewMu.Lock()
- defer s.viewMu.Unlock()
- for _, view := range s.views {
- if span.CompareURI(view.folder, folder) == 0 {
- return nil, nil, nil, source.ErrViewExists
- }
- }
- view, snapshot, release, err := s.createView(ctx, name, folder, options, 0)
- if err != nil {
- return nil, nil, func() {}, err
- }
- s.views = append(s.views, view)
- // we always need to drop the view map
- s.viewMap = make(map[span.URI]*View)
- return view, snapshot, release, nil
-}
-
-func (s *Session) createView(ctx context.Context, name string, folder span.URI, options *source.Options, snapshotID uint64) (*View, *snapshot, func(), error) {
- index := atomic.AddInt64(&viewIndex, 1)
-
- if s.cache.options != nil {
- s.cache.options(options)
- }
-
- // Set the module-specific information.
- ws, err := s.getWorkspaceInformation(ctx, folder, options)
- if err != nil {
- return nil, nil, func() {}, err
- }
- root := folder
- if options.ExpandWorkspaceToModule {
- root, err = findWorkspaceRoot(ctx, root, s, pathExcludedByFilterFunc(root.Filename(), ws.gomodcache, options), options.ExperimentalWorkspaceModule)
- if err != nil {
- return nil, nil, func() {}, err
- }
- }
-
- // Build the gopls workspace, collecting active modules in the view.
- workspace, err := newWorkspace(ctx, root, s, pathExcludedByFilterFunc(root.Filename(), ws.gomodcache, options), ws.userGo111Module == off, options.ExperimentalWorkspaceModule)
- if err != nil {
- return nil, nil, func() {}, err
- }
-
- // 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)
-
- v := &View{
- session: s,
- initialWorkspaceLoad: make(chan struct{}),
- initializationSema: make(chan struct{}, 1),
- id: strconv.FormatInt(index, 10),
- options: options,
- baseCtx: baseCtx,
- name: name,
- folder: folder,
- moduleUpgrades: map[string]string{},
- filesByURI: map[span.URI]*fileBase{},
- filesByBase: map[string][]*fileBase{},
- rootURI: root,
- workspaceInformation: *ws,
- }
- v.importsState = &importsState{
- ctx: backgroundCtx,
- processEnv: &imports.ProcessEnv{
- GocmdRunner: s.gocmdRunner,
- },
- }
- v.snapshot = &snapshot{
- id: snapshotID,
- view: v,
- backgroundCtx: backgroundCtx,
- cancel: cancel,
- initializeOnce: &sync.Once{},
- generation: s.cache.store.Generation(generationName(v, 0)),
- packages: make(map[packageKey]*packageHandle),
- ids: make(map[span.URI][]PackageID),
- metadata: make(map[PackageID]*KnownMetadata),
- files: make(map[span.URI]source.VersionedFileHandle),
- goFiles: make(map[parseKey]*parseGoHandle),
- symbols: make(map[span.URI]*symbolHandle),
- importedBy: make(map[PackageID][]PackageID),
- actions: make(map[actionKey]*actionHandle),
- workspacePackages: make(map[PackageID]PackagePath),
- unloadableFiles: make(map[span.URI]struct{}),
- parseModHandles: make(map[span.URI]*parseModHandle),
- parseWorkHandles: make(map[span.URI]*parseWorkHandle),
- modTidyHandles: make(map[span.URI]*modTidyHandle),
- modWhyHandles: make(map[span.URI]*modWhyHandle),
- workspace: workspace,
- }
-
- // Initialize the view without blocking.
- initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx))
- v.initCancelFirstAttempt = initCancel
- snapshot := v.snapshot
- release := snapshot.generation.Acquire()
- go func() {
- defer release()
- snapshot.initialize(initCtx, true)
- }()
- return v, snapshot, snapshot.generation.Acquire(), nil
-}
-
-// View returns the view by name.
-func (s *Session) View(name string) source.View {
- s.viewMu.RLock()
- defer s.viewMu.RUnlock()
- for _, view := range s.views {
- if view.Name() == name {
- return view
- }
- }
- return nil
-}
-
-// ViewOf returns a view corresponding to the given URI.
-// If the file is not already associated with a view, pick one using some heuristics.
-func (s *Session) ViewOf(uri span.URI) (source.View, error) {
- return s.viewOf(uri)
-}
-
-func (s *Session) viewOf(uri span.URI) (*View, error) {
- s.viewMu.RLock()
- defer s.viewMu.RUnlock()
- // Check if we already know this file.
- if v, found := s.viewMap[uri]; found {
- return v, nil
- }
- // Pick the best view for this file and memoize the result.
- if len(s.views) == 0 {
- return nil, fmt.Errorf("no views in session")
- }
- s.viewMap[uri] = bestViewForURI(uri, s.views)
- return s.viewMap[uri], nil
-}
-
-func (s *Session) viewsOf(uri span.URI) []*View {
- s.viewMu.RLock()
- defer s.viewMu.RUnlock()
-
- var views []*View
- for _, view := range s.views {
- if source.InDir(view.folder.Filename(), uri.Filename()) {
- views = append(views, view)
- }
- }
- return views
-}
-
-func (s *Session) Views() []source.View {
- s.viewMu.RLock()
- defer s.viewMu.RUnlock()
- result := make([]source.View, len(s.views))
- for i, v := range s.views {
- result[i] = v
- }
- return result
-}
-
-// bestViewForURI returns the most closely matching view for the given URI
-// out of the given set of views.
-func bestViewForURI(uri span.URI, views []*View) *View {
- // we need to find the best view for this file
- var longest *View
- for _, view := range views {
- if longest != nil && len(longest.Folder()) > len(view.Folder()) {
- continue
- }
- if view.contains(uri) {
- longest = view
- }
- }
- if longest != nil {
- return longest
- }
- // Try our best to return a view that knows the file.
- for _, view := range views {
- if view.knownFile(uri) {
- return view
- }
- }
- // TODO: are there any more heuristics we can use?
- return views[0]
-}
-
-func (s *Session) removeView(ctx context.Context, view *View) error {
- s.viewMu.Lock()
- defer s.viewMu.Unlock()
- i, err := s.dropView(ctx, view)
- if err != nil {
- return err
- }
- // delete this view... we don't care about order but we do want to make
- // sure we can garbage collect the view
- s.views[i] = s.views[len(s.views)-1]
- s.views[len(s.views)-1] = nil
- s.views = s.views[:len(s.views)-1]
- return nil
-}
-
-func (s *Session) updateView(ctx context.Context, view *View, options *source.Options) (*View, error) {
- s.viewMu.Lock()
- defer s.viewMu.Unlock()
-
- // Preserve the snapshot ID if we are recreating the view.
- view.snapshotMu.Lock()
- if view.snapshot == nil {
- view.snapshotMu.Unlock()
- panic("updateView called after View was already shut down")
- }
- snapshotID := view.snapshot.id
- view.snapshotMu.Unlock()
-
- i, err := s.dropView(ctx, view)
- if err != nil {
- return nil, err
- }
-
- v, _, release, err := s.createView(ctx, view.name, view.folder, options, snapshotID)
- release()
-
- if err != nil {
- // we have dropped the old view, but could not create the new one
- // this should not happen and is very bad, but we still need to clean
- // up the view array if it happens
- s.views[i] = s.views[len(s.views)-1]
- s.views[len(s.views)-1] = nil
- s.views = s.views[:len(s.views)-1]
- return nil, err
- }
- // substitute the new view into the array where the old view was
- s.views[i] = v
- return v, nil
-}
-
-func (s *Session) dropView(ctx context.Context, v *View) (int, error) {
- // we always need to drop the view map
- s.viewMap = make(map[span.URI]*View)
- for i := range s.views {
- if v == s.views[i] {
- // we found the view, drop it and return the index it was found at
- s.views[i] = nil
- v.shutdown(ctx)
- return i, nil
- }
- }
- return -1, errors.Errorf("view %s for %v not found", v.Name(), v.Folder())
-}
-
-func (s *Session) ModifyFiles(ctx context.Context, changes []source.FileModification) error {
- _, releases, err := s.DidModifyFiles(ctx, changes)
- for _, release := range releases {
- release()
- }
- return err
-}
-
-type fileChange struct {
- content []byte
- exists bool
- fileHandle source.VersionedFileHandle
-
- // isUnchanged indicates whether the file action is one that does not
- // change the actual contents of the file. Opens and closes should not
- // be treated like other changes, since the file content doesn't change.
- isUnchanged bool
-}
-
-func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModification) (map[source.Snapshot][]span.URI, []func(), error) {
- s.viewMu.RLock()
- defer s.viewMu.RUnlock()
- views := make(map[*View]map[span.URI]*fileChange)
- affectedViews := map[span.URI][]*View{}
-
- overlays, err := s.updateOverlays(ctx, changes)
- if err != nil {
- return nil, nil, err
- }
- var forceReloadMetadata bool
- for _, c := range changes {
- if c.Action == source.InvalidateMetadata {
- forceReloadMetadata = true
- }
-
- // Build the list of affected views.
- var changedViews []*View
- for _, view := range s.views {
- // Don't propagate changes that are outside of the view's scope
- // or knowledge.
- if !view.relevantChange(c) {
- continue
- }
- changedViews = append(changedViews, view)
- }
- // If the change is not relevant to any view, but the change is
- // happening in the editor, assign it the most closely matching view.
- if len(changedViews) == 0 {
- if c.OnDisk {
- continue
- }
- bestView, err := s.viewOf(c.URI)
- if err != nil {
- return nil, nil, err
- }
- changedViews = append(changedViews, bestView)
- }
- affectedViews[c.URI] = changedViews
-
- isUnchanged := c.Action == source.Open || c.Action == source.Close
-
- // Apply the changes to all affected views.
- for _, view := range changedViews {
- // Make sure that the file is added to the view.
- _ = view.getFile(c.URI)
- if _, ok := views[view]; !ok {
- views[view] = make(map[span.URI]*fileChange)
- }
- if fh, ok := overlays[c.URI]; ok {
- views[view][c.URI] = &fileChange{
- content: fh.text,
- exists: true,
- fileHandle: fh,
- isUnchanged: isUnchanged,
- }
- } else {
- fsFile, err := s.cache.getFile(ctx, c.URI)
- if err != nil {
- return nil, nil, err
- }
- content, err := fsFile.Read()
- fh := &closedFile{fsFile}
- views[view][c.URI] = &fileChange{
- content: content,
- exists: err == nil,
- fileHandle: fh,
- isUnchanged: isUnchanged,
- }
- }
- }
- }
-
- var releases []func()
- viewToSnapshot := map[*View]*snapshot{}
- for view, changed := range views {
- snapshot, release := view.invalidateContent(ctx, changed, forceReloadMetadata)
- releases = append(releases, release)
- viewToSnapshot[view] = snapshot
- }
-
- // We only want to diagnose each changed file once, in the view to which
- // it "most" belongs. We do this by picking the best view for each URI,
- // and then aggregating the set of snapshots and their URIs (to avoid
- // diagnosing the same snapshot multiple times).
- snapshotURIs := map[source.Snapshot][]span.URI{}
- for _, mod := range changes {
- viewSlice, ok := affectedViews[mod.URI]
- if !ok || len(viewSlice) == 0 {
- continue
- }
- view := bestViewForURI(mod.URI, viewSlice)
- snapshot, ok := viewToSnapshot[view]
- if !ok {
- panic(fmt.Sprintf("no snapshot for view %s", view.Folder()))
- }
- snapshotURIs[snapshot] = append(snapshotURIs[snapshot], mod.URI)
- }
- return snapshotURIs, releases, nil
-}
-
-func (s *Session) ExpandModificationsToDirectories(ctx context.Context, changes []source.FileModification) []source.FileModification {
- s.viewMu.RLock()
- defer s.viewMu.RUnlock()
- var snapshots []*snapshot
- for _, v := range s.views {
- snapshot, release := v.getSnapshot()
- defer release()
- snapshots = append(snapshots, snapshot)
- }
- knownDirs := knownDirectories(ctx, snapshots)
- var result []source.FileModification
- for _, c := range changes {
- if _, ok := knownDirs[c.URI]; !ok {
- result = append(result, c)
- continue
- }
- affectedFiles := knownFilesInDir(ctx, snapshots, c.URI)
- var fileChanges []source.FileModification
- for uri := range affectedFiles {
- fileChanges = append(fileChanges, source.FileModification{
- URI: uri,
- Action: c.Action,
- LanguageID: "",
- OnDisk: c.OnDisk,
- // changes to directories cannot include text or versions
- })
- }
- result = append(result, fileChanges...)
- }
- return result
-}
-
-// knownDirectories returns all of the directories known to the given
-// snapshots, including workspace directories and their subdirectories.
-func knownDirectories(ctx context.Context, snapshots []*snapshot) map[span.URI]struct{} {
- result := map[span.URI]struct{}{}
- for _, snapshot := range snapshots {
- dirs := snapshot.workspace.dirs(ctx, snapshot)
- for _, dir := range dirs {
- result[dir] = struct{}{}
- }
- for _, dir := range snapshot.getKnownSubdirs(dirs) {
- result[dir] = struct{}{}
- }
- }
- return result
-}
-
-// knownFilesInDir returns the files known to the snapshots in the session.
-// It does not respect symlinks.
-func knownFilesInDir(ctx context.Context, snapshots []*snapshot, dir span.URI) map[span.URI]struct{} {
- files := map[span.URI]struct{}{}
-
- for _, snapshot := range snapshots {
- for _, uri := range snapshot.knownFilesInDir(ctx, dir) {
- files[uri] = struct{}{}
- }
- }
- return files
-}
-
-func (s *Session) updateOverlays(ctx context.Context, changes []source.FileModification) (map[span.URI]*overlay, error) {
- s.overlayMu.Lock()
- defer s.overlayMu.Unlock()
-
- for _, c := range changes {
- // Don't update overlays for metadata invalidations.
- if c.Action == source.InvalidateMetadata {
- continue
- }
-
- o, ok := s.overlays[c.URI]
-
- // 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 source.FileKind
- switch c.Action {
- case source.Open:
- kind = source.FileKindForLang(c.LanguageID)
- default:
- if !ok {
- return nil, errors.Errorf("updateOverlays: modifying unopened overlay %v", c.URI)
- }
- kind = o.kind
- }
-
- // Closing a file just deletes its overlay.
- if c.Action == source.Close {
- delete(s.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 == source.Save || c.OnDisk) {
- if !ok {
- return nil, fmt.Errorf("no known content for overlay for %s", c.Action)
- }
- text = o.text
- }
- // On-disk changes don't come with versions.
- version := c.Version
- if c.OnDisk || c.Action == source.Save {
- version = o.version
- }
- hash := hashContents(text)
- var sameContentOnDisk bool
- switch c.Action {
- case source.Delete:
- // Do nothing. sameContentOnDisk should be false.
- case source.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, errors.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, errors.Errorf("updateOverlays: overlay %s changed on save", c.URI)
- }
- sameContentOnDisk = true
- default:
- fh, err := s.cache.getFile(ctx, c.URI)
- if err != nil {
- return nil, err
- }
- _, readErr := fh.Read()
- sameContentOnDisk = (readErr == nil && fh.FileIdentity().Hash == hash)
- }
- o = &overlay{
- session: s,
- uri: c.URI,
- version: version,
- text: text,
- kind: kind,
- hash: hash,
- saved: sameContentOnDisk,
- }
-
- // When opening files, ensure that we actually have a well-defined view and file kind.
- if c.Action == source.Open {
- view, err := s.ViewOf(o.uri)
- if err != nil {
- return nil, errors.Errorf("updateOverlays: finding view for %s: %v", o.uri, err)
- }
- if kind := view.FileKind(o); kind == source.UnknownKind {
- return nil, errors.Errorf("updateOverlays: unknown file kind for %s", o.uri)
- }
- }
-
- s.overlays[c.URI] = o
- }
-
- // Get the overlays for each change while the session's overlay map is
- // locked.
- overlays := make(map[span.URI]*overlay)
- for _, c := range changes {
- if o, ok := s.overlays[c.URI]; ok {
- overlays[c.URI] = o
- }
- }
- return overlays, nil
-}
-
-func (s *Session) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
- if overlay := s.readOverlay(uri); overlay != nil {
- return overlay, nil
- }
- // Fall back to the cache-level file system.
- return s.cache.getFile(ctx, uri)
-}
-
-func (s *Session) readOverlay(uri span.URI) *overlay {
- s.overlayMu.Lock()
- defer s.overlayMu.Unlock()
-
- if overlay, ok := s.overlays[uri]; ok {
- return overlay
- }
- return nil
-}
-
-func (s *Session) Overlays() []source.Overlay {
- s.overlayMu.Lock()
- defer s.overlayMu.Unlock()
-
- overlays := make([]source.Overlay, 0, len(s.overlays))
- for _, overlay := range s.overlays {
- overlays = append(overlays, overlay)
- }
- return overlays
-}
-
-func (s *Session) FileWatchingGlobPatterns(ctx context.Context) map[string]struct{} {
- s.viewMu.RLock()
- defer s.viewMu.RUnlock()
- patterns := map[string]struct{}{}
- for _, view := range s.views {
- snapshot, release := view.getSnapshot()
- for k, v := range snapshot.fileWatchingGlobPatterns(ctx) {
- patterns[k] = v
- }
- release()
- }
- return patterns
-}
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
deleted file mode 100644
index 900f13f28..000000000
--- a/internal/lsp/cache/snapshot.go
+++ /dev/null
@@ -1,2479 +0,0 @@
-// 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"
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "regexp"
- "sort"
- "strconv"
- "strings"
- "sync"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/mod/module"
- "golang.org/x/mod/semver"
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/packages"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/gocommand"
- "golang.org/x/tools/internal/lsp/debug/log"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/memoize"
- "golang.org/x/tools/internal/packagesinternal"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/typesinternal"
- errors "golang.org/x/xerrors"
-)
-
-type snapshot struct {
- memoize.Arg // allow as a memoize.Function arg
-
- id uint64
- view *View
-
- cancel func()
- backgroundCtx context.Context
-
- // the cache generation that contains the data for this snapshot.
- generation *memoize.Generation
-
- // The snapshot's initialization state is controlled by the fields below.
- //
- // initializeOnce guards snapshot initialization. Each snapshot is
- // initialized at most once: reinitialization is triggered on later snapshots
- // by invalidating this field.
- initializeOnce *sync.Once
- // initializedErr holds the last error resulting from initialization. If
- // initialization fails, we only retry when the the workspace modules change,
- // to avoid too many go/packages calls.
- initializedErr *source.CriticalError
-
- // mu guards all of the maps in the snapshot, as well as the builtin URI.
- mu sync.Mutex
-
- // builtin pins the AST and package for builtin.go in memory.
- builtin span.URI
-
- // ids maps file URIs to package IDs.
- // It may be invalidated on calls to go/packages.
- ids map[span.URI][]PackageID
-
- // metadata maps file IDs to their associated metadata.
- // It may invalidated on calls to go/packages.
- metadata map[PackageID]*KnownMetadata
-
- // importedBy maps package IDs to the list of packages that import them.
- importedBy map[PackageID][]PackageID
-
- // files maps file URIs to their corresponding FileHandles.
- // It may invalidated when a file's content changes.
- files map[span.URI]source.VersionedFileHandle
-
- // goFiles maps a parseKey to its parseGoHandle.
- goFiles map[parseKey]*parseGoHandle
-
- // TODO(rfindley): consider merging this with files to reduce burden on clone.
- symbols map[span.URI]*symbolHandle
-
- // packages maps a packageKey to a set of packageHandles to which that file belongs.
- // It may be invalidated when a file's content changes.
- packages map[packageKey]*packageHandle
-
- // actions maps an actionkey to its actionHandle.
- actions map[actionKey]*actionHandle
-
- // workspacePackages contains the workspace's packages, which are loaded
- // when the view is created.
- workspacePackages map[PackageID]PackagePath
-
- // unloadableFiles keeps track of files that we've failed to load.
- unloadableFiles map[span.URI]struct{}
-
- // parseModHandles keeps track of any parseModHandles for the snapshot.
- // The handles need not refer to only the view's go.mod file.
- parseModHandles map[span.URI]*parseModHandle
-
- // parseWorkHandles keeps track of any parseWorkHandles for the snapshot.
- // The handles need not refer to only the view's go.work file.
- parseWorkHandles map[span.URI]*parseWorkHandle
-
- // 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 map[span.URI]*modTidyHandle
- modWhyHandles map[span.URI]*modWhyHandle
-
- workspace *workspace
- workspaceDirHandle *memoize.Handle
-
- // knownSubdirs is the set of subdirectories in the workspace, used to
- // create glob patterns for file watching.
- knownSubdirs map[span.URI]struct{}
- // unprocessedSubdirChanges are any changes that might affect the set of
- // subdirectories in the workspace. They are not reflected to knownSubdirs
- // during the snapshot cloning step as it can slow down cloning.
- unprocessedSubdirChanges []*fileChange
-}
-
-type packageKey struct {
- mode source.ParseMode
- id PackageID
-}
-
-type actionKey struct {
- pkg packageKey
- analyzer *analysis.Analyzer
-}
-
-func (s *snapshot) ID() uint64 {
- return s.id
-}
-
-func (s *snapshot) View() source.View {
- return s.view
-}
-
-func (s *snapshot) BackgroundContext() context.Context {
- return s.backgroundCtx
-}
-
-func (s *snapshot) FileSet() *token.FileSet {
- return s.view.session.cache.fset
-}
-
-func (s *snapshot) ModFiles() []span.URI {
- var uris []span.URI
- for modURI := range s.workspace.getActiveModFiles() {
- uris = append(uris, modURI)
- }
- return uris
-}
-
-func (s *snapshot) WorkFile() span.URI {
- return s.workspace.workFile
-}
-
-func (s *snapshot) Templates() map[span.URI]source.VersionedFileHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- tmpls := map[span.URI]source.VersionedFileHandle{}
- for k, fh := range s.files {
- if s.view.FileKind(fh) == source.Tmpl {
- tmpls[k] = fh
- }
- }
- return tmpls
-}
-
-func (s *snapshot) ValidBuildConfiguration() bool {
- return validBuildConfiguration(s.view.rootURI, &s.view.workspaceInformation, s.workspace.getActiveModFiles())
-}
-
-// workspaceMode describes the way in which the snapshot's workspace should
-// be loaded.
-func (s *snapshot) workspaceMode() workspaceMode {
- var mode workspaceMode
-
- // If the view has an invalid configuration, don't build the workspace
- // module.
- validBuildConfiguration := s.ValidBuildConfiguration()
- if !validBuildConfiguration {
- return mode
- }
- // If the view is not in a module and contains no modules, but still has a
- // valid workspace configuration, do not create the workspace module.
- // It could be using GOPATH or a different build system entirely.
- if len(s.workspace.getActiveModFiles()) == 0 && validBuildConfiguration {
- return mode
- }
- mode |= moduleMode
- options := s.view.Options()
- // The -modfile flag is available for Go versions >= 1.14.
- if options.TempModfile && s.view.workspaceInformation.goversion >= 14 {
- mode |= tempModfile
- }
- return mode
-}
-
-// 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 on config, so buildOverlay needs to filter overlays by
-// module.
-func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config {
- s.view.optionsMu.Lock()
- verboseOutput := s.view.options.VerboseOutput
- s.view.optionsMu.Unlock()
-
- 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,
- Fset: s.FileSet(),
- Overlay: s.buildOverlay(),
- 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 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)
- }
- packagesinternal.SetGoCmdRunner(cfg, s.view.session.gocmdRunner)
- return cfg
-}
-
-func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) {
- _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
- if err != nil {
- return nil, err
- }
- defer cleanup()
-
- return s.view.session.gocmdRunner.Run(ctx, *inv)
-}
-
-func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error {
- _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
- if err != nil {
- return err
- }
- defer cleanup()
- return s.view.session.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr)
-}
-
-func (s *snapshot) RunGoCommands(ctx context.Context, allowNetwork bool, wd string, run func(invoke func(...string) (*bytes.Buffer, error)) error) (bool, []byte, []byte, error) {
- var flags source.InvocationFlags
- if s.workspaceMode()&tempModfile != 0 {
- flags = source.WriteTemporaryModFile
- } else {
- flags = source.Normal
- }
- if allowNetwork {
- flags |= source.AllowNetwork
- }
- tmpURI, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{WorkingDir: wd})
- if err != nil {
- return false, nil, nil, err
- }
- defer cleanup()
- invoke := func(args ...string) (*bytes.Buffer, error) {
- inv.Verb = args[0]
- inv.Args = args[1:]
- return s.view.session.gocmdRunner.Run(ctx, *inv)
- }
- if err := run(invoke); err != nil {
- return false, nil, nil, err
- }
- if flags.Mode() != source.WriteTemporaryModFile {
- return false, nil, nil, nil
- }
- var modBytes, sumBytes []byte
- modBytes, err = ioutil.ReadFile(tmpURI.Filename())
- if err != nil && !os.IsNotExist(err) {
- return false, nil, nil, err
- }
- sumBytes, err = ioutil.ReadFile(strings.TrimSuffix(tmpURI.Filename(), ".mod") + ".sum")
- if err != nil && !os.IsNotExist(err) {
- return false, nil, nil, err
- }
- return true, modBytes, sumBytes, nil
-}
-
-func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.InvocationFlags, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) {
- s.view.optionsMu.Lock()
- allowModfileModificationOption := s.view.options.AllowModfileModifications
- allowNetworkOption := s.view.options.AllowImplicitNetworkAccess
- inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.effectiveGo111Module)
- inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...)
- s.view.optionsMu.Unlock()
- cleanup = func() {} // fallback
-
- // All logic below is for module mode.
- if s.workspaceMode()&moduleMode == 0 {
- return "", inv, cleanup, nil
- }
-
- mode, allowNetwork := flags.Mode(), flags.AllowNetwork()
- if !allowNetwork && !allowNetworkOption {
- inv.Env = append(inv.Env, "GOPROXY=off")
- }
-
- // What follows is rather complicated logic for how to actually run the go
- // command. A word of warning: this is the result of various incremental
- // features added to gopls, and varying behavior of the Go command across Go
- // versions. It can surely be cleaned up significantly, but tread carefully.
- //
- // Roughly speaking we need to resolve four things:
- // - the working directory.
- // - the -mod flag
- // - the -modfile flag
- // - the -workfile flag
- //
- // These are dependent on a number of factors: whether we need to run in a
- // synthetic workspace, whether flags are supported at the current go
- // version, and what we're actually trying to achieve (the
- // source.InvocationFlags).
-
- var modURI span.URI
- // Select the module context to use.
- // If we're type checking, we need to use the workspace context, meaning
- // the main (workspace) module. Otherwise, we should use the module for
- // the passed-in working dir.
- if mode == source.LoadWorkspace {
- switch s.workspace.moduleSource {
- case legacyWorkspace:
- for m := range s.workspace.getActiveModFiles() { // range to access the only element
- modURI = m
- }
- case goWorkWorkspace:
- if s.view.goversion >= 18 {
- break
- }
- // Before go 1.18, the Go command did not natively support go.work files,
- // so we 'fake' them with a workspace module.
- fallthrough
- case fileSystemWorkspace, goplsModWorkspace:
- var tmpDir span.URI
- var err error
- tmpDir, err = s.getWorkspaceDir(ctx)
- if err != nil {
- return "", nil, cleanup, err
- }
- inv.WorkingDir = tmpDir.Filename()
- modURI = span.URIFromPath(filepath.Join(tmpDir.Filename(), "go.mod"))
- }
- } else {
- modURI = s.GoModForFile(span.URIFromPath(inv.WorkingDir))
- }
-
- var modContent []byte
- if modURI != "" {
- modFH, err := s.GetFile(ctx, modURI)
- if err != nil {
- return "", nil, cleanup, err
- }
- modContent, err = modFH.Read()
- if err != nil {
- return "", nil, cleanup, err
- }
- }
-
- vendorEnabled, err := s.vendorEnabled(ctx, modURI, modContent)
- if err != nil {
- return "", nil, cleanup, err
- }
-
- mutableModFlag := ""
- // If the mod flag isn't set, populate it based on the mode and workspace.
- if inv.ModFlag == "" {
- if s.view.goversion >= 16 {
- mutableModFlag = "mod"
- }
-
- switch mode {
- case source.LoadWorkspace, source.Normal:
- if vendorEnabled {
- inv.ModFlag = "vendor"
- } else if !allowModfileModificationOption {
- inv.ModFlag = "readonly"
- } else {
- inv.ModFlag = mutableModFlag
- }
- case source.WriteTemporaryModFile:
- inv.ModFlag = mutableModFlag
- // -mod must be readonly when using go.work files - see issue #48941
- inv.Env = append(inv.Env, "GOWORK=off")
- }
- }
-
- // Only use a temp mod file if the modfile can actually be mutated.
- needTempMod := inv.ModFlag == mutableModFlag
- useTempMod := s.workspaceMode()&tempModfile != 0
- if needTempMod && !useTempMod {
- return "", nil, cleanup, source.ErrTmpModfileUnsupported
- }
-
- // We should use -workfile if:
- // 1. We're not actively trying to mutate a modfile.
- // 2. We have an active go.work file.
- // 3. We're using at least Go 1.18.
- useWorkFile := !needTempMod && s.workspace.moduleSource == goWorkWorkspace && s.view.goversion >= 18
- if useWorkFile {
- // TODO(#51215): build a temp workfile and set GOWORK in the environment.
- } else if useTempMod {
- if modURI == "" {
- return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir)
- }
- modFH, err := s.GetFile(ctx, modURI)
- if err != nil {
- return "", nil, cleanup, err
- }
- // Use the go.sum if it happens to be available.
- gosum := s.goSum(ctx, modURI)
- tmpURI, cleanup, err = tempModFile(modFH, gosum)
- if err != nil {
- return "", nil, cleanup, err
- }
- inv.ModFile = tmpURI.Filename()
- }
-
- return tmpURI, inv, cleanup, nil
-}
-
-func (s *snapshot) buildOverlay() map[string][]byte {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- overlays := make(map[string][]byte)
- for uri, fh := range s.files {
- overlay, ok := fh.(*overlay)
- if !ok {
- continue
- }
- if overlay.saved {
- continue
- }
- // TODO(rstambler): Make sure not to send overlays outside of the current view.
- overlays[uri.Filename()] = overlay.text
- }
- return overlays
-}
-
-func hashUnsavedOverlays(files map[span.URI]source.VersionedFileHandle) string {
- var unsaved []string
- for uri, fh := range files {
- if overlay, ok := fh.(*overlay); ok && !overlay.saved {
- unsaved = append(unsaved, uri.Filename())
- }
- }
- sort.Strings(unsaved)
- return hashContents([]byte(strings.Join(unsaved, "")))
-}
-
-func (s *snapshot) PackagesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, includeTestVariants bool) ([]source.Package, error) {
- ctx = event.Label(ctx, tag.URI.Of(uri))
-
- phs, err := s.packageHandlesForFile(ctx, uri, mode, includeTestVariants)
- if err != nil {
- return nil, err
- }
- var pkgs []source.Package
- for _, ph := range phs {
- pkg, err := ph.check(ctx, s)
- if err != nil {
- return nil, err
- }
- pkgs = append(pkgs, pkg)
- }
- return pkgs, nil
-}
-
-func (s *snapshot) PackageForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, pkgPolicy source.PackageFilter) (source.Package, error) {
- ctx = event.Label(ctx, tag.URI.Of(uri))
-
- phs, err := s.packageHandlesForFile(ctx, uri, mode, false)
- if err != nil {
- return nil, err
- }
-
- if len(phs) < 1 {
- return nil, errors.Errorf("no packages")
- }
-
- ph := phs[0]
- for _, handle := range phs[1:] {
- switch pkgPolicy {
- case source.WidestPackage:
- if ph == nil || len(handle.CompiledGoFiles()) > len(ph.CompiledGoFiles()) {
- ph = handle
- }
- case source.NarrowestPackage:
- if ph == nil || len(handle.CompiledGoFiles()) < len(ph.CompiledGoFiles()) {
- ph = handle
- }
- }
- }
- if ph == nil {
- return nil, errors.Errorf("no packages in input")
- }
-
- return ph.check(ctx, s)
-}
-
-func (s *snapshot) packageHandlesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, includeTestVariants bool) ([]*packageHandle, error) {
- // Check if we should reload metadata for the file. We don't invalidate IDs
- // (though we should), so the IDs will be a better source of truth than the
- // metadata. If there are no IDs for the file, then we should also reload.
- fh, err := s.GetFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- if kind := s.view.FileKind(fh); kind != source.Go {
- return nil, fmt.Errorf("no packages for non-Go file %s (%v)", uri, kind)
- }
- knownIDs, err := s.getOrLoadIDsForURI(ctx, uri)
- if err != nil {
- return nil, err
- }
-
- var phs []*packageHandle
- for _, id := range knownIDs {
- // Filter out any intermediate test variants. We typically aren't
- // interested in these packages for file= style queries.
- if m := s.getMetadata(id); m != nil && m.IsIntermediateTestVariant && !includeTestVariants {
- continue
- }
- var parseModes []source.ParseMode
- switch mode {
- case source.TypecheckAll:
- if s.workspaceParseMode(id) == source.ParseFull {
- parseModes = []source.ParseMode{source.ParseFull}
- } else {
- parseModes = []source.ParseMode{source.ParseExported, source.ParseFull}
- }
- case source.TypecheckFull:
- parseModes = []source.ParseMode{source.ParseFull}
- case source.TypecheckWorkspace:
- parseModes = []source.ParseMode{s.workspaceParseMode(id)}
- }
-
- for _, parseMode := range parseModes {
- ph, err := s.buildPackageHandle(ctx, id, parseMode)
- if err != nil {
- return nil, err
- }
- phs = append(phs, ph)
- }
- }
- return phs, nil
-}
-
-func (s *snapshot) getOrLoadIDsForURI(ctx context.Context, uri span.URI) ([]PackageID, error) {
- knownIDs := s.getIDsForURI(uri)
- reload := len(knownIDs) == 0
- for _, id := range knownIDs {
- // Reload package metadata if any of the metadata has missing
- // dependencies, in case something has changed since the last time we
- // reloaded it.
- if s.noValidMetadataForID(id) {
- reload = true
- break
- }
- // TODO(golang/go#36918): Previously, we would reload any package with
- // missing dependencies. This is expensive and results in too many
- // calls to packages.Load. Determine what we should do instead.
- }
- if reload {
- err := s.load(ctx, false, fileURI(uri))
-
- if !s.useInvalidMetadata() && err != nil {
- return nil, err
- }
- // We've tried to reload and there are still no known IDs for the URI.
- // Return the load error, if there was one.
- knownIDs = s.getIDsForURI(uri)
- if len(knownIDs) == 0 {
- return nil, err
- }
- }
- return knownIDs, nil
-}
-
-// Only use invalid metadata for Go versions >= 1.13. Go 1.12 and below has
-// issues with overlays that will cause confusing error messages if we reuse
-// old metadata.
-func (s *snapshot) useInvalidMetadata() bool {
- return s.view.goversion >= 13 && s.view.Options().ExperimentalUseInvalidMetadata
-}
-
-func (s *snapshot) GetReverseDependencies(ctx context.Context, id string) ([]source.Package, error) {
- if err := s.awaitLoaded(ctx); err != nil {
- return nil, err
- }
- ids := make(map[PackageID]struct{})
- s.transitiveReverseDependencies(PackageID(id), ids)
-
- // Make sure to delete the original package ID from the map.
- delete(ids, PackageID(id))
-
- var pkgs []source.Package
- for id := range ids {
- pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id))
- if err != nil {
- return nil, err
- }
- pkgs = append(pkgs, pkg)
- }
- return pkgs, nil
-}
-
-func (s *snapshot) checkedPackage(ctx context.Context, id PackageID, mode source.ParseMode) (*pkg, error) {
- ph, err := s.buildPackageHandle(ctx, id, mode)
- if err != nil {
- return nil, err
- }
- return ph.check(ctx, s)
-}
-
-// transitiveReverseDependencies populates the ids map with package IDs
-// belonging to the provided package and its transitive reverse dependencies.
-func (s *snapshot) transitiveReverseDependencies(id PackageID, ids map[PackageID]struct{}) {
- if _, ok := ids[id]; ok {
- return
- }
- m := s.getMetadata(id)
- // Only use invalid metadata if we support it.
- if m == nil || !(m.Valid || s.useInvalidMetadata()) {
- return
- }
- ids[id] = struct{}{}
- importedBy := s.getImportedBy(id)
- for _, parentID := range importedBy {
- s.transitiveReverseDependencies(parentID, ids)
- }
-}
-
-func (s *snapshot) getGoFile(key parseKey) *parseGoHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
- return s.goFiles[key]
-}
-
-func (s *snapshot) addGoFile(key parseKey, pgh *parseGoHandle) *parseGoHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
- if existing, ok := s.goFiles[key]; ok {
- return existing
- }
- s.goFiles[key] = pgh
- return pgh
-}
-
-func (s *snapshot) getParseModHandle(uri span.URI) *parseModHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
- return s.parseModHandles[uri]
-}
-
-func (s *snapshot) getParseWorkHandle(uri span.URI) *parseWorkHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
- return s.parseWorkHandles[uri]
-}
-
-func (s *snapshot) getModWhyHandle(uri span.URI) *modWhyHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
- return s.modWhyHandles[uri]
-}
-
-func (s *snapshot) getModTidyHandle(uri span.URI) *modTidyHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
- return s.modTidyHandles[uri]
-}
-
-func (s *snapshot) getImportedBy(id PackageID) []PackageID {
- s.mu.Lock()
- defer s.mu.Unlock()
- return s.getImportedByLocked(id)
-}
-
-func (s *snapshot) getImportedByLocked(id PackageID) []PackageID {
- // If we haven't rebuilt the import graph since creating the snapshot.
- if len(s.importedBy) == 0 {
- s.rebuildImportGraph()
- }
- return s.importedBy[id]
-}
-
-func (s *snapshot) clearAndRebuildImportGraph() {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- // Completely invalidate the original map.
- s.importedBy = make(map[PackageID][]PackageID)
- s.rebuildImportGraph()
-}
-
-func (s *snapshot) rebuildImportGraph() {
- for id, m := range s.metadata {
- for _, importID := range m.Deps {
- s.importedBy[importID] = append(s.importedBy[importID], id)
- }
- }
-}
-
-func (s *snapshot) addPackageHandle(ph *packageHandle) *packageHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- // If the package handle has already been cached,
- // return the cached handle instead of overriding it.
- if ph, ok := s.packages[ph.packageKey()]; ok {
- return ph
- }
- s.packages[ph.packageKey()] = ph
- return ph
-}
-
-func (s *snapshot) workspacePackageIDs() (ids []PackageID) {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- for id := range s.workspacePackages {
- ids = append(ids, id)
- }
- return ids
-}
-
-func (s *snapshot) activePackageIDs() (ids []PackageID) {
- if s.view.Options().MemoryMode == source.ModeNormal {
- return s.workspacePackageIDs()
- }
-
- s.mu.Lock()
- defer s.mu.Unlock()
-
- seen := make(map[PackageID]bool)
- for id := range s.workspacePackages {
- if s.isActiveLocked(id, seen) {
- ids = append(ids, id)
- }
- }
- return ids
-}
-
-func (s *snapshot) isActiveLocked(id PackageID, seen map[PackageID]bool) (active bool) {
- if seen == nil {
- seen = make(map[PackageID]bool)
- }
- if seen, ok := seen[id]; ok {
- return seen
- }
- defer func() {
- seen[id] = active
- }()
- m, ok := s.metadata[id]
- if !ok {
- return false
- }
- for _, cgf := range m.CompiledGoFiles {
- if s.isOpenLocked(cgf) {
- return true
- }
- }
- for _, dep := range m.Deps {
- if s.isActiveLocked(dep, seen) {
- return true
- }
- }
- return false
-}
-
-const fileExtensions = "go,mod,sum,work"
-
-func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} {
- extensions := fileExtensions
- for _, ext := range s.View().Options().TemplateExtensions {
- extensions += "," + ext
- }
- // Work-around microsoft/vscode#100870 by making sure that we are,
- // at least, watching the user's entire workspace. This will still be
- // applied to every folder in the workspace.
- patterns := map[string]struct{}{
- fmt.Sprintf("**/*.{%s}", extensions): {},
- }
- dirs := s.workspace.dirs(ctx, s)
- for _, dir := range dirs {
- dirName := dir.Filename()
-
- // If the directory is within the view's folder, we're already watching
- // it with the pattern above.
- if source.InDir(s.view.folder.Filename(), dirName) {
- continue
- }
- // TODO(rstambler): If microsoft/vscode#3025 is resolved before
- // microsoft/vscode#101042, we will need a work-around for Windows
- // drive letter casing.
- patterns[fmt.Sprintf("%s/**/*.{%s}", dirName, extensions)] = struct{}{}
- }
-
- // Some clients 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.
- var dirNames []string
- for _, uri := range s.getKnownSubdirs(dirs) {
- dirNames = append(dirNames, uri.Filename())
- }
- sort.Strings(dirNames)
- if len(dirNames) > 0 {
- patterns[fmt.Sprintf("{%s}", strings.Join(dirNames, ","))] = struct{}{}
- }
- return patterns
-}
-
-// collectAllKnownSubdirs collects all of the subdirectories within the
-// snapshot's workspace directories. None of the workspace directories are
-// included.
-func (s *snapshot) collectAllKnownSubdirs(ctx context.Context) {
- dirs := s.workspace.dirs(ctx, s)
-
- s.mu.Lock()
- defer s.mu.Unlock()
-
- s.knownSubdirs = map[span.URI]struct{}{}
- for uri := range s.files {
- s.addKnownSubdirLocked(uri, dirs)
- }
-}
-
-func (s *snapshot) getKnownSubdirs(wsDirs []span.URI) []span.URI {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- // First, process any pending changes and update the set of known
- // subdirectories.
- for _, c := range s.unprocessedSubdirChanges {
- if c.isUnchanged {
- continue
- }
- if !c.exists {
- s.removeKnownSubdirLocked(c.fileHandle.URI())
- } else {
- s.addKnownSubdirLocked(c.fileHandle.URI(), wsDirs)
- }
- }
- s.unprocessedSubdirChanges = nil
-
- var result []span.URI
- for uri := range s.knownSubdirs {
- result = append(result, uri)
- }
- return result
-}
-
-func (s *snapshot) addKnownSubdirLocked(uri span.URI, dirs []span.URI) {
- dir := filepath.Dir(uri.Filename())
- // First check if the directory is already known, because then we can
- // return early.
- if _, ok := s.knownSubdirs[span.URIFromPath(dir)]; ok {
- return
- }
- var matched span.URI
- for _, wsDir := range dirs {
- if source.InDir(wsDir.Filename(), dir) {
- matched = wsDir
- break
- }
- }
- // Don't watch any directory outside of the workspace directories.
- if matched == "" {
- return
- }
- for {
- if dir == "" || dir == matched.Filename() {
- break
- }
- uri := span.URIFromPath(dir)
- if _, ok := s.knownSubdirs[uri]; ok {
- break
- }
- s.knownSubdirs[uri] = struct{}{}
- dir = filepath.Dir(dir)
- }
-}
-
-func (s *snapshot) removeKnownSubdirLocked(uri span.URI) {
- dir := filepath.Dir(uri.Filename())
- for dir != "" {
- uri := span.URIFromPath(dir)
- if _, ok := s.knownSubdirs[uri]; !ok {
- break
- }
- if info, _ := os.Stat(dir); info == nil {
- delete(s.knownSubdirs, uri)
- }
- dir = filepath.Dir(dir)
- }
-}
-
-// knownFilesInDir returns the files known to the given snapshot that are in
-// the given directory. It does not respect symlinks.
-func (s *snapshot) knownFilesInDir(ctx context.Context, dir span.URI) []span.URI {
- var files []span.URI
- s.mu.Lock()
- defer s.mu.Unlock()
-
- for uri := range s.files {
- if source.InDir(dir.Filename(), uri.Filename()) {
- files = append(files, uri)
- }
- }
- return files
-}
-
-func (s *snapshot) workspacePackageHandles(ctx context.Context) ([]*packageHandle, error) {
- if err := s.awaitLoaded(ctx); err != nil {
- return nil, err
- }
- var phs []*packageHandle
- for _, pkgID := range s.workspacePackageIDs() {
- ph, err := s.buildPackageHandle(ctx, pkgID, s.workspaceParseMode(pkgID))
- if err != nil {
- return nil, err
- }
- phs = append(phs, ph)
- }
- return phs, nil
-}
-
-func (s *snapshot) ActivePackages(ctx context.Context) ([]source.Package, error) {
- phs, err := s.activePackageHandles(ctx)
- if err != nil {
- return nil, err
- }
- var pkgs []source.Package
- for _, ph := range phs {
- pkg, err := ph.check(ctx, s)
- if err != nil {
- return nil, err
- }
- pkgs = append(pkgs, pkg)
- }
- return pkgs, nil
-}
-
-func (s *snapshot) activePackageHandles(ctx context.Context) ([]*packageHandle, error) {
- if err := s.awaitLoaded(ctx); err != nil {
- return nil, err
- }
- var phs []*packageHandle
- for _, pkgID := range s.activePackageIDs() {
- ph, err := s.buildPackageHandle(ctx, pkgID, s.workspaceParseMode(pkgID))
- if err != nil {
- return nil, err
- }
- phs = append(phs, ph)
- }
- return phs, nil
-}
-
-func (s *snapshot) Symbols(ctx context.Context) (map[span.URI][]source.Symbol, error) {
- result := make(map[span.URI][]source.Symbol)
-
- // Keep going on errors, but log the first failure. Partial symbol results
- // are better than no symbol results.
- var firstErr error
- for uri, f := range s.files {
- sh := s.buildSymbolHandle(ctx, f)
- v, err := sh.handle.Get(ctx, s.generation, s)
- if err != nil {
- if firstErr == nil {
- firstErr = err
- }
- continue
- }
- data := v.(*symbolData)
- result[uri] = data.symbols
- }
- if firstErr != nil {
- event.Error(ctx, "getting snapshot symbols", firstErr)
- }
- return result, nil
-}
-
-func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]source.Metadata, error) {
- knownIDs, err := s.getOrLoadIDsForURI(ctx, uri)
- if err != nil {
- return nil, err
- }
- var mds []source.Metadata
- for _, id := range knownIDs {
- md := s.getMetadata(id)
- // TODO(rfindley): knownIDs and metadata should be in sync, but existing
- // code is defensive of nil metadata.
- if md != nil {
- mds = append(mds, md)
- }
- }
- return mds, nil
-}
-
-func (s *snapshot) KnownPackages(ctx context.Context) ([]source.Package, error) {
- if err := s.awaitLoaded(ctx); err != nil {
- return nil, err
- }
-
- // The WorkspaceSymbols implementation relies on this function returning
- // workspace packages first.
- ids := s.workspacePackageIDs()
- s.mu.Lock()
- for id := range s.metadata {
- if _, ok := s.workspacePackages[id]; ok {
- continue
- }
- ids = append(ids, id)
- }
- s.mu.Unlock()
-
- var pkgs []source.Package
- for _, id := range ids {
- pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id))
- if err != nil {
- return nil, err
- }
- pkgs = append(pkgs, pkg)
- }
- return pkgs, nil
-}
-
-func (s *snapshot) CachedImportPaths(ctx context.Context) (map[string]source.Package, error) {
- // Don't reload workspace package metadata.
- // This function is meant to only return currently cached information.
- s.AwaitInitialized(ctx)
-
- s.mu.Lock()
- defer s.mu.Unlock()
-
- results := map[string]source.Package{}
- for _, ph := range s.packages {
- cachedPkg, err := ph.cached(s.generation)
- if err != nil {
- continue
- }
- for importPath, newPkg := range cachedPkg.imports {
- if oldPkg, ok := results[string(importPath)]; ok {
- // Using the same trick as NarrowestPackage, prefer non-variants.
- if len(newPkg.compiledGoFiles) < len(oldPkg.(*pkg).compiledGoFiles) {
- results[string(importPath)] = newPkg
- }
- } else {
- results[string(importPath)] = newPkg
- }
- }
- }
- return results, nil
-}
-
-func (s *snapshot) GoModForFile(uri span.URI) span.URI {
- return moduleForURI(s.workspace.activeModFiles, uri)
-}
-
-func moduleForURI(modFiles map[span.URI]struct{}, uri span.URI) span.URI {
- var match span.URI
- for modURI := range modFiles {
- if !source.InDir(dirURI(modURI).Filename(), uri.Filename()) {
- continue
- }
- if len(modURI) > len(match) {
- match = modURI
- }
- }
- return match
-}
-
-func (s *snapshot) getPackage(id PackageID, mode source.ParseMode) *packageHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- key := packageKey{
- id: id,
- mode: mode,
- }
- return s.packages[key]
-}
-
-func (s *snapshot) getSymbolHandle(uri span.URI) *symbolHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- return s.symbols[uri]
-}
-
-func (s *snapshot) addSymbolHandle(sh *symbolHandle) *symbolHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- uri := sh.fh.URI()
- // If the package handle has already been cached,
- // return the cached handle instead of overriding it.
- if sh, ok := s.symbols[uri]; ok {
- return sh
- }
- s.symbols[uri] = sh
- return sh
-}
-
-func (s *snapshot) getActionHandle(id PackageID, m source.ParseMode, a *analysis.Analyzer) *actionHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- key := actionKey{
- pkg: packageKey{
- id: id,
- mode: m,
- },
- analyzer: a,
- }
- return s.actions[key]
-}
-
-func (s *snapshot) addActionHandle(ah *actionHandle) *actionHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- key := actionKey{
- analyzer: ah.analyzer,
- pkg: packageKey{
- id: ah.pkg.m.ID,
- mode: ah.pkg.mode,
- },
- }
- if ah, ok := s.actions[key]; ok {
- return ah
- }
- s.actions[key] = ah
- return ah
-}
-
-func (s *snapshot) getIDsForURI(uri span.URI) []PackageID {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- return s.ids[uri]
-}
-
-func (s *snapshot) getMetadata(id PackageID) *KnownMetadata {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- return s.metadata[id]
-}
-
-func (s *snapshot) shouldLoad(scope interface{}) bool {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- switch scope := scope.(type) {
- case PackagePath:
- var meta *KnownMetadata
- for _, m := range s.metadata {
- if m.PkgPath != scope {
- continue
- }
- meta = m
- }
- if meta == nil || meta.ShouldLoad {
- return true
- }
- return false
- case fileURI:
- uri := span.URI(scope)
- ids := s.ids[uri]
- if len(ids) == 0 {
- return true
- }
- for _, id := range ids {
- m, ok := s.metadata[id]
- if !ok || m.ShouldLoad {
- return true
- }
- }
- return false
- default:
- return true
- }
-}
-
-func (s *snapshot) clearShouldLoad(scope interface{}) {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- switch scope := scope.(type) {
- case PackagePath:
- var meta *KnownMetadata
- for _, m := range s.metadata {
- if m.PkgPath == scope {
- meta = m
- }
- }
- if meta == nil {
- return
- }
- meta.ShouldLoad = false
- case fileURI:
- uri := span.URI(scope)
- ids := s.ids[uri]
- if len(ids) == 0 {
- return
- }
- for _, id := range ids {
- if m, ok := s.metadata[id]; ok {
- m.ShouldLoad = false
- }
- }
- }
-}
-
-// noValidMetadataForURILocked reports whether there is any valid metadata for
-// the given URI.
-func (s *snapshot) noValidMetadataForURILocked(uri span.URI) bool {
- ids, ok := s.ids[uri]
- if !ok {
- return true
- }
- for _, id := range ids {
- if m, ok := s.metadata[id]; ok && m.Valid {
- return false
- }
- }
- return true
-}
-
-// noValidMetadataForID reports whether there is no valid metadata for the
-// given ID.
-func (s *snapshot) noValidMetadataForID(id PackageID) bool {
- s.mu.Lock()
- defer s.mu.Unlock()
- return s.noValidMetadataForIDLocked(id)
-}
-
-func (s *snapshot) noValidMetadataForIDLocked(id PackageID) bool {
- m := s.metadata[id]
- return m == nil || !m.Valid
-}
-
-// updateIDForURIsLocked adds the given ID to the set of known IDs for the given URI.
-// Any existing invalid IDs are removed from the set of known IDs. IDs that are
-// not "command-line-arguments" are preferred, so if a new ID comes in for a
-// URI that previously only had "command-line-arguments", the new ID will
-// replace the "command-line-arguments" ID.
-func (s *snapshot) updateIDForURIsLocked(id PackageID, uris map[span.URI]struct{}) {
- for uri := range uris {
- // Collect the new set of IDs, preserving any valid existing IDs.
- newIDs := []PackageID{id}
- for _, existingID := range s.ids[uri] {
- // Don't set duplicates of the same ID.
- if existingID == id {
- continue
- }
- // If the package previously only had a command-line-arguments ID,
- // delete the command-line-arguments workspace package.
- if source.IsCommandLineArguments(string(existingID)) {
- delete(s.workspacePackages, existingID)
- continue
- }
- // If the metadata for an existing ID is invalid, and we are
- // setting metadata for a new, valid ID--don't preserve the old ID.
- if m, ok := s.metadata[existingID]; !ok || !m.Valid {
- continue
- }
- newIDs = append(newIDs, existingID)
- }
- sort.Slice(newIDs, func(i, j int) bool {
- return newIDs[i] < newIDs[j]
- })
- s.ids[uri] = newIDs
- }
-}
-
-func (s *snapshot) isWorkspacePackage(id PackageID) bool {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- _, ok := s.workspacePackages[id]
- return ok
-}
-
-func (s *snapshot) FindFile(uri span.URI) source.VersionedFileHandle {
- f := s.view.getFile(uri)
-
- s.mu.Lock()
- defer s.mu.Unlock()
-
- return s.files[f.URI()]
-}
-
-// GetVersionedFile returns a File for the given URI. If the file is unknown it
-// is added to the managed set.
-//
-// GetVersionedFile 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) GetVersionedFile(ctx context.Context, uri span.URI) (source.VersionedFileHandle, error) {
- f := s.view.getFile(uri)
-
- s.mu.Lock()
- defer s.mu.Unlock()
- return s.getFileLocked(ctx, f)
-}
-
-// GetFile implements the fileSource interface by wrapping GetVersionedFile.
-func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
- return s.GetVersionedFile(ctx, uri)
-}
-
-func (s *snapshot) getFileLocked(ctx context.Context, f *fileBase) (source.VersionedFileHandle, error) {
- if fh, ok := s.files[f.URI()]; ok {
- return fh, nil
- }
-
- fh, err := s.view.session.cache.getFile(ctx, f.URI())
- if err != nil {
- return nil, err
- }
- closed := &closedFile{fh}
- s.files[f.URI()] = closed
- return closed, nil
-}
-
-func (s *snapshot) IsOpen(uri span.URI) bool {
- s.mu.Lock()
- defer s.mu.Unlock()
- return s.isOpenLocked(uri)
-
-}
-
-func (s *snapshot) openFiles() []source.VersionedFileHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- var open []source.VersionedFileHandle
- for _, fh := range s.files {
- if s.isOpenLocked(fh.URI()) {
- open = append(open, fh)
- }
- }
- return open
-}
-
-func (s *snapshot) isOpenLocked(uri span.URI) bool {
- _, open := s.files[uri].(*overlay)
- return open
-}
-
-func (s *snapshot) awaitLoaded(ctx context.Context) error {
- loadErr := s.awaitLoadedAllErrors(ctx)
-
- s.mu.Lock()
- defer s.mu.Unlock()
-
- // If we still have absolutely no metadata, check if the view failed to
- // initialize and return any errors.
- if s.useInvalidMetadata() && len(s.metadata) > 0 {
- return nil
- }
- for _, m := range s.metadata {
- if m.Valid {
- return nil
- }
- }
- if loadErr != nil {
- return loadErr.MainError
- }
- return nil
-}
-
-func (s *snapshot) GetCriticalError(ctx context.Context) *source.CriticalError {
- loadErr := s.awaitLoadedAllErrors(ctx)
- if loadErr != nil && errors.Is(loadErr.MainError, context.Canceled) {
- return nil
- }
-
- // Even if packages didn't fail to load, we still may want to show
- // additional warnings.
- if loadErr == nil {
- wsPkgs, _ := s.ActivePackages(ctx)
- if msg := shouldShowAdHocPackagesWarning(s, wsPkgs); msg != "" {
- return &source.CriticalError{
- MainError: errors.New(msg),
- }
- }
- // Even if workspace packages were returned, there still may be an error
- // with the user's workspace layout. Workspace packages that only have the
- // ID "command-line-arguments" are usually a symptom of a bad workspace
- // configuration.
- if containsCommandLineArguments(wsPkgs) {
- return s.workspaceLayoutError(ctx)
- }
- return nil
- }
-
- if errMsg := loadErr.MainError.Error(); strings.Contains(errMsg, "cannot find main module") || strings.Contains(errMsg, "go.mod file not found") {
- return s.workspaceLayoutError(ctx)
- }
- return loadErr
-}
-
-const adHocPackagesWarning = `You are outside of a module and outside of $GOPATH/src.
-If you are using modules, please open your editor to a directory in your module.
-If you believe this warning is incorrect, please file an issue: https://github.com/golang/go/issues/new.`
-
-func shouldShowAdHocPackagesWarning(snapshot source.Snapshot, pkgs []source.Package) string {
- if snapshot.ValidBuildConfiguration() {
- return ""
- }
- for _, pkg := range pkgs {
- if len(pkg.MissingDependencies()) > 0 {
- return adHocPackagesWarning
- }
- }
- return ""
-}
-
-func containsCommandLineArguments(pkgs []source.Package) bool {
- for _, pkg := range pkgs {
- if source.IsCommandLineArguments(pkg.ID()) {
- return true
- }
- }
- return false
-}
-
-func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) *source.CriticalError {
- // Do not return results until the snapshot's view has been initialized.
- s.AwaitInitialized(ctx)
-
- // TODO(rstambler): Should we be more careful about returning the
- // initialization error? Is it possible for the initialization error to be
- // corrected without a successful reinitialization?
- s.mu.Lock()
- initializedErr := s.initializedErr
- s.mu.Unlock()
- if initializedErr != nil {
- return initializedErr
- }
-
- if ctx.Err() != nil {
- return &source.CriticalError{MainError: ctx.Err()}
- }
-
- if err := s.reloadWorkspace(ctx); err != nil {
- diags, _ := s.extractGoCommandErrors(ctx, err.Error())
- return &source.CriticalError{
- MainError: err,
- DiagList: diags,
- }
- }
- if err := s.reloadOrphanedFiles(ctx); err != nil {
- diags, _ := s.extractGoCommandErrors(ctx, err.Error())
- return &source.CriticalError{
- MainError: err,
- DiagList: diags,
- }
- }
- return nil
-}
-
-func (s *snapshot) getInitializationError(ctx context.Context) *source.CriticalError {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- return s.initializedErr
-}
-
-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) error {
- // See which of the workspace packages are missing metadata.
- s.mu.Lock()
- missingMetadata := len(s.workspacePackages) == 0 || len(s.metadata) == 0
- pkgPathSet := map[PackagePath]struct{}{}
- for id, pkgPath := range s.workspacePackages {
- if m, ok := s.metadata[id]; ok && m.Valid {
- continue
- }
- missingMetadata = true
-
- // Don't try to reload "command-line-arguments" directly.
- if source.IsCommandLineArguments(string(pkgPath)) {
- continue
- }
- pkgPathSet[pkgPath] = struct{}{}
- }
- s.mu.Unlock()
-
- // If the view's build configuration is invalid, we cannot reload by
- // package path. Just reload the directory instead.
- if missingMetadata && !s.ValidBuildConfiguration() {
- return s.load(ctx, false, viewLoadScope("LOAD_INVALID_VIEW"))
- }
-
- if len(pkgPathSet) == 0 {
- return nil
- }
-
- var pkgPaths []interface{}
- for pkgPath := range pkgPathSet {
- pkgPaths = append(pkgPaths, pkgPath)
- }
- return s.load(ctx, false, pkgPaths...)
-}
-
-func (s *snapshot) reloadOrphanedFiles(ctx context.Context) error {
- // When we load ./... or a package path directly, we may not get packages
- // that exist only in overlays. As a workaround, we search all of the files
- // available in the snapshot and reload their metadata individually using a
- // file= query if the metadata is unavailable.
- files := s.orphanedFiles()
-
- // Files without a valid package declaration can't be loaded. Don't try.
- var scopes []interface{}
- for _, file := range files {
- pgf, err := s.ParseGo(ctx, file, source.ParseHeader)
- if err != nil {
- continue
- }
- if !pgf.File.Package.IsValid() {
- continue
- }
- scopes = append(scopes, fileURI(file.URI()))
- }
-
- if len(scopes) == 0 {
- return nil
- }
-
- // The regtests match this exact log message, keep them in sync.
- event.Log(ctx, "reloadOrphanedFiles reloading", tag.Query.Of(scopes))
- err := s.load(ctx, false, scopes...)
-
- // If we failed to load some files, i.e. they have no metadata,
- // mark the failures so we don't bother retrying until the file's
- // content changes.
- //
- // TODO(rstambler): This may be an overestimate if the load stopped
- // early for an unrelated errors. Add a fallback?
- //
- // Check for context cancellation so that we don't incorrectly mark files
- // as unloadable, but don't return before setting all workspace packages.
- if ctx.Err() == nil && err != nil {
- event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Query.Of(scopes))
- s.mu.Lock()
- for _, scope := range scopes {
- uri := span.URI(scope.(fileURI))
- if s.noValidMetadataForURILocked(uri) {
- s.unloadableFiles[uri] = struct{}{}
- }
- }
- s.mu.Unlock()
- }
- return nil
-}
-
-func (s *snapshot) orphanedFiles() []source.VersionedFileHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- var files []source.VersionedFileHandle
- for uri, fh := range s.files {
- // Don't try to reload metadata for go.mod files.
- if s.view.FileKind(fh) != source.Go {
- continue
- }
- // If the URI doesn't belong to this view, then it's not in a workspace
- // package and should not be reloaded directly.
- if !contains(s.view.session.viewsOf(uri), s.view) {
- continue
- }
- // If the file is not open and is in a vendor directory, don't treat it
- // like a workspace package.
- if _, ok := fh.(*overlay); !ok && inVendor(uri) {
- continue
- }
- // Don't reload metadata for files we've already deemed unloadable.
- if _, ok := s.unloadableFiles[uri]; ok {
- continue
- }
- if s.noValidMetadataForURILocked(uri) {
- files = append(files, fh)
- }
- }
- return files
-}
-
-func contains(views []*View, view *View) bool {
- for _, v := range views {
- if v == view {
- return true
- }
- }
- return false
-}
-
-func inVendor(uri span.URI) bool {
- if !strings.Contains(string(uri), "/vendor/") {
- return false
- }
- // Only packages in _subdirectories_ of /vendor/ are considered vendored
- // (/vendor/a/foo.go is vendored, /vendor/foo.go is not).
- split := strings.Split(string(uri), "/vendor/")
- if len(split) < 2 {
- return false
- }
- return strings.Contains(split[1], "/")
-}
-
-func generationName(v *View, snapshotID uint64) string {
- return fmt.Sprintf("v%v/%v", v.id, snapshotID)
-}
-
-// checkSnapshotLocked verifies that some invariants are preserved on the
-// snapshot.
-func checkSnapshotLocked(ctx context.Context, s *snapshot) {
- // Check that every go file for a workspace package is identified as
- // belonging to that workspace package.
- for wsID := range s.workspacePackages {
- if m, ok := s.metadata[wsID]; ok {
- for _, uri := range m.GoFiles {
- found := false
- for _, id := range s.ids[uri] {
- if id == wsID {
- found = true
- break
- }
- }
- if !found {
- log.Error.Logf(ctx, "workspace package %v not associated with %v", wsID, uri)
- }
- }
- }
- }
-}
-
-// unappliedChanges is a file source that handles an uncloned snapshot.
-type unappliedChanges struct {
- originalSnapshot *snapshot
- changes map[span.URI]*fileChange
-}
-
-func (ac *unappliedChanges) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
- if c, ok := ac.changes[uri]; ok {
- return c.fileHandle, nil
- }
- return ac.originalSnapshot.GetFile(ctx, uri)
-}
-
-func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) *snapshot {
- var vendorChanged bool
- newWorkspace, workspaceChanged, workspaceReload := s.workspace.invalidate(ctx, changes, &unappliedChanges{
- originalSnapshot: s,
- changes: changes,
- })
-
- s.mu.Lock()
- defer s.mu.Unlock()
-
- checkSnapshotLocked(ctx, s)
-
- newGen := s.view.session.cache.store.Generation(generationName(s.view, s.id+1))
- bgCtx, cancel := context.WithCancel(bgCtx)
- result := &snapshot{
- id: s.id + 1,
- generation: newGen,
- view: s.view,
- backgroundCtx: bgCtx,
- cancel: cancel,
- builtin: s.builtin,
- initializeOnce: s.initializeOnce,
- initializedErr: s.initializedErr,
- ids: make(map[span.URI][]PackageID, len(s.ids)),
- importedBy: make(map[PackageID][]PackageID, len(s.importedBy)),
- metadata: make(map[PackageID]*KnownMetadata, len(s.metadata)),
- packages: make(map[packageKey]*packageHandle, len(s.packages)),
- actions: make(map[actionKey]*actionHandle, len(s.actions)),
- files: make(map[span.URI]source.VersionedFileHandle, len(s.files)),
- goFiles: make(map[parseKey]*parseGoHandle, len(s.goFiles)),
- symbols: make(map[span.URI]*symbolHandle, len(s.symbols)),
- workspacePackages: make(map[PackageID]PackagePath, len(s.workspacePackages)),
- unloadableFiles: make(map[span.URI]struct{}, len(s.unloadableFiles)),
- parseModHandles: make(map[span.URI]*parseModHandle, len(s.parseModHandles)),
- parseWorkHandles: make(map[span.URI]*parseWorkHandle, len(s.parseWorkHandles)),
- modTidyHandles: make(map[span.URI]*modTidyHandle, len(s.modTidyHandles)),
- modWhyHandles: make(map[span.URI]*modWhyHandle, len(s.modWhyHandles)),
- knownSubdirs: make(map[span.URI]struct{}, len(s.knownSubdirs)),
- workspace: newWorkspace,
- }
-
- if !workspaceChanged && s.workspaceDirHandle != nil {
- result.workspaceDirHandle = s.workspaceDirHandle
- newGen.Inherit(s.workspaceDirHandle)
- }
-
- // Copy all of the FileHandles.
- for k, v := range s.files {
- result.files[k] = v
- }
- for k, v := range s.symbols {
- if change, ok := changes[k]; ok {
- if change.exists {
- result.symbols[k] = result.buildSymbolHandle(ctx, change.fileHandle)
- }
- continue
- }
- newGen.Inherit(v.handle)
- result.symbols[k] = v
- }
-
- // Copy the set of unloadable files.
- for k, v := range s.unloadableFiles {
- result.unloadableFiles[k] = v
- }
- // Copy all of the modHandles.
- for k, v := range s.parseModHandles {
- result.parseModHandles[k] = v
- }
- // Copy all of the parseWorkHandles.
- for k, v := range s.parseWorkHandles {
- result.parseWorkHandles[k] = v
- }
-
- for k, v := range s.goFiles {
- if _, ok := changes[k.file.URI]; ok {
- continue
- }
- newGen.Inherit(v.handle)
- result.goFiles[k] = v
- }
-
- // Copy all of the go.mod-related handles. They may be invalidated later,
- // so we inherit them at the end of the function.
- for k, v := range s.modTidyHandles {
- if _, ok := changes[k]; ok {
- continue
- }
- result.modTidyHandles[k] = v
- }
- for k, v := range s.modWhyHandles {
- if _, ok := changes[k]; ok {
- continue
- }
- result.modWhyHandles[k] = v
- }
-
- // Add all of the known subdirectories, but don't update them for the
- // changed files. We need to rebuild the workspace module to know the
- // true set of known subdirectories, but we don't want to do that in clone.
- for k, v := range s.knownSubdirs {
- result.knownSubdirs[k] = v
- }
- for _, c := range changes {
- result.unprocessedSubdirChanges = append(result.unprocessedSubdirChanges, c)
- }
-
- // directIDs keeps track of package IDs that have directly changed.
- // It maps id->invalidateMetadata.
- directIDs := map[PackageID]bool{}
-
- // Invalidate all package metadata if the workspace module has changed.
- if workspaceReload {
- for k := range s.metadata {
- directIDs[k] = true
- }
- }
-
- changedPkgNames := map[PackageID]struct{}{}
- anyImportDeleted := false
- for uri, change := range changes {
- // Maybe reinitialize the view if we see a change in the vendor
- // directory.
- if inVendor(uri) {
- vendorChanged = true
- }
-
- // The original FileHandle for this URI is cached on the snapshot.
- originalFH := s.files[uri]
-
- // Check if the file's package name or imports have changed,
- // and if so, invalidate this file's packages' metadata.
- var shouldInvalidateMetadata, pkgNameChanged, importDeleted bool
- if !isGoMod(uri) {
- shouldInvalidateMetadata, pkgNameChanged, importDeleted = s.shouldInvalidateMetadata(ctx, result, originalFH, change.fileHandle)
- }
- invalidateMetadata := forceReloadMetadata || workspaceReload || shouldInvalidateMetadata
- anyImportDeleted = anyImportDeleted || importDeleted
-
- // Mark all of the package IDs containing the given file.
- // TODO: if the file has moved into a new package, we should invalidate that too.
- filePackageIDs := guessPackageIDsForURI(uri, s.ids)
- if pkgNameChanged {
- for _, id := range filePackageIDs {
- changedPkgNames[id] = struct{}{}
- }
- }
- for _, id := range filePackageIDs {
- directIDs[id] = directIDs[id] || invalidateMetadata
- }
-
- // Invalidate the previous modTidyHandle if any of the files have been
- // saved or if any of the metadata has been invalidated.
- if invalidateMetadata || fileWasSaved(originalFH, change.fileHandle) {
- // TODO(rstambler): Only delete mod handles for which the
- // withoutURI is relevant.
- for k := range s.modTidyHandles {
- delete(result.modTidyHandles, k)
- }
- for k := range s.modWhyHandles {
- delete(result.modWhyHandles, k)
- }
- }
- delete(result.parseModHandles, uri)
- delete(result.parseWorkHandles, uri)
- // Handle the invalidated file; it may have new contents or not exist.
- if !change.exists {
- delete(result.files, uri)
- } else {
- result.files[uri] = change.fileHandle
- }
-
- // Make sure to remove the changed file from the unloadable set.
- delete(result.unloadableFiles, uri)
- }
-
- // 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, metadata := range s.metadata {
- if len(metadata.Errors) > 0 {
- directIDs[id] = true
- }
- }
- }
-
- // Invalidate reverse dependencies too.
- // TODO(heschi): figure out the locking model and use transitiveReverseDeps?
- // 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.getImportedByLocked(id) {
- addRevDeps(rid, invalidateMetadata)
- }
- }
- for id, invalidateMetadata := range directIDs {
- addRevDeps(id, invalidateMetadata)
- }
-
- // Copy the package type information.
- for k, v := range s.packages {
- if _, ok := idsToInvalidate[k.id]; ok {
- continue
- }
- newGen.Inherit(v.handle)
- result.packages[k] = v
- }
- // Copy the package analysis information.
- for k, v := range s.actions {
- if _, ok := idsToInvalidate[k.pkg.id]; ok {
- continue
- }
- newGen.Inherit(v.handle)
- result.actions[k] = v
- }
-
- // If the workspace mode has changed, we must delete all metadata, as it
- // is unusable and may produce confusing or incorrect diagnostics.
- // If a file has been deleted, we must delete metadata all packages
- // containing that file.
- workspaceModeChanged := s.workspaceMode() != result.workspaceMode()
- skipID := map[PackageID]bool{}
- for _, c := range changes {
- if c.exists {
- continue
- }
- // The file has been deleted.
- if ids, ok := s.ids[c.fileHandle.URI()]; ok {
- for _, id := range ids {
- skipID[id] = true
- }
- }
- }
-
- // Collect all of the IDs that are reachable from the workspace packages.
- // Any unreachable IDs will have their metadata deleted outright.
- reachableID := map[PackageID]bool{}
- var addForwardDeps func(PackageID)
- addForwardDeps = func(id PackageID) {
- if reachableID[id] {
- return
- }
- reachableID[id] = true
- m, ok := s.metadata[id]
- if !ok {
- return
- }
- for _, depID := range m.Deps {
- addForwardDeps(depID)
- }
- }
- for id := range s.workspacePackages {
- addForwardDeps(id)
- }
-
- // Copy the URI to package ID mappings, skipping only those URIs whose
- // metadata will be reloaded in future calls to load.
- deleteInvalidMetadata := forceReloadMetadata || workspaceModeChanged
- idsInSnapshot := map[PackageID]bool{} // track all known IDs
- for uri, ids := range s.ids {
- for _, id := range ids {
- invalidateMetadata := idsToInvalidate[id]
- if skipID[id] || (invalidateMetadata && deleteInvalidMetadata) {
- continue
- }
- // The ID is not reachable from any workspace package, so it should
- // be deleted.
- if !reachableID[id] {
- continue
- }
- idsInSnapshot[id] = true
- result.ids[uri] = append(result.ids[uri], id)
- }
- }
-
- // Copy the package metadata. We only need to invalidate packages directly
- // containing the affected file, and only if it changed in a relevant way.
- for k, v := range s.metadata {
- if !idsInSnapshot[k] {
- // Delete metadata for IDs that are no longer reachable from files
- // in the snapshot.
- continue
- }
- invalidateMetadata := idsToInvalidate[k]
- // Mark invalidated metadata rather than deleting it outright.
- result.metadata[k] = &KnownMetadata{
- Metadata: v.Metadata,
- Valid: v.Valid && !invalidateMetadata,
- ShouldLoad: v.ShouldLoad || invalidateMetadata,
- }
- }
-
- // Copy the set of initially loaded packages.
- for id, pkgPath := range s.workspacePackages {
- // Packages with the id "command-line-arguments" are generated by the
- // go command when the user is outside of GOPATH and outside of a
- // module. Do not cache them as workspace packages for longer than
- // necessary.
- if source.IsCommandLineArguments(string(id)) {
- if invalidateMetadata, ok := idsToInvalidate[id]; invalidateMetadata && ok {
- continue
- }
- }
-
- // If all the files we know about in a package have been deleted,
- // the package is gone and we should no longer try to load it.
- if m := s.metadata[id]; m != nil {
- hasFiles := false
- for _, uri := range s.metadata[id].GoFiles {
- // For internal tests, we need _test files, not just the normal
- // ones. External tests only have _test files, but we can check
- // them anyway.
- if m.ForTest != "" && !strings.HasSuffix(string(uri), "_test.go") {
- continue
- }
- if _, ok := result.files[uri]; ok {
- hasFiles = true
- break
- }
- }
- if !hasFiles {
- continue
- }
- }
-
- // If the package name of a file in the package has changed, it's
- // possible that the package ID may no longer exist. Delete it from
- // the set of workspace packages, on the assumption that we will add it
- // back when the relevant files are reloaded.
- if _, ok := changedPkgNames[id]; ok {
- continue
- }
-
- result.workspacePackages[id] = pkgPath
- }
-
- // Inherit all of the go.mod-related handles.
- for _, v := range result.modTidyHandles {
- newGen.Inherit(v.handle)
- }
- for _, v := range result.modWhyHandles {
- newGen.Inherit(v.handle)
- }
- for _, v := range result.parseModHandles {
- newGen.Inherit(v.handle)
- }
- for _, v := range result.parseWorkHandles {
- newGen.Inherit(v.handle)
- }
- // Don't bother copying the importedBy graph,
- // as it changes each time we update metadata.
-
- // If the snapshot's workspace mode has changed, the packages loaded using
- // the previous mode are no longer relevant, so clear them out.
- if workspaceModeChanged {
- result.workspacePackages = map[PackageID]PackagePath{}
- }
-
- // The snapshot may need to be reinitialized.
- if workspaceReload || vendorChanged {
- if workspaceChanged || result.initializedErr != nil {
- result.initializeOnce = &sync.Once{}
- }
- }
- return result
-}
-
-// guessPackageIDsForURI returns all packages related 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.
-func guessPackageIDsForURI(uri span.URI, known map[span.URI][]PackageID) []PackageID {
- packages := known[uri]
- if len(packages) > 0 {
- // We've seen this file before.
- return packages
- }
- // This is a file we don't yet know about. 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.Filename())
- fi, err := getInfo(dir)
- if err != nil {
- return nil
- }
-
- // Aggregate all possibly relevant package IDs.
- var found []PackageID
- for knownURI, ids := range known {
- knownDir := filepath.Dir(knownURI.Filename())
- knownFI, err := getInfo(knownDir)
- if err != nil {
- continue
- }
- if os.SameFile(fi, knownFI) {
- found = append(found, ids...)
- }
- }
- return found
-}
-
-// 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 source.FileHandle) 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
-}
-
-// shouldInvalidateMetadata reparses the full file's AST to determine
-// if the file requires a metadata reload.
-func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, newSnapshot *snapshot, originalFH, currentFH source.FileHandle) (invalidate, pkgNameChanged, importDeleted bool) {
- if originalFH == nil {
- return true, false, false
- }
- // If the file hasn't changed, there's no need to reload.
- if originalFH.FileIdentity() == currentFH.FileIdentity() {
- return false, false, false
- }
- // Get the original and current parsed files in order to check package name
- // and imports. Use the new snapshot to parse to avoid modifying the
- // current snapshot.
- original, originalErr := newSnapshot.ParseGo(ctx, originalFH, source.ParseFull)
- current, currentErr := newSnapshot.ParseGo(ctx, currentFH, source.ParseFull)
- if originalErr != nil || currentErr != nil {
- return (originalErr == nil) != (currentErr == nil), false, (currentErr != nil) // we don't know if an import was deleted
- }
- // Check if the package's metadata has changed. The cases handled are:
- // 1. A package's name has changed
- // 2. A file's imports have changed
- if original.File.Name.Name != current.File.Name.Name {
- invalidate = true
- pkgNameChanged = true
- }
- origImportSet := make(map[string]struct{})
- for _, importSpec := range original.File.Imports {
- origImportSet[importSpec.Path.Value] = struct{}{}
- }
- curImportSet := make(map[string]struct{})
- for _, importSpec := range current.File.Imports {
- curImportSet[importSpec.Path.Value] = struct{}{}
- }
- // If any of the current imports were not in the original imports.
- for path := range curImportSet {
- if _, ok := origImportSet[path]; ok {
- delete(origImportSet, path)
- continue
- }
- // If the import path is obviously not valid, we can skip reloading
- // metadata. For now, valid means properly quoted and without a
- // terminal slash.
- if isBadImportPath(path) {
- continue
- }
- invalidate = true
- }
-
- for path := range origImportSet {
- if !isBadImportPath(path) {
- invalidate = true
- importDeleted = true
- }
- }
-
- if !invalidate {
- invalidate = magicCommentsChanged(original.File, current.File)
- }
- return invalidate, pkgNameChanged, 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
-}
-
-func isBadImportPath(path string) bool {
- path, err := strconv.Unquote(path)
- if err != nil {
- return true
- }
- if path == "" {
- return true
- }
- if path[len(path)-1] == '/' {
- return true
- }
- return false
-}
-
-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
-}
-
-func (s *snapshot) BuiltinFile(ctx context.Context) (*source.ParsedGoFile, error) {
- s.AwaitInitialized(ctx)
-
- s.mu.Lock()
- builtin := s.builtin
- s.mu.Unlock()
-
- if builtin == "" {
- return nil, errors.Errorf("no builtin package for view %s", s.view.name)
- }
-
- fh, err := s.GetFile(ctx, builtin)
- if err != nil {
- return nil, err
- }
- return s.ParseGo(ctx, fh, source.ParseFull)
-}
-
-func (s *snapshot) IsBuiltin(ctx context.Context, uri span.URI) 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 = span.URIFromPath(path)
-}
-
-// BuildGoplsMod generates a go.mod file for all modules in the workspace. It
-// bypasses any existing gopls.mod.
-func (s *snapshot) BuildGoplsMod(ctx context.Context) (*modfile.File, error) {
- allModules, err := findModules(s.view.folder, pathExcludedByFilterFunc(s.view.rootURI.Filename(), s.view.gomodcache, s.View().Options()), 0)
- if err != nil {
- return nil, err
- }
- return buildWorkspaceModFile(ctx, allModules, s)
-}
-
-// TODO(rfindley): move this to workspacemodule.go
-func buildWorkspaceModFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) (*modfile.File, error) {
- file := &modfile.File{}
- file.AddModuleStmt("gopls-workspace")
- // Track the highest Go version, to be set on the workspace module.
- // Fall back to 1.12 -- old versions insist on having some version.
- goVersion := "1.12"
-
- paths := map[string]span.URI{}
- excludes := map[string][]string{}
- var sortedModURIs []span.URI
- for uri := range modFiles {
- sortedModURIs = append(sortedModURIs, uri)
- }
- sort.Slice(sortedModURIs, func(i, j int) bool {
- return sortedModURIs[i] < sortedModURIs[j]
- })
- for _, modURI := range sortedModURIs {
- fh, err := fs.GetFile(ctx, modURI)
- if err != nil {
- return nil, err
- }
- content, err := fh.Read()
- if err != nil {
- return nil, err
- }
- parsed, err := modfile.Parse(fh.URI().Filename(), content, nil)
- if err != nil {
- return nil, err
- }
- if file == nil || parsed.Module == nil {
- return nil, fmt.Errorf("no module declaration for %s", modURI)
- }
- // Prepend "v" to go versions to make them valid semver.
- if parsed.Go != nil && semver.Compare("v"+goVersion, "v"+parsed.Go.Version) < 0 {
- goVersion = parsed.Go.Version
- }
- path := parsed.Module.Mod.Path
- if _, ok := paths[path]; ok {
- return nil, fmt.Errorf("found module %q twice in the workspace", path)
- }
- paths[path] = modURI
- // If the module's path includes a major version, we expect it to have
- // a matching major version.
- _, majorVersion, _ := module.SplitPathVersion(path)
- if majorVersion == "" {
- majorVersion = "/v0"
- }
- majorVersion = strings.TrimLeft(majorVersion, "/.") // handle gopkg.in versions
- file.AddNewRequire(path, source.WorkspaceModuleVersion(majorVersion), false)
- if err := file.AddReplace(path, "", dirURI(modURI).Filename(), ""); err != nil {
- return nil, err
- }
- for _, exclude := range parsed.Exclude {
- excludes[exclude.Mod.Path] = append(excludes[exclude.Mod.Path], exclude.Mod.Version)
- }
- }
- if goVersion != "" {
- file.AddGoStmt(goVersion)
- }
- // Go back through all of the modules to handle any of their replace
- // statements.
- for _, modURI := range sortedModURIs {
- fh, err := fs.GetFile(ctx, modURI)
- if err != nil {
- return nil, err
- }
- content, err := fh.Read()
- if err != nil {
- return nil, err
- }
- parsed, err := modfile.Parse(fh.URI().Filename(), content, nil)
- if err != nil {
- return nil, err
- }
- // If any of the workspace modules have replace directives, they need
- // to be reflected in the workspace module.
- for _, rep := range parsed.Replace {
- // Don't replace any modules that are in our workspace--we should
- // always use the version in the workspace.
- if _, ok := paths[rep.Old.Path]; ok {
- continue
- }
- newPath := rep.New.Path
- newVersion := rep.New.Version
- // If a replace points to a module in the workspace, make sure we
- // direct it to version of the module in the workspace.
- if m, ok := paths[rep.New.Path]; ok {
- newPath = dirURI(m).Filename()
- newVersion = ""
- } else if rep.New.Version == "" && !filepath.IsAbs(rep.New.Path) {
- // Make any relative paths absolute.
- newPath = filepath.Join(dirURI(modURI).Filename(), rep.New.Path)
- }
- if err := file.AddReplace(rep.Old.Path, rep.Old.Version, newPath, newVersion); err != nil {
- return nil, err
- }
- }
- }
- for path, versions := range excludes {
- for _, version := range versions {
- file.AddExclude(path, version)
- }
- }
- file.SortBlocks()
- return file, nil
-}
-
-func buildWorkspaceSumFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) ([]byte, error) {
- allSums := map[module.Version][]string{}
- for modURI := range modFiles {
- // TODO(rfindley): factor out this pattern into a uripath package.
- sumURI := span.URIFromPath(filepath.Join(filepath.Dir(modURI.Filename()), "go.sum"))
- fh, err := fs.GetFile(ctx, sumURI)
- if err != nil {
- continue
- }
- data, err := fh.Read()
- if os.IsNotExist(err) {
- continue
- }
- if err != nil {
- return nil, errors.Errorf("reading go sum: %w", err)
- }
- if err := readGoSum(allSums, sumURI.Filename(), data); err != nil {
- return nil, err
- }
- }
- // This logic to write go.sum is copied (with minor modifications) from
- // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=631;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0
- var mods []module.Version
- for m := range allSums {
- mods = append(mods, m)
- }
- module.Sort(mods)
-
- var buf bytes.Buffer
- for _, m := range mods {
- list := allSums[m]
- sort.Strings(list)
- // Note (rfindley): here we add all sum lines without verification, because
- // the assumption is that if they come from a go.sum file, they are
- // trusted.
- for _, h := range list {
- fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
- }
- }
- return buf.Bytes(), nil
-}
-
-// readGoSum is copied (with minor modifications) from
-// https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=398;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0
-func readGoSum(dst map[module.Version][]string, file string, data []byte) error {
- lineno := 0
- for len(data) > 0 {
- var line []byte
- lineno++
- i := bytes.IndexByte(data, '\n')
- if i < 0 {
- line, data = data, nil
- } else {
- line, data = data[:i], data[i+1:]
- }
- f := strings.Fields(string(line))
- if len(f) == 0 {
- // blank line; skip it
- continue
- }
- if len(f) != 3 {
- return fmt.Errorf("malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f))
- }
- mod := module.Version{Path: f[0], Version: f[1]}
- dst[mod] = append(dst[mod], f[2])
- }
- return nil
-}
diff --git a/internal/lsp/cache/symbols.go b/internal/lsp/cache/symbols.go
deleted file mode 100644
index 831017246..000000000
--- a/internal/lsp/cache/symbols.go
+++ /dev/null
@@ -1,210 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/memoize"
- "golang.org/x/tools/internal/span"
-)
-
-type symbolHandle struct {
- handle *memoize.Handle
-
- fh source.FileHandle
-
- // key is the hashed key for the package.
- key symbolHandleKey
-}
-
-// symbolData contains the data produced by extracting symbols from a file.
-type symbolData struct {
- symbols []source.Symbol
- err error
-}
-
-type symbolHandleKey string
-
-func (s *snapshot) buildSymbolHandle(ctx context.Context, fh source.FileHandle) *symbolHandle {
- if h := s.getSymbolHandle(fh.URI()); h != nil {
- return h
- }
- key := symbolHandleKey(fh.FileIdentity().Hash)
- h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
- snapshot := arg.(*snapshot)
- data := &symbolData{}
- data.symbols, data.err = symbolize(ctx, snapshot, fh)
- return data
- }, nil)
-
- sh := &symbolHandle{
- handle: h,
- fh: fh,
- key: key,
- }
- return s.addSymbolHandle(sh)
-}
-
-// symbolize extracts symbols from a file. It does not parse the file through the cache.
-func symbolize(ctx context.Context, snapshot *snapshot, fh source.FileHandle) ([]source.Symbol, error) {
- var w symbolWalker
- fset := token.NewFileSet() // don't use snapshot.FileSet, as that would needlessly leak memory.
- data := parseGo(ctx, fset, fh, source.ParseFull)
- if data.parsed != nil && data.parsed.File != nil {
- w.curFile = data.parsed
- w.curURI = protocol.URIFromSpanURI(data.parsed.URI)
- w.fileDecls(data.parsed.File.Decls)
- }
- return w.symbols, w.firstError
-}
-
-type symbolWalker struct {
- curFile *source.ParsedGoFile
- curURI protocol.DocumentURI
- symbols []source.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 := fileRange(w.curFile, node.Pos(), node.End())
- if err != nil {
- w.error(err)
- return
- }
- sym := source.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 fileRange(pgf *source.ParsedGoFile, start, end token.Pos) (protocol.Range, error) {
- s, err := span.FileSpan(pgf.Tok, pgf.Mapper.Converter, start, end)
- if err != nil {
- return protocol.Range{}, nil
- }
- return pgf.Mapper.Range(s)
-}
-
-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 = 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
-}
-
-func unpackRecv(rtyp ast.Expr) *ast.Ident {
- // Extract the receiver identifier. Lifted from go/types/resolver.go
-L:
- for {
- switch t := rtyp.(type) {
- case *ast.ParenExpr:
- rtyp = t.X
- case *ast.StarExpr:
- rtyp = t.X
- default:
- break L
- }
- }
- if name, _ := rtyp.(*ast.Ident); name != nil {
- return name
- }
- return nil
-}
-
-// 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/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
deleted file mode 100644
index b34807c6e..000000000
--- a/internal/lsp/cache/view.go
+++ /dev/null
@@ -1,1076 +0,0 @@
-// 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 implements the caching layer for gopls.
-package cache
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
- "reflect"
- "regexp"
- "sort"
- "strings"
- "sync"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/mod/semver"
- exec "golang.org/x/sys/execabs"
- "golang.org/x/tools/go/packages"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/gocommand"
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/xcontext"
- errors "golang.org/x/xerrors"
-)
-
-type View struct {
- session *Session
- id string
-
- optionsMu sync.Mutex
- options *source.Options
-
- // mu protects most mutable state of the view.
- mu sync.Mutex
-
- // baseCtx is the context handed to NewView. This is the parent of all
- // background contexts created for this view.
- baseCtx context.Context
-
- // cancel is called when all action being performed by the current view
- // should be stopped.
- cancel context.CancelFunc
-
- // name is the user visible name of this view.
- name string
-
- // folder is the folder with which this view was constructed.
- folder span.URI
-
- importsState *importsState
-
- // moduleUpgrades tracks known upgrades for module paths.
- moduleUpgrades map[string]string
-
- // keep track of files by uri and by basename, a single file may be mapped
- // to multiple uris, and the same basename may map to multiple files
- filesByURI map[span.URI]*fileBase
- filesByBase map[string][]*fileBase
-
- // initCancelFirstAttempt can be used to terminate the view's first
- // attempt at initialization.
- initCancelFirstAttempt context.CancelFunc
-
- snapshotMu sync.Mutex
- snapshot *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.
- initializationSema chan struct{}
-
- // rootURI is the rootURI directory of this view. If we are in GOPATH mode, this
- // is just the folder. If we are in module mode, this is the module rootURI.
- rootURI span.URI
-
- // workspaceInformation tracks various details about this view's
- // environment variables, go version, and use of modules.
- workspaceInformation
-}
-
-type workspaceInformation struct {
- // The Go version in use: X in Go 1.X.
- goversion int
-
- // hasGopackagesDriver is true if the user has a value set for the
- // GOPACKAGESDRIVER environment variable or a gopackagesdriver binary on
- // their machine.
- hasGopackagesDriver bool
-
- // `go env` variables that need to be tracked by gopls.
- environmentVariables
-
- // userGo111Module is the user's value of GO111MODULE.
- userGo111Module go111module
-
- // The value of GO111MODULE we want to run with.
- effectiveGo111Module string
-
- // goEnv is the `go env` output collected when a view is created.
- // It includes the values of the environment variables above.
- goEnv map[string]string
-}
-
-type go111module int
-
-const (
- off = go111module(iota)
- auto
- on
-)
-
-type environmentVariables struct {
- gocache, gopath, goroot, goprivate, gomodcache, go111module string
-}
-
-type workspaceMode int
-
-const (
- moduleMode workspaceMode = 1 << iota
-
- // tempModfile indicates whether or not the -modfile flag should be used.
- tempModfile
-)
-
-// fileBase holds the common functionality for all files.
-// It is intended to be embedded in the file implementations
-type fileBase struct {
- uris []span.URI
- fname string
-
- view *View
-}
-
-func (f *fileBase) URI() span.URI {
- return f.uris[0]
-}
-
-func (f *fileBase) filename() string {
- return f.fname
-}
-
-func (f *fileBase) addURI(uri span.URI) int {
- f.uris = append(f.uris, uri)
- return len(f.uris)
-}
-
-func (v *View) ID() string { return v.id }
-
-// tempModFile creates a temporary go.mod file based on the contents of the
-// given go.mod file. It is the caller's responsibility to clean up the files
-// when they are done using them.
-func tempModFile(modFh source.FileHandle, gosum []byte) (tmpURI span.URI, cleanup func(), err error) {
- filenameHash := hashContents([]byte(modFh.URI().Filename()))
- tmpMod, err := ioutil.TempFile("", fmt.Sprintf("go.%s.*.mod", filenameHash))
- if err != nil {
- return "", nil, err
- }
- defer tmpMod.Close()
-
- tmpURI = span.URIFromPath(tmpMod.Name())
- tmpSumName := sumFilename(tmpURI)
-
- content, err := modFh.Read()
- if err != nil {
- return "", nil, err
- }
-
- if _, err := tmpMod.Write(content); err != nil {
- return "", nil, err
- }
-
- cleanup = func() {
- _ = os.Remove(tmpSumName)
- _ = os.Remove(tmpURI.Filename())
- }
-
- // Be careful to clean up if we return an error from this function.
- defer func() {
- if err != nil {
- cleanup()
- cleanup = nil
- }
- }()
-
- // Create an analogous go.sum, if one exists.
- if gosum != nil {
- if err := ioutil.WriteFile(tmpSumName, gosum, 0655); err != nil {
- return "", cleanup, err
- }
- }
-
- return tmpURI, cleanup, nil
-}
-
-// Name returns the user visible name of this view.
-func (v *View) Name() string {
- return v.name
-}
-
-// Folder returns the folder at the base of this view.
-func (v *View) Folder() span.URI {
- return v.folder
-}
-
-func (v *View) Options() *source.Options {
- v.optionsMu.Lock()
- defer v.optionsMu.Unlock()
- return v.options
-}
-
-func (v *View) FileKind(fh source.FileHandle) source.FileKind {
- if o, ok := fh.(source.Overlay); ok {
- if o.Kind() != source.UnknownKind {
- return o.Kind()
- }
- }
- fext := filepath.Ext(fh.URI().Filename())
- switch fext {
- case ".go":
- return source.Go
- case ".mod":
- return source.Mod
- case ".sum":
- return source.Sum
- case ".work":
- return source.Work
- }
- exts := v.Options().TemplateExtensions
- for _, ext := range exts {
- if fext == ext || fext == "."+ext {
- return source.Tmpl
- }
- }
- // and now what? This should never happen, but it does for cgo before go1.15
- return source.Go
-}
-
-func minorOptionsChange(a, b *source.Options) bool {
- // Check if any of the settings that modify our understanding of files have been changed
- if !reflect.DeepEqual(a.Env, b.Env) {
- return false
- }
- if !reflect.DeepEqual(a.DirectoryFilters, b.DirectoryFilters) {
- return false
- }
- if a.MemoryMode != b.MemoryMode {
- return false
- }
- aBuildFlags := make([]string, len(a.BuildFlags))
- bBuildFlags := make([]string, len(b.BuildFlags))
- copy(aBuildFlags, a.BuildFlags)
- copy(bBuildFlags, b.BuildFlags)
- sort.Strings(aBuildFlags)
- sort.Strings(bBuildFlags)
- // the rest of the options are benign
- return reflect.DeepEqual(aBuildFlags, bBuildFlags)
-}
-
-func (v *View) SetOptions(ctx context.Context, options *source.Options) (source.View, error) {
- // no need to rebuild the view if the options were not materially changed
- v.optionsMu.Lock()
- if minorOptionsChange(v.options, options) {
- v.options = options
- v.optionsMu.Unlock()
- return v, nil
- }
- v.optionsMu.Unlock()
- newView, err := v.session.updateView(ctx, v, options)
- return newView, err
-}
-
-func (v *View) Rebuild(ctx context.Context) (source.Snapshot, func(), error) {
- newView, err := v.session.updateView(ctx, v, v.Options())
- if err != nil {
- return nil, func() {}, err
- }
- snapshot, release := newView.Snapshot(ctx)
- return snapshot, release, nil
-}
-
-func (s *snapshot) WriteEnv(ctx context.Context, w io.Writer) error {
- s.view.optionsMu.Lock()
- env := s.view.options.EnvSlice()
- buildFlags := append([]string{}, s.view.options.BuildFlags...)
- s.view.optionsMu.Unlock()
-
- fullEnv := make(map[string]string)
- for k, v := range s.view.goEnv {
- fullEnv[k] = v
- }
- for _, v := range env {
- s := strings.SplitN(v, "=", 2)
- if len(s) != 2 {
- continue
- }
- if _, ok := fullEnv[s[0]]; ok {
- fullEnv[s[0]] = s[1]
- }
- }
- goVersion, err := s.view.session.gocmdRunner.Run(ctx, gocommand.Invocation{
- Verb: "version",
- Env: env,
- WorkingDir: s.view.rootURI.Filename(),
- })
- if err != nil {
- return err
- }
- fmt.Fprintf(w, `go env for %v
-(root %s)
-(go version %s)
-(valid build configuration = %v)
-(build flags: %v)
-`,
- s.view.folder.Filename(),
- s.view.rootURI.Filename(),
- strings.TrimRight(goVersion.String(), "\n"),
- s.ValidBuildConfiguration(),
- buildFlags)
- for k, v := range fullEnv {
- fmt.Fprintf(w, "%s=%s\n", k, v)
- }
- return nil
-}
-
-func (s *snapshot) RunProcessEnvFunc(ctx context.Context, fn func(*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
-}
-
-func (s *snapshot) locateTemplateFiles(ctx context.Context) {
- if len(s.view.Options().TemplateExtensions) == 0 {
- return
- }
- suffixes := s.view.Options().TemplateExtensions
-
- // The workspace root may have been expanded to a module, but we should apply
- // directory filters based on the configured workspace folder.
- //
- // TODO(rfindley): we should be more principled about paths outside of the
- // workspace folder: do we even consider them? Do we support absolute
- // exclusions? Relative exclusions starting with ..?
- dir := s.workspace.root.Filename()
- relativeTo := s.view.folder.Filename()
-
- searched := 0
- // Change to WalkDir when we move up to 1.16
- err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- relpath := strings.TrimPrefix(path, relativeTo)
- excluded := pathExcludedByFilter(relpath, dir, s.view.gomodcache, s.view.options)
- if fileHasExtension(path, suffixes) && !excluded && !fi.IsDir() {
- k := span.URIFromPath(path)
- _, err := s.GetVersionedFile(ctx, k)
- if err != nil {
- return nil
- }
- }
- searched++
- if fileLimit > 0 && searched > fileLimit {
- return errExhausted
- }
- return nil
- })
- if err != nil {
- event.Error(ctx, "searching for template files failed", err)
- }
-}
-
-func (v *View) contains(uri span.URI) bool {
- inRoot := source.InDir(v.rootURI.Filename(), uri.Filename())
- inFolder := source.InDir(v.folder.Filename(), uri.Filename())
- if !inRoot && !inFolder {
- return false
- }
- // Filters are applied relative to the workspace folder.
- if inFolder {
- return !pathExcludedByFilter(strings.TrimPrefix(uri.Filename(), v.folder.Filename()), v.rootURI.Filename(), v.gomodcache, v.Options())
- }
- return true
-}
-
-func (v *View) mapFile(uri span.URI, f *fileBase) {
- v.filesByURI[uri] = f
- if f.addURI(uri) == 1 {
- basename := basename(f.filename())
- v.filesByBase[basename] = append(v.filesByBase[basename], f)
- }
-}
-
-func basename(filename string) string {
- return strings.ToLower(filepath.Base(filename))
-}
-
-func (v *View) relevantChange(c source.FileModification) bool {
- // If the file is known to the view, the change is relevant.
- if v.knownFile(c.URI) {
- return true
- }
- // The go.work/gopls.mod may not be "known" because we first access it
- // through the session. As a result, treat changes to the view's go.work or
- // gopls.mod file as always relevant, even if they are only on-disk
- // changes.
- // TODO(rstambler): Make sure the go.work/gopls.mod files are always known
- // to the view.
- for _, src := range []workspaceSource{goWorkWorkspace, goplsModWorkspace} {
- if c.URI == uriForSource(v.rootURI, src) {
- return true
- }
- }
- // If the file is not known to the view, and the change is only on-disk,
- // we should not invalidate the snapshot. This is necessary because Emacs
- // sends didChangeWatchedFiles events for temp files.
- if c.OnDisk && (c.Action == source.Change || c.Action == source.Delete) {
- return false
- }
- return v.contains(c.URI)
-}
-
-func (v *View) knownFile(uri span.URI) bool {
- v.mu.Lock()
- defer v.mu.Unlock()
-
- f, err := v.findFile(uri)
- return f != nil && err == nil
-}
-
-// getFile returns a file for the given URI.
-func (v *View) getFile(uri span.URI) *fileBase {
- v.mu.Lock()
- defer v.mu.Unlock()
-
- f, _ := v.findFile(uri)
- if f != nil {
- return f
- }
- f = &fileBase{
- view: v,
- fname: uri.Filename(),
- }
- v.mapFile(uri, f)
- return f
-}
-
-// findFile checks the cache for any file matching the given uri.
-//
-// An error is only returned for an irreparable failure, for example, if the
-// filename in question does not exist.
-func (v *View) findFile(uri span.URI) (*fileBase, error) {
- if f := v.filesByURI[uri]; f != nil {
- // a perfect match
- return f, nil
- }
- // no exact match stored, time to do some real work
- // check for any files with the same basename
- fname := uri.Filename()
- basename := basename(fname)
- if candidates := v.filesByBase[basename]; candidates != nil {
- pathStat, err := os.Stat(fname)
- if os.IsNotExist(err) {
- return nil, err
- }
- if err != nil {
- return nil, nil // the file may exist, return without an error
- }
- for _, c := range candidates {
- if cStat, err := os.Stat(c.filename()); err == nil {
- if os.SameFile(pathStat, cStat) {
- // same file, map it
- v.mapFile(uri, c)
- return c, nil
- }
- }
- }
- }
- // no file with a matching name was found, it wasn't in our cache
- return nil, nil
-}
-
-func (v *View) Shutdown(ctx context.Context) {
- v.session.removeView(ctx, v)
-}
-
-// TODO(rFindley): probably some of this should also be one in View.Shutdown
-// above?
-func (v *View) shutdown(ctx context.Context) {
- // Cancel the initial workspace load if it is still running.
- v.initCancelFirstAttempt()
-
- v.mu.Lock()
- if v.cancel != nil {
- v.cancel()
- v.cancel = nil
- }
- v.mu.Unlock()
- v.snapshotMu.Lock()
- if v.snapshot != nil {
- go v.snapshot.generation.Destroy("View.shutdown")
- v.snapshot = nil
- }
- v.snapshotMu.Unlock()
- v.importsState.destroy()
-}
-
-func (v *View) Session() *Session {
- return v.session
-}
-
-func (s *snapshot) IgnoredFile(uri span.URI) bool {
- filename := uri.Filename()
- var prefixes []string
- if len(s.workspace.getActiveModFiles()) == 0 {
- for _, entry := range filepath.SplitList(s.view.gopath) {
- prefixes = append(prefixes, filepath.Join(entry, "src"))
- }
- } else {
- prefixes = append(prefixes, s.view.gomodcache)
- for m := range s.workspace.getActiveModFiles() {
- prefixes = append(prefixes, dirURI(m).Filename())
- }
- }
- for _, prefix := range prefixes {
- if strings.HasPrefix(filename, prefix) {
- return checkIgnored(filename[len(prefix):])
- }
- }
- return false
-}
-
-// checkIgnored implements go list's exclusion rules. 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 {
- 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
-}
-
-func (v *View) Snapshot(ctx context.Context) (source.Snapshot, func()) {
- return v.getSnapshot()
-}
-
-func (v *View) getSnapshot() (*snapshot, func()) {
- v.snapshotMu.Lock()
- defer v.snapshotMu.Unlock()
- if v.snapshot == nil {
- panic("getSnapshot called after shutdown")
- }
- return v.snapshot, v.snapshot.generation.Acquire()
-}
-
-func (s *snapshot) initialize(ctx context.Context, firstAttempt bool) {
- select {
- case <-ctx.Done():
- return
- case s.view.initializationSema <- struct{}{}:
- }
-
- defer func() {
- <-s.view.initializationSema
- }()
-
- if s.initializeOnce == nil {
- return
- }
- s.initializeOnce.Do(func() {
- s.loadWorkspace(ctx, firstAttempt)
- s.collectAllKnownSubdirs(ctx)
- })
-}
-
-func (s *snapshot) loadWorkspace(ctx context.Context, firstAttempt bool) {
- defer func() {
- s.initializeOnce = nil
- if firstAttempt {
- close(s.view.initialWorkspaceLoad)
- }
- }()
-
- // If we have multiple modules, we need to load them by paths.
- var scopes []interface{}
- var modDiagnostics []*source.Diagnostic
- addError := func(uri span.URI, err error) {
- modDiagnostics = append(modDiagnostics, &source.Diagnostic{
- URI: uri,
- Severity: protocol.SeverityError,
- Source: source.ListError,
- Message: err.Error(),
- })
- }
- s.locateTemplateFiles(ctx)
- if len(s.workspace.getActiveModFiles()) > 0 {
- for modURI := range s.workspace.getActiveModFiles() {
- fh, err := s.GetFile(ctx, modURI)
- if err != nil {
- addError(modURI, err)
- continue
- }
- parsed, err := s.ParseMod(ctx, fh)
- if err != nil {
- addError(modURI, err)
- continue
- }
- if parsed.File == nil || parsed.File.Module == nil {
- addError(modURI, fmt.Errorf("no module path for %s", modURI))
- continue
- }
- path := parsed.File.Module.Mod.Path
- scopes = append(scopes, moduleLoadScope(path))
- }
- } else {
- scopes = append(scopes, viewLoadScope("LOAD_VIEW"))
- }
-
- // If we're loading anything, ensure we also load builtin.
- // TODO(rstambler): explain the rationale for this.
- if len(scopes) > 0 {
- scopes = append(scopes, PackagePath("builtin"))
- }
- err := s.load(ctx, firstAttempt, scopes...)
-
- // If the context is canceled on the first attempt, loading has failed
- // because the go command has timed out--that should be a critical error.
- if err != nil && !firstAttempt && ctx.Err() != nil {
- return
- }
-
- var criticalErr *source.CriticalError
- switch {
- case err != nil && ctx.Err() != nil:
- event.Error(ctx, fmt.Sprintf("initial workspace load: %v", err), err)
- criticalErr = &source.CriticalError{
- MainError: err,
- }
- case err != nil:
- event.Error(ctx, "initial workspace load failed", err)
- extractedDiags, _ := s.extractGoCommandErrors(ctx, err.Error())
- criticalErr = &source.CriticalError{
- MainError: err,
- DiagList: append(modDiagnostics, extractedDiags...),
- }
- case len(modDiagnostics) == 1:
- criticalErr = &source.CriticalError{
- MainError: fmt.Errorf(modDiagnostics[0].Message),
- DiagList: modDiagnostics,
- }
- case len(modDiagnostics) > 1:
- criticalErr = &source.CriticalError{
- MainError: fmt.Errorf("error loading module names"),
- DiagList: modDiagnostics,
- }
- }
-
- // Lock the snapshot when setting the initialized error.
- s.mu.Lock()
- defer s.mu.Unlock()
- s.initializedErr = criticalErr
-}
-
-// invalidateContent invalidates the content of a Go file,
-// including any position and type information that depends on it.
-//
-// invalidateContent returns a non-nil snapshot for the new content, along with
-// a callback which the caller must invoke to release that snapshot.
-func (v *View) invalidateContent(ctx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (*snapshot, func()) {
- // 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()
-
- if v.snapshot == nil {
- panic("invalidateContent called after shutdown")
- }
-
- // Cancel all still-running previous requests, since they would be
- // operating on stale data.
- v.snapshot.cancel()
-
- // Do not clone a snapshot until its view has finished initializing.
- v.snapshot.AwaitInitialized(ctx)
-
- oldSnapshot := v.snapshot
-
- v.snapshot = oldSnapshot.clone(ctx, v.baseCtx, changes, forceReloadMetadata)
- go oldSnapshot.generation.Destroy("View.invalidateContent")
-
- return v.snapshot, v.snapshot.generation.Acquire()
-}
-
-func (s *Session) getWorkspaceInformation(ctx context.Context, folder span.URI, options *source.Options) (*workspaceInformation, error) {
- if err := checkPathCase(folder.Filename()); err != nil {
- return nil, errors.Errorf("invalid workspace folder path: %w; check that the casing of the configured workspace folder path agrees with the casing reported by the operating system", err)
- }
- var err error
- inv := gocommand.Invocation{
- WorkingDir: folder.Filename(),
- Env: options.EnvSlice(),
- }
- goversion, err := gocommand.GoVersion(ctx, inv, s.gocmdRunner)
- if err != nil {
- return nil, err
- }
-
- go111module := os.Getenv("GO111MODULE")
- if v, ok := options.Env["GO111MODULE"]; ok {
- go111module = v
- }
- // Make sure to get the `go env` before continuing with initialization.
- envVars, env, err := s.getGoEnv(ctx, folder.Filename(), goversion, go111module, options.EnvSlice())
- if err != nil {
- return nil, err
- }
- // If using 1.16, change the default back to auto. The primary effect of
- // GO111MODULE=on is to break GOPATH, which we aren't too interested in.
- if goversion >= 16 && go111module == "" {
- go111module = "auto"
- }
- // The value of GOPACKAGESDRIVER is not returned through the go command.
- gopackagesdriver := os.Getenv("GOPACKAGESDRIVER")
- for _, s := range env {
- split := strings.SplitN(s, "=", 2)
- if split[0] == "GOPACKAGESDRIVER" {
- gopackagesdriver = split[1]
- }
- }
-
- // A user may also have a gopackagesdriver binary on their machine, which
- // works the same way as setting GOPACKAGESDRIVER.
- tool, _ := exec.LookPath("gopackagesdriver")
- hasGopackagesDriver := gopackagesdriver != "off" && (gopackagesdriver != "" || tool != "")
-
- return &workspaceInformation{
- hasGopackagesDriver: hasGopackagesDriver,
- effectiveGo111Module: go111module,
- userGo111Module: go111moduleForVersion(go111module, goversion),
- goversion: goversion,
- environmentVariables: envVars,
- goEnv: env,
- }, nil
-}
-
-func go111moduleForVersion(go111module string, goversion int) go111module {
- // Off by default until Go 1.12.
- if go111module == "off" || (goversion < 12 && go111module == "") {
- return off
- }
- // On by default as of Go 1.16.
- if go111module == "on" || (goversion >= 16 && go111module == "") {
- return on
- }
- return auto
-}
-
-// findWorkspaceRoot searches for the best workspace root according to the
-// following heuristics:
-// - First, look for a parent directory containing a gopls.mod file
-// (experimental only).
-// - Then, a parent directory containing a go.mod file.
-// - Then, a child directory containing a go.mod file, if there is exactly
-// one (non-experimental only).
-// Otherwise, it returns folder.
-// TODO (rFindley): move this to workspace.go
-// TODO (rFindley): simplify this once workspace modules are enabled by default.
-func findWorkspaceRoot(ctx context.Context, folder span.URI, fs source.FileSource, excludePath func(string) bool, experimental bool) (span.URI, error) {
- patterns := []string{"go.work", "go.mod"}
- if experimental {
- patterns = []string{"go.work", "gopls.mod", "go.mod"}
- }
- for _, basename := range patterns {
- dir, err := findRootPattern(ctx, folder, basename, fs)
- if err != nil {
- return "", errors.Errorf("finding %s: %w", basename, err)
- }
- if dir != "" {
- return dir, nil
- }
- }
-
- // The experimental workspace can handle nested modules at this point...
- if experimental {
- return folder, nil
- }
-
- // ...else we should check if there's exactly one nested module.
- all, err := findModules(folder, excludePath, 2)
- if err == errExhausted {
- // Fall-back behavior: if we don't find any modules after searching 10000
- // files, assume there are none.
- event.Log(ctx, fmt.Sprintf("stopped searching for modules after %d files", fileLimit))
- return folder, nil
- }
- if err != nil {
- return "", err
- }
- if len(all) == 1 {
- // range to access first element.
- for uri := range all {
- return dirURI(uri), nil
- }
- }
- return folder, nil
-}
-
-func findRootPattern(ctx context.Context, folder span.URI, basename string, fs source.FileSource) (span.URI, error) {
- dir := folder.Filename()
- for dir != "" {
- target := filepath.Join(dir, basename)
- exists, err := fileExists(ctx, span.URIFromPath(target), fs)
- if err != nil {
- return "", err
- }
- if exists {
- return span.URIFromPath(dir), 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
-}
-
-// OS-specific path case check, for case-insensitive filesystems.
-var checkPathCase = defaultCheckPathCase
-
-func defaultCheckPathCase(path string) error {
- return nil
-}
-
-func validBuildConfiguration(folder span.URI, ws *workspaceInformation, modFiles map[span.URI]struct{}) bool {
- // Since we only really understand the `go` command, if the user has a
- // different GOPACKAGESDRIVER, assume that their configuration is valid.
- if ws.hasGopackagesDriver {
- return true
- }
- // Check if the user is working within a module or if we have found
- // multiple modules in the workspace.
- if len(modFiles) > 0 {
- return true
- }
- // The user may have a multiple directories in their GOPATH.
- // Check if the workspace is within any of them.
- for _, gp := range filepath.SplitList(ws.gopath) {
- if source.InDir(filepath.Join(gp, "src"), folder.Filename()) {
- return true
- }
- }
- return false
-}
-
-// getGoEnv gets the view's various GO* values.
-func (s *Session) getGoEnv(ctx context.Context, folder string, goversion int, go111module string, configEnv []string) (environmentVariables, map[string]string, error) {
- envVars := environmentVariables{}
- vars := map[string]*string{
- "GOCACHE": &envVars.gocache,
- "GOPATH": &envVars.gopath,
- "GOROOT": &envVars.goroot,
- "GOPRIVATE": &envVars.goprivate,
- "GOMODCACHE": &envVars.gomodcache,
- "GO111MODULE": &envVars.go111module,
- }
-
- // We can save ~200 ms by requesting only the variables we care about.
- args := append([]string{"-json"}, imports.RequiredGoEnvVars...)
- for k := range vars {
- args = append(args, k)
- }
- args = append(args, "GOWORK")
-
- inv := gocommand.Invocation{
- Verb: "env",
- Args: args,
- Env: configEnv,
- WorkingDir: folder,
- }
- // Don't go through runGoCommand, as we don't need a temporary -modfile to
- // run `go env`.
- stdout, err := s.gocmdRunner.Run(ctx, inv)
- if err != nil {
- return environmentVariables{}, nil, err
- }
- env := make(map[string]string)
- if err := json.Unmarshal(stdout.Bytes(), &env); err != nil {
- return environmentVariables{}, nil, err
- }
-
- for key, ptr := range vars {
- *ptr = env[key]
- }
-
- // Old versions of Go don't have GOMODCACHE, so emulate it.
- if envVars.gomodcache == "" && envVars.gopath != "" {
- envVars.gomodcache = filepath.Join(filepath.SplitList(envVars.gopath)[0], "pkg/mod")
- }
- // GO111MODULE does not appear in `go env` output until Go 1.13.
- if goversion < 13 {
- envVars.go111module = go111module
- }
- return envVars, env, err
-}
-
-func (v *View) IsGoPrivatePath(target string) bool {
- return globsMatchPath(v.goprivate, target)
-}
-
-func (v *View) ModuleUpgrades() map[string]string {
- v.mu.Lock()
- defer v.mu.Unlock()
-
- upgrades := map[string]string{}
- for mod, ver := range v.moduleUpgrades {
- upgrades[mod] = ver
- }
- return upgrades
-}
-
-func (v *View) RegisterModuleUpgrades(upgrades map[string]string) {
- v.mu.Lock()
- defer v.mu.Unlock()
-
- for mod, ver := range upgrades {
- v.moduleUpgrades[mod] = ver
- }
-}
-
-// 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(rstambler): Consolidate modURI and modContent back into a FileHandle
-// after we have a version of the workspace go.mod file on disk. Getting a
-// FileHandle from the cache for temporary files is problematic, since we
-// cannot delete it.
-func (s *snapshot) vendorEnabled(ctx context.Context, modURI span.URI, modContent []byte) (bool, error) {
- if s.workspaceMode()&moduleMode == 0 {
- return false, nil
- }
- matches := modFlagRegexp.FindStringSubmatch(s.view.goEnv["GOFLAGS"])
- var modFlag string
- if len(matches) != 0 {
- modFlag = matches[1]
- }
- if modFlag != "" {
- // Don't override an explicit '-mod=vendor' argument.
- // We do want to override '-mod=readonly': it would break various module code lenses,
- // and on 1.16 we know -modfile is available, so we won't mess with go.mod anyway.
- return modFlag == "vendor", nil
- }
-
- modFile, err := modfile.Parse(modURI.Filename(), modContent, nil)
- if err != nil {
- return false, err
- }
- if fi, err := os.Stat(filepath.Join(s.view.rootURI.Filename(), "vendor")); err != nil || !fi.IsDir() {
- return false, nil
- }
- vendorEnabled := modFile.Go != nil && modFile.Go.Version != "" && semver.Compare("v"+modFile.Go.Version, "v1.14") >= 0
- return vendorEnabled, nil
-}
-
-func (v *View) allFilesExcluded(pkg *packages.Package) bool {
- opts := v.Options()
- folder := filepath.ToSlash(v.folder.Filename())
- for _, f := range pkg.GoFiles {
- f = filepath.ToSlash(f)
- if !strings.HasPrefix(f, folder) {
- return false
- }
- if !pathExcludedByFilter(strings.TrimPrefix(f, folder), v.rootURI.Filename(), v.gomodcache, opts) {
- return false
- }
- }
- return true
-}
-
-func pathExcludedByFilterFunc(root, gomodcache string, opts *source.Options) func(string) bool {
- return func(path string) bool {
- return pathExcludedByFilter(path, root, gomodcache, opts)
- }
-}
-
-// pathExcludedByFilter reports whether the path (relative to the workspace
-// folder) should be excluded by the configured directory filters.
-//
-// TODO(rfindley): passing root and gomodcache here makes it confusing whether
-// path should be absolute or relative, and has already caused at least one
-// bug.
-func pathExcludedByFilter(path, root, gomodcache string, opts *source.Options) bool {
- path = strings.TrimPrefix(filepath.ToSlash(path), "/")
- gomodcache = strings.TrimPrefix(filepath.ToSlash(strings.TrimPrefix(gomodcache, root)), "/")
- filters := opts.DirectoryFilters
- if gomodcache != "" {
- filters = append(filters, "-"+gomodcache)
- }
- return source.FiltersDisallow(path, filters)
-}
diff --git a/internal/lsp/cache/view_test.go b/internal/lsp/cache/view_test.go
deleted file mode 100644
index d76dcda8e..000000000
--- a/internal/lsp/cache/view_test.go
+++ /dev/null
@@ -1,218 +0,0 @@
-// 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"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "golang.org/x/tools/internal/lsp/fake"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
-)
-
-func TestCaseInsensitiveFilesystem(t *testing.T) {
- base, err := ioutil.TempDir("", t.Name())
- if err != nil {
- t.Fatal(err)
- }
-
- 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 := ioutil.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 := checkPathCase(tt.path)
- if err != nil != tt.err {
- t.Errorf("checkPathCase(%q) = %v, wanted error: %v", tt.path, err, tt.err)
- }
- }
-}
-
-func TestFindWorkspaceRoot(t *testing.T) {
- workspace := `
--- a/go.mod --
-module a
--- a/x/x.go
-package x
--- a/x/y/y.go
-package x
--- b/go.mod --
-module b
--- b/c/go.mod --
-module bc
--- d/gopls.mod --
-module d-goplsworkspace
--- d/e/go.mod --
-module de
--- f/g/go.mod --
-module fg
-`
- dir, err := fake.Tempdir(fake.UnpackTxt(workspace))
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
-
- tests := []struct {
- folder, want string
- experimental bool
- }{
- {"", "", false}, // no module at root, and more than one nested module
- {"a", "a", false},
- {"a/x", "a", false},
- {"a/x/y", "a", false},
- {"b/c", "b/c", false},
- {"d", "d/e", false},
- {"d", "d", true},
- {"d/e", "d/e", false},
- {"d/e", "d", true},
- {"f", "f/g", false},
- {"f", "f", true},
- }
-
- for _, test := range tests {
- ctx := context.Background()
- rel := fake.RelativeTo(dir)
- folderURI := span.URIFromPath(rel.AbsPath(test.folder))
- excludeNothing := func(string) bool { return false }
- got, err := findWorkspaceRoot(ctx, folderURI, &osFileSource{}, excludeNothing, test.experimental)
- if err != nil {
- t.Fatal(err)
- }
- if gotf, wantf := filepath.Clean(got.Filename()), rel.AbsPath(test.want); gotf != wantf {
- t.Errorf("findWorkspaceRoot(%q, %t) = %q, want %q", test.folder, test.experimental, gotf, wantf)
- }
- }
-}
-
-func TestInVendor(t *testing.T) {
- for _, tt := range []struct {
- path string
- inVendor bool
- }{
- {
- path: "foo/vendor/x.go",
- inVendor: false,
- },
- {
- path: "foo/vendor/x/x.go",
- inVendor: true,
- },
- {
- path: "foo/x.go",
- inVendor: false,
- },
- } {
- if got := inVendor(span.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 {
- opts := &source.Options{}
- opts.DirectoryFilters = tt.filters
- for _, inc := range tt.included {
- if pathExcludedByFilter(inc, "root", "root/gopath/pkg/mod", opts) {
- t.Errorf("filters %q excluded %v, wanted included", tt.filters, inc)
- }
- }
- for _, exc := range tt.excluded {
- if !pathExcludedByFilter(exc, "root", "root/gopath/pkg/mod", opts) {
- 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)
- }
- }
- }
-}
diff --git a/internal/lsp/cache/workspace.go b/internal/lsp/cache/workspace.go
deleted file mode 100644
index 5d62d6691..000000000
--- a/internal/lsp/cache/workspace.go
+++ /dev/null
@@ -1,599 +0,0 @@
-// 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"
- "os"
- "path/filepath"
- "sort"
- "strings"
- "sync"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/xcontext"
- errors "golang.org/x/xerrors"
-)
-
-// workspaceSource reports how the set of active modules has been derived.
-type workspaceSource int
-
-const (
- legacyWorkspace = iota // non-module or single module mode
- goplsModWorkspace // modules provided by a gopls.mod file
- goWorkWorkspace // modules provided by a go.work file
- fileSystemWorkspace // modules scanned from the filesystem
-)
-
-func (s workspaceSource) String() string {
- switch s {
- case legacyWorkspace:
- return "legacy"
- case goplsModWorkspace:
- return "gopls.mod"
- case goWorkWorkspace:
- return "go.work"
- case fileSystemWorkspace:
- return "file system"
- default:
- return "!(unknown module source)"
- }
-}
-
-// workspace tracks go.mod files in the workspace, along with the
-// gopls.mod file, to provide support for multi-module workspaces.
-//
-// Specifically, it provides:
-// - the set of modules contained within in the workspace root considered to
-// be 'active'
-// - the workspace modfile, to be used for the go command `-modfile` flag
-// - the set of workspace directories
-//
-// This type is immutable (or rather, idempotent), so that it may be shared
-// across multiple snapshots.
-type workspace struct {
- root span.URI
- excludePath func(string) bool
- moduleSource workspaceSource
-
- // activeModFiles holds the active go.mod files.
- activeModFiles map[span.URI]struct{}
-
- // knownModFiles holds the set of all go.mod files in the workspace.
- // In all modes except for legacy, this is equivalent to modFiles.
- knownModFiles map[span.URI]struct{}
-
- // workFile, if nonEmpty, is the go.work file for the workspace.
- workFile span.URI
-
- // The workspace module is lazily re-built once after being invalidated.
- // buildMu+built guards this reconstruction.
- //
- // file and wsDirs may be non-nil even if built == false, if they were copied
- // from the previous workspace module version. In this case, they will be
- // preserved if building fails.
- buildMu sync.Mutex
- built bool
- buildErr error
- mod *modfile.File
- sum []byte
- wsDirs map[span.URI]struct{}
-}
-
-// newWorkspace creates a new workspace at the given root directory,
-// determining its module source based on the presence of a gopls.mod or
-// go.work file, and the go111moduleOff and useWsModule settings.
-//
-// If useWsModule is set, the workspace may use a synthetic mod file replacing
-// all modules in the root.
-//
-// If there is no active workspace file (a gopls.mod or go.work), newWorkspace
-// scans the filesystem to find modules.
-func newWorkspace(ctx context.Context, root span.URI, fs source.FileSource, excludePath func(string) bool, go111moduleOff bool, useWsModule bool) (*workspace, error) {
- ws := &workspace{
- root: root,
- excludePath: excludePath,
- }
-
- // The user may have a gopls.mod or go.work file that defines their
- // workspace.
- if err := loadExplicitWorkspaceFile(ctx, ws, fs); err == nil {
- return ws, nil
- }
-
- // Otherwise, in all other modes, search for all of the go.mod files in the
- // workspace.
- knownModFiles, err := findModules(root, excludePath, 0)
- if err != nil {
- return nil, err
- }
- ws.knownModFiles = knownModFiles
-
- switch {
- case go111moduleOff:
- ws.moduleSource = legacyWorkspace
- case useWsModule:
- ws.activeModFiles = knownModFiles
- ws.moduleSource = fileSystemWorkspace
- default:
- ws.moduleSource = legacyWorkspace
- activeModFiles, err := getLegacyModules(ctx, root, fs)
- if err != nil {
- return nil, err
- }
- ws.activeModFiles = activeModFiles
- }
- return ws, nil
-}
-
-// loadExplicitWorkspaceFile loads workspace information from go.work or
-// gopls.mod files, setting the active modules, mod file, and module source
-// accordingly.
-func loadExplicitWorkspaceFile(ctx context.Context, ws *workspace, fs source.FileSource) error {
- for _, src := range []workspaceSource{goWorkWorkspace, goplsModWorkspace} {
- fh, err := fs.GetFile(ctx, uriForSource(ws.root, src))
- if err != nil {
- return err
- }
- contents, err := fh.Read()
- if err != nil {
- continue
- }
- var file *modfile.File
- var activeModFiles map[span.URI]struct{}
- switch src {
- case goWorkWorkspace:
- file, activeModFiles, err = parseGoWork(ctx, ws.root, fh.URI(), contents, fs)
- ws.workFile = fh.URI()
- case goplsModWorkspace:
- file, activeModFiles, err = parseGoplsMod(ws.root, fh.URI(), contents)
- }
- if err != nil {
- ws.buildMu.Lock()
- ws.built = true
- ws.buildErr = err
- ws.buildMu.Unlock()
- }
- ws.mod = file
- ws.activeModFiles = activeModFiles
- ws.moduleSource = src
- return nil
- }
- return noHardcodedWorkspace
-}
-
-var noHardcodedWorkspace = errors.New("no hardcoded workspace")
-
-func (w *workspace) getKnownModFiles() map[span.URI]struct{} {
- return w.knownModFiles
-}
-
-func (w *workspace) getActiveModFiles() map[span.URI]struct{} {
- return w.activeModFiles
-}
-
-// modFile gets the workspace modfile associated with this workspace,
-// computing it if it doesn't exist.
-//
-// A fileSource must be passed in to solve a chicken-egg problem: it is not
-// correct to pass in the snapshot file source to newWorkspace when
-// invalidating, because at the time these are called the snapshot is locked.
-// So we must pass it in later on when actually using the modFile.
-func (w *workspace) modFile(ctx context.Context, fs source.FileSource) (*modfile.File, error) {
- w.build(ctx, fs)
- return w.mod, w.buildErr
-}
-
-func (w *workspace) sumFile(ctx context.Context, fs source.FileSource) ([]byte, error) {
- w.build(ctx, fs)
- return w.sum, w.buildErr
-}
-
-func (w *workspace) build(ctx context.Context, fs source.FileSource) {
- w.buildMu.Lock()
- defer w.buildMu.Unlock()
-
- if w.built {
- return
- }
- // Building should never be cancelled. Since the workspace module is shared
- // across multiple snapshots, doing so would put us in a bad state, and it
- // would not be obvious to the user how to recover.
- ctx = xcontext.Detach(ctx)
-
- // If our module source is not gopls.mod, try to build the workspace module
- // from modules. Fall back on the pre-existing mod file if parsing fails.
- if w.moduleSource != goplsModWorkspace {
- file, err := buildWorkspaceModFile(ctx, w.activeModFiles, fs)
- switch {
- case err == nil:
- w.mod = file
- case w.mod != nil:
- // Parsing failed, but we have a previous file version.
- event.Error(ctx, "building workspace mod file", err)
- default:
- // No file to fall back on.
- w.buildErr = err
- }
- }
- if w.mod != nil {
- w.wsDirs = map[span.URI]struct{}{
- w.root: {},
- }
- for _, r := range w.mod.Replace {
- // We may be replacing a module with a different version, not a path
- // on disk.
- if r.New.Version != "" {
- continue
- }
- w.wsDirs[span.URIFromPath(r.New.Path)] = struct{}{}
- }
- }
- // Ensure that there is always at least the root dir.
- if len(w.wsDirs) == 0 {
- w.wsDirs = map[span.URI]struct{}{
- w.root: {},
- }
- }
- sum, err := buildWorkspaceSumFile(ctx, w.activeModFiles, fs)
- if err == nil {
- w.sum = sum
- } else {
- event.Error(ctx, "building workspace sum file", err)
- }
- w.built = true
-}
-
-// dirs returns the workspace directories for the loaded modules.
-func (w *workspace) dirs(ctx context.Context, fs source.FileSource) []span.URI {
- w.build(ctx, fs)
- var dirs []span.URI
- for d := range w.wsDirs {
- dirs = append(dirs, d)
- }
- sort.Slice(dirs, func(i, j int) bool {
- return source.CompareURI(dirs[i], dirs[j]) < 0
- })
- return dirs
-}
-
-// invalidate returns a (possibly) new workspace after invalidating the changed
-// files. If w is still valid in the presence of changedURIs, it returns itself
-// unmodified.
-//
-// The returned changed and reload flags control the level of invalidation.
-// Some workspace changes may affect workspace contents without requiring a
-// reload of metadata (for example, unsaved changes to a go.mod or go.sum
-// file).
-func (w *workspace) invalidate(ctx context.Context, changes map[span.URI]*fileChange, fs source.FileSource) (_ *workspace, changed, reload bool) {
- // Prevent races to w.modFile or w.wsDirs below, if wmhas not yet been built.
- w.buildMu.Lock()
- defer w.buildMu.Unlock()
-
- // Clone the workspace. This may be discarded if nothing changed.
- result := &workspace{
- root: w.root,
- moduleSource: w.moduleSource,
- knownModFiles: make(map[span.URI]struct{}),
- activeModFiles: make(map[span.URI]struct{}),
- workFile: w.workFile,
- mod: w.mod,
- sum: w.sum,
- wsDirs: w.wsDirs,
- excludePath: w.excludePath,
- }
- for k, v := range w.knownModFiles {
- result.knownModFiles[k] = v
- }
- for k, v := range w.activeModFiles {
- result.activeModFiles[k] = v
- }
-
- // First handle changes to the go.work or gopls.mod file. This must be
- // considered before any changes to go.mod or go.sum files, as these files
- // determine which modules we care about. If go.work/gopls.mod has changed
- // we need to either re-read it if it exists or walk the filesystem if it
- // has been deleted. go.work should override the gopls.mod if both exist.
- changed, reload = handleWorkspaceFileChanges(ctx, result, changes, fs)
- // Next, handle go.mod changes that could affect our workspace.
- for uri, change := range changes {
- // Otherwise, we only care about go.mod files in the workspace directory.
- if change.isUnchanged || !isGoMod(uri) || !source.InDir(result.root.Filename(), uri.Filename()) {
- continue
- }
- changed = true
- active := result.moduleSource != legacyWorkspace || source.CompareURI(modURI(w.root), uri) == 0
- reload = reload || (active && change.fileHandle.Saved())
- // Don't mess with the list of mod files if using go.work or gopls.mod.
- if result.moduleSource == goplsModWorkspace || result.moduleSource == goWorkWorkspace {
- continue
- }
- if change.exists {
- result.knownModFiles[uri] = struct{}{}
- if active {
- result.activeModFiles[uri] = struct{}{}
- }
- } else {
- delete(result.knownModFiles, uri)
- delete(result.activeModFiles, uri)
- }
- }
-
- // Finally, process go.sum changes for any modules that are now active.
- for uri, change := range changes {
- if !isGoSum(uri) {
- continue
- }
- // TODO(rFindley) factor out this URI mangling.
- dir := filepath.Dir(uri.Filename())
- modURI := span.URIFromPath(filepath.Join(dir, "go.mod"))
- if _, active := result.activeModFiles[modURI]; !active {
- continue
- }
- // Only changes to active go.sum files actually cause the workspace to
- // change.
- changed = true
- reload = reload || change.fileHandle.Saved()
- }
-
- if !changed {
- return w, false, false
- }
-
- return result, changed, reload
-}
-
-// handleWorkspaceFileChanges handles changes related to a go.work or gopls.mod
-// file, updating ws accordingly. ws.root must be set.
-func handleWorkspaceFileChanges(ctx context.Context, ws *workspace, changes map[span.URI]*fileChange, fs source.FileSource) (changed, reload bool) {
- // If go.work/gopls.mod has changed we need to either re-read it if it
- // exists or walk the filesystem if it has been deleted.
- // go.work should override the gopls.mod if both exist.
- for _, src := range []workspaceSource{goWorkWorkspace, goplsModWorkspace} {
- uri := uriForSource(ws.root, src)
- // File opens/closes are just no-ops.
- change, ok := changes[uri]
- if !ok {
- continue
- }
- if change.isUnchanged {
- break
- }
- if change.exists {
- // Only invalidate if the file if it actually parses.
- // Otherwise, stick with the current file.
- var parsedFile *modfile.File
- var parsedModules map[span.URI]struct{}
- var err error
- switch src {
- case goWorkWorkspace:
- parsedFile, parsedModules, err = parseGoWork(ctx, ws.root, uri, change.content, fs)
- case goplsModWorkspace:
- parsedFile, parsedModules, err = parseGoplsMod(ws.root, uri, change.content)
- }
- if err != nil {
- // An unparseable file should not invalidate the workspace:
- // nothing good could come from changing the workspace in
- // this case.
- event.Error(ctx, fmt.Sprintf("parsing %s", filepath.Base(uri.Filename())), err)
- } else {
- // only update the modfile if it parsed.
- changed = true
- reload = change.fileHandle.Saved()
- ws.mod = parsedFile
- ws.moduleSource = src
- ws.knownModFiles = parsedModules
- ws.activeModFiles = make(map[span.URI]struct{})
- for k, v := range parsedModules {
- ws.activeModFiles[k] = v
- }
- }
- break // We've found an explicit workspace file, so can stop looking.
- } else {
- // go.work/gopls.mod is deleted. search for modules again.
- changed = true
- reload = true
- ws.moduleSource = fileSystemWorkspace
- // The parsed file is no longer valid.
- ws.mod = nil
- knownModFiles, err := findModules(ws.root, ws.excludePath, 0)
- if err != nil {
- ws.knownModFiles = nil
- ws.activeModFiles = nil
- event.Error(ctx, "finding file system modules", err)
- } else {
- ws.knownModFiles = knownModFiles
- ws.activeModFiles = make(map[span.URI]struct{})
- for k, v := range ws.knownModFiles {
- ws.activeModFiles[k] = v
- }
- }
- }
- }
- return changed, reload
-}
-
-// goplsModURI returns the URI for the gopls.mod file contained in root.
-func uriForSource(root span.URI, src workspaceSource) span.URI {
- var basename string
- switch src {
- case goplsModWorkspace:
- basename = "gopls.mod"
- case goWorkWorkspace:
- basename = "go.work"
- default:
- return ""
- }
- return span.URIFromPath(filepath.Join(root.Filename(), basename))
-}
-
-// modURI returns the URI for the go.mod file contained in root.
-func modURI(root span.URI) span.URI {
- return span.URIFromPath(filepath.Join(root.Filename(), "go.mod"))
-}
-
-// isGoMod reports if uri is a go.mod file.
-func isGoMod(uri span.URI) bool {
- return filepath.Base(uri.Filename()) == "go.mod"
-}
-
-func isGoSum(uri span.URI) bool {
- return filepath.Base(uri.Filename()) == "go.sum" || filepath.Base(uri.Filename()) == "go.work.sum"
-}
-
-// fileExists reports if the file uri exists within source.
-func fileExists(ctx context.Context, uri span.URI, source source.FileSource) (bool, error) {
- fh, err := source.GetFile(ctx, uri)
- if err != nil {
- return false, err
- }
- return fileHandleExists(fh)
-}
-
-// fileHandleExists reports if the file underlying fh actually exits.
-func fileHandleExists(fh source.FileHandle) (bool, error) {
- _, err := fh.Read()
- if err == nil {
- return true, nil
- }
- if os.IsNotExist(err) {
- return false, nil
- }
- return false, err
-}
-
-// TODO(rFindley): replace this (and similar) with a uripath package analogous
-// to filepath.
-func dirURI(uri span.URI) span.URI {
- return span.URIFromPath(filepath.Dir(uri.Filename()))
-}
-
-// getLegacyModules returns a module set containing at most the root module.
-func getLegacyModules(ctx context.Context, root span.URI, fs source.FileSource) (map[span.URI]struct{}, error) {
- uri := span.URIFromPath(filepath.Join(root.Filename(), "go.mod"))
- modules := make(map[span.URI]struct{})
- exists, err := fileExists(ctx, uri, fs)
- if err != nil {
- return nil, err
- }
- if exists {
- modules[uri] = struct{}{}
- }
- return modules, nil
-}
-
-func parseGoWork(ctx context.Context, root, uri span.URI, contents []byte, fs source.FileSource) (*modfile.File, map[span.URI]struct{}, error) {
- workFile, err := modfile.ParseWork(uri.Filename(), contents, nil)
- if err != nil {
- return nil, nil, errors.Errorf("parsing go.work: %w", err)
- }
- modFiles := make(map[span.URI]struct{})
- for _, dir := range workFile.Use {
- // The resulting modfile must use absolute paths, so that it can be
- // written to a temp directory.
- dir.Path = absolutePath(root, dir.Path)
- modURI := span.URIFromPath(filepath.Join(dir.Path, "go.mod"))
- modFiles[modURI] = struct{}{}
- }
- modFile, err := buildWorkspaceModFile(ctx, modFiles, fs)
- if err != nil {
- return nil, nil, err
- }
-
- // Require a go directive, per the spec.
- if workFile.Go == nil || workFile.Go.Version == "" {
- return nil, nil, fmt.Errorf("go.work has missing or incomplete go directive")
- }
- if err := modFile.AddGoStmt(workFile.Go.Version); err != nil {
- return nil, nil, err
- }
-
- return modFile, modFiles, nil
-}
-
-func parseGoplsMod(root, uri span.URI, contents []byte) (*modfile.File, map[span.URI]struct{}, error) {
- modFile, err := modfile.Parse(uri.Filename(), contents, nil)
- if err != nil {
- return nil, nil, errors.Errorf("parsing gopls.mod: %w", err)
- }
- modFiles := make(map[span.URI]struct{})
- for _, replace := range modFile.Replace {
- if replace.New.Version != "" {
- return nil, nil, errors.Errorf("gopls.mod: replaced module %q@%q must not have version", replace.New.Path, replace.New.Version)
- }
- // The resulting modfile must use absolute paths, so that it can be
- // written to a temp directory.
- replace.New.Path = absolutePath(root, replace.New.Path)
- modURI := span.URIFromPath(filepath.Join(replace.New.Path, "go.mod"))
- modFiles[modURI] = struct{}{}
- }
- return modFile, modFiles, nil
-}
-
-func absolutePath(root span.URI, path string) string {
- dirFP := filepath.FromSlash(path)
- if !filepath.IsAbs(dirFP) {
- dirFP = filepath.Join(root.Filename(), dirFP)
- }
- return dirFP
-}
-
-// 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).
-const fileLimit = 1000000
-
-// findModules recursively walks the root directory looking for go.mod files,
-// returning the set of modules it discovers. If modLimit is non-zero,
-// searching stops once modLimit modules have been found.
-//
-// TODO(rfindley): consider overlays.
-func findModules(root span.URI, excludePath func(string) bool, modLimit int) (map[span.URI]struct{}, error) {
- // Walk the view's folder to find all modules in the view.
- modFiles := make(map[span.URI]struct{})
- searched := 0
- errDone := errors.New("done")
- err := filepath.Walk(root.Filename(), func(path string, info os.FileInfo, err error) error {
- if err != nil {
- // Probably a permission error. Keep looking.
- return filepath.SkipDir
- }
- // For any path that is not the workspace folder, check if the path
- // would be ignored by the go command. Vendor directories also do not
- // contain workspace modules.
- if info.IsDir() && path != root.Filename() {
- suffix := strings.TrimPrefix(path, root.Filename())
- switch {
- case checkIgnored(suffix),
- strings.Contains(filepath.ToSlash(suffix), "/vendor/"),
- excludePath(suffix):
- return filepath.SkipDir
- }
- }
- // We're only interested in go.mod files.
- uri := span.URIFromPath(path)
- if isGoMod(uri) {
- modFiles[uri] = struct{}{}
- }
- if modLimit > 0 && len(modFiles) >= modLimit {
- return errDone
- }
- searched++
- if fileLimit > 0 && searched >= fileLimit {
- return errExhausted
- }
- return nil
- })
- if err == errDone {
- return modFiles, nil
- }
- return modFiles, err
-}
diff --git a/internal/lsp/cache/workspace_test.go b/internal/lsp/cache/workspace_test.go
deleted file mode 100644
index b809ad196..000000000
--- a/internal/lsp/cache/workspace_test.go
+++ /dev/null
@@ -1,425 +0,0 @@
-// 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"
- "os"
- "strings"
- "testing"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/tools/internal/lsp/fake"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
-)
-
-// osFileSource is a fileSource that just reads from the operating system.
-type osFileSource struct {
- overlays map[span.URI]fakeOverlay
-}
-
-type fakeOverlay struct {
- source.VersionedFileHandle
- uri span.URI
- content string
- err error
- saved bool
-}
-
-func (o fakeOverlay) Saved() bool { return o.saved }
-
-func (o fakeOverlay) Read() ([]byte, error) {
- if o.err != nil {
- return nil, o.err
- }
- return []byte(o.content), nil
-}
-
-func (o fakeOverlay) URI() span.URI {
- return o.uri
-}
-
-// change updates the file source with the given file content. For convenience,
-// empty content signals a deletion. If saved is true, these changes are
-// persisted to disk.
-func (s *osFileSource) change(ctx context.Context, uri span.URI, content string, saved bool) (*fileChange, error) {
- if content == "" {
- delete(s.overlays, uri)
- if saved {
- if err := os.Remove(uri.Filename()); err != nil {
- return nil, err
- }
- }
- fh, err := s.GetFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- data, err := fh.Read()
- return &fileChange{exists: err == nil, content: data, fileHandle: &closedFile{fh}}, nil
- }
- if s.overlays == nil {
- s.overlays = map[span.URI]fakeOverlay{}
- }
- s.overlays[uri] = fakeOverlay{uri: uri, content: content, saved: saved}
- return &fileChange{
- exists: content != "",
- content: []byte(content),
- fileHandle: s.overlays[uri],
- }, nil
-}
-
-func (s *osFileSource) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
- if overlay, ok := s.overlays[uri]; ok {
- return overlay, nil
- }
- fi, statErr := os.Stat(uri.Filename())
- if statErr != nil {
- return &fileHandle{
- err: statErr,
- uri: uri,
- }, nil
- }
- fh, err := readFile(ctx, uri, fi)
- if err != nil {
- return nil, err
- }
- return fh, nil
-}
-
-type wsState struct {
- source workspaceSource
- modules []string
- dirs []string
- sum string
-}
-
-type wsChange struct {
- content string
- saved bool
-}
-
-func TestWorkspaceModule(t *testing.T) {
- tests := []struct {
- desc string
- initial string // txtar-encoded
- legacyMode bool
- initialState wsState
- updates map[string]wsChange
- wantChanged bool
- wantReload bool
- finalState wsState
- }{
- {
- desc: "legacy mode",
- initial: `
--- go.mod --
-module mod.com
--- go.sum --
-golang.org/x/mod v0.3.0 h1:deadbeef
--- a/go.mod --
-module moda.com`,
- legacyMode: true,
- initialState: wsState{
- modules: []string{"./go.mod"},
- source: legacyWorkspace,
- dirs: []string{"."},
- sum: "golang.org/x/mod v0.3.0 h1:deadbeef\n",
- },
- },
- {
- desc: "nested module",
- initial: `
--- go.mod --
-module mod.com
--- a/go.mod --
-module moda.com`,
- initialState: wsState{
- modules: []string{"./go.mod", "a/go.mod"},
- source: fileSystemWorkspace,
- dirs: []string{".", "a"},
- },
- },
- {
- desc: "removing module",
- initial: `
--- a/go.mod --
-module moda.com
--- a/go.sum --
-golang.org/x/mod v0.3.0 h1:deadbeef
--- b/go.mod --
-module modb.com
--- b/go.sum --
-golang.org/x/mod v0.3.0 h1:beefdead`,
- initialState: wsState{
- modules: []string{"a/go.mod", "b/go.mod"},
- source: fileSystemWorkspace,
- dirs: []string{".", "a", "b"},
- sum: "golang.org/x/mod v0.3.0 h1:beefdead\ngolang.org/x/mod v0.3.0 h1:deadbeef\n",
- },
- updates: map[string]wsChange{
- "gopls.mod": {`module gopls-workspace
-
-require moda.com v0.0.0-goplsworkspace
-replace moda.com => $SANDBOX_WORKDIR/a`, true},
- },
- wantChanged: true,
- wantReload: true,
- finalState: wsState{
- modules: []string{"a/go.mod"},
- source: goplsModWorkspace,
- dirs: []string{".", "a"},
- sum: "golang.org/x/mod v0.3.0 h1:deadbeef\n",
- },
- },
- {
- desc: "adding module",
- initial: `
--- gopls.mod --
-require moda.com v0.0.0-goplsworkspace
-replace moda.com => $SANDBOX_WORKDIR/a
--- a/go.mod --
-module moda.com
--- b/go.mod --
-module modb.com`,
- initialState: wsState{
- modules: []string{"a/go.mod"},
- source: goplsModWorkspace,
- dirs: []string{".", "a"},
- },
- updates: map[string]wsChange{
- "gopls.mod": {`module gopls-workspace
-
-require moda.com v0.0.0-goplsworkspace
-require modb.com v0.0.0-goplsworkspace
-
-replace moda.com => $SANDBOX_WORKDIR/a
-replace modb.com => $SANDBOX_WORKDIR/b`, true},
- },
- wantChanged: true,
- wantReload: true,
- finalState: wsState{
- modules: []string{"a/go.mod", "b/go.mod"},
- source: goplsModWorkspace,
- dirs: []string{".", "a", "b"},
- },
- },
- {
- desc: "deleting gopls.mod",
- initial: `
--- gopls.mod --
-module gopls-workspace
-
-require moda.com v0.0.0-goplsworkspace
-replace moda.com => $SANDBOX_WORKDIR/a
--- a/go.mod --
-module moda.com
--- b/go.mod --
-module modb.com`,
- initialState: wsState{
- modules: []string{"a/go.mod"},
- source: goplsModWorkspace,
- dirs: []string{".", "a"},
- },
- updates: map[string]wsChange{
- "gopls.mod": {"", true},
- },
- wantChanged: true,
- wantReload: true,
- finalState: wsState{
- modules: []string{"a/go.mod", "b/go.mod"},
- source: fileSystemWorkspace,
- dirs: []string{".", "a", "b"},
- },
- },
- {
- desc: "broken module parsing",
- initial: `
--- a/go.mod --
-module moda.com
-
-require gopls.test v0.0.0-goplsworkspace
-replace gopls.test => ../../gopls.test // (this path shouldn't matter)
--- b/go.mod --
-module modb.com`,
- initialState: wsState{
- modules: []string{"a/go.mod", "b/go.mod"},
- source: fileSystemWorkspace,
- dirs: []string{".", "a", "b", "../gopls.test"},
- },
- updates: map[string]wsChange{
- "a/go.mod": {`modul moda.com
-
-require gopls.test v0.0.0-goplsworkspace
-replace gopls.test => ../../gopls.test2`, false},
- },
- wantChanged: true,
- wantReload: false,
- finalState: wsState{
- modules: []string{"a/go.mod", "b/go.mod"},
- source: fileSystemWorkspace,
- // finalDirs should be unchanged: we should preserve dirs in the presence
- // of a broken modfile.
- dirs: []string{".", "a", "b", "../gopls.test"},
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.desc, func(t *testing.T) {
- ctx := context.Background()
- dir, err := fake.Tempdir(fake.UnpackTxt(test.initial))
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
- root := span.URIFromPath(dir)
-
- fs := &osFileSource{}
- excludeNothing := func(string) bool { return false }
- w, err := newWorkspace(ctx, root, fs, excludeNothing, false, !test.legacyMode)
- if err != nil {
- t.Fatal(err)
- }
- rel := fake.RelativeTo(dir)
- checkState(ctx, t, fs, rel, w, test.initialState)
-
- // Apply updates.
- if test.updates != nil {
- changes := make(map[span.URI]*fileChange)
- for k, v := range test.updates {
- content := strings.ReplaceAll(v.content, "$SANDBOX_WORKDIR", string(rel))
- uri := span.URIFromPath(rel.AbsPath(k))
- changes[uri], err = fs.change(ctx, uri, content, v.saved)
- if err != nil {
- t.Fatal(err)
- }
- }
- got, gotChanged, gotReload := w.invalidate(ctx, changes, fs)
- if gotChanged != test.wantChanged {
- t.Errorf("w.invalidate(): got changed %t, want %t", gotChanged, test.wantChanged)
- }
- if gotReload != test.wantReload {
- t.Errorf("w.invalidate(): got reload %t, want %t", gotReload, test.wantReload)
- }
- checkState(ctx, t, fs, rel, got, test.finalState)
- }
- })
- }
-}
-
-func workspaceFromTxtar(t *testing.T, files string) (*workspace, func(), error) {
- ctx := context.Background()
- dir, err := fake.Tempdir(fake.UnpackTxt(files))
- if err != nil {
- return nil, func() {}, err
- }
- cleanup := func() {
- os.RemoveAll(dir)
- }
- root := span.URIFromPath(dir)
-
- fs := &osFileSource{}
- excludeNothing := func(string) bool { return false }
- workspace, err := newWorkspace(ctx, root, fs, excludeNothing, false, false)
- return workspace, cleanup, err
-}
-
-func TestWorkspaceParseError(t *testing.T) {
- w, cleanup, err := workspaceFromTxtar(t, `
--- go.work --
-go 1.18
-
-usa ./typo
--- typo/go.mod --
-module foo
-`)
- defer cleanup()
- if err != nil {
- t.Fatalf("error creating workspace: %v; want no error", err)
- }
- w.buildMu.Lock()
- built, buildErr := w.built, w.buildErr
- w.buildMu.Unlock()
- if !built || buildErr == nil {
- t.Fatalf("built, buildErr: got %v, %v; want true, non-nil", built, buildErr)
- }
- var errList modfile.ErrorList
- if !errors.As(buildErr, &errList) {
- t.Fatalf("expected error to be an errorlist; got %v", buildErr)
- }
- if len(errList) != 1 {
- t.Fatalf("expected errorList to have one element; got %v elements", len(errList))
- }
- parseErr := errList[0]
- if parseErr.Pos.Line != 3 {
- t.Fatalf("expected error to be on line 3; got %v", parseErr.Pos.Line)
- }
-}
-
-func TestWorkspaceMissingModFile(t *testing.T) {
- w, cleanup, err := workspaceFromTxtar(t, `
--- go.work --
-go 1.18
-
-use ./missing
-`)
- defer cleanup()
- if err != nil {
- t.Fatalf("error creating workspace: %v; want no error", err)
- }
- w.buildMu.Lock()
- built, buildErr := w.built, w.buildErr
- w.buildMu.Unlock()
- if !built || buildErr == nil {
- t.Fatalf("built, buildErr: got %v, %v; want true, non-nil", built, buildErr)
- }
-}
-
-func checkState(ctx context.Context, t *testing.T, fs source.FileSource, rel fake.RelativeTo, got *workspace, want wsState) {
- t.Helper()
- if got.moduleSource != want.source {
- t.Errorf("module source = %v, want %v", got.moduleSource, want.source)
- }
- modules := make(map[span.URI]struct{})
- for k := range got.getActiveModFiles() {
- modules[k] = struct{}{}
- }
- for _, modPath := range want.modules {
- path := rel.AbsPath(modPath)
- uri := span.URIFromPath(path)
- if _, ok := modules[uri]; !ok {
- t.Errorf("missing module %q", uri)
- }
- delete(modules, uri)
- }
- for remaining := range modules {
- t.Errorf("unexpected module %q", remaining)
- }
- gotDirs := got.dirs(ctx, fs)
- gotM := make(map[span.URI]bool)
- for _, dir := range gotDirs {
- gotM[dir] = true
- }
- for _, dir := range want.dirs {
- path := rel.AbsPath(dir)
- uri := span.URIFromPath(path)
- if !gotM[uri] {
- t.Errorf("missing dir %q", uri)
- }
- delete(gotM, uri)
- }
- for remaining := range gotM {
- t.Errorf("unexpected dir %q", remaining)
- }
- gotSumBytes, err := got.sumFile(ctx, fs)
- if err != nil {
- t.Fatal(err)
- }
- if gotSum := string(gotSumBytes); gotSum != want.sum {
- t.Errorf("got final sum %q, want %q", gotSum, want.sum)
- }
-}
diff --git a/internal/lsp/call_hierarchy.go b/internal/lsp/call_hierarchy.go
deleted file mode 100644
index 43c4ea8d5..000000000
--- a/internal/lsp/call_hierarchy.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-func (s *Server) prepareCallHierarchy(ctx context.Context, params *protocol.CallHierarchyPrepareParams) ([]protocol.CallHierarchyItem, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.Go)
- defer release()
- if !ok {
- return nil, err
- }
-
- return source.PrepareCallHierarchy(ctx, snapshot, fh, params.Position)
-}
-
-func (s *Server) incomingCalls(ctx context.Context, params *protocol.CallHierarchyIncomingCallsParams) ([]protocol.CallHierarchyIncomingCall, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.Item.URI, source.Go)
- defer release()
- if !ok {
- return nil, err
- }
-
- return source.IncomingCalls(ctx, snapshot, fh, params.Item.Range.Start)
-}
-
-func (s *Server) outgoingCalls(ctx context.Context, params *protocol.CallHierarchyOutgoingCallsParams) ([]protocol.CallHierarchyOutgoingCall, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.Item.URI, source.Go)
- defer release()
- if !ok {
- return nil, err
- }
-
- return source.OutgoingCalls(ctx, snapshot, fh, params.Item.Range.Start)
-}
diff --git a/internal/lsp/cmd/call_hierarchy.go b/internal/lsp/cmd/call_hierarchy.go
deleted file mode 100644
index c9f9e73e0..000000000
--- a/internal/lsp/cmd/call_hierarchy.go
+++ /dev/null
@@ -1,146 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "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 "<position>" }
-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 := span.Parse(args[0])
- file := conn.AddFile(ctx, from.URI())
- if file.err != nil {
- return file.err
- }
-
- loc, err := file.mapper.Location(from)
- if err != nil {
- return err
- }
-
- p := protocol.CallHierarchyPrepareParams{
- TextDocumentPositionParams: protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- },
- }
-
- 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 := conn.AddFile(ctx, item.URI.SpanURI())
- if itemFile.err != nil {
- return "", itemFile.err
- }
- itemSpan, err := itemFile.mapper.Span(protocol.Location{URI: item.URI, Range: item.Range})
- if err != nil {
- return "", err
- }
-
- callsFile := conn.AddFile(ctx, callsURI.SpanURI())
- if callsURI != "" && callsFile.err != nil {
- return "", callsFile.err
- }
- var callRanges []string
- for _, rng := range calls {
- callSpan, err := callsFile.mapper.Span(protocol.Location{URI: item.URI, Range: rng})
- if err != nil {
- return "", err
- }
-
- spn := fmt.Sprint(callSpan)
- callRanges = append(callRanges, fmt.Sprint(spn[strings.Index(spn, ":")+1:]))
- }
-
- 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.SpanURI().Filename(), printString)
- }
- return printString, nil
-}
diff --git a/internal/lsp/cmd/capabilities_test.go b/internal/lsp/cmd/capabilities_test.go
deleted file mode 100644
index 70db8d7d3..000000000
--- a/internal/lsp/cmd/capabilities_test.go
+++ /dev/null
@@ -1,166 +0,0 @@
-// 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"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "golang.org/x/tools/internal/lsp"
- "golang.org/x/tools/internal/lsp/cache"
- "golang.org/x/tools/internal/lsp/protocol"
- errors "golang.org/x/xerrors"
-)
-
-// 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) {
- tmpDir, err := ioutil.TempDir("", "fake")
- if err != nil {
- t.Fatal(err)
- }
- tmpFile := filepath.Join(tmpDir, "fake.go")
- if err := ioutil.WriteFile(tmpFile, []byte(""), 0775); err != nil {
- t.Fatal(err)
- }
- if err := ioutil.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("gopls-test", tmpDir, os.Environ(), nil)
- c := newConnection(app)
- ctx := context.Background()
- defer c.terminate(ctx)
-
- params := &protocol.ParamInitialize{}
- params.RootURI = protocol.URIFromPath(c.Client.app.wd)
- params.Capabilities.Workspace.Configuration = true
-
- // Send an initialize request to the server.
- c.Server = lsp.NewServer(cache.New(app.options).NewSession(ctx), c.Client)
- result, err := c.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 := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
- t.Fatal(err)
- }
-
- // 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 interface{} = item.TextEdit
- if _, ok := textEdit.(*protocol.TextEdit); !ok {
- t.Errorf("textEdit is not a *protocol.TextEdit, 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 errors.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 errors.Errorf("CodeActionSupport must be a boolean if CodeActionKind.ValueSet has length 0 (got %T)", v)
- }
- return nil
-}
diff --git a/internal/lsp/cmd/check.go b/internal/lsp/cmd/check.go
deleted file mode 100644
index 566924aa6..000000000
--- a/internal/lsp/cmd/check.go
+++ /dev/null
@@ -1,74 +0,0 @@
-// 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/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// 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 "<filename>" }
-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/lsp/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 {
- // no files, so no results
- return nil
- }
- checking := map[span.URI]*cmdFile{}
- var uris []span.URI
- // now we ready to kick things off
- conn, err := c.app.connect(ctx)
- if err != nil {
- return err
- }
- defer conn.terminate(ctx)
- for _, arg := range args {
- uri := span.URIFromPath(arg)
- uris = append(uris, uri)
- file := conn.AddFile(ctx, uri)
- if file.err != nil {
- return file.err
- }
- checking[uri] = file
- }
- if err := conn.diagnoseFiles(ctx, uris); err != nil {
- return err
- }
- conn.Client.filesMu.Lock()
- defer conn.Client.filesMu.Unlock()
-
- for _, file := range checking {
- for _, d := range file.diagnostics {
- spn, err := file.mapper.RangeSpan(d.Range)
- if err != nil {
- return errors.Errorf("Could not convert position %v for %q", d.Range, d.Message)
- }
- fmt.Printf("%v: %v\n", spn, d.Message)
- }
- }
- return nil
-}
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
deleted file mode 100644
index d48398d0d..000000000
--- a/internal/lsp/cmd/cmd.go
+++ /dev/null
@@ -1,630 +0,0 @@
-// 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"
- "go/token"
- "io/ioutil"
- "log"
- "os"
- "reflect"
- "sort"
- "strings"
- "sync"
- "text/tabwriter"
- "time"
-
- "golang.org/x/tools/internal/jsonrpc2"
- "golang.org/x/tools/internal/lsp"
- "golang.org/x/tools/internal/lsp/cache"
- "golang.org/x/tools/internal/lsp/debug"
- "golang.org/x/tools/internal/lsp/lsprpc"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/tool"
- "golang.org/x/tools/internal/xcontext"
- errors "golang.org/x/xerrors"
-)
-
-// 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(*source.Options)
-
- // The name of the binary, used in help and telemetry.
- name string
-
- // The working directory to run commands in.
- wd string
-
- // The environment variables to use.
- env []string
-
- // 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(*source.Options)
-}
-
-func (app *Application) verbose() bool {
- return app.Verbose || app.VeryVerbose
-}
-
-// New returns a new Application ready to run.
-func New(name, wd string, env []string, options func(*source.Options)) *Application {
- if wd == "" {
- wd, _ = os.Getwd()
- }
- app := &Application{
- options: options,
- name: name,
- wd: wd,
- env: env,
- 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 app.name }
-
-// 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.
-
-Usage:
- gopls help [<subject>]
-
-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())
- }
- 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 {
- ctx = debug.WithInstance(ctx, app.wd, 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()...)
- return commands
-}
-
-func (app *Application) mainCommands() []tool.Application {
- return []tool.Application{
- &app.Serve,
- &version{app: app},
- &bug{app: app},
- &apiJSON{app: app},
- &licenses{app: app},
- }
-}
-
-func (app *Application) featureCommands() []tool.Application {
- return []tool.Application{
- &callHierarchy{app: app},
- &check{app: app},
- &definition{app: app},
- &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},
- &suggestedFix{app: app},
- &symbols{app: app},
- newWorkspace(app),
- &workspaceSymbol{app: app},
- &vulncheck{app: app},
- }
-}
-
-var (
- internalMu sync.Mutex
- internalConnections = make(map[string]*connection)
-)
-
-func (app *Application) connect(ctx context.Context) (*connection, error) {
- switch {
- case app.Remote == "":
- connection := newConnection(app)
- connection.Server = lsp.NewServer(cache.New(app.options).NewSession(ctx), connection.Client)
- ctx = protocol.WithClient(ctx, connection.Client)
- return connection, connection.initialize(ctx, app.options)
- case strings.HasPrefix(app.Remote, "internal@"):
- internalMu.Lock()
- defer internalMu.Unlock()
- opts := source.DefaultOptions().Clone()
- if app.options != nil {
- app.options(opts)
- }
- key := fmt.Sprintf("%s %v %v %v", app.wd, opts.PreferredContentFormat, opts.HierarchicalDocumentSymbolSupport, opts.SymbolMatcher)
- if c := internalConnections[key]; c != nil {
- return c, nil
- }
- remote := app.Remote[len("internal@"):]
- ctx := xcontext.Detach(ctx) //TODO:a way of shutting down the internal server
- connection, err := app.connectRemote(ctx, remote)
- if err != nil {
- return nil, err
- }
- internalConnections[key] = connection
- return connection, nil
- default:
- return app.connectRemote(ctx, app.Remote)
- }
-}
-
-// CloseTestConnections terminates shared connections used in command tests. It
-// should only be called from tests.
-func CloseTestConnections(ctx context.Context) {
- for _, c := range internalConnections {
- c.Shutdown(ctx)
- c.Exit(ctx)
- }
-}
-
-func (app *Application) connectRemote(ctx context.Context, remote string) (*connection, error) {
- connection := newConnection(app)
- conn, err := lsprpc.ConnectToRemote(ctx, remote)
- if err != nil {
- return nil, err
- }
- stream := jsonrpc2.NewHeaderStream(conn)
- cc := jsonrpc2.NewConn(stream)
- connection.Server = protocol.ServerDispatcher(cc)
- ctx = protocol.WithClient(ctx, connection.Client)
- cc.Go(ctx,
- protocol.Handlers(
- protocol.ClientHandler(connection.Client,
- jsonrpc2.MethodNotFound)))
- return connection, connection.initialize(ctx, app.options)
-}
-
-var matcherString = map[source.SymbolMatcher]string{
- source.SymbolFuzzy: "fuzzy",
- source.SymbolCaseSensitive: "caseSensitive",
- source.SymbolCaseInsensitive: "caseInsensitive",
-}
-
-func (c *connection) initialize(ctx context.Context, options func(*source.Options)) error {
- params := &protocol.ParamInitialize{}
- params.RootURI = protocol.URIFromPath(c.Client.app.wd)
- params.Capabilities.Workspace.Configuration = true
-
- // Make sure to respect configured options when sending initialize request.
- opts := source.DefaultOptions().Clone()
- if options != nil {
- options(opts)
- }
- // 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 = []string{"relative"}
- params.Capabilities.TextDocument.SemanticTokens.Requests.Range = true
- params.Capabilities.TextDocument.SemanticTokens.Requests.Full = true
- params.Capabilities.TextDocument.SemanticTokens.TokenTypes = lsp.SemanticTypes()
- params.Capabilities.TextDocument.SemanticTokens.TokenModifiers = lsp.SemanticModifiers()
- params.InitializationOptions = map[string]interface{}{
- "symbolMatcher": matcherString[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
-}
-
-type cmdClient struct {
- protocol.Server
- app *Application
- fset *token.FileSet
-
- diagnosticsMu sync.Mutex
- diagnosticsDone chan struct{}
-
- filesMu sync.Mutex
- files map[span.URI]*cmdFile
-}
-
-type cmdFile struct {
- uri span.URI
- mapper *protocol.ColumnMapper
- err error
- added bool
- diagnostics []protocol.Diagnostic
-}
-
-func newConnection(app *Application) *connection {
- return &connection{
- Client: &cmdClient{
- app: app,
- fset: token.NewFileSet(),
- files: make(map[span.URI]*cmdFile),
- },
- }
-}
-
-// fileURI converts a DocumentURI to a file:// span.URI, panicking if it's not a file.
-func fileURI(uri protocol.DocumentURI) span.URI {
- sURI := uri.SpanURI()
- if !sURI.IsFile() {
- panic(fmt.Sprintf("%q is not a file URI", uri))
- }
- return sURI
-}
-
-func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { 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 {
- 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
- }
- env := map[string]interface{}{}
- for _, value := range c.app.env {
- l := strings.SplitN(value, "=", 2)
- if len(l) != 2 {
- continue
- }
- env[l[0]] = l[1]
- }
- m := map[string]interface{}{
- "env": env,
- "analyses": map[string]bool{
- "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) {
- return &protocol.ApplyWorkspaceEditResult{Applied: false, FailureReason: "not implemented"}, nil
-}
-
-func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {
- if p.URI == "gopls://diagnostics-done" {
- close(c.diagnosticsDone)
- }
- // Don't worry about diagnostics without versions.
- if p.Version == 0 {
- return nil
- }
-
- c.filesMu.Lock()
- defer c.filesMu.Unlock()
-
- file := c.getFile(ctx, fileURI(p.URI))
- file.diagnostics = p.Diagnostics
- return nil
-}
-
-func (c *cmdClient) Progress(context.Context, *protocol.ProgressParams) error {
- return nil
-}
-
-func (c *cmdClient) ShowDocument(context.Context, *protocol.ShowDocumentParams) (*protocol.ShowDocumentResult, error) {
- return nil, nil
-}
-
-func (c *cmdClient) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error {
- return nil
-}
-
-func (c *cmdClient) getFile(ctx context.Context, uri span.URI) *cmdFile {
- file, found := c.files[uri]
- if !found || file.err != nil {
- file = &cmdFile{
- uri: uri,
- }
- c.files[uri] = file
- }
- if file.mapper == nil {
- fname := uri.Filename()
- content, err := ioutil.ReadFile(fname)
- if err != nil {
- file.err = errors.Errorf("getFile: %v: %v", uri, err)
- return file
- }
- f := c.fset.AddFile(fname, -1, len(content))
- f.SetLinesForContent(content)
- converter := span.NewContentConverter(fname, content)
- file.mapper = &protocol.ColumnMapper{
- URI: uri,
- Converter: converter,
- Content: content,
- }
- }
- return file
-}
-
-func (c *connection) AddFile(ctx context.Context, uri span.URI) *cmdFile {
- c.Client.filesMu.Lock()
- defer c.Client.filesMu.Unlock()
-
- file := c.Client.getFile(ctx, uri)
- // This should never happen.
- if file == nil {
- return &cmdFile{
- uri: uri,
- err: fmt.Errorf("no file found for %s", uri),
- }
- }
- if file.err != nil || file.added {
- return file
- }
- file.added = true
- p := &protocol.DidOpenTextDocumentParams{
- TextDocument: protocol.TextDocumentItem{
- URI: protocol.URIFromSpanURI(uri),
- LanguageID: "go",
- Version: 1,
- Text: string(file.mapper.Content),
- },
- }
- if err := c.Server.DidOpen(ctx, p); err != nil {
- file.err = errors.Errorf("%v: %v", uri, err)
- }
- return file
-}
-
-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 []span.URI) error {
- var untypedFiles []interface{}
- for _, file := range files {
- untypedFiles = append(untypedFiles, string(file))
- }
- c.Client.diagnosticsMu.Lock()
- defer c.Client.diagnosticsMu.Unlock()
-
- c.Client.diagnosticsDone = make(chan struct{})
- _, err := c.Server.NonstandardRequest(ctx, "gopls/diagnoseFiles", map[string]interface{}{"files": untypedFiles})
- if err != nil {
- close(c.Client.diagnosticsDone)
- return err
- }
-
- <-c.Client.diagnosticsDone
- return nil
-}
-
-func (c *connection) terminate(ctx context.Context) {
- if strings.HasPrefix(c.Client.app.Remote, "internal@") {
- // internal connections need to be left alive for the next test
- return
- }
- //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
-}
diff --git a/internal/lsp/cmd/cmd_test.go b/internal/lsp/cmd/cmd_test.go
deleted file mode 100644
index 29816c83e..000000000
--- a/internal/lsp/cmd/cmd_test.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// 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
-
-import (
- "os"
- "testing"
-
- cmdtest "golang.org/x/tools/internal/lsp/cmd/test"
- "golang.org/x/tools/internal/lsp/tests"
- "golang.org/x/tools/internal/testenv"
-)
-
-func TestMain(m *testing.M) {
- testenv.ExitIfSmallMachine()
- os.Exit(m.Run())
-}
-
-func TestCommandLine(t *testing.T) {
- cmdtest.TestCommandLine(t, "../testdata", tests.DefaultOptions)
-}
diff --git a/internal/lsp/cmd/definition.go b/internal/lsp/cmd/definition.go
deleted file mode 100644
index f3c71b671..000000000
--- a/internal/lsp/cmd/definition.go
+++ /dev/null
@@ -1,137 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/tool"
- errors "golang.org/x/xerrors"
-)
-
-// A Definition is the result of a 'definition' query.
-type Definition struct {
- Span 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] <position>" }
-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/lsp/cmd/definition.go:%[1]v:%[2]v
- $ gopls definition internal/lsp/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 *source.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 := span.Parse(args[0])
- file := conn.AddFile(ctx, from.URI())
- if file.err != nil {
- return file.err
- }
- loc, err := file.mapper.Location(from)
- if err != nil {
- return err
- }
- tdpp := protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- }
- p := protocol.DefinitionParams{
- TextDocumentPositionParams: tdpp,
- }
- locs, err := conn.Definition(ctx, &p)
- if err != nil {
- return errors.Errorf("%v: %v", from, err)
- }
-
- if len(locs) == 0 {
- return errors.Errorf("%v: not an identifier", from)
- }
- q := protocol.HoverParams{
- TextDocumentPositionParams: tdpp,
- }
- hover, err := conn.Hover(ctx, &q)
- if err != nil {
- return errors.Errorf("%v: %v", from, err)
- }
- if hover == nil {
- return errors.Errorf("%v: not an identifier", from)
- }
- file = conn.AddFile(ctx, fileURI(locs[0].URI))
- if file.err != nil {
- return errors.Errorf("%v: %v", from, file.err)
- }
- definition, err := file.mapper.Span(locs[0])
- if err != nil {
- return errors.Errorf("%v: %v", from, err)
- }
- 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: defined here as %s", result.Span, result.Description)
- return nil
-}
diff --git a/internal/lsp/cmd/export_test.go b/internal/lsp/cmd/export_test.go
deleted file mode 100644
index 05b3cd312..000000000
--- a/internal/lsp/cmd/export_test.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// 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
-
-const (
- ExampleLine = exampleLine
- ExampleColumn = exampleColumn
- ExampleOffset = exampleOffset
-)
diff --git a/internal/lsp/cmd/folding_range.go b/internal/lsp/cmd/folding_range.go
deleted file mode 100644
index 513c9bdd2..000000000
--- a/internal/lsp/cmd/folding_range.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "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 "<file>" }
-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 := span.Parse(args[0])
- file := conn.AddFile(ctx, from.URI())
- if file.err != nil {
- return file.err
- }
-
- p := protocol.FoldingRangeParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(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,
- )
- }
-
- return nil
-}
diff --git a/internal/lsp/cmd/format.go b/internal/lsp/cmd/format.go
deleted file mode 100644
index 2d0f3f7c3..000000000
--- a/internal/lsp/cmd/format.go
+++ /dev/null
@@ -1,108 +0,0 @@
-// 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"
- "io/ioutil"
-
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// format implements the format verb for gopls.
-type format struct {
- Diff bool `flag:"d,diff" help:"display diffs instead of rewriting files"`
- Write bool `flag:"w,write" help:"write result to (source) file instead of stdout"`
- List bool `flag:"l,list" help:"list files whose formatting differs from gofmt's"`
-
- 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] <filerange>" }
-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/lsp/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 {
- // no files, so no results
- return nil
- }
- // now we ready to kick things off
- conn, err := c.app.connect(ctx)
- if err != nil {
- return err
- }
- defer conn.terminate(ctx)
- for _, arg := range args {
- spn := span.Parse(arg)
- file := conn.AddFile(ctx, spn.URI())
- if file.err != nil {
- return file.err
- }
- filename := spn.URI().Filename()
- loc, err := file.mapper.Location(spn)
- if err != nil {
- return err
- }
- if loc.Range.Start != loc.Range.End {
- return errors.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 errors.Errorf("%v: %v", spn, err)
- }
- sedits, err := source.FromProtocolEdits(file.mapper, edits)
- if err != nil {
- return errors.Errorf("%v: %v", spn, err)
- }
- formatted := diff.ApplyEdits(string(file.mapper.Content), sedits)
- printIt := true
- if c.List {
- printIt = false
- if len(edits) > 0 {
- fmt.Println(filename)
- }
- }
- if c.Write {
- printIt = false
- if len(edits) > 0 {
- ioutil.WriteFile(filename, []byte(formatted), 0644)
- }
- }
- if c.Diff {
- printIt = false
- u := diff.ToUnified(filename+".orig", filename, string(file.mapper.Content), sedits)
- fmt.Print(u)
- }
- if printIt {
- fmt.Print(formatted)
- }
- }
- return nil
-}
diff --git a/internal/lsp/cmd/help_test.go b/internal/lsp/cmd/help_test.go
deleted file mode 100644
index 536d19dc2..000000000
--- a/internal/lsp/cmd/help_test.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// 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
-
-import (
- "bytes"
- "context"
- "flag"
- "io/ioutil"
- "path/filepath"
- "testing"
-
- "golang.org/x/tools/internal/lsp/cmd"
- "golang.org/x/tools/internal/testenv"
- "golang.org/x/tools/internal/tool"
-)
-
-//go:generate go test -run Help -update-help-files
-
-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(appName, "", nil, nil)
- 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 := ioutil.WriteFile(helpFile, got, 0666); err != nil {
- t.Errorf("Failed writing %v: %v", helpFile, err)
- }
- return
- }
- expect, err := ioutil.ReadFile(helpFile)
- switch {
- case err != nil:
- t.Errorf("Missing help file %q", helpFile)
- case !bytes.Equal(expect, got):
- t.Errorf("Help file %q did not match, got:\n%q\nwant:\n%q", helpFile, string(got), string(expect))
- }
- })
- }
-}
diff --git a/internal/lsp/cmd/highlight.go b/internal/lsp/cmd/highlight.go
deleted file mode 100644
index a325a2d53..000000000
--- a/internal/lsp/cmd/highlight.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "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 "<position>" }
-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 := span.Parse(args[0])
- file := conn.AddFile(ctx, from.URI())
- if file.err != nil {
- return file.err
- }
-
- loc, err := file.mapper.Location(from)
- if err != nil {
- return err
- }
-
- p := protocol.DocumentHighlightParams{
- TextDocumentPositionParams: protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- },
- }
- highlights, err := conn.DocumentHighlight(ctx, &p)
- if err != nil {
- return err
- }
-
- var results []span.Span
- for _, h := range highlights {
- l := protocol.Location{Range: h.Range}
- s, err := file.mapper.Span(l)
- if err != nil {
- return err
- }
- results = append(results, s)
- }
- // Sort results to make tests deterministic since DocumentHighlight uses a map.
- sort.SliceStable(results, func(i, j int) bool {
- return span.Compare(results[i], results[j]) == -1
- })
-
- for _, s := range results {
- fmt.Println(s)
- }
- return nil
-}
diff --git a/internal/lsp/cmd/implementation.go b/internal/lsp/cmd/implementation.go
deleted file mode 100644
index 7b42d9943..000000000
--- a/internal/lsp/cmd/implementation.go
+++ /dev/null
@@ -1,88 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "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 "<position>" }
-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 := span.Parse(args[0])
- file := conn.AddFile(ctx, from.URI())
- if file.err != nil {
- return file.err
- }
-
- loc, err := file.mapper.Location(from)
- if err != nil {
- return err
- }
-
- p := protocol.ImplementationParams{
- TextDocumentPositionParams: protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- },
- }
-
- implementations, err := conn.Implementation(ctx, &p)
- if err != nil {
- return err
- }
-
- var spans []string
- for _, impl := range implementations {
- f := conn.AddFile(ctx, fileURI(impl.URI))
- span, err := f.mapper.Span(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/internal/lsp/cmd/imports.go b/internal/lsp/cmd/imports.go
deleted file mode 100644
index 215c57f11..000000000
--- a/internal/lsp/cmd/imports.go
+++ /dev/null
@@ -1,102 +0,0 @@
-// 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"
- "io/ioutil"
-
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/tool"
- errors "golang.org/x/xerrors"
-)
-
-// imports implements the import verb for gopls.
-type imports struct {
- Diff bool `flag:"d,diff" help:"display diffs instead of rewriting files"`
- Write bool `flag:"w,write" help:"write result to (source) file instead of stdout"`
-
- 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] <filename>" }
-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/lsp/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")
- }
- conn, err := t.app.connect(ctx)
- if err != nil {
- return err
- }
- defer conn.terminate(ctx)
-
- from := span.Parse(args[0])
- uri := from.URI()
- file := conn.AddFile(ctx, uri)
- if file.err != nil {
- return file.err
- }
- actions, err := conn.CodeAction(ctx, &protocol.CodeActionParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- })
- if err != nil {
- return errors.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 {
- if fileURI(c.TextDocument.URI) == uri {
- edits = append(edits, c.Edits...)
- }
- }
- }
- sedits, err := source.FromProtocolEdits(file.mapper, edits)
- if err != nil {
- return errors.Errorf("%v: %v", edits, err)
- }
- newContent := diff.ApplyEdits(string(file.mapper.Content), sedits)
-
- filename := file.uri.Filename()
- switch {
- case t.Write:
- if len(edits) > 0 {
- ioutil.WriteFile(filename, []byte(newContent), 0644)
- }
- case t.Diff:
- diffs := diff.ToUnified(filename+".orig", filename, string(file.mapper.Content), sedits)
- fmt.Print(diffs)
- default:
- fmt.Print(string(newContent))
- }
- return nil
-}
diff --git a/internal/lsp/cmd/info.go b/internal/lsp/cmd/info.go
deleted file mode 100644
index 09f453e1a..000000000
--- a/internal/lsp/cmd/info.go
+++ /dev/null
@@ -1,197 +0,0 @@
-// 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 (
- "bytes"
- "context"
- "encoding/json"
- "flag"
- "fmt"
- "net/url"
- "os"
- "strings"
-
- "golang.org/x/tools/internal/lsp/browser"
- "golang.org/x/tools/internal/lsp/debug"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-// 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: <DESCRIBE THE PROBLEM>"
-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 {
- buf := &bytes.Buffer{}
- fmt.Fprint(buf, goplsBugHeader)
- debug.PrintVersionInfo(ctx, buf, true, debug.Markdown)
- body := buf.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)
- }
- 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(), ``)
- printFlagDefaults(f)
-}
-
-func (j *apiJSON) Run(ctx context.Context, args ...string) error {
- js, err := json.MarshalIndent(source.GeneratedAPIJSON, "", "\t")
- if err != nil {
- return err
- }
- fmt.Fprint(os.Stdout, string(js))
- 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 {
- opts := source.DefaultOptions()
- l.app.options(opts)
- txt := licensePreamble
- if opts.LicensesText == "" {
- txt += "(development gopls, license information not available)"
- } else {
- txt += opts.LicensesText
- }
- fmt.Fprint(os.Stdout, txt)
- return nil
-}
diff --git a/internal/lsp/cmd/links.go b/internal/lsp/cmd/links.go
deleted file mode 100644
index d49aabb6f..000000000
--- a/internal/lsp/cmd/links.go
+++ /dev/null
@@ -1,78 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/tool"
- errors "golang.org/x/xerrors"
-)
-
-// 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] <filename>" }
-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/lsp/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 := span.Parse(args[0])
- uri := from.URI()
- file := conn.AddFile(ctx, uri)
- if file.err != nil {
- return file.err
- }
- results, err := conn.DocumentLink(ctx, &protocol.DocumentLinkParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- })
- if err != nil {
- return errors.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/internal/lsp/cmd/prepare_rename.go b/internal/lsp/cmd/prepare_rename.go
deleted file mode 100644
index aef0477e8..000000000
--- a/internal/lsp/cmd/prepare_rename.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/tool"
- errors "golang.org/x/xerrors"
-)
-
-// 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 "<position>" }
-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 := span.Parse(args[0])
- file := conn.AddFile(ctx, from.URI())
- if file.err != nil {
- return file.err
- }
- loc, err := file.mapper.Location(from)
- if err != nil {
- return err
- }
- p := protocol.PrepareRenameParams{
- TextDocumentPositionParams: protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- },
- }
- result, err := conn.PrepareRename(ctx, &p)
- if err != nil {
- return errors.Errorf("prepare_rename failed: %w", err)
- }
- if result == nil {
- return ErrInvalidRenamePosition
- }
-
- l := protocol.Location{Range: result.Range}
- s, err := file.mapper.Span(l)
- if err != nil {
- return err
- }
-
- fmt.Println(s)
- return nil
-}
diff --git a/internal/lsp/cmd/references.go b/internal/lsp/cmd/references.go
deleted file mode 100644
index 0697d2e11..000000000
--- a/internal/lsp/cmd/references.go
+++ /dev/null
@@ -1,92 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "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] <position>" }
-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 := span.Parse(args[0])
- file := conn.AddFile(ctx, from.URI())
- if file.err != nil {
- return file.err
- }
- loc, err := file.mapper.Location(from)
- if err != nil {
- return err
- }
- p := protocol.ReferenceParams{
- Context: protocol.ReferenceContext{
- IncludeDeclaration: r.IncludeDeclaration,
- },
- TextDocumentPositionParams: protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- },
- }
- locations, err := conn.References(ctx, &p)
- if err != nil {
- return err
- }
- var spans []string
- for _, l := range locations {
- f := conn.AddFile(ctx, fileURI(l.URI))
- // convert location to span for user-friendly 1-indexed line
- // and column numbers
- span, err := f.mapper.Span(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/internal/lsp/cmd/remote.go b/internal/lsp/cmd/remote.go
deleted file mode 100644
index f71113576..000000000
--- a/internal/lsp/cmd/remote.go
+++ /dev/null
@@ -1,164 +0,0 @@
-// 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"
- "flag"
- "fmt"
- "log"
- "os"
-
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/lsprpc"
- errors "golang.org/x/xerrors"
-)
-
-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.ID(), 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/internal/lsp/cmd/rename.go b/internal/lsp/cmd/rename.go
deleted file mode 100644
index b0a22a1b4..000000000
--- a/internal/lsp/cmd/rename.go
+++ /dev/null
@@ -1,128 +0,0 @@
-// 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"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
-
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/tool"
- errors "golang.org/x/xerrors"
-)
-
-// rename implements the rename verb for gopls.
-type rename struct {
- Diff bool `flag:"d,diff" help:"display diffs instead of rewriting files"`
- Write bool `flag:"w,write" help:"write result to (source) file instead of stdout"`
- Preserve bool `flag:"preserve" help:"preserve original files"`
-
- 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] <position> <name>" }
-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("definition expects 2 arguments (position, new name)")
- }
- conn, err := r.app.connect(ctx)
- if err != nil {
- return err
- }
- defer conn.terminate(ctx)
-
- from := span.Parse(args[0])
- file := conn.AddFile(ctx, from.URI())
- if file.err != nil {
- return file.err
- }
- loc, err := file.mapper.Location(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
- }
- var orderedURIs []string
- edits := map[span.URI][]protocol.TextEdit{}
- for _, c := range edit.DocumentChanges {
- uri := fileURI(c.TextDocument.URI)
- edits[uri] = append(edits[uri], c.Edits...)
- orderedURIs = append(orderedURIs, string(uri))
- }
- sort.Strings(orderedURIs)
- changeCount := len(orderedURIs)
-
- for _, u := range orderedURIs {
- uri := span.URIFromURI(u)
- cmdFile := conn.AddFile(ctx, uri)
- filename := cmdFile.uri.Filename()
-
- // convert LSP-style edits to []diff.TextEdit cuz Spans are handy
- renameEdits, err := source.FromProtocolEdits(cmdFile.mapper, edits[uri])
- if err != nil {
- return errors.Errorf("%v: %v", edits, err)
- }
- newContent := diff.ApplyEdits(string(cmdFile.mapper.Content), renameEdits)
-
- switch {
- case r.Write:
- fmt.Fprintln(os.Stderr, filename)
- if r.Preserve {
- if err := os.Rename(filename, filename+".orig"); err != nil {
- return errors.Errorf("%v: %v", edits, err)
- }
- }
- ioutil.WriteFile(filename, []byte(newContent), 0644)
- case r.Diff:
- diffs := diff.ToUnified(filename+".orig", filename, string(cmdFile.mapper.Content), renameEdits)
- fmt.Print(diffs)
- default:
- if len(orderedURIs) > 1 {
- fmt.Printf("%s:\n", filepath.Base(filename))
- }
- fmt.Print(string(newContent))
- if changeCount > 1 { // if this wasn't last change, print newline
- fmt.Println()
- }
- changeCount -= 1
- }
- }
- return nil
-}
diff --git a/internal/lsp/cmd/semantictokens.go b/internal/lsp/cmd/semantictokens.go
deleted file mode 100644
index 120f91d36..000000000
--- a/internal/lsp/cmd/semantictokens.go
+++ /dev/null
@@ -1,230 +0,0 @@
-// 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"
- "go/parser"
- "go/token"
- "io/ioutil"
- "log"
- "os"
- "unicode/utf8"
-
- "golang.org/x/tools/internal/lsp"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
-)
-
-// 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:
-// /*<arrow><length>,<token type>,[<modifiers]*/
-// For most occurrences, the comment comes just before the token it
-// describes, and arrow is a right arrow. If the token is inside a string
-// the comment comes just after the string, and the arrow is a left arrow.
-// <length> is the length of the token in runes, <token type> is one
-// of the supported semantic token types, and <modifiers. is a
-// (possibly empty) list of token type modifiers.
-
-// There are 3 coordinate systems for lines and character offsets in lines
-// LSP (what's returned from semanticTokens()):
-// 0-based: the first line is line 0, the first character of a line
-// is character 0, and characters are counted as UTF-16 code points
-// gopls (and Go error messages):
-// 1-based: the first line is line1, the first chararcter of a line
-// is character 0, and characters are counted as bytes
-// internal (as used in marks, and lines:=bytes.Split(buf, '\n'))
-// 0-based: lines and character positions are 1 less than in
-// the gopls coordinate system
-
-type semtok struct {
- app *Application
-}
-
-var colmap *protocol.ColumnMapper
-
-func (c *semtok) Name() string { return "semtok" }
-func (c *semtok) Parent() string { return c.app.Name() }
-func (c *semtok) Usage() string { return "<filename>" }
-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/lsp/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 *source.Options) {
- origOptions(opts)
- opts.SemanticTokens = true
- }
- conn, err := c.app.connect(ctx)
- if err != nil {
- return err
- }
- defer conn.terminate(ctx)
- uri := span.URIFromPath(args[0])
- file := conn.AddFile(ctx, uri)
- if file.err != nil {
- return file.err
- }
-
- buf, err := ioutil.ReadFile(args[0])
- if err != nil {
- return err
- }
- lines := bytes.Split(buf, []byte{'\n'})
- p := &protocol.SemanticTokensRangeParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(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
- }
- fset := token.NewFileSet()
- f, err := parser.ParseFile(fset, args[0], buf, 0)
- if err != nil {
- log.Printf("parsing %s failed %v", args[0], err)
- return err
- }
- tok := fset.File(f.Pos())
- if tok == nil {
- // can't happen; just parsed this file
- return fmt.Errorf("can't find %s in fset", args[0])
- }
- tc := span.NewContentConverter(args[0], buf)
- colmap = &protocol.ColumnMapper{
- URI: span.URI(args[0]),
- Content: buf,
- Converter: tc,
- }
- err = decorate(file.uri.Filename(), resp.Data)
- if err != nil {
- return err
- }
- return nil
-}
-
-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 string, result []uint32) error {
- buf, err := ioutil.ReadFile(file)
- if err != nil {
- return err
- }
- marks := newMarks(result)
- if len(marks) == 0 {
- return nil
- }
- lines := bytes.Split(buf, []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(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 := colmap.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: lsp.SemType(int(d[5*i+3])),
- mods: lsp.SemMods(int(d[5*i+4])),
- }
- ans = append(ans, m)
- }
- return ans
-}
diff --git a/internal/lsp/cmd/serve.go b/internal/lsp/cmd/serve.go
deleted file mode 100644
index f6e268397..000000000
--- a/internal/lsp/cmd/serve.go
+++ /dev/null
@@ -1,130 +0,0 @@
-// 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"
- "flag"
- "fmt"
- "io"
- "log"
- "os"
- "time"
-
- "golang.org/x/tools/internal/fakenet"
- "golang.org/x/tools/internal/jsonrpc2"
- "golang.org/x/tools/internal/lsp/cache"
- "golang.org/x/tools/internal/lsp/debug"
- "golang.org/x/tools/internal/lsp/lsprpc"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/tool"
- errors "golang.org/x/xerrors"
-)
-
-// 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.MonitorMemory(ctx)
- 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 errors.Errorf("creating forwarder: %w", err)
- }
- } else {
- ss = lsprpc.NewStreamServer(cache.New(s.app.options), isDaemon)
- }
-
- var network, addr string
- if s.Address != "" {
- network, addr = lsprpc.ParseAddr(s.Address)
- }
- if s.Port != 0 {
- network = "tcp"
- 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/internal/lsp/cmd/signature.go b/internal/lsp/cmd/signature.go
deleted file mode 100644
index db9484301..000000000
--- a/internal/lsp/cmd/signature.go
+++ /dev/null
@@ -1,87 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "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 "<position>" }
-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 := span.Parse(args[0])
- file := conn.AddFile(ctx, from.URI())
- if file.err != nil {
- return file.err
- }
-
- loc, err := file.mapper.Location(from)
- if err != nil {
- return err
- }
-
- tdpp := protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(from.URI()),
- },
- Position: loc.Range.Start,
- }
- p := protocol.SignatureHelpParams{
- TextDocumentPositionParams: tdpp,
- }
-
- 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)
- if signature.Documentation != "" {
- fmt.Printf("\n%s\n", signature.Documentation)
- }
-
- return nil
-}
diff --git a/internal/lsp/cmd/subcommands.go b/internal/lsp/cmd/subcommands.go
deleted file mode 100644
index deac5c822..000000000
--- a/internal/lsp/cmd/subcommands.go
+++ /dev/null
@@ -1,44 +0,0 @@
-// 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 "<subcommand> [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)
-}
diff --git a/internal/lsp/cmd/suggested_fix.go b/internal/lsp/cmd/suggested_fix.go
deleted file mode 100644
index df14631da..000000000
--- a/internal/lsp/cmd/suggested_fix.go
+++ /dev/null
@@ -1,159 +0,0 @@
-// 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"
- "io/ioutil"
-
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/tool"
- errors "golang.org/x/xerrors"
-)
-
-// suggestedFix implements the fix verb for gopls.
-type suggestedFix struct {
- Diff bool `flag:"d,diff" help:"display diffs instead of rewriting files"`
- Write bool `flag:"w,write" help:"write result to (source) file instead of stdout"`
- All bool `flag:"a,all" help:"apply all fixes, not just preferred fixes"`
-
- app *Application
-}
-
-func (s *suggestedFix) Name() string { return "fix" }
-func (s *suggestedFix) Parent() string { return s.app.Name() }
-func (s *suggestedFix) Usage() string { return "[fix-flags] <filename>" }
-func (s *suggestedFix) ShortHelp() string { return "apply suggested fixes" }
-func (s *suggestedFix) DetailedHelp(f *flag.FlagSet) {
- fmt.Fprintf(f.Output(), `
-Example: apply suggested fixes for this file
- $ gopls fix -w internal/lsp/cmd/check.go
-
-fix-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 (s *suggestedFix) Run(ctx context.Context, args ...string) error {
- if len(args) < 1 {
- return tool.CommandLineErrorf("fix expects at least 1 argument")
- }
- conn, err := s.app.connect(ctx)
- if err != nil {
- return err
- }
- defer conn.terminate(ctx)
-
- from := span.Parse(args[0])
- uri := from.URI()
- file := conn.AddFile(ctx, uri)
- if file.err != nil {
- return file.err
- }
-
- if err := conn.diagnoseFiles(ctx, []span.URI{uri}); err != nil {
- return err
- }
- conn.Client.filesMu.Lock()
- defer conn.Client.filesMu.Unlock()
-
- codeActionKinds := []protocol.CodeActionKind{protocol.QuickFix}
- if len(args) > 1 {
- codeActionKinds = []protocol.CodeActionKind{}
- for _, k := range args[1:] {
- codeActionKinds = append(codeActionKinds, protocol.CodeActionKind(k))
- }
- }
-
- rng, err := file.mapper.Range(from)
- if err != nil {
- return err
- }
- p := protocol.CodeActionParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- Context: protocol.CodeActionContext{
- Only: codeActionKinds,
- Diagnostics: file.diagnostics,
- },
- Range: rng,
- }
- actions, err := conn.CodeAction(ctx, &p)
- if err != nil {
- return errors.Errorf("%v: %v", from, err)
- }
- var edits []protocol.TextEdit
- for _, a := range actions {
- if a.Command != nil {
- return fmt.Errorf("ExecuteCommand is not yet supported on the command line")
- }
- if !a.IsPreferred && !s.All {
- continue
- }
- if !from.HasPosition() {
- for _, c := range a.Edit.DocumentChanges {
- if fileURI(c.TextDocument.URI) == uri {
- edits = append(edits, c.Edits...)
- }
- }
- continue
- }
- // If the span passed in has a position, then we need to find
- // the codeaction that has the same range as the passed in span.
- for _, diag := range a.Diagnostics {
- spn, err := file.mapper.RangeSpan(diag.Range)
- if err != nil {
- continue
- }
- if span.ComparePoint(from.Start(), spn.Start()) == 0 {
- for _, c := range a.Edit.DocumentChanges {
- if fileURI(c.TextDocument.URI) == uri {
- edits = append(edits, c.Edits...)
- }
- }
- break
- }
- }
-
- // If suggested fix is not a diagnostic, still must collect edits.
- if len(a.Diagnostics) == 0 {
- for _, c := range a.Edit.DocumentChanges {
- if fileURI(c.TextDocument.URI) == uri {
- edits = append(edits, c.Edits...)
- }
- }
- }
- }
-
- sedits, err := source.FromProtocolEdits(file.mapper, edits)
- if err != nil {
- return errors.Errorf("%v: %v", edits, err)
- }
- newContent := diff.ApplyEdits(string(file.mapper.Content), sedits)
-
- filename := file.uri.Filename()
- switch {
- case s.Write:
- if len(edits) > 0 {
- ioutil.WriteFile(filename, []byte(newContent), 0644)
- }
- case s.Diff:
- diffs := diff.ToUnified(filename+".orig", filename, string(file.mapper.Content), sedits)
- fmt.Print(diffs)
- default:
- fmt.Print(string(newContent))
- }
- return nil
-}
diff --git a/internal/lsp/cmd/symbols.go b/internal/lsp/cmd/symbols.go
deleted file mode 100644
index b43a6dcd1..000000000
--- a/internal/lsp/cmd/symbols.go
+++ /dev/null
@@ -1,116 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "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 "<file>" }
-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 := span.Parse(args[0])
- p := protocol.DocumentSymbolParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(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/internal/lsp/cmd/test/call_hierarchy.go b/internal/lsp/cmd/test/call_hierarchy.go
deleted file mode 100644
index 38f8ed707..000000000
--- a/internal/lsp/cmd/test/call_hierarchy.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// 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 cmdtest
-
-import (
- "fmt"
- "sort"
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/tests"
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) CallHierarchy(t *testing.T, spn span.Span, expectedCalls *tests.CallHierarchyResult) {
- collectCallSpansString := func(callItems []protocol.CallHierarchyItem) string {
- var callSpans []string
- for _, call := range callItems {
- mapper, err := r.data.Mapper(call.URI.SpanURI())
- if err != nil {
- t.Fatal(err)
- }
- callSpan, err := mapper.Span(protocol.Location{URI: call.URI, Range: call.Range})
- if err != nil {
- t.Fatal(err)
- }
- callSpans = append(callSpans, fmt.Sprint(callSpan))
- }
- // to make tests deterministic
- sort.Strings(callSpans)
- return r.Normalize(strings.Join(callSpans, "\n"))
- }
-
- expectIn, expectOut := collectCallSpansString(expectedCalls.IncomingCalls), collectCallSpansString(expectedCalls.OutgoingCalls)
- expectIdent := r.Normalize(fmt.Sprint(spn))
-
- uri := spn.URI()
- filename := uri.Filename()
- target := filename + fmt.Sprintf(":%v:%v", spn.Start().Line(), spn.Start().Column())
-
- got, stderr := r.NormalizeGoplsCmd(t, "call_hierarchy", target)
- if stderr != "" {
- t.Fatalf("call_hierarchy failed for %s: %s", target, stderr)
- }
-
- gotIn, gotIdent, gotOut := cleanCallHierarchyCmdResult(got)
- if expectIn != gotIn {
- t.Errorf("incoming calls call_hierarchy failed for %s expected:\n%s\ngot:\n%s", target, expectIn, gotIn)
- }
- if expectIdent != gotIdent {
- t.Errorf("call_hierarchy failed for %s expected:\n%s\ngot:\n%s", target, expectIdent, gotIdent)
- }
- if expectOut != gotOut {
- t.Errorf("outgoing calls call_hierarchy failed for %s expected:\n%s\ngot:\n%s", target, expectOut, gotOut)
- }
-
-}
-
-// parses function URI and Range from call hierarchy cmd output to
-// incoming, identifier and outgoing calls (returned in that order)
-// ex: "identifier: function d at .../callhierarchy/callhierarchy.go:19:6-7" -> ".../callhierarchy/callhierarchy.go:19:6-7"
-func cleanCallHierarchyCmdResult(output string) (incoming, ident, outgoing string) {
- var incomingCalls, outgoingCalls []string
- for _, out := range strings.Split(output, "\n") {
- if out == "" {
- continue
- }
-
- callLocation := out[strings.LastIndex(out, " ")+1:]
- if strings.HasPrefix(out, "caller") {
- incomingCalls = append(incomingCalls, callLocation)
- } else if strings.HasPrefix(out, "callee") {
- outgoingCalls = append(outgoingCalls, callLocation)
- } else {
- ident = callLocation
- }
- }
- sort.Strings(incomingCalls)
- sort.Strings(outgoingCalls)
- incoming, outgoing = strings.Join(incomingCalls, "\n"), strings.Join(outgoingCalls, "\n")
- return
-}
diff --git a/internal/lsp/cmd/test/check.go b/internal/lsp/cmd/test/check.go
deleted file mode 100644
index f0e6d8fef..000000000
--- a/internal/lsp/cmd/test/check.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// 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 cmdtest
-
-import (
- "fmt"
- "io/ioutil"
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []*source.Diagnostic) {
- if len(want) == 1 && want[0].Message == "" {
- return
- }
- fname := uri.Filename()
- out, _ := r.runGoplsCmd(t, "check", fname)
- // parse got into a collection of reports
- got := map[string]struct{}{}
- for _, l := range strings.Split(out, "\n") {
- if len(l) == 0 {
- continue
- }
- // parse and reprint to normalize the span
- bits := strings.SplitN(l, ": ", 2)
- if len(bits) == 2 {
- spn := span.Parse(strings.TrimSpace(bits[0]))
- spn = span.New(spn.URI(), spn.Start(), span.Point{})
- data, err := ioutil.ReadFile(fname)
- if err != nil {
- t.Fatal(err)
- }
- converter := span.NewContentConverter(fname, data)
- s, err := spn.WithPosition(converter)
- if err != nil {
- t.Fatal(err)
- }
- l = fmt.Sprintf("%s: %s", s, strings.TrimSpace(bits[1]))
- }
- got[r.NormalizePrefix(l)] = struct{}{}
- }
- for _, diag := range want {
- expect := fmt.Sprintf("%v:%v:%v: %v", uri.Filename(), diag.Range.Start.Line+1, diag.Range.Start.Character+1, diag.Message)
- if diag.Range.Start.Character == 0 {
- expect = fmt.Sprintf("%v:%v: %v", uri.Filename(), diag.Range.Start.Line+1, diag.Message)
- }
- expect = r.NormalizePrefix(expect)
- _, found := got[expect]
- if !found {
- t.Errorf("missing diagnostic %q, %v", expect, got)
- } else {
- delete(got, expect)
- }
- }
- for extra := range got {
- t.Errorf("extra diagnostic %q", extra)
- }
-}
diff --git a/internal/lsp/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go
deleted file mode 100644
index 312f7b8b4..000000000
--- a/internal/lsp/cmd/test/cmdtest.go
+++ /dev/null
@@ -1,169 +0,0 @@
-// 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 cmdtest contains the test suite for the command line behavior of gopls.
-package cmdtest
-
-import (
- "bytes"
- "context"
- "flag"
- "fmt"
- "io"
- "os"
- "sync"
- "testing"
-
- "golang.org/x/tools/internal/jsonrpc2/servertest"
- "golang.org/x/tools/internal/lsp/cache"
- "golang.org/x/tools/internal/lsp/cmd"
- "golang.org/x/tools/internal/lsp/debug"
- "golang.org/x/tools/internal/lsp/lsprpc"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/tests"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/tool"
-)
-
-type runner struct {
- data *tests.Data
- ctx context.Context
- options func(*source.Options)
- normalizers []tests.Normalizer
- remote string
-}
-
-func TestCommandLine(t *testing.T, testdata string, options func(*source.Options)) {
- // On Android, the testdata directory is not copied to the runner.
- if stat, err := os.Stat(testdata); err != nil || !stat.IsDir() {
- t.Skip("testdata directory not present")
- }
- tests.RunTests(t, testdata, false, func(t *testing.T, datum *tests.Data) {
- ctx := tests.Context(t)
- ts := NewTestServer(ctx, options)
- tests.Run(t, NewRunner(datum, ctx, ts.Addr, options), datum)
- cmd.CloseTestConnections(ctx)
- })
-}
-
-func NewTestServer(ctx context.Context, options func(*source.Options)) *servertest.TCPServer {
- ctx = debug.WithInstance(ctx, "", "")
- cache := cache.New(options)
- ss := lsprpc.NewStreamServer(cache, false)
- return servertest.NewTCPServer(ctx, ss, nil)
-}
-
-func NewRunner(data *tests.Data, ctx context.Context, remote string, options func(*source.Options)) *runner {
- return &runner{
- data: data,
- ctx: ctx,
- options: options,
- normalizers: tests.CollectNormalizers(data.Exported),
- remote: remote,
- }
-}
-
-func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) {
- //TODO: add command line completions tests when it works
-}
-
-func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- //TODO: add command line completions tests when it works
-}
-
-func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) {
- //TODO: add command line completions tests when it works
-}
-
-func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- //TODO: add command line completions tests when it works
-}
-
-func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- //TODO: add command line completions tests when it works
-}
-
-func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- //TODO: add command line completions tests when it works
-}
-
-func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- //TODO: add command line completions tests when it works
-}
-
-func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- //TODO: add command line completions tests when it works
-}
-
-func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span) {
- //TODO: function extraction not supported on command line
-}
-
-func (r *runner) MethodExtraction(t *testing.T, start span.Span, end span.Span) {
- //TODO: function extraction not supported on command line
-}
-
-func (r *runner) AddImport(t *testing.T, uri span.URI, expectedImport string) {
- //TODO: import addition not supported on command line
-}
-
-func (r *runner) Hover(t *testing.T, spn span.Span, info string) {
- //TODO: hovering not supported on command line
-}
-
-func (r *runner) runGoplsCmd(t testing.TB, args ...string) (string, string) {
- rStdout, wStdout, err := os.Pipe()
- if err != nil {
- t.Fatal(err)
- }
- oldStdout := os.Stdout
- rStderr, wStderr, err := os.Pipe()
- if err != nil {
- t.Fatal(err)
- }
- oldStderr := os.Stderr
- stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
- var wg sync.WaitGroup
- wg.Add(2)
- go func() {
- io.Copy(stdout, rStdout)
- wg.Done()
- }()
- go func() {
- io.Copy(stderr, rStderr)
- wg.Done()
- }()
- os.Stdout, os.Stderr = wStdout, wStderr
- app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env, r.options)
- remote := r.remote
- s := flag.NewFlagSet(app.Name(), flag.ExitOnError)
- err = tool.Run(tests.Context(t), s,
- app,
- append([]string{fmt.Sprintf("-remote=internal@%s", remote)}, args...))
- if err != nil {
- fmt.Fprint(os.Stderr, err)
- }
- wStdout.Close()
- wStderr.Close()
- wg.Wait()
- os.Stdout, os.Stderr = oldStdout, oldStderr
- rStdout.Close()
- rStderr.Close()
- return stdout.String(), stderr.String()
-}
-
-// NormalizeGoplsCmd runs the gopls command and normalizes its output.
-func (r *runner) NormalizeGoplsCmd(t testing.TB, args ...string) (string, string) {
- stdout, stderr := r.runGoplsCmd(t, args...)
- return r.Normalize(stdout), r.Normalize(stderr)
-}
-
-func (r *runner) Normalize(s string) string {
- return tests.Normalize(s, r.normalizers)
-}
-
-func (r *runner) NormalizePrefix(s string) string {
- return tests.NormalizePrefix(s, r.normalizers)
-}
diff --git a/internal/lsp/cmd/test/definition.go b/internal/lsp/cmd/test/definition.go
deleted file mode 100644
index c82d9a6c1..000000000
--- a/internal/lsp/cmd/test/definition.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// 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 cmdtest
-
-import (
- "fmt"
- "runtime"
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/diff/myers"
- "golang.org/x/tools/internal/lsp/tests"
- "golang.org/x/tools/internal/span"
-)
-
-type godefMode int
-
-const (
- plainGodef = godefMode(1 << iota)
- jsonGoDef
-)
-
-var godefModes = []godefMode{
- plainGodef,
- jsonGoDef,
-}
-
-func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) {
- if d.IsType || d.OnlyHover {
- // TODO: support type definition, hover queries
- return
- }
- d.Src = span.New(d.Src.URI(), span.NewPoint(0, 0, d.Src.Start().Offset()), span.Point{})
- for _, mode := range godefModes {
- args := []string{"definition", "-markdown"}
- tag := d.Name + "-definition"
- if mode&jsonGoDef != 0 {
- tag += "-json"
- args = append(args, "-json")
- }
- uri := d.Src.URI()
- args = append(args, fmt.Sprint(d.Src))
- got, _ := r.NormalizeGoplsCmd(t, args...)
- if mode&jsonGoDef != 0 && runtime.GOOS == "windows" {
- got = strings.Replace(got, "file:///", "file://", -1)
- }
- expect := strings.TrimSpace(string(r.data.Golden(tag, uri.Filename(), func() ([]byte, error) {
- return []byte(got), nil
- })))
- if expect != "" && !strings.HasPrefix(got, expect) {
- d, err := myers.ComputeEdits("", expect, got)
- if err != nil {
- t.Fatal(err)
- }
- t.Errorf("definition %v failed with %#v\n%s", tag, args, diff.ToUnified("expect", "got", expect, d))
- }
- }
-}
diff --git a/internal/lsp/cmd/test/folding_range.go b/internal/lsp/cmd/test/folding_range.go
deleted file mode 100644
index 4478687b5..000000000
--- a/internal/lsp/cmd/test/folding_range.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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 cmdtest
-
-import (
- "testing"
-
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) FoldingRanges(t *testing.T, spn span.Span) {
- goldenTag := "foldingRange-cmd"
- uri := spn.URI()
- filename := uri.Filename()
- got, _ := r.NormalizeGoplsCmd(t, "folding_ranges", filename)
- expect := string(r.data.Golden(goldenTag, filename, func() ([]byte, error) {
- return []byte(got), nil
- }))
-
- if expect != got {
- t.Errorf("folding_ranges failed failed for %s expected:\n%s\ngot:\n%s", filename, expect, got)
- }
-}
diff --git a/internal/lsp/cmd/test/format.go b/internal/lsp/cmd/test/format.go
deleted file mode 100644
index 77eedd440..000000000
--- a/internal/lsp/cmd/test/format.go
+++ /dev/null
@@ -1,86 +0,0 @@
-// 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 cmdtest
-
-import (
- "bytes"
- exec "golang.org/x/sys/execabs"
- "io/ioutil"
- "os"
- "regexp"
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/testenv"
-)
-
-func (r *runner) Format(t *testing.T, spn span.Span) {
- tag := "gofmt"
- uri := spn.URI()
- filename := uri.Filename()
- expect := string(r.data.Golden(tag, filename, func() ([]byte, error) {
- cmd := exec.Command("gofmt", filename)
- contents, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files
- contents = []byte(r.Normalize(fixFileHeader(string(contents))))
- return contents, nil
- }))
- if expect == "" {
- //TODO: our error handling differs, for now just skip unformattable files
- t.Skip("Unformattable file")
- }
- got, _ := r.NormalizeGoplsCmd(t, "format", filename)
- if expect != got {
- t.Errorf("format failed for %s expected:\n%s\ngot:\n%s", filename, expect, got)
- }
- // now check we can build a valid unified diff
- unified, _ := r.NormalizeGoplsCmd(t, "format", "-d", filename)
- checkUnified(t, filename, expect, unified)
-}
-
-var unifiedHeader = regexp.MustCompile(`^diff -u.*\n(---\s+\S+\.go\.orig)\s+[\d-:. ]+(\n\+\+\+\s+\S+\.go)\s+[\d-:. ]+(\n@@)`)
-
-func fixFileHeader(s string) string {
- match := unifiedHeader.FindStringSubmatch(s)
- if match == nil {
- return s
- }
- return strings.Join(append(match[1:], s[len(match[0]):]), "")
-}
-
-func checkUnified(t *testing.T, filename string, expect string, patch string) {
- testenv.NeedsTool(t, "patch")
- if strings.Count(patch, "\n+++ ") > 1 {
- // TODO(golang/go/#34580)
- t.Skip("multi-file patch tests not supported yet")
- }
- applied := ""
- if patch == "" {
- applied = expect
- } else {
- temp, err := ioutil.TempFile("", "applied")
- if err != nil {
- t.Fatal(err)
- }
- temp.Close()
- defer os.Remove(temp.Name())
- cmd := exec.Command("patch", "-u", "-p0", "-o", temp.Name(), filename)
- cmd.Stdin = bytes.NewBuffer([]byte(patch))
- msg, err := cmd.CombinedOutput()
- if err != nil {
- t.Errorf("failed applying patch to %s: %v\ngot:\n%s\npatch:\n%s", filename, err, msg, patch)
- return
- }
- out, err := ioutil.ReadFile(temp.Name())
- if err != nil {
- t.Errorf("failed reading patched output for %s: %v\n", filename, err)
- return
- }
- applied = string(out)
- }
- if expect != applied {
- t.Errorf("apply unified gave wrong result for %s expected:\n%s\ngot:\n%s\npatch:\n%s", filename, expect, applied, patch)
- }
-}
diff --git a/internal/lsp/cmd/test/highlight.go b/internal/lsp/cmd/test/highlight.go
deleted file mode 100644
index 99e8b2c3f..000000000
--- a/internal/lsp/cmd/test/highlight.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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 cmdtest
-
-import (
- "testing"
-
- "fmt"
-
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) Highlight(t *testing.T, spn span.Span, spans []span.Span) {
- var expect string
- for _, l := range spans {
- expect += fmt.Sprintln(l)
- }
- expect = r.Normalize(expect)
-
- uri := spn.URI()
- filename := uri.Filename()
- target := filename + ":" + fmt.Sprint(spn.Start().Line()) + ":" + fmt.Sprint(spn.Start().Column())
- got, _ := r.NormalizeGoplsCmd(t, "highlight", target)
- if expect != got {
- t.Errorf("highlight failed for %s expected:\n%s\ngot:\n%s", target, expect, got)
- }
-}
diff --git a/internal/lsp/cmd/test/implementation.go b/internal/lsp/cmd/test/implementation.go
deleted file mode 100644
index 189452466..000000000
--- a/internal/lsp/cmd/test/implementation.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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 cmdtest
-
-import (
- "fmt"
- "sort"
- "testing"
-
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) Implementation(t *testing.T, spn span.Span, imps []span.Span) {
- var itemStrings []string
- for _, i := range imps {
- itemStrings = append(itemStrings, fmt.Sprint(i))
- }
- sort.Strings(itemStrings)
- var expect string
- for _, i := range itemStrings {
- expect += i + "\n"
- }
- expect = r.Normalize(expect)
-
- uri := spn.URI()
- filename := uri.Filename()
- target := filename + fmt.Sprintf(":%v:%v", spn.Start().Line(), spn.Start().Column())
-
- got, stderr := r.NormalizeGoplsCmd(t, "implementation", target)
- if stderr != "" {
- t.Errorf("implementation failed for %s: %s", target, stderr)
- } else if expect != got {
- t.Errorf("implementation failed for %s expected:\n%s\ngot:\n%s", target, expect, got)
- }
-}
diff --git a/internal/lsp/cmd/test/imports.go b/internal/lsp/cmd/test/imports.go
deleted file mode 100644
index ce8aee55d..000000000
--- a/internal/lsp/cmd/test/imports.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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 cmdtest
-
-import (
- "testing"
-
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/diff/myers"
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) Import(t *testing.T, spn span.Span) {
- uri := spn.URI()
- filename := uri.Filename()
- got, _ := r.NormalizeGoplsCmd(t, "imports", filename)
- want := string(r.data.Golden("goimports", filename, func() ([]byte, error) {
- return []byte(got), nil
- }))
- if want != got {
- d, err := myers.ComputeEdits(uri, want, got)
- if err != nil {
- t.Fatal(err)
- }
- t.Errorf("imports failed for %s, expected:\n%s", filename, diff.ToUnified("want", "got", want, d))
- }
-}
diff --git a/internal/lsp/cmd/test/links.go b/internal/lsp/cmd/test/links.go
deleted file mode 100644
index 88df76832..000000000
--- a/internal/lsp/cmd/test/links.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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 cmdtest
-
-import (
- "encoding/json"
- "testing"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/tests"
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
- m, err := r.data.Mapper(uri)
- if err != nil {
- t.Fatal(err)
- }
- out, _ := r.NormalizeGoplsCmd(t, "links", "-json", uri.Filename())
- var got []protocol.DocumentLink
- err = json.Unmarshal([]byte(out), &got)
- if err != nil {
- t.Fatal(err)
- }
- if diff := tests.DiffLinks(m, wantLinks, got); diff != "" {
- t.Error(diff)
- }
-}
diff --git a/internal/lsp/cmd/test/prepare_rename.go b/internal/lsp/cmd/test/prepare_rename.go
deleted file mode 100644
index b5359e57b..000000000
--- a/internal/lsp/cmd/test/prepare_rename.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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 cmdtest
-
-import (
- "fmt"
- "testing"
-
- "golang.org/x/tools/internal/lsp/cmd"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) {
- m, err := r.data.Mapper(src.URI())
- if err != nil {
- t.Errorf("prepare_rename failed: %v", err)
- }
-
- var (
- target = fmt.Sprintf("%v", src)
- args = []string{"prepare_rename", target}
- stdOut, stdErr = r.NormalizeGoplsCmd(t, args...)
- expect string
- )
-
- if want.Text == "" {
- if stdErr != "" && stdErr != cmd.ErrInvalidRenamePosition.Error() {
- t.Errorf("prepare_rename failed for %s,\nexpected:\n`%v`\ngot:\n`%v`", target, expect, stdErr)
- }
- return
- }
-
- ws, err := m.Span(protocol.Location{Range: want.Range})
- if err != nil {
- t.Errorf("prepare_rename failed: %v", err)
- }
-
- expect = r.Normalize(fmt.Sprintln(ws))
- if expect != stdOut {
- t.Errorf("prepare_rename failed for %s expected:\n`%s`\ngot:\n`%s`\n", target, expect, stdOut)
- }
-}
diff --git a/internal/lsp/cmd/test/references.go b/internal/lsp/cmd/test/references.go
deleted file mode 100644
index 66d0d0662..000000000
--- a/internal/lsp/cmd/test/references.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// 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 cmdtest
-
-import (
- "fmt"
- "sort"
- "testing"
-
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) References(t *testing.T, spn span.Span, itemList []span.Span) {
- for _, includeDeclaration := range []bool{true, false} {
- t.Run(fmt.Sprintf("refs-declaration-%v", includeDeclaration), func(t *testing.T) {
- var itemStrings []string
- for i, s := range itemList {
- // We don't want the first result if we aren't including the declaration.
- if i == 0 && !includeDeclaration {
- continue
- }
- itemStrings = append(itemStrings, fmt.Sprint(s))
- }
- sort.Strings(itemStrings)
- var expect string
- for _, s := range itemStrings {
- expect += s + "\n"
- }
- expect = r.Normalize(expect)
-
- uri := spn.URI()
- filename := uri.Filename()
- target := filename + fmt.Sprintf(":%v:%v", spn.Start().Line(), spn.Start().Column())
- args := []string{"references"}
- if includeDeclaration {
- args = append(args, "-d")
- }
- args = append(args, target)
- got, stderr := r.NormalizeGoplsCmd(t, args...)
- if stderr != "" {
- t.Errorf("references failed for %s: %s", target, stderr)
- } else if expect != got {
- t.Errorf("references failed for %s expected:\n%s\ngot:\n%s", target, expect, got)
- }
- })
- }
-}
diff --git a/internal/lsp/cmd/test/rename.go b/internal/lsp/cmd/test/rename.go
deleted file mode 100644
index 0fe2d1e18..000000000
--- a/internal/lsp/cmd/test/rename.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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 cmdtest
-
-import (
- "fmt"
- "testing"
-
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) Rename(t *testing.T, spn span.Span, newText string) {
- filename := spn.URI().Filename()
- goldenTag := newText + "-rename"
- loc := fmt.Sprintf("%v", spn)
- got, err := r.NormalizeGoplsCmd(t, "rename", loc, newText)
- got += err
- expect := string(r.data.Golden(goldenTag, filename, func() ([]byte, error) {
- return []byte(got), nil
- }))
- if expect != got {
- t.Errorf("rename failed with %v %v\nexpected:\n%s\ngot:\n%s", loc, newText, expect, got)
- }
- // now check we can build a valid unified diff
- unified, _ := r.NormalizeGoplsCmd(t, "rename", "-d", loc, newText)
- checkUnified(t, filename, expect, unified)
-}
diff --git a/internal/lsp/cmd/test/semanticdriver.go b/internal/lsp/cmd/test/semanticdriver.go
deleted file mode 100644
index 80dc61e3d..000000000
--- a/internal/lsp/cmd/test/semanticdriver.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// 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 cmdtest
-
-import (
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) SemanticTokens(t *testing.T, spn span.Span) {
- uri := spn.URI()
- filename := uri.Filename()
- got, stderr := r.NormalizeGoplsCmd(t, "semtok", filename)
- if stderr != "" {
- t.Fatalf("%s: %q", filename, stderr)
- }
- want := string(r.data.Golden("semantic", filename, func() ([]byte, error) {
- return []byte(got), nil
- }))
- if want != got {
- lwant := strings.Split(want, "\n")
- lgot := strings.Split(got, "\n")
- t.Errorf("want(%d-%d) != got(%d-%d) for %s", len(want), len(lwant), len(got), len(lgot), r.Normalize(filename))
- for i := 0; i < len(lwant) && i < len(lgot); i++ {
- if lwant[i] != lgot[i] {
- t.Errorf("line %d:\nwant%q\ngot %q\n", i, lwant[i], lgot[i])
- }
- }
- }
-}
diff --git a/internal/lsp/cmd/test/signature.go b/internal/lsp/cmd/test/signature.go
deleted file mode 100644
index f6bdaebf3..000000000
--- a/internal/lsp/cmd/test/signature.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// 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 cmdtest
-
-import (
- "fmt"
- "testing"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/tests"
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.SignatureHelp) {
- uri := spn.URI()
- filename := uri.Filename()
- target := filename + fmt.Sprintf(":%v:%v", spn.Start().Line(), spn.Start().Column())
- got, _ := r.NormalizeGoplsCmd(t, "signature", target)
- if want == nil {
- if got != "" {
- t.Fatalf("want nil, but got %s", got)
- }
- return
- }
- goldenTag := want.Signatures[0].Label + "-signature"
- expect := string(r.data.Golden(goldenTag, filename, func() ([]byte, error) {
- return []byte(got), nil
- }))
- if tests.NormalizeAny(expect) != tests.NormalizeAny(got) {
- t.Errorf("signature failed for %s expected:\n%q\ngot:\n%q'", filename, expect, got)
- }
-}
diff --git a/internal/lsp/cmd/test/suggested_fix.go b/internal/lsp/cmd/test/suggested_fix.go
deleted file mode 100644
index c819e0517..000000000
--- a/internal/lsp/cmd/test/suggested_fix.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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 cmdtest
-
-import (
- "fmt"
- "testing"
-
- "golang.org/x/tools/internal/lsp/tests"
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) SuggestedFix(t *testing.T, spn span.Span, actionKinds []string, expectedActions int) {
- uri := spn.URI()
- filename := uri.Filename()
- args := []string{"fix", "-a", fmt.Sprintf("%s", spn)}
- for _, kind := range actionKinds {
- if kind == "refactor.rewrite" {
- t.Skip("refactor.rewrite is not yet supported on the command line")
- }
- }
- args = append(args, actionKinds...)
- got, stderr := r.NormalizeGoplsCmd(t, args...)
- if stderr == "ExecuteCommand is not yet supported on the command line" {
- return // don't skip to keep the summary counts correct
- }
- want := string(r.data.Golden("suggestedfix_"+tests.SpanName(spn), filename, func() ([]byte, error) {
- return []byte(got), nil
- }))
- if want != got {
- t.Errorf("suggested fixes failed for %s:\n%s", filename, tests.Diff(t, want, got))
- }
-}
diff --git a/internal/lsp/cmd/test/symbols.go b/internal/lsp/cmd/test/symbols.go
deleted file mode 100644
index 055be0308..000000000
--- a/internal/lsp/cmd/test/symbols.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// 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 cmdtest
-
-import (
- "testing"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) {
- filename := uri.Filename()
- got, _ := r.NormalizeGoplsCmd(t, "symbols", filename)
- expect := string(r.data.Golden("symbols", filename, func() ([]byte, error) {
- return []byte(got), nil
- }))
- if expect != got {
- t.Errorf("symbols failed for %s expected:\n%s\ngot:\n%s", filename, expect, got)
- }
-}
diff --git a/internal/lsp/cmd/test/workspace_symbol.go b/internal/lsp/cmd/test/workspace_symbol.go
deleted file mode 100644
index ce965f03a..000000000
--- a/internal/lsp/cmd/test/workspace_symbol.go
+++ /dev/null
@@ -1,53 +0,0 @@
-// 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 cmdtest
-
-import (
- "fmt"
- "path/filepath"
- "sort"
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/tests"
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) WorkspaceSymbols(t *testing.T, uri span.URI, query string, typ tests.WorkspaceSymbolsTestType) {
- var matcher string
- switch typ {
- case tests.WorkspaceSymbolsFuzzy:
- matcher = "fuzzy"
- case tests.WorkspaceSymbolsCaseSensitive:
- matcher = "caseSensitive"
- case tests.WorkspaceSymbolsDefault:
- matcher = "caseInsensitive"
- }
- r.runWorkspaceSymbols(t, uri, matcher, query)
-}
-
-func (r *runner) runWorkspaceSymbols(t *testing.T, uri span.URI, matcher, query string) {
- t.Helper()
-
- out, _ := r.runGoplsCmd(t, "workspace_symbol", "-matcher", matcher, query)
- var filtered []string
- dir := filepath.Dir(uri.Filename())
- for _, line := range strings.Split(out, "\n") {
- if source.InDir(dir, line) {
- filtered = append(filtered, filepath.ToSlash(line))
- }
- }
- sort.Strings(filtered)
- got := r.Normalize(strings.Join(filtered, "\n") + "\n")
-
- expect := string(r.data.Golden(fmt.Sprintf("workspace_symbol-%s-%s", strings.ToLower(string(matcher)), query), uri.Filename(), func() ([]byte, error) {
- return []byte(got), nil
- }))
-
- if expect != got {
- t.Errorf("workspace_symbol failed for %s:\n%s", query, tests.Diff(t, expect, got))
- }
-}
diff --git a/internal/lsp/cmd/usage/api-json.hlp b/internal/lsp/cmd/usage/api-json.hlp
deleted file mode 100644
index cb9fbfbea..000000000
--- a/internal/lsp/cmd/usage/api-json.hlp
+++ /dev/null
@@ -1,4 +0,0 @@
-print json describing gopls API
-
-Usage:
- gopls [flags] api-json
diff --git a/internal/lsp/cmd/usage/bug.hlp b/internal/lsp/cmd/usage/bug.hlp
deleted file mode 100644
index 772d54d5f..000000000
--- a/internal/lsp/cmd/usage/bug.hlp
+++ /dev/null
@@ -1,4 +0,0 @@
-report a bug in gopls
-
-Usage:
- gopls [flags] bug
diff --git a/internal/lsp/cmd/usage/call_hierarchy.hlp b/internal/lsp/cmd/usage/call_hierarchy.hlp
deleted file mode 100644
index 07fccc828..000000000
--- a/internal/lsp/cmd/usage/call_hierarchy.hlp
+++ /dev/null
@@ -1,10 +0,0 @@
-display selected identifier's call hierarchy
-
-Usage:
- gopls [flags] call_hierarchy <position>
-
-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/internal/lsp/cmd/usage/check.hlp b/internal/lsp/cmd/usage/check.hlp
deleted file mode 100644
index ba89588d5..000000000
--- a/internal/lsp/cmd/usage/check.hlp
+++ /dev/null
@@ -1,8 +0,0 @@
-show diagnostic results for the specified file
-
-Usage:
- gopls [flags] check <filename>
-
-Example: show the diagnostic results of this file:
-
- $ gopls check internal/lsp/cmd/check.go
diff --git a/internal/lsp/cmd/usage/definition.hlp b/internal/lsp/cmd/usage/definition.hlp
deleted file mode 100644
index 500e6c9a4..000000000
--- a/internal/lsp/cmd/usage/definition.hlp
+++ /dev/null
@@ -1,15 +0,0 @@
-show declaration of selected identifier
-
-Usage:
- gopls [flags] definition [definition-flags] <position>
-
-Example: show the definition of the identifier at syntax at offset 44 in this file (flag.FlagSet):
-
- $ gopls definition internal/lsp/cmd/definition.go:44:47
- $ gopls definition internal/lsp/cmd/definition.go:#1270
-
-definition-flags:
- -json
- emit output in JSON format
- -markdown
- support markdown in responses
diff --git a/internal/lsp/cmd/usage/fix.hlp b/internal/lsp/cmd/usage/fix.hlp
deleted file mode 100644
index 4789a6c5b..000000000
--- a/internal/lsp/cmd/usage/fix.hlp
+++ /dev/null
@@ -1,15 +0,0 @@
-apply suggested fixes
-
-Usage:
- gopls [flags] fix [fix-flags] <filename>
-
-Example: apply suggested fixes for this file
- $ gopls fix -w internal/lsp/cmd/check.go
-
-fix-flags:
- -a,-all
- apply all fixes, not just preferred fixes
- -d,-diff
- display diffs instead of rewriting files
- -w,-write
- write result to (source) file instead of stdout
diff --git a/internal/lsp/cmd/usage/folding_ranges.hlp b/internal/lsp/cmd/usage/folding_ranges.hlp
deleted file mode 100644
index 4af2da615..000000000
--- a/internal/lsp/cmd/usage/folding_ranges.hlp
+++ /dev/null
@@ -1,8 +0,0 @@
-display selected file's folding ranges
-
-Usage:
- gopls [flags] folding_ranges <file>
-
-Example:
-
- $ gopls folding_ranges helper/helper.go
diff --git a/internal/lsp/cmd/usage/format.hlp b/internal/lsp/cmd/usage/format.hlp
deleted file mode 100644
index 7ef0bbe43..000000000
--- a/internal/lsp/cmd/usage/format.hlp
+++ /dev/null
@@ -1,18 +0,0 @@
-format the code according to the go standard
-
-Usage:
- gopls [flags] format [format-flags] <filerange>
-
-The arguments supplied may be simple file names, or ranges within files.
-
-Example: reformat this file:
-
- $ gopls format -w internal/lsp/cmd/check.go
-
-format-flags:
- -d,-diff
- display diffs instead of rewriting files
- -l,-list
- list files whose formatting differs from gofmt's
- -w,-write
- write result to (source) file instead of stdout
diff --git a/internal/lsp/cmd/usage/highlight.hlp b/internal/lsp/cmd/usage/highlight.hlp
deleted file mode 100644
index e128eb7de..000000000
--- a/internal/lsp/cmd/usage/highlight.hlp
+++ /dev/null
@@ -1,10 +0,0 @@
-display selected identifier's highlights
-
-Usage:
- gopls [flags] highlight <position>
-
-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/internal/lsp/cmd/usage/implementation.hlp b/internal/lsp/cmd/usage/implementation.hlp
deleted file mode 100644
index 09414f190..000000000
--- a/internal/lsp/cmd/usage/implementation.hlp
+++ /dev/null
@@ -1,10 +0,0 @@
-display selected identifier's implementation
-
-Usage:
- gopls [flags] implementation <position>
-
-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/internal/lsp/cmd/usage/imports.hlp b/internal/lsp/cmd/usage/imports.hlp
deleted file mode 100644
index 295f4daa2..000000000
--- a/internal/lsp/cmd/usage/imports.hlp
+++ /dev/null
@@ -1,14 +0,0 @@
-updates import statements
-
-Usage:
- gopls [flags] imports [imports-flags] <filename>
-
-Example: update imports statements in a file:
-
- $ gopls imports -w internal/lsp/cmd/check.go
-
-imports-flags:
- -d,-diff
- display diffs instead of rewriting files
- -w,-write
- write result to (source) file instead of stdout
diff --git a/internal/lsp/cmd/usage/inspect.hlp b/internal/lsp/cmd/usage/inspect.hlp
deleted file mode 100644
index 3d0a0f3c4..000000000
--- a/internal/lsp/cmd/usage/inspect.hlp
+++ /dev/null
@@ -1,8 +0,0 @@
-interact with the gopls daemon (deprecated: use 'remote')
-
-Usage:
- gopls [flags] inspect <subcommand> [arg]...
-
-Subcommand:
- sessions print information about current gopls sessions
- debug start the debug server
diff --git a/internal/lsp/cmd/usage/licenses.hlp b/internal/lsp/cmd/usage/licenses.hlp
deleted file mode 100644
index ab60ebc2f..000000000
--- a/internal/lsp/cmd/usage/licenses.hlp
+++ /dev/null
@@ -1,4 +0,0 @@
-print licenses of included software
-
-Usage:
- gopls [flags] licenses
diff --git a/internal/lsp/cmd/usage/links.hlp b/internal/lsp/cmd/usage/links.hlp
deleted file mode 100644
index 7f7612ce7..000000000
--- a/internal/lsp/cmd/usage/links.hlp
+++ /dev/null
@@ -1,12 +0,0 @@
-list links in a file
-
-Usage:
- gopls [flags] links [links-flags] <filename>
-
-Example: list links contained within a file:
-
- $ gopls links internal/lsp/cmd/check.go
-
-links-flags:
- -json
- emit document links in JSON format
diff --git a/internal/lsp/cmd/usage/prepare_rename.hlp b/internal/lsp/cmd/usage/prepare_rename.hlp
deleted file mode 100644
index 7f8a6f32d..000000000
--- a/internal/lsp/cmd/usage/prepare_rename.hlp
+++ /dev/null
@@ -1,10 +0,0 @@
-test validity of a rename operation at location
-
-Usage:
- gopls [flags] prepare_rename <position>
-
-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/internal/lsp/cmd/usage/references.hlp b/internal/lsp/cmd/usage/references.hlp
deleted file mode 100644
index c55ef0337..000000000
--- a/internal/lsp/cmd/usage/references.hlp
+++ /dev/null
@@ -1,14 +0,0 @@
-display selected identifier's references
-
-Usage:
- gopls [flags] references [references-flags] <position>
-
-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/internal/lsp/cmd/usage/remote.hlp b/internal/lsp/cmd/usage/remote.hlp
deleted file mode 100644
index dd6034f46..000000000
--- a/internal/lsp/cmd/usage/remote.hlp
+++ /dev/null
@@ -1,8 +0,0 @@
-interact with the gopls daemon
-
-Usage:
- gopls [flags] remote <subcommand> [arg]...
-
-Subcommand:
- sessions print information about current gopls sessions
- debug start the debug server
diff --git a/internal/lsp/cmd/usage/rename.hlp b/internal/lsp/cmd/usage/rename.hlp
deleted file mode 100644
index ae58cbf60..000000000
--- a/internal/lsp/cmd/usage/rename.hlp
+++ /dev/null
@@ -1,18 +0,0 @@
-rename selected identifier
-
-Usage:
- gopls [flags] rename [rename-flags] <position> <name>
-
-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 rewriting files
- -preserve
- preserve original files
- -w,-write
- write result to (source) file instead of stdout
diff --git a/internal/lsp/cmd/usage/semtok.hlp b/internal/lsp/cmd/usage/semtok.hlp
deleted file mode 100644
index 459ed596c..000000000
--- a/internal/lsp/cmd/usage/semtok.hlp
+++ /dev/null
@@ -1,8 +0,0 @@
-show semantic tokens for the specified file
-
-Usage:
- gopls [flags] semtok <filename>
-
-Example: show the semantic tokens for this file:
-
- $ gopls semtok internal/lsp/cmd/semtok.go
diff --git a/internal/lsp/cmd/usage/serve.hlp b/internal/lsp/cmd/usage/serve.hlp
deleted file mode 100644
index 370cbce83..000000000
--- a/internal/lsp/cmd/usage/serve.hlp
+++ /dev/null
@@ -1,30 +0,0 @@
-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/internal/lsp/cmd/usage/signature.hlp b/internal/lsp/cmd/usage/signature.hlp
deleted file mode 100644
index f9fd0bfb7..000000000
--- a/internal/lsp/cmd/usage/signature.hlp
+++ /dev/null
@@ -1,10 +0,0 @@
-display selected identifier's signature
-
-Usage:
- gopls [flags] signature <position>
-
-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/internal/lsp/cmd/usage/symbols.hlp b/internal/lsp/cmd/usage/symbols.hlp
deleted file mode 100644
index 2aa36aa84..000000000
--- a/internal/lsp/cmd/usage/symbols.hlp
+++ /dev/null
@@ -1,7 +0,0 @@
-display selected file's symbols
-
-Usage:
- gopls [flags] symbols <file>
-
-Example:
- $ gopls symbols helper/helper.go
diff --git a/internal/lsp/cmd/usage/usage.hlp b/internal/lsp/cmd/usage/usage.hlp
deleted file mode 100644
index 1d0fb8d4c..000000000
--- a/internal/lsp/cmd/usage/usage.hlp
+++ /dev/null
@@ -1,77 +0,0 @@
-
-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.
-
-Usage:
- gopls help [<subject>]
-
-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
- 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
- definition show declaration of selected identifier
- 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
- fix apply suggested fixes
- symbols display selected file's symbols
- workspace manage the gopls workspace (experimental: under development)
- workspace_symbol search symbols in workspace
- vulncheck run experimental vulncheck analysis (experimental: under development)
-
-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.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/internal/lsp/cmd/usage/version.hlp b/internal/lsp/cmd/usage/version.hlp
deleted file mode 100644
index 3a09ddedf..000000000
--- a/internal/lsp/cmd/usage/version.hlp
+++ /dev/null
@@ -1,6 +0,0 @@
-print the gopls version information
-
-Usage:
- gopls [flags] version
- -json
- outputs in json format.
diff --git a/internal/lsp/cmd/usage/vulncheck.hlp b/internal/lsp/cmd/usage/vulncheck.hlp
deleted file mode 100644
index 4bfdc4b47..000000000
--- a/internal/lsp/cmd/usage/vulncheck.hlp
+++ /dev/null
@@ -1,9 +0,0 @@
-run experimental vulncheck analysis (experimental: under development)
-
-Usage:
- gopls [flags] vulncheck
-
- WARNING: this command is experimental.
-
- Example:
- $ gopls vulncheck <packages>
diff --git a/internal/lsp/cmd/usage/workspace.hlp b/internal/lsp/cmd/usage/workspace.hlp
deleted file mode 100644
index 912cf2946..000000000
--- a/internal/lsp/cmd/usage/workspace.hlp
+++ /dev/null
@@ -1,7 +0,0 @@
-manage the gopls workspace (experimental: under development)
-
-Usage:
- gopls [flags] workspace <subcommand> [arg]...
-
-Subcommand:
- generate generate a gopls.mod file for a workspace
diff --git a/internal/lsp/cmd/usage/workspace_symbol.hlp b/internal/lsp/cmd/usage/workspace_symbol.hlp
deleted file mode 100644
index a61b47b33..000000000
--- a/internal/lsp/cmd/usage/workspace_symbol.hlp
+++ /dev/null
@@ -1,13 +0,0 @@
-search symbols in workspace
-
-Usage:
- gopls [flags] workspace_symbol [workspace_symbol-flags] <query>
-
-Example:
-
- $ gopls workspace_symbol -matcher fuzzy 'wsymbols'
-
-workspace_symbol-flags:
- -matcher=string
- specifies the type of matcher: fuzzy, caseSensitive, or caseInsensitive.
- The default is caseInsensitive.
diff --git a/internal/lsp/cmd/vulncheck.go b/internal/lsp/cmd/vulncheck.go
deleted file mode 100644
index adf59cecb..000000000
--- a/internal/lsp/cmd/vulncheck.go
+++ /dev/null
@@ -1,79 +0,0 @@
-// 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"
- "encoding/json"
- "flag"
- "fmt"
- "os"
-
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/tool"
-)
-
-// vulncheck implements the vulncheck command.
-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 experimental vulncheck analysis (experimental: under development)"
-}
-func (v *vulncheck) DetailedHelp(f *flag.FlagSet) {
- fmt.Fprint(f.Output(), `
- WARNING: this command is experimental.
-
- Example:
- $ gopls vulncheck <packages>
-`)
- printFlagDefaults(f)
-}
-
-func (v *vulncheck) Run(ctx context.Context, args ...string) error {
- if len(args) > 1 {
- return tool.CommandLineErrorf("vulncheck accepts at most one package pattern")
- }
- pattern := "."
- if len(args) == 1 {
- pattern = args[0]
- }
-
- conn, err := v.app.connect(ctx)
- if err != nil {
- return err
- }
- defer conn.terminate(ctx)
-
- cwd, err := os.Getwd()
- if err != nil {
- return err
- }
-
- cmd, err := command.NewRunVulncheckExpCommand("", command.VulncheckArgs{
- Dir: protocol.URIFromPath(cwd),
- Pattern: pattern,
- })
- if err != nil {
- return err
- }
-
- params := &protocol.ExecuteCommandParams{Command: cmd.Command, Arguments: cmd.Arguments}
- res, err := conn.ExecuteCommand(ctx, params)
- if err != nil {
- return fmt.Errorf("executing server command: %v", err)
- }
- data, err := json.MarshalIndent(res, " ", " ")
- if err != nil {
- return fmt.Errorf("failed to decode results: %v", err)
- }
- fmt.Printf("%s\n", data)
- return nil
-}
diff --git a/internal/lsp/cmd/workspace.go b/internal/lsp/cmd/workspace.go
deleted file mode 100644
index c0ddd9eb4..000000000
--- a/internal/lsp/cmd/workspace.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// 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"
-
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-// workspace is a top-level command for working with the gopls workspace. This
-// is experimental and subject to change. The idea is that subcommands could be
-// used for manipulating the workspace mod file, rather than editing it
-// manually.
-type workspace struct {
- app *Application
- subcommands
-}
-
-func newWorkspace(app *Application) *workspace {
- return &workspace{
- app: app,
- subcommands: subcommands{
- &generateWorkspaceMod{app: app},
- },
- }
-}
-
-func (w *workspace) Name() string { return "workspace" }
-func (w *workspace) Parent() string { return w.app.Name() }
-func (w *workspace) ShortHelp() string {
- return "manage the gopls workspace (experimental: under development)"
-}
-
-// generateWorkspaceMod (re)generates the gopls.mod file for the current
-// workspace.
-type generateWorkspaceMod struct {
- app *Application
-}
-
-func (c *generateWorkspaceMod) Name() string { return "generate" }
-func (c *generateWorkspaceMod) Usage() string { return "" }
-func (c *generateWorkspaceMod) ShortHelp() string {
- return "generate a gopls.mod file for a workspace"
-}
-
-func (c *generateWorkspaceMod) DetailedHelp(f *flag.FlagSet) {
- printFlagDefaults(f)
-}
-
-func (c *generateWorkspaceMod) Run(ctx context.Context, args ...string) error {
- origOptions := c.app.options
- c.app.options = func(opts *source.Options) {
- origOptions(opts)
- opts.ExperimentalWorkspaceModule = true
- }
- conn, err := c.app.connect(ctx)
- if err != nil {
- return err
- }
- defer conn.terminate(ctx)
- cmd, err := command.NewGenerateGoplsModCommand("", command.URIArg{})
- if err != nil {
- return err
- }
- params := &protocol.ExecuteCommandParams{Command: cmd.Command, Arguments: cmd.Arguments}
- if _, err := conn.ExecuteCommand(ctx, params); err != nil {
- return fmt.Errorf("executing server command: %v", err)
- }
- return nil
-}
diff --git a/internal/lsp/cmd/workspace_symbol.go b/internal/lsp/cmd/workspace_symbol.go
deleted file mode 100644
index 38fe5decf..000000000
--- a/internal/lsp/cmd/workspace_symbol.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// 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"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "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, 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] <query>" }
-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 *source.Options) {
- if opts != nil {
- opts(o)
- }
- switch r.Matcher {
- case "fuzzy":
- o.SymbolMatcher = source.SymbolFuzzy
- case "caseSensitive":
- o.SymbolMatcher = source.SymbolCaseSensitive
- case "fastfuzzy":
- o.SymbolMatcher = source.SymbolFastFuzzy
- default:
- o.SymbolMatcher = source.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 := conn.AddFile(ctx, fileURI(s.Location.URI))
- span, err := f.mapper.Span(s.Location)
- if err != nil {
- return err
- }
- fmt.Printf("%s %s %s\n", span, s.Name, s.Kind)
- }
-
- return nil
-}
diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go
deleted file mode 100644
index 7ddf81296..000000000
--- a/internal/lsp/code_action.go
+++ /dev/null
@@ -1,455 +0,0 @@
-// 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 lsp
-
-import (
- "context"
- "fmt"
- "sort"
- "strings"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/mod"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
- defer release()
- if !ok {
- return nil, err
- }
- uri := fh.URI()
-
- // Determine the supported actions for this file kind.
- kind := snapshot.View().FileKind(fh)
- supportedCodeActions, ok := snapshot.View().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 that the client wants all of the non-explicit code actions.
- var wanted map[protocol.CodeActionKind]bool
-
- // Explicit Code Actions are opt-in and shouldn't be returned to the client unless
- // requested using Only.
- // TODO: Add other CodeLenses such as GoGenerate, RegenerateCgo, etc..
- explicit := map[protocol.CodeActionKind]bool{
- protocol.GoTest: true,
- }
-
- if len(params.Context.Only) == 0 {
- wanted = supportedCodeActions
- } else {
- wanted = make(map[protocol.CodeActionKind]bool)
- for _, only := range params.Context.Only {
- for k, v := range supportedCodeActions {
- if only == k || strings.HasPrefix(string(k), string(only)+".") {
- wanted[k] = wanted[k] || v
- }
- }
- wanted[only] = wanted[only] || explicit[only]
- }
- }
- if len(supportedCodeActions) == 0 {
- return nil, nil // not an error if there are none supported
- }
- if len(wanted) == 0 {
- return nil, fmt.Errorf("no supported code action to execute for %s, wanted %v", uri, params.Context.Only)
- }
-
- var codeActions []protocol.CodeAction
- switch kind {
- case source.Mod:
- if diagnostics := params.Context.Diagnostics; len(diagnostics) > 0 {
- diags, err := mod.DiagnosticsForMod(ctx, snapshot, fh)
- if source.IsNonFatalGoModError(err) {
- return nil, nil
- }
- if err != nil {
- return nil, err
- }
- quickFixes, err := codeActionsMatchingDiagnostics(ctx, snapshot, diagnostics, diags)
- if err != nil {
- return nil, err
- }
- codeActions = append(codeActions, quickFixes...)
- }
- case source.Go:
- // Don't suggest fixes for generated files, since they are generally
- // not useful and some editors may apply them automatically on save.
- if source.IsGenerated(ctx, snapshot, uri) {
- return nil, nil
- }
- diagnostics := params.Context.Diagnostics
-
- // First, process any missing imports and pair them with the
- // diagnostics they fix.
- if wantQuickFixes := wanted[protocol.QuickFix] && len(diagnostics) > 0; wantQuickFixes || wanted[protocol.SourceOrganizeImports] {
- importEdits, importEditsPerFix, err := source.AllImportsFixes(ctx, snapshot, fh)
- if err != nil {
- event.Error(ctx, "imports fixes", err, tag.File.Of(fh.URI().Filename()))
- }
- // 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 {
- fixes := importDiagnostics(importFix.Fix, diagnostics)
- if len(fixes) == 0 {
- continue
- }
- codeActions = append(codeActions, protocol.CodeAction{
- Title: importFixTitle(importFix.Fix),
- Kind: protocol.QuickFix,
- Edit: protocol.WorkspaceEdit{
- DocumentChanges: documentChanges(fh, importFix.Edits),
- },
- Diagnostics: fixes,
- })
- }
- }
-
- // Send all of the import edits as one code action if the file is
- // being organized.
- if wanted[protocol.SourceOrganizeImports] && len(importEdits) > 0 {
- codeActions = append(codeActions, protocol.CodeAction{
- Title: "Organize Imports",
- Kind: protocol.SourceOrganizeImports,
- Edit: protocol.WorkspaceEdit{
- DocumentChanges: documentChanges(fh, importEdits),
- },
- })
- }
- }
- if ctx.Err() != nil {
- return nil, ctx.Err()
- }
- pkg, err := snapshot.PackageForFile(ctx, fh.URI(), source.TypecheckFull, source.WidestPackage)
- if err != nil {
- return nil, err
- }
-
- pkgDiagnostics, err := snapshot.DiagnosePackage(ctx, pkg)
- if err != nil {
- return nil, err
- }
- analysisDiags, err := source.Analyze(ctx, snapshot, pkg, true)
- if err != nil {
- return nil, err
- }
- fileDiags := append(pkgDiagnostics[uri], analysisDiags[uri]...)
-
- // Split diagnostics into fixes, which must match incoming diagnostics,
- // and non-fixes, which must match the requested range. Build actions
- // for all of them.
- var fixDiags, nonFixDiags []*source.Diagnostic
- for _, d := range fileDiags {
- if len(d.SuggestedFixes) == 0 {
- continue
- }
- var isFix bool
- for _, fix := range d.SuggestedFixes {
- if fix.ActionKind == protocol.QuickFix || fix.ActionKind == protocol.SourceFixAll {
- isFix = true
- break
- }
- }
- if isFix {
- fixDiags = append(fixDiags, d)
- } else {
- nonFixDiags = append(nonFixDiags, d)
- }
- }
-
- fixActions, err := codeActionsMatchingDiagnostics(ctx, snapshot, diagnostics, fixDiags)
- if err != nil {
- return nil, err
- }
- codeActions = append(codeActions, fixActions...)
-
- for _, nonfix := range nonFixDiags {
- // For now, only show diagnostics for matching lines. Maybe we should
- // alter this behavior in the future, depending on the user experience.
- if !protocol.Intersect(nonfix.Range, params.Range) {
- continue
- }
- actions, err := codeActionsForDiagnostic(ctx, snapshot, nonfix, nil)
- if err != nil {
- return nil, err
- }
- codeActions = append(codeActions, actions...)
- }
-
- if wanted[protocol.RefactorExtract] {
- fixes, err := extractionFixes(ctx, snapshot, pkg, uri, params.Range)
- if err != nil {
- return nil, err
- }
- codeActions = append(codeActions, fixes...)
- }
-
- if wanted[protocol.GoTest] {
- fixes, err := goTest(ctx, snapshot, uri, params.Range)
- if err != nil {
- return nil, err
- }
- codeActions = append(codeActions, fixes...)
- }
-
- default:
- // Unsupported file kind for a code action.
- return nil, nil
- }
-
- var filtered []protocol.CodeAction
- for _, action := range codeActions {
- if wanted[action.Kind] {
- filtered = append(filtered, action)
- }
- }
- return filtered, nil
-}
-
-func (s *Server) getSupportedCodeActions() []protocol.CodeActionKind {
- allCodeActionKinds := make(map[protocol.CodeActionKind]struct{})
- for _, kinds := range s.session.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
-}
-
-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
-}
-
-func importDiagnostics(fix *imports.ImportFix, diagnostics []protocol.Diagnostic) (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)
- }
- // "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
-}
-
-func extractionFixes(ctx context.Context, snapshot source.Snapshot, pkg source.Package, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
- if rng.Start == rng.End {
- return nil, nil
- }
- fh, err := snapshot.GetFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- _, pgf, err := source.GetParsedFile(ctx, snapshot, fh, source.NarrowestPackage)
- if err != nil {
- return nil, errors.Errorf("getting file for Identifier: %w", err)
- }
- srng, err := pgf.Mapper.RangeToSpanRange(rng)
- if err != nil {
- return nil, err
- }
- puri := protocol.URIFromSpanURI(uri)
- var commands []protocol.Command
- if _, ok, methodOk, _ := source.CanExtractFunction(snapshot.FileSet(), srng, pgf.Src, pgf.File); ok {
- cmd, err := command.NewApplyFixCommand("Extract function", command.ApplyFixArgs{
- URI: puri,
- Fix: source.ExtractFunction,
- Range: rng,
- })
- if err != nil {
- return nil, err
- }
- commands = append(commands, cmd)
- if methodOk {
- cmd, err := command.NewApplyFixCommand("Extract method", command.ApplyFixArgs{
- URI: puri,
- Fix: source.ExtractMethod,
- Range: rng,
- })
- if err != nil {
- return nil, err
- }
- commands = append(commands, cmd)
- }
- }
- if _, _, ok, _ := source.CanExtractVariable(srng, pgf.File); ok {
- cmd, err := command.NewApplyFixCommand("Extract variable", command.ApplyFixArgs{
- URI: puri,
- Fix: source.ExtractVariable,
- Range: rng,
- })
- if err != nil {
- return nil, err
- }
- commands = append(commands, cmd)
- }
- var actions []protocol.CodeAction
- for i := range commands {
- actions = append(actions, protocol.CodeAction{
- Title: commands[i].Title,
- Kind: protocol.RefactorExtract,
- Command: &commands[i],
- })
- }
- return actions, nil
-}
-
-func documentChanges(fh source.VersionedFileHandle, edits []protocol.TextEdit) []protocol.TextDocumentEdit {
- return []protocol.TextDocumentEdit{
- {
- TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
- Version: fh.Version(),
- TextDocumentIdentifier: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(fh.URI()),
- },
- },
- Edits: edits,
- },
- }
-}
-
-func codeActionsMatchingDiagnostics(ctx context.Context, snapshot source.Snapshot, pdiags []protocol.Diagnostic, sdiags []*source.Diagnostic) ([]protocol.CodeAction, error) {
- var actions []protocol.CodeAction
- for _, sd := range sdiags {
- var diag *protocol.Diagnostic
- for _, pd := range pdiags {
- if sameDiagnostic(pd, sd) {
- diag = &pd
- break
- }
- }
- if diag == nil {
- continue
- }
- diagActions, err := codeActionsForDiagnostic(ctx, snapshot, sd, diag)
- if err != nil {
- return nil, err
- }
- actions = append(actions, diagActions...)
-
- }
- return actions, nil
-}
-
-func codeActionsForDiagnostic(ctx context.Context, snapshot source.Snapshot, sd *source.Diagnostic, pd *protocol.Diagnostic) ([]protocol.CodeAction, error) {
- var actions []protocol.CodeAction
- for _, fix := range sd.SuggestedFixes {
- var changes []protocol.TextDocumentEdit
- for uri, edits := range fix.Edits {
- fh, err := snapshot.GetVersionedFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- changes = append(changes, protocol.TextDocumentEdit{
- TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
- Version: fh.Version(),
- TextDocumentIdentifier: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- },
- Edits: edits,
- })
- }
- action := protocol.CodeAction{
- Title: fix.Title,
- Kind: fix.ActionKind,
- Edit: protocol.WorkspaceEdit{
- DocumentChanges: changes,
- },
- Command: fix.Command,
- }
- if pd != nil {
- action.Diagnostics = []protocol.Diagnostic{*pd}
- }
- actions = append(actions, action)
- }
- return actions, nil
-}
-
-func sameDiagnostic(pd protocol.Diagnostic, sd *source.Diagnostic) bool {
- return pd.Message == sd.Message && protocol.CompareRange(pd.Range, sd.Range) == 0 && pd.Source == string(sd.Source)
-}
-
-func goTest(ctx context.Context, snapshot source.Snapshot, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
- fh, err := snapshot.GetFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- fns, err := source.TestsAndBenchmarks(ctx, snapshot, fh)
- if err != nil {
- return nil, err
- }
-
- var tests, benchmarks []string
- for _, fn := range fns.Tests {
- if !protocol.Intersect(fn.Rng, rng) {
- continue
- }
- tests = append(tests, fn.Name)
- }
- for _, fn := range fns.Benchmarks {
- if !protocol.Intersect(fn.Rng, rng) {
- continue
- }
- benchmarks = append(benchmarks, fn.Name)
- }
-
- if len(tests) == 0 && len(benchmarks) == 0 {
- return nil, nil
- }
-
- cmd, err := command.NewTestCommand("Run tests and benchmarks", protocol.URIFromSpanURI(uri), tests, benchmarks)
- if err != nil {
- return nil, err
- }
- return []protocol.CodeAction{{
- Title: cmd.Title,
- Kind: protocol.GoTest,
- Command: &cmd,
- }}, nil
-}
diff --git a/internal/lsp/code_lens.go b/internal/lsp/code_lens.go
deleted file mode 100644
index e19445838..000000000
--- a/internal/lsp/code_lens.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// 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 lsp
-
-import (
- "context"
- "fmt"
- "sort"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/mod"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-func (s *Server) codeLens(ctx context.Context, params *protocol.CodeLensParams) ([]protocol.CodeLens, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
- defer release()
- if !ok {
- return nil, err
- }
- var lenses map[command.Command]source.LensFunc
- switch snapshot.View().FileKind(fh) {
- case source.Mod:
- lenses = mod.LensFuncs()
- case source.Go:
- lenses = source.LensFuncs()
- default:
- // Unsupported file kind for a code lens.
- return nil, nil
- }
- var result []protocol.CodeLens
- for cmd, lf := range lenses {
- if !snapshot.View().Options().Codelenses[string(cmd)] {
- continue
- }
- added, err := lf(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", cmd), err)
- continue
- }
- result = append(result, added...)
- }
- sort.Slice(result, func(i, j int) bool {
- a, b := result[i], result[j]
- if protocol.CompareRange(a.Range, b.Range) == 0 {
- return a.Command.Command < b.Command.Command
- }
- return protocol.CompareRange(a.Range, b.Range) < 0
- })
- return result, nil
-}
diff --git a/internal/lsp/command.go b/internal/lsp/command.go
deleted file mode 100644
index 088fa57d5..000000000
--- a/internal/lsp/command.go
+++ /dev/null
@@ -1,819 +0,0 @@
-// 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 lsp
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "strings"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/go/packages"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/gocommand"
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/debug"
- "golang.org/x/tools/internal/lsp/progress"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/xcontext"
- errors "golang.org/x/xerrors"
-)
-
-func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) {
- var found bool
- for _, name := range s.session.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
-}
-
-// commandConfig configures common command set-up and execution.
-type commandConfig struct {
- async bool // whether to run the command asynchronously. Async commands can only return errors.
- 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.
- 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 source.Snapshot // present if cfg.forURI was set
- fh source.VersionedFileHandle // present if cfg.forURI was set
- work *progress.WorkDone // present cfg.progress was set
-}
-
-type commandFunc func(context.Context, commandDeps) error
-
-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.Saved() {
- unsaved = append(unsaved, overlay.URI().Filename())
- }
- }
- if len(unsaved) > 0 {
- return errors.Errorf("All files must be saved first (unsaved: %v).", unsaved)
- }
- }
- var deps commandDeps
- if cfg.forURI != "" {
- var ok bool
- var release func()
- deps.snapshot, deps.fh, ok, release, err = c.s.beginFileRequest(ctx, cfg.forURI, source.UnknownKind)
- defer release()
- if !ok {
- if err != nil {
- return err
- }
- return fmt.Errorf("invalid file URL: %v", cfg.forURI)
- }
- }
- 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 cancel()
- err := run(ctx, deps)
- if deps.work != nil {
- switch {
- case errors.Is(err, context.Canceled):
- deps.work.End("canceled")
- case err != nil:
- event.Error(ctx, "command error", err)
- deps.work.End("failed")
- default:
- deps.work.End("completed")
- }
- }
- return err
- }
- if cfg.async {
- go func() {
- if err := runcmd(); err != nil {
- if showMessageErr := c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
- Type: protocol.Error,
- Message: err.Error(),
- }); showMessageErr != nil {
- event.Error(ctx, fmt.Sprintf("failed to show message: %q", err.Error()), showMessageErr)
- }
- }
- }()
- return nil
- }
- return runcmd()
-}
-
-func (c *commandHandler) ApplyFix(ctx context.Context, args command.ApplyFixArgs) error {
- return c.run(ctx, commandConfig{
- // Note: no progress here. Applying fixes should be quick.
- forURI: args.URI,
- }, func(ctx context.Context, deps commandDeps) error {
- edits, err := source.ApplyFix(ctx, args.Fix, deps.snapshot, deps.fh, args.Range)
- if err != nil {
- return err
- }
- r, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
- Edit: protocol.WorkspaceEdit{
- DocumentChanges: edits,
- },
- })
- if err != nil {
- return err
- }
- if !r.Applied {
- return errors.New(r.FailureReason)
- }
- return nil
- })
-}
-
-func (c *commandHandler) RegenerateCgo(ctx context.Context, args command.URIArg) error {
- return c.run(ctx, commandConfig{
- progress: "Regenerating Cgo",
- }, func(ctx context.Context, deps commandDeps) error {
- mod := source.FileModification{
- URI: args.URI.SpanURI(),
- Action: source.InvalidateMetadata,
- }
- return c.s.didModifyFiles(ctx, []source.FileModification{mod}, FromRegenerateCgo)
- })
-}
-
-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 {
- upgrades, err := c.s.getUpgrades(ctx, deps.snapshot, args.URI.SpanURI(), args.Modules)
- if err != nil {
- return err
- }
- deps.snapshot.View().RegisterModuleUpgrades(upgrades)
- // Re-diagnose the snapshot to publish the new module diagnostics.
- c.s.diagnoseSnapshot(deps.snapshot, nil, false)
- return nil
- })
-}
-
-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) 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.SpanURI(), 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, deps commandDeps) error {
- for _, uri := range args.URIs {
- snapshot, fh, ok, release, err := c.s.beginFileRequest(ctx, uri, source.UnknownKind)
- defer release()
- if !ok {
- return err
- }
- 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{
- requireSave: true,
- progress: "Running go mod tidy",
- }, func(ctx context.Context, deps commandDeps) error {
- for _, uri := range args.URIs {
- snapshot, fh, ok, release, err := c.s.beginFileRequest(ctx, uri, source.UnknownKind)
- defer release()
- if !ok {
- return err
- }
- 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,
- progress: "Running go mod vendor",
- forURI: args.URI,
- }, func(ctx context.Context, deps commandDeps) error {
- _, err := deps.snapshot.RunGoCommandDirect(ctx, source.Normal|source.AllowNetwork, &gocommand.Invocation{
- Verb: "mod",
- Args: []string{"vendor"},
- WorkingDir: filepath.Dir(args.URI.SpanURI().Filename()),
- })
- return err
- })
-}
-
-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, deps commandDeps) error {
- snapshot, fh, ok, release, err := c.s.beginFileRequest(ctx, args.URI, source.UnknownKind)
- defer release()
- if !ok {
- return err
- }
- 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 {
- // 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.
- // TODO(rstambler): 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.SpanURI(), 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(deps.snapshot, pm, args.ModulePath)
- if err != nil {
- return err
- }
- response, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
- Edit: protocol.WorkspaceEdit{
- DocumentChanges: []protocol.TextDocumentEdit{{
- TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
- Version: deps.fh.Version(),
- TextDocumentIdentifier: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(deps.fh.URI()),
- },
- },
- Edits: 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(snapshot source.Snapshot, pm *source.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, err := snapshot.View().Options().ComputeEdits(pm.URI, string(pm.Mapper.Content), string(newContent))
- if err != nil {
- return nil, err
- }
- return source.ToProtocolEdits(pm.Mapper, diff)
-}
-
-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) RunTests(ctx context.Context, args command.RunTestsArgs) error {
- return c.run(ctx, commandConfig{
- async: true,
- progress: "Running go test",
- requireSave: true,
- forURI: args.URI,
- }, func(ctx context.Context, deps commandDeps) error {
- if err := c.runTests(ctx, deps.snapshot, deps.work, args.URI, args.Tests, args.Benchmarks); err != nil {
- return errors.Errorf("running tests failed: %w", err)
- }
- return nil
- })
-}
-
-func (c *commandHandler) runTests(ctx context.Context, snapshot source.Snapshot, work *progress.WorkDone, uri protocol.DocumentURI, tests, benchmarks []string) error {
- // TODO: fix the error reporting when this runs async.
- pkgs, err := snapshot.PackagesForFile(ctx, uri.SpanURI(), source.TypecheckWorkspace, false)
- if err != nil {
- return err
- }
- if len(pkgs) == 0 {
- return fmt.Errorf("package could not be found for file: %s", uri.SpanURI().Filename())
- }
- pkgPath := pkgs[0].ForTest()
-
- // create output
- buf := &bytes.Buffer{}
- ew := progress.NewEventWriter(ctx, "test")
- out := io.MultiWriter(ew, progress.NewWorkDoneWriter(work), buf)
-
- // Run `go test -run Func` on each test.
- var failedTests int
- for _, funcName := range tests {
- inv := &gocommand.Invocation{
- Verb: "test",
- Args: []string{pkgPath, "-v", "-count=1", "-run", fmt.Sprintf("^%s$", funcName)},
- WorkingDir: filepath.Dir(uri.SpanURI().Filename()),
- }
- if err := snapshot.RunGoCommandPiped(ctx, source.Normal, 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 := &gocommand.Invocation{
- Verb: "test",
- Args: []string{pkgPath, "-v", "-run=^$", "-bench", fmt.Sprintf("^%s$", funcName)},
- WorkingDir: filepath.Dir(uri.SpanURI().Filename()),
- }
- if err := snapshot.RunGoCommandPiped(ctx, source.Normal, 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()
- }
-
- return c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
- Type: protocol.Info,
- Message: message,
- })
-}
-
-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,
- progress: title,
- forURI: args.Dir,
- }, func(ctx context.Context, deps commandDeps) error {
- er := progress.NewEventWriter(ctx, "generate")
-
- pattern := "."
- if args.Recursive {
- pattern = "./..."
- }
- inv := &gocommand.Invocation{
- Verb: "generate",
- Args: []string{"-x", pattern},
- WorkingDir: args.Dir.SpanURI().Filename(),
- }
- stderr := io.MultiWriter(er, progress.NewWorkDoneWriter(deps.work))
- if err := deps.snapshot.RunGoCommandPiped(ctx, source.Normal, 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 {
- // Run on a throwaway go.mod, otherwise it'll write to the real one.
- stdout, err := deps.snapshot.RunGoCommandDirect(ctx, source.WriteTemporaryModFile|source.AllowNetwork, &gocommand.Invocation{
- Verb: "list",
- Args: []string{"-f", "{{.Module.Path}}@{{.Module.Version}}", args.Pkg},
- WorkingDir: filepath.Dir(args.URI.SpanURI().Filename()),
- })
- if err != nil {
- return err
- }
- ver := strings.TrimSpace(stdout.String())
- return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI.SpanURI(), 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 source.Snapshot, uri span.URI, run func(invoke func(...string) (*bytes.Buffer, error)) error) error {
- tmpModfile, newModBytes, newSumBytes, err := snapshot.RunGoCommands(ctx, true, filepath.Dir(uri.Filename()), run)
- if err != nil {
- return err
- }
- if !tmpModfile {
- return nil
- }
- modURI := snapshot.GoModForFile(uri)
- sumURI := span.URIFromPath(strings.TrimSuffix(modURI.Filename(), ".mod") + ".sum")
- modEdits, err := applyFileEdits(ctx, snapshot, modURI, newModBytes)
- if err != nil {
- return err
- }
- sumEdits, err := applyFileEdits(ctx, snapshot, sumURI, newSumBytes)
- if err != nil {
- return err
- }
- changes := append(sumEdits, modEdits...)
- if len(changes) == 0 {
- return nil
- }
- response, err := s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
- Edit: protocol.WorkspaceEdit{
- DocumentChanges: changes,
- },
- })
- if err != nil {
- return err
- }
- if !response.Applied {
- return fmt.Errorf("edits not applied because of %s", response.FailureReason)
- }
- return nil
-}
-
-func applyFileEdits(ctx context.Context, snapshot source.Snapshot, uri span.URI, newContent []byte) ([]protocol.TextDocumentEdit, error) {
- fh, err := snapshot.GetVersionedFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- oldContent, err := fh.Read()
- if err != nil && !os.IsNotExist(err) {
- return nil, err
- }
- if bytes.Equal(oldContent, newContent) {
- return nil, nil
- }
-
- // 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 := ioutil.WriteFile(uri.Filename(), newContent, 0666)
- return nil, err
- }
-
- m := &protocol.ColumnMapper{
- URI: fh.URI(),
- Converter: span.NewContentConverter(fh.URI().Filename(), oldContent),
- Content: oldContent,
- }
- diff, err := snapshot.View().Options().ComputeEdits(uri, string(oldContent), string(newContent))
- if err != nil {
- return nil, err
- }
- edits, err := source.ToProtocolEdits(m, diff)
- if err != nil {
- return nil, err
- }
- return []protocol.TextDocumentEdit{{
- TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
- Version: fh.Version(),
- TextDocumentIdentifier: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- },
- Edits: edits,
- }}, 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
-}
-
-func (s *Server) getUpgrades(ctx context.Context, snapshot source.Snapshot, uri span.URI, modules []string) (map[string]string, error) {
- stdout, err := snapshot.RunGoCommandDirect(ctx, source.Normal|source.AllowNetwork, &gocommand.Invocation{
- Verb: "list",
- Args: append([]string{"-m", "-u", "-json"}, modules...),
- WorkingDir: filepath.Dir(uri.Filename()),
- ModFlag: "readonly",
- })
- 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{
- requireSave: true,
- progress: "Toggling GC Details",
- forURI: args.URI,
- }, func(ctx context.Context, deps commandDeps) error {
- pkg, err := deps.snapshot.PackageForFile(ctx, deps.fh.URI(), source.TypecheckWorkspace, source.NarrowestPackage)
- if err != nil {
- return err
- }
- c.s.gcOptimizationDetailsMu.Lock()
- if _, ok := c.s.gcOptimizationDetails[pkg.ID()]; ok {
- delete(c.s.gcOptimizationDetails, pkg.ID())
- c.s.clearDiagnosticSource(gcDetailsSource)
- } else {
- c.s.gcOptimizationDetails[pkg.ID()] = struct{}{}
- }
- c.s.gcOptimizationDetailsMu.Unlock()
- c.s.diagnoseSnapshot(deps.snapshot, nil, false)
- return nil
- })
-}
-
-func (c *commandHandler) GenerateGoplsMod(ctx context.Context, args command.URIArg) error {
- // TODO: go back to using URI
- return c.run(ctx, commandConfig{
- requireSave: true,
- progress: "Generating gopls.mod",
- }, func(ctx context.Context, deps commandDeps) error {
- views := c.s.session.Views()
- if len(views) != 1 {
- return fmt.Errorf("cannot resolve view: have %d views", len(views))
- }
- v := views[0]
- snapshot, release := v.Snapshot(ctx)
- defer release()
- modFile, err := snapshot.BuildGoplsMod(ctx)
- if err != nil {
- return errors.Errorf("getting workspace mod file: %w", err)
- }
- content, err := modFile.Format()
- if err != nil {
- return errors.Errorf("formatting mod file: %w", err)
- }
- filename := filepath.Join(snapshot.View().Folder().Filename(), "gopls.mod")
- if err := ioutil.WriteFile(filename, content, 0644); err != nil {
- return errors.Errorf("writing mod file: %w", err)
- }
- return nil
- })
-}
-
-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 {
- var err error
- result.Packages, err = source.KnownPackages(ctx, deps.snapshot, deps.fh)
- 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 {
- pkg, err := deps.snapshot.PackageForFile(ctx, args.URI.SpanURI(), source.TypecheckWorkspace, source.NarrowestPackage)
- if err != nil {
- return err
- }
- pgf, err := pkg.File(args.URI.SpanURI())
- if err != nil {
- return err
- }
- for _, group := range astutil.Imports(deps.snapshot.FileSet(), 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: source.ImportPath(imp),
- Name: name,
- })
- }
- }
- for _, imp := range pkg.Imports() {
- result.PackageImports = append(result.PackageImports, command.PackageImport{
- Path: imp.PkgPath(), // This might be the vendored path under GOPATH vendoring, in which case it's a bug.
- })
- }
- 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 := source.AddImport(ctx, deps.snapshot, deps.fh, args.ImportPath)
- if err != nil {
- return fmt.Errorf("could not add import: %v", err)
- }
- if _, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
- Edit: protocol.WorkspaceEdit{
- DocumentChanges: documentChanges(deps.fh, edits),
- },
- }); err != nil {
- return fmt.Errorf("could not apply import edits: %v", err)
- }
- 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, errors.Errorf("starting debug server: %w", err)
- }
- result.URLs = []string{"http://" + listenedAddr}
- return result, nil
-}
-
-func (c *commandHandler) RunVulncheckExp(ctx context.Context, args command.VulncheckArgs) (result command.VulncheckResult, _ error) {
- err := c.run(ctx, commandConfig{
- progress: "Running vulncheck",
- requireSave: true,
- forURI: args.Dir, // Will dir work?
- }, func(ctx context.Context, deps commandDeps) error {
- view := deps.snapshot.View()
- opts := view.Options()
- if opts == nil || opts.Hooks.Govulncheck == nil {
- return errors.New("vulncheck feature is not available")
- }
-
- buildFlags := opts.BuildFlags // XXX: is session.Options equivalent to view.Options?
- var viewEnv []string
- if e := opts.EnvSlice(); e != nil {
- viewEnv = append(os.Environ(), e...)
- }
- cfg := &packages.Config{
- Context: ctx,
- Tests: true, // TODO(hyangah): add a field in args.
- BuildFlags: buildFlags,
- Env: viewEnv,
- Dir: view.Folder().Filename(),
- // TODO(hyangah): configure overlay
- }
- var err error
- result, err = opts.Hooks.Govulncheck(ctx, cfg, args)
- return err
- })
- return result, err
-}
diff --git a/internal/lsp/command/command_gen.go b/internal/lsp/command/command_gen.go
deleted file mode 100644
index 22cfeff5b..000000000
--- a/internal/lsp/command/command_gen.go
+++ /dev/null
@@ -1,473 +0,0 @@
-// 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.
-
-// 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
-
-package command
-
-// Code generated by generate.go. DO NOT EDIT.
-
-import (
- "context"
- "fmt"
-
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-const (
- AddDependency Command = "add_dependency"
- AddImport Command = "add_import"
- ApplyFix Command = "apply_fix"
- CheckUpgrades Command = "check_upgrades"
- EditGoDirective Command = "edit_go_directive"
- GCDetails Command = "gc_details"
- Generate Command = "generate"
- GenerateGoplsMod Command = "generate_gopls_mod"
- GoGetPackage Command = "go_get_package"
- ListImports Command = "list_imports"
- ListKnownPackages Command = "list_known_packages"
- RegenerateCgo Command = "regenerate_cgo"
- RemoveDependency Command = "remove_dependency"
- RunTests Command = "run_tests"
- RunVulncheckExp Command = "run_vulncheck_exp"
- StartDebugging Command = "start_debugging"
- Test Command = "test"
- Tidy Command = "tidy"
- ToggleGCDetails Command = "toggle_gc_details"
- UpdateGoSum Command = "update_go_sum"
- UpgradeDependency Command = "upgrade_dependency"
- Vendor Command = "vendor"
-)
-
-var Commands = []Command{
- AddDependency,
- AddImport,
- ApplyFix,
- CheckUpgrades,
- EditGoDirective,
- GCDetails,
- Generate,
- GenerateGoplsMod,
- GoGetPackage,
- ListImports,
- ListKnownPackages,
- RegenerateCgo,
- RemoveDependency,
- RunTests,
- RunVulncheckExp,
- StartDebugging,
- Test,
- Tidy,
- ToggleGCDetails,
- UpdateGoSum,
- UpgradeDependency,
- Vendor,
-}
-
-func Dispatch(ctx context.Context, params *protocol.ExecuteCommandParams, s Interface) (interface{}, error) {
- switch params.Command {
- case "gopls.add_dependency":
- var a0 DependencyArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.AddDependency(ctx, a0)
- case "gopls.add_import":
- var a0 AddImportArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.AddImport(ctx, a0)
- case "gopls.apply_fix":
- var a0 ApplyFixArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.ApplyFix(ctx, a0)
- case "gopls.check_upgrades":
- var a0 CheckUpgradesArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.CheckUpgrades(ctx, a0)
- case "gopls.edit_go_directive":
- var a0 EditGoDirectiveArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.EditGoDirective(ctx, a0)
- case "gopls.gc_details":
- var a0 protocol.DocumentURI
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.GCDetails(ctx, a0)
- case "gopls.generate":
- var a0 GenerateArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.Generate(ctx, a0)
- case "gopls.generate_gopls_mod":
- var a0 URIArg
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.GenerateGoplsMod(ctx, a0)
- case "gopls.go_get_package":
- var a0 GoGetPackageArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.GoGetPackage(ctx, a0)
- case "gopls.list_imports":
- var a0 URIArg
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return s.ListImports(ctx, a0)
- case "gopls.list_known_packages":
- var a0 URIArg
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return s.ListKnownPackages(ctx, a0)
- case "gopls.regenerate_cgo":
- var a0 URIArg
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.RegenerateCgo(ctx, a0)
- case "gopls.remove_dependency":
- var a0 RemoveDependencyArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.RemoveDependency(ctx, a0)
- case "gopls.run_tests":
- var a0 RunTestsArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.RunTests(ctx, a0)
- case "gopls.run_vulncheck_exp":
- var a0 VulncheckArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return s.RunVulncheckExp(ctx, a0)
- case "gopls.start_debugging":
- var a0 DebuggingArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return s.StartDebugging(ctx, a0)
- case "gopls.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 "gopls.tidy":
- var a0 URIArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.Tidy(ctx, a0)
- case "gopls.toggle_gc_details":
- var a0 URIArg
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.ToggleGCDetails(ctx, a0)
- case "gopls.update_go_sum":
- var a0 URIArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.UpdateGoSum(ctx, a0)
- case "gopls.upgrade_dependency":
- var a0 DependencyArgs
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.UpgradeDependency(ctx, a0)
- case "gopls.vendor":
- var a0 URIArg
- if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
- return nil, err
- }
- return nil, s.Vendor(ctx, a0)
- }
- return nil, fmt.Errorf("unsupported command %q", params.Command)
-}
-
-func NewAddDependencyCommand(title string, a0 DependencyArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.add_dependency",
- Arguments: args,
- }, nil
-}
-
-func NewAddImportCommand(title string, a0 AddImportArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.add_import",
- Arguments: args,
- }, nil
-}
-
-func NewApplyFixCommand(title string, a0 ApplyFixArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.apply_fix",
- Arguments: args,
- }, nil
-}
-
-func NewCheckUpgradesCommand(title string, a0 CheckUpgradesArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.check_upgrades",
- Arguments: args,
- }, nil
-}
-
-func NewEditGoDirectiveCommand(title string, a0 EditGoDirectiveArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.edit_go_directive",
- Arguments: args,
- }, nil
-}
-
-func NewGCDetailsCommand(title string, a0 protocol.DocumentURI) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.gc_details",
- Arguments: args,
- }, nil
-}
-
-func NewGenerateCommand(title string, a0 GenerateArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.generate",
- Arguments: args,
- }, nil
-}
-
-func NewGenerateGoplsModCommand(title string, a0 URIArg) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.generate_gopls_mod",
- Arguments: args,
- }, nil
-}
-
-func NewGoGetPackageCommand(title string, a0 GoGetPackageArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.go_get_package",
- Arguments: args,
- }, nil
-}
-
-func NewListImportsCommand(title string, a0 URIArg) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.list_imports",
- Arguments: args,
- }, nil
-}
-
-func NewListKnownPackagesCommand(title string, a0 URIArg) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.list_known_packages",
- Arguments: args,
- }, nil
-}
-
-func NewRegenerateCgoCommand(title string, a0 URIArg) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.regenerate_cgo",
- Arguments: args,
- }, nil
-}
-
-func NewRemoveDependencyCommand(title string, a0 RemoveDependencyArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.remove_dependency",
- Arguments: args,
- }, nil
-}
-
-func NewRunTestsCommand(title string, a0 RunTestsArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.run_tests",
- Arguments: args,
- }, nil
-}
-
-func NewRunVulncheckExpCommand(title string, a0 VulncheckArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.run_vulncheck_exp",
- Arguments: args,
- }, nil
-}
-
-func NewStartDebuggingCommand(title string, a0 DebuggingArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.start_debugging",
- Arguments: args,
- }, nil
-}
-
-func NewTestCommand(title string, a0 protocol.DocumentURI, a1 []string, a2 []string) (protocol.Command, error) {
- args, err := MarshalArgs(a0, a1, a2)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.test",
- Arguments: args,
- }, nil
-}
-
-func NewTidyCommand(title string, a0 URIArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.tidy",
- Arguments: args,
- }, nil
-}
-
-func NewToggleGCDetailsCommand(title string, a0 URIArg) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.toggle_gc_details",
- Arguments: args,
- }, nil
-}
-
-func NewUpdateGoSumCommand(title string, a0 URIArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.update_go_sum",
- Arguments: args,
- }, nil
-}
-
-func NewUpgradeDependencyCommand(title string, a0 DependencyArgs) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.upgrade_dependency",
- Arguments: args,
- }, nil
-}
-
-func NewVendorCommand(title string, a0 URIArg) (protocol.Command, error) {
- args, err := MarshalArgs(a0)
- if err != nil {
- return protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "gopls.vendor",
- Arguments: args,
- }, nil
-}
diff --git a/internal/lsp/command/commandmeta/meta.go b/internal/lsp/command/commandmeta/meta.go
deleted file mode 100644
index 102b89839..000000000
--- a/internal/lsp/command/commandmeta/meta.go
+++ /dev/null
@@ -1,258 +0,0 @@
-// 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 analyzing the
-// command.Interface type.
-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/lsp/command"
-)
-
-type Command struct {
- MethodName string
- Name string
- // TODO(rFindley): I think Title can actually be eliminated. In all cases
- // where we use it, there is probably a more appropriate contextual title.
- Title string
- Doc string
- Args []*Field
- Result *Field
-}
-
-func (c *Command) ID() string {
- return command.ID(c.Name)
-}
-
-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
-}
-
-func Load() (*packages.Package, []*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/internal/lsp/command",
- )
- if err != nil {
- return nil, nil, fmt.Errorf("packages.Load: %v", err)
- }
- pkg := pkgs[0]
- if len(pkg.Errors) > 0 {
- return pkg, nil, pkg.Errors[0]
- }
-
- // For a bit of type safety, use reflection to get the interface name within
- // the package scope.
- it := reflect.TypeOf((*command.Interface)(nil)).Elem()
- obj := pkg.Types.Scope().Lookup(it.Name()).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, nil, fmt.Errorf("loading %s: %v", m.Name(), err)
- }
- commands = append(commands, c)
- }
- return pkg, 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 := 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"),
- }
- 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 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.Position(f.Pos()).Filename == fset.Position(pos).Filename {
- 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/internal/lsp/command/gen/gen.go b/internal/lsp/command/gen/gen.go
deleted file mode 100644
index 8f7a2d503..000000000
--- a/internal/lsp/command/gen/gen.go
+++ /dev/null
@@ -1,155 +0,0 @@
-// 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"
- "text/template"
-
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/command/commandmeta"
-)
-
-const src = `// 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.
-
-// 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
-
-package command
-
-// Code generated by generate.go. DO NOT EDIT.
-
-import (
- {{range $k, $v := .Imports -}}
- "{{$k}}"
- {{end}}
-)
-
-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 params.Command {
- {{- range .Commands}}
- case "{{.ID}}":
- {{- 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}}
-
-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 protocol.Command{}, err
- }
- return protocol.Command{
- Title: title,
- Command: "{{.ID}}",
- Arguments: args,
- }, nil
-}
-{{end}}
-`
-
-type data struct {
- Imports map[string]bool
- Commands []*commandmeta.Command
-}
-
-func Generate() ([]byte, error) {
- pkg, cmds, err := commandmeta.Load()
- if err != nil {
- return nil, fmt.Errorf("loading command data: %v", err)
- }
- qf := func(p *types.Package) string {
- if p == pkg.Types {
- return ""
- }
- return p.Name()
- }
- tmpl, err := template.New("").Funcs(template.FuncMap{
- "typeString": func(t types.Type) string {
- return types.TypeString(t, qf)
- },
- }).Parse(src)
- if err != nil {
- return nil, err
- }
- d := data{
- Commands: cmds,
- Imports: map[string]bool{
- "context": true,
- "fmt": true,
- "golang.org/x/tools/internal/lsp/protocol": true,
- },
- }
- const thispkg = "golang.org/x/tools/internal/lsp/command"
- 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 {
- if n, ok := t.(*types.Named); ok {
- if pkg := n.Obj().Pkg(); pkg != nil {
- return pkg.Path()
- }
- }
- return ""
-}
diff --git a/internal/lsp/command/generate.go b/internal/lsp/command/generate.go
deleted file mode 100644
index 14628c733..000000000
--- a/internal/lsp/command/generate.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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
-
-package main
-
-import (
- "fmt"
- "io/ioutil"
- "os"
-
- "golang.org/x/tools/internal/lsp/command/gen"
-)
-
-func main() {
- content, err := gen.Generate()
- if err != nil {
- fmt.Fprintf(os.Stderr, "%v\n", err)
- os.Exit(1)
- }
- ioutil.WriteFile("command_gen.go", content, 0644)
-}
diff --git a/internal/lsp/command/interface.go b/internal/lsp/command/interface.go
deleted file mode 100644
index 9aecfbe78..000000000
--- a/internal/lsp/command/interface.go
+++ /dev/null
@@ -1,384 +0,0 @@
-// 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 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
-
-//go:generate go run -tags=generate generate.go
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-// Interface defines the interface gopls exposes for the
-// workspace/executeCommand request.
-//
-// This interface is used to generate marshaling/unmarshaling code, dispatch,
-// and documentation, and so 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'.
-// TODO(rFindley): reconsider this -- Title may be unnecessary.
-type Interface interface {
- // ApplyFix: Apply a fix
- //
- // Applies a fix to a region of source code.
- ApplyFix(context.Context, ApplyFixArgs) error
- // Test: Run test(s) (legacy)
- //
- // Runs `go test` for a specific set of test or benchmark functions.
- Test(context.Context, protocol.DocumentURI, []string, []string) error
-
- // TODO: deprecate Test in favor of RunTests below.
-
- // Test: Run test(s)
- //
- // Runs `go test` for a specific set of test or benchmark functions.
- RunTests(context.Context, RunTestsArgs) error
-
- // Generate: Run go generate
- //
- // Runs `go generate` for a given directory.
- Generate(context.Context, GenerateArgs) 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
-
- // 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
-
- // GenerateGoplsMod: Generate gopls.mod
- //
- // (Re)generate the gopls.mod file for a workspace.
- GenerateGoplsMod(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
-
- // 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)
-
- // RunVulncheckExp: Run vulncheck (experimental)
- //
- // Run vulnerability check (`govulncheck`).
- RunVulncheckExp(context.Context, VulncheckArgs) (VulncheckResult, 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 fix to apply.
- Fix string
- // The file URI for the document to fix.
- URI protocol.DocumentURI
- // The document range to scan for fixes.
- Range protocol.Range
-}
-
-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
- 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 WorkspaceMetadataArgs struct {
-}
-
-type WorkspaceMetadataResult struct {
- // All workspaces for this session.
- Workspaces []Workspace
-}
-
-type Workspace struct {
- // The workspace name.
- Name string
- // The workspace module directory.
- ModuleDir 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
-}
-
-type VulncheckArgs struct {
- // Dir is the directory from which vulncheck will run from.
- Dir protocol.DocumentURI
-
- // Package pattern. E.g. "", ".", "./...".
- Pattern string
-
- // TODO: Flag []string (flags accepted by govulncheck, e.g., -tests)
- // TODO: Format string (json, text)
-}
-
-type VulncheckResult struct {
- Vuln []Vuln
-
- // TODO: Text string format output?
-}
-
-// 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)
-}
-
-// Vuln models an osv.Entry and representative call stacks.
-type Vuln struct {
- // ID is the vulnerability ID (osv.Entry.ID).
- // https://ossf.github.io/osv-schema/#id-modified-fields
- ID string
- // Details is the description of the vulnerability (osv.Entry.Details).
- // https://ossf.github.io/osv-schema/#summary-details-fields
- Details string `json:",omitempty"`
- // Aliases are alternative IDs of the vulnerability.
- // https://ossf.github.io/osv-schema/#aliases-field
- Aliases []string `json:",omitempty"`
-
- // Symbol is the name of the detected vulnerable function or method.
- Symbol string `json:",omitempty"`
- // PkgPath is the package path of the detected Symbol.
- PkgPath string `json:",omitempty"`
- // ModPath is the module path corresponding to PkgPath.
- // TODO: how do we specify standard library's vulnerability?
- ModPath string `json:",omitempty"`
-
- // URL is the URL for more info about the information.
- // Either the database specific URL or the one of the URLs
- // included in osv.Entry.References.
- URL string `json:",omitempty"`
-
- // Current is the current module version.
- CurrentVersion string `json:",omitempty"`
-
- // Fixed is the minimum module version that contains the fix.
- FixedVersion string `json:",omitempty"`
-
- // Example call stacks.
- CallStacks []CallStack `json:",omitempty"`
-
- // TODO: import graph & module graph.
-}
diff --git a/internal/lsp/command/interface_test.go b/internal/lsp/command/interface_test.go
deleted file mode 100644
index 9ea30b446..000000000
--- a/internal/lsp/command/interface_test.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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 (
- "bytes"
- "io/ioutil"
- "testing"
-
- "golang.org/x/tools/internal/lsp/command/gen"
- "golang.org/x/tools/internal/testenv"
-)
-
-func TestGenerated(t *testing.T) {
- testenv.NeedsGoBuild(t) // This is a lie. We actually need the source code.
-
- onDisk, err := ioutil.ReadFile("command_gen.go")
- if err != nil {
- t.Fatal(err)
- }
-
- generated, err := gen.Generate()
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(onDisk, generated) {
- t.Error("command_gen.go is stale -- regenerate")
- }
-}
diff --git a/internal/lsp/command/util.go b/internal/lsp/command/util.go
deleted file mode 100644
index dc9f22fad..000000000
--- a/internal/lsp/command/util.go
+++ /dev/null
@@ -1,65 +0,0 @@
-// 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"
-)
-
-// ID returns the command name for use in the LSP.
-func ID(name string) string {
- return "gopls." + name
-}
-
-type Command string
-
-func (c Command) ID() string {
- return ID(string(c))
-}
-
-// 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
-}
-
-// 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/internal/lsp/completion.go b/internal/lsp/completion.go
deleted file mode 100644
index 5c88ed0e4..000000000
--- a/internal/lsp/completion.go
+++ /dev/null
@@ -1,178 +0,0 @@
-// 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 lsp
-
-import (
- "bytes"
- "context"
- "fmt"
- "strings"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/source/completion"
- "golang.org/x/tools/internal/lsp/template"
- "golang.org/x/tools/internal/lsp/work"
- "golang.org/x/tools/internal/span"
-)
-
-func (s *Server) completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
- defer release()
- if !ok {
- return nil, err
- }
- var candidates []completion.CompletionItem
- var surrounding *completion.Selection
- switch snapshot.View().FileKind(fh) {
- case source.Go:
- candidates, surrounding, err = completion.Completion(ctx, snapshot, fh, params.Position, params.Context)
- case source.Mod:
- candidates, surrounding = nil, nil
- case source.Work:
- cl, err := work.Completion(ctx, snapshot, fh, params.Position)
- if err != nil {
- break
- }
- return cl, nil
- case source.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, tag.Position.Of(params.Position))
- }
- if candidates == nil {
- return &protocol.CompletionList{
- IsIncomplete: true,
- Items: []protocol.CompletionItem{},
- }, nil
- }
- // We might need to adjust the position to account for the prefix.
- rng, err := surrounding.Range()
- if err != nil {
- return nil, err
- }
-
- // internal/span treats end of file as the beginning of the next line, even
- // when it's not newline-terminated. We correct for that behaviour here if
- // end of file is not newline-terminated. See golang/go#41029.
- src, err := fh.Read()
- if err != nil {
- return nil, err
- }
- numLines := len(bytes.Split(src, []byte("\n")))
- tok := snapshot.FileSet().File(surrounding.Start())
- eof := tok.Pos(tok.Size())
-
- // For newline-terminated files, the line count reported by go/token should
- // be lower than the actual number of lines we see when splitting by \n. If
- // they're the same, the file isn't newline-terminated.
- if tok.Size() > 0 && tok.LineCount() == numLines {
- // Get the span for the last character in the file-1. This is
- // technically incorrect, but will get span to point to the previous
- // line.
- spn, err := span.NewRange(snapshot.FileSet(), eof-1, eof-1).Span()
- if err != nil {
- return nil, err
- }
- m := &protocol.ColumnMapper{
- URI: fh.URI(),
- Converter: span.NewContentConverter(fh.URI().Filename(), src),
- Content: src,
- }
- eofRng, err := m.Range(spn)
- if err != nil {
- return nil, err
- }
- // Instead of using the computed range, correct for our earlier
- // position adjustment by adding 1 to the column, not the line number.
- pos := protocol.Position{
- Line: eofRng.Start.Line,
- Character: eofRng.Start.Character + 1,
- }
- if surrounding.Start() >= eof {
- rng.Start = pos
- }
- if surrounding.End() >= eof {
- rng.End = pos
- }
- }
-
- // When using deep completions/fuzzy matching, report results as incomplete so
- // client fetches updated completions after every key stroke.
- options := snapshot.View().Options()
- incompleteResults := options.DeepCompletion || options.Matcher == source.Fuzzy
-
- items := toProtocolCompletionItems(candidates, rng, options)
-
- return &protocol.CompletionList{
- IsIncomplete: incompleteResults,
- Items: items,
- }, nil
-}
-
-func toProtocolCompletionItems(candidates []completion.CompletionItem, rng protocol.Range, options *source.Options) []protocol.CompletionItem {
- 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
- }
-
- item := protocol.CompletionItem{
- Label: candidate.Label,
- Detail: candidate.Detail,
- Kind: candidate.Kind,
- TextEdit: &protocol.TextEdit{
- NewText: insertText,
- Range: rng,
- },
- 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: candidate.Documentation,
- Tags: candidate.Tags,
- Deprecated: candidate.Deprecated,
- }
- items = append(items, item)
- }
- return items
-}
diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go
deleted file mode 100644
index d496a40a5..000000000
--- a/internal/lsp/completion_test.go
+++ /dev/null
@@ -1,154 +0,0 @@
-// 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 lsp
-
-import (
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/tests"
- "golang.org/x/tools/internal/span"
-)
-
-func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- got := r.callCompletion(t, src, func(opts *source.Options) {
- opts.DeepCompletion = false
- opts.Matcher = source.CaseInsensitive
- opts.CompleteUnimported = false
- opts.InsertTextFormat = protocol.SnippetTextFormat
- opts.LiteralCompletions = strings.Contains(string(src.URI()), "literal")
- opts.ExperimentalPostfixCompletions = strings.Contains(string(src.URI()), "postfix")
- })
- got = tests.FilterBuiltins(src, got)
- want := expected(t, test, items)
- if diff := tests.DiffCompletionItems(want, got); diff != "" {
- t.Errorf("%s", diff)
- }
-}
-
-func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) {
- list := r.callCompletion(t, src, func(opts *source.Options) {
- opts.UsePlaceholders = placeholders
- opts.DeepCompletion = true
- opts.Matcher = source.Fuzzy
- opts.CompleteUnimported = false
- })
- got := tests.FindItem(list, *items[expected.CompletionItem])
- want := expected.PlainSnippet
- if placeholders {
- want = expected.PlaceholderSnippet
- }
- if diff := tests.DiffSnippets(want, got); diff != "" {
- t.Errorf("%s", diff)
- }
-}
-
-func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- got := r.callCompletion(t, src, func(opts *source.Options) {})
- got = tests.FilterBuiltins(src, got)
- want := expected(t, test, items)
- if diff := tests.CheckCompletionOrder(want, got, false); diff != "" {
- t.Errorf("%s", diff)
- }
-}
-
-func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- got := r.callCompletion(t, src, func(opts *source.Options) {
- opts.DeepCompletion = true
- opts.Matcher = source.CaseInsensitive
- opts.CompleteUnimported = false
- })
- got = tests.FilterBuiltins(src, got)
- want := expected(t, test, items)
- if msg := tests.DiffCompletionItems(want, got); msg != "" {
- t.Errorf("%s", msg)
- }
-}
-
-func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- got := r.callCompletion(t, src, func(opts *source.Options) {
- opts.DeepCompletion = true
- opts.Matcher = source.Fuzzy
- opts.CompleteUnimported = false
- })
- got = tests.FilterBuiltins(src, got)
- want := expected(t, test, items)
- if msg := tests.DiffCompletionItems(want, got); msg != "" {
- t.Errorf("%s", msg)
- }
-}
-
-func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- got := r.callCompletion(t, src, func(opts *source.Options) {
- opts.Matcher = source.CaseSensitive
- opts.CompleteUnimported = false
- })
- got = tests.FilterBuiltins(src, got)
- want := expected(t, test, items)
- if msg := tests.DiffCompletionItems(want, got); msg != "" {
- t.Errorf("%s", msg)
- }
-}
-
-func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- got := r.callCompletion(t, src, func(opts *source.Options) {
- opts.DeepCompletion = true
- opts.Matcher = source.Fuzzy
- opts.CompleteUnimported = false
- opts.LiteralCompletions = true
- opts.ExperimentalPostfixCompletions = true
- })
- want := expected(t, test, items)
- if msg := tests.CheckCompletionOrder(want, got, true); msg != "" {
- t.Errorf("%s", msg)
- }
-}
-
-func expected(t *testing.T, test tests.Completion, items tests.CompletionItems) []protocol.CompletionItem {
- t.Helper()
-
- var want []protocol.CompletionItem
- for _, pos := range test.CompletionItems {
- item := items[pos]
- want = append(want, tests.ToProtocolCompletionItem(*item))
- }
- return want
-}
-
-func (r *runner) callCompletion(t *testing.T, src span.Span, options func(*source.Options)) []protocol.CompletionItem {
- t.Helper()
-
- view, err := r.server.session.ViewOf(src.URI())
- if err != nil {
- t.Fatal(err)
- }
- original := view.Options()
- modified := view.Options().Clone()
- options(modified)
- view, err = view.SetOptions(r.ctx, modified)
- if err != nil {
- t.Error(err)
- return nil
- }
- defer view.SetOptions(r.ctx, original)
-
- list, err := r.server.Completion(r.ctx, &protocol.CompletionParams{
- TextDocumentPositionParams: protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(src.URI()),
- },
- Position: protocol.Position{
- Line: uint32(src.Start().Line() - 1),
- Character: uint32(src.Start().Column() - 1),
- },
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- return list.Items
-}
diff --git a/internal/lsp/debounce.go b/internal/lsp/debounce.go
deleted file mode 100644
index 06f411471..000000000
--- a/internal/lsp/debounce.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// 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 lsp
-
-import (
- "sync"
- "time"
-)
-
-type debounceEvent struct {
- order uint64
- done chan struct{}
-}
-
-type debouncer struct {
- mu sync.Mutex
- events map[string]*debounceEvent
-}
-
-func newDebouncer() *debouncer {
- return &debouncer{
- events: make(map[string]*debounceEvent),
- }
-}
-
-// debounce returns a channel that receives a boolean reporting whether,
-// by the time the delay channel receives a value, this call is (or will be)
-// the most recent call with the highest order number for its key.
-func (d *debouncer) debounce(key string, order uint64, delay <-chan time.Time) <-chan bool {
- okc := make(chan bool, 1)
-
- d.mu.Lock()
- if prev, ok := d.events[key]; ok {
- if prev.order > order {
- // If we have a logical ordering of events (as is the case for snapshots),
- // don't overwrite a later event with an earlier event.
- d.mu.Unlock()
- okc <- false
- return okc
- }
- close(prev.done)
- }
- done := make(chan struct{})
- next := &debounceEvent{
- order: order,
- done: done,
- }
- d.events[key] = next
- d.mu.Unlock()
-
- go func() {
- ok := false
- select {
- case <-delay:
- d.mu.Lock()
- if d.events[key] == next {
- ok = true
- delete(d.events, key)
- } else {
- // The event was superseded before we acquired d.mu.
- }
- d.mu.Unlock()
- case <-done:
- }
- okc <- ok
- }()
-
- return okc
-}
diff --git a/internal/lsp/debounce_test.go b/internal/lsp/debounce_test.go
deleted file mode 100644
index b5597faf5..000000000
--- a/internal/lsp/debounce_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// 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 lsp
-
-import (
- "testing"
- "time"
-)
-
-func TestDebouncer(t *testing.T) {
- t.Parallel()
-
- type event struct {
- key string
- order uint64
- wantFired bool
- }
- tests := []struct {
- label string
- events []*event
- }{
- {
- label: "overridden",
- events: []*event{
- {key: "a", order: 1, wantFired: false},
- {key: "a", order: 2, wantFired: true},
- },
- },
- {
- label: "distinct labels",
- events: []*event{
- {key: "a", order: 1, wantFired: true},
- {key: "b", order: 2, wantFired: true},
- },
- },
- {
- label: "reverse order",
- events: []*event{
- {key: "a", order: 2, wantFired: true},
- {key: "a", order: 1, wantFired: false},
- },
- },
- {
- label: "multiple overrides",
- events: []*event{
- {key: "a", order: 1, wantFired: false},
- {key: "a", order: 2, wantFired: false},
- {key: "a", order: 3, wantFired: false},
- {key: "a", order: 4, wantFired: false},
- {key: "a", order: 5, wantFired: true},
- },
- },
- }
- for _, test := range tests {
- test := test
- t.Run(test.label, func(t *testing.T) {
- d := newDebouncer()
-
- delays := make([]chan time.Time, len(test.events))
- okcs := make([]<-chan bool, len(test.events))
-
- // Register the events in deterministic order, synchronously.
- for i, e := range test.events {
- delays[i] = make(chan time.Time, 1)
- okcs[i] = d.debounce(e.key, e.order, delays[i])
- }
-
- // Now see which event fired.
- for i, okc := range okcs {
- event := test.events[i]
- delays[i] <- time.Now()
- fired := <-okc
- if fired != event.wantFired {
- t.Errorf("[key: %q, order: %d]: fired = %t, want %t", event.key, event.order, fired, event.wantFired)
- }
- }
- })
- }
-}
diff --git a/internal/lsp/debug/buildinfo_go1.12.go b/internal/lsp/debug/buildinfo_go1.12.go
deleted file mode 100644
index 2f360dbfc..000000000
--- a/internal/lsp/debug/buildinfo_go1.12.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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.18
-// +build !go1.18
-
-package debug
-
-import (
- "runtime"
- "runtime/debug"
-)
-
-type BuildInfo struct {
- debug.BuildInfo
- GoVersion string // Version of Go that produced this binary
-}
-
-func readBuildInfo() (*BuildInfo, bool) {
- rinfo, ok := debug.ReadBuildInfo()
- if !ok {
- return nil, false
- }
- return &BuildInfo{
- GoVersion: runtime.Version(),
- BuildInfo: *rinfo,
- }, true
-}
diff --git a/internal/lsp/debug/buildinfo_go1.18.go b/internal/lsp/debug/buildinfo_go1.18.go
deleted file mode 100644
index 4121c4bc9..000000000
--- a/internal/lsp/debug/buildinfo_go1.18.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// 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.18
-// +build go1.18
-
-package debug
-
-import (
- "runtime/debug"
-)
-
-type BuildInfo debug.BuildInfo
-
-func readBuildInfo() (*BuildInfo, bool) {
- info, ok := debug.ReadBuildInfo()
- return (*BuildInfo)(info), ok
-}
diff --git a/internal/lsp/debug/info.go b/internal/lsp/debug/info.go
deleted file mode 100644
index bcc2f4f06..000000000
--- a/internal/lsp/debug/info.go
+++ /dev/null
@@ -1,265 +0,0 @@
-// 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"
- "reflect"
- "runtime"
- "runtime/debug"
- "sort"
- "strings"
-
- "golang.org/x/tools/internal/lsp/source"
-)
-
-type PrintMode int
-
-const (
- PlainText = PrintMode(iota)
- Markdown
- HTML
- JSON
-)
-
-// Version is a manually-updated mechanism for tracking versions.
-const Version = "master"
-
-// 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 {
- *BuildInfo
- Version string
-}
-
-type Module struct {
- ModuleVersion
- Replace *ModuleVersion `json:"replace,omitempty"`
-}
-
-type ModuleVersion struct {
- Path string `json:"path,omitempty"`
- Version string `json:"version,omitempty"`
- Sum string `json:"sum,omitempty"`
-}
-
-// 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 := readBuildInfo(); ok {
- return getVersion(info)
- }
- buildInfo := &BuildInfo{}
- // go1.17 or earlier, part of s.BuildInfo are embedded fields.
- buildInfo.Path = "gopls, built in GOPATH mode"
- buildInfo.GoVersion = runtime.Version()
- return &ServerVersion{
- Version: Version,
- BuildInfo: buildInfo,
- }
-}
-
-func getVersion(info *BuildInfo) *ServerVersion {
- return &ServerVersion{
- Version: Version,
- BuildInfo: info,
- }
-}
-
-// PrintServerInfo writes HTML debug info to w for the Instance.
-func (i *Instance) PrintServerInfo(ctx context.Context, w io.Writer) {
- 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, "Working directory: %s\n", i.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, "<a href=/debug/pprof/cmdline>cmdline</a>")
- })
-}
-
-// 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, "<h3>%s</h3>\n<pre>\n", title)
- body()
- fmt.Fprint(w, "</pre>\n")
- }
-}
-
-func printBuildInfo(w io.Writer, info *ServerVersion, verbose bool, mode PrintMode) {
- fmt.Fprintf(w, "%v %v\n", info.Path, Version)
- printModuleInfo(w, info.Main, mode)
- if !verbose {
- return
- }
- 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")
-}
-
-type field struct {
- index []int
-}
-
-var fields []field
-
-// find all the options. The presumption is that the Options are nested structs
-// and that pointers don't need to be dereferenced
-func swalk(t reflect.Type, ix []int, indent string) {
- switch t.Kind() {
- case reflect.Struct:
- for i := 0; i < t.NumField(); i++ {
- fld := t.Field(i)
- ixx := append(append([]int{}, ix...), i)
- swalk(fld.Type, ixx, indent+". ")
- }
- default:
- // everything is either a struct or a field (that's an assumption about Options)
- fields = append(fields, field{ix})
- }
-}
-
-type sessionOption struct {
- Name string
- Type string
- Current string
- Default string
-}
-
-func showOptions(o *source.Options) []sessionOption {
- var out []sessionOption
- t := reflect.TypeOf(*o)
- swalk(t, []int{}, "")
- v := reflect.ValueOf(*o)
- do := reflect.ValueOf(*source.DefaultOptions())
- for _, f := range fields {
- val := v.FieldByIndex(f.index)
- def := do.FieldByIndex(f.index)
- tx := t.FieldByIndex(f.index)
- is := strVal(val)
- was := strVal(def)
- out = append(out, sessionOption{
- Name: tx.Name,
- Type: tx.Type.String(),
- Current: is,
- Default: was,
- })
- }
- sort.Slice(out, func(i, j int) bool {
- rd := out[i].Current == out[i].Default
- ld := out[j].Current == out[j].Default
- if rd != ld {
- return ld
- }
- return out[i].Name < out[j].Name
- })
- return out
-}
-
-func strVal(val reflect.Value) string {
- switch val.Kind() {
- case reflect.Bool:
- return fmt.Sprintf("%v", val.Interface())
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return fmt.Sprintf("%v", val.Interface())
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- return fmt.Sprintf("%v", val.Interface())
- case reflect.Uintptr, reflect.UnsafePointer:
- return fmt.Sprintf("0x%x", val.Pointer())
- case reflect.Complex64, reflect.Complex128:
- return fmt.Sprintf("%v", val.Complex())
- case reflect.Array, reflect.Slice:
- ans := []string{}
- for i := 0; i < val.Len(); i++ {
- ans = append(ans, strVal(val.Index(i)))
- }
- sort.Strings(ans)
- return fmt.Sprintf("%v", ans)
- case reflect.Chan, reflect.Func, reflect.Ptr:
- return val.Kind().String()
- case reflect.Struct:
- var x source.Analyzer
- if val.Type() != reflect.TypeOf(x) {
- return val.Kind().String()
- }
- // this is sort of ugly, but usable
- str := val.FieldByName("Analyzer").Elem().FieldByName("Doc").String()
- ix := strings.Index(str, "\n")
- if ix == -1 {
- ix = len(str)
- }
- return str[:ix]
- case reflect.String:
- return fmt.Sprintf("%q", val.Interface())
- case reflect.Map:
- ans := []string{}
- iter := val.MapRange()
- for iter.Next() {
- k := iter.Key()
- v := iter.Value()
- ans = append(ans, fmt.Sprintf("%s:%s, ", strVal(k), strVal(v)))
- }
- sort.Strings(ans)
- return fmt.Sprintf("%v", ans)
- }
- return fmt.Sprintf("??%s??", val.Type())
-}
diff --git a/internal/lsp/debug/info_test.go b/internal/lsp/debug/info_test.go
deleted file mode 100644
index 5a5362841..000000000
--- a/internal/lsp/debug/info_test.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// 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"
-)
-
-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; 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.
- if !bytes.Contains(res, []byte(Version)) || !bytes.Contains(res, []byte(runtime.Version())) {
- t.Errorf("plaintext output = %q,\nwant (version: %v, go: %v)", res, Version, runtime.Version())
- }
-}
diff --git a/internal/lsp/debug/log/log.go b/internal/lsp/debug/log/log.go
deleted file mode 100644
index 44638f8a5..000000000
--- a/internal/lsp/debug/log/log.go
+++ /dev/null
@@ -1,43 +0,0 @@
-// 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"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/event/label"
- "golang.org/x/tools/internal/lsp/debug/tag"
-)
-
-// 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, tag.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(tag.Level.Get(lm))
-}
diff --git a/internal/lsp/debug/metrics.go b/internal/lsp/debug/metrics.go
deleted file mode 100644
index 8efc1d495..000000000
--- a/internal/lsp/debug/metrics.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// 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/lsp/debug/tag"
-)
-
-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{tag.RPCDirection, tag.Method},
- Buckets: bytesDistribution,
- }
-
- sentBytes = metric.HistogramInt64{
- Name: "sent_bytes",
- Description: "Distribution of sent bytes, by method.",
- Keys: []label.Key{tag.RPCDirection, tag.Method},
- Buckets: bytesDistribution,
- }
-
- latency = metric.HistogramFloat64{
- Name: "latency",
- Description: "Distribution of latency in milliseconds, by method.",
- Keys: []label.Key{tag.RPCDirection, tag.Method},
- Buckets: millisecondsDistribution,
- }
-
- started = metric.Scalar{
- Name: "started",
- Description: "Count of RPCs started by method.",
- Keys: []label.Key{tag.RPCDirection, tag.Method},
- }
-
- completed = metric.Scalar{
- Name: "completed",
- Description: "Count of RPCs completed by method and status.",
- Keys: []label.Key{tag.RPCDirection, tag.Method, tag.StatusCode},
- }
-)
-
-func registerMetrics(m *metric.Config) {
- receivedBytes.Record(m, tag.ReceivedBytes)
- sentBytes.Record(m, tag.SentBytes)
- latency.Record(m, tag.Latency)
- started.Count(m, tag.Started)
- completed.Count(m, tag.Latency)
-}
diff --git a/internal/lsp/debug/rpc.go b/internal/lsp/debug/rpc.go
deleted file mode 100644
index 033ee3797..000000000
--- a/internal/lsp/debug/rpc.go
+++ /dev/null
@@ -1,239 +0,0 @@
-// 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/lsp/debug/tag"
-)
-
-var RPCTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}RPC Information{{end}}
-{{define "body"}}
- <H2>Inbound</H2>
- {{template "rpcSection" .Inbound}}
- <H2>Outbound</H2>
- {{template "rpcSection" .Outbound}}
-{{end}}
-{{define "rpcSection"}}
- {{range .}}<P>
- <b>{{.Method}}</b> {{.Started}} <a href="/trace/{{.Method}}">traces</a> ({{.InProgress}} in progress)
- <br>
- <i>Latency</i> {{with .Latency}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
- <i>By bucket</i> 0s {{range .Latency.Values}}{{if gt .Count 0}}<b>{{.Count}}</b> {{.Limit}} {{end}}{{end}}
- <br>
- <i>Received</i> {{.Received}} (avg. {{.ReceivedMean}})
- <i>Sent</i> {{.Sent}} (avg. {{.SentMean}})
- <br>
- <i>Result codes</i> {{range .Codes}}{{.Key}}={{.Count}} {{end}}
- </P>
- {{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, ev); stats != nil {
- stats.Started++
- }
- case event.IsEnd(ev):
- span, stats := r.getRPCSpan(ctx, ev)
- if stats != nil {
- endRPC(ctx, ev, span, stats)
- }
- case event.IsMetric(ev):
- sent := byteUnits(tag.SentBytes.Get(lm))
- rec := byteUnits(tag.ReceivedBytes.Get(lm))
- if sent != 0 || rec != 0 {
- if _, stats := r.getRPCSpan(ctx, ev); stats != nil {
- stats.Sent += sent
- stats.Received += rec
- }
- }
- }
- return ctx
-}
-
-func endRPC(ctx context.Context, ev core.Event, 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, ev core.Event) (*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 := tag.Method.Get(lm)
- if method == "" {
- return nil
- }
- set := &r.Inbound
- if tag.RPCDirection.Get(lm) != tag.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 := tag.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/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go
deleted file mode 100644
index b6dba60ab..000000000
--- a/internal/lsp/debug/serve.go
+++ /dev/null
@@ -1,954 +0,0 @@
-// 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 (
- "archive/zip"
- "bytes"
- "context"
- "fmt"
- "html/template"
- "io"
- stdlog "log"
- "net"
- "net/http"
- "net/http/pprof"
- "os"
- "path"
- "path/filepath"
- "runtime"
- rpprof "runtime/pprof"
- "sort"
- "strconv"
- "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/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"
- "golang.org/x/tools/internal/lsp/cache"
- "golang.org/x/tools/internal/lsp/debug/log"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- errors "golang.org/x/xerrors"
-)
-
-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
- Workdir 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
-
- // bugs maps bug description -> formatted event
- bugs map[string]string
-}
-
-func Bug(ctx context.Context, desc, format string, args ...interface{}) {
- labels := []label.Label{tag.Bug.Of(desc)}
- _, file, line, ok := runtime.Caller(1)
- if ok {
- labels = append(labels, tag.Callsite.Of(fmt.Sprintf("%s:%d", file, line)))
- }
- msg := fmt.Sprintf(format, args...)
- event.Log(ctx, msg, labels...)
-}
-
-type bug struct {
- Description, Event string
-}
-
-func (st *State) Bugs() []bug {
- st.mu.Lock()
- defer st.mu.Unlock()
- var bugs []bug
- for k, v := range st.bugs {
- bugs = append(bugs, bug{k, v})
- }
- sort.Slice(bugs, func(i, j int) bool {
- return bugs[i].Description < bugs[j].Description
- })
- return bugs
-}
-
-func (st *State) recordBug(description, event string) {
- st.mu.Lock()
- defer st.mu.Unlock()
- if st.bugs == nil {
- st.bugs = make(map[string]string)
- }
- st.bugs[description] = event
-}
-
-// 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, ok := client.Session.Cache().(*cache.Cache)
- if !ok {
- continue
- }
- 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
-}
-
-// 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() {
- for _, v := range s.Views() {
- if cv, ok := v.(*cache.View); ok {
- views = append(views, cv)
- }
- }
- }
- 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 source.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
- }
- }
-}
-
-// AddServer adds 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) 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) getView(r *http.Request) interface{} {
- return i.State.View(path.Base(r.URL.Path))
-}
-
-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() {
- if o.FileIdentity().Hash == 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, workdir, agent string) context.Context {
- i := &Instance{
- StartTime: time.Now(),
- Workdir: workdir,
- 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, errors.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", tag.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("/cache/", render(CacheTmpl, i.getCache))
- mux.HandleFunc("/session/", render(SessionTmpl, i.getSession))
- mux.HandleFunc("/view/", render(ViewTmpl, i.getView))
- 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))
- 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
-}
-
-// MonitorMemory starts recording memory statistics each second.
-func (i *Instance) MonitorMemory(ctx context.Context) {
- tick := time.NewTicker(time.Second)
- nextThresholdGiB := uint64(1)
- go func() {
- for {
- <-tick.C
- var mem runtime.MemStats
- runtime.ReadMemStats(&mem)
- if mem.HeapAlloc < nextThresholdGiB*1<<30 {
- continue
- }
- if err := i.writeMemoryDebug(nextThresholdGiB, true); err != nil {
- event.Error(ctx, "writing memory debug info", err)
- }
- if err := i.writeMemoryDebug(nextThresholdGiB, false); err != nil {
- event.Error(ctx, "writing memory debug info", err)
- }
- event.Log(ctx, fmt.Sprintf("Wrote memory usage debug info to %v", os.TempDir()))
- nextThresholdGiB++
- }
- }()
-}
-
-func (i *Instance) writeMemoryDebug(threshold uint64, withNames bool) error {
- suffix := "withnames"
- if !withNames {
- suffix = "nonames"
- }
-
- filename := fmt.Sprintf("gopls.%d-%dGiB-%s.zip", os.Getpid(), threshold, suffix)
- zipf, err := os.OpenFile(filepath.Join(os.TempDir(), filename), os.O_CREATE|os.O_RDWR, 0644)
- if err != nil {
- return err
- }
- zipw := zip.NewWriter(zipf)
-
- f, err := zipw.Create("heap.pb.gz")
- if err != nil {
- return err
- }
- if err := rpprof.Lookup("heap").WriteTo(f, 0); err != nil {
- return err
- }
-
- f, err = zipw.Create("goroutines.txt")
- if err != nil {
- return err
- }
- if err := rpprof.Lookup("goroutine").WriteTo(f, 1); err != nil {
- return err
- }
-
- for _, cache := range i.State.Caches() {
- cf, err := zipw.Create(fmt.Sprintf("cache-%v.html", cache.ID()))
- if err != nil {
- return err
- }
- if _, err := cf.Write([]byte(cache.PackageStats(withNames))); err != nil {
- return err
- }
- }
-
- if err := zipw.Close(); err != nil {
- return err
- }
- return zipf.Close()
-}
-
-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 := tag.NewServer.Get(ev); sid != "" {
- i.State.updateServer(&Server{
- ID: sid,
- Logfile: tag.Logfile.Get(ev),
- DebugAddress: tag.DebugAddress.Get(ev),
- GoplsPath: tag.GoplsPath.Get(ev),
- ClientID: tag.ClientID.Get(ev),
- })
- }
- if s := cache.KeyShutdownSession.Get(ev); s != nil {
- i.State.dropClient(s)
- }
- if sid := tag.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 = tag.DebugAddress.Get(ev)
- c.Logfile = tag.Logfile.Get(ev)
- c.ServerID = tag.ServerID.Get(ev)
- c.GoplsPath = tag.GoplsPath.Get(ev)
- }
- }
- }
- if b := tag.Bug.Get(ev); b != "" {
- i.State.recordBug(b, fmt.Sprintf("%v", 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(`
-<html>
-<head>
-<title>{{template "title" .}}</title>
-<style>
-.profile-name{
- display:inline-block;
- width:6rem;
-}
-td.value {
- text-align: right;
-}
-ul.events {
- list-style-type: none;
-}
-
-</style>
-{{block "head" .}}{{end}}
-</head>
-<body>
-<a href="/">Main</a>
-<a href="/info">Info</a>
-<a href="/memory">Memory</a>
-<a href="/metrics">Metrics</a>
-<a href="/rpc">RPC</a>
-<a href="/trace">Trace</a>
-<hr>
-<h1>{{template "title" .}}</h1>
-{{block "body" .}}
-Unknown page
-{{end}}
-</body>
-</html>
-
-{{define "cachelink"}}<a href="/cache/{{.}}">Cache {{.}}</a>{{end}}
-{{define "clientlink"}}<a href="/client/{{.}}">Client {{.}}</a>{{end}}
-{{define "serverlink"}}<a href="/server/{{.}}">Server {{.}}</a>{{end}}
-{{define "sessionlink"}}<a href="/session/{{.}}">Session {{.}}</a>{{end}}
-{{define "viewlink"}}<a href="/view/{{.}}">View {{.}}</a>{{end}}
-{{define "filelink"}}<a href="/file/{{.Session}}/{{.FileIdentity.Hash}}">{{.FileIdentity.URI}}</a>{{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
- },
- "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"}}
-<h2>Caches</h2>
-<ul>{{range .State.Caches}}<li>{{template "cachelink" .ID}}</li>{{end}}</ul>
-<h2>Sessions</h2>
-<ul>{{range .State.Sessions}}<li>{{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}</li>{{end}}</ul>
-<h2>Views</h2>
-<ul>{{range .State.Views}}<li>{{.Name}} is {{template "viewlink" .ID}} from {{template "sessionlink" .Session.ID}} in {{.Folder}}</li>{{end}}</ul>
-<h2>Clients</h2>
-<ul>{{range .State.Clients}}<li>{{template "clientlink" .Session.ID}}</li>{{end}}</ul>
-<h2>Servers</h2>
-<ul>{{range .State.Servers}}<li>{{template "serverlink" .ID}}</li>{{end}}</ul>
-<h2>Known bugs encountered</h2>
-<dl>{{range .State.Bugs}}<dt>{{.Description}}</dt><dd>{{.Event}}</dd>{{end}}</dl>
-{{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"}}<meta http-equiv="refresh" content="5">{{end}}
-{{define "body"}}
-<h2>Stats</h2>
-<table>
-<tr><td class="label">Allocated bytes</td><td class="value">{{fuint64 .HeapAlloc}}</td></tr>
-<tr><td class="label">Total allocated bytes</td><td class="value">{{fuint64 .TotalAlloc}}</td></tr>
-<tr><td class="label">System bytes</td><td class="value">{{fuint64 .Sys}}</td></tr>
-<tr><td class="label">Heap system bytes</td><td class="value">{{fuint64 .HeapSys}}</td></tr>
-<tr><td class="label">Malloc calls</td><td class="value">{{fuint64 .Mallocs}}</td></tr>
-<tr><td class="label">Frees</td><td class="value">{{fuint64 .Frees}}</td></tr>
-<tr><td class="label">Idle heap bytes</td><td class="value">{{fuint64 .HeapIdle}}</td></tr>
-<tr><td class="label">In use bytes</td><td class="value">{{fuint64 .HeapInuse}}</td></tr>
-<tr><td class="label">Released to system bytes</td><td class="value">{{fuint64 .HeapReleased}}</td></tr>
-<tr><td class="label">Heap object count</td><td class="value">{{fuint64 .HeapObjects}}</td></tr>
-<tr><td class="label">Stack in use bytes</td><td class="value">{{fuint64 .StackInuse}}</td></tr>
-<tr><td class="label">Stack from system bytes</td><td class="value">{{fuint64 .StackSys}}</td></tr>
-<tr><td class="label">Bucket hash bytes</td><td class="value">{{fuint64 .BuckHashSys}}</td></tr>
-<tr><td class="label">GC metadata bytes</td><td class="value">{{fuint64 .GCSys}}</td></tr>
-<tr><td class="label">Off heap bytes</td><td class="value">{{fuint64 .OtherSys}}</td></tr>
-</table>
-<h2>By size</h2>
-<table>
-<tr><th>Size</th><th>Mallocs</th><th>Frees</th></tr>
-{{range .BySize}}<tr><td class="value">{{fuint32 .Size}}</td><td class="value">{{fuint64 .Mallocs}}</td><td class="value">{{fuint64 .Frees}}</td></tr>{{end}}
-</table>
-{{end}}
-`))
-
-var DebugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}GoPls Debug pages{{end}}
-{{define "body"}}
-<a href="/debug/pprof">Profiling</a>
-{{end}}
-`))
-
-var CacheTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}Cache {{.ID}}{{end}}
-{{define "body"}}
-<h2>memoize.Store entries</h2>
-<ul>{{range $k,$v := .MemStats}}<li>{{$k}} - {{$v}}</li>{{end}}</ul>
-<h2>Per-package usage - not accurate, for guidance only</h2>
-{{.PackageStats true}}
-{{end}}
-`))
-
-var ClientTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}Client {{.Session.ID}}{{end}}
-{{define "body"}}
-Using session: <b>{{template "sessionlink" .Session.ID}}</b><br>
-{{if .DebugAddress}}Debug this client at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}}
-Logfile: {{.Logfile}}<br>
-Gopls Path: {{.GoplsPath}}<br>
-<h2>Diagnostics</h2>
-{{/*Service: []protocol.Server; each server has map[uri]fileReports;
- each fileReport: map[diagnosticSoure]diagnosticReport
- diagnosticSource is one of 5 source
- diagnosticReport: snapshotID and map[hash]*source.Diagnostic
- sourceDiagnostic: struct {
- Range protocol.Range
- Message string
- Source string
- Code string
- CodeHref string
- Severity protocol.DiagnosticSeverity
- Tags []protocol.DiagnosticTag
-
- Related []RelatedInformation
- }
- RelatedInformation: struct {
- URI span.URI
- Range protocol.Range
- Message string
- }
- */}}
-<ul>{{range $k, $v := .Service.Diagnostics}}<li>{{$k}}:<ol>{{range $v}}<li>{{.}}</li>{{end}}</ol></li>{{end}}</ul>
-{{end}}
-`))
-
-var ServerTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}Server {{.ID}}{{end}}
-{{define "body"}}
-{{if .DebugAddress}}Debug this server at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>{{end}}
-Logfile: {{.Logfile}}<br>
-Gopls Path: {{.GoplsPath}}<br>
-{{end}}
-`))
-
-var SessionTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}Session {{.ID}}{{end}}
-{{define "body"}}
-From: <b>{{template "cachelink" .Cache.ID}}</b><br>
-<h2>Views</h2>
-<ul>{{range .Views}}<li>{{.Name}} is {{template "viewlink" .ID}} in {{.Folder}}</li>{{end}}</ul>
-<h2>Overlays</h2>
-<ul>{{range .Overlays}}<li>{{template "filelink" .}}</li>{{end}}</ul>
-<h2>Options</h2>
-{{range options .}}
-<p><b>{{.Name}}</b> {{.Type}}</p>
-<p><i>default:</i> {{.Default}}</p>
-{{if ne .Default .Current}}<p><i>current:</i> {{.Current}}</p>{{end}}
-{{end}}
-{{end}}
-`))
-
-var ViewTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}View {{.ID}}{{end}}
-{{define "body"}}
-Name: <b>{{.Name}}</b><br>
-Folder: <b>{{.Folder}}</b><br>
-From: <b>{{template "sessionlink" .Session.ID}}</b><br>
-<h2>Environment</h2>
-<ul>{{range .Options.Env}}<li>{{.}}</li>{{end}}</ul>
-{{end}}
-`))
-
-var FileTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}Overlay {{.FileIdentity.Hash}}{{end}}
-{{define "body"}}
-{{with .}}
- From: <b>{{template "sessionlink" .Session}}</b><br>
- URI: <b>{{.URI}}</b><br>
- Identifier: <b>{{.FileIdentity.Hash}}</b><br>
- Version: <b>{{.Version}}</b><br>
- Kind: <b>{{.Kind}}</b><br>
-{{end}}
-<h3>Contents</h3>
-<pre>{{fcontent .Read}}</pre>
-{{end}}
-`))
diff --git a/internal/lsp/debug/tag/tag.go b/internal/lsp/debug/tag/tag.go
deleted file mode 100644
index 1d00038f0..000000000
--- a/internal/lsp/debug/tag/tag.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// 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 tag provides the labels used for telemetry throughout gopls.
-package tag
-
-import (
- "golang.org/x/tools/internal/event/keys"
-)
-
-var (
- // create the label keys we use
- Method = keys.NewString("method", "")
- StatusCode = keys.NewString("status.code", "")
- StatusMessage = keys.NewString("status.message", "")
- RPCID = keys.NewString("id", "")
- RPCDirection = keys.NewString("direction", "")
- File = keys.NewString("file", "")
- Directory = keys.New("directory", "")
- URI = keys.New("URI", "")
- Package = keys.NewString("package", "") // Package ID
- PackagePath = keys.NewString("package_path", "")
- Query = keys.New("query", "")
- Snapshot = keys.NewUInt64("snapshot", "")
- Operation = keys.NewString("operation", "")
-
- Position = keys.New("position", "")
- Category = keys.NewString("category", "")
- PackageCount = keys.NewInt("packages", "")
- Files = keys.New("files", "")
- Port = keys.NewInt("port", "")
- Type = keys.New("type", "")
- HoverKind = keys.NewString("hoverkind", "")
-
- 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")
-
- // Bug tracks occurrences of known bugs in the server.
- Bug = keys.NewString("bug", "A bug has occurred")
- Callsite = keys.NewString("callsite", "gopls function call site")
-)
-
-var (
- // create the stats we measure
- Started = keys.NewInt64("started", "Count of started RPCs.")
- ReceivedBytes = keys.NewInt64("received_bytes", "Bytes received.") //, unit.Bytes)
- SentBytes = keys.NewInt64("sent_bytes", "Bytes sent.") //, unit.Bytes)
- Latency = keys.NewFloat64("latency_ms", "Elapsed time in milliseconds") //, unit.Milliseconds)
-)
-
-const (
- Inbound = "in"
- Outbound = "out"
-)
diff --git a/internal/lsp/debug/trace.go b/internal/lsp/debug/trace.go
deleted file mode 100644
index ca612867a..000000000
--- a/internal/lsp/debug/trace.go
+++ /dev/null
@@ -1,226 +0,0 @@
-// 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"
-)
-
-var TraceTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
-{{define "title"}}Trace Information{{end}}
-{{define "body"}}
- {{range .Traces}}<a href="/trace/{{.Name}}">{{.Name}}</a> last: {{.Last.Duration}}, longest: {{.Longest.Duration}}<br>{{end}}
- {{if .Selected}}
- <H2>{{.Selected.Name}}</H2>
- {{if .Selected.Last}}<H3>Last</H3><ul>{{template "details" .Selected.Last}}</ul>{{end}}
- {{if .Selected.Longest}}<H3>Longest</H3><ul>{{template "details" .Selected.Longest}}</ul>{{end}}
- {{end}}
-{{end}}
-{{define "details"}}
- <li>{{.Offset}} {{.Name}} {{.Duration}} {{.Tags}}</li>
- {{if .Events}}<ul class=events>{{range .Events}}<li>{{.Offset}} {{.Tags}}</li>{{end}}</ul>{{end}}
- {{if .Children}}<ul>{{range .Children}}{{template "details" .}}{{end}}</ul>{{end}}
-{{end}}
-`))
-
-type traces struct {
- mu sync.Mutex
- sets map[string]*traceSet
- unfinished map[export.SpanContext]*traceData
-}
-
-type TraceResults struct { // exported for testing
- Traces []*traceSet
- Selected *traceSet
-}
-
-type traceSet struct {
- Name string
- Last *traceData
- Longest *traceData
-}
-
-type traceData struct {
- TraceID export.TraceID
- SpanID export.SpanID
- ParentID export.SpanID
- Name string
- Start time.Time
- Finish time.Time
- Offset time.Duration
- Duration time.Duration
- Tags string
- Events []traceEvent
- Children []*traceData
-}
-
-type traceEvent struct {
- Time time.Time
- Offset time.Duration
- Tags string
-}
-
-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(ctx, ev, lm)
- trace.Log(ctx, "start", msg)
- case event.IsLog(ev):
- category := ""
- if event.IsError(ev) {
- category = "error"
- }
- msg := formatEvent(ctx, 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(ctx context.Context, 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 {
- t.mu.Lock()
- defer t.mu.Unlock()
- span := export.GetSpan(ctx)
- if span == nil {
- return ctx
- }
-
- switch {
- case event.IsStart(ev):
- if t.sets == nil {
- t.sets = make(map[string]*traceSet)
- t.unfinished = make(map[export.SpanContext]*traceData)
- }
- // just starting, add it to the unfinished map
- td := &traceData{
- TraceID: span.ID.TraceID,
- SpanID: span.ID.SpanID,
- ParentID: span.ParentID,
- Name: span.Name,
- Start: span.Start().At(),
- Tags: renderLabels(span.Start()),
- }
- t.unfinished[span.ID] = td
- // and wire up parents if we have them
- if !span.ParentID.IsValid() {
- return ctx
- }
- parentID := export.SpanContext{TraceID: span.ID.TraceID, SpanID: span.ParentID}
- parent, found := t.unfinished[parentID]
- if !found {
- // trace had an invalid parent, so it cannot itself be valid
- return ctx
- }
- parent.Children = append(parent.Children, td)
-
- case event.IsEnd(ev):
- // finishing, must be already in the map
- 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())
- events := span.Events()
- td.Events = make([]traceEvent, len(events))
- for i, event := range events {
- td.Events[i] = traceEvent{
- Time: event.At(),
- Tags: renderLabels(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.ParentID.IsValid() {
- fillOffsets(td, td.Start)
- }
- }
- return ctx
-}
-
-func (t *traces) getData(req *http.Request) interface{} {
- if len(t.sets) == 0 {
- return nil
- }
- data := TraceResults{}
- data.Traces = make([]*traceSet, 0, len(t.sets))
- for _, set := range t.sets {
- data.Traces = append(data.Traces, set)
- }
- sort.Slice(data.Traces, func(i, j int) bool { return data.Traces[i].Name < data.Traces[j].Name })
- if bits := strings.SplitN(req.URL.Path, "/trace/", 2); len(bits) > 1 {
- data.Selected = t.sets[bits[1]]
- }
- return data
-}
-
-func fillOffsets(td *traceData, start time.Time) {
- td.Offset = td.Start.Sub(start)
- for i := range td.Events {
- td.Events[i].Offset = td.Events[i].Time.Sub(start)
- }
- for _, child := range td.Children {
- fillOffsets(child, start)
- }
-}
-
-func renderLabels(labels label.List) string {
- buf := &bytes.Buffer{}
- for index := 0; labels.Valid(index); index++ {
- if l := labels.Label(index); l.Valid() {
- fmt.Fprintf(buf, "%v ", l)
- }
- }
- return buf.String()
-}
diff --git a/internal/lsp/definition.go b/internal/lsp/definition.go
deleted file mode 100644
index 599228a89..000000000
--- a/internal/lsp/definition.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/template"
-)
-
-func (s *Server) definition(ctx context.Context, params *protocol.DefinitionParams) ([]protocol.Location, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
- defer release()
- if !ok {
- return nil, err
- }
- if snapshot.View().FileKind(fh) == source.Tmpl {
- return template.Definition(snapshot, fh, params.Position)
- }
- ident, err := source.Identifier(ctx, snapshot, fh, params.Position)
- if err != nil {
- return nil, err
- }
- if ident.IsImport() && !snapshot.View().Options().ImportShortcut.ShowDefinition() {
- return nil, nil
- }
- var locations []protocol.Location
- for _, ref := range ident.Declaration.MappedRange {
- decRange, err := ref.Range()
- if err != nil {
- return nil, err
- }
-
- locations = append(locations, protocol.Location{
- URI: protocol.URIFromSpanURI(ref.URI()),
- Range: decRange,
- })
- }
-
- return locations, nil
-}
-
-func (s *Server) typeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) ([]protocol.Location, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.Go)
- defer release()
- if !ok {
- return nil, err
- }
- ident, err := source.Identifier(ctx, snapshot, fh, params.Position)
- if err != nil {
- return nil, err
- }
- identRange, err := ident.Type.Range()
- if err != nil {
- return nil, err
- }
- return []protocol.Location{
- {
- URI: protocol.URIFromSpanURI(ident.Type.URI()),
- Range: identRange,
- },
- }, nil
-}
diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go
deleted file mode 100644
index 3bf81226c..000000000
--- a/internal/lsp/diagnostics.go
+++ /dev/null
@@ -1,649 +0,0 @@
-// 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 lsp
-
-import (
- "context"
- "crypto/sha256"
- "fmt"
- "os"
- "path/filepath"
- "strings"
- "sync"
- "time"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/log"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/mod"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/template"
- "golang.org/x/tools/internal/lsp/work"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/xcontext"
- errors "golang.org/x/xerrors"
-)
-
-// diagnosticSource differentiates different sources of diagnostics.
-type diagnosticSource int
-
-const (
- modSource diagnosticSource = iota
- gcDetailsSource
- analysisSource
- typeCheckSource
- orphanedSource
- workSource
-)
-
-// A diagnosticReport holds results for a single diagnostic source.
-type diagnosticReport struct {
- snapshotID uint64
- publishedHash string
- diags map[string]*source.Diagnostic
-}
-
-// fileReports holds a collection of diagnostic reports for a single file, as
-// well as the hash of the last published set of diagnostics.
-type fileReports struct {
- snapshotID uint64
- publishedHash string
- reports map[diagnosticSource]diagnosticReport
-}
-
-func (d diagnosticSource) String() string {
- switch d {
- case modSource:
- return "FromSource"
- case gcDetailsSource:
- return "FromGCDetails"
- case analysisSource:
- return "FromAnalysis"
- case typeCheckSource:
- return "FromTypeChecking"
- case orphanedSource:
- return "FromOrphans"
- default:
- return fmt.Sprintf("From?%d?", d)
- }
-}
-
-// hashDiagnostics computes a hash to identify diags.
-func hashDiagnostics(diags ...*source.Diagnostic) string {
- source.SortDiagnostics(diags)
- h := sha256.New()
- for _, d := range diags {
- for _, t := range d.Tags {
- fmt.Fprintf(h, "%s", t)
- }
- for _, r := range d.Related {
- fmt.Fprintf(h, "%s%s%s", r.URI, r.Message, r.Range)
- }
- fmt.Fprintf(h, "%s%s%s%s", d.Message, d.Range, d.Severity, d.Source)
- }
- return fmt.Sprintf("%x", h.Sum(nil))
-}
-
-func (s *Server) diagnoseDetached(snapshot source.Snapshot) {
- ctx := snapshot.BackgroundContext()
- ctx = xcontext.Detach(ctx)
- s.diagnose(ctx, snapshot, false)
- s.publishDiagnostics(ctx, true, snapshot)
-}
-
-func (s *Server) diagnoseSnapshots(snapshots map[source.Snapshot][]span.URI, onDisk bool) {
- var diagnosticWG sync.WaitGroup
- for snapshot, uris := range snapshots {
- diagnosticWG.Add(1)
- go func(snapshot source.Snapshot, uris []span.URI) {
- defer diagnosticWG.Done()
- s.diagnoseSnapshot(snapshot, uris, onDisk)
- }(snapshot, uris)
- }
- diagnosticWG.Wait()
-}
-
-func (s *Server) diagnoseSnapshot(snapshot source.Snapshot, changedURIs []span.URI, onDisk bool) {
- ctx := snapshot.BackgroundContext()
- ctx, done := event.Start(ctx, "Server.diagnoseSnapshot", tag.Snapshot.Of(snapshot.ID()))
- defer done()
-
- delay := snapshot.View().Options().DiagnosticsDelay
- if delay > 0 {
- // 2-phase diagnostics.
- //
- // The first phase just parses and checks packages that have been
- // affected by file modifications (no analysis).
- //
- // The second phase does everything, and is debounced by the configured
- // delay.
- s.diagnoseChangedFiles(ctx, snapshot, changedURIs, onDisk)
- s.publishDiagnostics(ctx, false, snapshot)
- if ok := <-s.diagDebouncer.debounce(snapshot.View().Name(), snapshot.ID(), time.After(delay)); ok {
- s.diagnose(ctx, snapshot, false)
- s.publishDiagnostics(ctx, true, snapshot)
- }
- return
- }
-
- // Ignore possible workspace configuration warnings in the normal flow.
- s.diagnose(ctx, snapshot, false)
- s.publishDiagnostics(ctx, true, snapshot)
-}
-
-func (s *Server) diagnoseChangedFiles(ctx context.Context, snapshot source.Snapshot, uris []span.URI, onDisk bool) {
- ctx, done := event.Start(ctx, "Server.diagnoseChangedFiles", tag.Snapshot.Of(snapshot.ID()))
- defer done()
-
- packages := make(map[source.Package]struct{})
- for _, uri := range uris {
- // If the change is only on-disk and the file is not open, don't
- // directly request its package. It may not be a workspace package.
- if onDisk && !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 call PackagesForFile for builtin.go, as it results in a
- // command-line-arguments load.
- if snapshot.IsBuiltin(ctx, uri) {
- continue
- }
- pkgs, err := snapshot.PackagesForFile(ctx, uri, source.TypecheckFull, false)
- if err != nil {
- // 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
- }
- for _, pkg := range pkgs {
- packages[pkg] = struct{}{}
- }
- }
- var wg sync.WaitGroup
- for pkg := range packages {
- wg.Add(1)
-
- go func(pkg source.Package) {
- defer wg.Done()
-
- s.diagnosePkg(ctx, snapshot, pkg, false)
- }(pkg)
- }
- wg.Wait()
-}
-
-// diagnose is a helper function for running diagnostics with a given context.
-// Do not call it directly. forceAnalysis is only true for testing purposes.
-func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, forceAnalysis bool) {
- ctx, done := event.Start(ctx, "Server.diagnose", tag.Snapshot.Of(snapshot.ID()))
- defer done()
-
- // Wait for a free diagnostics slot.
- select {
- case <-ctx.Done():
- return
- case s.diagnosticsSema <- struct{}{}:
- }
- defer func() {
- <-s.diagnosticsSema
- }()
-
- // First, diagnose the go.mod file.
- modReports, modErr := mod.Diagnostics(ctx, snapshot)
- if ctx.Err() != nil {
- log.Trace.Log(ctx, "diagnose cancelled")
- return
- }
- if modErr != nil {
- event.Error(ctx, "warning: diagnose go.mod", modErr, tag.Directory.Of(snapshot.View().Folder().Filename()), tag.Snapshot.Of(snapshot.ID()))
- }
- for id, diags := range modReports {
- if id.URI == "" {
- event.Error(ctx, "missing URI for module diagnostics", fmt.Errorf("empty URI"), tag.Directory.Of(snapshot.View().Folder().Filename()))
- continue
- }
- s.storeDiagnostics(snapshot, id.URI, modSource, diags)
- }
-
- // Diagnose the go.work file, if it exists.
- workReports, workErr := work.Diagnostics(ctx, snapshot)
- if ctx.Err() != nil {
- log.Trace.Log(ctx, "diagnose cancelled")
- return
- }
- if workErr != nil {
- event.Error(ctx, "warning: diagnose go.work", workErr, tag.Directory.Of(snapshot.View().Folder().Filename()), tag.Snapshot.Of(snapshot.ID()))
- }
- for id, diags := range workReports {
- if id.URI == "" {
- event.Error(ctx, "missing URI for work file diagnostics", fmt.Errorf("empty URI"), tag.Directory.Of(snapshot.View().Folder().Filename()))
- continue
- }
- s.storeDiagnostics(snapshot, id.URI, workSource, diags)
- }
-
- // Diagnose all of the packages in the workspace.
- wsPkgs, err := snapshot.ActivePackages(ctx)
- if s.shouldIgnoreError(ctx, snapshot, err) {
- return
- }
- criticalErr := snapshot.GetCriticalError(ctx)
-
- // 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.
- s.showCriticalErrorStatus(ctx, snapshot, criticalErr)
-
- // There may be .tmpl files.
- for _, f := range snapshot.Templates() {
- diags := template.Diagnose(f)
- s.storeDiagnostics(snapshot, f.URI(), typeCheckSource, diags)
- }
-
- // If there are no workspace packages, there is nothing to diagnose and
- // there are no orphaned files.
- if len(wsPkgs) == 0 {
- return
- }
-
- var (
- wg sync.WaitGroup
- seen = map[span.URI]struct{}{}
- )
- for _, pkg := range wsPkgs {
- wg.Add(1)
-
- for _, pgf := range pkg.CompiledGoFiles() {
- seen[pgf.URI] = struct{}{}
- }
-
- go func(pkg source.Package) {
- defer wg.Done()
-
- s.diagnosePkg(ctx, snapshot, pkg, forceAnalysis)
- }(pkg)
- }
- wg.Wait()
-
- // Confirm that every opened file belongs to a package (if any exist in
- // the workspace). Otherwise, add a diagnostic to the file.
- for _, o := range s.session.Overlays() {
- if _, ok := seen[o.URI()]; ok {
- continue
- }
- diagnostic := s.checkForOrphanedFile(ctx, snapshot, o)
- if diagnostic == nil {
- continue
- }
- s.storeDiagnostics(snapshot, o.URI(), orphanedSource, []*source.Diagnostic{diagnostic})
- }
-}
-
-func (s *Server) diagnosePkg(ctx context.Context, snapshot source.Snapshot, pkg source.Package, alwaysAnalyze bool) {
- ctx, done := event.Start(ctx, "Server.diagnosePkg", tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(pkg.ID()))
- defer done()
- enableDiagnostics := false
- includeAnalysis := alwaysAnalyze // only run analyses for packages with open files
- for _, pgf := range pkg.CompiledGoFiles() {
- enableDiagnostics = enableDiagnostics || !snapshot.IgnoredFile(pgf.URI)
- includeAnalysis = includeAnalysis || snapshot.IsOpen(pgf.URI)
- }
- // Don't show any diagnostics on ignored files.
- if !enableDiagnostics {
- return
- }
-
- pkgDiagnostics, err := snapshot.DiagnosePackage(ctx, pkg)
- if err != nil {
- event.Error(ctx, "warning: diagnosing package", err, tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(pkg.ID()))
- return
- }
- for _, cgf := range pkg.CompiledGoFiles() {
- // builtin.go exists only for documentation purposes, and is not valid Go code.
- // Don't report distracting errors
- if !snapshot.IsBuiltin(ctx, cgf.URI) {
- s.storeDiagnostics(snapshot, cgf.URI, typeCheckSource, pkgDiagnostics[cgf.URI])
- }
- }
- if includeAnalysis && !pkg.HasListOrParseErrors() {
- reports, err := source.Analyze(ctx, snapshot, pkg, false)
- if err != nil {
- event.Error(ctx, "warning: analyzing package", err, tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(pkg.ID()))
- return
- }
- for _, cgf := range pkg.CompiledGoFiles() {
- s.storeDiagnostics(snapshot, cgf.URI, analysisSource, reports[cgf.URI])
- }
- }
-
- // If gc optimization details are requested, add them to the
- // diagnostic reports.
- s.gcOptimizationDetailsMu.Lock()
- _, enableGCDetails := s.gcOptimizationDetails[pkg.ID()]
- s.gcOptimizationDetailsMu.Unlock()
- if enableGCDetails {
- gcReports, err := source.GCOptimizationDetails(ctx, snapshot, pkg)
- if err != nil {
- event.Error(ctx, "warning: gc details", err, tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(pkg.ID()))
- }
- s.gcOptimizationDetailsMu.Lock()
- _, enableGCDetails := s.gcOptimizationDetails[pkg.ID()]
-
- // NOTE(golang/go#44826): hold the gcOptimizationDetails lock, and re-check
- // whether gc optimization details are enabled, while storing gc_details
- // results. This ensures that the toggling of GC details and clearing of
- // diagnostics does not race with storing the results here.
- if enableGCDetails {
- for id, diags := range gcReports {
- fh := snapshot.FindFile(id.URI)
- // Don't publish gc details for unsaved buffers, since the underlying
- // logic operates on the file on disk.
- if fh == nil || !fh.Saved() {
- continue
- }
- s.storeDiagnostics(snapshot, id.URI, gcDetailsSource, diags)
- }
- }
- s.gcOptimizationDetailsMu.Unlock()
- }
-}
-
-// storeDiagnostics stores results from a single diagnostic source. If merge is
-// true, it merges results into any existing results for this snapshot.
-func (s *Server) storeDiagnostics(snapshot source.Snapshot, uri span.URI, dsource diagnosticSource, diags []*source.Diagnostic) {
- // Safeguard: ensure that the file actually exists in the snapshot
- // (see golang.org/issues/38602).
- fh := snapshot.FindFile(uri)
- if fh == nil {
- return
- }
- s.diagnosticsMu.Lock()
- defer s.diagnosticsMu.Unlock()
- if s.diagnostics[uri] == nil {
- s.diagnostics[uri] = &fileReports{
- publishedHash: hashDiagnostics(), // Hash for 0 diagnostics.
- reports: map[diagnosticSource]diagnosticReport{},
- }
- }
- report := s.diagnostics[uri].reports[dsource]
- // Don't set obsolete diagnostics.
- if report.snapshotID > snapshot.ID() {
- return
- }
- if report.diags == nil || report.snapshotID != snapshot.ID() {
- report.diags = map[string]*source.Diagnostic{}
- }
- report.snapshotID = snapshot.ID()
- for _, d := range diags {
- report.diags[hashDiagnostics(d)] = d
- }
- s.diagnostics[uri].reports[dsource] = report
-}
-
-// clearDiagnosticSource clears all diagnostics for a given source type. It is
-// necessary for cases where diagnostics have been invalidated by something
-// other than a snapshot change, for example when gc_details is toggled.
-func (s *Server) clearDiagnosticSource(dsource diagnosticSource) {
- s.diagnosticsMu.Lock()
- defer s.diagnosticsMu.Unlock()
- for _, reports := range s.diagnostics {
- delete(reports.reports, dsource)
- }
-}
-
-const WorkspaceLoadFailure = "Error loading workspace"
-
-// showCriticalErrorStatus shows the error as a progress report.
-// If the error is nil, it clears any existing error progress report.
-func (s *Server) showCriticalErrorStatus(ctx context.Context, snapshot source.Snapshot, err *source.CriticalError) {
- 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 {
- event.Error(ctx, "errors loading workspace", err.MainError, tag.Snapshot.Of(snapshot.ID()), tag.Directory.Of(snapshot.View().Folder()))
- for _, d := range err.DiagList {
- s.storeDiagnostics(snapshot, d.URI, modSource, []*source.Diagnostic{d})
- }
- errMsg = strings.ReplaceAll(err.MainError.Error(), "\n", " ")
- }
-
- if s.criticalErrorStatus == nil {
- if errMsg != "" {
- 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("Done.")
- s.criticalErrorStatus = nil
- } else {
- s.criticalErrorStatus.Report(errMsg, 0)
- }
-}
-
-// checkForOrphanedFile checks that the given URIs can be mapped to packages.
-// If they cannot and the workspace is not otherwise unloaded, it also surfaces
-// a warning, suggesting that the user check the file for build tags.
-func (s *Server) checkForOrphanedFile(ctx context.Context, snapshot source.Snapshot, fh source.VersionedFileHandle) *source.Diagnostic {
- if snapshot.View().FileKind(fh) != source.Go {
- return nil
- }
- // builtin files won't have a package, but they are never orphaned.
- if snapshot.IsBuiltin(ctx, fh.URI()) {
- return nil
- }
- pkgs, err := snapshot.PackagesForFile(ctx, fh.URI(), source.TypecheckWorkspace, false)
- if len(pkgs) > 0 || err == nil {
- return nil
- }
- pgf, err := snapshot.ParseGo(ctx, fh, source.ParseHeader)
- if err != nil {
- return nil
- }
- spn, err := span.NewRange(snapshot.FileSet(), pgf.File.Name.Pos(), pgf.File.Name.End()).Span()
- if err != nil {
- return nil
- }
- rng, err := pgf.Mapper.Range(spn)
- if err != nil {
- return nil
- }
- // If the file no longer has a name ending in .go, this diagnostic is wrong
- if filepath.Ext(fh.URI().Filename()) != ".go" {
- return nil
- }
- // TODO(rstambler): We should be able to parse the build tags in the
- // file and show a more specific error message. For now, put the diagnostic
- // on the package declaration.
- return &source.Diagnostic{
- URI: fh.URI(),
- Range: rng,
- Severity: protocol.SeverityWarning,
- Source: source.ListError,
- Message: fmt.Sprintf(`No packages found for open file %s: %v.
-If this file contains build tags, try adding "-tags=<build tag>" to your gopls "buildFlags" configuration (see (https://github.com/golang/tools/blob/master/gopls/doc/settings.md#buildflags-string).
-Otherwise, see the troubleshooting guidelines for help investigating (https://github.com/golang/tools/blob/master/gopls/doc/troubleshooting.md).
-`, fh.URI().Filename(), err),
- }
-}
-
-// publishDiagnostics collects and publishes any unpublished diagnostic reports.
-func (s *Server) publishDiagnostics(ctx context.Context, final bool, snapshot source.Snapshot) {
- ctx, done := event.Start(ctx, "Server.publishDiagnostics", tag.Snapshot.Of(snapshot.ID()))
- defer done()
- s.diagnosticsMu.Lock()
- defer s.diagnosticsMu.Unlock()
-
- published := 0
- defer func() {
- log.Trace.Logf(ctx, "published %d diagnostics", published)
- }()
-
- for uri, r := range s.diagnostics {
- // Snapshot IDs are always increasing, so we use them instead of file
- // versions to create the correct order for diagnostics.
-
- // If we've already delivered diagnostics for a future snapshot for this
- // file, do not deliver them.
- if r.snapshotID > snapshot.ID() {
- continue
- }
- anyReportsChanged := false
- reportHashes := map[diagnosticSource]string{}
- var diags []*source.Diagnostic
- for dsource, report := range r.reports {
- if report.snapshotID != snapshot.ID() {
- continue
- }
- var reportDiags []*source.Diagnostic
- for _, d := range report.diags {
- diags = append(diags, d)
- reportDiags = append(reportDiags, d)
- }
- hash := hashDiagnostics(reportDiags...)
- if hash != report.publishedHash {
- anyReportsChanged = true
- }
- reportHashes[dsource] = hash
- }
-
- if !final && !anyReportsChanged {
- // Don't invalidate existing reports on the client if we haven't got any
- // new information.
- continue
- }
- source.SortDiagnostics(diags)
- hash := hashDiagnostics(diags...)
- if hash == r.publishedHash {
- // Update snapshotID to be the latest snapshot for which this diagnostic
- // hash is valid.
- r.snapshotID = snapshot.ID()
- continue
- }
- var version int32
- if fh := snapshot.FindFile(uri); fh != nil { // file may have been deleted
- version = fh.Version()
- }
- if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
- Diagnostics: toProtocolDiagnostics(diags),
- URI: protocol.URIFromSpanURI(uri),
- Version: version,
- }); err == nil {
- published++
- r.publishedHash = hash
- r.snapshotID = snapshot.ID()
- for dsource, hash := range reportHashes {
- report := r.reports[dsource]
- report.publishedHash = hash
- r.reports[dsource] = report
- }
- } else {
- if ctx.Err() != nil {
- // Publish may have failed due to a cancelled context.
- log.Trace.Log(ctx, "publish cancelled")
- return
- }
- event.Error(ctx, "publishReports: failed to deliver diagnostic", err, tag.URI.Of(uri))
- }
- }
-}
-
-func toProtocolDiagnostics(diagnostics []*source.Diagnostic) []protocol.Diagnostic {
- reports := []protocol.Diagnostic{}
- for _, diag := range diagnostics {
- related := make([]protocol.DiagnosticRelatedInformation, 0, len(diag.Related))
- for _, rel := range diag.Related {
- related = append(related, protocol.DiagnosticRelatedInformation{
- Location: protocol.Location{
- URI: protocol.URIFromSpanURI(rel.URI),
- Range: rel.Range,
- },
- Message: rel.Message,
- })
- }
- 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: diag.Tags,
- RelatedInformation: related,
- }
- 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(ctx context.Context, snapshot source.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.
- var hasGo bool
- _ = filepath.Walk(snapshot.View().Folder().Filename(), func(path 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
-}
-
-// Diagnostics formattedfor the debug server
-// (all the relevant fields of Server are private)
-// (The alternative is to export them)
-func (s *Server) Diagnostics() map[string][]string {
- ans := make(map[string][]string)
- s.diagnosticsMu.Lock()
- defer s.diagnosticsMu.Unlock()
- for k, v := range s.diagnostics {
- fn := k.Filename()
- for typ, d := range v.reports {
- if len(d.diags) == 0 {
- continue
- }
- for _, dx := range d.diags {
- ans[fn] = append(ans[fn], auxStr(dx, d, typ))
- }
- }
- }
- return ans
-}
-
-func auxStr(v *source.Diagnostic, d diagnosticReport, typ diagnosticSource) string {
- // Tags? RelatedInformation?
- msg := fmt.Sprintf("(%s)%q(source:%q,code:%q,severity:%s,snapshot:%d,type:%s)",
- v.Range, v.Message, v.Source, v.Code, v.Severity, d.snapshotID, typ)
- for _, r := range v.Related {
- msg += fmt.Sprintf(" [%s:%s,%q]", r.URI.Filename(), r.Range, r.Message)
- }
- return msg
-}
diff --git a/internal/lsp/diff/diff.go b/internal/lsp/diff/diff.go
deleted file mode 100644
index 5d8c69ca5..000000000
--- a/internal/lsp/diff/diff.go
+++ /dev/null
@@ -1,159 +0,0 @@
-// 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 supports a pluggable diff algorithm.
-package diff
-
-import (
- "sort"
- "strings"
-
- "golang.org/x/tools/internal/span"
-)
-
-// TextEdit represents a change to a section of a document.
-// The text within the specified span should be replaced by the supplied new text.
-type TextEdit struct {
- Span span.Span
- NewText string
-}
-
-// ComputeEdits is the type for a function that produces a set of edits that
-// convert from the before content to the after content.
-type ComputeEdits func(uri span.URI, before, after string) ([]TextEdit, error)
-
-// SortTextEdits attempts to order all edits by their starting points.
-// The sort is stable so that edits with the same starting point will not
-// be reordered.
-func SortTextEdits(d []TextEdit) {
- // Use a stable sort to maintain the order of edits inserted at the same position.
- sort.SliceStable(d, func(i int, j int) bool {
- return span.Compare(d[i].Span, d[j].Span) < 0
- })
-}
-
-// ApplyEdits applies the set of edits to the before and returns the resulting
-// content.
-// It may panic or produce garbage if the edits are not valid for the provided
-// before content.
-func ApplyEdits(before string, edits []TextEdit) string {
- // Preconditions:
- // - all of the edits apply to before
- // - and all the spans for each TextEdit have the same URI
- if len(edits) == 0 {
- return before
- }
- _, edits, _ = prepareEdits(before, edits)
- after := strings.Builder{}
- last := 0
- for _, edit := range edits {
- start := edit.Span.Start().Offset()
- if start > last {
- after.WriteString(before[last:start])
- last = start
- }
- after.WriteString(edit.NewText)
- last = edit.Span.End().Offset()
- }
- if last < len(before) {
- after.WriteString(before[last:])
- }
- return after.String()
-}
-
-// LineEdits takes a set of edits and expands and merges them as necessary
-// to ensure that there are only full line edits left when it is done.
-func LineEdits(before string, edits []TextEdit) []TextEdit {
- if len(edits) == 0 {
- return nil
- }
- c, edits, partial := prepareEdits(before, edits)
- if partial {
- edits = lineEdits(before, c, edits)
- }
- return edits
-}
-
-// prepareEdits returns a sorted copy of the edits
-func prepareEdits(before string, edits []TextEdit) (*span.TokenConverter, []TextEdit, bool) {
- partial := false
- c := span.NewContentConverter("", []byte(before))
- copied := make([]TextEdit, len(edits))
- for i, edit := range edits {
- edit.Span, _ = edit.Span.WithAll(c)
- copied[i] = edit
- partial = partial ||
- edit.Span.Start().Offset() >= len(before) ||
- edit.Span.Start().Column() > 1 || edit.Span.End().Column() > 1
- }
- SortTextEdits(copied)
- return c, copied, partial
-}
-
-// lineEdits rewrites the edits to always be full line edits
-func lineEdits(before string, c *span.TokenConverter, edits []TextEdit) []TextEdit {
- adjusted := make([]TextEdit, 0, len(edits))
- current := TextEdit{Span: span.Invalid}
- for _, edit := range edits {
- if current.Span.IsValid() && edit.Span.Start().Line() <= current.Span.End().Line() {
- // overlaps with the current edit, need to combine
- // first get the gap from the previous edit
- gap := before[current.Span.End().Offset():edit.Span.Start().Offset()]
- // now add the text of this edit
- current.NewText += gap + edit.NewText
- // and then adjust the end position
- current.Span = span.New(current.Span.URI(), current.Span.Start(), edit.Span.End())
- } else {
- // does not overlap, add previous run (if there is one)
- adjusted = addEdit(before, adjusted, current)
- // and then remember this edit as the start of the next run
- current = edit
- }
- }
- // add the current pending run if there is one
- return addEdit(before, adjusted, current)
-}
-
-func addEdit(before string, edits []TextEdit, edit TextEdit) []TextEdit {
- if !edit.Span.IsValid() {
- return edits
- }
- // if edit is partial, expand it to full line now
- start := edit.Span.Start()
- end := edit.Span.End()
- if start.Column() > 1 {
- // prepend the text and adjust to start of line
- delta := start.Column() - 1
- start = span.NewPoint(start.Line(), 1, start.Offset()-delta)
- edit.Span = span.New(edit.Span.URI(), start, end)
- edit.NewText = before[start.Offset():start.Offset()+delta] + edit.NewText
- }
- if start.Offset() >= len(before) && start.Line() > 1 && before[len(before)-1] != '\n' {
- // after end of file that does not end in eol, so join to last line of file
- // to do this we need to know where the start of the last line was
- eol := strings.LastIndex(before, "\n")
- if eol < 0 {
- // file is one non terminated line
- eol = 0
- }
- delta := len(before) - eol
- start = span.NewPoint(start.Line()-1, 1, start.Offset()-delta)
- edit.Span = span.New(edit.Span.URI(), start, end)
- edit.NewText = before[start.Offset():start.Offset()+delta] + edit.NewText
- }
- if end.Column() > 1 {
- remains := before[end.Offset():]
- eol := strings.IndexRune(remains, '\n')
- if eol < 0 {
- eol = len(remains)
- } else {
- eol++
- }
- end = span.NewPoint(end.Line()+1, 1, end.Offset()+eol)
- edit.Span = span.New(edit.Span.URI(), start, end)
- edit.NewText = edit.NewText + remains[:eol]
- }
- edits = append(edits, edit)
- return edits
-}
diff --git a/internal/lsp/diff/diff_test.go b/internal/lsp/diff/diff_test.go
deleted file mode 100644
index dd9414e5d..000000000
--- a/internal/lsp/diff/diff_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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 (
- "fmt"
- "testing"
-
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/diff/difftest"
- "golang.org/x/tools/internal/span"
-)
-
-func TestApplyEdits(t *testing.T) {
- for _, tc := range difftest.TestCases {
- t.Run(tc.Name, func(t *testing.T) {
- t.Helper()
- if got := diff.ApplyEdits(tc.In, tc.Edits); got != tc.Out {
- t.Errorf("ApplyEdits edits got %q, want %q", got, tc.Out)
- }
- if tc.LineEdits != nil {
- if got := diff.ApplyEdits(tc.In, tc.LineEdits); got != tc.Out {
- t.Errorf("ApplyEdits lineEdits got %q, want %q", got, tc.Out)
- }
- }
- })
- }
-}
-
-func TestLineEdits(t *testing.T) {
- for _, tc := range difftest.TestCases {
- t.Run(tc.Name, func(t *testing.T) {
- t.Helper()
- // if line edits not specified, it is the same as edits
- edits := tc.LineEdits
- if edits == nil {
- edits = tc.Edits
- }
- if got := diff.LineEdits(tc.In, tc.Edits); diffEdits(got, edits) {
- t.Errorf("LineEdits got %q, want %q", got, edits)
- }
- })
- }
-}
-
-func TestUnified(t *testing.T) {
- for _, tc := range difftest.TestCases {
- t.Run(tc.Name, func(t *testing.T) {
- t.Helper()
- unified := fmt.Sprint(diff.ToUnified(difftest.FileA, difftest.FileB, tc.In, tc.Edits))
- if unified != tc.Unified {
- t.Errorf("edits got diff:\n%v\nexpected:\n%v", unified, tc.Unified)
- }
- if tc.LineEdits != nil {
- unified := fmt.Sprint(diff.ToUnified(difftest.FileA, difftest.FileB, tc.In, tc.LineEdits))
- if unified != tc.Unified {
- t.Errorf("lineEdits got diff:\n%v\nexpected:\n%v", unified, tc.Unified)
- }
- }
- })
- }
-}
-
-func diffEdits(got, want []diff.TextEdit) bool {
- if len(got) != len(want) {
- return true
- }
- for i, w := range want {
- g := got[i]
- if span.Compare(w.Span, g.Span) != 0 {
- return true
- }
- if w.NewText != g.NewText {
- return true
- }
- }
- return false
-}
diff --git a/internal/lsp/diff/difftest/difftest.go b/internal/lsp/diff/difftest/difftest.go
deleted file mode 100644
index 0e014bc30..000000000
--- a/internal/lsp/diff/difftest/difftest.go
+++ /dev/null
@@ -1,243 +0,0 @@
-// 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
-// "golang.org/x/tools/internal/lsp/diff"
-package difftest
-
-import (
- "fmt"
- "testing"
-
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/span"
-)
-
-const (
- FileA = "from"
- FileB = "to"
- UnifiedPrefix = "--- " + FileA + "\n+++ " + FileB + "\n"
-)
-
-var TestCases = []struct {
- Name, In, Out, Unified string
- Edits, LineEdits []diff.TextEdit
- 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.TextEdit{{Span: newSpan(0, 5), NewText: "cheese"}},
- LineEdits: []diff.TextEdit{{Span: newSpan(0, 6), NewText: "cheese\n"}},
-}, {
- Name: "insert_rune",
- In: "gord\n",
- Out: "gourd\n",
- Unified: UnifiedPrefix + `
-@@ -1 +1 @@
--gord
-+gourd
-`[1:],
- Edits: []diff.TextEdit{{Span: newSpan(2, 2), NewText: "u"}},
- LineEdits: []diff.TextEdit{{Span: newSpan(0, 5), NewText: "gourd\n"}},
-}, {
- Name: "delete_rune",
- In: "groat\n",
- Out: "goat\n",
- Unified: UnifiedPrefix + `
-@@ -1 +1 @@
--groat
-+goat
-`[1:],
- Edits: []diff.TextEdit{{Span: newSpan(1, 2), NewText: ""}},
- LineEdits: []diff.TextEdit{{Span: newSpan(0, 6), NewText: "goat\n"}},
-}, {
- Name: "replace_rune",
- In: "loud\n",
- Out: "lord\n",
- Unified: UnifiedPrefix + `
-@@ -1 +1 @@
--loud
-+lord
-`[1:],
- Edits: []diff.TextEdit{{Span: newSpan(2, 3), NewText: "r"}},
- LineEdits: []diff.TextEdit{{Span: newSpan(0, 5), NewText: "lord\n"}},
-}, {
- Name: "replace_partials",
- In: "blanket\n",
- Out: "bunker\n",
- Unified: UnifiedPrefix + `
-@@ -1 +1 @@
--blanket
-+bunker
-`[1:],
- Edits: []diff.TextEdit{
- {Span: newSpan(1, 3), NewText: "u"},
- {Span: newSpan(6, 7), NewText: "r"},
- },
- LineEdits: []diff.TextEdit{{Span: newSpan(0, 8), NewText: "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.TextEdit{{Span: newSpan(7, 7), NewText: "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.TextEdit{{Span: newSpan(0, 1), NewText: "B"}},
-}, {
- 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.TextEdit{{Span: newSpan(1, 1), NewText: "B"}},
- LineEdits: []diff.TextEdit{{Span: newSpan(0, 1), NewText: "AB"}},
-}, {
- Name: "add_newline",
- In: "A",
- Out: "A\n",
- Unified: UnifiedPrefix + `
-@@ -1 +1 @@
--A
-\ No newline at end of file
-+A
-`[1:],
- Edits: []diff.TextEdit{{Span: newSpan(1, 1), NewText: "\n"}},
- LineEdits: []diff.TextEdit{{Span: newSpan(0, 1), NewText: "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:],
- Edits: []diff.TextEdit{
- {Span: newSpan(0, 4), NewText: ""},
- {Span: newSpan(6, 6), NewText: "B\n"},
- {Span: newSpan(10, 12), NewText: ""},
- {Span: newSpan(14, 14), NewText: "C\n"},
- },
- NoDiff: true, // diff algorithm produces different delete/insert pattern
-},
- {
- 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.TextEdit{{Span: newSpan(2, 3), NewText: "C\n"}},
- LineEdits: []diff.TextEdit{{Span: newSpan(2, 4), NewText: "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.TextEdit{
- {Span: newSpan(2, 8), NewText: "H\nI\nJ\n"},
- {Span: newSpan(12, 14), NewText: "K\n"},
- },
- NoDiff: true, // diff algorithm produces different delete/insert pattern
- },
-}
-
-func init() {
- // expand all the spans to full versions
- // we need them all to have their line number and column
- for _, tc := range TestCases {
- c := span.NewContentConverter("", []byte(tc.In))
- for i := range tc.Edits {
- tc.Edits[i].Span, _ = tc.Edits[i].Span.WithAll(c)
- }
- for i := range tc.LineEdits {
- tc.LineEdits[i].Span, _ = tc.LineEdits[i].Span.WithAll(c)
- }
- }
-}
-
-func DiffTest(t *testing.T, compute diff.ComputeEdits) {
- t.Helper()
- for _, test := range TestCases {
- t.Run(test.Name, func(t *testing.T) {
- t.Helper()
- edits, err := compute(span.URIFromPath("/"+test.Name), test.In, test.Out)
- if err != nil {
- t.Fatal(err)
- }
- got := diff.ApplyEdits(test.In, edits)
- unified := fmt.Sprint(diff.ToUnified(FileA, FileB, test.In, edits))
- if got != test.Out {
- t.Errorf("got patched:\n%v\nfrom diff:\n%v\nexpected:\n%v", got, unified, test.Out)
- }
- if !test.NoDiff && unified != test.Unified {
- t.Errorf("got diff:\n%v\nexpected:\n%v", unified, test.Unified)
- }
- })
- }
-}
-
-func newSpan(start, end int) span.Span {
- return span.New("", span.NewPoint(0, 0, start), span.NewPoint(0, 0, end))
-}
diff --git a/internal/lsp/diff/difftest/difftest_test.go b/internal/lsp/diff/difftest/difftest_test.go
deleted file mode 100644
index fd7ecf959..000000000
--- a/internal/lsp/diff/difftest/difftest_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// 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
-// "golang.org/x/tools/internal/lsp/diff"
-package difftest_test
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "os/exec"
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/lsp/diff/difftest"
- "golang.org/x/tools/internal/testenv"
-)
-
-func TestVerifyUnified(t *testing.T) {
- testenv.NeedsTool(t, "diff")
- for _, test := range difftest.TestCases {
- t.Run(test.Name, func(t *testing.T) {
- t.Helper()
- 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%q\ndiff -u:\n%q", test.Unified, diff)
- }
- })
- }
-}
-
-func getDiffOutput(a, b string) (string, error) {
- fileA, err := ioutil.TempFile("", "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 := ioutil.TempFile("", "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.CombinedOutput()
- if err != nil {
- if _, ok := err.(*exec.ExitError); !ok {
- return "", fmt.Errorf("failed to run diff -u %v %v: %v\n%v", fileA.Name(), fileB.Name(), err, string(out))
- }
- }
- 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/internal/lsp/diff/myers/diff.go b/internal/lsp/diff/myers/diff.go
deleted file mode 100644
index a59475058..000000000
--- a/internal/lsp/diff/myers/diff.go
+++ /dev/null
@@ -1,205 +0,0 @@
-// 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"
-
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/span"
-)
-
-// 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
-
-func ComputeEdits(uri span.URI, before, after string) ([]diff.TextEdit, error) {
- ops := operations(splitLines(before), splitLines(after))
- edits := make([]diff.TextEdit, 0, len(ops))
- for _, op := range ops {
- s := span.New(uri, span.NewPoint(op.I1+1, 1, 0), span.NewPoint(op.I2+1, 1, 0))
- switch op.Kind {
- case diff.Delete:
- // Delete: unformatted[i1:i2] is deleted.
- edits = append(edits, diff.TextEdit{Span: s})
- case diff.Insert:
- // Insert: formatted[j1:j2] is inserted at unformatted[i1:i1].
- if content := strings.Join(op.Content, ""); content != "" {
- edits = append(edits, diff.TextEdit{Span: s, NewText: content})
- }
- }
- }
- return edits, nil
-}
-
-type operation struct {
- Kind diff.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 == diff.Insert {
- 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: diff.Delete,
- 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: diff.Insert,
- 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/internal/lsp/diff/myers/diff_test.go b/internal/lsp/diff/myers/diff_test.go
deleted file mode 100644
index bce0399c5..000000000
--- a/internal/lsp/diff/myers/diff_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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"
-
- "golang.org/x/tools/internal/lsp/diff/difftest"
- "golang.org/x/tools/internal/lsp/diff/myers"
-)
-
-func TestDiff(t *testing.T) {
- difftest.DiffTest(t, myers.ComputeEdits)
-}
diff --git a/internal/lsp/diff/unified.go b/internal/lsp/diff/unified.go
deleted file mode 100644
index b2e630eff..000000000
--- a/internal/lsp/diff/unified.go
+++ /dev/null
@@ -1,210 +0,0 @@
-// 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"
- "strings"
-)
-
-// 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 (
- // Delete is the operation kind for a line that is present in the input
- // but not in the output.
- Delete OpKind = iota
- // Insert is the operation kind for a line that is new in the output.
- Insert
- // Equal is the operation kind for a line that is the same in the input and
- // output, often used to provide context around edited lines.
- Equal
-)
-
-// String returns a human readable representation of an OpKind. It is not
-// intended for machine processing.
-func (k OpKind) String() string {
- switch k {
- case Delete:
- return "delete"
- case Insert:
- return "insert"
- case Equal:
- return "equal"
- default:
- panic("unknown operation kind")
- }
-}
-
-const (
- edge = 3
- gap = edge * 2
-)
-
-// ToUnified takes a file contents and a sequence of edits, and calculates
-// a unified diff that represents those edits.
-func ToUnified(from, to string, content string, edits []TextEdit) Unified {
- u := Unified{
- From: from,
- To: to,
- }
- if len(edits) == 0 {
- return u
- }
- c, edits, partial := prepareEdits(content, edits)
- if partial {
- edits = lineEdits(content, c, edits)
- }
- lines := splitLines(content)
- var h *Hunk
- last := 0
- toLine := 0
- for _, edit := range edits {
- start := edit.Span.Start().Line() - 1
- end := edit.Span.End().Line() - 1
- 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+edge)
- 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-edge, start)
- h.FromLine -= delta
- h.ToLine -= delta
- }
- last = start
- for i := start; i < end; i++ {
- h.Lines = append(h.Lines, Line{Kind: Delete, Content: lines[i]})
- last++
- }
- if edit.NewText != "" {
- for _, line := range splitLines(edit.NewText) {
- h.Lines = append(h.Lines, Line{Kind: Insert, Content: line})
- toLine++
- }
- }
- }
- if h != nil {
- // add the edge to the final hunk
- addEqualLines(h, lines, last, last+edge)
- u.Hunks = append(u.Hunks, h)
- }
- return u
-}
-
-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: Equal, Content: lines[i]})
- delta++
- }
- return delta
-}
-
-// Format 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) Format(f fmt.State, r rune) {
- if len(u.Hunks) == 0 {
- return
- }
- fmt.Fprintf(f, "--- %s\n", u.From)
- fmt.Fprintf(f, "+++ %s\n", u.To)
- for _, hunk := range u.Hunks {
- fromCount, toCount := 0, 0
- for _, l := range hunk.Lines {
- switch l.Kind {
- case Delete:
- fromCount++
- case Insert:
- toCount++
- default:
- fromCount++
- toCount++
- }
- }
- fmt.Fprint(f, "@@")
- if fromCount > 1 {
- fmt.Fprintf(f, " -%d,%d", hunk.FromLine, fromCount)
- } else {
- fmt.Fprintf(f, " -%d", hunk.FromLine)
- }
- if toCount > 1 {
- fmt.Fprintf(f, " +%d,%d", hunk.ToLine, toCount)
- } else {
- fmt.Fprintf(f, " +%d", hunk.ToLine)
- }
- fmt.Fprint(f, " @@\n")
- for _, l := range hunk.Lines {
- switch l.Kind {
- case Delete:
- fmt.Fprintf(f, "-%s", l.Content)
- case Insert:
- fmt.Fprintf(f, "+%s", l.Content)
- default:
- fmt.Fprintf(f, " %s", l.Content)
- }
- if !strings.HasSuffix(l.Content, "\n") {
- fmt.Fprintf(f, "\n\\ No newline at end of file\n")
- }
- }
- }
-}
diff --git a/internal/lsp/fake/client.go b/internal/lsp/fake/client.go
deleted file mode 100644
index fdc67a6cc..000000000
--- a/internal/lsp/fake/client.go
+++ /dev/null
@@ -1,128 +0,0 @@
-// 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"
- "fmt"
-
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-// ClientHooks are called to handle the corresponding client LSP method.
-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
- OnShowMessage func(context.Context, *protocol.ShowMessageParams) error
- OnShowMessageRequest func(context.Context, *protocol.ShowMessageRequestParams) error
- OnRegistration func(context.Context, *protocol.RegistrationParams) error
- OnUnregistration func(context.Context, *protocol.UnregistrationParams) error
-}
-
-// Client is an adapter that converts an *Editor into an LSP Client. It mosly
-// delegates functionality to hooks that can be configured by tests.
-type Client struct {
- editor *Editor
- hooks ClientHooks
-}
-
-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 len(params.Actions) == 0 || len(params.Actions) > 1 {
- return nil, fmt.Errorf("fake editor cannot handle multiple action items")
- }
- return &params.Actions[0], nil
-}
-
-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.Section != "gopls" {
- continue
- }
- results[i] = c.editor.configuration()
- }
- return results, nil
-}
-
-func (c *Client) RegisterCapability(ctx context.Context, params *protocol.RegistrationParams) error {
- if c.hooks.OnRegistration != nil {
- return c.hooks.OnRegistration(ctx, params)
- }
- return nil
-}
-
-func (c *Client) UnregisterCapability(ctx context.Context, params *protocol.UnregistrationParams) error {
- if c.hooks.OnUnregistration != nil {
- return c.hooks.OnUnregistration(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(context.Context, *protocol.ShowDocumentParams) (*protocol.ShowDocumentResult, error) {
- return nil, nil
-}
-
-// ApplyEdit applies edits sent from the server.
-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
- }
- for _, change := range params.Edit.DocumentChanges {
- if err := c.editor.applyProtocolEdit(ctx, change); err != nil {
- return nil, err
- }
- }
- return &protocol.ApplyWorkspaceEditResult{Applied: true}, nil
-}
diff --git a/internal/lsp/fake/doc.go b/internal/lsp/fake/doc.go
deleted file mode 100644
index 6051781de..000000000
--- a/internal/lsp/fake/doc.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// 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 fake implementations of a text editor, LSP client
-// plugin, and Sandbox environment for use in 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/internal/lsp/fake/edit.go b/internal/lsp/fake/edit.go
deleted file mode 100644
index 8b04c390f..000000000
--- a/internal/lsp/fake/edit.go
+++ /dev/null
@@ -1,157 +0,0 @@
-// 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"
- "sort"
- "strings"
-
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-// Pos represents a position in a text buffer. Both Line and Column are
-// 0-indexed.
-type Pos struct {
- Line, Column int
-}
-
-func (p Pos) String() string {
- return fmt.Sprintf("%v:%v", p.Line, p.Column)
-}
-
-// Range corresponds to protocol.Range, but uses the editor friend Pos
-// instead of UTF-16 oriented protocol.Position
-type Range struct {
- Start Pos
- End Pos
-}
-
-func (p Pos) ToProtocolPosition() protocol.Position {
- return protocol.Position{
- Line: uint32(p.Line),
- Character: uint32(p.Column),
- }
-}
-
-func fromProtocolPosition(pos protocol.Position) Pos {
- return Pos{
- Line: int(pos.Line),
- Column: int(pos.Character),
- }
-}
-
-// Edit represents a single (contiguous) buffer edit.
-type Edit struct {
- Start, End Pos
- Text string
-}
-
-// Location is the editor friendly equivalent of protocol.Location
-type Location struct {
- Path string
- Range Range
-}
-
-// SymbolInformation is an editor friendly version of
-// protocol.SymbolInformation, with location information transformed to byte
-// offsets. Field names correspond to the protocol type.
-type SymbolInformation struct {
- Name string
- Kind protocol.SymbolKind
- Location Location
-}
-
-// NewEdit creates an edit replacing all content between
-// (startLine, startColumn) and (endLine, endColumn) with text.
-func NewEdit(startLine, startColumn, endLine, endColumn int, text string) Edit {
- return Edit{
- Start: Pos{Line: startLine, Column: startColumn},
- End: Pos{Line: endLine, Column: endColumn},
- Text: text,
- }
-}
-
-func (e Edit) toProtocolChangeEvent() protocol.TextDocumentContentChangeEvent {
- return protocol.TextDocumentContentChangeEvent{
- Range: &protocol.Range{
- Start: e.Start.ToProtocolPosition(),
- End: e.End.ToProtocolPosition(),
- },
- Text: e.Text,
- }
-}
-
-func fromProtocolTextEdit(textEdit protocol.TextEdit) Edit {
- return Edit{
- Start: fromProtocolPosition(textEdit.Range.Start),
- End: fromProtocolPosition(textEdit.Range.End),
- Text: textEdit.NewText,
- }
-}
-
-// inText reports whether p is a valid position in the text buffer.
-func inText(p Pos, content []string) bool {
- if p.Line < 0 || p.Line >= len(content) {
- return false
- }
- // Note the strict right bound: the column indexes character _separators_,
- // not characters.
- if p.Column < 0 || p.Column > len([]rune(content[p.Line])) {
- return false
- }
- return true
-}
-
-// editContent implements a simplistic, inefficient algorithm for applying text
-// edits to our buffer representation. It returns an error if the edit is
-// invalid for the current content.
-func editContent(content []string, edits []Edit) ([]string, error) {
- newEdits := make([]Edit, len(edits))
- copy(newEdits, edits)
- sort.Slice(newEdits, func(i, j int) bool {
- if newEdits[i].Start.Line < newEdits[j].Start.Line {
- return true
- }
- if newEdits[i].Start.Line > newEdits[j].Start.Line {
- return false
- }
- return newEdits[i].Start.Column < newEdits[j].Start.Column
- })
-
- // Validate edits.
- for _, edit := range newEdits {
- if edit.End.Line < edit.Start.Line || (edit.End.Line == edit.Start.Line && edit.End.Column < edit.Start.Column) {
- return nil, fmt.Errorf("invalid edit: end %v before start %v", edit.End, edit.Start)
- }
- if !inText(edit.Start, content) {
- return nil, fmt.Errorf("start position %v is out of bounds", edit.Start)
- }
- if !inText(edit.End, content) {
- return nil, fmt.Errorf("end position %v is out of bounds", edit.End)
- }
- }
-
- var (
- b strings.Builder
- line, column int
- )
- advance := func(toLine, toColumn int) {
- for ; line < toLine; line++ {
- b.WriteString(string([]rune(content[line])[column:]) + "\n")
- column = 0
- }
- b.WriteString(string([]rune(content[line])[column:toColumn]))
- column = toColumn
- }
- for _, edit := range newEdits {
- advance(edit.Start.Line, edit.Start.Column)
- b.WriteString(edit.Text)
- line = edit.End.Line
- column = edit.End.Column
- }
- advance(len(content)-1, len([]rune(content[len(content)-1])))
- return strings.Split(b.String(), "\n"), nil
-}
diff --git a/internal/lsp/fake/edit_test.go b/internal/lsp/fake/edit_test.go
deleted file mode 100644
index 4fa23bdb7..000000000
--- a/internal/lsp/fake/edit_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// 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 (
- "strings"
- "testing"
-)
-
-func TestApplyEdit(t *testing.T) {
- tests := []struct {
- label string
- content string
- edits []Edit
- want string
- wantErr bool
- }{
- {
- label: "empty content",
- },
- {
- label: "empty edit",
- content: "hello",
- edits: []Edit{},
- want: "hello",
- },
- {
- label: "unicode edit",
- content: "hello, 日本語",
- edits: []Edit{{
- Start: Pos{Line: 0, Column: 7},
- End: Pos{Line: 0, Column: 10},
- Text: "world",
- }},
- want: "hello, world",
- },
- {
- label: "range edit",
- content: "ABC\nDEF\nGHI\nJKL",
- edits: []Edit{{
- Start: Pos{Line: 1, Column: 1},
- End: Pos{Line: 2, Column: 3},
- Text: "12\n345",
- }},
- want: "ABC\nD12\n345\nJKL",
- },
- {
- label: "end before start",
- content: "ABC\nDEF\nGHI\nJKL",
- edits: []Edit{{
- End: Pos{Line: 1, Column: 1},
- Start: Pos{Line: 2, Column: 3},
- Text: "12\n345",
- }},
- wantErr: true,
- },
- {
- label: "out of bounds line",
- content: "ABC\nDEF\nGHI\nJKL",
- edits: []Edit{{
- Start: Pos{Line: 1, Column: 1},
- End: Pos{Line: 4, Column: 3},
- Text: "12\n345",
- }},
- wantErr: true,
- },
- {
- label: "out of bounds column",
- content: "ABC\nDEF\nGHI\nJKL",
- edits: []Edit{{
- Start: Pos{Line: 1, Column: 4},
- End: Pos{Line: 2, Column: 3},
- Text: "12\n345",
- }},
- wantErr: true,
- },
- }
-
- for _, test := range tests {
- test := test
- t.Run(test.label, func(t *testing.T) {
- lines := strings.Split(test.content, "\n")
- newLines, err := editContent(lines, test.edits)
- if (err != nil) != test.wantErr {
- t.Errorf("got err %v, want error: %t", err, test.wantErr)
- }
- if err != nil {
- return
- }
- if got := strings.Join(newLines, "\n"); got != test.want {
- t.Errorf("got %q, want %q", got, test.want)
- }
- })
- }
-}
diff --git a/internal/lsp/fake/editor.go b/internal/lsp/fake/editor.go
deleted file mode 100644
index 5bce5609f..000000000
--- a/internal/lsp/fake/editor.go
+++ /dev/null
@@ -1,1258 +0,0 @@
-// 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 (
- "bufio"
- "context"
- "fmt"
- "os"
- "path"
- "path/filepath"
- "regexp"
- "strings"
- "sync"
-
- "golang.org/x/tools/internal/jsonrpc2"
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// Editor is a fake editor client. It keeps track of client state and can be
-// used for writing LSP tests.
-type Editor struct {
- Config EditorConfig
-
- // Server, client, and sandbox are concurrency safe and written only
- // at construction time, so do not require synchronization.
- Server protocol.Server
- serverConn jsonrpc2.Conn
- client *Client
- sandbox *Sandbox
- defaultEnv map[string]string
-
- // Since this editor is intended just for testing, we use very coarse
- // locking.
- mu sync.Mutex
- // Editor state.
- buffers map[string]buffer
- // Capabilities / Options
- serverCapabilities protocol.ServerCapabilities
-
- // 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
-}
-
-type CallCounts struct {
- DidOpen, DidChange, DidSave, DidChangeWatchedFiles, DidClose uint64
-}
-
-type buffer struct {
- windowsLineEndings bool
- version int
- path string
- lines []string
- dirty bool
-}
-
-func (b buffer) text() string {
- eol := "\n"
- if b.windowsLineEndings {
- eol = "\r\n"
- }
- return strings.Join(b.lines, eol)
-}
-
-// EditorConfig configures the editor's LSP session. This is similar to
-// source.UserOptions, but we use a separate type here so that we expose only
-// that configuration which we support.
-//
-// The zero value for EditorConfig should correspond to its defaults.
-type EditorConfig struct {
- Env map[string]string
- BuildFlags []string
-
- // CodeLenses is a map defining whether codelens are enabled, keyed by the
- // codeLens command. CodeLenses which are not present in this map are left in
- // their default state.
- CodeLenses map[string]bool
-
- // SymbolMatcher is the config associated with the "symbolMatcher" gopls
- // config option.
- SymbolMatcher, SymbolStyle *string
-
- // LimitWorkspaceScope is true if the user does not want to expand their
- // workspace scope to the entire module.
- LimitWorkspaceScope bool
-
- // WorkspaceFolders is the workspace folders to configure on the LSP server,
- // relative to the sandbox workdir.
- //
- // As a special case, 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
-
- // EnableStaticcheck enables staticcheck analyzers.
- EnableStaticcheck bool
-
- // AllExperiments sets the "allExperiments" configuration, which enables
- // all of gopls's opt-in settings.
- AllExperiments bool
-
- // Whether to send the current process ID, for testing data that is joined to
- // the PID. This can only be set by one test.
- SendPID bool
-
- // 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 arbitrary additional settings to apply to the gopls config.
- // TODO(rfindley): replace existing EditorConfig fields with Settings.
- Settings map[string]interface{}
-
- ImportShortcut string
- DirectoryFilters []string
- VerboseOutput bool
- ExperimentalUseInvalidMetadata bool
-}
-
-// NewEditor Creates a new Editor.
-func NewEditor(sandbox *Sandbox, config EditorConfig) *Editor {
- return &Editor{
- buffers: make(map[string]buffer),
- sandbox: sandbox,
- defaultEnv: sandbox.GoEnv(),
- 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)
-func (e *Editor) Connect(ctx context.Context, conn jsonrpc2.Conn, hooks ClientHooks) (*Editor, error) {
- e.serverConn = conn
- e.Server = protocol.ServerDispatcher(conn)
- e.client = &Client{editor: e, hooks: hooks}
- conn.Go(ctx,
- protocol.Handlers(
- protocol.ClientHandler(e.client,
- jsonrpc2.MethodNotFound)))
- if err := e.initialize(ctx, e.Config.WorkspaceFolders); 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 errors.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 errors.Errorf("Exit: %w", err)
- }
- }
- return nil
-}
-
-// Close issues the shutdown and exit sequence an editor should.
-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
- }
- // 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 errors.Errorf("connection not closed: %w", ctx.Err())
- }
-}
-
-// Client returns the LSP client for this editor.
-func (e *Editor) Client() *Client {
- return e.client
-}
-
-func (e *Editor) overlayEnv() map[string]string {
- env := make(map[string]string)
- for k, v := range e.defaultEnv {
- v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", e.sandbox.Workdir.RootURI().SpanURI().Filename())
- env[k] = v
- }
- for k, v := range e.Config.Env {
- v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", e.sandbox.Workdir.RootURI().SpanURI().Filename())
- env[k] = v
- }
- return env
-}
-
-func (e *Editor) configuration() map[string]interface{} {
- config := map[string]interface{}{
- "verboseWorkDoneProgress": true,
- "env": e.overlayEnv(),
- "expandWorkspaceToModule": !e.Config.LimitWorkspaceScope,
- "completionBudget": "10s",
- }
-
- for k, v := range e.Config.Settings {
- config[k] = v
- }
-
- if e.Config.BuildFlags != nil {
- config["buildFlags"] = e.Config.BuildFlags
- }
- if e.Config.DirectoryFilters != nil {
- config["directoryFilters"] = e.Config.DirectoryFilters
- }
- if e.Config.ExperimentalUseInvalidMetadata {
- config["experimentalUseInvalidMetadata"] = true
- }
- if e.Config.CodeLenses != nil {
- config["codelenses"] = e.Config.CodeLenses
- }
- if e.Config.SymbolMatcher != nil {
- config["symbolMatcher"] = *e.Config.SymbolMatcher
- }
- if e.Config.SymbolStyle != nil {
- config["symbolStyle"] = *e.Config.SymbolStyle
- }
- if e.Config.EnableStaticcheck {
- config["staticcheck"] = true
- }
- if e.Config.AllExperiments {
- config["allExperiments"] = true
- }
-
- if e.Config.VerboseOutput {
- config["verboseOutput"] = true
- }
-
- if e.Config.ImportShortcut != "" {
- config["importShortcut"] = e.Config.ImportShortcut
- }
-
- config["diagnosticsDelay"] = "10ms"
-
- // ExperimentalWorkspaceModule is only set as a mode, not a configuration.
- return config
-}
-
-func (e *Editor) initialize(ctx context.Context, workspaceFolders []string) error {
- params := &protocol.ParamInitialize{}
- params.ClientInfo.Name = "fakeclient"
- params.ClientInfo.Version = "v1.0.0"
-
- if workspaceFolders == nil {
- workspaceFolders = []string{string(e.sandbox.Workdir.RelativeTo)}
- }
- for _, folder := range workspaceFolders {
- params.WorkspaceFolders = append(params.WorkspaceFolders, protocol.WorkspaceFolder{
- URI: string(e.sandbox.Workdir.URI(folder)),
- Name: filepath.Base(folder),
- })
- }
-
- params.Capabilities.Workspace.Configuration = true
- params.Capabilities.Window.WorkDoneProgress = true
- // TODO: set client capabilities
- params.Capabilities.TextDocument.Completion.CompletionItem.TagSupport.ValueSet = []protocol.CompletionItemTag{protocol.ComplDeprecated}
- params.InitializationOptions = e.configuration()
- if e.Config.SendPID {
- params.ProcessID = int32(os.Getpid())
- }
-
- params.Capabilities.TextDocument.Completion.CompletionItem.SnippetSupport = true
- params.Capabilities.TextDocument.SemanticTokens.Requests.Full = true
- // copied from lsp/semantic.go to avoid import cycle in tests
- params.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",
- }
-
- // This is a bit of a hack, since the fake editor doesn't actually support
- // watching changed files that match a specific glob pattern. However, the
- // editor does send didChangeWatchedFiles notifications, so set this to
- // true.
- params.Capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration = true
-
- params.Trace = "messages"
- // TODO: support workspace folders.
- if e.Server != nil {
- resp, err := e.Server.Initialize(ctx, params)
- if err != nil {
- return errors.Errorf("initialize: %w", err)
- }
- e.mu.Lock()
- e.serverCapabilities = resp.Capabilities
- e.mu.Unlock()
-
- if err := e.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
- return errors.Errorf("initialized: %w", err)
- }
- }
- // TODO: await initial configuration here, or expect gopls to manage that?
- return nil
-}
-
-// 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 []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()
- var lspevts []protocol.FileEvent
- for _, evt := range evts {
- // Always send an on-disk change, even for events that seem useless
- // because they're shadowed by an open buffer.
- lspevts = append(lspevts, evt.ProtocolEvent)
-
- if buf, ok := e.buffers[evt.Path]; ok {
- // Following VS Code, don't honor deletions or changes to dirty buffers.
- if buf.dirty || evt.ProtocolEvent.Type == protocol.Deleted {
- continue
- }
-
- content, err := e.sandbox.Workdir.ReadFile(evt.Path)
- if err != nil {
- continue // A race with some other operation.
- }
- // No need to update if the buffer content hasn't changed.
- if content == buf.text() {
- continue
- }
- // During shutdown, this call will fail. Ignore the error.
- _ = e.setBufferContentLocked(ctx, evt.Path, false, lines(content), nil)
- }
- }
- e.Server.DidChangeWatchedFiles(ctx, &protocol.DidChangeWatchedFilesParams{
- Changes: lspevts,
- })
- }()
-}
-
-// OpenFile creates a buffer for the given workdir-relative file.
-func (e *Editor) OpenFile(ctx context.Context, path string) error {
- content, err := e.sandbox.Workdir.ReadFile(path)
- if err != nil {
- return err
- }
- return e.createBuffer(ctx, path, false, content)
-}
-
-// 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, content)
-}
-
-func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, content string) error {
- buf := buffer{
- windowsLineEndings: e.Config.WindowsLineEndings,
- version: 1,
- path: path,
- lines: lines(content),
- dirty: dirty,
- }
- e.mu.Lock()
- defer e.mu.Unlock()
- e.buffers[path] = buf
-
- item := protocol.TextDocumentItem{
- URI: e.sandbox.Workdir.URI(buf.path),
- LanguageID: e.languageID(buf.path),
- Version: int32(buf.version),
- Text: buf.text(),
- }
-
- if e.Server != nil {
- if err := e.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
- TextDocument: item,
- }); err != nil {
- return errors.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$`),
-}
-
-func (e *Editor) languageID(p string) string {
- base := path.Base(p)
- for lang, re := range e.Config.FileAssociations {
- re := regexp.MustCompile(re)
- if re.MatchString(base) {
- return lang
- }
- }
- for lang, re := range defaultFileAssociations {
- if re.MatchString(base) {
- return lang
- }
- }
- return ""
-}
-
-// lines returns line-ending agnostic line representation of content.
-func lines(content string) []string {
- lines := strings.Split(content, "\n")
- for i, l := range lines {
- lines[i] = strings.TrimSuffix(l, "\r")
- }
- return lines
-}
-
-// CloseBuffer removes the current buffer (regardless of whether it is saved).
-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()
-
- if e.Server != nil {
- if err := e.Server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{
- TextDocument: e.textDocumentIdentifier(path),
- }); err != nil {
- return errors.Errorf("DidClose: %w", err)
- }
- e.callsMu.Lock()
- e.calls.DidClose++
- e.callsMu.Unlock()
- }
- return nil
-}
-
-func (e *Editor) textDocumentIdentifier(path string) protocol.TextDocumentIdentifier {
- return protocol.TextDocumentIdentifier{
- URI: e.sandbox.Workdir.URI(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 errors.Errorf("organizing imports before save: %w", err)
- }
- if err := e.FormatBuffer(ctx, path); err != nil {
- return errors.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(fmt.Sprintf("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 errors.Errorf("WillSave: %w", err)
- }
- }
- if err := e.sandbox.Workdir.WriteFile(ctx, path, content); err != nil {
- return errors.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 errors.Errorf("DidSave: %w", err)
- }
- e.callsMu.Lock()
- e.calls.DidSave++
- e.callsMu.Unlock()
- }
- return nil
-}
-
-// contentPosition returns the (Line, Column) position corresponding to offset
-// in the buffer referenced by path.
-func contentPosition(content string, offset int) (Pos, error) {
- scanner := bufio.NewScanner(strings.NewReader(content))
- start := 0
- line := 0
- for scanner.Scan() {
- end := start + len([]rune(scanner.Text())) + 1
- if offset < end {
- return Pos{Line: line, Column: offset - start}, nil
- }
- start = end
- line++
- }
- if err := scanner.Err(); err != nil {
- return Pos{}, errors.Errorf("scanning content: %w", err)
- }
- // Scan() will drop the last line if it is empty. Correct for this.
- if (strings.HasSuffix(content, "\n") || content == "") && offset == start {
- return Pos{Line: line, Column: 0}, nil
- }
- return Pos{}, fmt.Errorf("position %d out of bounds in %q (line = %d, start = %d)", offset, content, line, start)
-}
-
-// ErrNoMatch is returned if a regexp search fails.
-var (
- ErrNoMatch = errors.New("no match")
- ErrUnknownBuffer = errors.New("unknown buffer")
-)
-
-// regexpRange returns the start and end of the first occurrence of either re
-// or its singular subgroup. It returns ErrNoMatch if the regexp doesn't match.
-func regexpRange(content, re string) (Pos, Pos, error) {
- content = normalizeEOL(content)
- var start, end int
- rec, err := regexp.Compile(re)
- if err != nil {
- return Pos{}, Pos{}, err
- }
- indexes := rec.FindStringSubmatchIndex(content)
- if indexes == nil {
- return Pos{}, Pos{}, 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 Pos{}, Pos{}, fmt.Errorf("invalid search regexp %q: expect either 0 or 1 subgroups, got %d", re, len(indexes)/2-1)
- }
- startPos, err := contentPosition(content, start)
- if err != nil {
- return Pos{}, Pos{}, err
- }
- endPos, err := contentPosition(content, end)
- if err != nil {
- return Pos{}, Pos{}, err
- }
- return startPos, endPos, nil
-}
-
-func normalizeEOL(content string) string {
- return strings.Join(lines(content), "\n")
-}
-
-// RegexpRange returns the first range in the buffer bufName matching re. See
-// RegexpSearch for more information on matching.
-func (e *Editor) RegexpRange(bufName, re string) (Pos, Pos, error) {
- e.mu.Lock()
- defer e.mu.Unlock()
- buf, ok := e.buffers[bufName]
- if !ok {
- return Pos{}, Pos{}, ErrUnknownBuffer
- }
- return regexpRange(buf.text(), re)
-}
-
-// RegexpSearch returns the position 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) (Pos, error) {
- start, _, err := e.RegexpRange(bufName, re)
- return start, err
-}
-
-// 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
- }
- content := buf.text()
- start, end, err := regexpRange(content, re)
- if err != nil {
- return err
- }
- return e.editBufferLocked(ctx, path, []Edit{{
- Start: start,
- End: end,
- Text: replace,
- }})
-}
-
-// EditBuffer applies the given test edits to the buffer identified by path.
-func (e *Editor) EditBuffer(ctx context.Context, path string, edits []Edit) 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()
- lines := lines(content)
- return e.setBufferContentLocked(ctx, path, true, lines, 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.
-func (e *Editor) BufferText(name string) string {
- e.mu.Lock()
- defer e.mu.Unlock()
- return e.buffers[name].text()
-}
-
-// 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 []Edit) error {
- buf, ok := e.buffers[path]
- if !ok {
- return fmt.Errorf("unknown buffer %q", path)
- }
- content := make([]string, len(buf.lines))
- copy(content, buf.lines)
- content, err := editContent(content, edits)
- if err != nil {
- return err
- }
- return e.setBufferContentLocked(ctx, path, true, content, edits)
-}
-
-func (e *Editor) setBufferContentLocked(ctx context.Context, path string, dirty bool, content []string, fromEdits []Edit) error {
- buf, ok := e.buffers[path]
- if !ok {
- return fmt.Errorf("unknown buffer %q", path)
- }
- buf.lines = 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 evts []protocol.TextDocumentContentChangeEvent
- if len(fromEdits) == 1 {
- evts = append(evts, fromEdits[0].toProtocolChangeEvent())
- } else {
- evts = append(evts, protocol.TextDocumentContentChangeEvent{
- Text: buf.text(),
- })
- }
- params := &protocol.DidChangeTextDocumentParams{
- TextDocument: protocol.VersionedTextDocumentIdentifier{
- Version: int32(buf.version),
- TextDocumentIdentifier: e.textDocumentIdentifier(buf.path),
- },
- ContentChanges: evts,
- }
- if e.Server != nil {
- if err := e.Server.DidChange(ctx, params); err != nil {
- return errors.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 path and position of the resulting jump.
-func (e *Editor) GoToDefinition(ctx context.Context, path string, pos Pos) (string, Pos, error) {
- if err := e.checkBufferPosition(path, pos); err != nil {
- return "", Pos{}, err
- }
- params := &protocol.DefinitionParams{}
- params.TextDocument.URI = e.sandbox.Workdir.URI(path)
- params.Position = pos.ToProtocolPosition()
-
- resp, err := e.Server.Definition(ctx, params)
- if err != nil {
- return "", Pos{}, errors.Errorf("definition: %w", err)
- }
- return e.extractFirstPathAndPos(ctx, resp)
-}
-
-// GoToTypeDefinition jumps to the type definition of the symbol at the given position
-// in an open buffer.
-func (e *Editor) GoToTypeDefinition(ctx context.Context, path string, pos Pos) (string, Pos, error) {
- if err := e.checkBufferPosition(path, pos); err != nil {
- return "", Pos{}, err
- }
- params := &protocol.TypeDefinitionParams{}
- params.TextDocument.URI = e.sandbox.Workdir.URI(path)
- params.Position = pos.ToProtocolPosition()
-
- resp, err := e.Server.TypeDefinition(ctx, params)
- if err != nil {
- return "", Pos{}, errors.Errorf("type definition: %w", err)
- }
- return e.extractFirstPathAndPos(ctx, resp)
-}
-
-// extractFirstPathAndPos returns the path and the position of the first location.
-// It opens the file if needed.
-func (e *Editor) extractFirstPathAndPos(ctx context.Context, locs []protocol.Location) (string, Pos, error) {
- if len(locs) == 0 {
- return "", Pos{}, nil
- }
-
- newPath := e.sandbox.Workdir.URIToPath(locs[0].URI)
- newPos := fromProtocolPosition(locs[0].Range.Start)
- if !e.HasBuffer(newPath) {
- if err := e.OpenFile(ctx, newPath); err != nil {
- return "", Pos{}, errors.Errorf("OpenFile: %w", err)
- }
- }
- return newPath, newPos, nil
-}
-
-// Symbol performs a workspace symbol search using query
-func (e *Editor) Symbol(ctx context.Context, query string) ([]SymbolInformation, error) {
- params := &protocol.WorkspaceSymbolParams{}
- params.Query = query
-
- resp, err := e.Server.Symbol(ctx, params)
- if err != nil {
- return nil, errors.Errorf("symbol: %w", err)
- }
- var res []SymbolInformation
- for _, si := range resp {
- ploc := si.Location
- path := e.sandbox.Workdir.URIToPath(ploc.URI)
- start := fromProtocolPosition(ploc.Range.Start)
- end := fromProtocolPosition(ploc.Range.End)
- rnge := Range{
- Start: start,
- End: end,
- }
- loc := Location{
- Path: path,
- Range: rnge,
- }
- res = append(res, SymbolInformation{
- Name: si.Name,
- Kind: si.Kind,
- Location: loc,
- })
- }
- return res, nil
-}
-
-// OrganizeImports requests and performs the source.organizeImports codeAction.
-func (e *Editor) OrganizeImports(ctx context.Context, path string) error {
- _, err := e.applyCodeActions(ctx, path, nil, nil, protocol.SourceOrganizeImports)
- return err
-}
-
-// RefactorRewrite requests and performs the source.refactorRewrite codeAction.
-func (e *Editor) RefactorRewrite(ctx context.Context, path string, rng *protocol.Range) error {
- applied, err := e.applyCodeActions(ctx, path, rng, nil, protocol.RefactorRewrite)
- if applied == 0 {
- return errors.Errorf("no refactorings were applied")
- }
- return err
-}
-
-// ApplyQuickFixes requests and performs the quickfix codeAction.
-func (e *Editor) ApplyQuickFixes(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) error {
- applied, err := e.applyCodeActions(ctx, path, rng, diagnostics, protocol.SourceFixAll, protocol.QuickFix)
- if applied == 0 {
- return errors.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 {
- for _, change := range action.Edit.DocumentChanges {
- path := e.sandbox.Workdir.URIToPath(change.TextDocument.URI)
- if int32(e.buffers[path].version) != change.TextDocument.Version {
- // Skip edits for old versions.
- continue
- }
- edits := convertEdits(change.Edits)
- if err := e.EditBuffer(ctx, path, edits); err != nil {
- return errors.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, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
- return e.getCodeActions(ctx, path, rng, diagnostics, protocol.QuickFix, protocol.SourceFixAll)
-}
-
-func (e *Editor) applyCodeActions(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) (int, error) {
- actions, err := e.getCodeActions(ctx, path, rng, diagnostics, only...)
- if err != nil {
- return 0, err
- }
- applied := 0
- for _, action := range actions {
- if action.Title == "" {
- return 0, errors.Errorf("empty title for code action")
- }
- var match bool
- for _, o := range only {
- if action.Kind == o {
- match = true
- break
- }
- }
- if !match {
- continue
- }
- applied++
- if err := e.ApplyCodeAction(ctx, action); err != nil {
- return 0, err
- }
- }
- return applied, nil
-}
-
-func (e *Editor) getCodeActions(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) ([]protocol.CodeAction, error) {
- if e.Server == nil {
- return nil, nil
- }
- params := &protocol.CodeActionParams{}
- params.TextDocument.URI = e.sandbox.Workdir.URI(path)
- params.Context.Only = only
- if diagnostics != nil {
- params.Context.Diagnostics = diagnostics
- }
- if rng != nil {
- params.Range = *rng
- }
- 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
- // 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, err
- }
- return result, nil
-}
-
-func convertEdits(protocolEdits []protocol.TextEdit) []Edit {
- var edits []Edit
- for _, lspEdit := range protocolEdits {
- edits = append(edits, fromProtocolTextEdit(lspEdit))
- }
- return edits
-}
-
-// 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)
- resp, err := e.Server.Formatting(ctx, params)
- if err != nil {
- return errors.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)
- }
- edits := convertEdits(resp)
- if len(edits) == 0 {
- return nil
- }
- return e.editBufferLocked(ctx, path, edits)
-}
-
-func (e *Editor) checkBufferPosition(path string, pos Pos) error {
- e.mu.Lock()
- defer e.mu.Unlock()
- buf, ok := e.buffers[path]
- if !ok {
- return fmt.Errorf("buffer %q is not open", path)
- }
- if !inText(pos, buf.lines) {
- return fmt.Errorf("position %v is invalid in buffer %q", pos, path)
- }
- return nil
-}
-
-// 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, err := command.NewGenerateCommand("", command.GenerateArgs{
- Dir: protocol.URIFromSpanURI(span.URIFromPath(absDir)),
- Recursive: false,
- })
- if err != nil {
- return err
- }
- 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 regtests, 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, path string, pos Pos) (*protocol.CompletionList, 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.CompletionParams{
- TextDocumentPositionParams: protocol.TextDocumentPositionParams{
- TextDocument: e.textDocumentIdentifier(path),
- Position: pos.ToProtocolPosition(),
- },
- }
- completions, err := e.Server.Completion(ctx, params)
- if err != nil {
- return nil, err
- }
- return completions, nil
-}
-
-// AcceptCompletion accepts a completion for the given item at the given
-// position.
-func (e *Editor) AcceptCompletion(ctx context.Context, path string, pos Pos, item protocol.CompletionItem) error {
- if e.Server == nil {
- return nil
- }
- e.mu.Lock()
- defer e.mu.Unlock()
- _, ok := e.buffers[path]
- if !ok {
- return fmt.Errorf("buffer %q is not open", path)
- }
- return e.editBufferLocked(ctx, path, convertEdits(append([]protocol.TextEdit{
- *item.TextEdit,
- }, 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
-}
-
-// References executes a reference request on the server.
-func (e *Editor) References(ctx context.Context, path string, pos Pos) ([]protocol.Location, 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.ReferenceParams{
- TextDocumentPositionParams: protocol.TextDocumentPositionParams{
- TextDocument: e.textDocumentIdentifier(path),
- Position: pos.ToProtocolPosition(),
- },
- Context: protocol.ReferenceContext{
- IncludeDeclaration: true,
- },
- }
- locations, err := e.Server.References(ctx, params)
- if err != nil {
- return nil, err
- }
- return locations, nil
-}
-
-func (e *Editor) Rename(ctx context.Context, path string, pos Pos, newName string) error {
- if e.Server == nil {
- return nil
- }
- params := &protocol.RenameParams{
- TextDocument: e.textDocumentIdentifier(path),
- Position: pos.ToProtocolPosition(),
- NewName: newName,
- }
- wsEdits, err := e.Server.Rename(ctx, params)
- if err != nil {
- return err
- }
- for _, change := range wsEdits.DocumentChanges {
- if err := e.applyProtocolEdit(ctx, change); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (e *Editor) applyProtocolEdit(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
- }
- }
- fakeEdits := convertEdits(change.Edits)
- return e.EditBuffer(ctx, path, fakeEdits)
-}
-
-// CodeAction executes a codeAction request on the server.
-func (e *Editor) CodeAction(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, 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.CodeActionParams{
- TextDocument: e.textDocumentIdentifier(path),
- Context: protocol.CodeActionContext{
- Diagnostics: diagnostics,
- },
- }
- if rng != nil {
- params.Range = *rng
- }
- lens, err := e.Server.CodeAction(ctx, params)
- if err != nil {
- return nil, err
- }
- return lens, nil
-}
-
-// Hover triggers a hover at the given position in an open buffer.
-func (e *Editor) Hover(ctx context.Context, path string, pos Pos) (*protocol.MarkupContent, Pos, error) {
- if err := e.checkBufferPosition(path, pos); err != nil {
- return nil, Pos{}, err
- }
- params := &protocol.HoverParams{}
- params.TextDocument.URI = e.sandbox.Workdir.URI(path)
- params.Position = pos.ToProtocolPosition()
-
- resp, err := e.Server.Hover(ctx, params)
- if err != nil {
- return nil, Pos{}, errors.Errorf("hover: %w", err)
- }
- if resp == nil {
- return nil, Pos{}, nil
- }
- return &resp.Contents, fromProtocolPosition(resp.Range.Start), 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, path string, pos Pos) ([]protocol.DocumentHighlight, error) {
- if e.Server == nil {
- return nil, nil
- }
- if err := e.checkBufferPosition(path, pos); err != nil {
- return nil, err
- }
- params := &protocol.DocumentHighlightParams{}
- params.TextDocument.URI = e.sandbox.Workdir.URI(path)
- params.Position = pos.ToProtocolPosition()
-
- return e.Server.DocumentHighlight(ctx, params)
-}
diff --git a/internal/lsp/fake/editor_test.go b/internal/lsp/fake/editor_test.go
deleted file mode 100644
index 3ce5df6e0..000000000
--- a/internal/lsp/fake/editor_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-// 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"
-)
-
-func TestContentPosition(t *testing.T) {
- content := "foo\n😀\nbar"
- tests := []struct {
- offset, wantLine, wantColumn int
- }{
- {0, 0, 0},
- {3, 0, 3},
- {4, 1, 0},
- {5, 1, 1},
- {6, 2, 0},
- }
- for _, test := range tests {
- pos, err := contentPosition(content, test.offset)
- if err != nil {
- t.Fatal(err)
- }
- if pos.Line != test.wantLine {
- t.Errorf("contentPosition(%q, %d): Line = %d, want %d", content, test.offset, pos.Line, test.wantLine)
- }
- if pos.Column != test.wantColumn {
- t.Errorf("contentPosition(%q, %d): Column = %d, want %d", content, test.offset, pos.Column, test.wantColumn)
- }
- }
-}
-
-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", []Edit{
- {
- Start: Pos{5, 14},
- End: Pos{5, 26},
- Text: "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/internal/lsp/fake/proxy.go b/internal/lsp/fake/proxy.go
deleted file mode 100644
index 9e56efeb1..000000000
--- a/internal/lsp/fake/proxy.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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/internal/lsp/fake/sandbox.go b/internal/lsp/fake/sandbox.go
deleted file mode 100644
index f628f2d54..000000000
--- a/internal/lsp/fake/sandbox.go
+++ /dev/null
@@ -1,273 +0,0 @@
-// 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"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
-
- "golang.org/x/tools/internal/gocommand"
- "golang.org/x/tools/internal/testenv"
- "golang.org/x/tools/txtar"
- errors "golang.org/x/xerrors"
-)
-
-// 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
-}
-
-// 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.
-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 = ioutil.TempDir(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 = NewWorkdir(config.Workdir)
- 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 = NewWorkdir(workdir)
- if err := sb.Workdir.writeInitialFiles(config.Files); err != nil {
- return nil, err
- }
- return sb, nil
-}
-
-// Tempdir creates a new temp directory with the given txtar-encoded files. It
-// is the responsibility of the caller to call os.RemoveAll on the returned
-// file path when it is no longer needed.
-func Tempdir(files map[string][]byte) (string, error) {
- dir, err := ioutil.TempDir("", "gopls-tempdir-")
- if err != nil {
- return "", err
- }
- for name, data := range files {
- if err := WriteFileData(name, data, RelativeTo(dir)); err != nil {
- return "", errors.Errorf("writing to tempdir: %w", err)
- }
- }
- return dir, nil
-}
-
-func UnpackTxt(txt string) map[string][]byte {
- dataMap := make(map[string][]byte)
- archive := txtar.Parse([]byte(txt))
- for _, f := range archive.Files {
- 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
-}
-
-// 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 []string, checkForFileChanges bool) error {
- var vars []string
- for k, v := range sb.GoEnv() {
- vars = append(vars, fmt.Sprintf("%s=%s", k, v))
- }
- inv := gocommand.Invocation{
- Verb: verb,
- Args: args,
- Env: vars,
- }
- // Use the provided directory for the working directory, if available.
- // 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 dir != "" {
- inv.WorkingDir = sb.Workdir.AbsPath(dir)
- } else if sb.Workdir != nil {
- inv.WorkingDir = string(sb.Workdir.RelativeTo)
- }
- gocmdRunner := &gocommand.Runner{}
- stdout, stderr, _, err := gocmdRunner.RunRaw(ctx, inv)
- if err != nil {
- return errors.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 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 errors.Errorf("checking for file changes: %w", err)
- }
- }
- return nil
-}
-
-// Close removes all state associated with the sandbox.
-func (sb *Sandbox) Close() error {
- var goCleanErr error
- if sb.gopath != "" {
- goCleanErr = sb.RunGoCommand(context.Background(), "", "clean", []string{"-modcache"}, false)
- }
- err := os.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/internal/lsp/fake/workdir.go b/internal/lsp/fake/workdir.go
deleted file mode 100644
index 0be1d8fdf..000000000
--- a/internal/lsp/fake/workdir.go
+++ /dev/null
@@ -1,365 +0,0 @@
-// 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/ioutil"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "sync"
- "time"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// FileEvent wraps the protocol.FileEvent so that it can be associated with a
-// workdir-relative path.
-type FileEvent struct {
- Path, Content string
- ProtocolEvent protocol.FileEvent
-}
-
-// 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.
-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 errors.Errorf("creating nested directory: %w", err)
- }
- backoff := 1 * time.Millisecond
- for {
- err := ioutil.WriteFile(fp, []byte(content), 0644)
- if err != nil {
- if isWindowsErrLockViolation(err) {
- time.Sleep(backoff)
- backoff *= 2
- continue
- }
- return errors.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, []FileEvent)
-
- fileMu sync.Mutex
- files map[string]string
-}
-
-// 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) *Workdir {
- return &Workdir{RelativeTo: RelativeTo(dir)}
-}
-
-func hashFile(data []byte) string {
- return fmt.Sprintf("%x", sha256.Sum256(data))
-}
-
-func (w *Workdir) writeInitialFiles(files map[string][]byte) error {
- w.files = map[string]string{}
- for name, data := range files {
- w.files[name] = hashFile(data)
- if err := WriteFileData(name, data, w.RelativeTo); err != nil {
- return errors.Errorf("writing to workdir: %w", err)
- }
- }
- return nil
-}
-
-// RootURI returns the root URI for this working directory of this scratch
-// environment.
-func (w *Workdir) RootURI() protocol.DocumentURI {
- return toURI(string(w.RelativeTo))
-}
-
-// AddWatcher registers the given func to be called on any file change.
-func (w *Workdir) AddWatcher(watcher func(context.Context, []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 toURI(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 {
- fp := uri.SpanURI().Filename()
- return w.RelPath(fp)
-}
-
-func toURI(fp string) protocol.DocumentURI {
- return protocol.DocumentURI(span.URIFromPath(fp))
-}
-
-// ReadFile reads a text file specified by a workdir-relative path.
-func (w *Workdir) ReadFile(path string) (string, error) {
- backoff := 1 * time.Millisecond
- for {
- b, err := ioutil.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 "", err
- }
- return string(b), nil
- }
-}
-
-func (w *Workdir) RegexpRange(path, re string) (Pos, Pos, error) {
- content, err := w.ReadFile(path)
- if err != nil {
- return Pos{}, Pos{}, err
- }
- return regexpRange(content, re)
-}
-
-// RegexpSearch searches the file corresponding to path for the first position
-// matching re.
-func (w *Workdir) RegexpSearch(path string, re string) (Pos, error) {
- content, err := w.ReadFile(path)
- if err != nil {
- return Pos{}, err
- }
- start, _, err := regexpRange(content, re)
- return start, err
-}
-
-// ChangeFilesOnDisk executes the given on-disk file changes in a batch,
-// simulating the action of changing branches outside of an editor.
-func (w *Workdir) ChangeFilesOnDisk(ctx context.Context, events []FileEvent) error {
- for _, e := range events {
- switch e.ProtocolEvent.Type {
- case protocol.Deleted:
- fp := w.AbsPath(e.Path)
- if err := os.Remove(fp); err != nil {
- return errors.Errorf("removing %q: %w", e.Path, err)
- }
- case protocol.Changed, protocol.Created:
- if _, err := w.writeFile(ctx, e.Path, e.Content); err != nil {
- return err
- }
- }
- }
- w.sendEvents(ctx, events)
- return nil
-}
-
-// RemoveFile removes a workdir-relative file path.
-func (w *Workdir) RemoveFile(ctx context.Context, path string) error {
- fp := w.AbsPath(path)
- if err := os.RemoveAll(fp); err != nil {
- return errors.Errorf("removing %q: %w", path, err)
- }
- w.fileMu.Lock()
- defer w.fileMu.Unlock()
-
- evts := []FileEvent{{
- Path: path,
- ProtocolEvent: protocol.FileEvent{
- URI: w.URI(path),
- Type: protocol.Deleted,
- },
- }}
- w.sendEvents(ctx, evts)
- delete(w.files, path)
- return nil
-}
-
-func (w *Workdir) sendEvents(ctx context.Context, evts []FileEvent) {
- if len(evts) == 0 {
- return
- }
- w.watcherMu.Lock()
- watchers := make([]func(context.Context, []FileEvent), len(w.watchers))
- copy(watchers, w.watchers)
- w.watcherMu.Unlock()
- for _, w := range watchers {
- w(ctx, evts)
- }
-}
-
-// WriteFiles writes the text file content to workdir-relative paths.
-// It batches notifications rather than sending them consecutively.
-func (w *Workdir) WriteFiles(ctx context.Context, files map[string]string) error {
- var evts []FileEvent
- for filename, content := range files {
- evt, err := w.writeFile(ctx, filename, content)
- if err != nil {
- return err
- }
- evts = append(evts, evt)
- }
- w.sendEvents(ctx, evts)
- return nil
-}
-
-// WriteFile writes text file content to a workdir-relative path.
-func (w *Workdir) WriteFile(ctx context.Context, path, content string) error {
- evt, err := w.writeFile(ctx, path, content)
- if err != nil {
- return err
- }
- w.sendEvents(ctx, []FileEvent{evt})
- return nil
-}
-
-func (w *Workdir) writeFile(ctx context.Context, path, content string) (FileEvent, error) {
- fp := w.AbsPath(path)
- _, err := os.Stat(fp)
- if err != nil && !os.IsNotExist(err) {
- return FileEvent{}, errors.Errorf("checking if %q exists: %w", path, err)
- }
- var changeType protocol.FileChangeType
- if os.IsNotExist(err) {
- changeType = protocol.Created
- } else {
- changeType = protocol.Changed
- }
- if err := WriteFileData(path, []byte(content), w.RelativeTo); err != nil {
- return FileEvent{}, err
- }
- return FileEvent{
- Path: path,
- ProtocolEvent: protocol.FileEvent{
- URI: w.URI(path),
- Type: changeType,
- },
- }, nil
-}
-
-// listFiles lists files in the given directory, returning a map of relative
-// path to modification time.
-func (w *Workdir) listFiles(dir string) (map[string]string, error) {
- files := make(map[string]string)
- absDir := w.AbsPath(dir)
- if err := filepath.Walk(absDir, func(fp string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- if info.IsDir() {
- return nil
- }
- path := w.RelPath(fp)
- data, err := ioutil.ReadFile(fp)
- if err != nil {
- return err
- }
- files[path] = hashFile(data)
- return nil
- }); err != nil {
- return nil, err
- }
- return files, 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
- }
- w.sendEvents(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() ([]FileEvent, error) {
- w.fileMu.Lock()
- defer w.fileMu.Unlock()
-
- files, err := w.listFiles(".")
- if err != nil {
- return nil, err
- }
- var evts []FileEvent
- // Check which files have been added or modified.
- for path, hash := range files {
- oldhash, ok := w.files[path]
- delete(w.files, path)
- var typ protocol.FileChangeType
- switch {
- case !ok:
- typ = protocol.Created
- case oldhash != hash:
- typ = protocol.Changed
- default:
- continue
- }
- evts = append(evts, FileEvent{
- Path: path,
- ProtocolEvent: protocol.FileEvent{
- URI: w.URI(path),
- Type: typ,
- },
- })
- }
- // Any remaining files must have been deleted.
- for path := range w.files {
- evts = append(evts, FileEvent{
- Path: path,
- ProtocolEvent: protocol.FileEvent{
- URI: w.URI(path),
- Type: protocol.Deleted,
- },
- })
- }
- w.files = files
- return evts, nil
-}
diff --git a/internal/lsp/fake/workdir_test.go b/internal/lsp/fake/workdir_test.go
deleted file mode 100644
index 33fbb9fa1..000000000
--- a/internal/lsp/fake/workdir_test.go
+++ /dev/null
@@ -1,192 +0,0 @@
-// 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"
- "io/ioutil"
- "os"
- "sort"
- "testing"
- "time"
-
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-const data = `
--- go.mod --
-go 1.12
--- nested/README.md --
-Hello World!
-`
-
-func newWorkdir(t *testing.T) (*Workdir, <-chan []FileEvent, func()) {
- t.Helper()
-
- tmpdir, err := ioutil.TempDir("", "goplstest-workdir-")
- if err != nil {
- t.Fatal(err)
- }
- wd := NewWorkdir(tmpdir)
- if err := wd.writeInitialFiles(UnpackTxt(data)); err != nil {
- t.Fatal(err)
- }
- cleanup := func() {
- if err := os.RemoveAll(tmpdir); err != nil {
- t.Error(err)
- }
- }
-
- fileEvents := make(chan []FileEvent)
- watch := func(_ context.Context, events []FileEvent) {
- go func() {
- fileEvents <- events
- }()
- }
- wd.AddWatcher(watch)
- return wd, fileEvents, cleanup
-}
-
-func TestWorkdir_ReadFile(t *testing.T) {
- wd, _, cleanup := newWorkdir(t)
- defer cleanup()
-
- got, err := wd.ReadFile("nested/README.md")
- if err != nil {
- t.Fatal(err)
- }
- want := "Hello World!\n"
- if got != want {
- t.Errorf("reading workdir file, got %q, want %q", got, want)
- }
-}
-
-func TestWorkdir_WriteFile(t *testing.T) {
- wd, events, cleanup := newWorkdir(t)
- 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
- if got := len(es); got != 1 {
- t.Fatalf("len(events) = %d, want 1", got)
- }
- if es[0].Path != test.path {
- t.Errorf("event.Path = %q, want %q", es[0].Path, test.path)
- }
- if es[0].ProtocolEvent.Type != test.wantType {
- t.Errorf("event type = %v, want %v", es[0].ProtocolEvent.Type, test.wantType)
- }
- got, err := wd.ReadFile(test.path)
- if err != nil {
- t.Fatal(err)
- }
- want := "42"
- if got != want {
- t.Errorf("ws.ReadFile(%q) = %q, want %q", test.path, got, want)
- }
- }
-}
-
-func TestWorkdir_ListFiles(t *testing.T) {
- wd, _, cleanup := newWorkdir(t)
- defer cleanup()
-
- checkFiles := func(dir string, want []string) {
- files, err := wd.listFiles(dir)
- if err != nil {
- t.Fatal(err)
- }
- sort.Strings(want)
- var got []string
- for p := range files {
- got = append(got, p)
- }
- sort.Strings(got)
- if len(got) != len(want) {
- t.Fatalf("ListFiles(): len = %d, want %d; got=%v; want=%v", len(got), len(want), got, want)
- }
- for i, f := range got {
- if f != want[i] {
- t.Errorf("ListFiles()[%d] = %s, want %s", i, f, want[i])
- }
- }
- }
-
- checkFiles(".", []string{"go.mod", "nested/README.md"})
- checkFiles("nested", []string{"nested/README.md"})
-}
-
-func TestWorkdir_CheckForFileChanges(t *testing.T) {
- t.Skip("broken on darwin-amd64-10_12")
- wd, events, cleanup := newWorkdir(t)
- defer cleanup()
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
-
- checkChange := func(path string, typ protocol.FileChangeType) {
- if err := wd.CheckForFileChanges(ctx); err != nil {
- t.Fatal(err)
- }
- var gotEvt FileEvent
- select {
- case <-ctx.Done():
- t.Fatal(ctx.Err())
- case ev := <-events:
- gotEvt = ev[0]
- }
- // Only check relative path and Type
- if gotEvt.Path != path || gotEvt.ProtocolEvent.Type != typ {
- t.Errorf("file events: got %v, want {Path: %s, Type: %v}", gotEvt, path, typ)
- }
- }
- // Sleep some positive amount of time to ensure a distinct mtime.
- time.Sleep(100 * time.Millisecond)
- 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/internal/lsp/fake/workdir_windows.go b/internal/lsp/fake/workdir_windows.go
deleted file mode 100644
index ed2b4bb36..000000000
--- a/internal/lsp/fake/workdir_windows.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// 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 (
- "syscall"
-
- errors "golang.org/x/xerrors"
-)
-
-func init() {
- // from https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
- const ERROR_LOCK_VIOLATION syscall.Errno = 33
-
- isWindowsErrLockViolation = func(err error) bool {
- return errors.Is(err, ERROR_LOCK_VIOLATION)
- }
-}
diff --git a/internal/lsp/folding_range.go b/internal/lsp/folding_range.go
deleted file mode 100644
index 75f48a449..000000000
--- a/internal/lsp/folding_range.go
+++ /dev/null
@@ -1,44 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-func (s *Server) foldingRange(ctx context.Context, params *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.Go)
- defer release()
- if !ok {
- return nil, err
- }
-
- ranges, err := source.FoldingRange(ctx, snapshot, fh, snapshot.View().Options().LineFoldingOnly)
- if err != nil {
- return nil, err
- }
- return toProtocolFoldingRanges(ranges)
-}
-
-func toProtocolFoldingRanges(ranges []*source.FoldingRangeInfo) ([]protocol.FoldingRange, error) {
- result := make([]protocol.FoldingRange, 0, len(ranges))
- for _, info := range ranges {
- rng, err := info.Range()
- if err != nil {
- return nil, err
- }
- 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/internal/lsp/format.go b/internal/lsp/format.go
deleted file mode 100644
index 19736af38..000000000
--- a/internal/lsp/format.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/mod"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/work"
-)
-
-func (s *Server) formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
- defer release()
- if !ok {
- return nil, err
- }
- switch snapshot.View().FileKind(fh) {
- case source.Mod:
- return mod.Format(ctx, snapshot, fh)
- case source.Go:
- return source.Format(ctx, snapshot, fh)
- case source.Work:
- return work.Format(ctx, snapshot, fh)
- }
- return nil, nil
-}
diff --git a/internal/lsp/fuzzy/input.go b/internal/lsp/fuzzy/input.go
deleted file mode 100644
index c1038163f..000000000
--- a/internal/lsp/fuzzy/input.go
+++ /dev/null
@@ -1,183 +0,0 @@
-// 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/internal/lsp/fuzzy/input_test.go b/internal/lsp/fuzzy/input_test.go
deleted file mode 100644
index 0228347e4..000000000
--- a/internal/lsp/fuzzy/input_test.go
+++ /dev/null
@@ -1,141 +0,0 @@
-// 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/internal/lsp/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/internal/lsp/fuzzy/matcher.go b/internal/lsp/fuzzy/matcher.go
deleted file mode 100644
index 265cdcf16..000000000
--- a/internal/lsp/fuzzy/matcher.go
+++ /dev/null
@@ -1,407 +0,0 @@
-// 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 1: the char is in the candidate's last segment.
- if segmentsLeft <= 1 {
- charScore++
- }
- // Bonus 2: Case match or a Head in the pattern aligns with one in the word.
- // Single-case patterns lack segmentation signals and we assume any character
- // can be a head of a segment.
- if candidate[i-1] == m.pattern[j-1] || role == RHead && (!m.caseSensitive || pRole == RHead) {
- charScore++
- }
-
- // Penalty 1: pattern char is Head, candidate char is Tail.
- if role == RTail && pRole == RHead {
- charScore--
- }
- // Penalty 2: 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 3: 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 3: 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
-}
diff --git a/internal/lsp/fuzzy/matcher_test.go b/internal/lsp/fuzzy/matcher_test.go
deleted file mode 100644
index bac81c098..000000000
--- a/internal/lsp/fuzzy/matcher_test.go
+++ /dev/null
@@ -1,295 +0,0 @@
-// 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/internal/lsp/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
- 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",
- },
- },
-}
-
-func TestCompareCandidateScores(t *testing.T) {
- for _, tc := range compareCandidatesTestCases {
- m := fuzzy.NewMatcher(tc.pattern)
-
- var prevScore float32
- prevCand := "MIN_SCORE"
- for _, cand := range tc.orderedCandidates {
- score := m.Score(cand)
- if prevScore > score {
- t.Errorf("%s[=%v] is scored lower than %s[=%v]", cand, score, prevCand, prevScore)
- }
- if score < -1 || score > 1 {
- t.Errorf("%s score is %v; want value between [-1, 1]", cand, score)
- }
- prevScore = score
- prevCand = cand
- }
- }
-}
-
-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/internal/lsp/fuzzy/symbol.go b/internal/lsp/fuzzy/symbol.go
deleted file mode 100644
index df9fbd514..000000000
--- a/internal/lsp/fuzzy/symbol.go
+++ /dev/null
@@ -1,236 +0,0 @@
-// 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 (
- "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.
-//
-// This implementation is experimental, serving as a reference fast algorithm
-// to compare to the fuzzy algorithm implemented by Matcher.
-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
-}
-
-const (
- segmentStart uint32 = 1 << iota
- wordStart
- separator
-)
-
-// 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 looks for the right-most match of the search pattern within the symbol
-// represented by concatenating the given chunks, returning its offset and
-// score.
-//
-// If a match is found, the first return value will hold 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, ""). If no match is found,
-// the first return value will be -1.
-//
-// 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.
-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
- }
-
- // First phase: populate the input buffer with lower-cased runes.
- //
- // 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 |= 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 phase: 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)
- )
- 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 {
- 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 phase: find the shortest match, and compute the score.
-
- // Score is the average score for each character.
- //
- // A character score is the multiple of:
- // 1. 1.0 if the character starts a segment, .8 if the character start a
- // mid-segment word, otherwise 0.6. This carries over to immediately
- // following characters.
- // 2. For the final character match, the multiplier from (1) is reduced to
- // .8 if the next character in the input is a mid-segment word, or 0.6 if
- // the next character in the input is not a word or segment start. This
- // ensures that we favor whole-word or whole-segment matches over prefix
- // matches.
- // 3. 1.0 if the character is part of the last segment, otherwise
- // 1.0-.2*<segments from the right>, with a max segment count of 3.
- //
- // This is a very naive algorithm, but it is fast. There's lots of prior art
- // here, and we should leverage it. For example, we could explicitly consider
- // character 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.
-
- pi = 0
- p = m.pattern[pi]
-
- const (
- segStreak = 1.0
- wordStreak = 0.8
- noStreak = 0.6
- perSegment = 0.2 // we count at most 3 segments above
- )
-
- streakBonus := noStreak
- totScore := 0.0
- for ii := uint8(start); ii < inputLen; ii++ {
- r := m.inputBuffer[ii]
- if r == p {
- pi++
- p = m.pattern[pi]
- // Note: this could be optimized with some bit operations.
- switch {
- case m.roles[ii]&segmentStart != 0 && segStreak > streakBonus:
- streakBonus = segStreak
- case m.roles[ii]&wordStart != 0 && wordStreak > streakBonus:
- streakBonus = wordStreak
- }
- finalChar := pi >= m.patternLen
- // finalCost := 1.0
- if finalChar && streakBonus > noStreak {
- switch {
- case ii == inputLen-1 || m.roles[ii+1]&segmentStart != 0:
- // Full segment: no reduction
- case m.roles[ii+1]&wordStart != 0:
- streakBonus = wordStreak
- default:
- streakBonus = noStreak
- }
- }
- totScore += streakBonus * (1.0 - float64(m.segments[ii])*perSegment)
- if finalChar {
- break
- }
- } else {
- streakBonus = noStreak
- }
- }
-
- return start, totScore / float64(m.patternLen)
-}
diff --git a/internal/lsp/fuzzy/symbol_test.go b/internal/lsp/fuzzy/symbol_test.go
deleted file mode 100644
index cb28160de..000000000
--- a/internal/lsp/fuzzy/symbol_test.go
+++ /dev/null
@@ -1,79 +0,0 @@
-// 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 (
- "testing"
-
- . "golang.org/x/tools/internal/lsp/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) {
- matcher := NewSymbolMatcher("test")
-
- // symbols to match, in ascending order of ranking.
- symbols := []string{
- "this.is.better.than.most",
- "test.foo.bar",
- "atest",
- "thebest",
- "test.foo",
- "test.foo",
- "tTest",
- "testage",
- "foo.test",
- "test",
- }
- 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")
-
- chunked := [][]string{
- {"test"},
- {"", "test"},
- {"test", ""},
- {"te", "st"},
- }
-
- for _, chunks := range chunked {
- offset, score := matcher.Match(chunks)
- if offset != 0 || score != 1.0 {
- t.Errorf("Match(%v) = %v, %v, want 0, 1.0", chunks, offset, score)
- }
- }
-}
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
deleted file mode 100644
index a3662efd0..000000000
--- a/internal/lsp/general.go
+++ /dev/null
@@ -1,510 +0,0 @@
-// 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 lsp
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "os"
- "path"
- "path/filepath"
- "sync"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/jsonrpc2"
- "golang.org/x/tools/internal/lsp/debug"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
- s.stateMu.Lock()
- if s.state >= serverInitializing {
- defer s.stateMu.Unlock()
- return nil, errors.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.session.Options()
- defer func() { s.session.SetOptions(options) }()
-
- if err := s.handleOptionResults(ctx, source.SetOptions(options, params.InitializationOptions)); err != nil {
- return nil, err
- }
- options.ForClientCapabilities(params.Capabilities)
-
- folders := params.WorkspaceFolders
- if len(folders) == 0 {
- if params.RootURI != "" {
- folders = []protocol.WorkspaceFolder{{
- URI: string(params.RootURI),
- Name: path.Base(params.RootURI.SpanURI().Filename()),
- }}
- }
- }
- for _, folder := range folders {
- uri := span.URIFromURI(folder.URI)
- if !uri.IsFile() {
- continue
- }
- s.pendingFolders = append(s.pendingFolders, folder)
- }
- // gopls only supports URIs with a file:// scheme, so if we have no
- // workspace folders with a supported scheme, fail to initialize.
- if len(folders) > 0 && len(s.pendingFolders) == 0 {
- return nil, fmt.Errorf("unsupported URI schemes: %v (gopls only supports file URIs)", 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(),
- }
- }
- var renameOpts interface{} = true
- if r := params.Capabilities.TextDocument.Rename; r.PrepareSupport {
- renameOpts = protocol.RenameOptions{
- PrepareProvider: r.PrepareSupport,
- }
- }
-
- versionInfo := debug.VersionInfo()
-
- // golang/go#45732: Warn users who've installed sergi/go-diff@v1.2.0, since
- // it will corrupt the formatting of their files.
- for _, dep := range versionInfo.Deps {
- if dep.Path == "github.com/sergi/go-diff" && dep.Version == "v1.2.0" {
- if err := s.eventuallyShowMessage(ctx, &protocol.ShowMessageParams{
- Message: `It looks like you have a bad gopls installation.
-Please reinstall gopls by running 'GO111MODULE=on go install golang.org/x/tools/gopls@latest'.
-See https://github.com/golang/go/issues/45732 for more information.`,
- Type: protocol.Error,
- }); err != nil {
- return nil, err
- }
- }
- }
-
- goplsVersion, err := json.Marshal(versionInfo)
- if err != nil {
- return nil, err
- }
-
- return &protocol.InitializeResult{
- Capabilities: protocol.ServerCapabilities{
- CallHierarchyProvider: true,
- CodeActionProvider: codeActionProvider,
- CompletionProvider: protocol.CompletionOptions{
- TriggerCharacters: []string{"."},
- },
- DefinitionProvider: true,
- TypeDefinitionProvider: true,
- ImplementationProvider: true,
- DocumentFormattingProvider: true,
- DocumentSymbolProvider: true,
- WorkspaceSymbolProvider: true,
- ExecuteCommandProvider: protocol.ExecuteCommandOptions{
- Commands: options.SupportedCommands,
- },
- FoldingRangeProvider: true,
- HoverProvider: true,
- DocumentHighlightProvider: true,
- DocumentLinkProvider: protocol.DocumentLinkOptions{},
- ReferencesProvider: true,
- RenameProvider: renameOpts,
- SignatureHelpProvider: protocol.SignatureHelpOptions{
- TriggerCharacters: []string{"(", ","},
- },
- TextDocumentSync: &protocol.TextDocumentSyncOptions{
- Change: protocol.Incremental,
- OpenClose: true,
- Save: protocol.SaveOptions{
- IncludeText: false,
- },
- },
- Workspace: protocol.Workspace6Gn{
- WorkspaceFolders: protocol.WorkspaceFolders5Gn{
- Supported: true,
- ChangeNotifications: "workspace/didChangeWorkspaceFolders",
- },
- },
- },
- ServerInfo: struct {
- Name string `json:"name"`
- Version string `json:"version,omitempty"`
- }{
- Name: "gopls",
- Version: string(goplsVersion),
- },
- }, nil
-}
-
-func (s *Server) initialized(ctx context.Context, params *protocol.InitializedParams) error {
- s.stateMu.Lock()
- if s.state >= serverInitialized {
- defer s.stateMu.Unlock()
- return errors.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
-
- options := s.session.Options()
- defer func() { s.session.SetOptions(options) }()
-
- if err := s.addFolders(ctx, s.pendingFolders); err != nil {
- return err
- }
- s.pendingFolders = nil
-
- var registrations []protocol.Registration
- if options.ConfigurationSupported && options.DynamicConfigurationSupported {
- registrations = append(registrations, protocol.Registration{
- ID: "workspace/didChangeConfiguration",
- Method: "workspace/didChangeConfiguration",
- })
- }
- if options.SemanticTokens && options.DynamicRegistrationSemanticTokensSupported {
- registrations = append(registrations, semanticTokenRegistration(options.SemanticTypes, options.SemanticMods))
- }
- if len(registrations) > 0 {
- if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
- Registrations: registrations,
- }); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (s *Server) addFolders(ctx context.Context, folders []protocol.WorkspaceFolder) error {
- originalViews := len(s.session.Views())
- viewErrors := make(map[span.URI]error)
-
- var wg sync.WaitGroup
- if s.session.Options().VerboseWorkDoneProgress {
- work := s.progress.Start(ctx, DiagnosticWorkTitle(FromInitialWorkspaceLoad), "Calculating diagnostics for initial workspace load...", nil, nil)
- defer func() {
- go func() {
- wg.Wait()
- work.End("Done.")
- }()
- }()
- }
- // Only one view gets to have a workspace.
- var allFoldersWg sync.WaitGroup
- for _, folder := range folders {
- uri := span.URIFromURI(folder.URI)
- // Ignore non-file URIs.
- if !uri.IsFile() {
- continue
- }
- work := s.progress.Start(ctx, "Setting up workspace", "Loading packages...", nil, nil)
- snapshot, release, err := s.addView(ctx, folder.Name, uri)
- if err == source.ErrViewExists {
- continue
- }
- if err != nil {
- viewErrors[uri] = err
- work.End(fmt.Sprintf("Error loading packages: %s", err))
- continue
- }
- var swg sync.WaitGroup
- swg.Add(1)
- allFoldersWg.Add(1)
- go func() {
- defer swg.Done()
- defer allFoldersWg.Done()
- snapshot.AwaitInitialized(ctx)
- work.End("Finished loading packages.")
- }()
-
- // Print each view's environment.
- buf := &bytes.Buffer{}
- if err := snapshot.WriteEnv(ctx, buf); err != nil {
- viewErrors[uri] = err
- continue
- }
- event.Log(ctx, buf.String())
-
- // Diagnose the newly created view.
- wg.Add(1)
- go func() {
- s.diagnoseDetached(snapshot)
- swg.Wait()
- release()
- wg.Done()
- }()
- }
-
- // Register for file watching notifications, if they are supported.
- // Wait for all snapshots to be initialized first, since all files might
- // not yet be known to the snapshots.
- allFoldersWg.Wait()
- if err := s.updateWatchedDirectories(ctx); err != nil {
- event.Error(ctx, "failed to register for file watching notifications", err)
- }
-
- 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)
- }
- return s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
- Type: protocol.Error,
- Message: errMsg,
- })
- }
- return nil
-}
-
-// 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 equalURISet(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)
-}
-
-func equalURISet(m1, m2 map[string]struct{}) bool {
- if len(m1) != len(m2) {
- return false
- }
- for k := range m1 {
- _, ok := m2[k]
- if !ok {
- return false
- }
- }
- return true
-}
-
-// registerWatchedDirectoriesLocked sends the workspace/didChangeWatchedFiles
-// registrations to the client and updates s.watchedDirectories.
-func (s *Server) registerWatchedDirectoriesLocked(ctx context.Context, patterns map[string]struct{}) error {
- if !s.session.Options().DynamicWatchedFilesSupported {
- return nil
- }
- for k := range s.watchedGlobPatterns {
- delete(s.watchedGlobPatterns, k)
- }
- var watchers []protocol.FileSystemWatcher
- for pattern := range patterns {
- watchers = append(watchers, protocol.FileSystemWatcher{
- GlobPattern: pattern,
- Kind: uint32(protocol.WatchChange + protocol.WatchDelete + protocol.WatchCreate),
- })
- }
-
- 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++
-
- for k, v := range patterns {
- s.watchedGlobPatterns[k] = v
- }
- return nil
-}
-
-func (s *Server) fetchConfig(ctx context.Context, name string, folder span.URI, o *source.Options) error {
- if !s.session.Options().ConfigurationSupported {
- return nil
- }
- configs, err := s.client.Configuration(ctx, &protocol.ParamConfiguration{
- ConfigurationParams: protocol.ConfigurationParams{
- Items: []protocol.ConfigurationItem{{
- ScopeURI: string(folder),
- Section: "gopls",
- }},
- },
- })
- if err != nil {
- return fmt.Errorf("failed to get workspace configuration from client (%s): %v", folder, err)
- }
- for _, config := range configs {
- if err := s.handleOptionResults(ctx, source.SetOptions(o, config)); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (s *Server) eventuallyShowMessage(ctx context.Context, msg *protocol.ShowMessageParams) error {
- s.stateMu.Lock()
- defer s.stateMu.Unlock()
- if s.state == serverInitialized {
- return s.client.ShowMessage(ctx, msg)
- }
- s.notifications = append(s.notifications, msg)
- return nil
-}
-
-func (s *Server) handleOptionResults(ctx context.Context, results source.OptionResults) error {
- for _, result := range results {
- if result.Error != nil {
- msg := &protocol.ShowMessageParams{
- Type: protocol.Error,
- Message: result.Error.Error(),
- }
- if err := s.eventuallyShowMessage(ctx, msg); err != nil {
- return err
- }
- }
- switch result.State {
- case source.OptionUnexpected:
- msg := &protocol.ShowMessageParams{
- Type: protocol.Error,
- Message: fmt.Sprintf("unexpected gopls setting %q", result.Name),
- }
- if err := s.eventuallyShowMessage(ctx, msg); err != nil {
- return err
- }
- case source.OptionDeprecated:
- msg := fmt.Sprintf("gopls setting %q is deprecated", result.Name)
- if result.Replacement != "" {
- msg = fmt.Sprintf("%s, use %q instead", msg, result.Replacement)
- }
- if err := s.eventuallyShowMessage(ctx, &protocol.ShowMessageParams{
- Type: protocol.Warning,
- Message: msg,
- }); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-// beginFileRequest checks preconditions for a file-oriented request and routes
-// it to a snapshot.
-// We don't want to return errors for benign conditions like wrong file type,
-// so callers should do if !ok { return err } rather than if err != nil.
-func (s *Server) beginFileRequest(ctx context.Context, pURI protocol.DocumentURI, expectKind source.FileKind) (source.Snapshot, source.VersionedFileHandle, bool, func(), error) {
- uri := pURI.SpanURI()
- if !uri.IsFile() {
- // Not a file URI. Stop processing the request, but don't return an error.
- return nil, nil, false, func() {}, nil
- }
- view, err := s.session.ViewOf(uri)
- if err != nil {
- return nil, nil, false, func() {}, err
- }
- snapshot, release := view.Snapshot(ctx)
- fh, err := snapshot.GetVersionedFile(ctx, uri)
- if err != nil {
- release()
- return nil, nil, false, func() {}, err
- }
- kind := snapshot.View().FileKind(fh)
- if expectKind != source.UnknownKind && kind != expectKind {
- // Wrong kind of file. Nothing to do.
- release()
- return nil, nil, false, func() {}, nil
- }
- return snapshot, fh, true, release, nil
-}
-
-func (s *Server) shutdown(ctx context.Context) error {
- s.stateMu.Lock()
- defer s.stateMu.Unlock()
- if s.state < serverInitialized {
- event.Log(ctx, "server shutdown without initialization")
- }
- if s.state != serverShutDown {
- // 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 {
- 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
-}
diff --git a/internal/lsp/helper/README.md b/internal/lsp/helper/README.md
deleted file mode 100644
index 3c51efe88..000000000
--- a/internal/lsp/helper/README.md
+++ /dev/null
@@ -1,33 +0,0 @@
-# Generate server_gen.go
-
-`helper` generates boilerplate code for server.go by processing the
-generated code in `protocol/tsserver.go`.
-
-First, build `helper` in this directory (`go build .`).
-
-In directory `lsp`, executing `go generate server.go` generates the stylized file
-`server_gen.go` that contains stubs for type `Server`.
-
-It decides what stubs are needed and their signatures
-by looking at the `Server` interface (`-t` flag). These all look somewhat like
-`Resolve(context.Context, *CompletionItem) (*CompletionItem, error)`.
-
-It then parses the `lsp` directory (`-u` flag) to see if there is a corresponding
-implementation function (which in this case would be named `resolve`). If so
-it discovers the parameter names needed, and generates (in `server_gen.go`) code
-like
-
-``` go
-func (s *Server) resolve(ctx context.Context, params *protocol.CompletionItem) (*protocol.CompletionItem, error) {
- return s.resolve(ctx, params)
-}
-```
-
-If `resolve` is not defined (and it is not), then the body of the generated function is
-
-```go
- return nil, notImplemented("resolve")
-```
-
-So to add a capability currently not implemented, just define it somewhere in `lsp`.
-In this case, just define `func (s *Server) resolve(...)` and re-generate `server_gen.go`.
diff --git a/internal/lsp/helper/helper.go b/internal/lsp/helper/helper.go
deleted file mode 100644
index cadda0246..000000000
--- a/internal/lsp/helper/helper.go
+++ /dev/null
@@ -1,258 +0,0 @@
-// 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.
-
-// Invoke with //go:generate helper/helper -t Server -d protocol/tsserver.go -u lsp -o server_gen.go
-// invoke in internal/lsp
-package main
-
-import (
- "bytes"
- "flag"
- "fmt"
- "go/ast"
- "go/format"
- "go/parser"
- "go/token"
- "log"
- "os"
- "sort"
- "strings"
- "text/template"
-)
-
-var (
- typ = flag.String("t", "Server", "generate code for this type")
- def = flag.String("d", "", "the file the type is defined in") // this relies on punning
- use = flag.String("u", "", "look for uses in this package")
- out = flag.String("o", "", "where to write the generated file")
-)
-
-func main() {
- log.SetFlags(log.Lshortfile)
- flag.Parse()
- if *typ == "" || *def == "" || *use == "" || *out == "" {
- flag.PrintDefaults()
- return
- }
- // read the type definition and see what methods we're looking for
- doTypes()
-
- // parse the package and see which methods are defined
- doUses()
-
- output()
-}
-
-// replace "\\\n" with nothing before using
-var tmpl = `// 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 lsp
-
-// code generated by helper. DO NOT EDIT.
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-{{range $key, $v := .Stuff}}
-func (s *{{$.Type}}) {{$v.Name}}({{.Param}}) {{.Result}} {
- {{if ne .Found ""}} return s.{{.Internal}}({{.Invoke}})\
- {{else}}return {{if lt 1 (len .Results)}}nil, {{end}}notImplemented("{{.Name}}"){{end}}
-}
-{{end}}
-`
-
-func output() {
- // put in empty param names as needed
- for _, t := range types {
- if t.paramnames == nil {
- t.paramnames = make([]string, len(t.paramtypes))
- }
- for i, p := range t.paramtypes {
- cm := ""
- if i > 0 {
- cm = ", "
- }
- t.Param += fmt.Sprintf("%s%s %s", cm, t.paramnames[i], p)
- this := t.paramnames[i]
- if this == "_" {
- this = "nil"
- }
- t.Invoke += fmt.Sprintf("%s%s", cm, this)
- }
- if len(t.Results) > 1 {
- t.Result = "("
- }
- for i, r := range t.Results {
- cm := ""
- if i > 0 {
- cm = ", "
- }
- t.Result += fmt.Sprintf("%s%s", cm, r)
- }
- if len(t.Results) > 1 {
- t.Result += ")"
- }
- }
-
- fd, err := os.Create(*out)
- if err != nil {
- log.Fatal(err)
- }
- t, err := template.New("foo").Parse(tmpl)
- if err != nil {
- log.Fatal(err)
- }
- type par struct {
- Type string
- Stuff []*Function
- }
- p := par{*typ, types}
- if false { // debugging the template
- t.Execute(os.Stderr, &p)
- }
- buf := bytes.NewBuffer(nil)
- err = t.Execute(buf, &p)
- if err != nil {
- log.Fatal(err)
- }
- ans, err := format.Source(bytes.Replace(buf.Bytes(), []byte("\\\n"), []byte{}, -1))
- if err != nil {
- log.Fatal(err)
- }
- fd.Write(ans)
-}
-
-func doUses() {
- fset := token.NewFileSet()
- pkgs, err := parser.ParseDir(fset, *use, nil, 0)
- if err != nil {
- log.Fatalf("%q:%v", *use, err)
- }
- pkg := pkgs["lsp"] // CHECK
- files := pkg.Files
- for fname, f := range files {
- for _, d := range f.Decls {
- fd, ok := d.(*ast.FuncDecl)
- if !ok {
- continue
- }
- nm := fd.Name.String()
- if ast.IsExported(nm) {
- // we're looking for things like didChange
- continue
- }
- if fx, ok := byname[nm]; ok {
- if fx.Found != "" {
- log.Fatalf("found %s in %s and %s", fx.Internal, fx.Found, fname)
- }
- fx.Found = fname
- // and the Paramnames
- ft := fd.Type
- for _, f := range ft.Params.List {
- nm := ""
- if len(f.Names) > 0 {
- nm = f.Names[0].String()
- if nm == "_" {
- nm = "_gen"
- }
- }
- fx.paramnames = append(fx.paramnames, nm)
- }
- }
- }
- }
- if false {
- for i, f := range types {
- log.Printf("%d %s %s", i, f.Internal, f.Found)
- }
- }
-}
-
-type Function struct {
- Name string
- Internal string // first letter lower case
- paramtypes []string
- paramnames []string
- Results []string
- Param string
- Result string // do it in code, easier than in a template
- Invoke string
- Found string // file it was found in
-}
-
-var types []*Function
-var byname = map[string]*Function{} // internal names
-
-func doTypes() {
- fset := token.NewFileSet()
- f, err := parser.ParseFile(fset, *def, nil, 0)
- if err != nil {
- log.Fatal(err)
- }
- fd, err := os.Create("/tmp/ast")
- if err != nil {
- log.Fatal(err)
- }
- ast.Fprint(fd, fset, f, ast.NotNilFilter)
- ast.Inspect(f, inter)
- sort.Slice(types, func(i, j int) bool { return types[i].Name < types[j].Name })
- if false {
- for i, f := range types {
- log.Printf("%d %s(%v) %v", i, f.Name, f.paramtypes, f.Results)
- }
- }
-}
-
-func inter(n ast.Node) bool {
- x, ok := n.(*ast.TypeSpec)
- if !ok || x.Name.Name != *typ {
- return true
- }
- m := x.Type.(*ast.InterfaceType).Methods.List
- for _, fld := range m {
- fn := fld.Type.(*ast.FuncType)
- p := fn.Params.List
- r := fn.Results.List
- fx := &Function{
- Name: fld.Names[0].String(),
- }
- fx.Internal = strings.ToLower(fx.Name[:1]) + fx.Name[1:]
- for _, f := range p {
- fx.paramtypes = append(fx.paramtypes, whatis(f.Type))
- }
- for _, f := range r {
- fx.Results = append(fx.Results, whatis(f.Type))
- }
- types = append(types, fx)
- byname[fx.Internal] = fx
- }
- return false
-}
-
-func whatis(x ast.Expr) string {
- switch n := x.(type) {
- case *ast.SelectorExpr:
- return whatis(n.X) + "." + n.Sel.String()
- case *ast.StarExpr:
- return "*" + whatis(n.X)
- case *ast.Ident:
- if ast.IsExported(n.Name) {
- // these are from package protocol
- return "protocol." + n.Name
- }
- return n.Name
- case *ast.ArrayType:
- return "[]" + whatis(n.Elt)
- case *ast.InterfaceType:
- return "interface{}"
- default:
- log.Fatalf("Fatal %T", x)
- return fmt.Sprintf("%T", x)
- }
-}
diff --git a/internal/lsp/highlight.go b/internal/lsp/highlight.go
deleted file mode 100644
index 5dc636eb5..000000000
--- a/internal/lsp/highlight.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/template"
-)
-
-func (s *Server) documentHighlight(ctx context.Context, params *protocol.DocumentHighlightParams) ([]protocol.DocumentHighlight, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.Go)
- defer release()
- if !ok {
- return nil, err
- }
-
- if snapshot.View().FileKind(fh) == source.Tmpl {
- return template.Highlight(ctx, snapshot, fh, params.Position)
- }
-
- rngs, err := source.Highlight(ctx, snapshot, fh, params.Position)
- if err != nil {
- event.Error(ctx, "no highlight", err, tag.URI.Of(params.TextDocument.URI))
- }
- return toProtocolHighlight(rngs), nil
-}
-
-func toProtocolHighlight(rngs []protocol.Range) []protocol.DocumentHighlight {
- result := make([]protocol.DocumentHighlight, 0, len(rngs))
- kind := protocol.Text
- for _, rng := range rngs {
- result = append(result, protocol.DocumentHighlight{
- Kind: kind,
- Range: rng,
- })
- }
- return result
-}
diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go
deleted file mode 100644
index d59f5dbdb..000000000
--- a/internal/lsp/hover.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/mod"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/template"
- "golang.org/x/tools/internal/lsp/work"
-)
-
-func (s *Server) hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
- defer release()
- if !ok {
- return nil, err
- }
- switch snapshot.View().FileKind(fh) {
- case source.Mod:
- return mod.Hover(ctx, snapshot, fh, params.Position)
- case source.Go:
- return source.Hover(ctx, snapshot, fh, params.Position)
- case source.Tmpl:
- return template.Hover(ctx, snapshot, fh, params.Position)
- case source.Work:
- return work.Hover(ctx, snapshot, fh, params.Position)
- }
- return nil, nil
-}
diff --git a/internal/lsp/implementation.go b/internal/lsp/implementation.go
deleted file mode 100644
index 49992b911..000000000
--- a/internal/lsp/implementation.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-func (s *Server) implementation(ctx context.Context, params *protocol.ImplementationParams) ([]protocol.Location, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.Go)
- defer release()
- if !ok {
- return nil, err
- }
- return source.Implementation(ctx, snapshot, fh, params.Position)
-}
diff --git a/internal/lsp/link.go b/internal/lsp/link.go
deleted file mode 100644
index 86c59fc4d..000000000
--- a/internal/lsp/link.go
+++ /dev/null
@@ -1,280 +0,0 @@
-// 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 lsp
-
-import (
- "bytes"
- "context"
- "fmt"
- "go/ast"
- "go/token"
- "net/url"
- "regexp"
- "strconv"
- "strings"
- "sync"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
-)
-
-func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLinkParams) (links []protocol.DocumentLink, err error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
- defer release()
- if !ok {
- return nil, err
- }
- switch snapshot.View().FileKind(fh) {
- case source.Mod:
- links, err = modLinks(ctx, snapshot, fh)
- case source.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, tag.URI.Of(fh.URI()))
- return nil, nil
- }
- return links, nil
-}
-
-func modLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]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.View().IsGoPrivatePath(req.Mod.Path) {
- continue
- }
- dep := []byte(req.Mod.Path)
- s, e := req.Syntax.Start.Byte, req.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.
- start, end := token.Pos(s+i), token.Pos(s+i+len(dep))
- target := source.BuildLink(snapshot.View().Options().LinkTarget, "mod/"+req.Mod.String(), "")
- l, err := toProtocolLink(snapshot, pm.Mapper, target, start, end, source.Mod)
- 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.
- 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(ctx, snapshot, comment.Token, token.Pos(comment.Start.Byte), pm.Mapper, source.Mod)
- if err != nil {
- return nil, err
- }
- links = append(links, l...)
- }
- }
- }
- return links, nil
-}
-
-func goLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.DocumentLink, error) {
- view := snapshot.View()
- // We don't actually need type information, so any typecheck mode is fine.
- pkg, err := snapshot.PackageForFile(ctx, fh.URI(), source.TypecheckWorkspace, source.WidestPackage)
- if err != nil {
- return nil, err
- }
- pgf, err := snapshot.ParseGo(ctx, fh, source.ParseFull)
- if err != nil {
- return nil, err
- }
- var imports []*ast.ImportSpec
- var str []*ast.BasicLit
- ast.Inspect(pgf.File, func(node ast.Node) bool {
- switch n := node.(type) {
- case *ast.ImportSpec:
- imports = append(imports, n)
- return false
- case *ast.BasicLit:
- // Look for links in string literals.
- if n.Kind == token.STRING {
- str = append(str, n)
- }
- return false
- }
- return true
- })
- var links []protocol.DocumentLink
- // For import specs, provide a link to a documentation website, like
- // https://pkg.go.dev.
- if view.Options().ImportShortcut.ShowLinks() {
- for _, imp := range imports {
- target, err := strconv.Unquote(imp.Path.Value)
- if err != nil {
- continue
- }
- // See golang/go#36998: don't link to modules matching GOPRIVATE.
- if view.IsGoPrivatePath(target) {
- continue
- }
- if mod, version, ok := moduleAtVersion(ctx, snapshot, target, pkg); ok && strings.ToLower(view.Options().LinkTarget) == "pkg.go.dev" {
- target = strings.Replace(target, mod, mod+"@"+version, 1)
- }
- // Account for the quotation marks in the positions.
- start := imp.Path.Pos() + 1
- end := imp.Path.End() - 1
- target = source.BuildLink(view.Options().LinkTarget, target, "")
- l, err := toProtocolLink(snapshot, pgf.Mapper, target, start, end, source.Go)
- if err != nil {
- return nil, err
- }
- links = append(links, l)
- }
- }
- for _, s := range str {
- l, err := findLinksInString(ctx, snapshot, s.Value, s.Pos(), pgf.Mapper, source.Go)
- if err != nil {
- return nil, err
- }
- links = append(links, l...)
- }
- for _, commentGroup := range pgf.File.Comments {
- for _, comment := range commentGroup.List {
- l, err := findLinksInString(ctx, snapshot, comment.Text, comment.Pos(), pgf.Mapper, source.Go)
- if err != nil {
- return nil, err
- }
- links = append(links, l...)
- }
- }
- return links, nil
-}
-
-func moduleAtVersion(ctx context.Context, snapshot source.Snapshot, target string, pkg source.Package) (string, string, bool) {
- impPkg, err := pkg.GetImport(target)
- if err != nil {
- return "", "", false
- }
- if impPkg.Version() == nil {
- return "", "", false
- }
- version, modpath := impPkg.Version().Version, impPkg.Version().Path
- if modpath == "" || version == "" {
- return "", "", false
- }
- return modpath, version, true
-}
-
-func findLinksInString(ctx context.Context, snapshot source.Snapshot, src string, pos token.Pos, m *protocol.ColumnMapper, fileKind source.FileKind) ([]protocol.DocumentLink, error) {
- var links []protocol.DocumentLink
- for _, index := range snapshot.View().Options().URLRegexp.FindAllIndex([]byte(src), -1) {
- start, end := index[0], index[1]
- startPos := token.Pos(int(pos) + start)
- endPos := token.Pos(int(pos) + end)
- 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"
- }
- l, err := toProtocolLink(snapshot, m, linkURL.String(), startPos, endPos, fileKind)
- 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]
- startPos := token.Pos(int(pos) + start)
- endPos := token.Pos(int(pos) + end)
- matches := r.FindStringSubmatch(src)
- if len(matches) < 4 {
- continue
- }
- org, repo, number := matches[1], matches[2], matches[3]
- target := fmt.Sprintf("https://github.com/%s/%s/issues/%s", org, repo, number)
- l, err := toProtocolLink(snapshot, m, target, startPos, endPos, fileKind)
- 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(snapshot source.Snapshot, m *protocol.ColumnMapper, target string, start, end token.Pos, fileKind source.FileKind) (protocol.DocumentLink, error) {
- var rng protocol.Range
- switch fileKind {
- case source.Go:
- spn, err := span.NewRange(snapshot.FileSet(), start, end).Span()
- if err != nil {
- return protocol.DocumentLink{}, err
- }
- rng, err = m.Range(spn)
- if err != nil {
- return protocol.DocumentLink{}, err
- }
- case source.Mod:
- s, e := int(start), int(end)
- line, col, err := m.Converter.ToPosition(s)
- if err != nil {
- return protocol.DocumentLink{}, err
- }
- start := span.NewPoint(line, col, s)
- line, col, err = m.Converter.ToPosition(e)
- if err != nil {
- return protocol.DocumentLink{}, err
- }
- end := span.NewPoint(line, col, e)
- rng, err = m.Range(span.New(m.URI, start, end))
- if err != nil {
- return protocol.DocumentLink{}, err
- }
- }
- return protocol.DocumentLink{
- Range: rng,
- Target: target,
- }, nil
-}
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
deleted file mode 100644
index ca0985a98..000000000
--- a/internal/lsp/lsp_test.go
+++ /dev/null
@@ -1,1319 +0,0 @@
-// 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 lsp
-
-import (
- "context"
- "fmt"
- "go/token"
- "os"
- "os/exec"
- "path/filepath"
- "sort"
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/lsp/cache"
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/diff/myers"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/tests"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/testenv"
-)
-
-func TestMain(m *testing.M) {
- testenv.ExitIfSmallMachine()
- os.Exit(m.Run())
-}
-
-func TestLSP(t *testing.T) {
- tests.RunTests(t, "testdata", true, testLSP)
-}
-
-type runner struct {
- server *Server
- data *tests.Data
- diagnostics map[span.URI][]*source.Diagnostic
- ctx context.Context
- normalizers []tests.Normalizer
- editRecv chan map[span.URI]string
-}
-
-func testLSP(t *testing.T, datum *tests.Data) {
- ctx := tests.Context(t)
-
- cache := cache.New(nil)
- session := cache.NewSession(ctx)
- options := source.DefaultOptions().Clone()
- tests.DefaultOptions(options)
- session.SetOptions(options)
- options.SetEnvSlice(datum.Config.Env)
- view, snapshot, release, err := session.NewView(ctx, datum.Config.Dir, span.URIFromPath(datum.Config.Dir), options)
- if err != nil {
- t.Fatal(err)
- }
-
- defer view.Shutdown(ctx)
-
- // Enable type error analyses for tests.
- // TODO(golang/go#38212): Delete this once they are enabled by default.
- tests.EnableAllAnalyzers(view, options)
- view.SetOptions(ctx, options)
-
- // Only run the -modfile specific tests in module mode with Go 1.14 or above.
- datum.ModfileFlagAvailable = len(snapshot.ModFiles()) > 0 && testenv.Go1Point() >= 14
- release()
-
- var modifications []source.FileModification
- for filename, content := range datum.Config.Overlay {
- if filepath.Ext(filename) != ".go" {
- continue
- }
- modifications = append(modifications, source.FileModification{
- URI: span.URIFromPath(filename),
- Action: source.Open,
- Version: -1,
- Text: content,
- LanguageID: "go",
- })
- }
- if err := session.ModifyFiles(ctx, modifications); err != nil {
- t.Fatal(err)
- }
- r := &runner{
- data: datum,
- ctx: ctx,
- normalizers: tests.CollectNormalizers(datum.Exported),
- editRecv: make(chan map[span.URI]string, 1),
- }
-
- r.server = NewServer(session, testClient{runner: r})
- tests.Run(t, r, datum)
-}
-
-// testClient stubs any client functions that may be called by LSP functions.
-type testClient struct {
- protocol.Client
- runner *runner
-}
-
-func (c testClient) Close() error {
- return nil
-}
-
-// Trivially implement PublishDiagnostics so that we can call
-// server.publishReports below to de-dup sent diagnostics.
-func (c testClient) PublishDiagnostics(context.Context, *protocol.PublishDiagnosticsParams) error {
- return nil
-}
-
-func (c testClient) ShowMessage(context.Context, *protocol.ShowMessageParams) error {
- return nil
-}
-
-func (c testClient) ApplyEdit(ctx context.Context, params *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResult, error) {
- res, err := applyTextDocumentEdits(c.runner, params.Edit.DocumentChanges)
- if err != nil {
- return nil, err
- }
- c.runner.editRecv <- res
- return &protocol.ApplyWorkspaceEditResult{Applied: true}, nil
-}
-
-func (r *runner) CallHierarchy(t *testing.T, spn span.Span, expectedCalls *tests.CallHierarchyResult) {
- mapper, err := r.data.Mapper(spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- loc, err := mapper.Location(spn)
- if err != nil {
- t.Fatalf("failed for %v: %v", spn, err)
- }
-
- params := &protocol.CallHierarchyPrepareParams{
- TextDocumentPositionParams: protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- },
- }
-
- items, err := r.server.PrepareCallHierarchy(r.ctx, params)
- if err != nil {
- t.Fatal(err)
- }
- if len(items) == 0 {
- t.Fatalf("expected call hierarchy item to be returned for identifier at %v\n", loc.Range)
- }
-
- callLocation := protocol.Location{
- URI: items[0].URI,
- Range: items[0].Range,
- }
- if callLocation != loc {
- t.Fatalf("expected server.PrepareCallHierarchy to return identifier at %v but got %v\n", loc, callLocation)
- }
-
- incomingCalls, err := r.server.IncomingCalls(r.ctx, &protocol.CallHierarchyIncomingCallsParams{Item: items[0]})
- if err != nil {
- t.Error(err)
- }
- var incomingCallItems []protocol.CallHierarchyItem
- for _, item := range incomingCalls {
- incomingCallItems = append(incomingCallItems, item.From)
- }
- msg := tests.DiffCallHierarchyItems(incomingCallItems, expectedCalls.IncomingCalls)
- if msg != "" {
- t.Error(fmt.Sprintf("incoming calls: %s", msg))
- }
-
- outgoingCalls, err := r.server.OutgoingCalls(r.ctx, &protocol.CallHierarchyOutgoingCallsParams{Item: items[0]})
- if err != nil {
- t.Error(err)
- }
- var outgoingCallItems []protocol.CallHierarchyItem
- for _, item := range outgoingCalls {
- outgoingCallItems = append(outgoingCallItems, item.To)
- }
- msg = tests.DiffCallHierarchyItems(outgoingCallItems, expectedCalls.OutgoingCalls)
- if msg != "" {
- t.Error(fmt.Sprintf("outgoing calls: %s", msg))
- }
-}
-
-func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) {
- if !strings.HasSuffix(uri.Filename(), "go.mod") {
- return
- }
- got, err := r.server.codeLens(r.ctx, &protocol.CodeLensParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.DocumentURI(uri),
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- if diff := tests.DiffCodeLens(uri, want, got); diff != "" {
- t.Errorf("%s: %s", uri, diff)
- }
-}
-
-func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []*source.Diagnostic) {
- // Get the diagnostics for this view if we have not done it before.
- v := r.server.session.View(r.data.Config.Dir)
- r.collectDiagnostics(v)
- d := r.diagnostics[uri]
- got := make([]*source.Diagnostic, len(d))
- copy(got, d)
- // A special case to test that there are no diagnostics for a file.
- if len(want) == 1 && want[0].Source == "no_diagnostics" {
- if len(got) != 0 {
- t.Errorf("expected no diagnostics for %s, got %v", uri, got)
- }
- return
- }
- if diff := tests.DiffDiagnostics(uri, want, got); diff != "" {
- t.Error(diff)
- }
-}
-
-func (r *runner) FoldingRanges(t *testing.T, spn span.Span) {
- uri := spn.URI()
- view, err := r.server.session.ViewOf(uri)
- if err != nil {
- t.Fatal(err)
- }
- original := view.Options()
- modified := original
-
- // Test all folding ranges.
- modified.LineFoldingOnly = false
- view, err = view.SetOptions(r.ctx, modified)
- if err != nil {
- t.Error(err)
- return
- }
- ranges, err := r.server.FoldingRange(r.ctx, &protocol.FoldingRangeParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- })
- if err != nil {
- t.Error(err)
- return
- }
- r.foldingRanges(t, "foldingRange", uri, ranges)
-
- // Test folding ranges with lineFoldingOnly = true.
- modified.LineFoldingOnly = true
- view, err = view.SetOptions(r.ctx, modified)
- if err != nil {
- t.Error(err)
- return
- }
- ranges, err = r.server.FoldingRange(r.ctx, &protocol.FoldingRangeParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- })
- if err != nil {
- t.Error(err)
- return
- }
- r.foldingRanges(t, "foldingRange-lineFolding", uri, ranges)
- view.SetOptions(r.ctx, original)
-}
-
-func (r *runner) foldingRanges(t *testing.T, prefix string, uri span.URI, ranges []protocol.FoldingRange) {
- m, err := r.data.Mapper(uri)
- if err != nil {
- t.Fatal(err)
- }
- // Fold all ranges.
- nonOverlapping := nonOverlappingRanges(ranges)
- for i, rngs := range nonOverlapping {
- got, err := foldRanges(m, string(m.Content), rngs)
- if err != nil {
- t.Error(err)
- continue
- }
- tag := fmt.Sprintf("%s-%d", prefix, i)
- want := string(r.data.Golden(tag, uri.Filename(), func() ([]byte, error) {
- return []byte(got), nil
- }))
-
- if want != got {
- t.Errorf("%s: foldingRanges failed for %s, expected:\n%v\ngot:\n%v", tag, uri.Filename(), want, got)
- }
- }
-
- // Filter by kind.
- kinds := []protocol.FoldingRangeKind{protocol.Imports, protocol.Comment}
- for _, kind := range kinds {
- var kindOnly []protocol.FoldingRange
- for _, fRng := range ranges {
- if fRng.Kind == string(kind) {
- kindOnly = append(kindOnly, fRng)
- }
- }
-
- nonOverlapping := nonOverlappingRanges(kindOnly)
- for i, rngs := range nonOverlapping {
- got, err := foldRanges(m, string(m.Content), rngs)
- if err != nil {
- t.Error(err)
- continue
- }
- tag := fmt.Sprintf("%s-%s-%d", prefix, kind, i)
- want := string(r.data.Golden(tag, uri.Filename(), func() ([]byte, error) {
- return []byte(got), nil
- }))
-
- if want != got {
- t.Errorf("%s: foldingRanges failed for %s, expected:\n%v\ngot:\n%v", tag, uri.Filename(), want, got)
- }
- }
-
- }
-}
-
-func nonOverlappingRanges(ranges []protocol.FoldingRange) (res [][]protocol.FoldingRange) {
- for _, fRng := range ranges {
- setNum := len(res)
- for i := 0; i < len(res); i++ {
- canInsert := true
- for _, rng := range res[i] {
- if conflict(rng, fRng) {
- canInsert = false
- break
- }
- }
- if canInsert {
- setNum = i
- break
- }
- }
- if setNum == len(res) {
- res = append(res, []protocol.FoldingRange{})
- }
- res[setNum] = append(res[setNum], fRng)
- }
- return res
-}
-
-func conflict(a, b protocol.FoldingRange) bool {
- // a start position is <= b start positions
- return (a.StartLine < b.StartLine || (a.StartLine == b.StartLine && a.StartCharacter <= b.StartCharacter)) &&
- (a.EndLine > b.StartLine || (a.EndLine == b.StartLine && a.EndCharacter > b.StartCharacter))
-}
-
-func foldRanges(m *protocol.ColumnMapper, contents string, ranges []protocol.FoldingRange) (string, error) {
- foldedText := "<>"
- res := contents
- // Apply the edits from the end of the file forward
- // to preserve the offsets
- for i := len(ranges) - 1; i >= 0; i-- {
- fRange := ranges[i]
- spn, err := m.RangeSpan(protocol.Range{
- Start: protocol.Position{
- Line: fRange.StartLine,
- Character: fRange.StartCharacter,
- },
- End: protocol.Position{
- Line: fRange.EndLine,
- Character: fRange.EndCharacter,
- },
- })
- if err != nil {
- return "", err
- }
- start := spn.Start().Offset()
- end := spn.End().Offset()
-
- tmp := res[0:start] + foldedText
- res = tmp + res[end:]
- }
- return res, nil
-}
-
-func (r *runner) Format(t *testing.T, spn span.Span) {
- uri := spn.URI()
- filename := uri.Filename()
- gofmted := string(r.data.Golden("gofmt", filename, func() ([]byte, error) {
- cmd := exec.Command("gofmt", filename)
- out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files
- return out, nil
- }))
-
- edits, err := r.server.Formatting(r.ctx, &protocol.DocumentFormattingParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- })
- if err != nil {
- if gofmted != "" {
- t.Error(err)
- }
- return
- }
- m, err := r.data.Mapper(uri)
- if err != nil {
- t.Fatal(err)
- }
- sedits, err := source.FromProtocolEdits(m, edits)
- if err != nil {
- t.Error(err)
- }
- got := diff.ApplyEdits(string(m.Content), sedits)
- if gofmted != got {
- t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got)
- }
-}
-
-func (r *runner) SemanticTokens(t *testing.T, spn span.Span) {
- uri := spn.URI()
- filename := uri.Filename()
- // this is called solely for coverage in semantic.go
- _, err := r.server.semanticTokensFull(r.ctx, &protocol.SemanticTokensParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- })
- if err != nil {
- t.Errorf("%v for %s", err, filename)
- }
- _, err = r.server.semanticTokensRange(r.ctx, &protocol.SemanticTokensRangeParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- // any legal range. Just to exercise the call.
- Range: protocol.Range{
- Start: protocol.Position{
- Line: 0,
- Character: 0,
- },
- End: protocol.Position{
- Line: 2,
- Character: 0,
- },
- },
- })
- if err != nil {
- t.Errorf("%v for Range %s", err, filename)
- }
-}
-
-func (r *runner) Import(t *testing.T, spn span.Span) {
- uri := spn.URI()
- filename := uri.Filename()
- actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- m, err := r.data.Mapper(uri)
- if err != nil {
- t.Fatal(err)
- }
- got := string(m.Content)
- if len(actions) > 0 {
- res, err := applyTextDocumentEdits(r, actions[0].Edit.DocumentChanges)
- if err != nil {
- t.Fatal(err)
- }
- got = res[uri]
- }
- want := string(r.data.Golden("goimports", filename, func() ([]byte, error) {
- return []byte(got), nil
- }))
- if want != got {
- d, err := myers.ComputeEdits(uri, want, got)
- if err != nil {
- t.Fatal(err)
- }
- t.Errorf("import failed for %s: %s", filename, diff.ToUnified("want", "got", want, d))
- }
-}
-
-func (r *runner) SuggestedFix(t *testing.T, spn span.Span, actionKinds []string, expectedActions int) {
- uri := spn.URI()
- view, err := r.server.session.ViewOf(uri)
- if err != nil {
- t.Fatal(err)
- }
-
- m, err := r.data.Mapper(uri)
- if err != nil {
- t.Fatal(err)
- }
- rng, err := m.Range(spn)
- if err != nil {
- t.Fatal(err)
- }
- // Get the diagnostics for this view if we have not done it before.
- r.collectDiagnostics(view)
- var diagnostics []protocol.Diagnostic
- for _, d := range r.diagnostics[uri] {
- // Compare the start positions rather than the entire range because
- // some diagnostics have a range with the same start and end position (8:1-8:1).
- // The current marker functionality prevents us from having a range of 0 length.
- if protocol.ComparePosition(d.Range.Start, rng.Start) == 0 {
- diagnostics = append(diagnostics, toProtocolDiagnostics([]*source.Diagnostic{d})...)
- break
- }
- }
- codeActionKinds := []protocol.CodeActionKind{}
- for _, k := range actionKinds {
- codeActionKinds = append(codeActionKinds, protocol.CodeActionKind(k))
- }
- actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- Range: rng,
- Context: protocol.CodeActionContext{
- Only: codeActionKinds,
- Diagnostics: diagnostics,
- },
- })
- if err != nil {
- t.Fatalf("CodeAction %s failed: %v", spn, err)
- }
- if len(actions) != expectedActions {
- // Hack: We assume that we only get one code action per range.
- var cmds []string
- for _, a := range actions {
- cmds = append(cmds, fmt.Sprintf("%s (%s)", a.Command, a.Title))
- }
- t.Fatalf("unexpected number of code actions, want %d, got %d: %v", expectedActions, len(actions), cmds)
- }
- action := actions[0]
- var match bool
- for _, k := range codeActionKinds {
- if action.Kind == k {
- match = true
- break
- }
- }
- if !match {
- t.Fatalf("unexpected kind for code action %s, expected one of %v, got %v", action.Title, codeActionKinds, action.Kind)
- }
- var res map[span.URI]string
- if cmd := action.Command; cmd != nil {
- _, err := r.server.ExecuteCommand(r.ctx, &protocol.ExecuteCommandParams{
- Command: action.Command.Command,
- Arguments: action.Command.Arguments,
- })
- if err != nil {
- t.Fatalf("error converting command %q to edits: %v", action.Command.Command, err)
- }
- res = <-r.editRecv
- } else {
- res, err = applyTextDocumentEdits(r, action.Edit.DocumentChanges)
- if err != nil {
- t.Fatal(err)
- }
- }
- for u, got := range res {
- want := string(r.data.Golden("suggestedfix_"+tests.SpanName(spn), u.Filename(), func() ([]byte, error) {
- return []byte(got), nil
- }))
- if want != got {
- t.Errorf("suggested fixes failed for %s:\n%s", u.Filename(), tests.Diff(t, want, got))
- }
- }
-}
-
-func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span) {
- uri := start.URI()
- m, err := r.data.Mapper(uri)
- if err != nil {
- t.Fatal(err)
- }
- spn := span.New(start.URI(), start.Start(), end.End())
- rng, err := m.Range(spn)
- if err != nil {
- t.Fatal(err)
- }
- actionsRaw, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- Range: rng,
- Context: protocol.CodeActionContext{
- Only: []protocol.CodeActionKind{"refactor.extract"},
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- var actions []protocol.CodeAction
- for _, action := range actionsRaw {
- if action.Command.Title == "Extract function" {
- actions = append(actions, action)
- }
- }
- // Hack: We assume that we only get one code action per range.
- // TODO(rstambler): Support multiple code actions per test.
- if len(actions) == 0 || len(actions) > 1 {
- t.Fatalf("unexpected number of code actions, want 1, got %v", len(actions))
- }
- _, err = r.server.ExecuteCommand(r.ctx, &protocol.ExecuteCommandParams{
- Command: actions[0].Command.Command,
- Arguments: actions[0].Command.Arguments,
- })
- if err != nil {
- t.Fatal(err)
- }
- res := <-r.editRecv
- for u, got := range res {
- want := string(r.data.Golden("functionextraction_"+tests.SpanName(spn), u.Filename(), func() ([]byte, error) {
- return []byte(got), nil
- }))
- if want != got {
- t.Errorf("function extraction failed for %s:\n%s", u.Filename(), tests.Diff(t, want, got))
- }
- }
-}
-
-func (r *runner) MethodExtraction(t *testing.T, start span.Span, end span.Span) {
- uri := start.URI()
- m, err := r.data.Mapper(uri)
- if err != nil {
- t.Fatal(err)
- }
- spn := span.New(start.URI(), start.Start(), end.End())
- rng, err := m.Range(spn)
- if err != nil {
- t.Fatal(err)
- }
- actionsRaw, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- Range: rng,
- Context: protocol.CodeActionContext{
- Only: []protocol.CodeActionKind{"refactor.extract"},
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- var actions []protocol.CodeAction
- for _, action := range actionsRaw {
- if action.Command.Title == "Extract method" {
- actions = append(actions, action)
- }
- }
- // Hack: We assume that we only get one matching code action per range.
- // TODO(rstambler): Support multiple code actions per test.
- if len(actions) == 0 || len(actions) > 1 {
- t.Fatalf("unexpected number of code actions, want 1, got %v", len(actions))
- }
- _, err = r.server.ExecuteCommand(r.ctx, &protocol.ExecuteCommandParams{
- Command: actions[0].Command.Command,
- Arguments: actions[0].Command.Arguments,
- })
- if err != nil {
- t.Fatal(err)
- }
- res := <-r.editRecv
- for u, got := range res {
- want := string(r.data.Golden("methodextraction_"+tests.SpanName(spn), u.Filename(), func() ([]byte, error) {
- return []byte(got), nil
- }))
- if want != got {
- t.Errorf("method extraction failed for %s:\n%s", u.Filename(), tests.Diff(t, want, got))
- }
- }
-}
-
-func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) {
- sm, err := r.data.Mapper(d.Src.URI())
- if err != nil {
- t.Fatal(err)
- }
- loc, err := sm.Location(d.Src)
- if err != nil {
- t.Fatalf("failed for %v: %v", d.Src, err)
- }
- tdpp := protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- }
- var locs []protocol.Location
- var hover *protocol.Hover
- if d.IsType {
- params := &protocol.TypeDefinitionParams{
- TextDocumentPositionParams: tdpp,
- }
- locs, err = r.server.TypeDefinition(r.ctx, params)
- } else {
- params := &protocol.DefinitionParams{
- TextDocumentPositionParams: tdpp,
- }
- locs, err = r.server.Definition(r.ctx, params)
- if err != nil {
- t.Fatalf("failed for %v: %+v", d.Src, err)
- }
- v := &protocol.HoverParams{
- TextDocumentPositionParams: tdpp,
- }
- hover, err = r.server.Hover(r.ctx, v)
- }
- if err != nil {
- t.Fatalf("failed for %v: %v", d.Src, err)
- }
- if len(locs) != 1 {
- t.Errorf("got %d locations for definition, expected 1", len(locs))
- }
- didSomething := false
- if hover != nil {
- didSomething = true
- tag := fmt.Sprintf("%s-hoverdef", d.Name)
- expectHover := string(r.data.Golden(tag, d.Src.URI().Filename(), func() ([]byte, error) {
- return []byte(hover.Contents.Value), nil
- }))
- got := tests.StripSubscripts(hover.Contents.Value)
- expectHover = tests.StripSubscripts(expectHover)
- if got != expectHover {
- t.Errorf("%s:\n%s", d.Src, tests.Diff(t, expectHover, got))
- }
- }
- if !d.OnlyHover {
- didSomething = true
- locURI := locs[0].URI.SpanURI()
- lm, err := r.data.Mapper(locURI)
- if err != nil {
- t.Fatal(err)
- }
- if def, err := lm.Span(locs[0]); err != nil {
- t.Fatalf("failed for %v: %v", locs[0], err)
- } else if def != d.Def {
- t.Errorf("for %v got %v want %v", d.Src, def, d.Def)
- }
- }
- if !didSomething {
- t.Errorf("no tests ran for %s", d.Src.URI())
- }
-}
-
-func (r *runner) Implementation(t *testing.T, spn span.Span, impls []span.Span) {
- sm, err := r.data.Mapper(spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- loc, err := sm.Location(spn)
- if err != nil {
- t.Fatalf("failed for %v: %v", spn, err)
- }
- tdpp := protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- }
- var locs []protocol.Location
- params := &protocol.ImplementationParams{
- TextDocumentPositionParams: tdpp,
- }
- locs, err = r.server.Implementation(r.ctx, params)
- if err != nil {
- t.Fatalf("failed for %v: %v", spn, err)
- }
- if len(locs) != len(impls) {
- t.Fatalf("got %d locations for implementation, expected %d", len(locs), len(impls))
- }
-
- var results []span.Span
- for i := range locs {
- locURI := locs[i].URI.SpanURI()
- lm, err := r.data.Mapper(locURI)
- if err != nil {
- t.Fatal(err)
- }
- imp, err := lm.Span(locs[i])
- if err != nil {
- t.Fatalf("failed for %v: %v", locs[i], err)
- }
- results = append(results, imp)
- }
- // Sort results and expected to make tests deterministic.
- sort.SliceStable(results, func(i, j int) bool {
- return span.Compare(results[i], results[j]) == -1
- })
- sort.SliceStable(impls, func(i, j int) bool {
- return span.Compare(impls[i], impls[j]) == -1
- })
- for i := range results {
- if results[i] != impls[i] {
- t.Errorf("for %dth implementation of %v got %v want %v", i, spn, results[i], impls[i])
- }
- }
-}
-
-func (r *runner) Highlight(t *testing.T, src span.Span, locations []span.Span) {
- m, err := r.data.Mapper(src.URI())
- if err != nil {
- t.Fatal(err)
- }
- loc, err := m.Location(src)
- if err != nil {
- t.Fatalf("failed for %v: %v", locations[0], err)
- }
- tdpp := protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- }
- params := &protocol.DocumentHighlightParams{
- TextDocumentPositionParams: tdpp,
- }
- highlights, err := r.server.DocumentHighlight(r.ctx, params)
- if err != nil {
- t.Fatal(err)
- }
- if len(highlights) != len(locations) {
- t.Fatalf("got %d highlights for highlight at %v:%v:%v, expected %d", len(highlights), src.URI().Filename(), src.Start().Line(), src.Start().Column(), len(locations))
- }
- // Check to make sure highlights have a valid range.
- var results []span.Span
- for i := range highlights {
- h, err := m.RangeSpan(highlights[i].Range)
- if err != nil {
- t.Fatalf("failed for %v: %v", highlights[i], err)
- }
- results = append(results, h)
- }
- // Sort results to make tests deterministic since DocumentHighlight uses a map.
- sort.SliceStable(results, func(i, j int) bool {
- return span.Compare(results[i], results[j]) == -1
- })
- // Check to make sure all the expected highlights are found.
- for i := range results {
- if results[i] != locations[i] {
- t.Errorf("want %v, got %v\n", locations[i], results[i])
- }
- }
-}
-
-func (r *runner) Hover(t *testing.T, src span.Span, text string) {
- m, err := r.data.Mapper(src.URI())
- if err != nil {
- t.Fatal(err)
- }
- loc, err := m.Location(src)
- if err != nil {
- t.Fatalf("failed for %v", err)
- }
- tdpp := protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- }
- params := &protocol.HoverParams{
- TextDocumentPositionParams: tdpp,
- }
- hover, err := r.server.Hover(r.ctx, params)
- if err != nil {
- t.Fatal(err)
- }
- if text == "" {
- if hover != nil {
- t.Errorf("want nil, got %v\n", hover)
- }
- } else {
- if hover == nil {
- t.Fatalf("want hover result to include %s, but got nil", text)
- }
- if got := hover.Contents.Value; got != text {
- t.Errorf("want %v, got %v\n", text, got)
- }
- if want, got := loc.Range, hover.Range; want != got {
- t.Errorf("want range %v, got %v instead", want, got)
- }
- }
-}
-
-func (r *runner) References(t *testing.T, src span.Span, itemList []span.Span) {
- sm, err := r.data.Mapper(src.URI())
- if err != nil {
- t.Fatal(err)
- }
- loc, err := sm.Location(src)
- if err != nil {
- t.Fatalf("failed for %v: %v", src, err)
- }
- for _, includeDeclaration := range []bool{true, false} {
- t.Run(fmt.Sprintf("refs-declaration-%v", includeDeclaration), func(t *testing.T) {
- want := make(map[protocol.Location]bool)
- for i, pos := range itemList {
- // We don't want the first result if we aren't including the declaration.
- if i == 0 && !includeDeclaration {
- continue
- }
- m, err := r.data.Mapper(pos.URI())
- if err != nil {
- t.Fatal(err)
- }
- loc, err := m.Location(pos)
- if err != nil {
- t.Fatalf("failed for %v: %v", src, err)
- }
- want[loc] = true
- }
- params := &protocol.ReferenceParams{
- TextDocumentPositionParams: protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- },
- Context: protocol.ReferenceContext{
- IncludeDeclaration: includeDeclaration,
- },
- }
- got, err := r.server.References(r.ctx, params)
- if err != nil {
- t.Fatalf("failed for %v: %v", src, err)
- }
- if len(got) != len(want) {
- t.Errorf("references failed: different lengths got %v want %v", len(got), len(want))
- }
- for _, loc := range got {
- if !want[loc] {
- t.Errorf("references failed: incorrect references got %v want %v", loc, want)
- }
- }
- })
- }
-}
-
-func (r *runner) Rename(t *testing.T, spn span.Span, newText string) {
- tag := fmt.Sprintf("%s-rename", newText)
-
- uri := spn.URI()
- filename := uri.Filename()
- sm, err := r.data.Mapper(uri)
- if err != nil {
- t.Fatal(err)
- }
- loc, err := sm.Location(spn)
- if err != nil {
- t.Fatalf("failed for %v: %v", spn, err)
- }
-
- wedit, err := r.server.Rename(r.ctx, &protocol.RenameParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- Position: loc.Range.Start,
- NewName: newText,
- })
- if err != nil {
- renamed := string(r.data.Golden(tag, filename, func() ([]byte, error) {
- return []byte(err.Error()), nil
- }))
- if err.Error() != renamed {
- t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v\n", newText, renamed, err)
- }
- return
- }
- res, err := applyTextDocumentEdits(r, wedit.DocumentChanges)
- if err != nil {
- t.Fatal(err)
- }
- var orderedURIs []string
- for uri := range res {
- orderedURIs = append(orderedURIs, string(uri))
- }
- sort.Strings(orderedURIs)
-
- var got string
- for i := 0; i < len(res); i++ {
- if i != 0 {
- got += "\n"
- }
- uri := span.URIFromURI(orderedURIs[i])
- if len(res) > 1 {
- got += filepath.Base(uri.Filename()) + ":\n"
- }
- val := res[uri]
- got += val
- }
- want := string(r.data.Golden(tag, filename, func() ([]byte, error) {
- return []byte(got), nil
- }))
- if want != got {
- t.Errorf("rename failed for %s:\n%s", newText, tests.Diff(t, want, got))
- }
-}
-
-func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) {
- m, err := r.data.Mapper(src.URI())
- if err != nil {
- t.Fatal(err)
- }
- loc, err := m.Location(src)
- if err != nil {
- t.Fatalf("failed for %v: %v", src, err)
- }
- tdpp := protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
- }
- params := &protocol.PrepareRenameParams{
- TextDocumentPositionParams: tdpp,
- }
- got, err := r.server.PrepareRename(context.Background(), params)
- if err != nil {
- t.Errorf("prepare rename failed for %v: got error: %v", src, err)
- return
- }
- // we all love typed nils
- if got == nil {
- if want.Text != "" { // expected an ident.
- t.Errorf("prepare rename failed for %v: got nil", src)
- }
- return
- }
- if got.Range.Start == got.Range.End {
- // Special case for 0-length ranges. Marks can't specify a 0-length range,
- // so just compare the start.
- if got.Range.Start != want.Range.Start {
- t.Errorf("prepare rename failed: incorrect point, got %v want %v", got.Range.Start, want.Range.Start)
- }
- } else {
- if protocol.CompareRange(got.Range, want.Range) != 0 {
- t.Errorf("prepare rename failed: incorrect range got %v want %v", got.Range, want.Range)
- }
- }
- if got.Placeholder != want.Text {
- t.Errorf("prepare rename failed: incorrect text got %v want %v", got.Placeholder, want.Text)
- }
-}
-
-func applyTextDocumentEdits(r *runner, edits []protocol.TextDocumentEdit) (map[span.URI]string, error) {
- res := map[span.URI]string{}
- for _, docEdits := range edits {
- uri := docEdits.TextDocument.URI.SpanURI()
- var m *protocol.ColumnMapper
- // If we have already edited this file, we use the edited version (rather than the
- // file in its original state) so that we preserve our initial changes.
- if content, ok := res[uri]; ok {
- m = &protocol.ColumnMapper{
- URI: uri,
- Converter: span.NewContentConverter(
- uri.Filename(), []byte(content)),
- Content: []byte(content),
- }
- } else {
- var err error
- if m, err = r.data.Mapper(uri); err != nil {
- return nil, err
- }
- }
- res[uri] = string(m.Content)
- sedits, err := source.FromProtocolEdits(m, docEdits.Edits)
- if err != nil {
- return nil, err
- }
- res[uri] = applyEdits(res[uri], sedits)
- }
- return res, nil
-}
-
-func applyEdits(contents string, edits []diff.TextEdit) string {
- res := contents
-
- // Apply the edits from the end of the file forward
- // to preserve the offsets
- for i := len(edits) - 1; i >= 0; i-- {
- edit := edits[i]
- start := edit.Span.Start().Offset()
- end := edit.Span.End().Offset()
- tmp := res[0:start] + edit.NewText
- res = tmp + res[end:]
- }
- return res
-}
-
-func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) {
- params := &protocol.DocumentSymbolParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- }
- got, err := r.server.DocumentSymbol(r.ctx, params)
- if err != nil {
- t.Fatal(err)
- }
- if len(got) != len(expectedSymbols) {
- t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(got))
- return
- }
- symbols := make([]protocol.DocumentSymbol, len(got))
- for i, s := range got {
- s, ok := s.(protocol.DocumentSymbol)
- if !ok {
- t.Fatalf("%v: wanted []DocumentSymbols but got %v", uri, got)
- }
- symbols[i] = s
- }
- if diff := tests.DiffSymbols(t, uri, expectedSymbols, symbols); diff != "" {
- t.Error(diff)
- }
-}
-
-func (r *runner) WorkspaceSymbols(t *testing.T, uri span.URI, query string, typ tests.WorkspaceSymbolsTestType) {
- r.callWorkspaceSymbols(t, uri, query, typ)
-}
-
-func (r *runner) callWorkspaceSymbols(t *testing.T, uri span.URI, query string, typ tests.WorkspaceSymbolsTestType) {
- t.Helper()
-
- matcher := tests.WorkspaceSymbolsTestTypeToMatcher(typ)
-
- original := r.server.session.Options()
- modified := original
- modified.SymbolMatcher = matcher
- r.server.session.SetOptions(modified)
- defer r.server.session.SetOptions(original)
-
- params := &protocol.WorkspaceSymbolParams{
- Query: query,
- }
- gotSymbols, err := r.server.Symbol(r.ctx, params)
- if err != nil {
- t.Fatal(err)
- }
- got, err := tests.WorkspaceSymbolsString(r.ctx, r.data, uri, gotSymbols)
- if err != nil {
- t.Fatal(err)
- }
- got = filepath.ToSlash(tests.Normalize(got, r.normalizers))
- want := string(r.data.Golden(fmt.Sprintf("workspace_symbol-%s-%s", strings.ToLower(string(matcher)), query), uri.Filename(), func() ([]byte, error) {
- return []byte(got), nil
- }))
- if diff := tests.Diff(t, want, got); diff != "" {
- t.Error(diff)
- }
-}
-
-func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.SignatureHelp) {
- m, err := r.data.Mapper(spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- loc, err := m.Location(spn)
- if err != nil {
- t.Fatalf("failed for %v: %v", loc, err)
- }
- tdpp := protocol.TextDocumentPositionParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(spn.URI()),
- },
- Position: loc.Range.Start,
- }
- params := &protocol.SignatureHelpParams{
- TextDocumentPositionParams: tdpp,
- }
- got, err := r.server.SignatureHelp(r.ctx, params)
- if err != nil {
- // Only fail if we got an error we did not expect.
- if want != nil {
- t.Fatal(err)
- }
- return
- }
- if want == nil {
- if got != nil {
- t.Errorf("expected no signature, got %v", got)
- }
- return
- }
- if got == nil {
- t.Fatalf("expected %v, got nil", want)
- }
- diff, err := tests.DiffSignatures(spn, want, got)
- if err != nil {
- t.Fatal(err)
- }
- if diff != "" {
- t.Error(diff)
- }
-}
-
-func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
- m, err := r.data.Mapper(uri)
- if err != nil {
- t.Fatal(err)
- }
- got, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{
- TextDocument: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(uri),
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- if diff := tests.DiffLinks(m, wantLinks, got); diff != "" {
- t.Error(diff)
- }
-}
-
-func (r *runner) AddImport(t *testing.T, uri span.URI, expectedImport string) {
- cmd, err := command.NewListKnownPackagesCommand("List Known Packages", command.URIArg{
- URI: protocol.URIFromSpanURI(uri),
- })
- if err != nil {
- t.Fatal(err)
- }
- resp, err := r.server.executeCommand(r.ctx, &protocol.ExecuteCommandParams{
- Command: cmd.Command,
- Arguments: cmd.Arguments,
- })
- if err != nil {
- t.Fatal(err)
- }
- res := resp.(command.ListKnownPackagesResult)
- var hasPkg bool
- for _, p := range res.Packages {
- if p == expectedImport {
- hasPkg = true
- break
- }
- }
- if !hasPkg {
- t.Fatalf("%s: got %v packages\nwant contains %q", command.ListKnownPackages, res.Packages, expectedImport)
- }
- cmd, err = command.NewAddImportCommand("Add Imports", command.AddImportArgs{
- URI: protocol.URIFromSpanURI(uri),
- ImportPath: expectedImport,
- })
- if err != nil {
- t.Fatal(err)
- }
- _, err = r.server.executeCommand(r.ctx, &protocol.ExecuteCommandParams{
- Command: cmd.Command,
- Arguments: cmd.Arguments,
- })
- if err != nil {
- t.Fatal(err)
- }
- got := (<-r.editRecv)[uri]
- want := r.data.Golden("addimport", uri.Filename(), func() ([]byte, error) {
- return []byte(got), nil
- })
- if want == nil {
- t.Fatalf("golden file %q not found", uri.Filename())
- }
- if diff := tests.Diff(t, got, string(want)); diff != "" {
- t.Errorf("%s mismatch\n%s", command.AddImport, diff)
- }
-}
-
-func TestBytesOffset(t *testing.T) {
- tests := []struct {
- text string
- pos protocol.Position
- want int
- }{
- {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: 3},
- {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: 7},
- {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)
- fset := token.NewFileSet()
- f := fset.AddFile(fname, -1, len(test.text))
- f.SetLinesForContent([]byte(test.text))
- uri := span.URIFromPath(fname)
- converter := span.NewContentConverter(fname, []byte(test.text))
- mapper := &protocol.ColumnMapper{
- URI: uri,
- Converter: converter,
- Content: []byte(test.text),
- }
- got, err := mapper.Point(test.pos)
- if err != nil && test.want != -1 {
- t.Errorf("unexpected error: %v", err)
- }
- if err == nil && got.Offset() != 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.Offset())
- }
- }
-}
-
-func (r *runner) collectDiagnostics(view source.View) {
- if r.diagnostics != nil {
- return
- }
- r.diagnostics = make(map[span.URI][]*source.Diagnostic)
-
- snapshot, release := view.Snapshot(r.ctx)
- defer release()
-
- // Always run diagnostics with analysis.
- r.server.diagnose(r.ctx, snapshot, true)
- for uri, reports := range r.server.diagnostics {
- for _, report := range reports.reports {
- for _, d := range report.diags {
- r.diagnostics[uri] = append(r.diagnostics[uri], d)
- }
- }
- }
-}
diff --git a/internal/lsp/lsppos/lsppos.go b/internal/lsp/lsppos/lsppos.go
deleted file mode 100644
index f27bde573..000000000
--- a/internal/lsp/lsppos/lsppos.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// 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 lsppos provides utilities for working with LSP positions.
-//
-// See https://microsoft.github.io/language-server-protocol/specification#textDocuments
-// for a description of LSP positions. Notably:
-// - Positions are specified by a 0-based line count and 0-based utf-16
-// character offset.
-// - Positions are line-ending agnostic: there is no way to specify \r|\n or
-// \n|. Instead the former maps to the end of the current line, and the
-// latter to the start of the next line.
-package lsppos
-
-import (
- "sort"
- "unicode/utf8"
-)
-
-type Mapper struct {
- nonASCII bool
- src []byte
-
- // Start-of-line positions. If src is newline-terminated, the final entry will be empty.
- lines []int
-}
-
-func NewMapper(src []byte) *Mapper {
- m := &Mapper{src: src}
- if len(src) == 0 {
- return m
- }
- m.lines = []int{0}
- for offset, b := range src {
- if b == '\n' {
- m.lines = append(m.lines, offset+1)
- }
- if b >= utf8.RuneSelf {
- m.nonASCII = true
- }
- }
- return m
-}
-
-func (m *Mapper) Position(offset int) (line, char int) {
- if offset < 0 || offset > len(m.src) {
- return -1, -1
- }
- nextLine := sort.Search(len(m.lines), func(i int) bool {
- return offset < m.lines[i]
- })
- if nextLine == 0 {
- return -1, -1
- }
- line = nextLine - 1
- start := m.lines[line]
- var charOffset int
- if m.nonASCII {
- charOffset = UTF16len(m.src[start:offset])
- } else {
- charOffset = offset - start
- }
-
- var eol int
- if line == len(m.lines)-1 {
- eol = len(m.src)
- } else {
- eol = m.lines[line+1] - 1
- }
-
- // Adjustment for line-endings: \r|\n is the same as |\r\n.
- if offset == eol && offset > 0 && m.src[offset-1] == '\r' {
- charOffset--
- }
-
- return line, charOffset
-}
-
-func UTF16len(buf []byte) int {
- cnt := 0
- for _, r := range string(buf) {
- cnt++
- if r >= 1<<16 {
- cnt++
- }
- }
- return cnt
-}
diff --git a/internal/lsp/lsprpc/autostart_default.go b/internal/lsp/lsprpc/autostart_default.go
deleted file mode 100644
index b23a1e508..000000000
--- a/internal/lsp/lsprpc/autostart_default.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// 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 (
- exec "golang.org/x/sys/execabs"
-
- errors "golang.org/x/xerrors"
-)
-
-var (
- daemonize = func(*exec.Cmd) {}
- autoNetworkAddress = autoNetworkAddressDefault
- verifyRemoteOwnership = verifyRemoteOwnershipDefault
-)
-
-func runRemote(cmd *exec.Cmd) error {
- daemonize(cmd)
- if err := cmd.Start(); err != nil {
- return errors.Errorf("starting remote gopls: %w", err)
- }
- return nil
-}
-
-// autoNetworkAddress 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/internal/lsp/lsprpc/autostart_posix.go b/internal/lsp/lsprpc/autostart_posix.go
deleted file mode 100644
index d5644e2b6..000000000
--- a/internal/lsp/lsprpc/autostart_posix.go
+++ /dev/null
@@ -1,99 +0,0 @@
-// 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/user"
- "path/filepath"
- "strconv"
- "syscall"
-
- exec "golang.org/x/sys/execabs"
-
- "golang.org/x/xerrors"
-)
-
-func init() {
- daemonize = daemonizePosix
- autoNetworkAddress = autoNetworkAddressPosix
- verifyRemoteOwnership = verifyRemoteOwnershipPosix
-}
-
-func daemonizePosix(cmd *exec.Cmd) {
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Setsid: true,
- }
-}
-
-// autoNetworkAddress 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, xerrors.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, xerrors.Errorf("checking current user: %w", err)
- }
- uid, err := strconv.ParseUint(user.Uid, 10, 32)
- if err != nil {
- return false, xerrors.Errorf("parsing current UID: %w", err)
- }
- return stat.Uid == uint32(uid), nil
-}
diff --git a/internal/lsp/lsprpc/binder.go b/internal/lsp/lsprpc/binder.go
deleted file mode 100644
index f3320e17a..000000000
--- a/internal/lsp/lsprpc/binder.go
+++ /dev/null
@@ -1,143 +0,0 @@
-// 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/event"
- jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/xcontext"
- errors "golang.org/x/xerrors"
-)
-
-// 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, error)
-
-func (f BinderFunc) Bind(ctx context.Context, conn *jsonrpc2_v2.Connection) (jsonrpc2_v2.ConnectionOptions, error) {
- 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
-
-// A ServerFunc is used to construct an LSP server for a given client.
-type ServerFunc func(context.Context, protocol.ClientCloser) protocol.Server
-
-// ServerBinder binds incoming connections to a new server.
-type ServerBinder struct {
- newServer ServerFunc
-}
-
-func NewServerBinder(newServer ServerFunc) *ServerBinder {
- return &ServerBinder{newServer: newServer}
-}
-
-func (b *ServerBinder) Bind(ctx context.Context, conn *jsonrpc2_v2.Connection) (jsonrpc2_v2.ConnectionOptions, error) {
- 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,
- }, nil
-}
-
-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, &params); err != nil {
- return nil, errors.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, errors.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, _ error) {
- 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 opts, 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,
- }, nil
-}
-
-// 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 NewClientBinder(newClient ClientFunc) *ClientBinder {
- return &ClientBinder{newClient}
-}
-
-func (b *ClientBinder) Bind(ctx context.Context, conn *jsonrpc2_v2.Connection) (jsonrpc2_v2.ConnectionOptions, error) {
- server := protocol.ServerDispatcherV2(conn)
- client := b.newClient(ctx, server)
- return jsonrpc2_v2.ConnectionOptions{
- Handler: protocol.ClientHandlerV2(client),
- }, nil
-}
diff --git a/internal/lsp/lsprpc/binder_test.go b/internal/lsp/lsprpc/binder_test.go
deleted file mode 100644
index f7dd83033..000000000
--- a/internal/lsp/lsprpc/binder_test.go
+++ /dev/null
@@ -1,154 +0,0 @@
-// 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"
-
- jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2"
- "golang.org/x/tools/internal/lsp/protocol"
-
- . "golang.org/x/tools/internal/lsp/lsprpc"
-)
-
-type TestEnv struct {
- Listeners []jsonrpc2_v2.Listener
- Conns []*jsonrpc2_v2.Connection
- Servers []*jsonrpc2_v2.Server
-}
-
-func (e *TestEnv) Shutdown(t *testing.T) {
- for _, l := range e.Listeners {
- if err := l.Close(); err != nil {
- t.Error(err)
- }
- }
- 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)
- }
- e.Listeners = append(e.Listeners, l)
- s, err := jsonrpc2_v2.Serve(ctx, l, server)
- if err != nil {
- t.Fatal(err)
- }
- 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/internal/lsp/lsprpc/commandinterceptor.go b/internal/lsp/lsprpc/commandinterceptor.go
deleted file mode 100644
index 5c36af759..000000000
--- a/internal/lsp/lsprpc/commandinterceptor.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// 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"
-
- jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2"
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-// 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, error) {
- opts, err := binder.Bind(ctx, conn)
- if err != nil {
- return opts, err
- }
- opts.Handler = hmw(opts.Handler)
- return opts, nil
- })
- })
-}
-
-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, &params); err == nil {
- if params.Command == command {
- return run(&params)
- }
- }
- }
-
- return delegate.Handle(ctx, req)
- })
- })
-}
diff --git a/internal/lsp/lsprpc/commandinterceptor_test.go b/internal/lsp/lsprpc/commandinterceptor_test.go
deleted file mode 100644
index 06550e8fa..000000000
--- a/internal/lsp/lsprpc/commandinterceptor_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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"
- "testing"
-
- "golang.org/x/tools/internal/lsp/protocol"
-
- . "golang.org/x/tools/internal/lsp/lsprpc"
-)
-
-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/internal/lsp/lsprpc/dialer.go b/internal/lsp/lsprpc/dialer.go
deleted file mode 100644
index 713307ca0..000000000
--- a/internal/lsp/lsprpc/dialer.go
+++ /dev/null
@@ -1,115 +0,0 @@
-// 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"
- "time"
-
- exec "golang.org/x/sys/execabs"
- "golang.org/x/tools/internal/event"
- errors "golang.org/x/xerrors"
-)
-
-// 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, errors.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, errors.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, errors.Errorf("dialing remote: %w", err)
-}
diff --git a/internal/lsp/lsprpc/goenv.go b/internal/lsp/lsprpc/goenv.go
deleted file mode 100644
index 4b16d8d9e..000000000
--- a/internal/lsp/lsprpc/goenv.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// 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/event"
- "golang.org/x/tools/internal/gocommand"
- jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2"
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-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
-}
-
-func addGoEnvToInitializeRequestV2(ctx context.Context, req *jsonrpc2_v2.Request) error {
- var params protocol.ParamInitialize
- if err := json.Unmarshal(req.Params, &params); 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
- }
- for govar, value := range goenv {
- 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
-}
-
-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/internal/lsp/lsprpc/goenv_test.go b/internal/lsp/lsprpc/goenv_test.go
deleted file mode 100644
index cdfe23c90..000000000
--- a/internal/lsp/lsprpc/goenv_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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"
- "testing"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/testenv"
-
- . "golang.org/x/tools/internal/lsp/lsprpc"
-)
-
-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.NeedsGo1Point(t, 13)
-
- 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/internal/lsp/lsprpc/lsprpc.go b/internal/lsp/lsprpc/lsprpc.go
deleted file mode 100644
index ca32f0e17..000000000
--- a/internal/lsp/lsprpc/lsprpc.go
+++ /dev/null
@@ -1,530 +0,0 @@
-// 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/internal/event"
- "golang.org/x/tools/internal/jsonrpc2"
- "golang.org/x/tools/internal/lsp"
- "golang.org/x/tools/internal/lsp/cache"
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/debug"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- errors "golang.org/x/xerrors"
-)
-
-// 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
-
- // 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) *StreamServer {
- return &StreamServer{cache: cache, daemon: daemon}
-}
-
-func (s *StreamServer) Binder() *ServerBinder {
- newServer := func(ctx context.Context, client protocol.ClientCloser) protocol.Server {
- session := s.cache.NewSession(ctx)
- server := s.serverForTest
- if server == nil {
- server = lsp.NewServer(session, client)
- debug.GetInstance(ctx).AddService(server, session)
- }
- return server
- }
- return NewServerBinder(newServer)
-}
-
-// 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 := s.cache.NewSession(ctx)
- server := s.serverForTest
- if server == nil {
- server = lsp.NewServer(session, client)
- debug.GetInstance(ctx).AddService(server, 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 := server.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(server,
- 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, 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) (*Forwarder, error) {
- dialer, err := NewAutoDialer(rawAddr, argFunc)
- if err != nil {
- return nil, err
- }
- fwd := &Forwarder{
- dialer: dialer,
- }
- return fwd, nil
-}
-
-// QueryServerState queries the server state of the current server.
-func QueryServerState(ctx context.Context, addr string) (*ServerState, 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, errors.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, errors.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, errors.Errorf("dialing remote: %w", err)
- }
- serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn))
- serverConn.Go(ctx, jsonrpc2.MethodNotFound)
- return serverConn, nil
-}
-
-func ExecuteCommand(ctx context.Context, addr string, id string, request, result interface{}) 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 errors.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 = errors.Errorf("remote disconnected: %v", serverConn.Err())
- } else if clientConn.Err() != nil {
- err = errors.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.Execuable 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",
- tag.NewServer.Of(f.serverID),
- tag.Logfile.Of(hresp.Logfile),
- tag.DebugAddress.Of(hresp.DebugAddr),
- tag.GoplsPath.Of(hresp.GoplsPath),
- tag.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(), &params); err == nil {
- if params.Command == command.StartDebugging.ID() {
- 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 requst, 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(), &params); 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
- }
- for govar, value := range goenv {
- 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),
- tag.DebugAddress.Of(req.DebugAddr),
- tag.Logfile.Of(req.Logfile),
- tag.ServerID.Of(req.ServerID),
- tag.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 = errors.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/internal/lsp/lsprpc/lsprpc_test.go b/internal/lsp/lsprpc/lsprpc_test.go
deleted file mode 100644
index 795c887e4..000000000
--- a/internal/lsp/lsprpc/lsprpc_test.go
+++ /dev/null
@@ -1,349 +0,0 @@
-// 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"
- "errors"
- "regexp"
- "strings"
- "testing"
- "time"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/jsonrpc2"
- "golang.org/x/tools/internal/jsonrpc2/servertest"
- "golang.org/x/tools/internal/lsp/cache"
- "golang.org/x/tools/internal/lsp/debug"
- "golang.org/x/tools/internal/lsp/fake"
- "golang.org/x/tools/internal/lsp/protocol"
- "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)
- ss.serverForTest = server
- ts := servertest.NewPipeServer(ctx, 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)
- ss.serverForTest = s
- tsDirect := servertest.NewTCPServer(serveCtx, ss, nil)
-
- forwarderCtx := debug.WithInstance(ctx, "", "")
- forwarder, err := NewForwarder("tcp;"+tsDirect.Addr, nil)
- if err != nil {
- t.Fatal(err)
- }
- tsForwarded := servertest.NewPipeServer(forwarderCtx, 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)
- tsBackend := servertest.NewTCPServer(serverCtx, ss, nil)
-
- forwarder, err := NewForwarder("tcp;"+tsBackend.Addr, nil)
- if err != nil {
- t.Fatal(err)
- }
- tsForwarder := servertest.NewPipeServer(clientCtx, forwarder, nil)
-
- conn1 := tsForwarder.Connect(clientCtx)
- ed1, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(clientCtx, conn1, fake.ClientHooks{})
- if err != nil {
- t.Fatal(err)
- }
- defer ed1.Close(clientCtx)
- conn2 := tsBackend.Connect(baseCtx)
- ed2, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(baseCtx, conn2, 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.NeedsGo1Point(t, 13)
- server := &initServer{}
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
- _, 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)
- }
- }
-}
diff --git a/internal/lsp/lsprpc/middleware.go b/internal/lsp/lsprpc/middleware.go
deleted file mode 100644
index 2ee83a203..000000000
--- a/internal/lsp/lsprpc/middleware.go
+++ /dev/null
@@ -1,145 +0,0 @@
-// 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"
- "sync"
-
- "golang.org/x/tools/internal/event"
- jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2"
- "golang.org/x/xerrors"
-)
-
-// Metadata holds arbitrary data transferred between jsonrpc2 peers.
-type Metadata map[string]interface{}
-
-// 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
-}
-
-// Handshaker handles both server and client handshaking over jsonrpc2. 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
-}
-
-// 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, error) {
- opts, err := inner.Bind(ctx, conn)
- if err != nil {
- return opts, err
- }
-
- 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, xerrors.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, nil
- })
-}
-
-// 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/internal/lsp/lsprpc/middleware_test.go b/internal/lsp/lsprpc/middleware_test.go
deleted file mode 100644
index a385f1003..000000000
--- a/internal/lsp/lsprpc/middleware_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// 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"
- "errors"
- "fmt"
- "testing"
- "time"
-
- jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2"
- . "golang.org/x/tools/internal/lsp/lsprpc"
-)
-
-var noopBinder = BinderFunc(func(context.Context, *jsonrpc2_v2.Connection) (jsonrpc2_v2.ConnectionOptions, error) {
- return jsonrpc2_v2.ConnectionOptions{}, nil
-})
-
-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
- }
-}
diff --git a/internal/lsp/mod/code_lens.go b/internal/lsp/mod/code_lens.go
deleted file mode 100644
index b26bae75c..000000000
--- a/internal/lsp/mod/code_lens.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// 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/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-// LensFuncs returns the supported lensFuncs for go.mod files.
-func LensFuncs() map[command.Command]source.LensFunc {
- return map[command.Command]source.LensFunc{
- command.UpgradeDependency: upgradeLenses,
- command.Tidy: tidyLens,
- command.Vendor: vendorLens,
- }
-}
-
-func upgradeLenses(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]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 upgrade.
- return nil, nil
- }
- var requires []string
- for _, req := range pm.File.Require {
- requires = append(requires, req.Mod.Path)
- }
- uri := protocol.URIFromSpanURI(fh.URI())
- checkUpgrade, err := command.NewCheckUpgradesCommand("Check for upgrades", command.CheckUpgradesArgs{
- URI: uri,
- Modules: requires,
- })
- if err != nil {
- return nil, err
- }
- upgradeTransitive, err := command.NewUpgradeDependencyCommand("Upgrade transitive dependencies", command.DependencyArgs{
- URI: uri,
- AddRequire: false,
- GoCmdArgs: []string{"-d", "-u", "-t", "./..."},
- })
- if err != nil {
- return nil, err
- }
- upgradeDirect, err := command.NewUpgradeDependencyCommand("Upgrade direct dependencies", command.DependencyArgs{
- URI: uri,
- AddRequire: false,
- GoCmdArgs: append([]string{"-d"}, requires...),
- })
- if err != nil {
- return nil, err
- }
- // Put the upgrade code lenses above the first require block or statement.
- rng, err := firstRequireRange(fh, pm)
- if err != nil {
- return nil, err
- }
-
- return []protocol.CodeLens{
- {Range: rng, Command: checkUpgrade},
- {Range: rng, Command: upgradeTransitive},
- {Range: rng, Command: upgradeDirect},
- }, nil
-}
-
-func tidyLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
- pm, err := snapshot.ParseMod(ctx, fh)
- if err != nil || pm.File == nil {
- return nil, err
- }
- uri := protocol.URIFromSpanURI(fh.URI())
- cmd, err := command.NewTidyCommand("Run go mod tidy", command.URIArgs{URIs: []protocol.DocumentURI{uri}})
- if err != nil {
- return nil, err
- }
- 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 source.Snapshot, fh source.FileHandle) ([]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 := protocol.URIFromSpanURI(fh.URI())
- cmd, err := command.NewVendorCommand(title, command.URIArg{URI: uri})
- if err != nil {
- return nil, err
- }
- // Change the message depending on whether or not the module already has a
- // vendor directory.
- vendorDir := filepath.Join(filepath.Dir(fh.URI().Filename()), "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 source.FileHandle, pm *source.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 source.LineToRange(pm.Mapper, fh.URI(), syntax.Start, syntax.End)
-}
-
-// 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 source.FileHandle, pm *source.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 source.LineToRange(pm.Mapper, fh.URI(), start, end)
-}
diff --git a/internal/lsp/mod/diagnostics.go b/internal/lsp/mod/diagnostics.go
deleted file mode 100644
index 9c49d8b36..000000000
--- a/internal/lsp/mod/diagnostics.go
+++ /dev/null
@@ -1,116 +0,0 @@
-// 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"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.VersionedFileIdentity][]*source.Diagnostic, error) {
- ctx, done := event.Start(ctx, "mod.Diagnostics", tag.Snapshot.Of(snapshot.ID()))
- defer done()
-
- reports := map[source.VersionedFileIdentity][]*source.Diagnostic{}
- for _, uri := range snapshot.ModFiles() {
- fh, err := snapshot.GetVersionedFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- reports[fh.VersionedFileIdentity()] = []*source.Diagnostic{}
- diagnostics, err := DiagnosticsForMod(ctx, snapshot, fh)
- if err != nil {
- return nil, err
- }
- for _, d := range diagnostics {
- fh, err := snapshot.GetVersionedFile(ctx, d.URI)
- if err != nil {
- return nil, err
- }
- reports[fh.VersionedFileIdentity()] = append(reports[fh.VersionedFileIdentity()], d)
- }
- }
- return reports, nil
-}
-
-func DiagnosticsForMod(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]*source.Diagnostic, error) {
- pm, err := snapshot.ParseMod(ctx, fh)
- if err != nil {
- if pm == nil || len(pm.ParseErrors) == 0 {
- return nil, err
- }
- return pm.ParseErrors, nil
- }
-
- var diagnostics []*source.Diagnostic
-
- // Add upgrade quick fixes for individual modules if we know about them.
- upgrades := snapshot.View().ModuleUpgrades()
- for _, req := range pm.File.Require {
- ver, ok := upgrades[req.Mod.Path]
- if !ok || req.Mod.Version == ver {
- continue
- }
- rng, err := source.LineToRange(pm.Mapper, fh.URI(), req.Syntax.Start, req.Syntax.End)
- if err != nil {
- return nil, err
- }
- // Upgrade to the exact version we offer the user, not the most recent.
- title := fmt.Sprintf("Upgrade to %v", ver)
- cmd, err := command.NewUpgradeDependencyCommand(title, command.DependencyArgs{
- URI: protocol.URIFromSpanURI(fh.URI()),
- AddRequire: false,
- GoCmdArgs: []string{req.Mod.Path + "@" + ver},
- })
- if err != nil {
- return nil, err
- }
- diagnostics = append(diagnostics, &source.Diagnostic{
- URI: fh.URI(),
- Range: rng,
- Severity: protocol.SeverityInformation,
- Source: source.UpgradeNotification,
- Message: fmt.Sprintf("%v can be upgraded", req.Mod.Path),
- SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)},
- })
- }
-
- // Packages in the workspace can contribute diagnostics to go.mod files.
- wspkgs, err := snapshot.ActivePackages(ctx)
- if err != nil && !source.IsNonFatalGoModError(err) {
- event.Error(ctx, fmt.Sprintf("workspace packages: diagnosing %s", pm.URI), err)
- }
- if err == nil {
- for _, pkg := range wspkgs {
- pkgDiagnostics, err := snapshot.DiagnosePackage(ctx, pkg)
- if err != nil {
- return nil, err
- }
- diagnostics = append(diagnostics, pkgDiagnostics[fh.URI()]...)
- }
- }
-
- tidied, err := snapshot.ModTidy(ctx, pm)
- if err != nil && !source.IsNonFatalGoModError(err) {
- event.Error(ctx, fmt.Sprintf("tidy: diagnosing %s", pm.URI), err)
- }
- if err == nil {
- for _, d := range tidied.Diagnostics {
- if d.URI != fh.URI() {
- continue
- }
- diagnostics = append(diagnostics, d)
- }
- }
- return diagnostics, nil
-}
diff --git a/internal/lsp/mod/format.go b/internal/lsp/mod/format.go
deleted file mode 100644
index c35576632..000000000
--- a/internal/lsp/mod/format.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-func Format(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]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.
- diff, err := snapshot.View().Options().ComputeEdits(fh.URI(), string(pm.Mapper.Content), string(formatted))
- if err != nil {
- return nil, err
- }
- return source.ToProtocolEdits(pm.Mapper, diff)
-}
diff --git a/internal/lsp/mod/hover.go b/internal/lsp/mod/hover.go
deleted file mode 100644
index 0837e2aaa..000000000
--- a/internal/lsp/mod/hover.go
+++ /dev/null
@@ -1,163 +0,0 @@
-// 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"
- "go/token"
- "strings"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- errors "golang.org/x/xerrors"
-)
-
-func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, position protocol.Position) (*protocol.Hover, error) {
- var found bool
- for _, uri := range snapshot.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, errors.Errorf("getting modfile handle: %w", err)
- }
- spn, err := pm.Mapper.PointSpan(position)
- if err != nil {
- return nil, errors.Errorf("computing cursor position: %w", err)
- }
- hoverRng, err := spn.Range(pm.Mapper.Converter)
- if err != nil {
- return nil, errors.Errorf("computing hover range: %w", err)
- }
-
- // Confirm that the cursor is at the position of a require statement.
- var req *modfile.Require
- var startPos, endPos 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.
- startPos, endPos = s+i, s+i+len(dep)
- if token.Pos(startPos) <= hoverRng.Start && hoverRng.Start <= token.Pos(endPos) {
- req = r
- break
- }
- }
-
- // The cursor position is not on a require statement.
- if req == nil {
- return nil, nil
- }
-
- // 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.
- rng, err := source.ByteOffsetsToRange(pm.Mapper, fh.URI(), startPos, endPos)
- if err != nil {
- return nil, err
- }
- if err != nil {
- return nil, err
- }
- options := snapshot.View().Options()
- isPrivate := snapshot.View().IsGoPrivatePath(req.Mod.Path)
- explanation = formatExplanation(explanation, req, options, isPrivate)
- return &protocol.Hover{
- Contents: protocol.MarkupContent{
- Kind: options.PreferredContentFormat,
- Value: explanation,
- },
- Range: rng,
- }, nil
-}
-
-func formatExplanation(text string, req *modfile.Require, options *source.Options, isPrivate bool) string {
- text = strings.TrimSuffix(text, "\n")
- splt := strings.Split(text, "\n")
- length := len(splt)
-
- var b strings.Builder
- // Write the heading as an H3.
- b.WriteString("##" + splt[0])
- if options.PreferredContentFormat == protocol.Markdown {
- b.WriteString("\n\n")
- } else {
- b.WriteRune('\n')
- }
-
- // 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, source.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/internal/lsp/mod/mod_test.go b/internal/lsp/mod/mod_test.go
deleted file mode 100644
index b2d257cae..000000000
--- a/internal/lsp/mod/mod_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// 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
-
-import (
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "golang.org/x/tools/internal/lsp/cache"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/tests"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/testenv"
-)
-
-func TestMain(m *testing.M) {
- testenv.ExitIfSmallMachine()
- os.Exit(m.Run())
-}
-
-func TestModfileRemainsUnchanged(t *testing.T) {
- testenv.NeedsGo1Point(t, 14)
-
- ctx := tests.Context(t)
- cache := cache.New(nil)
- session := cache.NewSession(ctx)
- options := source.DefaultOptions().Clone()
- tests.DefaultOptions(options)
- options.TempModfile = true
- options.Env = map[string]string{"GOPACKAGESDRIVER": "off", "GOROOT": ""}
-
- // Make sure to copy the test directory to a temporary directory so we do not
- // modify the test code or add go.sum files when we run the tests.
- folder, err := tests.CopyFolderToTempDir(filepath.Join("testdata", "unchanged"))
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(folder)
-
- before, err := ioutil.ReadFile(filepath.Join(folder, "go.mod"))
- if err != nil {
- t.Fatal(err)
- }
- _, _, release, err := session.NewView(ctx, "diagnostics_test", span.URIFromPath(folder), options)
- release()
- if err != nil {
- t.Fatal(err)
- }
- after, err := ioutil.ReadFile(filepath.Join(folder, "go.mod"))
- if err != nil {
- t.Fatal(err)
- }
- if string(before) != string(after) {
- t.Errorf("the real go.mod file was changed even when tempModfile=true")
- }
-}
diff --git a/internal/lsp/mod/testdata/unchanged/go.mod b/internal/lsp/mod/testdata/unchanged/go.mod
deleted file mode 100644
index e3d13cebe..000000000
--- a/internal/lsp/mod/testdata/unchanged/go.mod
+++ /dev/null
@@ -1 +0,0 @@
-module unchanged
diff --git a/internal/lsp/mod/testdata/unchanged/main.go b/internal/lsp/mod/testdata/unchanged/main.go
deleted file mode 100644
index b258445f4..000000000
--- a/internal/lsp/mod/testdata/unchanged/main.go
+++ /dev/null
@@ -1,6 +0,0 @@
-// Package unchanged does something
-package unchanged
-
-func Yo() {
- println("yo")
-}
diff --git a/internal/lsp/progress/progress.go b/internal/lsp/progress/progress.go
deleted file mode 100644
index 18e1bd0f1..000000000
--- a/internal/lsp/progress/progress.go
+++ /dev/null
@@ -1,269 +0,0 @@
-// 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"
- "math/rand"
- "strconv"
- "strings"
- "sync"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/xcontext"
- errors "golang.org/x/xerrors"
-)
-
-type Tracker struct {
- client protocol.Client
- supportsWorkDoneProgress bool
-
- mu sync.Mutex
- inProgress map[protocol.ProgressToken]*WorkDone
-}
-
-func NewTracker(client protocol.Client) *Tracker {
- return &Tracker{
- client: client,
- inProgress: make(map[protocol.ProgressToken]*WorkDone),
- }
-}
-
-func (tracker *Tracker) SetSupportsWorkDoneProgress(b bool) {
- tracker.supportsWorkDoneProgress = b
-}
-
-// 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 {
- wd := &WorkDone{
- ctx: xcontext.Detach(ctx),
- 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(wd.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, "generate progress begin", err)
- }
- return wd
-}
-
-func (t *Tracker) Cancel(ctx context.Context, token protocol.ProgressToken) error {
- t.mu.Lock()
- defer t.mu.Unlock()
- wd, ok := t.inProgress[token]
- if !ok {
- return errors.Errorf("token %q not found in progress", token)
- }
- if wd.cancel == nil {
- return errors.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 {
- // ctx is detached, for sending $/progress updates.
- ctx context.Context
- 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(message string, percentage float64) {
- 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(wd.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(wd.ctx, "reporting progress", err)
- }
-}
-
-// end reports a workdone completion back to the client.
-func (wd *WorkDone) End(message string) {
- 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(wd.ctx, &protocol.ShowMessageParams{
- Type: protocol.Info,
- Message: message,
- })
- default:
- err = wd.client.Progress(wd.ctx, &protocol.ProgressParams{
- Token: wd.token,
- Value: &protocol.WorkDoneProgressEnd{
- Kind: "end",
- Message: message,
- },
- })
- }
- if err != nil {
- event.Error(wd.ctx, "ending work", err)
- }
- if wd.cleanup != nil {
- wd.cleanup()
- }
-}
-
-// EventWriter writes every incoming []byte to
-// event.Print with the operation=generate tag
-// to distinguish its logs from others.
-type EventWriter struct {
- ctx context.Context
- operation string
-}
-
-func NewEventWriter(ctx context.Context, operation string) *EventWriter {
- return &EventWriter{ctx: ctx, operation: operation}
-}
-
-func (ew *EventWriter) Write(p []byte) (n int, err error) {
- event.Log(ew.ctx, string(p), tag.Operation.Of(ew.operation))
- return len(p), nil
-}
-
-// WorkDoneWriter wraps a workDone handle to provide a Writer interface,
-// so that workDone reporting can more easily be hooked into commands.
-type WorkDoneWriter struct {
- wd *WorkDone
-}
-
-func NewWorkDoneWriter(wd *WorkDone) *WorkDoneWriter {
- return &WorkDoneWriter{wd: wd}
-}
-
-func (wdw WorkDoneWriter) Write(p []byte) (n int, err error) {
- wdw.wd.Report(string(p), 0)
- // Don't fail just because of a failure to report progress.
- return len(p), nil
-}
diff --git a/internal/lsp/progress/progress_test.go b/internal/lsp/progress/progress_test.go
deleted file mode 100644
index b3c821938..000000000
--- a/internal/lsp/progress/progress_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-// 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/internal/lsp/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(token protocol.ProgressToken) (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(test.token)
- 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("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("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(token)
- var canceled bool
- cancel := func() { canceled = true }
- work := tracker.Start(ctx, "work", "message", token, cancel)
- if err := tracker.Cancel(ctx, work.Token()); err != nil {
- t.Fatal(err)
- }
- if !canceled {
- t.Errorf("tracker.cancel(...): cancel not called")
- }
- }
-}
diff --git a/internal/lsp/protocol/codeactionkind.go b/internal/lsp/protocol/codeactionkind.go
deleted file mode 100644
index 9a95800fb..000000000
--- a/internal/lsp/protocol/codeactionkind.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// 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 protocol
-
-// Custom code actions that aren't explicitly stated in LSP
-const (
- GoTest CodeActionKind = "goTest"
- // TODO: Add GoGenerate, RegenerateCgo etc.
-)
diff --git a/internal/lsp/protocol/context.go b/internal/lsp/protocol/context.go
deleted file mode 100644
index 487e4dfe5..000000000
--- a/internal/lsp/protocol/context.go
+++ /dev/null
@@ -1,43 +0,0 @@
-// 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"
-
- "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
- }
- go client.LogMessage(xcontext.Detach(ctx), msg)
- return ctx
-}
diff --git a/internal/lsp/protocol/doc.go b/internal/lsp/protocol/doc.go
deleted file mode 100644
index 2ffdf5128..000000000
--- a/internal/lsp/protocol/doc.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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 contains the structs that map directly to the wire format
-// 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/internal/lsp/protocol/enums.go b/internal/lsp/protocol/enums.go
deleted file mode 100644
index 434808eeb..000000000
--- a/internal/lsp/protocol/enums.go
+++ /dev/null
@@ -1,246 +0,0 @@
-// 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"
-)
-
-var (
- namesTextDocumentSyncKind [int(Incremental) + 1]string
- namesInitializeError [int(UnknownProtocolVersion) + 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"
-
- namesInitializeError[int(UnknownProtocolVersion)] = "UnknownProtocolVersion"
-
- 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, c rune, 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 parseEnum(s string, names []string) int {
- for i, name := range names {
- if s == name {
- return i
- }
- }
- return 0
-}
-
-func (e TextDocumentSyncKind) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesTextDocumentSyncKind[:], "TextDocumentSyncKind")
-}
-
-func ParseTextDocumentSyncKind(s string) TextDocumentSyncKind {
- return TextDocumentSyncKind(parseEnum(s, namesTextDocumentSyncKind[:]))
-}
-
-func (e InitializeError) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesInitializeError[:], "InitializeError")
-}
-
-func ParseInitializeError(s string) InitializeError {
- return InitializeError(parseEnum(s, namesInitializeError[:]))
-}
-
-func (e MessageType) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesMessageType[:], "MessageType")
-}
-
-func ParseMessageType(s string) MessageType {
- return MessageType(parseEnum(s, namesMessageType[:]))
-}
-
-func (e FileChangeType) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesFileChangeType[:], "FileChangeType")
-}
-
-func ParseFileChangeType(s string) FileChangeType {
- return FileChangeType(parseEnum(s, namesFileChangeType[:]))
-}
-
-func (e WatchKind) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesWatchKind[:], "WatchKind")
-}
-
-func ParseWatchKind(s string) WatchKind {
- return WatchKind(parseEnum(s, namesWatchKind[:]))
-}
-
-func (e CompletionTriggerKind) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesCompletionTriggerKind[:], "CompletionTriggerKind")
-}
-
-func ParseCompletionTriggerKind(s string) CompletionTriggerKind {
- return CompletionTriggerKind(parseEnum(s, namesCompletionTriggerKind[:]))
-}
-
-func (e DiagnosticSeverity) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesDiagnosticSeverity[:], "DiagnosticSeverity")
-}
-
-func ParseDiagnosticSeverity(s string) DiagnosticSeverity {
- return DiagnosticSeverity(parseEnum(s, namesDiagnosticSeverity[:]))
-}
-
-func (e DiagnosticTag) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesDiagnosticTag[:], "DiagnosticTag")
-}
-
-func ParseDiagnosticTag(s string) DiagnosticTag {
- return DiagnosticTag(parseEnum(s, namesDiagnosticTag[:]))
-}
-
-func (e CompletionItemKind) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesCompletionItemKind[:], "CompletionItemKind")
-}
-
-func ParseCompletionItemKind(s string) CompletionItemKind {
- return CompletionItemKind(parseEnum(s, namesCompletionItemKind[:]))
-}
-
-func (e InsertTextFormat) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesInsertTextFormat[:], "InsertTextFormat")
-}
-
-func ParseInsertTextFormat(s string) InsertTextFormat {
- return InsertTextFormat(parseEnum(s, namesInsertTextFormat[:]))
-}
-
-func (e DocumentHighlightKind) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesDocumentHighlightKind[:], "DocumentHighlightKind")
-}
-
-func ParseDocumentHighlightKind(s string) DocumentHighlightKind {
- return DocumentHighlightKind(parseEnum(s, namesDocumentHighlightKind[:]))
-}
-
-func (e SymbolKind) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesSymbolKind[:], "SymbolKind")
-}
-
-func ParseSymbolKind(s string) SymbolKind {
- return SymbolKind(parseEnum(s, namesSymbolKind[:]))
-}
-
-func (e TextDocumentSaveReason) Format(f fmt.State, c rune) {
- formatEnum(f, c, int(e), namesTextDocumentSaveReason[:], "TextDocumentSaveReason")
-}
-
-func ParseTextDocumentSaveReason(s string) TextDocumentSaveReason {
- return TextDocumentSaveReason(parseEnum(s, namesTextDocumentSaveReason[:]))
-}
diff --git a/internal/lsp/protocol/log.go b/internal/lsp/protocol/log.go
deleted file mode 100644
index fdcbb7a8d..000000000
--- a/internal/lsp/protocol/log.go
+++ /dev/null
@@ -1,136 +0,0 @@
-// 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/internal/lsp/protocol/protocol.go b/internal/lsp/protocol/protocol.go
deleted file mode 100644
index a8b3354b4..000000000
--- a/internal/lsp/protocol/protocol.go
+++ /dev/null
@@ -1,277 +0,0 @@
-// 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 (
- "context"
- "encoding/json"
- "fmt"
- "io"
-
- "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"
- errors "golang.org/x/xerrors"
-)
-
-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 {
- result, resErr = res, err
- 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
- }
- //TODO: This code is wrong, it ignores handler and assumes non standard
- // request handles everything
- // non standard request should just be a layered handler.
- var params interface{}
- if err := json.Unmarshal(req.Params(), &params); err != nil {
- return sendParseError(ctx, reply, err)
- }
- resp, err := server.NonstandardRequest(ctx, req.Method(), params)
- return reply(ctx, resp, err)
-
- }
-}
-
-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 {
- result, resErr = res, err
- 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 := json.Unmarshal(req.Params(), &params); 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})
-}
-
-func sendParseError(ctx context.Context, reply jsonrpc2.Replier, err error) error {
- return reply(ctx, nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err))
-}
diff --git a/internal/lsp/protocol/span.go b/internal/lsp/protocol/span.go
deleted file mode 100644
index 381e5f500..000000000
--- a/internal/lsp/protocol/span.go
+++ /dev/null
@@ -1,151 +0,0 @@
-// 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 file contains protocol<->span converters
-
-package protocol
-
-import (
- "fmt"
-
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-type ColumnMapper struct {
- URI span.URI
- Converter *span.TokenConverter
- Content []byte
-}
-
-func URIFromSpanURI(uri span.URI) DocumentURI {
- return DocumentURI(uri)
-}
-
-func URIFromPath(path string) DocumentURI {
- return URIFromSpanURI(span.URIFromPath(path))
-}
-
-func (u DocumentURI) SpanURI() span.URI {
- return span.URIFromURI(string(u))
-}
-
-func (m *ColumnMapper) Location(s span.Span) (Location, error) {
- rng, err := m.Range(s)
- if err != nil {
- return Location{}, err
- }
- return Location{URI: URIFromSpanURI(s.URI()), Range: rng}, nil
-}
-
-func (m *ColumnMapper) Range(s span.Span) (Range, error) {
- if span.CompareURI(m.URI, s.URI()) != 0 {
- return Range{}, errors.Errorf("column mapper is for file %q instead of %q", m.URI, s.URI())
- }
- s, err := s.WithAll(m.Converter)
- if err != nil {
- return Range{}, err
- }
- start, err := m.Position(s.Start())
- if err != nil {
- return Range{}, err
- }
- end, err := m.Position(s.End())
- if err != nil {
- return Range{}, err
- }
- return Range{Start: start, End: end}, nil
-}
-
-func (m *ColumnMapper) Position(p span.Point) (Position, error) {
- chr, err := span.ToUTF16Column(p, m.Content)
- if err != nil {
- return Position{}, err
- }
- return Position{
- Line: uint32(p.Line() - 1),
- Character: uint32(chr - 1),
- }, nil
-}
-
-func (m *ColumnMapper) Span(l Location) (span.Span, error) {
- return m.RangeSpan(l.Range)
-}
-
-func (m *ColumnMapper) RangeSpan(r Range) (span.Span, error) {
- start, err := m.Point(r.Start)
- if err != nil {
- return span.Span{}, err
- }
- end, err := m.Point(r.End)
- if err != nil {
- return span.Span{}, err
- }
- return span.New(m.URI, start, end).WithAll(m.Converter)
-}
-
-func (m *ColumnMapper) RangeToSpanRange(r Range) (span.Range, error) {
- spn, err := m.RangeSpan(r)
- if err != nil {
- return span.Range{}, err
- }
- return spn.Range(m.Converter)
-}
-
-func (m *ColumnMapper) PointSpan(p Position) (span.Span, error) {
- start, err := m.Point(p)
- if err != nil {
- return span.Span{}, err
- }
- return span.New(m.URI, start, start).WithAll(m.Converter)
-}
-
-func (m *ColumnMapper) Point(p Position) (span.Point, error) {
- line := int(p.Line) + 1
- offset, err := m.Converter.ToOffset(line, 1)
- if err != nil {
- return span.Point{}, err
- }
- lineStart := span.NewPoint(line, 1, offset)
- return span.FromUTF16Column(lineStart, int(p.Character)+1, m.Content)
-}
-
-func IsPoint(r Range) bool {
- return r.Start.Line == r.End.Line && r.Start.Character == r.End.Character
-}
-
-func CompareRange(a, b Range) int {
- if r := ComparePosition(a.Start, b.Start); r != 0 {
- return r
- }
- return ComparePosition(a.End, b.End)
-}
-
-func ComparePosition(a, b Position) int {
- if a.Line < b.Line {
- return -1
- }
- if a.Line > b.Line {
- return 1
- }
- if a.Character < b.Character {
- return -1
- }
- if a.Character > b.Character {
- 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)
-}
-
-func (r Range) Format(f fmt.State, _ rune) {
- fmt.Fprintf(f, "%v:%v-%v:%v", r.Start.Line, r.Start.Character, r.End.Line, r.End.Character)
-}
diff --git a/internal/lsp/protocol/tsclient.go b/internal/lsp/protocol/tsclient.go
deleted file mode 100644
index 004cad93b..000000000
--- a/internal/lsp/protocol/tsclient.go
+++ /dev/null
@@ -1,205 +0,0 @@
-// 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.
-
-// Code generated (see typescript/README.md) DO NOT EDIT.
-
-package protocol
-
-// Package protocol contains data types and code for LSP json rpcs
-// generated automatically from vscode-languageserver-node
-// commit: 696f9285bf849b73745682fdb1c1feac73eb8772
-// last fetched Fri Mar 04 2022 14:48:10 GMT-0500 (Eastern Standard Time)
-
-import (
- "context"
- "encoding/json"
-
- "golang.org/x/tools/internal/jsonrpc2"
- errors "golang.org/x/xerrors"
-)
-
-type Client interface {
- ShowMessage(context.Context, *ShowMessageParams) error
- LogMessage(context.Context, *LogMessageParams) error
- Event(context.Context, *interface{}) error
- PublishDiagnostics(context.Context, *PublishDiagnosticsParams) error
- Progress(context.Context, *ProgressParams) error
- WorkspaceFolders(context.Context) ([]WorkspaceFolder /*WorkspaceFolder[] | null*/, error)
- Configuration(context.Context, *ParamConfiguration) ([]LSPAny, error)
- WorkDoneProgressCreate(context.Context, *WorkDoneProgressCreateParams) error
- ShowDocument(context.Context, *ShowDocumentParams) (*ShowDocumentResult, error)
- RegisterCapability(context.Context, *RegistrationParams) error
- UnregisterCapability(context.Context, *UnregistrationParams) error
- ShowMessageRequest(context.Context, *ShowMessageRequestParams) (*MessageActionItem /*MessageActionItem | null*/, error)
- ApplyEdit(context.Context, *ApplyWorkspaceEditParams) (*ApplyWorkspaceEditResult, error)
-}
-
-func clientDispatch(ctx context.Context, client Client, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) {
- switch r.Method() {
- case "window/showMessage": // notif
- var params ShowMessageParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := client.ShowMessage(ctx, &params)
- return true, reply(ctx, nil, err)
- case "window/logMessage": // notif
- var params LogMessageParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := client.LogMessage(ctx, &params)
- return true, reply(ctx, nil, err)
- case "telemetry/event": // notif
- var params interface{}
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := client.Event(ctx, &params)
- return true, reply(ctx, nil, err)
- case "textDocument/publishDiagnostics": // notif
- var params PublishDiagnosticsParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := client.PublishDiagnostics(ctx, &params)
- return true, reply(ctx, nil, err)
- case "$/progress": // notif
- var params ProgressParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := client.Progress(ctx, &params)
- return true, reply(ctx, nil, err)
- case "workspace/workspaceFolders": // req
- if len(r.Params()) > 0 {
- return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
- }
- resp, err := client.WorkspaceFolders(ctx)
- return true, reply(ctx, resp, err)
- case "workspace/configuration": // req
- var params ParamConfiguration
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := client.Configuration(ctx, &params)
- return true, reply(ctx, resp, err)
- case "window/workDoneProgress/create": // req
- var params WorkDoneProgressCreateParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := client.WorkDoneProgressCreate(ctx, &params)
- return true, reply(ctx, nil, err)
- case "window/showDocument": // req
- var params ShowDocumentParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := client.ShowDocument(ctx, &params)
- return true, reply(ctx, resp, err)
- case "client/registerCapability": // req
- var params RegistrationParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := client.RegisterCapability(ctx, &params)
- return true, reply(ctx, nil, err)
- case "client/unregisterCapability": // req
- var params UnregistrationParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := client.UnregisterCapability(ctx, &params)
- return true, reply(ctx, nil, err)
- case "window/showMessageRequest": // req
- var params ShowMessageRequestParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := client.ShowMessageRequest(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspace/applyEdit": // req
- var params ApplyWorkspaceEditParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := client.ApplyEdit(ctx, &params)
- return true, reply(ctx, resp, err)
-
- default:
- return false, nil
- }
-}
-
-func (s *clientDispatcher) ShowMessage(ctx context.Context, params *ShowMessageParams) error {
- return s.sender.Notify(ctx, "window/showMessage", params)
-}
-
-func (s *clientDispatcher) LogMessage(ctx context.Context, params *LogMessageParams) error {
- return s.sender.Notify(ctx, "window/logMessage", params)
-}
-
-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) Progress(ctx context.Context, params *ProgressParams) error {
- return s.sender.Notify(ctx, "$/progress", params)
-}
-func (s *clientDispatcher) WorkspaceFolders(ctx context.Context) ([]WorkspaceFolder /*WorkspaceFolder[] | null*/, error) {
- var result []WorkspaceFolder /*WorkspaceFolder[] | null*/
- if err := s.sender.Call(ctx, "workspace/workspaceFolders", nil, &result); err != nil {
- return nil, err
- }
- return result, 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) WorkDoneProgressCreate(ctx context.Context, params *WorkDoneProgressCreateParams) error {
- return s.sender.Call(ctx, "window/workDoneProgress/create", params, nil) // Call, not Notify
-}
-
-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) RegisterCapability(ctx context.Context, params *RegistrationParams) error {
- return s.sender.Call(ctx, "client/registerCapability", params, nil) // Call, not Notify
-}
-
-func (s *clientDispatcher) UnregisterCapability(ctx context.Context, params *UnregistrationParams) error {
- return s.sender.Call(ctx, "client/unregisterCapability", params, nil) // Call, not Notify
-}
-
-func (s *clientDispatcher) ShowMessageRequest(ctx context.Context, params *ShowMessageRequestParams) (*MessageActionItem /*MessageActionItem | null*/, error) {
- var result *MessageActionItem /*MessageActionItem | null*/
- if err := s.sender.Call(ctx, "window/showMessageRequest", params, &result); err != nil {
- return nil, err
- }
- return result, 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
-}
diff --git a/internal/lsp/protocol/tsprotocol.go b/internal/lsp/protocol/tsprotocol.go
deleted file mode 100644
index 2438d40c3..000000000
--- a/internal/lsp/protocol/tsprotocol.go
+++ /dev/null
@@ -1,6750 +0,0 @@
-// 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.
-
-// Code generated (see typescript/README.md) DO NOT EDIT.
-
-// Package protocol contains data types and code for LSP json rpcs
-// generated automatically from vscode-languageserver-node
-// commit: 696f9285bf849b73745682fdb1c1feac73eb8772
-// last fetched Fri Mar 04 2022 14:48:10 GMT-0500 (Eastern Standard Time)
-package protocol
-
-import "encoding/json"
-
-/**
- * A special text edit with an additional change annotation.
- *
- * @since 3.16.0.
- */
-type AnnotatedTextEdit struct {
- /**
- * The actual identifier of the change annotation
- */
- AnnotationID ChangeAnnotationIdentifier `json:"annotationId"`
- TextEdit
-}
-
-/**
- * The parameters passed via a apply workspace edit request.
- */
-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
- */
-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"`
-}
-
-/**
- * @since 3.16.0
- */
-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
- */
-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 [`this.from`](#CallHierarchyIncomingCall.from).
- */
- FromRanges []Range `json:"fromRanges"`
-}
-
-/**
- * The parameter of a `callHierarchy/incomingCalls` request.
- *
- * @since 3.16.0
- */
-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
- */
-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 [`range`](#CallHierarchyItem.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 LSPAny `json:"data,omitempty"`
-}
-
-/**
- * Call hierarchy options used during static registration.
- *
- * @since 3.16.0
- */
-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
- */
-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 [`provideCallHierarchyOutgoingCalls`](#CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls)
- * and not [`this.to`](#CallHierarchyOutgoingCall.to).
- */
- FromRanges []Range `json:"fromRanges"`
-}
-
-/**
- * The parameter of a `callHierarchy/outgoingCalls` request.
- *
- * @since 3.16.0
- */
-type CallHierarchyOutgoingCallsParams struct {
- Item CallHierarchyItem `json:"item"`
- WorkDoneProgressParams
- PartialResultParams
-}
-
-/**
- * The parameter of a `textDocument/prepareCallHierarchy` request.
- *
- * @since 3.16.0
- */
-type CallHierarchyPrepareParams struct {
- TextDocumentPositionParams
- WorkDoneProgressParams
-}
-
-/**
- * Call hierarchy options used during static or dynamic registration.
- *
- * @since 3.16.0
- */
-type CallHierarchyRegistrationOptions struct {
- TextDocumentRegistrationOptions
- CallHierarchyOptions
- StaticRegistrationOptions
-}
-
-type CancelParams struct {
- /**
- * The request id to cancel.
- */
- ID interface{} /*number | string*/ `json:"id"`
-}
-
-/**
- * Additional information that describes document changes.
- *
- * @since 3.16.0
- */
-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.
- */
-type ChangeAnnotationIdentifier = string
-
-type ClientCapabilities struct {
- /**
- * The workspace client capabilities
- */
- Workspace Workspace3Gn `json:"workspace,omitempty"`
- /**
- * Text document specific client capabilities.
- */
- TextDocument TextDocumentClientCapabilities `json:"textDocument,omitempty"`
- /**
- * Window specific client capabilities.
- */
- Window struct {
- /**
- * Whether client supports server initiated progress using the
- * `window/workDoneProgress/create` request.
- *
- * 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"`
- } `json:"window,omitempty"`
- /**
- * General client capabilities.
- *
- * @since 3.16.0
- */
- General GeneralClientCapabilities `json:"general,omitempty"`
- /**
- * Experimental client capabilities.
- */
- Experimental interface{} `json:"experimental,omitempty"`
-}
-
-/**
- * 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.
- */
-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 [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action)
- * code action menu.
- *
- * - Disabled actions are shown as faded out in the code action menu when the user request 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 a disabled code actions are returned, the client should show the user an
- * error message with `reason` in the editor.
- *
- * @since 3.16.0
- */
- Disabled *struct {
- /**
- * Human readable description of why the code action is currently disabled.
- *
- * This is displayed in the code actions UI.
- */
- Reason string `json:"reason"`
- } `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 a 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 LSPAny `json:"data,omitempty"`
-}
-
-/**
- * The Client Capabilities of a [CodeActionRequest](#CodeActionRequest).
- */
-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 struct {
- /**
- * The code action kind is support with the following value
- * set.
- */
- CodeActionKind 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"`
- } `json:"codeActionKind"`
- } `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 support resolving additional code action
- * properties via a separate `codeAction/resolve` request.
- *
- * @since 3.16.0
- */
- ResolveSupport struct {
- /**
- * The properties that a client can resolve lazily.
- */
- Properties []string `json:"properties"`
- } `json:"resolveSupport,omitempty"`
- /**
- * Whether th 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"`
-}
-
-/**
- * Contains additional diagnostic information about the context in which
- * a [code action](#CodeActionProvider.provideCodeActions) is run.
- */
-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"`
-}
-
-/**
- * A set of predefined code action kinds
- */
-type CodeActionKind string
-
-/**
- * Provider options for a [CodeActionRequest](#CodeActionRequest).
- */
-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"`
- /**
- * 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 [CodeActionRequest](#CodeActionRequest).
- */
-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
-}
-
-/**
- * The reason why code actions were requested.
- *
- * @since 3.17.0 - proposed state
- */
-type CodeActionTriggerKind float64
-
-/**
- * Structure to capture a description for an error code.
- *
- * @since 3.16.0
- */
-type CodeDescription struct {
- /**
- * An URI to open with more information about the diagnostic error.
- */
- Href URI `json:"href"`
-}
-
-/**
- * A code lens represents a [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 to two stages.
- */
-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 [CodeLensRequest](#CodeLensRequest) and a [CodeLensResolveRequest]
- * (#CodeLensResolveRequest)
- */
- Data LSPAny `json:"data,omitempty"`
-}
-
-/**
- * The client capabilities of a [CodeLensRequest](#CodeLensRequest).
- */
-type CodeLensClientCapabilities struct {
- /**
- * Whether code lens supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
-}
-
-/**
- * Code Lens provider options of a [CodeLensRequest](#CodeLensRequest).
- */
-type CodeLensOptions struct {
- /**
- * Code lens has a resolve provider as well.
- */
- ResolveProvider bool `json:"resolveProvider,omitempty"`
- WorkDoneProgressOptions
-}
-
-/**
- * The parameters of a [CodeLensRequest](#CodeLensRequest).
- */
-type CodeLensParams struct {
- /**
- * The document to request code lens for.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- WorkDoneProgressParams
- PartialResultParams
-}
-
-/**
- * @since 3.16.0
- */
-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.
- */
-type Color struct {
- /**
- * The red component of this color in the range [0-1].
- */
- Red Decimal `json:"red"`
- /**
- * The green component of this color in the range [0-1].
- */
- Green Decimal `json:"green"`
- /**
- * The blue component of this color in the range [0-1].
- */
- Blue Decimal `json:"blue"`
- /**
- * The alpha component of this color in the range [0-1].
- */
- Alpha Decimal `json:"alpha"`
-}
-
-/**
- * Represents a color range from a document.
- */
-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"`
-}
-
-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 [edit](#TextEdit) which is applied to a document when selecting
- * this presentation for the color. When `falsy` the [label](#ColorPresentation.label)
- * is used.
- */
- TextEdit TextEdit `json:"textEdit,omitempty"`
- /**
- * An optional array of additional [text edits](#TextEdit) that are applied when
- * selecting this color presentation. Edits must not overlap with the main [edit](#ColorPresentation.textEdit) nor with themselves.
- */
- AdditionalTextEdits []TextEdit `json:"additionalTextEdits,omitempty"`
-}
-
-/**
- * Parameters for a [ColorPresentationRequest](#ColorPresentationRequest).
- */
-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.
- */
-type Command struct {
- /**
- * Title of the command, like `save`.
- */
- Title string `json:"title"`
- /**
- * 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
- */
-type CompletionClientCapabilities struct {
- /**
- * Whether completion supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
- /**
- * The client supports the following `CompletionItem` specific
- * capabilities.
- */
- CompletionItem 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 follow 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 struct {
- /**
- * The tags supported by the client.
- */
- ValueSet []CompletionItemTag `json:"valueSet"`
- } `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 struct {
- /**
- * The properties that a client can resolve lazily.
- */
- Properties []string `json:"properties"`
- } `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 struct {
- ValueSet []InsertTextMode `json:"valueSet"`
- } `json:"insertTextModeSupport,omitempty"`
- /**
- * The client has support for completion item label
- * details (see also `CompletionItemLabelDetails`).
- *
- * @since 3.17.0 - proposed state
- */
- LabelDetailsSupport bool `json:"labelDetailsSupport,omitempty"`
- } `json:"completionItem,omitempty"`
- CompletionItemKind 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"`
- } `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 - proposed state
- */
- 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 - proposed state
- */
- CompletionList struct {
- /**
- * The client supports the 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 - proposed state
- */
- ItemDefaults []string `json:"itemDefaults,omitempty"`
- } `json:"completionList,omitempty"`
-}
-
-/**
- * Contains additional information about the context in which a completion request is triggered.
- */
-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.
- */
-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 - proposed state
- */
- 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 string/*string | MarkupContent*/ `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 [label](#CompletionItem.label)
- * is used.
- */
- SortText string `json:"sortText,omitempty"`
- /**
- * A string that should be used when filtering a set of
- * completion items. When `falsy` the [label](#CompletionItem.label)
- * is used.
- */
- FilterText string `json:"filterText,omitempty"`
- /**
- * A string that should be inserted into a document when selecting
- * this completion. When `falsy` the [label](#CompletionItem.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<cursor position>`
- * 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 ignored the clients default value depends on
- * the `textDocument.completion.insertTextMode` client capability.
- *
- * @since 3.16.0
- */
- InsertTextMode InsertTextMode `json:"insertTextMode,omitempty"`
- /**
- * An [edit](#TextEdit) which is applied to a document when selecting
- * this completion. When an edit is provided the value of
- * [insertText](#CompletionItem.insertText) is ignored.
- *
- * Most editors support two different operation 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 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 a 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 *TextEdit/*TextEdit | InsertReplaceEdit*/ `json:"textEdit,omitempty"`
- /**
- * An optional array of additional [text edits](#TextEdit) that are applied when
- * selecting this completion. Edits must not overlap (including the same insert position)
- * with the main [edit](#CompletionItem.textEdit) 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 [command](#Command) that is executed *after* inserting this completion. *Note* that
- * additional modifications to the current document should be described with the
- * [additionalTextEdits](#CompletionItem.additionalTextEdits)-property.
- */
- Command *Command `json:"command,omitempty"`
- /**
- * A data entry field that is preserved on a completion item between a
- * [CompletionRequest](#CompletionRequest) and a [CompletionResolveRequest](#CompletionResolveRequest).
- */
- Data LSPAny `json:"data,omitempty"`
-}
-
-/**
- * The kind of a completion entry.
- */
-type CompletionItemKind float64
-
-/**
- * Additional details for a completion item label.
- *
- * @since 3.17.0 - proposed state
- */
-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 or 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 or file path.
- */
- 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 float64
-
-/**
- * Represents a collection of [completion items](#CompletionItem) to be presented
- * in the editor.
- */
-type CompletionList struct {
- /**
- * This list it not complete. Further typing results in recomputing this list.
- */
- 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 - proposed state
- */
- ItemDefaults struct {
- /**
- * A default commit character set.
- *
- * @since 3.17.0 - proposed state
- */
- CommitCharacters []string `json:"commitCharacters,omitempty"`
- /**
- * A default edit range
- *
- * @since 3.17.0 - proposed state
- */
- EditRange Range/*Range | { insert: Range; replace: Range; }*/ `json:"editRange,omitempty"`
- /**
- * A default insert text format
- *
- * @since 3.17.0 - proposed state
- */
- InsertTextFormat InsertTextFormat `json:"insertTextFormat,omitempty"`
- /**
- * A default insert text mode
- *
- * @since 3.17.0 - proposed state
- */
- InsertTextMode InsertTextMode `json:"insertTextMode,omitempty"`
- } `json:"itemDefaults,omitempty"`
- /**
- * The completion items.
- */
- Items []CompletionItem `json:"items"`
-}
-
-/**
- * Completion options.
- */
-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 - proposed state
- */
- CompletionItem 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 - proposed state
- */
- LabelDetailsSupport bool `json:"labelDetailsSupport,omitempty"`
- } `json:"completionItem,omitempty"`
- WorkDoneProgressOptions
-}
-
-/**
- * Completion parameters
- */
-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
-}
-
-/**
- * How a completion was triggered
- */
-type CompletionTriggerKind float64
-
-type ConfigurationClientCapabilities struct {
- /**
- * The workspace client capabilities
- */
- Workspace Workspace4Gn `json:"workspace,omitempty"`
-}
-
-type ConfigurationItem struct {
- /**
- * The scope to get the configuration section for.
- */
- ScopeURI string `json:"scopeUri,omitempty"`
- /**
- * The configuration section asked for.
- */
- Section string `json:"section,omitempty"`
-}
-
-/**
- * The parameters of a configuration request.
- */
-type ConfigurationParams struct {
- Items []ConfigurationItem `json:"items"`
-}
-
-/**
- * Create file operation.
- */
-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.
- */
-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 file create requests/notifications.
- *
- * @since 3.16.0
- */
-type CreateFilesParams struct {
- /**
- * An array of all files/folders created in this operation.
- */
- Files []FileCreate `json:"files"`
-}
-
-/**
- * Defines a decimal number. Since decimal numbers are very
- * rare in the language server specification we denote the
- * exact range with every decimal using the mathematics
- * interval notations (e.g. [0, 1] denotes all decimals d with
- * 0 <= d <= 1.
- */
-type Decimal = float64
-
-/**
- * The declaration of a symbol representation as one or many [locations](#Location).
- */
-type Declaration = []Location /*Location | Location[]*/
-
-/**
- * @since 3.14.0
- */
-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 [location](#Location) declarations, including the range of
- * the declaring symbol.
- *
- * Servers should prefer returning `DeclarationLink` over `Declaration` if supported
- * by the client.
- */
-type DeclarationLink = LocationLink
-
-type DeclarationOptions struct {
- WorkDoneProgressOptions
-}
-
-type DeclarationParams struct {
- TextDocumentPositionParams
- WorkDoneProgressParams
- PartialResultParams
-}
-
-type DeclarationRegistrationOptions struct {
- DeclarationOptions
- TextDocumentRegistrationOptions
- StaticRegistrationOptions
-}
-
-/**
- * The definition of a symbol represented as one or many [locations](#Location).
- * 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.
- */
-type Definition = []Location /*Location | Location[]*/
-
-/**
- * Client Capabilities for a [DefinitionRequest](#DefinitionRequest).
- */
-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 [location](#Location) definitions, including the range of
- * the defining symbol
- */
-type DefinitionLink = LocationLink
-
-/**
- * Server Capabilities for a [DefinitionRequest](#DefinitionRequest).
- */
-type DefinitionOptions struct {
- WorkDoneProgressOptions
-}
-
-/**
- * Parameters for a [DefinitionRequest](#DefinitionRequest).
- */
-type DefinitionParams struct {
- TextDocumentPositionParams
- WorkDoneProgressParams
- PartialResultParams
-}
-
-/**
- * Delete file operation
- */
-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
- */
-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 file delete requests/notifications.
- *
- * @since 3.16.0
- */
-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.
- */
-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{}/*integer | string*/ `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 LSPAny `json:"data,omitempty"`
-}
-
-/**
- * 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.
- */
-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"`
-}
-
-/**
- * The diagnostic's severity.
- */
-type DiagnosticSeverity float64
-
-/**
- * The diagnostic tags.
- *
- * @since 3.15.0
- */
-type DiagnosticTag float64
-
-type DidChangeConfigurationClientCapabilities struct {
- /**
- * Did change configuration notification supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
-}
-
-/**
- * The parameters of a change configuration notification.
- */
-type DidChangeConfigurationParams struct {
- /**
- * The actual changed settings
- */
- Settings LSPAny `json:"settings"`
-}
-
-/**
- * The params sent in a change notebook document notification.
- *
- * @since 3.17.0 - proposed state
- */
-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.
- */
-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"`
-}
-
-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"`
-}
-
-/**
- * The watched files change notification's parameters.
- */
-type DidChangeWatchedFilesParams struct {
- /**
- * The actual file events.
- */
- Changes []FileEvent `json:"changes"`
-}
-
-/**
- * Describe options to be used when registered for text document change events.
- */
-type DidChangeWatchedFilesRegistrationOptions struct {
- /**
- * The watchers to register.
- */
- Watchers []FileSystemWatcher `json:"watchers"`
-}
-
-/**
- * The parameters of a `workspace/didChangeWorkspaceFolders` notification.
- */
-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 - proposed state
- */
-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 send in a close text document notification
- */
-type DidCloseTextDocumentParams struct {
- /**
- * The document that was closed.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
-}
-
-/**
- * The params sent in a open notebook document notification.
- *
- * @since 3.17.0 - proposed state
- */
-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 send in a open text document notification
- */
-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 - proposed state
- */
-type DidSaveNotebookDocumentParams = struct {
- /**
- * The notebook document that got saved.
- */
- NotebookDocument NotebookDocumentIdentifier `json:"notebookDocument"`
-}
-
-/**
- * The parameters send in a save text document notification
- */
-type DidSaveTextDocumentParams struct {
- /**
- * The document that was closed.
- */
- 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"`
-}
-
-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"`
-}
-
-type DocumentColorOptions struct {
- WorkDoneProgressOptions
-}
-
-/**
- * Parameters for a [DocumentColorRequest](#DocumentColorRequest).
- */
-type DocumentColorParams struct {
- /**
- * The text document.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- WorkDoneProgressParams
- PartialResultParams
-}
-
-type DocumentColorRegistrationOptions struct {
- TextDocumentRegistrationOptions
- StaticRegistrationOptions
- DocumentColorOptions
-}
-
-/**
- * Parameters of the document diagnostic request.
- *
- * @since 3.17.0 - proposed state
- */
-type DocumentDiagnosticParams struct {
- /**
- * An optional token that a server can use to report work done progress.
- */
- WorkDoneToken ProgressToken `json:"workDoneToken,omitempty"`
- /**
- * An optional token that a server can use to report partial results (e.g. streaming) to
- * the client.
- */
- PartialResultToken ProgressToken `json:"partialResultToken,omitempty"`
- /**
- * 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"`
-}
-
-/**
- * The result of a document diagnostic pull request. A report can
- * either be a full report containing all diagnostics for the
- * requested document or a unchanged report indicating that nothing
- * has changed in terms of diagnostics in comparison to the last
- * pull request.
- *
- * @since 3.17.0 - proposed state
- */
-type DocumentDiagnosticReport = interface{} /*RelatedFullDocumentDiagnosticReport | RelatedUnchangedDocumentDiagnosticReport*/
-
-/**
- * A document filter describes a top level text document or
- * a notebook cell document.
- *
- * @since 3.17.0 - proposed support for NotebookCellTextDocumentFilter.
- */
-type DocumentFilter = interface{} /*TextDocumentFilter | NotebookCellTextDocumentFilter*/
-
-/**
- * Client capabilities of a [DocumentFormattingRequest](#DocumentFormattingRequest).
- */
-type DocumentFormattingClientCapabilities struct {
- /**
- * Whether formatting supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
-}
-
-/**
- * Provider options for a [DocumentFormattingRequest](#DocumentFormattingRequest).
- */
-type DocumentFormattingOptions struct {
- WorkDoneProgressOptions
-}
-
-/**
- * The parameters of a [DocumentFormattingRequest](#DocumentFormattingRequest).
- */
-type DocumentFormattingParams struct {
- /**
- * The document to format.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- /**
- * The format options
- */
- Options FormattingOptions `json:"options"`
- WorkDoneProgressParams
-}
-
-/**
- * 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.
- */
-type DocumentHighlight struct {
- /**
- * The range this highlight applies to.
- */
- Range Range `json:"range"`
- /**
- * The highlight kind, default is [text](#DocumentHighlightKind.Text).
- */
- Kind DocumentHighlightKind `json:"kind,omitempty"`
-}
-
-/**
- * Client Capabilities for a [DocumentHighlightRequest](#DocumentHighlightRequest).
- */
-type DocumentHighlightClientCapabilities struct {
- /**
- * Whether document highlight supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
-}
-
-/**
- * A document highlight kind.
- */
-type DocumentHighlightKind float64
-
-/**
- * Provider options for a [DocumentHighlightRequest](#DocumentHighlightRequest).
- */
-type DocumentHighlightOptions struct {
- WorkDoneProgressOptions
-}
-
-/**
- * Parameters for a [DocumentHighlightRequest](#DocumentHighlightRequest).
- */
-type DocumentHighlightParams struct {
- TextDocumentPositionParams
- WorkDoneProgressParams
- PartialResultParams
-}
-
-/**
- * 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.
- */
-type DocumentLink struct {
- /**
- * The range this link applies to.
- */
- Range Range `json:"range"`
- /**
- * The uri this link points to.
- */
- Target string `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 LSPAny `json:"data,omitempty"`
-}
-
-/**
- * The client capabilities of a [DocumentLinkRequest](#DocumentLinkRequest).
- */
-type DocumentLinkClientCapabilities struct {
- /**
- * Whether document link supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
- /**
- * Whether the client support the `tooltip` property on `DocumentLink`.
- *
- * @since 3.15.0
- */
- TooltipSupport bool `json:"tooltipSupport,omitempty"`
-}
-
-/**
- * Provider options for a [DocumentLinkRequest](#DocumentLinkRequest).
- */
-type DocumentLinkOptions struct {
- /**
- * Document links have a resolve provider as well.
- */
- ResolveProvider bool `json:"resolveProvider,omitempty"`
- WorkDoneProgressOptions
-}
-
-/**
- * The parameters of a [DocumentLinkRequest](#DocumentLinkRequest).
- */
-type DocumentLinkParams struct {
- /**
- * The document to provide document links for.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- WorkDoneProgressParams
- PartialResultParams
-}
-
-/**
- * Client capabilities of a [DocumentOnTypeFormattingRequest](#DocumentOnTypeFormattingRequest).
- */
-type DocumentOnTypeFormattingClientCapabilities struct {
- /**
- * Whether on type formatting supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
-}
-
-/**
- * Provider options for a [DocumentOnTypeFormattingRequest](#DocumentOnTypeFormattingRequest).
- */
-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 [DocumentOnTypeFormattingRequest](#DocumentOnTypeFormattingRequest).
- */
-type DocumentOnTypeFormattingParams struct {
- /**
- * The document to format.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- /**
- * The position at which this request was send.
- */
- Position Position `json:"position"`
- /**
- * The character that has been typed.
- */
- Ch string `json:"ch"`
- /**
- * The format options.
- */
- Options FormattingOptions `json:"options"`
-}
-
-/**
- * Client capabilities of a [DocumentRangeFormattingRequest](#DocumentRangeFormattingRequest).
- */
-type DocumentRangeFormattingClientCapabilities struct {
- /**
- * Whether range formatting supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
-}
-
-/**
- * Provider options for a [DocumentRangeFormattingRequest](#DocumentRangeFormattingRequest).
- */
-type DocumentRangeFormattingOptions struct {
- WorkDoneProgressOptions
-}
-
-/**
- * The parameters of a [DocumentRangeFormattingRequest](#DocumentRangeFormattingRequest).
- */
-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
-}
-
-/**
- * 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.
- */
-type DocumentSelector = []string /*string | DocumentFilter*/
-
-/**
- * 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.
- */
-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 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 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 [DocumentSymbolRequest](#DocumentSymbolRequest).
- */
-type DocumentSymbolClientCapabilities struct {
- /**
- * Whether document symbol supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
- /**
- * Specific capabilities for the `SymbolKind`.
- */
- SymbolKind 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"`
- } `json:"symbolKind,omitempty"`
- /**
- * The client support 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 struct {
- /**
- * The tags supported by the client.
- */
- ValueSet []SymbolTag `json:"valueSet"`
- } `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 [DocumentSymbolRequest](#DocumentSymbolRequest).
- */
-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 [DocumentSymbolRequest](#DocumentSymbolRequest).
- */
-type DocumentSymbolParams struct {
- /**
- * The text document.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- WorkDoneProgressParams
- PartialResultParams
-}
-
-/**
- * A tagging type for string properties that are actually document URIs.
- */
-type DocumentURI string
-
-/**
- * The client capabilities of a [ExecuteCommandRequest](#ExecuteCommandRequest).
- */
-type ExecuteCommandClientCapabilities struct {
- /**
- * Execute command supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
-}
-
-/**
- * The server capabilities of a [ExecuteCommandRequest](#ExecuteCommandRequest).
- */
-type ExecuteCommandOptions struct {
- /**
- * The commands to be executed on the server
- */
- Commands []string `json:"commands"`
- WorkDoneProgressOptions
-}
-
-/**
- * The parameters of a [ExecuteCommandRequest](#ExecuteCommandRequest).
- */
-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
-}
-
-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 float64
-
-/**
- * Represents information on a file/folder create.
- *
- * @since 3.16.0
- */
-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
- */
-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.
- */
-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
- */
-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 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 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 willDeleteFiles requests.
- */
- WillDelete bool `json:"willDelete,omitempty"`
-}
-
-/**
- * A filter to describe in which file operation requests or notifications
- * the server is interested in.
- *
- * @since 3.16.0
- */
-type FileOperationFilter struct {
- /**
- * A Uri 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
- */
-type FileOperationOptions struct {
- /**
- * The server is interested in didCreateFiles notifications.
- */
- DidCreate FileOperationRegistrationOptions `json:"didCreate,omitempty"`
- /**
- * The server is interested in willCreateFiles requests.
- */
- WillCreate FileOperationRegistrationOptions `json:"willCreate,omitempty"`
- /**
- * The server is interested in didRenameFiles notifications.
- */
- DidRename FileOperationRegistrationOptions `json:"didRename,omitempty"`
- /**
- * The server is interested in willRenameFiles requests.
- */
- WillRename FileOperationRegistrationOptions `json:"willRename,omitempty"`
- /**
- * The server is interested in didDeleteFiles file notifications.
- */
- DidDelete FileOperationRegistrationOptions `json:"didDelete,omitempty"`
- /**
- * The server is interested in willDeleteFiles file requests.
- */
- WillDelete FileOperationRegistrationOptions `json:"willDelete,omitempty"`
-}
-
-/**
- * A pattern to describe in which file operation requests or notifications
- * the server is interested in.
- *
- * @since 3.16.0
- */
-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
- */
-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
- */
-type FileOperationRegistrationOptions struct {
- /**
- * The actual filters.
- */
- Filters []FileOperationFilter `json:"filters"`
-}
-
-/**
- * Represents information on a file/folder rename.
- *
- * @since 3.16.0
- */
-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"`
-}
-
-type FileSystemWatcher struct {
- /**
- * The glob pattern to watch. 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`)
- */
- GlobPattern string `json:"globPattern"`
- /**
- * The kind of events of interest. If omitted it defaults
- * to WatchKind.Create | WatchKind.Change | WatchKind.Delete
- * which is 7.
- */
- Kind uint32 `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.
- */
-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
- * [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds.
- */
- Kind string `json:"kind,omitempty"`
-}
-
-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"`
-}
-
-/**
- * Enum of known range kinds
- */
-type FoldingRangeKind string
-
-type FoldingRangeOptions struct {
- WorkDoneProgressOptions
-}
-
-/**
- * Parameters for a [FoldingRangeRequest](#FoldingRangeRequest).
- */
-type FoldingRangeParams struct {
- /**
- * The text document.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- WorkDoneProgressParams
- PartialResultParams
-}
-
-type FoldingRangeRegistrationOptions struct {
- TextDocumentRegistrationOptions
- FoldingRangeOptions
- StaticRegistrationOptions
-}
-
-/**
- * Value-object describing what options formatting should use.
- */
-type FormattingOptions struct {
- /**
- * Size of a tab in spaces.
- */
- TabSize uint32 `json:"tabSize"`
- /**
- * Prefer spaces over tabs.
- */
- InsertSpaces bool `json:"insertSpaces"`
- /**
- * Trim trailing whitespaces 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 - proposed state
- */
-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
- */
-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 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"`
- } `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 result of a hover request.
- */
-type Hover struct {
- /**
- * The hover's content
- */
- Contents MarkupContent/*MarkupContent | MarkedString | MarkedString[]*/ `json:"contents"`
- /**
- * An optional range
- */
- Range Range `json:"range,omitempty"`
-}
-
-type HoverClientCapabilities struct {
- /**
- * Whether hover supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
- /**
- * Client supports the follow content formats for the content
- * property. The order describes the preferred format of the client.
- */
- ContentFormat []MarkupKind `json:"contentFormat,omitempty"`
-}
-
-/**
- * Hover options.
- */
-type HoverOptions struct {
- WorkDoneProgressOptions
-}
-
-/**
- * Parameters for a [HoverRequest](#HoverRequest).
- */
-type HoverParams struct {
- TextDocumentPositionParams
- WorkDoneProgressParams
-}
-
-/**
- * @since 3.6.0
- */
-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"`
-}
-
-type ImplementationOptions struct {
- WorkDoneProgressOptions
-}
-
-type ImplementationParams struct {
- TextDocumentPositionParams
- WorkDoneProgressParams
- PartialResultParams
-}
-
-type ImplementationRegistrationOptions struct {
- TextDocumentRegistrationOptions
- ImplementationOptions
- StaticRegistrationOptions
-}
-
-/**
- * Known error codes for an `InitializeError`;
- */
-type InitializeError float64
-
-type InitializeParams struct {
- /**
- * The process Id of the parent process that started
- * the server.
- */
- ProcessID int32/*integer | null*/ `json:"processId"`
- /**
- * Information about the client
- *
- * @since 3.15.0
- */
- 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"`
- } `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/*string | null*/ `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/*DocumentUri | null*/ `json:"rootUri"`
- /**
- * The capabilities provided by the client (editor or tool)
- */
- Capabilities ClientCapabilities `json:"capabilities"`
- /**
- * User provided initialization options.
- */
- InitializationOptions LSPAny `json:"initializationOptions,omitempty"`
- /**
- * The initial trace setting. If omitted trace is disabled ('off').
- */
- Trace string/* 'off' | 'messages' | 'compact' | 'verbose' */ `json:"trace,omitempty"`
- /**
- * The actual configured workspace folders.
- */
- WorkspaceFolders []WorkspaceFolder/*WorkspaceFolder[] | null*/ `json:"workspaceFolders"`
-}
-
-/**
- * The result returned from an initialize request.
- */
-type InitializeResult struct {
- /**
- * The capabilities the language server provides.
- */
- Capabilities ServerCapabilities `json:"capabilities"`
- /**
- * Information about the server.
- *
- * @since 3.15.0
- */
- 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"`
- } `json:"serverInfo,omitempty"`
-}
-
-type InitializedParams struct {
-}
-
-/**
- * Inlay hint information.
- *
- * @since 3.17.0 - proposed state
- */
-type InlayHint = struct {
- /**
- * The position of this hint.
- */
- 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/*string | 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"`
- /**
- * The tooltip text when you hover over this item.
- */
- Tooltip string/*string | MarkupContent*/ `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"`
-}
-
-/**
- * Inlay hint client capabilities
- *
- * @since 3.17.0 - proposed state
- */
-type InlayHintClientCapabilities = struct {
- /**
- * Whether inlay hints support dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
- /**
- * Indicates which properties a client can resolve lazily on a inlay
- * hint.
- */
- ResolveSupport struct {
- /**
- * The properties that a client can resolve lazily.
- */
- Properties []string `json:"properties"`
- } `json:"resolveSupport,omitempty"`
-}
-
-/**
- * Inlay hint kinds.
- *
- * @since 3.17.0 - proposed state
- */
-type InlayHintKind float64
-
-/**
- * An inlay hint label part allows for interactive and composite labels
- * of inlay hints.
- *
- * @since 3.17.0 - proposed state
- */
-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 string/*string | MarkupContent*/ `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 - proposed state
- */
-type InlayHintOptions struct {
- WorkDoneProgress bool `json:"workDoneProgress,omitempty"`
- /**
- * The server provides support to resolve additional
- * information for an inlay hint item.
- */
- ResolveProvider bool `json:"resolveProvider,omitempty"`
-}
-
-/**
- * A parameter literal used in inlay hints requests.
- *
- * @since 3.17.0 - proposed state
- */
-type InlayHintParams struct {
- /**
- * An optional token that a server can use to report work done progress.
- */
- WorkDoneToken ProgressToken `json:"workDoneToken,omitempty"`
- /**
- * The text document.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- /**
- * The visible document range for which inlay hints should be computed.
- */
- ViewPort Range `json:"viewPort"`
-}
-
-/**
- * Inlay hint options used during static or dynamic registration.
- *
- * @since 3.17.0 - proposed state
- */
-type InlayHintRegistrationOptions struct {
- WorkDoneProgress bool `json:"workDoneProgress,omitempty"`
- /**
- * The server provides support to resolve additional
- * information for an inlay hint item.
- */
- ResolveProvider bool `json:"resolveProvider,omitempty"`
- /**
- * 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/*DocumentSelector | null*/ `json:"documentSelector"`
- /**
- * 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"`
-}
-
-/**
- * Client workspace capabilities specific to inlay hints.
- *
- * @since 3.17.0 - proposed state
- */
-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"`
-}
-
-/**
- * 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 - proposed state
- */
-type InlineValue = interface{} /* InlineValueText | InlineValueVariableLookup | InlineValueEvaluatableExpression*/
-
-/**
- * Client capabilities specific to inline values.
- *
- * @since 3.17.0 - proposed state
- */
-type InlineValueClientCapabilities = struct {
- /**
- * Whether implementation supports dynamic registration for inline value providers.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
-}
-
-/**
- * @since 3.17.0 - proposed state
- */
-type InlineValueContext = struct {
- /**
- * 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 - proposed state
- */
-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 - proposed state
- */
-type InlineValueOptions = WorkDoneProgressOptions
-
-/**
- * A parameter literal used in inline value requests.
- *
- * @since 3.17.0 - proposed state
- */
-type InlineValueParams struct {
- /**
- * An optional token that a server can use to report work done progress.
- */
- WorkDoneToken ProgressToken `json:"workDoneToken,omitempty"`
- /**
- * The text document.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- /**
- * The visible document range for which inline values should be computed.
- */
- ViewPort Range `json:"viewPort"`
- /**
- * Additional information about the context in which inline values were
- * requested.
- */
- Context InlineValueContext `json:"context"`
-}
-
-/**
- * Inline value options used during static or dynamic registration.
- *
- * @since 3.17.0 - proposed state
- */
-type InlineValueRegistrationOptions 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/*DocumentSelector | null*/ `json:"documentSelector"`
- /**
- * 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"`
-}
-
-/**
- * Provide inline value as text.
- *
- * @since 3.17.0 - proposed state
- */
-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 - proposed state
- */
-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 - proposed state
- */
-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
- */
-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 float64
-
-/**
- * How whitespace and indentation is handled during completion
- * item insertion.
- *
- * @since 3.16.0
- */
-type InsertTextMode float64
-
-/**
- * The LSP any type
- *
- * @since 3.17.0
- */
-type LSPAny = interface{} /* LSPObject | LSPArray | string | int32 | uint32 | Decimal | bool | float64*/
-
-/**
- * LSP arrays.
- *
- * @since 3.17.0
- */
-type LSPArray = []LSPAny
-
-/**
- * LSP object definition.
- *
- * @since 3.17.0
- */
-type LSPObject = map[string]interface{} /*[key: string]: LSPAny*/
-
-/**
- * Client capabilities for the linked editing range request.
- *
- * @since 3.16.0
- */
-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"`
-}
-
-type LinkedEditingRangeOptions struct {
- WorkDoneProgressOptions
-}
-
-type LinkedEditingRangeParams struct {
- TextDocumentPositionParams
- WorkDoneProgressParams
-}
-
-type LinkedEditingRangeRegistrationOptions struct {
- TextDocumentRegistrationOptions
- LinkedEditingRangeOptions
- StaticRegistrationOptions
-}
-
-/**
- * The result of a linked editing range request.
- *
- * @since 3.16.0
- */
-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"`
-}
-
-/**
- * Represents a location inside a resource, such as a line
- * inside a text file.
- */
-type Location struct {
- URI DocumentURI `json:"uri"`
- Range Range `json:"range"`
-}
-
-/**
- * Represents the connection of two locations. Provides additional metadata over normal [locations](#Location),
- * including an origin range.
- */
-type LocationLink struct {
- /**
- * Span of the origin of this link.
- *
- * Used as the underlined span for mouse definition hover. 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 the `targetRange`. See also `DocumentSymbol#range`
- */
- TargetSelectionRange Range `json:"targetSelectionRange"`
-}
-
-/**
- * The log message parameters.
- */
-type LogMessageParams struct {
- /**
- * The message type. See {@link MessageType}
- */
- Type MessageType `json:"type"`
- /**
- * The actual message
- */
- Message string `json:"message"`
-}
-
-type LogTraceParams struct {
- Message string `json:"message"`
- Verbose string `json:"verbose,omitempty"`
-}
-
-/**
- * Client capabilities specific to the used markdown parser.
- *
- * @since 3.16.0
- */
-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.
- */
-type MarkedString = string /*string | { language: string; value: string }*/
-
-/**
- * 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.
- */
-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
-
-type MessageActionItem struct {
- /**
- * A short title like 'Retry', 'Open Log' etc.
- */
- Title string `json:"title"`
-}
-
-/**
- * The message type
- */
-type MessageType float64
-
-/**
- * Moniker definition to match LSIF 0.5 moniker definition.
- *
- * @since 3.16.0
- */
-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
- */
-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
-
-type MonikerOptions struct {
- WorkDoneProgressOptions
-}
-
-type MonikerParams struct {
- TextDocumentPositionParams
- WorkDoneProgressParams
- PartialResultParams
-}
-
-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 - proposed state
- */
-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.
- */
- 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 - proposed state
- */
-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 - proposed state
- */
-type NotebookCellKind float64
-
-/**
- * A notebook cell text document filter denotes a cell text
- * document by different properties.
- *
- * @since 3.17.0 - proposed state.
- */
-type NotebookCellTextDocumentFilter = struct {
- /**
- * A filter that matches against the notebook
- * containing the notebook cell.
- */
- NotebookDocument NotebookDocumentFilter `json:"notebookDocument"`
- /**
- * A language id like `python`.
- *
- * Will be matched against the language id of the
- * notebook cell document.
- */
- CellLanguage string `json:"cellLanguage,omitempty"`
-}
-
-/**
- * A notebook document.
- *
- * @since 3.17.0 - proposed state
- */
-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.
- */
- Metadata LSPObject `json:"metadata,omitempty"`
- /**
- * The cells of a notebook.
- */
- Cells []NotebookCell `json:"cells"`
-}
-
-/**
- * A change event for a notebook document.
- *
- * @since 3.17.0 - proposed state
- */
-type NotebookDocumentChangeEvent = struct {
- /**
- * The changed meta data if any.
- */
- Metadata LSPObject `json:"metadata,omitempty"`
- /**
- * Changes to cells
- */
- Cells struct {
- /**
- * Changes to the cell structure to add or
- * remove cells.
- */
- Structure 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"`
- } `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 []struct {
- Document VersionedTextDocumentIdentifier `json:"document"`
- Changes []TextDocumentContentChangeEvent `json:"changes"`
- } `json:"textContent,omitempty"`
- } `json:"cells,omitempty"`
-}
-
-/**
- * A notebook document filter denotes a notebook document by
- * different properties.
- *
- * @since 3.17.0 - proposed state.
- */
-type NotebookDocumentFilter = struct {
- /** The type of the enclosing notebook. */
- NotebookType string `json:"notebookType"`
- /** A Uri [scheme](#Uri.scheme), like `file` or `untitled`.
- * Will be matched against the URI of the notebook. */
- Scheme string `json:"scheme,omitempty"`
- /** A glob pattern, like `*.ipynb`.
- * Will be matched against the notebooks` URI path section.*/
- Pattern string `json:"pattern,omitempty"`
-}
-
-/**
- * A literal to identify a notebook document in the client.
- *
- * @since 3.17.0 - proposed state
- */
-type NotebookDocumentIdentifier = struct {
- /**
- * The notebook document's uri.
- */
- URI URI `json:"uri"`
-}
-
-/**
- * A text document identifier to optionally denote a specific version of a text document.
- */
-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/*integer | null*/ `json:"version"`
- TextDocumentIdentifier
-}
-
-/**
- * Represents a parameter of a callable-signature. A parameter can
- * have a label and a doc-comment.
- */
-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.
- *
- * *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/*string | [uinteger, uinteger]*/ `json:"label"`
- /**
- * The human-readable doc-comment of this signature. Will be shown
- * in the UI but can be omitted.
- */
- Documentation string/*string | MarkupContent*/ `json:"documentation,omitempty"`
-}
-
-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"`
-}
-
-/**
- * Position in a text document expressed as 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.
- *
- * Positions are line end character agnostic. So you can not specify a position that
- * denotes `\r|\n` or `\n|` where `|` represents the character offset.
- */
-type Position struct {
- /**
- * Line position in a document (zero-based).
- */
- Line uint32 `json:"line"`
- /**
- * Character offset on a line in a document (zero-based). Assuming that the line is
- * represented as a string, the `character` value represents the gap between the
- * `character` and `character + 1`.
- *
- * If the character value is greater than the line length it defaults back to the
- * line length.
- */
- Character uint32 `json:"character"`
-}
-
-type PrepareRenameParams struct {
- TextDocumentPositionParams
- WorkDoneProgressParams
-}
-
-type PrepareSupportDefaultBehavior = interface{}
-
-/**
- * A previous result id in a workspace pull request.
- *
- * @since 3.17.0 - proposed state
- */
-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"`
-}
-
-type ProgressParams struct {
- /**
- * The progress token provided by the client or server.
- */
- Token ProgressToken `json:"token"`
- /**
- * The progress data.
- */
- Value interface{} `json:"value"`
-}
-
-type ProgressToken = interface{} /*number | string*/
-
-/**
- * The publish diagnostic client capabilities.
- */
-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 struct {
- /**
- * The tags supported by the client.
- */
- ValueSet []DiagnosticTag `json:"valueSet"`
- } `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.
- */
-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 }
- * }
- * ```
- */
-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 [ReferencesRequest](#ReferencesRequest).
- */
-type ReferenceClientCapabilities struct {
- /**
- * Whether references supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
-}
-
-/**
- * Value-object that contains additional information when
- * requesting references.
- */
-type ReferenceContext struct {
- /**
- * Include the declaration of the current symbol.
- */
- IncludeDeclaration bool `json:"includeDeclaration"`
-}
-
-/**
- * Reference options.
- */
-type ReferenceOptions struct {
- WorkDoneProgressOptions
-}
-
-/**
- * Parameters for a [ReferencesRequest](#ReferencesRequest).
- */
-type ReferenceParams struct {
- Context ReferenceContext `json:"context"`
- TextDocumentPositionParams
- WorkDoneProgressParams
- PartialResultParams
-}
-
-/**
- * General parameters to to register for an notification or to register a provider.
- */
-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 to register for.
- */
- Method string `json:"method"`
- /**
- * Options necessary for the registration.
- */
- RegisterOptions LSPAny `json:"registerOptions,omitempty"`
-}
-
-type RegistrationParams struct {
- Registrations []Registration `json:"registrations"`
-}
-
-/**
- * Client capabilities specific to regular expressions.
- *
- * @since 3.16.0
- */
-type RegularExpressionsClientCapabilities struct {
- /**
- * The engine's name.
- */
- Engine string `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 - proposed state
- */
-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 - proposed state
- */
- RelatedDocuments map[string]interface{} /*[uri: string ** DocumentUri *]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport;*/ `json:"relatedDocuments,omitempty"`
-}
-
-/**
- * An unchanged diagnostic report with a set of related documents.
- *
- * @since 3.17.0 - proposed state
- */
-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 - proposed state
- */
- RelatedDocuments map[string]interface{} /*[uri: string ** DocumentUri *]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport;*/ `json:"relatedDocuments,omitempty"`
-}
-
-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 th 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
- */
-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
- */
-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 file rename requests/notifications.
- *
- * @since 3.16.0
- */
-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 [RenameRequest](#RenameRequest).
- */
-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 [RenameRequest](#RenameRequest).
- */
-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 [ResponseError](#ResponseError) with an
- * appropriate message set.
- */
- NewName string `json:"newName"`
- WorkDoneProgressParams
-}
-
-/**
- * A generic resource operation.
- */
-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.
- */
-type SaveOptions struct {
- /**
- * The client is supposed to include the content on save.
- */
- IncludeText bool `json:"includeText,omitempty"`
-}
-
-/**
- * A selection range represents a part of a selection hierarchy. A selection range
- * may have a parent selection range that contains it.
- */
-type SelectionRange struct {
- /**
- * The [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"`
-}
-
-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"`
-}
-
-type SelectionRangeOptions struct {
- WorkDoneProgressOptions
-}
-
-/**
- * A parameter literal used in selection range requests.
- */
-type SelectionRangeParams struct {
- /**
- * The text document.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- /**
- * The positions inside the text document.
- */
- Positions []Position `json:"positions"`
- WorkDoneProgressParams
- PartialResultParams
-}
-
-type SelectionRangeRegistrationOptions struct {
- SelectionRangeOptions
- TextDocumentRegistrationOptions
- StaticRegistrationOptions
-}
-
-/**
- * @since 3.16.0
- */
-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
- */
-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 struct {
- /**
- * The client will send the `textDocument/semanticTokens/range` request if
- * the server provides a corresponding handler.
- */
- Range bool/*boolean | { }*/ `json:"range,omitempty"`
- /**
- * The client will send the `textDocument/semanticTokens/full` request if
- * the server provides a corresponding handler.
- */
- Full interface{}/*boolean | <elided struct>*/ `json:"full,omitempty"`
- } `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
- */
-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
- */
-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
- */
-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"`
-}
-
-/**
- * @since 3.16.0
- */
-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
- */
-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 bool/*boolean | { }*/ `json:"range,omitempty"`
- /**
- * Server supports providing semantic tokens for a full document.
- */
- Full interface{}/*boolean | <elided struct>*/ `json:"full,omitempty"`
- WorkDoneProgressOptions
-}
-
-/**
- * @since 3.16.0
- */
-type SemanticTokensParams struct {
- /**
- * The text document.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- WorkDoneProgressParams
- PartialResultParams
-}
-
-/**
- * @since 3.16.0
- */
-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
- */
-type SemanticTokensRegistrationOptions struct {
- TextDocumentRegistrationOptions
- SemanticTokensOptions
- StaticRegistrationOptions
-}
-
-/**
- * @since 3.16.0
- */
-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"`
-}
-
-type ServerCapabilities struct {
- /**
- * Defines how text documents are synced. Is either a detailed structure defining each notification or
- * for backwards compatibility the TextDocumentSyncKind number.
- */
- TextDocumentSync interface{}/*TextDocumentSyncOptions | TextDocumentSyncKind*/ `json:"textDocumentSync,omitempty"`
- /**
- * The server provides completion support.
- */
- CompletionProvider CompletionOptions `json:"completionProvider,omitempty"`
- /**
- * The server provides hover support.
- */
- HoverProvider bool/*boolean | HoverOptions*/ `json:"hoverProvider,omitempty"`
- /**
- * The server provides signature help support.
- */
- SignatureHelpProvider SignatureHelpOptions `json:"signatureHelpProvider,omitempty"`
- /**
- * The server provides Goto Declaration support.
- */
- DeclarationProvider interface{}/* bool | DeclarationOptions | DeclarationRegistrationOptions*/ `json:"declarationProvider,omitempty"`
- /**
- * The server provides goto definition support.
- */
- DefinitionProvider bool/*boolean | DefinitionOptions*/ `json:"definitionProvider,omitempty"`
- /**
- * The server provides Goto Type Definition support.
- */
- TypeDefinitionProvider interface{}/* bool | TypeDefinitionOptions | TypeDefinitionRegistrationOptions*/ `json:"typeDefinitionProvider,omitempty"`
- /**
- * The server provides Goto Implementation support.
- */
- ImplementationProvider interface{}/* bool | ImplementationOptions | ImplementationRegistrationOptions*/ `json:"implementationProvider,omitempty"`
- /**
- * The server provides find references support.
- */
- ReferencesProvider bool/*boolean | ReferenceOptions*/ `json:"referencesProvider,omitempty"`
- /**
- * The server provides document highlight support.
- */
- DocumentHighlightProvider bool/*boolean | DocumentHighlightOptions*/ `json:"documentHighlightProvider,omitempty"`
- /**
- * The server provides document symbol support.
- */
- DocumentSymbolProvider bool/*boolean | DocumentSymbolOptions*/ `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{}/*boolean | CodeActionOptions*/ `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 interface{}/* bool | DocumentColorOptions | DocumentColorRegistrationOptions*/ `json:"colorProvider,omitempty"`
- /**
- * The server provides workspace symbol support.
- */
- WorkspaceSymbolProvider bool/*boolean | WorkspaceSymbolOptions*/ `json:"workspaceSymbolProvider,omitempty"`
- /**
- * The server provides document formatting.
- */
- DocumentFormattingProvider bool/*boolean | DocumentFormattingOptions*/ `json:"documentFormattingProvider,omitempty"`
- /**
- * The server provides document range formatting.
- */
- DocumentRangeFormattingProvider bool/*boolean | DocumentRangeFormattingOptions*/ `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{}/*boolean | RenameOptions*/ `json:"renameProvider,omitempty"`
- /**
- * The server provides folding provider support.
- */
- FoldingRangeProvider interface{}/* bool | FoldingRangeOptions | FoldingRangeRegistrationOptions*/ `json:"foldingRangeProvider,omitempty"`
- /**
- * The server provides selection range support.
- */
- SelectionRangeProvider interface{}/* bool | SelectionRangeOptions | SelectionRangeRegistrationOptions*/ `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 interface{}/* bool | CallHierarchyOptions | CallHierarchyRegistrationOptions*/ `json:"callHierarchyProvider,omitempty"`
- /**
- * The server provides linked editing range support.
- *
- * @since 3.16.0
- */
- LinkedEditingRangeProvider interface{}/* bool | LinkedEditingRangeOptions | LinkedEditingRangeRegistrationOptions*/ `json:"linkedEditingRangeProvider,omitempty"`
- /**
- * The server provides semantic tokens support.
- *
- * @since 3.16.0
- */
- SemanticTokensProvider interface{}/*SemanticTokensOptions | SemanticTokensRegistrationOptions*/ `json:"semanticTokensProvider,omitempty"`
- /**
- * The workspace server capabilities
- */
- Workspace Workspace6Gn `json:"workspace,omitempty"`
- /**
- * The server provides moniker support.
- *
- * @since 3.16.0
- */
- MonikerProvider interface{}/* bool | MonikerOptions | MonikerRegistrationOptions*/ `json:"monikerProvider,omitempty"`
- /**
- * The server provides type hierarchy support.
- *
- * @since 3.17.0 - proposed state
- */
- TypeHierarchyProvider interface{}/* bool | TypeHierarchyOptions | TypeHierarchyRegistrationOptions*/ `json:"typeHierarchyProvider,omitempty"`
- /**
- * The server provides inline values.
- *
- * @since 3.17.0 - proposed state
- */
- InlineValueProvider interface{}/* bool | InlineValueOptions | InlineValueRegistrationOptions*/ `json:"inlineValueProvider,omitempty"`
- /**
- * The server provides inlay hints.
- *
- * @since 3.17.0 - proposed state
- */
- InlayHintProvider interface{}/* bool | InlayHintOptions | InlayHintRegistrationOptions*/ `json:"inlayHintProvider,omitempty"`
- /**
- * Experimental server capabilities.
- */
- Experimental interface{} `json:"experimental,omitempty"`
-}
-
-type SetTraceParams struct {
- Value TraceValues `json:"value"`
-}
-
-/**
- * Client capabilities for the show document request.
- *
- * @since 3.16.0
- */
-type ShowDocumentClientCapabilities struct {
- /**
- * The client has support for the show document
- * request.
- */
- Support bool `json:"support"`
-}
-
-/**
- * Params to show a document.
- *
- * @since 3.16.0
- */
-type ShowDocumentParams struct {
- /**
- * The document 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 in 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 an show document request.
- *
- * @since 3.16.0
- */
-type ShowDocumentResult struct {
- /**
- * A boolean indicating if the show was successful.
- */
- Success bool `json:"success"`
-}
-
-/**
- * The parameters of a notification message.
- */
-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
- */
-type ShowMessageRequestClientCapabilities struct {
- /**
- * Capabilities specific to the `MessageActionItem` type.
- */
- MessageActionItem 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"`
- } `json:"messageActionItem,omitempty"`
-}
-
-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.
- */
-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 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 to better express the active parameter if the
- * active signature does have any.
- */
- ActiveParameter uint32 `json:"activeParameter,omitempty"`
-}
-
-/**
- * Client Capabilities for a [SignatureHelpRequest](#SignatureHelpRequest).
- */
-type SignatureHelpClientCapabilities struct {
- /**
- * Whether signature help supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
- /**
- * The client supports the following `SignatureInformation`
- * specific properties.
- */
- SignatureInformation struct {
- /**
- * Client supports the follow 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 struct {
- /**
- * The client supports processing label offsets instead of a
- * simple label string.
- *
- * @since 3.14.0
- */
- LabelOffsetSupport bool `json:"labelOffsetSupport,omitempty"`
- } `json:"parameterInformation,omitempty"`
- /**
- * The client support the `activeParameter` property on `SignatureInformation`
- * literal.
- *
- * @since 3.16.0
- */
- ActiveParameterSupport bool `json:"activeParameterSupport,omitempty"`
- } `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
- */
-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.
- *
- * Retrigger 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 [SignatureHelpRequest](#SignatureHelpRequest).
- */
-type SignatureHelpOptions struct {
- /**
- * List of characters that trigger signature help.
- */
- 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 [SignatureHelpRequest](#SignatureHelpRequest).
- */
-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
-}
-
-/**
- * How a signature help was triggered.
- *
- * @since 3.15.0
- */
-type SignatureHelpTriggerKind float64
-
-/**
- * Represents the signature of something callable. A signature
- * can have a label, like a function-name, a doc-comment, and
- * a set of parameters.
- */
-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 string/*string | MarkupContent*/ `json:"documentation,omitempty"`
- /**
- * The parameters of this signature.
- */
- Parameters []ParameterInformation `json:"parameters,omitempty"`
- /**
- * The index of the active parameter.
- *
- * If provided, this is used in place of `SignatureHelp.activeParameter`.
- *
- * @since 3.16.0
- */
- ActiveParameter uint32 `json:"activeParameter,omitempty"`
-}
-
-/**
- * Static registration options to be returned in the initialize
- * request.
- */
-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"`
-}
-
-/**
- * Represents information about programming constructs like variables, classes,
- * interfaces etc.
- */
-type SymbolInformation struct {
- /**
- * The name of this symbol.
- */
- Name string `json:"name"`
- /**
- * The kind of this symbol.
- */
- Kind SymbolKind `json:"kind"`
- /**
- * Tags for this completion item.
- *
- * @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 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 thinks like visibility modifiers.
- *
- * The range doesn't have to denote a node range in the sense of a abstract
- * syntax tree. It can therefore not be used to re-construct a hierarchy of
- * the symbols.
- */
- Location Location `json:"location"`
- /**
- * 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 float64
-
-/**
- * Symbol tags are extra annotations that tweak the rendering of a symbol.
- * @since 3.16
- */
-type SymbolTag float64
-
-/**
- * Text document specific client capabilities.
- */
-type TextDocumentClientCapabilities struct {
- /**
- * Defines which synchronization capabilities the client supports.
- */
- Synchronization TextDocumentSyncClientCapabilities `json:"synchronization,omitempty"`
- /**
- * Capabilities specific to the `textDocument/completion`
- */
- Completion CompletionClientCapabilities `json:"completion,omitempty"`
- /**
- * Capabilities specific to the `textDocument/hover`
- */
- Hover HoverClientCapabilities `json:"hover,omitempty"`
- /**
- * Capabilities specific to the `textDocument/signatureHelp`
- */
- SignatureHelp SignatureHelpClientCapabilities `json:"signatureHelp,omitempty"`
- /**
- * Capabilities specific to the `textDocument/declaration`
- *
- * @since 3.14.0
- */
- Declaration DeclarationClientCapabilities `json:"declaration,omitempty"`
- /**
- * Capabilities specific to the `textDocument/definition`
- */
- Definition DefinitionClientCapabilities `json:"definition,omitempty"`
- /**
- * Capabilities specific to the `textDocument/typeDefinition`
- *
- * @since 3.6.0
- */
- TypeDefinition TypeDefinitionClientCapabilities `json:"typeDefinition,omitempty"`
- /**
- * Capabilities specific to the `textDocument/implementation`
- *
- * @since 3.6.0
- */
- Implementation ImplementationClientCapabilities `json:"implementation,omitempty"`
- /**
- * Capabilities specific to the `textDocument/references`
- */
- References ReferenceClientCapabilities `json:"references,omitempty"`
- /**
- * Capabilities specific to the `textDocument/documentHighlight`
- */
- DocumentHighlight DocumentHighlightClientCapabilities `json:"documentHighlight,omitempty"`
- /**
- * Capabilities specific to the `textDocument/documentSymbol`
- */
- DocumentSymbol DocumentSymbolClientCapabilities `json:"documentSymbol,omitempty"`
- /**
- * Capabilities specific to the `textDocument/codeAction`
- */
- CodeAction CodeActionClientCapabilities `json:"codeAction,omitempty"`
- /**
- * Capabilities specific to the `textDocument/codeLens`
- */
- CodeLens CodeLensClientCapabilities `json:"codeLens,omitempty"`
- /**
- * Capabilities specific to the `textDocument/documentLink`
- */
- DocumentLink DocumentLinkClientCapabilities `json:"documentLink,omitempty"`
- /**
- * Capabilities specific to the `textDocument/documentColor`
- */
- ColorProvider DocumentColorClientCapabilities `json:"colorProvider,omitempty"`
- /**
- * Capabilities specific to the `textDocument/formatting`
- */
- Formatting DocumentFormattingClientCapabilities `json:"formatting,omitempty"`
- /**
- * Capabilities specific to the `textDocument/rangeFormatting`
- */
- RangeFormatting DocumentRangeFormattingClientCapabilities `json:"rangeFormatting,omitempty"`
- /**
- * Capabilities specific to the `textDocument/onTypeFormatting`
- */
- OnTypeFormatting DocumentOnTypeFormattingClientCapabilities `json:"onTypeFormatting,omitempty"`
- /**
- * Capabilities specific to the `textDocument/rename`
- */
- Rename RenameClientCapabilities `json:"rename,omitempty"`
- /**
- * Capabilities specific to `textDocument/foldingRange` request.
- *
- * @since 3.10.0
- */
- FoldingRange FoldingRangeClientCapabilities `json:"foldingRange,omitempty"`
- /**
- * Capabilities specific to `textDocument/selectionRange` request.
- *
- * @since 3.15.0
- */
- SelectionRange SelectionRangeClientCapabilities `json:"selectionRange,omitempty"`
- /**
- * Capabilities specific to `textDocument/publishDiagnostics` notification.
- */
- PublishDiagnostics PublishDiagnosticsClientCapabilities `json:"publishDiagnostics,omitempty"`
- /**
- * Capabilities specific to the various call hierarchy request.
- *
- * @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 linked editing range request.
- *
- * @since 3.16.0
- */
- LinkedEditingRange LinkedEditingRangeClientCapabilities `json:"linkedEditingRange,omitempty"`
- /**
- * Client capabilities specific to the moniker request.
- *
- * @since 3.16.0
- */
- Moniker MonikerClientCapabilities `json:"moniker,omitempty"`
- /**
- * Capabilities specific to the various type hierarchy requests.
- *
- * @since 3.17.0 - proposed state
- */
- TypeHierarchy TypeHierarchyClientCapabilities `json:"typeHierarchy,omitempty"`
- /**
- * Capabilities specific to the `textDocument/inlineValue` request.
- *
- * @since 3.17.0 - proposed state
- */
- InlineValue InlineValueClientCapabilities `json:"inlineValue,omitempty"`
- /**
- * Capabilities specific to the `textDocument/inlayHint` request.
- *
- * @since 3.17.0 - proposed state
- */
- InlayHint InlayHintClientCapabilities `json:"inlayHint,omitempty"`
-}
-
-/**
- * An event describing a change to a text document. If range and rangeLength are omitted
- * the new text is considered to be the full content of the document.
- */
-type TextDocumentContentChangeEvent = 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"`
-}
-
-/**
- * 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.
- */
-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 []TextEdit/*TextEdit | AnnotatedTextEdit*/ `json:"edits"`
-}
-
-/**
- * A document filter denotes a document by different properties like
- * the [language](#TextDocument.languageId), the [scheme](#Uri.scheme) of
- * its resource, or a glob-pattern that is applied to the [path](#TextDocument.fileName).
- *
- * 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 - proposed state.
- */
-type TextDocumentFilter = struct {
- /** A language id, like `typescript`. */
- Language string `json:"language"`
- /** A Uri [scheme](#Uri.scheme), like `file` or `untitled`. */
- Scheme string `json:"scheme,omitempty"`
- /** A glob pattern, like `*.{ts,js}`. */
- Pattern string `json:"pattern,omitempty"`
-}
-
-/**
- * A literal to identify a text document in the client.
- */
-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.
- */
-type TextDocumentItem struct {
- /**
- * The text document's uri.
- */
- URI DocumentURI `json:"uri"`
- /**
- * The text document's language identifier
- */
- LanguageID string `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.
- */
-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.
- */
-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 /*DocumentSelector | null*/ `json:"documentSelector"`
-}
-
-/**
- * Represents reasons why a text document is saved.
- */
-type TextDocumentSaveReason float64
-
-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 float64
-
-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/*boolean | SaveOptions*/ `json:"save,omitempty"`
-}
-
-/**
- * A text edit applicable to a text document.
- */
-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 TraceValues = string /* 'off' | 'messages' | 'compact' | 'verbose' */
-
-/**
- * Since 3.6.0
- */
-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"`
-}
-
-type TypeDefinitionOptions struct {
- WorkDoneProgressOptions
-}
-
-type TypeDefinitionParams struct {
- TextDocumentPositionParams
- WorkDoneProgressParams
- PartialResultParams
-}
-
-type TypeDefinitionRegistrationOptions struct {
- TextDocumentRegistrationOptions
- TypeDefinitionOptions
- StaticRegistrationOptions
-}
-
-/**
- * @since 3.17.0 - proposed state
- */
-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 - proposed state
- */
-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
- * [`range`](#TypeHierarchyItem.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 LSPAny `json:"data,omitempty"`
-}
-
-/**
- * Type hierarchy options used during static registration.
- *
- * @since 3.17.0 - proposed state
- */
-type TypeHierarchyOptions = WorkDoneProgressOptions
-
-/**
- * The parameter of a `textDocument/prepareTypeHierarchy` request.
- *
- * @since 3.17.0 - proposed state
- */
-type TypeHierarchyPrepareParams struct {
- /**
- * The text document.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- /**
- * The position inside the text document.
- */
- Position Position `json:"position"`
- /**
- * An optional token that a server can use to report work done progress.
- */
- WorkDoneToken ProgressToken `json:"workDoneToken,omitempty"`
-}
-
-/**
- * Type hierarchy options used during static or dynamic registration.
- *
- * @since 3.17.0 - proposed state
- */
-type TypeHierarchyRegistrationOptions 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/*DocumentSelector | null*/ `json:"documentSelector"`
- /**
- * 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"`
-}
-
-/**
- * The parameter of a `typeHierarchy/subtypes` request.
- *
- * @since 3.17.0 - proposed state
- */
-type TypeHierarchySubtypesParams struct {
- /**
- * An optional token that a server can use to report work done progress.
- */
- WorkDoneToken ProgressToken `json:"workDoneToken,omitempty"`
- /**
- * An optional token that a server can use to report partial results (e.g. streaming) to
- * the client.
- */
- PartialResultToken ProgressToken `json:"partialResultToken,omitempty"`
- Item TypeHierarchyItem `json:"item"`
-}
-
-/**
- * The parameter of a `typeHierarchy/supertypes` request.
- *
- * @since 3.17.0 - proposed state
- */
-type TypeHierarchySupertypesParams struct {
- /**
- * An optional token that a server can use to report work done progress.
- */
- WorkDoneToken ProgressToken `json:"workDoneToken,omitempty"`
- /**
- * An optional token that a server can use to report partial results (e.g. streaming) to
- * the client.
- */
- PartialResultToken ProgressToken `json:"partialResultToken,omitempty"`
- Item TypeHierarchyItem `json:"item"`
-}
-
-/**
- * A tagging type for string properties that are actually URIs
- *
- * @since 3.16.0
- */
-type URI = string
-
-/**
- * A diagnostic report indicating that the last returned
- * report is still accurate.
- *
- * @since 3.17.0 - proposed state
- */
-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.
- */
-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"`
-}
-
-type UnregistrationParams struct {
- Unregisterations []Unregistration `json:"unregisterations"`
-}
-
-/**
- * A versioned notebook document identifier.
- *
- * @since 3.17.0 - proposed state
- */
-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.
- */
-type VersionedTextDocumentIdentifier struct {
- /**
- * The version number of this document.
- */
- Version int32 `json:"version"`
- TextDocumentIdentifier
-}
-
-type WatchKind float64
-
-/**
- * The parameters send in a will save text document notification.
- */
-type WillSaveTextDocumentParams struct {
- /**
- * The document that will be saved.
- */
- TextDocument TextDocumentIdentifier `json:"textDocument"`
- /**
- * The 'TextDocumentSaveReason'.
- */
- Reason TextDocumentSaveReason `json:"reason"`
-}
-
-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"`
-}
-
-type WorkDoneProgressCancelParams struct {
- /**
- * The token to be used to report progress.
- */
- Token ProgressToken `json:"token"`
-}
-
-type WorkDoneProgressClientCapabilities struct {
- /**
- * Window specific client capabilities.
- */
- Window struct {
- /**
- * Whether client supports server initiated progress using the
- * `window/workDoneProgress/create` request.
- *
- * 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"`
- } `json:"window,omitempty"`
-}
-
-type WorkDoneProgressCreateParams struct {
- /**
- * The token to be used to report progress.
- */
- Token ProgressToken `json:"token"`
-}
-
-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"`
-}
-
-type WorkDoneProgressOptions struct {
- WorkDoneProgress bool `json:"workDoneProgress,omitempty"`
-}
-
-type WorkDoneProgressParams struct {
- /**
- * An optional token that a server can use to report work done progress.
- */
- WorkDoneToken ProgressToken `json:"workDoneToken,omitempty"`
-}
-
-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.
- */
-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"`
- /**
- * 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 hints requests scoped to the
- * workspace.
- *
- * @since 3.17.0.
- */
- InlayHint InlayHintWorkspaceClientCapabilities `json:"inlayHint,omitempty"`
-}
-
-/**
- * Parameters of the workspace diagnostic request.
- *
- * @since 3.17.0 - proposed state
- */
-type WorkspaceDiagnosticParams struct {
- /**
- * An optional token that a server can use to report work done progress.
- */
- WorkDoneToken ProgressToken `json:"workDoneToken,omitempty"`
- /**
- * An optional token that a server can use to report partial results (e.g. streaming) to
- * the client.
- */
- PartialResultToken ProgressToken `json:"partialResultToken,omitempty"`
- /**
- * 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"`
-}
-
-/**
- * A workspace diagnostic report.
- *
- * @since 3.17.0 - proposed state
- */
-type WorkspaceDiagnosticReport = struct {
- Items []WorkspaceDocumentDiagnosticReport `json:"items"`
-}
-
-/**
- * A workspace diagnostic document report.
- *
- * @since 3.17.0 - proposed state
- */
-type WorkspaceDocumentDiagnosticReport = interface{} /*WorkspaceFullDocumentDiagnosticReport | WorkspaceUnchangedDocumentDiagnosticReport*/
-
-/**
- * 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`
- */
-type WorkspaceEdit struct {
- /**
- * Holds changes to existing resources.
- */
- Changes map[DocumentURI][]TextEdit/*[uri: 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 []TextDocumentEdit/*TextDocumentEdit | CreateFile | RenameFile | DeleteFile*/ `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[string]ChangeAnnotationIdentifier/*[id: ChangeAnnotationIdentifier]: ChangeAnnotation;*/ `json:"changeAnnotations,omitempty"`
-}
-
-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 containing to the client specific 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 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"`
- } `json:"changeAnnotationSupport,omitempty"`
-}
-
-type WorkspaceFolder struct {
- /**
- * The associated URI for this workspace folder.
- */
- URI string `json:"uri"`
- /**
- * The name of the workspace folder. Used to refer to this
- * workspace folder in the user interface.
- */
- Name string `json:"name"`
-}
-
-/**
- * The workspace folder change event.
- */
-type WorkspaceFoldersChangeEvent struct {
- /**
- * The array of added workspace folders
- */
- Added []WorkspaceFolder `json:"added"`
- /**
- * The array of the removed workspace folders
- */
- Removed []WorkspaceFolder `json:"removed"`
-}
-
-type WorkspaceFoldersClientCapabilities struct {
- /**
- * The workspace client capabilities
- */
- Workspace Workspace7Gn `json:"workspace,omitempty"`
-}
-
-type WorkspaceFoldersInitializeParams struct {
- /**
- * The actual configured workspace folders.
- */
- WorkspaceFolders []WorkspaceFolder /*WorkspaceFolder[] | null*/ `json:"workspaceFolders"`
-}
-
-type WorkspaceFoldersServerCapabilities struct {
- /**
- * The workspace server capabilities
- */
- Workspace Workspace9Gn `json:"workspace,omitempty"`
-}
-
-/**
- * A full document diagnostic report for a workspace diagnostic result.
- *
- * @since 3.17.0 - proposed state
- */
-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/*integer | null*/ `json:"version"`
-}
-
-/**
- * A special workspace symbol that supports locations without a range
- *
- * @since 3.17.0 - proposed state
- */
-type WorkspaceSymbol struct {
- /**
- * The location of the symbol.
- *
- * See SymbolInformation#location for more details.
- */
- Location Location/*Location | { uri: DocumentUri }*/ `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 LSPAny `json:"data,omitempty"`
-}
-
-/**
- * Client capabilities for a [WorkspaceSymbolRequest](#WorkspaceSymbolRequest).
- */
-type WorkspaceSymbolClientCapabilities struct {
- /**
- * Symbol request supports dynamic registration.
- */
- DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
- /**
- * Specific capabilities for the `SymbolKind` in the `workspace/symbol` request.
- */
- SymbolKind 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"`
- } `json:"symbolKind,omitempty"`
- /**
- * The client supports tags on `SymbolInformation`.
- * Clients supporting tags have to handle unknown tags gracefully.
- *
- * @since 3.16.0
- */
- TagSupport struct {
- /**
- * The tags supported by the client.
- */
- ValueSet []SymbolTag `json:"valueSet"`
- } `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 - proposedState
- */
- ResolveSupport struct {
- /**
- * The properties that a client can resolve lazily. Usually
- * `location.range`
- */
- Properties []string `json:"properties"`
- } `json:"resolveSupport,omitempty"`
-}
-
-/**
- * Server capabilities for a [WorkspaceSymbolRequest](#WorkspaceSymbolRequest).
- */
-type WorkspaceSymbolOptions struct {
- /**
- * The server provides support to resolve additional
- * information for a workspace symbol.
- *
- * @since 3.17.0 - proposed state
- */
- ResolveProvider bool `json:"resolveProvider,omitempty"`
- WorkDoneProgressOptions
-}
-
-/**
- * The parameters of a [WorkspaceSymbolRequest](#WorkspaceSymbolRequest).
- */
-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
-}
-
-/**
- * An unchanged document diagnostic report for a workspace diagnostic result.
- *
- * @since 3.17.0 - proposed state
- */
-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/*integer | null*/ `json:"version"`
-}
-
-const (
- /**
- * 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 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"
- /**
- * 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
- 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
- /**
- * Render a completion as obsolete, usually using a strike-out.
- */
-
- ComplDeprecated CompletionItemTag = 1
- /**
- * 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
- /**
- * Reports an error.
- */
-
- SeverityError DiagnosticSeverity = 1
- /**
- * Reports a warning.
- */
-
- SeverityWarning DiagnosticSeverity = 2
- /**
- * Reports an information.
- */
-
- SeverityInformation DiagnosticSeverity = 3
- /**
- * Reports a hint.
- */
-
- SeverityHint DiagnosticSeverity = 4
- /**
- * 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
- /**
- * 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
- /**
- * 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 got created.
- */
-
- Created FileChangeType = 1
- /**
- * The file got changed.
- */
-
- Changed FileChangeType = 2
- /**
- * The file got deleted.
- */
-
- Deleted FileChangeType = 3
- /**
- * The pattern matches a file only.
- */
-
- FileOp FileOperationPatternKind = "file"
- /**
- * The pattern matches a folder only.
- */
-
- FolderOp FileOperationPatternKind = "folder"
- /**
- * Folding range for a comment
- */
- Comment FoldingRangeKind = "comment"
- /**
- * Folding range for a imports or includes
- */
- Imports FoldingRangeKind = "imports"
- /**
- * Folding range for a region (e.g. `#region`)
- */
- Region FoldingRangeKind = "region"
- /**
- * If the protocol version provided by the client can't be handled by the server.
- * @deprecated This initialize error got replaced by client capabilities. There is
- * no version handshake in version 3.0x
- */
-
- UnknownProtocolVersion InitializeError = 1
- /**
- * An inlay hint that for a type annotation.
- */
-
- Type InlayHintKind = 1
- /**
- * An inlay hint that is for a parameter.
- */
-
- Parameter InlayHintKind = 2
- /**
- * 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
- /**
- * 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><cursor><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
- /**
- * Plain text is supported as a content format
- */
-
- PlainText MarkupKind = "plaintext"
- /**
- * Markdown is supported as a content format
- */
-
- Markdown MarkupKind = "markdown"
- /**
- * An error message.
- */
-
- Error MessageType = 1
- /**
- * A warning message.
- */
-
- Warning MessageType = 2
- /**
- * An information message.
- */
-
- Info MessageType = 3
- /**
- * A log message.
- */
-
- Log MessageType = 4
- /**
- * 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 markup-cell is formatted source that is used for display.
- */
-
- Markup NotebookCellKind = 1
- /**
- * A code-cell is source code.
- */
-
- Code NotebookCellKind = 2
- /**
- * 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"
- /**
- * 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
- 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
- /**
- * Render a symbol as obsolete, usually using a strike-out.
- */
-
- DeprecatedSymbol SymbolTag = 1
- /**
- * 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
- /**
- * 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
- /**
- * 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
-)
-
-// Types created to name formal parameters and embedded structs
-type ParamConfiguration struct {
- ConfigurationParams
- PartialResultParams
-}
-type ParamInitialize struct {
- InitializeParams
- WorkDoneProgressParams
-}
-type PrepareRename2Gn struct {
- Range Range `json:"range"`
- Placeholder string `json:"placeholder"`
-}
-type Workspace3Gn 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"`
-
- /**
- * 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 hints requests scoped to the
- * workspace.
- *
- * @since 3.17.0.
- */
- InlayHint InlayHintWorkspaceClientCapabilities `json:"inlayHint,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"`
-}
-type Workspace4Gn 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"`
-
- /**
- * 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 hints requests scoped to the
- * workspace.
- *
- * @since 3.17.0.
- */
- InlayHint InlayHintWorkspaceClientCapabilities `json:"inlayHint,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"`
-}
-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 strings is provided the string is treated as a 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/*string | boolean*/ `json:"changeNotifications,omitempty"`
-}
-type Workspace6Gn struct {
- /**
- * The server is interested in notifications/requests for operations on files.
- *
- * @since 3.16.0
- */
- FileOperations *FileOperationOptions `json:"fileOperations,omitempty"`
-
- WorkspaceFolders WorkspaceFolders5Gn `json:"workspaceFolders,omitempty"`
-}
-type Workspace7Gn 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"`
-
- /**
- * 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 hints requests scoped to the
- * workspace.
- *
- * @since 3.17.0.
- */
- InlayHint InlayHintWorkspaceClientCapabilities `json:"inlayHint,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"`
-}
-type WorkspaceFolders8Gn 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 strings is provided the string is treated as a 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/*string | boolean*/ `json:"changeNotifications,omitempty"`
-}
-type Workspace9Gn struct {
- /**
- * The server is interested in notifications/requests for operations on files.
- *
- * @since 3.16.0
- */
- FileOperations *FileOperationOptions `json:"fileOperations,omitempty"`
-
- WorkspaceFolders WorkspaceFolders8Gn `json:"workspaceFolders,omitempty"`
-}
diff --git a/internal/lsp/protocol/tsserver.go b/internal/lsp/protocol/tsserver.go
deleted file mode 100644
index db345b3e4..000000000
--- a/internal/lsp/protocol/tsserver.go
+++ /dev/null
@@ -1,1143 +0,0 @@
-// 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.
-
-// Code generated (see typescript/README.md) DO NOT EDIT.
-
-package protocol
-
-// Package protocol contains data types and code for LSP json rpcs
-// generated automatically from vscode-languageserver-node
-// commit: 696f9285bf849b73745682fdb1c1feac73eb8772
-// last fetched Fri Mar 04 2022 14:48:10 GMT-0500 (Eastern Standard Time)
-
-import (
- "context"
- "encoding/json"
-
- "golang.org/x/tools/internal/jsonrpc2"
- errors "golang.org/x/xerrors"
-)
-
-type Server interface {
- DidChangeWorkspaceFolders(context.Context, *DidChangeWorkspaceFoldersParams) error
- WorkDoneProgressCancel(context.Context, *WorkDoneProgressCancelParams) error
- DidCreateFiles(context.Context, *CreateFilesParams) error
- DidRenameFiles(context.Context, *RenameFilesParams) error
- DidDeleteFiles(context.Context, *DeleteFilesParams) error
- Initialized(context.Context, *InitializedParams) error
- Exit(context.Context) error
- DidChangeConfiguration(context.Context, *DidChangeConfigurationParams) error
- DidOpen(context.Context, *DidOpenTextDocumentParams) error
- DidChange(context.Context, *DidChangeTextDocumentParams) error
- DidClose(context.Context, *DidCloseTextDocumentParams) error
- DidSave(context.Context, *DidSaveTextDocumentParams) error
- WillSave(context.Context, *WillSaveTextDocumentParams) error
- DidChangeWatchedFiles(context.Context, *DidChangeWatchedFilesParams) error
- DidOpenNotebookDocument(context.Context, *DidOpenNotebookDocumentParams) error
- DidChangeNotebookDocument(context.Context, *DidChangeNotebookDocumentParams) error
- DidSaveNotebookDocument(context.Context, *DidSaveNotebookDocumentParams) error
- DidCloseNotebookDocument(context.Context, *DidCloseNotebookDocumentParams) error
- SetTrace(context.Context, *SetTraceParams) error
- LogTrace(context.Context, *LogTraceParams) error
- Implementation(context.Context, *ImplementationParams) (Definition /*Definition | DefinitionLink[] | null*/, error)
- TypeDefinition(context.Context, *TypeDefinitionParams) (Definition /*Definition | DefinitionLink[] | null*/, error)
- DocumentColor(context.Context, *DocumentColorParams) ([]ColorInformation, error)
- ColorPresentation(context.Context, *ColorPresentationParams) ([]ColorPresentation, error)
- FoldingRange(context.Context, *FoldingRangeParams) ([]FoldingRange /*FoldingRange[] | null*/, error)
- Declaration(context.Context, *DeclarationParams) (Declaration /*Declaration | DeclarationLink[] | null*/, error)
- SelectionRange(context.Context, *SelectionRangeParams) ([]SelectionRange /*SelectionRange[] | null*/, error)
- PrepareCallHierarchy(context.Context, *CallHierarchyPrepareParams) ([]CallHierarchyItem /*CallHierarchyItem[] | null*/, error)
- IncomingCalls(context.Context, *CallHierarchyIncomingCallsParams) ([]CallHierarchyIncomingCall /*CallHierarchyIncomingCall[] | null*/, error)
- OutgoingCalls(context.Context, *CallHierarchyOutgoingCallsParams) ([]CallHierarchyOutgoingCall /*CallHierarchyOutgoingCall[] | null*/, error)
- SemanticTokensFull(context.Context, *SemanticTokensParams) (*SemanticTokens /*SemanticTokens | null*/, error)
- SemanticTokensFullDelta(context.Context, *SemanticTokensDeltaParams) (interface{} /* SemanticTokens | SemanticTokensDelta | float64*/, error)
- SemanticTokensRange(context.Context, *SemanticTokensRangeParams) (*SemanticTokens /*SemanticTokens | null*/, error)
- SemanticTokensRefresh(context.Context) error
- LinkedEditingRange(context.Context, *LinkedEditingRangeParams) (*LinkedEditingRanges /*LinkedEditingRanges | null*/, error)
- WillCreateFiles(context.Context, *CreateFilesParams) (*WorkspaceEdit /*WorkspaceEdit | null*/, error)
- WillRenameFiles(context.Context, *RenameFilesParams) (*WorkspaceEdit /*WorkspaceEdit | null*/, error)
- WillDeleteFiles(context.Context, *DeleteFilesParams) (*WorkspaceEdit /*WorkspaceEdit | null*/, error)
- Moniker(context.Context, *MonikerParams) ([]Moniker /*Moniker[] | null*/, error)
- PrepareTypeHierarchy(context.Context, *TypeHierarchyPrepareParams) ([]TypeHierarchyItem /*TypeHierarchyItem[] | null*/, error)
- Supertypes(context.Context, *TypeHierarchySupertypesParams) ([]TypeHierarchyItem /*TypeHierarchyItem[] | null*/, error)
- Subtypes(context.Context, *TypeHierarchySubtypesParams) ([]TypeHierarchyItem /*TypeHierarchyItem[] | null*/, error)
- InlineValue(context.Context, *InlineValueParams) ([]InlineValue /*InlineValue[] | null*/, error)
- InlineValueRefresh(context.Context) error
- InlayHint(context.Context, *InlayHintParams) ([]InlayHint /*InlayHint[] | null*/, error)
- Resolve(context.Context, *InlayHint) (*InlayHint, error)
- InlayHintRefresh(context.Context) error
- Initialize(context.Context, *ParamInitialize) (*InitializeResult, error)
- Shutdown(context.Context) error
- WillSaveWaitUntil(context.Context, *WillSaveTextDocumentParams) ([]TextEdit /*TextEdit[] | null*/, error)
- Completion(context.Context, *CompletionParams) (*CompletionList /*CompletionItem[] | CompletionList | null*/, error)
- ResolveCompletionItem(context.Context, *CompletionItem) (*CompletionItem, error)
- Hover(context.Context, *HoverParams) (*Hover /*Hover | null*/, error)
- SignatureHelp(context.Context, *SignatureHelpParams) (*SignatureHelp /*SignatureHelp | null*/, error)
- Definition(context.Context, *DefinitionParams) (Definition /*Definition | DefinitionLink[] | null*/, error)
- References(context.Context, *ReferenceParams) ([]Location /*Location[] | null*/, error)
- DocumentHighlight(context.Context, *DocumentHighlightParams) ([]DocumentHighlight /*DocumentHighlight[] | null*/, error)
- DocumentSymbol(context.Context, *DocumentSymbolParams) ([]interface{} /*SymbolInformation[] | DocumentSymbol[] | null*/, error)
- CodeAction(context.Context, *CodeActionParams) ([]CodeAction /*(Command | CodeAction)[] | null*/, error)
- ResolveCodeAction(context.Context, *CodeAction) (*CodeAction, error)
- Symbol(context.Context, *WorkspaceSymbolParams) ([]SymbolInformation /*SymbolInformation[] | WorkspaceSymbol[] | null*/, error)
- ResolveWorkspaceSymbol(context.Context, *WorkspaceSymbol) (*WorkspaceSymbol, error)
- CodeLens(context.Context, *CodeLensParams) ([]CodeLens /*CodeLens[] | null*/, error)
- ResolveCodeLens(context.Context, *CodeLens) (*CodeLens, error)
- CodeLensRefresh(context.Context) error
- DocumentLink(context.Context, *DocumentLinkParams) ([]DocumentLink /*DocumentLink[] | null*/, error)
- ResolveDocumentLink(context.Context, *DocumentLink) (*DocumentLink, error)
- Formatting(context.Context, *DocumentFormattingParams) ([]TextEdit /*TextEdit[] | null*/, error)
- RangeFormatting(context.Context, *DocumentRangeFormattingParams) ([]TextEdit /*TextEdit[] | null*/, error)
- OnTypeFormatting(context.Context, *DocumentOnTypeFormattingParams) ([]TextEdit /*TextEdit[] | null*/, error)
- Rename(context.Context, *RenameParams) (*WorkspaceEdit /*WorkspaceEdit | null*/, error)
- PrepareRename(context.Context, *PrepareRenameParams) (*PrepareRename2Gn /*Range | { range: Range; placeholder: string } | { defaultBehavior: boolean } | null*/, error)
- ExecuteCommand(context.Context, *ExecuteCommandParams) (interface{} /* LSPAny | void | float64*/, error)
- Diagnostic(context.Context, *string) (*string, error)
- DiagnosticWorkspace(context.Context, *WorkspaceDiagnosticParams) (*WorkspaceDiagnosticReport, error)
- DiagnosticRefresh(context.Context) error
- NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error)
-}
-
-func serverDispatch(ctx context.Context, server Server, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) {
- switch r.Method() {
- case "workspace/didChangeWorkspaceFolders": // notif
- var params DidChangeWorkspaceFoldersParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidChangeWorkspaceFolders(ctx, &params)
- return true, reply(ctx, nil, err)
- case "window/workDoneProgress/cancel": // notif
- var params WorkDoneProgressCancelParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.WorkDoneProgressCancel(ctx, &params)
- return true, reply(ctx, nil, err)
- case "workspace/didCreateFiles": // notif
- var params CreateFilesParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidCreateFiles(ctx, &params)
- return true, reply(ctx, nil, err)
- case "workspace/didRenameFiles": // notif
- var params RenameFilesParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidRenameFiles(ctx, &params)
- return true, reply(ctx, nil, err)
- case "workspace/didDeleteFiles": // notif
- var params DeleteFilesParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidDeleteFiles(ctx, &params)
- return true, reply(ctx, nil, err)
- case "initialized": // notif
- var params InitializedParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.Initialized(ctx, &params)
- return true, reply(ctx, nil, err)
- case "exit": // notif
- err := server.Exit(ctx)
- return true, reply(ctx, nil, err)
- case "workspace/didChangeConfiguration": // notif
- var params DidChangeConfigurationParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidChangeConfiguration(ctx, &params)
- return true, reply(ctx, nil, err)
- case "textDocument/didOpen": // notif
- var params DidOpenTextDocumentParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidOpen(ctx, &params)
- return true, reply(ctx, nil, err)
- case "textDocument/didChange": // notif
- var params DidChangeTextDocumentParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidChange(ctx, &params)
- return true, reply(ctx, nil, err)
- case "textDocument/didClose": // notif
- var params DidCloseTextDocumentParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidClose(ctx, &params)
- return true, reply(ctx, nil, err)
- case "textDocument/didSave": // notif
- var params DidSaveTextDocumentParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidSave(ctx, &params)
- return true, reply(ctx, nil, err)
- case "textDocument/willSave": // notif
- var params WillSaveTextDocumentParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.WillSave(ctx, &params)
- return true, reply(ctx, nil, err)
- case "workspace/didChangeWatchedFiles": // notif
- var params DidChangeWatchedFilesParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidChangeWatchedFiles(ctx, &params)
- return true, reply(ctx, nil, err)
- case "notebookDocument/didOpen": // notif
- var params DidOpenNotebookDocumentParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidOpenNotebookDocument(ctx, &params)
- return true, reply(ctx, nil, err)
- case "notebookDocument/didChange": // notif
- var params DidChangeNotebookDocumentParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidChangeNotebookDocument(ctx, &params)
- return true, reply(ctx, nil, err)
- case "notebookDocument/didSave": // notif
- var params DidSaveNotebookDocumentParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidSaveNotebookDocument(ctx, &params)
- return true, reply(ctx, nil, err)
- case "notebookDocument/didClose": // notif
- var params DidCloseNotebookDocumentParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.DidCloseNotebookDocument(ctx, &params)
- return true, reply(ctx, nil, err)
- case "$/setTrace": // notif
- var params SetTraceParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.SetTrace(ctx, &params)
- return true, reply(ctx, nil, err)
- case "$/logTrace": // notif
- var params LogTraceParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err := server.LogTrace(ctx, &params)
- return true, reply(ctx, nil, err)
- case "textDocument/implementation": // req
- var params ImplementationParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Implementation(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/typeDefinition": // req
- var params TypeDefinitionParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.TypeDefinition(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/documentColor": // req
- var params DocumentColorParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.DocumentColor(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/colorPresentation": // req
- var params ColorPresentationParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.ColorPresentation(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/foldingRange": // req
- var params FoldingRangeParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.FoldingRange(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/declaration": // req
- var params DeclarationParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Declaration(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/selectionRange": // req
- var params SelectionRangeParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.SelectionRange(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/prepareCallHierarchy": // req
- var params CallHierarchyPrepareParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.PrepareCallHierarchy(ctx, &params)
- return true, reply(ctx, resp, err)
- case "callHierarchy/incomingCalls": // req
- var params CallHierarchyIncomingCallsParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.IncomingCalls(ctx, &params)
- return true, reply(ctx, resp, err)
- case "callHierarchy/outgoingCalls": // req
- var params CallHierarchyOutgoingCallsParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.OutgoingCalls(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/semanticTokens/full": // req
- var params SemanticTokensParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.SemanticTokensFull(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/semanticTokens/full/delta": // req
- var params SemanticTokensDeltaParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.SemanticTokensFullDelta(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/semanticTokens/range": // req
- var params SemanticTokensRangeParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.SemanticTokensRange(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspace/semanticTokens/refresh": // req
- if len(r.Params()) > 0 {
- return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
- }
- err := server.SemanticTokensRefresh(ctx)
- return true, reply(ctx, nil, err)
- case "textDocument/linkedEditingRange": // req
- var params LinkedEditingRangeParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.LinkedEditingRange(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspace/willCreateFiles": // req
- var params CreateFilesParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.WillCreateFiles(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspace/willRenameFiles": // req
- var params RenameFilesParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.WillRenameFiles(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspace/willDeleteFiles": // req
- var params DeleteFilesParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.WillDeleteFiles(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/moniker": // req
- var params MonikerParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Moniker(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/prepareTypeHierarchy": // req
- var params TypeHierarchyPrepareParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.PrepareTypeHierarchy(ctx, &params)
- return true, reply(ctx, resp, err)
- case "typeHierarchy/supertypes": // req
- var params TypeHierarchySupertypesParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Supertypes(ctx, &params)
- return true, reply(ctx, resp, err)
- case "typeHierarchy/subtypes": // req
- var params TypeHierarchySubtypesParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Subtypes(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/inlineValue": // req
- var params InlineValueParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.InlineValue(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspace/inlineValue/refresh": // req
- if len(r.Params()) > 0 {
- return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
- }
- err := server.InlineValueRefresh(ctx)
- return true, reply(ctx, nil, err)
- case "textDocument/inlayHint": // req
- var params InlayHintParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.InlayHint(ctx, &params)
- return true, reply(ctx, resp, err)
- case "inlayHint/resolve": // req
- var params InlayHint
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Resolve(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspace/inlayHint/refresh": // req
- if len(r.Params()) > 0 {
- return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
- }
- err := server.InlayHintRefresh(ctx)
- return true, reply(ctx, nil, err)
- case "initialize": // req
- var params ParamInitialize
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- if _, ok := err.(*json.UnmarshalTypeError); !ok {
- return true, sendParseError(ctx, reply, err)
- }
- }
- resp, err := server.Initialize(ctx, &params)
- return true, reply(ctx, resp, err)
- case "shutdown": // req
- if len(r.Params()) > 0 {
- return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
- }
- err := server.Shutdown(ctx)
- return true, reply(ctx, nil, err)
- case "textDocument/willSaveWaitUntil": // req
- var params WillSaveTextDocumentParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.WillSaveWaitUntil(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/completion": // req
- var params CompletionParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Completion(ctx, &params)
- return true, reply(ctx, resp, err)
- case "completionItem/resolve": // req
- var params CompletionItem
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.ResolveCompletionItem(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/hover": // req
- var params HoverParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Hover(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/signatureHelp": // req
- var params SignatureHelpParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.SignatureHelp(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/definition": // req
- var params DefinitionParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Definition(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/references": // req
- var params ReferenceParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.References(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/documentHighlight": // req
- var params DocumentHighlightParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.DocumentHighlight(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/documentSymbol": // req
- var params DocumentSymbolParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.DocumentSymbol(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/codeAction": // req
- var params CodeActionParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.CodeAction(ctx, &params)
- return true, reply(ctx, resp, err)
- case "codeAction/resolve": // req
- var params CodeAction
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.ResolveCodeAction(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspace/symbol": // req
- var params WorkspaceSymbolParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Symbol(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspaceSymbol/resolve": // req
- var params WorkspaceSymbol
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.ResolveWorkspaceSymbol(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/codeLens": // req
- var params CodeLensParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.CodeLens(ctx, &params)
- return true, reply(ctx, resp, err)
- case "codeLens/resolve": // req
- var params CodeLens
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.ResolveCodeLens(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspace/codeLens/refresh": // req
- if len(r.Params()) > 0 {
- return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
- }
- err := server.CodeLensRefresh(ctx)
- return true, reply(ctx, nil, err)
- case "textDocument/documentLink": // req
- var params DocumentLinkParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.DocumentLink(ctx, &params)
- return true, reply(ctx, resp, err)
- case "documentLink/resolve": // req
- var params DocumentLink
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.ResolveDocumentLink(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/formatting": // req
- var params DocumentFormattingParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Formatting(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/rangeFormatting": // req
- var params DocumentRangeFormattingParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.RangeFormatting(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/onTypeFormatting": // req
- var params DocumentOnTypeFormattingParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.OnTypeFormatting(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/rename": // req
- var params RenameParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Rename(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/prepareRename": // req
- var params PrepareRenameParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.PrepareRename(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspace/executeCommand": // req
- var params ExecuteCommandParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.ExecuteCommand(ctx, &params)
- return true, reply(ctx, resp, err)
- case "textDocument/diagnostic": // req
- var params string
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.Diagnostic(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspace/diagnostic": // req
- var params WorkspaceDiagnosticParams
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- resp, err := server.DiagnosticWorkspace(ctx, &params)
- return true, reply(ctx, resp, err)
- case "workspace/diagnostic/refresh": // req
- if len(r.Params()) > 0 {
- return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
- }
- err := server.DiagnosticRefresh(ctx)
- return true, reply(ctx, nil, err)
-
- default:
- return false, nil
- }
-}
-
-func (s *serverDispatcher) DidChangeWorkspaceFolders(ctx context.Context, params *DidChangeWorkspaceFoldersParams) error {
- return s.sender.Notify(ctx, "workspace/didChangeWorkspaceFolders", params)
-}
-
-func (s *serverDispatcher) WorkDoneProgressCancel(ctx context.Context, params *WorkDoneProgressCancelParams) error {
- return s.sender.Notify(ctx, "window/workDoneProgress/cancel", params)
-}
-
-func (s *serverDispatcher) DidCreateFiles(ctx context.Context, params *CreateFilesParams) error {
- return s.sender.Notify(ctx, "workspace/didCreateFiles", params)
-}
-
-func (s *serverDispatcher) DidRenameFiles(ctx context.Context, params *RenameFilesParams) error {
- return s.sender.Notify(ctx, "workspace/didRenameFiles", params)
-}
-
-func (s *serverDispatcher) DidDeleteFiles(ctx context.Context, params *DeleteFilesParams) error {
- return s.sender.Notify(ctx, "workspace/didDeleteFiles", params)
-}
-
-func (s *serverDispatcher) Initialized(ctx context.Context, params *InitializedParams) error {
- return s.sender.Notify(ctx, "initialized", params)
-}
-
-func (s *serverDispatcher) Exit(ctx context.Context) error {
- return s.sender.Notify(ctx, "exit", nil)
-}
-
-func (s *serverDispatcher) DidChangeConfiguration(ctx context.Context, params *DidChangeConfigurationParams) error {
- return s.sender.Notify(ctx, "workspace/didChangeConfiguration", params)
-}
-
-func (s *serverDispatcher) DidOpen(ctx context.Context, params *DidOpenTextDocumentParams) error {
- return s.sender.Notify(ctx, "textDocument/didOpen", params)
-}
-
-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) DidSave(ctx context.Context, params *DidSaveTextDocumentParams) error {
- return s.sender.Notify(ctx, "textDocument/didSave", params)
-}
-
-func (s *serverDispatcher) WillSave(ctx context.Context, params *WillSaveTextDocumentParams) error {
- return s.sender.Notify(ctx, "textDocument/willSave", params)
-}
-
-func (s *serverDispatcher) DidChangeWatchedFiles(ctx context.Context, params *DidChangeWatchedFilesParams) error {
- return s.sender.Notify(ctx, "workspace/didChangeWatchedFiles", params)
-}
-
-func (s *serverDispatcher) DidOpenNotebookDocument(ctx context.Context, params *DidOpenNotebookDocumentParams) error {
- return s.sender.Notify(ctx, "notebookDocument/didOpen", params)
-}
-
-func (s *serverDispatcher) DidChangeNotebookDocument(ctx context.Context, params *DidChangeNotebookDocumentParams) error {
- return s.sender.Notify(ctx, "notebookDocument/didChange", params)
-}
-
-func (s *serverDispatcher) DidSaveNotebookDocument(ctx context.Context, params *DidSaveNotebookDocumentParams) error {
- return s.sender.Notify(ctx, "notebookDocument/didSave", params)
-}
-
-func (s *serverDispatcher) DidCloseNotebookDocument(ctx context.Context, params *DidCloseNotebookDocumentParams) error {
- return s.sender.Notify(ctx, "notebookDocument/didClose", params)
-}
-
-func (s *serverDispatcher) SetTrace(ctx context.Context, params *SetTraceParams) error {
- return s.sender.Notify(ctx, "$/setTrace", params)
-}
-
-func (s *serverDispatcher) LogTrace(ctx context.Context, params *LogTraceParams) error {
- return s.sender.Notify(ctx, "$/logTrace", params)
-}
-func (s *serverDispatcher) Implementation(ctx context.Context, params *ImplementationParams) (Definition /*Definition | DefinitionLink[] | null*/, error) {
- var result Definition /*Definition | DefinitionLink[] | null*/
- if err := s.sender.Call(ctx, "textDocument/implementation", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) TypeDefinition(ctx context.Context, params *TypeDefinitionParams) (Definition /*Definition | DefinitionLink[] | null*/, error) {
- var result Definition /*Definition | DefinitionLink[] | null*/
- if err := s.sender.Call(ctx, "textDocument/typeDefinition", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-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) 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) FoldingRange(ctx context.Context, params *FoldingRangeParams) ([]FoldingRange /*FoldingRange[] | null*/, error) {
- var result []FoldingRange /*FoldingRange[] | null*/
- if err := s.sender.Call(ctx, "textDocument/foldingRange", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) Declaration(ctx context.Context, params *DeclarationParams) (Declaration /*Declaration | DeclarationLink[] | null*/, error) {
- var result Declaration /*Declaration | DeclarationLink[] | null*/
- if err := s.sender.Call(ctx, "textDocument/declaration", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) SelectionRange(ctx context.Context, params *SelectionRangeParams) ([]SelectionRange /*SelectionRange[] | null*/, error) {
- var result []SelectionRange /*SelectionRange[] | null*/
- if err := s.sender.Call(ctx, "textDocument/selectionRange", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) PrepareCallHierarchy(ctx context.Context, params *CallHierarchyPrepareParams) ([]CallHierarchyItem /*CallHierarchyItem[] | null*/, error) {
- var result []CallHierarchyItem /*CallHierarchyItem[] | null*/
- if err := s.sender.Call(ctx, "textDocument/prepareCallHierarchy", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) IncomingCalls(ctx context.Context, params *CallHierarchyIncomingCallsParams) ([]CallHierarchyIncomingCall /*CallHierarchyIncomingCall[] | null*/, error) {
- var result []CallHierarchyIncomingCall /*CallHierarchyIncomingCall[] | null*/
- 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 /*CallHierarchyOutgoingCall[] | null*/, error) {
- var result []CallHierarchyOutgoingCall /*CallHierarchyOutgoingCall[] | null*/
- if err := s.sender.Call(ctx, "callHierarchy/outgoingCalls", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) SemanticTokensFull(ctx context.Context, params *SemanticTokensParams) (*SemanticTokens /*SemanticTokens | null*/, error) {
- var result *SemanticTokens /*SemanticTokens | null*/
- 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{} /* SemanticTokens | SemanticTokensDelta | float64*/, error) {
- var result interface{} /* SemanticTokens | SemanticTokensDelta | float64*/
- 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 /*SemanticTokens | null*/, error) {
- var result *SemanticTokens /*SemanticTokens | null*/
- if err := s.sender.Call(ctx, "textDocument/semanticTokens/range", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) SemanticTokensRefresh(ctx context.Context) error {
- return s.sender.Call(ctx, "workspace/semanticTokens/refresh", nil, nil)
-}
-
-func (s *serverDispatcher) LinkedEditingRange(ctx context.Context, params *LinkedEditingRangeParams) (*LinkedEditingRanges /*LinkedEditingRanges | null*/, error) {
- var result *LinkedEditingRanges /*LinkedEditingRanges | null*/
- if err := s.sender.Call(ctx, "textDocument/linkedEditingRange", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) WillCreateFiles(ctx context.Context, params *CreateFilesParams) (*WorkspaceEdit /*WorkspaceEdit | null*/, error) {
- var result *WorkspaceEdit /*WorkspaceEdit | null*/
- if err := s.sender.Call(ctx, "workspace/willCreateFiles", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) WillRenameFiles(ctx context.Context, params *RenameFilesParams) (*WorkspaceEdit /*WorkspaceEdit | null*/, error) {
- var result *WorkspaceEdit /*WorkspaceEdit | null*/
- if err := s.sender.Call(ctx, "workspace/willRenameFiles", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) WillDeleteFiles(ctx context.Context, params *DeleteFilesParams) (*WorkspaceEdit /*WorkspaceEdit | null*/, error) {
- var result *WorkspaceEdit /*WorkspaceEdit | null*/
- if err := s.sender.Call(ctx, "workspace/willDeleteFiles", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) Moniker(ctx context.Context, params *MonikerParams) ([]Moniker /*Moniker[] | null*/, error) {
- var result []Moniker /*Moniker[] | null*/
- if err := s.sender.Call(ctx, "textDocument/moniker", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) PrepareTypeHierarchy(ctx context.Context, params *TypeHierarchyPrepareParams) ([]TypeHierarchyItem /*TypeHierarchyItem[] | null*/, error) {
- var result []TypeHierarchyItem /*TypeHierarchyItem[] | null*/
- if err := s.sender.Call(ctx, "textDocument/prepareTypeHierarchy", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) Supertypes(ctx context.Context, params *TypeHierarchySupertypesParams) ([]TypeHierarchyItem /*TypeHierarchyItem[] | null*/, error) {
- var result []TypeHierarchyItem /*TypeHierarchyItem[] | null*/
- if err := s.sender.Call(ctx, "typeHierarchy/supertypes", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) Subtypes(ctx context.Context, params *TypeHierarchySubtypesParams) ([]TypeHierarchyItem /*TypeHierarchyItem[] | null*/, error) {
- var result []TypeHierarchyItem /*TypeHierarchyItem[] | null*/
- if err := s.sender.Call(ctx, "typeHierarchy/subtypes", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) InlineValue(ctx context.Context, params *InlineValueParams) ([]InlineValue /*InlineValue[] | null*/, error) {
- var result []InlineValue /*InlineValue[] | null*/
- if err := s.sender.Call(ctx, "textDocument/inlineValue", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) InlineValueRefresh(ctx context.Context) error {
- return s.sender.Call(ctx, "workspace/inlineValue/refresh", nil, nil)
-}
-
-func (s *serverDispatcher) InlayHint(ctx context.Context, params *InlayHintParams) ([]InlayHint /*InlayHint[] | null*/, error) {
- var result []InlayHint /*InlayHint[] | null*/
- if err := s.sender.Call(ctx, "textDocument/inlayHint", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-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) InlayHintRefresh(ctx context.Context) error {
- return s.sender.Call(ctx, "workspace/inlayHint/refresh", nil, 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) Shutdown(ctx context.Context) error {
- return s.sender.Call(ctx, "shutdown", nil, nil)
-}
-
-func (s *serverDispatcher) WillSaveWaitUntil(ctx context.Context, params *WillSaveTextDocumentParams) ([]TextEdit /*TextEdit[] | null*/, error) {
- var result []TextEdit /*TextEdit[] | null*/
- if err := s.sender.Call(ctx, "textDocument/willSaveWaitUntil", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) Completion(ctx context.Context, params *CompletionParams) (*CompletionList /*CompletionItem[] | CompletionList | null*/, error) {
- var result *CompletionList /*CompletionItem[] | CompletionList | null*/
- if err := s.sender.Call(ctx, "textDocument/completion", 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) Hover(ctx context.Context, params *HoverParams) (*Hover /*Hover | null*/, error) {
- var result *Hover /*Hover | null*/
- if err := s.sender.Call(ctx, "textDocument/hover", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) SignatureHelp(ctx context.Context, params *SignatureHelpParams) (*SignatureHelp /*SignatureHelp | null*/, error) {
- var result *SignatureHelp /*SignatureHelp | null*/
- if err := s.sender.Call(ctx, "textDocument/signatureHelp", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) Definition(ctx context.Context, params *DefinitionParams) (Definition /*Definition | DefinitionLink[] | null*/, error) {
- var result Definition /*Definition | DefinitionLink[] | null*/
- if err := s.sender.Call(ctx, "textDocument/definition", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) References(ctx context.Context, params *ReferenceParams) ([]Location /*Location[] | null*/, error) {
- var result []Location /*Location[] | null*/
- if err := s.sender.Call(ctx, "textDocument/references", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) DocumentHighlight(ctx context.Context, params *DocumentHighlightParams) ([]DocumentHighlight /*DocumentHighlight[] | null*/, error) {
- var result []DocumentHighlight /*DocumentHighlight[] | null*/
- if err := s.sender.Call(ctx, "textDocument/documentHighlight", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) DocumentSymbol(ctx context.Context, params *DocumentSymbolParams) ([]interface{} /*SymbolInformation[] | DocumentSymbol[] | null*/, error) {
- var result []interface{} /*SymbolInformation[] | DocumentSymbol[] | null*/
- if err := s.sender.Call(ctx, "textDocument/documentSymbol", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) CodeAction(ctx context.Context, params *CodeActionParams) ([]CodeAction /*(Command | CodeAction)[] | null*/, error) {
- var result []CodeAction /*(Command | CodeAction)[] | null*/
- if err := s.sender.Call(ctx, "textDocument/codeAction", 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) Symbol(ctx context.Context, params *WorkspaceSymbolParams) ([]SymbolInformation /*SymbolInformation[] | WorkspaceSymbol[] | null*/, error) {
- var result []SymbolInformation /*SymbolInformation[] | WorkspaceSymbol[] | null*/
- if err := s.sender.Call(ctx, "workspace/symbol", 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
-}
-
-func (s *serverDispatcher) CodeLens(ctx context.Context, params *CodeLensParams) ([]CodeLens /*CodeLens[] | null*/, error) {
- var result []CodeLens /*CodeLens[] | null*/
- if err := s.sender.Call(ctx, "textDocument/codeLens", 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) CodeLensRefresh(ctx context.Context) error {
- return s.sender.Call(ctx, "workspace/codeLens/refresh", nil, nil)
-}
-
-func (s *serverDispatcher) DocumentLink(ctx context.Context, params *DocumentLinkParams) ([]DocumentLink /*DocumentLink[] | null*/, error) {
- var result []DocumentLink /*DocumentLink[] | null*/
- if err := s.sender.Call(ctx, "textDocument/documentLink", 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) Formatting(ctx context.Context, params *DocumentFormattingParams) ([]TextEdit /*TextEdit[] | null*/, error) {
- var result []TextEdit /*TextEdit[] | null*/
- if err := s.sender.Call(ctx, "textDocument/formatting", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) RangeFormatting(ctx context.Context, params *DocumentRangeFormattingParams) ([]TextEdit /*TextEdit[] | null*/, error) {
- var result []TextEdit /*TextEdit[] | null*/
- if err := s.sender.Call(ctx, "textDocument/rangeFormatting", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) OnTypeFormatting(ctx context.Context, params *DocumentOnTypeFormattingParams) ([]TextEdit /*TextEdit[] | null*/, error) {
- var result []TextEdit /*TextEdit[] | null*/
- if err := s.sender.Call(ctx, "textDocument/onTypeFormatting", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) Rename(ctx context.Context, params *RenameParams) (*WorkspaceEdit /*WorkspaceEdit | null*/, error) {
- var result *WorkspaceEdit /*WorkspaceEdit | null*/
- if err := s.sender.Call(ctx, "textDocument/rename", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) PrepareRename(ctx context.Context, params *PrepareRenameParams) (*PrepareRename2Gn /*Range | { range: Range; placeholder: string } | { defaultBehavior: boolean } | null*/, error) {
- var result *PrepareRename2Gn /*Range | { range: Range; placeholder: string } | { defaultBehavior: boolean } | null*/
- if err := s.sender.Call(ctx, "textDocument/prepareRename", params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
-
-func (s *serverDispatcher) ExecuteCommand(ctx context.Context, params *ExecuteCommandParams) (interface{} /* LSPAny | void | float64*/, error) {
- var result interface{} /* LSPAny | void | float64*/
- if err := s.sender.Call(ctx, "workspace/executeCommand", 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) 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) DiagnosticRefresh(ctx context.Context) error {
- return s.sender.Call(ctx, "workspace/diagnostic/refresh", nil, nil)
-}
-
-func (s *serverDispatcher) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
- var result interface{}
- if err := s.sender.Call(ctx, method, params, &result); err != nil {
- return nil, err
- }
- return result, nil
-}
diff --git a/internal/lsp/protocol/typescript/README.md b/internal/lsp/protocol/typescript/README.md
deleted file mode 100644
index 74bcd1883..000000000
--- a/internal/lsp/protocol/typescript/README.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# Generate Go types and signatures for the LSP protocol
-
-## Setup
-
-Make sure `node` and `tsc` are installed and in your PATH. There are detailed instructions below.
-(`tsc -v` should be at least `4.2.4`.)
-Get the typescript code for the jsonrpc protocol with
-
-`git clone git@github.com:microsoft vscode-languageserver-node.git` or
-`git clone https://github.com/microsoft/vscode-languageserver-node.git`
-
-`util.ts` expects it to be in your HOME directory
-
-If you want to reproduce the existing files you need to be on a branch with the same git hash that `util.ts` expects, for instance, `git checkout 7b90c29`
-
-## Usage
-
-Code is generated and normalized by
-
-`tsc && node code.js && gofmt -w ts*.go`
-
-(`code.ts` imports `util.ts`.) This generates 3 files in the current directory, `tsprotocol.go`
-containing type definitions, and `tsserver.go`, `tsclient.go` containing API stubs.
-
-## Notes
-
-1. `code.ts` and `util.ts` use the Typescript compiler's API, which is [introduced](https://github.com/Microsoft/TypeScript/wiki/Architectural-Overview) in their wiki.
-2. Because the Typescript and Go type systems are incompatible, `code.ts` and `util.ts` are filled with heuristics and special cases. Therefore they are tied to a specific commit of `vscode-languageserver-node`. The hash code of the commit is included in the header of
-the generated files and stored in the variable `gitHash` in `go.ts`. It is checked (see `git()` in `util.ts`) on every execution.
-3. Generating the `ts*.go` files is only semi-automated. Please file an issue if the released version is too far behind.
-4. For the impatient, first change `gitHash` by hand (`git()` shows how to find the hash).
- 1. Then try to run `code.ts`. This will likely fail because the heuristics don't cover some new case. For instance, some simple type like `string` might have changed to a union type `string | [number,number]`. Another example is that some generated formal parameter may have anonymous structure type, which is essentially unusable.
- 2. Next step is to move the generated code to `internal/lsp/protocol` and try to build `gopls` and its tests. This will likely fail because types have changed. Generally the fixes are fairly easy. Then run all the tests.
- 3. Since there are not adequate integration tests, the next step is to run `gopls`.
-
-## Detailed instructions for installing node and typescript
-
-(The instructions are somewhat different for Linux and MacOS. They install some things locally, so `$PATH` needs to be changed.)
-
-1. For Linux, it is possible to build node from scratch, but if there's a package manager, that's simpler.
- 1. To use the Ubuntu package manager
- 1. `sudo apt update` (if you can't `sudo` then these instructions are not helpful)
- 2. `sudo apt install nodejs` (this may install `/usr/bin/nodejs` rather than `/usr/bin/node`. For me, `/usr/bin/nodejs` pointed to an actual executable `/etc/alternatives/nodejs`, which should be copied to `/usr/bin/node`)
- 3. `sudo apt intall npm`
- 1. To build from scratch
- 1. Go to the [node site](https://nodejs.org), and download the one recommended for most users, and then you're on your own. (It's got binaries in it. Untar the file somewhere and put its `bin` directory in your path, perhaps?)
-2. The Mac is easier. Download the macOS installer from [nodejs](https://nodejs.org), click on it, and let it install.
-3. (There's a good chance that soon you will be asked to upgrade your new npm. `sudo npm install -g npm` is the command.)
-4. For either system, node and nvm should now be available. Running `node -v` and `npm -v` should produce version numbers.
-5. `npm install typescript`
- 1. This may give warning messages that indicate you've failed to set up a project. Ignore them.
- 2. Your home directory will now have new directories `.npm` and `node_modules` (and a `package_lock.json` file)
- 3. The typescript executable `tsc` will be in `node_modules/.bin`, so put that directory in your path.
- 4. `tsc -v` should print "Version 4.2.4" (or later). If not you may (as I did) have an obsolete tsc earlier in your path.
-6. `npm install @types/node` (Without this there will be many incomprehensible typescript error messages.)
diff --git a/internal/lsp/protocol/typescript/code.ts b/internal/lsp/protocol/typescript/code.ts
deleted file mode 100644
index dcb1b67a5..000000000
--- a/internal/lsp/protocol/typescript/code.ts
+++ /dev/null
@@ -1,1448 +0,0 @@
-/* eslint-disable no-useless-return */
-// read files from vscode-languageserver-node, and generate Go rpc stubs
-// and data definitions. (and maybe someday unmarshaling code)
-
-// The output is 3 files, tsprotocol.go contains the type definitions
-// while tsclient.go and tsserver.go contain the LSP API and stub. An LSP server
-// uses both APIs. To read the code, start in this file's main() function.
-
-// The code is rich in heuristics and special cases, some of which are to avoid
-// extensive changes to gopls, and some of which are due to the mismatch between
-// typescript and Go types. In particular, there is no Go equivalent to union
-// types, so each case ought to be considered separately. The Go equivalent of A
-// & B could frequently be struct{A;B;}, or it could be the equivalent type
-// listing all the members of A and B. Typically the code uses the former, but
-// especially if A and B have elements with the same name, it does a version of
-// the latter. ClientCapabilities has to be expanded, and ServerCapabilities is
-// expanded to make the generated code easier to read.
-
-// for us typescript ignorati, having an import makes this file a module
-import * as fs from 'fs';
-import * as ts from 'typescript';
-import * as u from './util';
-import { constName, getComments, goName, loc, strKind } from './util';
-
-var program: ts.Program;
-
-function parse() {
- // this won't complain if some fnames don't exist
- program = ts.createProgram(
- u.fnames,
- { target: ts.ScriptTarget.ES2018, module: ts.ModuleKind.CommonJS });
- program.getTypeChecker(); // finish type checking and assignment
-}
-
-// ----- collecting information for RPCs
-let req = new Map<string, ts.NewExpression>(); // requests
-let not = new Map<string, ts.NewExpression>(); // notifications
-let ptypes = new Map<string, [ts.TypeNode, ts.TypeNode]>(); // req, resp types
-let receives = new Map<string, 'server' | 'client'>(); // who receives it
-let rpcTypes = new Set<string>(); // types seen in the rpcs
-
-function findRPCs(node: ts.Node) {
- if (!ts.isModuleDeclaration(node)) {
- return;
- }
- if (!ts.isIdentifier(node.name)) {
- throw new Error(
- `expected Identifier, got ${strKind(node.name)} at ${loc(node)}`);
- }
- let reqnot = req;
- let v = node.name.getText();
- if (v.endsWith('Notification')) reqnot = not;
- else if (!v.endsWith('Request')) return;
-
- if (!ts.isModuleBlock(node.body)) {
- throw new Error(
- `expected ModuleBlock got ${strKind(node.body)} at ${loc(node)}`);
- }
- let x: ts.ModuleBlock = node.body;
- // The story is to expect const method = 'textDocument/implementation'
- // const type = new ProtocolRequestType<...>(method)
- // but the method may be an explicit string
- let rpc: string = '';
- let newNode: ts.NewExpression;
- for (let i = 0; i < x.statements.length; i++) {
- const uu = x.statements[i];
- if (!ts.isVariableStatement(uu)) continue;
- const dl: ts.VariableDeclarationList = uu.declarationList;
- if (dl.declarations.length != 1)
- throw new Error(`expected a single decl at ${loc(dl)}`);
- const decl: ts.VariableDeclaration = dl.declarations[0];
- const name = decl.name.getText();
- // we want the initializers
- if (name == 'method') { // mostly StringLiteral but NoSubstitutionTemplateLiteral in protocol.semanticTokens.ts
- if (!ts.isStringLiteral(decl.initializer)) {
- if (!ts.isNoSubstitutionTemplateLiteral(decl.initializer)) {
- console.log(`81: ${decl.initializer.getText()}`);
- throw new Error(`expect StringLiteral at ${loc(decl)} got ${strKind(decl.initializer)}`);
- }
- }
- rpc = decl.initializer.getText();
- }
- else if (name == 'type') { // NewExpression
- if (!ts.isNewExpression(decl.initializer))
- throw new Error(`89 expected new at ${loc(decl)}`);
- const nn: ts.NewExpression = decl.initializer;
- newNode = nn;
- const mtd = nn.arguments[0];
- if (ts.isStringLiteral(mtd)) rpc = mtd.getText();
- switch (nn.typeArguments.length) {
- case 1: // exit
- ptypes.set(rpc, [nn.typeArguments[0], null]);
- break;
- case 2: // notifications
- ptypes.set(rpc, [nn.typeArguments[0], null]);
- break;
- case 4: // request with no parameters
- ptypes.set(rpc, [null, nn.typeArguments[0]]);
- break;
- case 5: // request req, resp, partial(?)
- ptypes.set(rpc, [nn.typeArguments[0], nn.typeArguments[1]]);
- break;
- default:
- throw new Error(`${nn.typeArguments?.length} at ${loc(nn)}`);
- }
- }
- }
- if (rpc == '') throw new Error(`112 no name found at ${loc(x)}`);
- // remember the implied types
- const [a, b] = ptypes.get(rpc);
- const add = function (n: ts.Node) {
- rpcTypes.add(goName(n.getText()));
- };
- underlying(a, add);
- underlying(b, add);
- rpc = rpc.substring(1, rpc.length - 1); // 'exit'
- reqnot.set(rpc, newNode);
-}
-
-function setReceives() {
- // mark them all as server, then adjust the client ones.
- // it would be nice to have some independent check on this
- // (this logic fails if the server ever sends $/canceRequest
- // or $/progress)
- req.forEach((_, k) => { receives.set(k, 'server'); });
- not.forEach((_, k) => { receives.set(k, 'server'); });
- receives.set('window/showMessage', 'client');
- receives.set('window/showMessageRequest', 'client');
- receives.set('window/logMessage', 'client');
- receives.set('telemetry/event', 'client');
- receives.set('client/registerCapability', 'client');
- receives.set('client/unregisterCapability', 'client');
- receives.set('workspace/workspaceFolders', 'client');
- receives.set('workspace/configuration', 'client');
- receives.set('workspace/applyEdit', 'client');
- receives.set('textDocument/publishDiagnostics', 'client');
- receives.set('window/workDoneProgress/create', 'client');
- receives.set('window/showDocument', 'client');
- receives.set('$/progress', 'client');
- // a small check
- receives.forEach((_, k) => {
- if (!req.get(k) && !not.get(k)) throw new Error(`145 missing ${k}}`);
- if (req.get(k) && not.get(k)) throw new Error(`146 dup ${k}`);
- });
-}
-
-type DataKind = 'module' | 'interface' | 'alias' | 'enum' | 'class';
-
-interface Data {
- kind: DataKind;
- me: ts.Node; // root node for this type
- name: string; // Go name
- origname: string; // their name
- generics: ts.NodeArray<ts.TypeParameterDeclaration>;
- as: ts.NodeArray<ts.HeritageClause>; // inheritance
- // Interface
- properties: ts.NodeArray<ts.PropertySignature>
- alias: ts.TypeNode; // type alias
- // module
- statements: ts.NodeArray<ts.Statement>;
- enums: ts.NodeArray<ts.EnumMember>;
- // class
- members: ts.NodeArray<ts.PropertyDeclaration>;
-}
-function newData(n: ts.Node, nm: string, k: DataKind, origname: string): Data {
- return {
- kind: k,
- me: n, name: goName(nm), origname: origname,
- generics: ts.factory.createNodeArray<ts.TypeParameterDeclaration>(),
- as: ts.factory.createNodeArray<ts.HeritageClause>(),
- properties: ts.factory.createNodeArray<ts.PropertySignature>(), alias: undefined,
- statements: ts.factory.createNodeArray<ts.Statement>(),
- enums: ts.factory.createNodeArray<ts.EnumMember>(),
- members: ts.factory.createNodeArray<ts.PropertyDeclaration>(),
- };
-}
-
-// for debugging, produce a skeleton description
-function strData(d: Data): string {
- if (!d) { return 'nil'; }
- const f = function (na: ts.NodeArray<any>): number {
- return na.length;
- };
- const nm = d.name == d.origname ? `${d.name}` : `${d.name}/${d.origname}`;
- return `g:${f(d.generics)} a:${f(d.as)} p:${f(d.properties)} s:${f(d.statements)} e:${f(d.enums)} m:${f(d.members)} a:${d.alias !== undefined} D(${nm}) k:${d.kind}`;
-}
-
-let data = new Map<string, Data>(); // parsed data types
-let seenTypes = new Map<string, Data>(); // type names we've seen
-let extraTypes = new Map<string, string[]>(); // to avoid struct params
-
-function setData(nm: string, d: Data) {
- const v = data.get(nm);
- if (!v) {
- data.set(nm, d);
- return;
- }
- // if there are multiple definitions of the same name, decide what to do.
- // For now the choices are only aliases and modules
- // alias is preferred unless the constant values are needed
- if (nm === 'PrepareSupportDefaultBehavior') {
- // want the alias, as we're going to change the type and can't afford a constant
- if (d.kind === 'alias') data.set(nm, d);
- else if (v.kind == 'alias') data.set(nm, v);
- else throw new Error(`208 ${d.kind} ${v.kind}`);
- return;
- }
- if (nm === 'CodeActionKind') {
- // want the module, need the constants
- if (d.kind === 'module') data.set(nm, d);
- else if (v.kind === 'module') data.set(nm, v);
- else throw new Error(`215 ${d.kind} ${v.kind}`);
- }
- if (v.kind === 'alias' && d.kind !== 'alias') return;
- if (d.kind === 'alias' && v.kind !== 'alias') {
- data.set(nm, d);
- return;
- }
- if (v.kind === 'alias' && d.kind === 'alias') return;
- // protocol/src/common/protocol.foldingRange.ts 44: 1 (39: 2) and
- // types/src/main.ts 397: 1 (392: 2)
- // for FoldingRangeKind
- if (d.me.getText() === v.me.getText()) return;
- // error messages for an unexpected case
- console.log(`228 ${strData(v)} ${loc(v.me)} for`);
- console.log(`229 ${v.me.getText().replace(/\n/g, '\\n')}`);
- console.log(`230 ${strData(d)} ${loc(d.me)}`);
- console.log(`231 ${d.me.getText().replace(/\n/g, '\\n')}`);
- throw new Error(`232 setData found ${v.kind} for ${d.kind}`);
-}
-
-// look at top level data definitions
-function genTypes(node: ts.Node) {
- // Ignore top-level items that can't produce output
- if (ts.isExpressionStatement(node) || ts.isFunctionDeclaration(node) ||
- ts.isImportDeclaration(node) || ts.isVariableStatement(node) ||
- ts.isExportDeclaration(node) || ts.isEmptyStatement(node) ||
- ts.isExportAssignment(node) || ts.isImportEqualsDeclaration(node) ||
- ts.isBlock(node) || node.kind == ts.SyntaxKind.EndOfFileToken) {
- return;
- }
- if (ts.isInterfaceDeclaration(node)) {
- const v: ts.InterfaceDeclaration = node;
- // need to check the members, many of which are disruptive
- let mems: ts.PropertySignature[] = [];
- const f = function (t: ts.TypeElement) {
- if (ts.isPropertySignature(t)) {
- mems.push(t);
- } else if (ts.isMethodSignature(t) || ts.isCallSignatureDeclaration(t)) {
- return;
- } else if (ts.isIndexSignatureDeclaration(t)) {
- // probably safe to ignore these
- // [key: string]: boolean | number | string | undefined;
- // and InitializeResult: [custom: string]: any;]
- } else
- throw new Error(`259 unexpected ${strKind(t)}`);
- };
- v.members.forEach(f);
- if (mems.length == 0 && !v.heritageClauses &&
- v.name.getText() != 'InitializedParams') {
- return; // Don't seem to need any of these [Logger, PipTransport, ...]
- }
- // Found one we want
- let x = newData(v, goName(v.name.getText()), 'interface', v.name.getText());
- x.properties = ts.factory.createNodeArray<ts.PropertySignature>(mems);
- if (v.typeParameters) x.generics = v.typeParameters;
- if (v.heritageClauses) x.as = v.heritageClauses;
- if (x.generics.length > 1) { // Unneeded
- // Item interface Item<K, V>...
- return;
- }
- if (data.has(x.name)) { // modifying one we've seen
- x = dataChoose(x, data.get(x.name));
- }
- setData(x.name, x);
- } else if (ts.isTypeAliasDeclaration(node)) {
- const v: ts.TypeAliasDeclaration = node;
- let x = newData(v, v.name.getText(), 'alias', v.name.getText());
- x.alias = v.type;
- // if type is a union of constants, we (mostly) don't want it
- // (at the top level)
- // Unfortunately this is false for TraceValues
- if (ts.isUnionTypeNode(v.type) &&
- v.type.types.every((n: ts.TypeNode) => ts.isLiteralTypeNode(n))) {
- if (x.name != 'TraceValues') return;
- }
- if (v.typeParameters) {
- x.generics = v.typeParameters;
- }
- if (data.has(x.name)) x = dataChoose(x, data.get(x.name));
- if (x.generics.length > 1) {
- return;
- }
- setData(x.name, x);
- } else if (ts.isModuleDeclaration(node)) {
- const v: ts.ModuleDeclaration = node;
- if (!ts.isModuleBlock(v.body)) {
- throw new Error(`${loc(v)} not ModuleBlock, but ${strKind(v.body)}`);
- }
- const b: ts.ModuleBlock = v.body;
- var s: ts.Statement[] = [];
- // we don't want most of these
- const fx = function (x: ts.Statement) {
- if (ts.isFunctionDeclaration(x)) {
- return;
- }
- if (ts.isTypeAliasDeclaration(x) || ts.isModuleDeclaration(x)) {
- return;
- }
- if (!ts.isVariableStatement(x))
- throw new Error(
- `315 expected VariableStatment ${loc(x)} ${strKind(x)} ${x.getText()}`);
- if (hasNewExpression(x)) {
- return;
- }
- s.push(x);
- };
- b.statements.forEach(fx);
- if (s.length == 0) {
- return;
- }
- let m = newData(node, v.name.getText(), 'module', v.name.getText());
- m.statements = ts.factory.createNodeArray<ts.Statement>(s);
- if (data.has(m.name)) m = dataChoose(m, data.get(m.name));
- setData(m.name, m);
- } else if (ts.isEnumDeclaration(node)) {
- const nm = node.name.getText();
- let v = newData(node, nm, 'enum', node.name.getText());
- v.enums = node.members;
- if (data.has(nm)) {
- v = dataChoose(v, data.get(nm));
- }
- setData(nm, v);
- } else if (ts.isClassDeclaration(node)) {
- const v: ts.ClassDeclaration = node;
- var d: ts.PropertyDeclaration[] = [];
- const wanted = function (c: ts.ClassElement): string {
- if (ts.isConstructorDeclaration(c)) {
- return '';
- }
- if (ts.isMethodDeclaration(c)) {
- return '';
- }
- if (ts.isGetAccessor(c)) {
- return '';
- }
- if (ts.isSetAccessor(c)) {
- return '';
- }
- if (ts.isPropertyDeclaration(c)) {
- d.push(c);
- return strKind(c);
- }
- throw new Error(`Class decl ${strKind(c)} `);
- };
- v.members.forEach((c) => wanted(c));
- if (d.length == 0) {
- return;
- } // don't need it
- let c = newData(v, v.name.getText(), 'class', v.name.getText());
- c.members = ts.factory.createNodeArray<ts.PropertyDeclaration>(d);
- if (v.typeParameters) {
- c.generics = v.typeParameters;
- }
- if (c.generics.length > 1) {
- return;
- }
- if (v.heritageClauses) {
- c.as = v.heritageClauses;
- }
- if (data.has(c.name))
- throw new Error(`Class dup ${loc(c.me)} and ${loc(data.get(c.name).me)}`);
- setData(c.name, c);
- } else {
- throw new Error(`378 unexpected ${strKind(node)} ${loc(node)} `);
- }
-}
-
-// Typescript can accumulate, but this chooses one or the other
-function dataChoose(a: Data, b: Data): Data {
- // maybe they are textually identical? (e.g., FoldingRangeKind)
- const [at, bt] = [a.me.getText(), b.me.getText()];
- if (at == bt) {
- return a;
- }
- switch (a.name) {
- case 'InitializeError':
- case 'CompletionItemTag':
- case 'SymbolTag':
- case 'CodeActionKind':
- case 'Integer':
- case 'Uinteger':
- case 'Decimal':
- // want the Module, if anything
- return a.statements.length > 0 ? a : b;
- case 'CancellationToken':
- case 'CancellationStrategy':
- // want the Interface
- return a.properties.length > 0 ? a : b;
- case 'TextDocumentContentChangeEvent': // almost the same
- case 'TokenFormat':
- case 'PrepareSupportDefaultBehavior':
- return a;
- }
- console.log(
- `409 ${strKind(a.me)} ${strKind(b.me)} ${a.name} ${loc(a.me)} ${loc(b.me)}`);
- throw new Error(`410 Fix dataChoose for ${a.name}`);
-}
-
-// is a node an ancestor of a NewExpression
-function hasNewExpression(n: ts.Node): boolean {
- let ans = false;
- n.forEachChild((n: ts.Node) => {
- if (ts.isNewExpression(n)) ans = true;
- });
- return ans;
-}
-
-function checkOnce() {
- // Data for all the rpc types?
- rpcTypes.forEach(s => {
- if (!data.has(s)) throw new Error(`checkOnce, ${s}?`);
- });
-}
-
-// helper function to find underlying types
-// eslint-disable-next-line no-unused-vars
-function underlying(n: ts.Node | undefined, f: (n: ts.Node) => void) {
- if (!n) return;
- const ff = function (n: ts.Node) {
- underlying(n, f);
- };
- if (ts.isIdentifier(n)) {
- f(n);
- } else if (
- n.kind == ts.SyntaxKind.StringKeyword ||
- n.kind == ts.SyntaxKind.NumberKeyword ||
- n.kind == ts.SyntaxKind.AnyKeyword ||
- n.kind == ts.SyntaxKind.UnknownKeyword ||
- n.kind == ts.SyntaxKind.NullKeyword ||
- n.kind == ts.SyntaxKind.BooleanKeyword ||
- n.kind == ts.SyntaxKind.ObjectKeyword ||
- n.kind == ts.SyntaxKind.VoidKeyword) {
- // nothing to do
- } else if (ts.isTypeReferenceNode(n)) {
- f(n.typeName);
- } else if (ts.isArrayTypeNode(n)) {
- underlying(n.elementType, f);
- } else if (ts.isHeritageClause(n)) {
- n.types.forEach(ff);
- } else if (ts.isExpressionWithTypeArguments(n)) {
- underlying(n.expression, f);
- } else if (ts.isPropertySignature(n)) {
- underlying(n.type, f);
- } else if (ts.isTypeLiteralNode(n)) {
- n.members.forEach(ff);
- } else if (ts.isUnionTypeNode(n) || ts.isIntersectionTypeNode(n)) {
- n.types.forEach(ff);
- } else if (ts.isIndexSignatureDeclaration(n)) {
- underlying(n.type, f);
- } else if (ts.isParenthesizedTypeNode(n)) {
- underlying(n.type, f);
- } else if (
- ts.isLiteralTypeNode(n) || ts.isVariableStatement(n) ||
- ts.isTupleTypeNode(n)) {
- // we only see these in moreTypes, but they are handled elsewhere
- } else if (ts.isEnumMember(n)) {
- if (ts.isStringLiteral(n.initializer)) return;
- throw new Error(`472 EnumMember ${strKind(n.initializer)} ${n.name.getText()}`);
- } else {
- throw new Error(`474 saw ${strKind(n)} in underlying. ${n.getText()} at ${loc(n)}`);
- }
-}
-
-// find all the types implied by seenTypes.
-// Simplest way to the transitive closure is to stabilize the size of seenTypes
-// but it is slow
-function moreTypes() {
- const extra = function (s: string) {
- if (!data.has(s)) throw new Error(`moreTypes needs ${s}`);
- seenTypes.set(s, data.get(s));
- };
- rpcTypes.forEach(extra); // all the types needed by the rpcs
- // needed in enums.go (or elsewhere)
- extra('InitializeError');
- extra('WatchKind');
- extra('FoldingRangeKind');
- // not sure why these weren't picked up
- extra('DidChangeWatchedFilesRegistrationOptions');
- extra('WorkDoneProgressBegin');
- extra('WorkDoneProgressReport');
- extra('WorkDoneProgressEnd');
- let old = 0;
- do {
- old = seenTypes.size;
-
- const m = new Map<string, Data>();
- const add = function (n: ts.Node) {
- const nm = goName(n.getText());
- if (seenTypes.has(nm) || m.has(nm)) return;
- if (data.get(nm)) {
- m.set(nm, data.get(nm));
- }
- };
- // expect all the heritage clauses have single Identifiers
- const h = function (n: ts.Node) {
- underlying(n, add);
- };
- const f = function (x: ts.NodeArray<ts.Node>) {
- x.forEach(h);
- };
- seenTypes.forEach((d: Data) => d && f(d.as));
- // find the types in the properties
- seenTypes.forEach((d: Data) => d && f(d.properties));
- // and in the alias and in the statements and in the enums
- seenTypes.forEach((d: Data) => d && underlying(d.alias, add));
- seenTypes.forEach((d: Data) => d && f(d.statements));
- seenTypes.forEach((d: Data) => d && f(d.enums));
- m.forEach((d, k) => seenTypes.set(k, d));
- }
- while (seenTypes.size != old)
- ;
-}
-
-function cleanData() { // middle pass
- // seenTypes contains all the top-level types.
- seenTypes.forEach((d) => {
- if (d.kind == 'alias') mergeAlias(d);
- });
-}
-
-function sameType(a: ts.TypeNode, b: ts.TypeNode): boolean {
- if (a.kind !== b.kind) return false;
- if (a.kind === ts.SyntaxKind.BooleanKeyword) return true;
- if (a.kind === ts.SyntaxKind.StringKeyword) return true;
- if (ts.isTypeReferenceNode(a) && ts.isTypeReferenceNode(b) &&
- a.typeName.getText() === b.typeName.getText()) return true;
- if (ts.isArrayTypeNode(a) && ts.isArrayTypeNode(b)) return sameType(a.elementType, b.elementType);
- if (ts.isTypeLiteralNode(a) && ts.isTypeLiteralNode(b)) {
- if (a.members.length !== b.members.length) return false;
- if (a.members.length === 1) return a.members[0].name.getText() === b.members[0].name.getText();
- if (loc(a) === loc(b)) return true;
- }
- throw new Error(`544 sameType? ${strKind(a)} ${strKind(b)} ${a.getText()}`);
-}
-type CreateMutable<Type> = {
- -readonly [Property in keyof Type]: Type[Property];
-};
-type propMap = Map<string, ts.PropertySignature>;
-function propMapSet(pm: propMap, name: string, v: ts.PropertySignature) {
- if (!pm.get(name)) {
- try { getComments(v); } catch (e) { console.log(`552 ${name} ${e}`); }
- pm.set(name, v);
- return;
- }
- const a = pm.get(name).type;
- const b = v.type;
- if (sameType(a, b)) {
- return;
- }
- if (ts.isTypeReferenceNode(a) && ts.isTypeLiteralNode(b)) {
- const x = mergeTypeRefLit(a, b);
- const fake: CreateMutable<ts.PropertySignature> = v;
- fake['type'] = x;
- check(fake as ts.PropertySignature, '565');
- pm.set(name, fake as ts.PropertySignature);
- return;
- }
- if (ts.isTypeLiteralNode(a) && ts.isTypeLiteralNode(b)) {
- const x = mergeTypeLitLit(a, b);
- const fake: CreateMutable<ts.PropertySignature> = v;
- fake['type'] = x;
- check(fake as ts.PropertySignature, '578');
- pm.set(name, fake as ts.PropertySignature);
- return;
- }
- console.log(`577 ${pm.get(name).getText()}\n${v.getText()}`);
- throw new Error(`578 should merge ${strKind(a)} and ${strKind(b)} for ${name}`);
-}
-function addToProperties(pm: propMap, tn: ts.TypeNode | undefined, prefix = '') {
- if (!tn) return;
- if (ts.isTypeReferenceNode(tn)) {
- const d = seenTypes.get(goName(tn.typeName.getText()));
- if (tn.typeName.getText() === 'T') return;
- if (!d) throw new Error(`584 ${tn.typeName.getText()} not found`);
- if (d.properties.length === 0 && d.alias === undefined) return;
- if (d.alias !== undefined) {
- if (ts.isIntersectionTypeNode(d.alias)) {
- d.alias.types.forEach((tn) => addToProperties(pm, tn, prefix)); // prefix?
- return;
- }
- }
- d.properties.forEach((ps) => {
- const name = `${prefix}.${ps.name.getText()}`;
- propMapSet(pm, name, ps);
- addToProperties(pm, ps.type, name);
- });
- } else if (strKind(tn) === 'TypeLiteral') {
- if (!ts.isTypeLiteralNode(tn)) new Error(`599 ${strKind(tn)}`);
- tn.forEachChild((child: ts.Node) => {
- if (ts.isPropertySignature(child)) {
- const name = `${prefix}.${child.name.getText()}`;
- propMapSet(pm, name, child);
- addToProperties(pm, child.type, name);
- } else if (!ts.isIndexSignatureDeclaration(child)) {
- // ignoring IndexSignatures, seen as relatedDocument in
- // RelatedFullDocumentDiagnosticReport
- throw new Error(`608 ${strKind(child)} ${loc(child)}`);
- }
- });
- }
-}
-function deepProperties(d: Data): propMap | undefined {
- let properties: propMap = new Map<string, ts.PropertySignature>();
- if (!d.alias || !ts.isIntersectionTypeNode(d.alias)) return undefined;
- d.alias.types.forEach((ts) => addToProperties(properties, ts));
- return properties;
-}
-
-function mergeAlias(d: Data) {
- const props = deepProperties(d);
- if (!props) return; // nothing merged
- // now each element of props should have length 1
- // change d to merged, toss its alias field, fill in its properties
- const v: ts.PropertySignature[] = [];
- props.forEach((ps, nm) => {
- const xlen = nm.split('.').length;
- if (xlen !== 2) return; // not top-level
- v.push(ps);
- });
- d.kind = 'interface';
- d.alias = undefined;
- d.properties = ts.factory.createNodeArray(v);
-}
-
-function mergeTypeLitLit(a: ts.TypeLiteralNode, b: ts.TypeLiteralNode): ts.TypeLiteralNode {
- const v = new Map<string, ts.TypeElement>(); // avoid duplicates
- a.members.forEach((te) => v.set(te.name.getText(), te));
- b.members.forEach((te) => v.set(te.name.getText(), te));
- const x: ts.TypeElement[] = [];
- v.forEach((te) => x.push(te));
- const fake: CreateMutable<ts.TypeLiteralNode> = a;
- fake['members'] = ts.factory.createNodeArray(x);
- check(fake as ts.TypeLiteralNode, '643');
- return fake as ts.TypeLiteralNode;
-}
-
-function mergeTypeRefLit(a: ts.TypeReferenceNode, b: ts.TypeLiteralNode): ts.TypeLiteralNode {
- const d = seenTypes.get(goName(a.typeName.getText()));
- if (!d) throw new Error(`644 name ${a.typeName.getText()} not found`);
- const typ = d.me;
- if (!ts.isInterfaceDeclaration(typ)) throw new Error(`646 got ${strKind(typ)} not InterfaceDecl`);
- const v = new Map<string, ts.TypeElement>(); // avoid duplicates
- typ.members.forEach((te) => v.set(te.name.getText(), te));
- b.members.forEach((te) => v.set(te.name.getText(), te));
- const x: ts.TypeElement[] = [];
- v.forEach((te) => x.push(te));
-
- const w = ts.factory.createNodeArray(x);
- const fk: CreateMutable<ts.TypeLiteralNode> = b;
- fk['members'] = w;
- (fk['members'] as { pos: number })['pos'] = b.members.pos;
- (fk['members'] as { end: number })['end'] = b.members.end;
- check(fk as ts.TypeLiteralNode, '662');
- return fk as ts.TypeLiteralNode;
-}
-
-// check that constructed nodes still have associated text
-function check(n: ts.Node, loc: string) {
- try { getComments(n); } catch (e) { console.log(`check at ${loc} ${e}`); }
- try { n.getText(); } catch (e) { console.log(`text check at ${loc}`); }
-}
-
-let typesOut = new Array<string>();
-let constsOut = new Array<string>();
-
-// generate Go types
-function toGo(d: Data, nm: string) {
- if (!d) return; // this is probably a generic T
- if (d.name.startsWith('Inner') || d.name === 'WindowClientCapabilities') return; // removed by alias processing
- if (d.name === 'Integer' || d.name === 'Uinteger') return; // unneeded
- switch (d.kind) {
- case 'alias':
- goTypeAlias(d, nm); break;
- case 'module': goModule(d, nm); break;
- case 'enum': goEnum(d, nm); break;
- case 'interface': goInterface(d, nm); break;
- default:
- throw new Error(
- `672: more cases in toGo ${nm} ${d.kind}`);
- }
-}
-
-// these fields need a * and are not covered by the code
-// that calls isStructType.
-var starred: [string, string][] = [
- ['TextDocumentContentChangeEvent', 'range'], ['CodeAction', 'command'],
- ['CodeAction', 'disabled'],
- ['DidSaveTextDocumentParams', 'text'], ['CompletionItem', 'command'],
- ['Diagnostic', 'codeDescription']
-];
-
-// generate Go code for an interface
-function goInterface(d: Data, nm: string) {
- let ans = `type ${goName(nm)} struct {\n`;
-
- // generate the code for each member
- const g = function (n: ts.PropertySignature) {
- if (!ts.isPropertySignature(n))
- throw new Error(`expected PropertySignature got ${strKind(n)} `);
- ans = ans.concat(getComments(n));
- const json = u.JSON(n);
- let gt = goType(n.type, n.name.getText());
- if (gt == d.name) gt = '*' + gt; // avoid recursive types (SelectionRange)
- // there are several cases where a * is needed
- // (putting * in front of too many things breaks uses of CodeActionKind)
- starred.forEach(([a, b]) => {
- if (d.name == a && n.name.getText() == b) {
- gt = '*' + gt;
- }
- });
- ans = ans.concat(`${goName(n.name.getText())} ${gt}`, json, '\n');
- };
- d.properties.forEach(g);
- // heritage clauses become embedded types
- // check they are all Identifiers
- const f = function (n: ts.ExpressionWithTypeArguments) {
- if (!ts.isIdentifier(n.expression))
- throw new Error(`Interface ${nm} heritage ${strKind(n.expression)} `);
- if (n.expression.getText() === 'Omit') return; // Type modification type
- ans = ans.concat(goName(n.expression.getText()), '\n');
- };
- d.as.forEach((n: ts.HeritageClause) => n.types.forEach(f));
- ans = ans.concat('}\n');
- typesOut.push(getComments(d.me));
- typesOut.push(ans);
-}
-
-// generate Go code for a module (const declarations)
-// Generates type definitions, and named constants
-function goModule(d: Data, nm: string) {
- if (d.generics.length > 0 || d.as.length > 0) {
- throw new Error(`743 goModule: unexpected for ${nm}
- `);
- }
- // all the statements should be export const <id>: value
- // or value = value
- // They are VariableStatements with x.declarationList having a single
- // VariableDeclaration
- let isNumeric = false;
- const f = function (n: ts.Statement, i: number) {
- if (!ts.isVariableStatement(n)) {
- throw new Error(`753 ${nm} ${i} expected VariableStatement,
- got ${strKind(n)}`);
- }
- const c = getComments(n);
- const v = n.declarationList.declarations[0]; // only one
-
- if (!v.initializer)
- throw new Error(`760 no initializer ${nm} ${i} ${v.name.getText()}`);
- isNumeric = strKind(v.initializer) == 'NumericLiteral';
- if (c != '') constsOut.push(c); // no point if there are no comments
- // There are duplicates.
- const cname = constName(goName(v.name.getText()), nm);
- let val = v.initializer.getText();
- val = val.split('\'').join('"'); // useless work for numbers
- constsOut.push(`${cname} ${nm} = ${val}`);
- };
- d.statements.forEach(f);
- typesOut.push(getComments(d.me));
- // Or should they be type aliases?
- typesOut.push(`type ${nm} ${isNumeric ? 'float64' : 'string'}`);
-}
-
-// generate Go code for an enum. Both types and named constants
-function goEnum(d: Data, nm: string) {
- let isNumeric = false;
- const f = function (v: ts.EnumMember, j: number) { // same as goModule
- if (!v.initializer)
- throw new Error(`goEnum no initializer ${nm} ${j} ${v.name.getText()}`);
- isNumeric = strKind(v.initializer) == 'NumericLiteral';
- const c = getComments(v);
- const cname = constName(goName(v.name.getText()), nm);
- let val = v.initializer.getText();
- val = val.split('\'').join('"'); // replace quotes. useless work for numbers
- constsOut.push(`${c}${cname} ${nm} = ${val}`);
- };
- d.enums.forEach(f);
- typesOut.push(getComments(d.me));
- // Or should they be type aliases?
- typesOut.push(`type ${nm} ${isNumeric ? 'float64' : 'string'}`);
-}
-
-// generate code for a type alias
-function goTypeAlias(d: Data, nm: string) {
- if (d.as.length != 0 || d.generics.length != 0) {
- if (nm != 'ServerCapabilities')
- throw new Error(`${nm} has extra fields(${d.as.length},${d.generics.length}) ${d.me.getText()}`);
- }
- typesOut.push(getComments(d.me));
- // d.alias doesn't seem to have comments
- let aliasStr = goName(nm) == 'DocumentURI' ? ' ' : ' = ';
- if (nm == 'PrepareSupportDefaultBehavior') {
- // code-insiders is sending a bool, not a number. PJW: check this after Feb/2021
- // (and gopls never looks at it anyway)
- typesOut.push(`type ${goName(nm)}${aliasStr}interface{}\n`);
- return;
- }
- typesOut.push(`type ${goName(nm)}${aliasStr}${goType(d.alias, nm)}\n`);
-}
-
-// return a go type and maybe an assocated javascript tag
-function goType(n: ts.TypeNode | undefined, nm: string): string {
- if (!n) throw new Error(`goType undefined for ${nm}`);
- if (n.getText() == 'T') return 'interface{}'; // should check it's generic
- if (ts.isTypeReferenceNode(n)) {
- // DocumentDiagnosticReportKind.unChanged (or .new) value is "new" or "unChanged"
- if (n.getText().startsWith('DocumentDiagnostic')) return 'string';
- switch (n.getText()) {
- case 'integer': return 'int32';
- case 'uinteger': return 'uint32';
- default: return goName(n.typeName.getText()); // avoid <T>
- }
- } else if (ts.isUnionTypeNode(n)) {
- return goUnionType(n, nm);
- } else if (ts.isIntersectionTypeNode(n)) {
- return goIntersectionType(n, nm);
- } else if (strKind(n) == 'StringKeyword') {
- return 'string';
- } else if (strKind(n) == 'NumberKeyword') {
- return 'float64';
- } else if (strKind(n) == 'BooleanKeyword') {
- return 'bool';
- } else if (strKind(n) == 'AnyKeyword' || strKind(n) == 'UnknownKeyword') {
- return 'interface{}';
- } else if (strKind(n) == 'NullKeyword') {
- return 'nil';
- } else if (strKind(n) == 'VoidKeyword' || strKind(n) == 'NeverKeyword') {
- return 'void';
- } else if (strKind(n) == 'ObjectKeyword') {
- return 'interface{}';
- } else if (ts.isArrayTypeNode(n)) {
- if (nm === 'arguments') {
- // Command and ExecuteCommandParams
- return '[]json.RawMessage';
- }
- return `[]${goType(n.elementType, nm)}`;
- } else if (ts.isParenthesizedTypeNode(n)) {
- return goType(n.type, nm);
- } else if (ts.isLiteralTypeNode(n)) {
- return strKind(n.literal) == 'StringLiteral' ? 'string' : 'float64';
- } else if (ts.isTypeLiteralNode(n)) {
- // these are anonymous structs
- const v = goTypeLiteral(n, nm);
- return v;
- } else if (ts.isTupleTypeNode(n)) {
- if (n.getText() == '[number, number]') return '[]float64';
- throw new Error(`goType unexpected Tuple ${n.getText()}`);
- }
- throw new Error(`${strKind(n)} goType unexpected ${n.getText()} for ${nm}`);
-}
-
-// The choice is uniform interface{}, or some heuristically assigned choice,
-// or some better sytematic idea I haven't thought of. Using interface{}
-// is, in practice, impossibly complex in the existing code.
-function goUnionType(n: ts.UnionTypeNode, nm: string): string {
- let help = `/*${n.getText()}*/`; // show the original as a comment
- // There are some bad cases with newlines:
- // range?: boolean | {\n };
- // full?: boolean | {\n /**\n * The server supports deltas for full documents.\n */\n delta?: boolean;\n }
- // These are handled specially:
- if (nm == 'range') help = help.replace(/\n/, '');
- if (nm == 'full' && help.indexOf('\n') != -1) {
- help = '/*boolean | <elided struct>*/';
- }
- // handle all the special cases
- switch (n.types.length) {
- case 2: {
- const a = strKind(n.types[0]);
- const b = strKind(n.types[1]);
- if (a == 'NumberKeyword' && b == 'StringKeyword') { // ID
- return `interface{} ${help}`;
- }
- // for null, b is not useful (LiternalType)
- if (n.types[1].getText() === 'null') {
- if (nm == 'textDocument/codeAction') {
- // (Command | CodeAction)[] | null
- return `[]CodeAction ${help}`;
- }
- let v = goType(n.types[0], 'a');
- return `${v} ${help}`;
- }
- if (a == 'BooleanKeyword') { // usually want bool
- if (nm == 'codeActionProvider') return `interface{} ${help}`;
- if (nm == 'renameProvider') return `interface{} ${help}`;
- if (nm == 'full') return `interface{} ${help}`; // there's a struct
- if (nm == 'save') return `${goType(n.types[1], '680')} ${help}`;
- return `${goType(n.types[0], 'b')} ${help}`;
- }
- if (b == 'ArrayType') return `${goType(n.types[1], 'c')} ${help}`;
- if (help.includes('InsertReplaceEdit') && n.types[0].getText() == 'TextEdit') {
- return `*TextEdit ${help}`;
- }
- if (a == 'TypeReference') {
- if (nm == 'edits') return `${goType(n.types[0], '901')} ${help}`;
- if (a == b) return `interface{} ${help}`;
- if (nm == 'code') return `interface{} ${help}`;
- if (nm == 'editRange') return `${goType(n.types[0], '904')} ${help}`;
- if (nm === 'location') return `${goType(n.types[0], '905')} ${help}`;
- }
- if (a == 'StringKeyword') return `string ${help}`;
- if (a == 'TypeLiteral' && nm == 'TextDocumentContentChangeEvent') {
- return `${goType(n.types[0], nm)}`;
- }
- if (a == 'TypeLiteral' && b === 'TypeLiteral') {
- // DocumentDiagnosticReport
- // the first one includes the second one
- return `${goType(n.types[0], '9d')}`;
- }
- throw new Error(`911 ${nm}: a:${a}/${goType(n.types[0], '9a')} b:${b}/${goType(n.types[1], '9b')} ${loc(n)}`);
- }
- case 3: {
- const aa = strKind(n.types[0]);
- const bb = strKind(n.types[1]);
- const cc = strKind(n.types[2]);
- if (nm === 'workspace/symbol') return `${goType(n.types[0], '930')} ${help}`;
- if (nm == 'DocumentFilter' || nm == 'NotebookDocumentFilter' || nm == 'TextDocumentFilter') {
- // not really a union. the first is enough, up to a missing
- // omitempty but avoid repetitious comments
- return `${goType(n.types[0], 'g')}`;
- }
- if (nm == 'textDocument/documentSymbol') {
- return `[]interface{} ${help}`;
- }
- if (aa == 'TypeReference' && bb == 'ArrayType' && (cc == 'NullKeyword' || cc === 'LiteralType')) {
- return `${goType(n.types[0], 'd')} ${help}`;
- }
- if (aa == 'TypeReference' && bb == aa && cc == 'ArrayType') {
- // should check that this is Hover.Contents
- return `${goType(n.types[0], 'e')} ${help}`;
- }
- if (aa == 'ArrayType' && bb == 'TypeReference' && (cc == 'NullKeyword' || cc === 'LiteralType')) {
- // check this is nm == 'textDocument/completion'
- return `${goType(n.types[1], 'f')} ${help}`;
- }
- if (aa == 'LiteralType' && bb == aa && cc == aa) return `string ${help}`;
- // keep this for diagnosing unexpected interface{} results
- // console.log(`931, interface{} for ${aa}/${goType(n.types[0], 'g')},${bb}/${goType(n.types[1], 'h')},${cc}/${goType(n.types[2], 'i')} ${nm}`);
- break;
- }
- case 4:
- if (nm == 'documentChanges') return `TextDocumentEdit ${help} `;
- if (nm == 'textDocument/prepareRename') {
- // these names have to be made unique
- const genName = `${goName("prepareRename")}${extraTypes.size}Gn`;
- extraTypes.set(genName, [`Range Range \`json:"range"\`
- Placeholder string \`json:"placeholder"\``]);
- return `${genName} ${help} `;
- }
- break;
- case 8: // LSPany
- break;
- default:
- throw new Error(`957 goUnionType len=${n.types.length} nm=${nm} ${n.getText()}`);
- }
-
- // Result will be interface{} with a comment
- let isLiteral = true;
- let literal = 'string';
- let res = 'interface{} /* ';
- n.types.forEach((v: ts.TypeNode, i: number) => {
- // might get an interface inside:
- // (Command | CodeAction)[] | null
- let m = goType(v, nm);
- if (m.indexOf('interface') != -1) {
- // avoid nested comments
- m = m.split(' ')[0];
- }
- m = m.split('\n').join('; '); // sloppy: struct{;
- res = res.concat(`${i == 0 ? '' : ' | '}`, m);
- if (!ts.isLiteralTypeNode(v)) isLiteral = false;
- else literal = strKind(v.literal) == 'StringLiteral' ? 'string' : 'number';
- });
- if (!isLiteral) {
- return res + '*/';
- }
- // I don't think we get here
- // trace?: 'off' | 'messages' | 'verbose' should get string
- return `${literal} /* ${n.getText()} */`;
-}
-
-// some of the intersection types A&B are ok as struct{A;B;} and some
-// could be expanded, and ClientCapabilites has to be expanded,
-// at least for workspace. It's possible to check algorithmically,
-// but much simpler just to check explicitly.
-function goIntersectionType(n: ts.IntersectionTypeNode, nm: string): string {
- if (nm == 'ClientCapabilities') return expandIntersection(n);
- //if (nm == 'ServerCapabilities') return expandIntersection(n); // save for later consideration
- let inner = '';
- n.types.forEach(
- (t: ts.TypeNode) => { inner = inner.concat(goType(t, nm), '\n'); });
- return `struct{ \n${inner}} `;
-}
-
-// for each of the intersected types, extract its components (each will
-// have a Data with properties) extract the properties, and keep track
-// of them by name. The names that occur once can be output. The names
-// that occur more than once need to be combined.
-function expandIntersection(n: ts.IntersectionTypeNode): string {
- const bad = function (n: ts.Node, s: string) {
- return new Error(`expandIntersection ${strKind(n)} ${s}`);
- };
- let props = new Map<string, ts.PropertySignature[]>();
- for (const tp of n.types) {
- if (!ts.isTypeReferenceNode(tp)) throw bad(tp, 'A');
- const d = data.get(goName(tp.typeName.getText()));
- for (const p of d.properties) {
- if (!ts.isPropertySignature(p)) throw bad(p, 'B');
- let v = props.get(p.name.getText()) || [];
- v.push(p);
- props.set(p.name.getText(), v);
- }
- }
- let ans = 'struct {\n';
- for (const [k, v] of Array.from(props)) {
- if (v.length == 1) {
- const a = v[0];
- ans = ans.concat(getComments(a));
- ans = ans.concat(`${goName(k)} ${goType(a.type, k)} ${u.JSON(a)}\n`);
- continue;
- }
- ans = ans.concat(`${goName(k)} struct {\n`);
- for (let i = 0; i < v.length; i++) {
- const a = v[i];
- if (ts.isTypeReferenceNode(a.type)) {
- ans = ans.concat(getComments(a));
- ans = ans.concat(goName(a.type.typeName.getText()), '\n');
- } else if (ts.isTypeLiteralNode(a.type)) {
- if (a.type.members.length != 1) throw bad(a.type, 'C');
- const b = a.type.members[0];
- if (!ts.isPropertySignature(b)) throw bad(b, 'D');
- ans = ans.concat(getComments(b));
- ans = ans.concat(
- goName(b.name.getText()), ' ', goType(b.type, 'a'), u.JSON(b), '\n');
- } else {
- throw bad(a.type, `E ${a.getText()} in ${goName(k)} at ${loc(a)}`);
- }
- }
- ans = ans.concat('}\n');
- }
- ans = ans.concat('}\n');
- return ans;
-}
-
-// Does it make sense to use a pointer?
-function isStructType(te: ts.TypeNode): boolean {
- switch (strKind(te)) {
- case 'UnionType': // really need to know which type will be chosen
- case 'BooleanKeyword':
- case 'StringKeyword':
- case 'ArrayType':
- return false;
- case 'TypeLiteral': return false; // true makes for difficult compound constants
- // but think more carefully to understands why starred is needed.
- case 'TypeReference': {
- if (!ts.isTypeReferenceNode(te)) throw new Error(`1047 impossible ${strKind(te)}`);
- const d = seenTypes.get(goName(te.typeName.getText()));
- if (d === undefined || d.properties.length == 0) return false;
- if (d.properties.length > 1) return true;
- // alias or interface with a single property (The alias is Uinteger, which we ignore later)
- if (d.alias) return false;
- const x = d.properties[0].type;
- return isStructType(x);
- }
- default: throw new Error(`1055 indirectable> ${strKind(te)}`);
- }
-}
-
-function goTypeLiteral(n: ts.TypeLiteralNode, nm: string): string {
- let ans: string[] = []; // in case we generate a new extra type
- let res = 'struct{\n'; // the actual answer usually
- const g = function (nx: ts.TypeElement) {
- // add the json, as in goInterface(). Strange inside union types.
- if (ts.isPropertySignature(nx)) {
- let json = u.JSON(nx);
- let typ = goType(nx.type, nx.name.getText());
- // }/*\n*/`json:v` is not legal, the comment is a newline
- if (typ.includes('\n') && typ.indexOf('*/') === typ.length - 2) {
- typ = typ.replace(/\n\t*/g, ' ');
- }
- const v = getComments(nx) || '';
- starred.forEach(([a, b]) => {
- if (a != nm || b != typ.toLowerCase()) return;
- typ = '*' + typ;
- json = json.substring(0, json.length - 2) + ',omitempty"`';
- });
- if (typ[0] !== '*' && isStructType(nx.type)) typ = '*' + typ;
- res = res.concat(`${v} ${goName(nx.name.getText())} ${typ}`, json, '\n');
- ans.push(`${v}${goName(nx.name.getText())} ${typ} ${json}\n`);
- } else if (ts.isIndexSignatureDeclaration(nx)) {
- const comment = nx.getText().replace(/[/]/g, '');
- if (nx.getText() == '[uri: string]: TextEdit[];') {
- res = 'map[string][]TextEdit';
- } else if (nx.getText().startsWith('[id: ChangeAnnotationIdentifier]')) {
- res = 'map[string]ChangeAnnotationIdentifier';
- } else if (nx.getText().startsWith('[uri: string')) {
- res = 'map[string]interface{}';
- } else if (nx.getText().startsWith('[uri: DocumentUri')) {
- res = 'map[DocumentURI][]TextEdit';
- } else if (nx.getText().startsWith('[key: string')) {
- res = 'map[string]interface{}';
- } else {
- throw new Error(`1100 handle ${nx.getText()} ${loc(nx)}`);
- }
- res += ` /*${comment}*/`;
- ans.push(res);
- return;
- } else
- throw new Error(`TypeLiteral had ${strKind(nx)}`);
- };
- n.members.forEach(g);
- // for some the generated type is wanted, for others it's not needed
- if (!nm.startsWith('workspace')) {
- if (res.startsWith('struct')) return res + '}'; // map[] is special
- return res;
- }
- // these names have to be made unique
- const genName = `${goName(nm)}${extraTypes.size}Gn`;
- extraTypes.set(genName, ans);
- return genName;
-}
-
-// print all the types and constants and extra types
-function outputTypes() {
- // generate go types alphabeticaly
- let v = Array.from(seenTypes.keys());
- v.sort();
- v.forEach((x) => toGo(seenTypes.get(x), x));
- u.prgo(u.computeHeader(true));
- u.prgo('import "encoding/json"\n\n');
- typesOut.forEach((s) => {
- u.prgo(s);
- // it's more convenient not to have to think about trailing newlines
- // when generating types, but doc comments can't have an extra \n
- if (s.indexOf('/**') < 0) u.prgo('\n');
- });
- u.prgo('\nconst (\n');
- constsOut.forEach((s) => {
- u.prgo(s);
- u.prgo('\n');
- });
- u.prgo(')\n');
- u.prgo('// Types created to name formal parameters and embedded structs\n');
- extraTypes.forEach((v, k) => {
- u.prgo(` type ${k} struct {\n`);
- v.forEach((s) => {
- u.prgo(s);
- u.prgo('\n');
- });
- u.prgo('}\n');
- });
-}
-
-// client and server ------------------
-
-interface side {
- methods: string[];
- cases: string[];
- calls: string[];
- name: string; // client or server
- goName: string; // Client or Server
- outputFile?: string;
- fd?: number
-}
-let client: side = {
- methods: [],
- cases: [],
- calls: [],
- name: 'client',
- goName: 'Client',
-};
-let server: side = {
- methods: [],
- cases: [],
- calls: [],
- name: 'server',
- goName: 'Server',
-};
-
-// commonly used output
-const notNil = `if len(r.Params()) > 0 {
- return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
-}`;
-
-// Go code for notifications. Side is client or server, m is the request
-// method
-function goNot(side: side, m: string) {
- if (m == '$/cancelRequest') return; // handled specially in protocol.go
- const n = not.get(m);
- const a = goType(n.typeArguments[0], m);
- const nm = methodName(m);
- side.methods.push(sig(nm, a, ''));
- const caseHdr = ` case "${m}": // notif`;
- let case1 = notNil;
- if (a != '' && a != 'void') {
- case1 = `var params ${a}
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }
- err:= ${side.name}.${nm}(ctx, &params)
- return true, reply(ctx, nil, err)`;
- } else {
- case1 = `err := ${side.name}.${nm}(ctx)
- return true, reply(ctx, nil, err)`;
- }
- side.cases.push(`${caseHdr}\n${case1}`);
-
- const arg3 = a == '' || a == 'void' ? 'nil' : 'params';
- side.calls.push(`
- func (s *${side.name}Dispatcher) ${sig(nm, a, '', true)} {
- return s.sender.Notify(ctx, "${m}", ${arg3})
- }`);
-}
-
-// Go code for requests.
-function goReq(side: side, m: string) {
- const n = req.get(m);
- const nm = methodName(m);
- let a = goType(n.typeArguments[0], m);
- let b = goType(n.typeArguments[1], m);
- if (n.getText().includes('Type0')) {
- b = a;
- a = ''; // workspace/workspaceFolders and shutdown
- }
- u.prb(`${side.name} req ${a != ''}, ${b != ''} ${nm} ${m} ${loc(n)} `);
- side.methods.push(sig(nm, a, b));
-
- const caseHdr = `case "${m}": // req`;
- let case1 = notNil;
- if (a != '') {
- if (extraTypes.has('Param' + nm)) a = 'Param' + nm;
- case1 = `var params ${a}
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- return true, sendParseError(ctx, reply, err)
- }`;
- if (a === 'ParamInitialize') {
- case1 = `var params ${a}
- if err := json.Unmarshal(r.Params(), &params); err != nil {
- if _, ok := err.(*json.UnmarshalTypeError); !ok {
- return true, sendParseError(ctx, reply, err)
- }
- }`;
- }
- }
- const arg2 = a == '' ? '' : ', &params';
- // if case2 is not explicitly typed string, typescript makes it a union of strings
- let case2: string = `if err := ${side.name}.${nm}(ctx${arg2}); err != nil {
- event.Error(ctx, "", err)
- }`;
- if (b != '' && b != 'void') {
- case2 = `resp, err := ${side.name}.${nm}(ctx${arg2})
- return true, reply(ctx, resp, err)`;
- } else { // response is nil
- case2 = `err := ${side.name}.${nm}(ctx${arg2})
- return true, reply(ctx, nil, err)`;
- }
-
- side.cases.push(`${caseHdr}\n${case1}\n${case2}`);
-
- const callHdr = `func (s *${side.name}Dispatcher) ${sig(nm, a, b, true)} {`;
- let callBody = `return s.sender.Call(ctx, "${m}", nil, nil)\n}`;
- if (b != '' && b != 'void') {
- const p2 = a == '' ? 'nil' : 'params';
- const returnType = indirect(b) ? `*${b}` : b;
- callBody = `var result ${returnType}
- if err := s.sender.Call(ctx, "${m}", ${p2}, &result); err != nil {
- return nil, err
- }
- return result, nil
- }`;
- } else if (a != '') {
- callBody = `return s.sender.Call(ctx, "${m}", params, nil) // Call, not Notify
- }`;
- }
- side.calls.push(`${callHdr}\n${callBody}\n`);
-}
-
-// make sure method names are unique
-let seenNames = new Set<string>();
-function methodName(m: string): string {
- let i = m.indexOf('/');
- let s = m.substring(i + 1);
- let x = s[0].toUpperCase() + s.substring(1);
- for (let j = x.indexOf('/'); j >= 0; j = x.indexOf('/')) {
- let suffix = x.substring(j + 1);
- suffix = suffix[0].toUpperCase() + suffix.substring(1);
- let prefix = x.substring(0, j);
- x = prefix + suffix;
- }
- if (seenNames.has(x)) {
- // various Resolve and Diagnostic
- x += m[0].toUpperCase() + m.substring(1, i);
- }
- seenNames.add(x);
- return x;
-}
-
-// used in sig and in goReq
-function indirect(s: string): boolean {
- if (s == '' || s == 'void') return false;
- const skip = (x: string) => s.startsWith(x);
- if (skip('[]') || skip('interface') || skip('Declaration') ||
- skip('Definition') || skip('DocumentSelector'))
- return false;
- return true;
-}
-
-// Go signatures for methods.
-function sig(nm: string, a: string, b: string, names?: boolean): string {
- if (a.indexOf('struct') != -1) {
- const v = a.split('\n');
- extraTypes.set(`Param${nm}`, v.slice(1, v.length - 1));
- a = 'Param' + nm;
- }
- if (a == 'void')
- a = '';
- else if (a != '') {
- if (names)
- a = ', params *' + a;
- else
- a = ', *' + a;
- }
- let ret = 'error';
- if (b != '' && b != 'void') {
- // avoid * when it is senseless
- if (indirect(b)) b = '*' + b;
- ret = `(${b}, error)`;
- }
- let start = `${nm}(`;
- if (names) {
- start = start + 'ctx ';
- }
- return `${start}context.Context${a}) ${ret}`;
-}
-
-// write the request/notification code
-function output(side: side) {
- // make sure the output file exists
- if (!side.outputFile) {
- side.outputFile = `ts${side.name}.go`;
- side.fd = fs.openSync(side.outputFile, 'w');
- }
- const f = function (s: string) {
- fs.writeSync(side.fd!, s);
- fs.writeSync(side.fd!, '\n');
- };
- f(u.computeHeader(false));
- f(`
- import (
- "context"
- "encoding/json"
-
- "golang.org/x/tools/internal/jsonrpc2"
- errors "golang.org/x/xerrors"
- )
- `);
- const a = side.name[0].toUpperCase() + side.name.substring(1);
- f(`type ${a} interface {`);
- side.methods.forEach((v) => { f(v); });
- f('}\n');
- f(`func ${side.name}Dispatch(ctx context.Context, ${side.name} ${a}, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) {
- switch r.Method() {`);
- side.cases.forEach((v) => { f(v); });
- f(`
- default:
- return false, nil
- }
- }`);
- side.calls.forEach((v) => { f(v); });
-}
-
-// Handling of non-standard requests, so we can add gopls-specific calls.
-function nonstandardRequests() {
- server.methods.push(
- 'NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error)');
- server.calls.push(
- `func (s *serverDispatcher) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
- var result interface{}
- if err := s.sender.Call(ctx, method, params, &result); err != nil {
- return nil, err
- }
- return result, nil
- }
- `);
-}
-
-// ----- remember it's a scripting language
-function main() {
- if (u.gitHash != u.git()) {
- throw new Error(
- `git hash mismatch, wanted\n${u.gitHash} but source is at\n${u.git()}`);
- }
- u.createOutputFiles();
- parse();
- u.printAST(program);
- // find the Requests and Nofificatations
- for (const sourceFile of program.getSourceFiles()) {
- if (!sourceFile.isDeclarationFile) {
- ts.forEachChild(sourceFile, findRPCs);
- }
- }
- // separate RPCs into client and server
- setReceives();
- // visit every sourceFile collecting top-level type definitions
- for (const sourceFile of program.getSourceFiles()) {
- if (!sourceFile.isDeclarationFile) {
- ts.forEachChild(sourceFile, genTypes);
- }
- }
- // check that each thing occurs exactly once, and put pointers into
- // seenTypes
- checkOnce();
- // for each of Client and Server there are 3 parts to the output:
- // 1. type X interface {methods}
- // 2. func (h *serverHandler) Deliver(...) { switch r.method }
- // 3. func (x *xDispatcher) Method(ctx, parm)
- not.forEach( // notifications
- (v, k) => {
- receives.get(k) == 'client' ? goNot(client, k) : goNot(server, k);
- });
- req.forEach( // requests
- (v, k) => {
- receives.get(k) == 'client' ? goReq(client, k) : goReq(server, k);
- });
- nonstandardRequests();
- // find all the types implied by seenTypes and rpcs to try to avoid
- // generating types that aren't used
- moreTypes();
- // do merging
- cleanData();
- // and print the Go code
- outputTypes();
- console.log(`seen ${seenTypes.size + extraTypes.size}`);
- output(client);
- output(server);
-}
-
-main();
diff --git a/internal/lsp/protocol/typescript/tsconfig.json b/internal/lsp/protocol/typescript/tsconfig.json
deleted file mode 100644
index 14cfe0c7e..000000000
--- a/internal/lsp/protocol/typescript/tsconfig.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "compilerOptions": {
- "isolatedModules": true,
- "moduleResolution": "node",
- "lib":["ES2020"],
- "sourceMap": true, // sourceMap or inlineSourceMap? and see inlineSources
- "target": "ES5",
-
- "noFallthroughCasesInSwitch": false, // there is one legitimate on
- "noImplicitReturns": true,
- "noPropertyAccessFromIndexSignature": true,
- "noUncheckedIndexedAccess": true,
- "noUnusedLocals": true,
- "noUnusedParameters": false,
- "noEmitOnError": true,
-
- // "extendedDiagnostics": true, // for occasional amusement
-
- // "strict": true, // too many undefineds in types, etc
- "alwaysStrict": true,
- "noImplicitAny": true,
- "noImplicitThis": true,
- "strictBindCallApply": true,
- "strictFunctionTypes": true,
- "strictNullChecks": false, // doesn't like arrray access, among other things.
- //"strictPropertyInitialization": true, // needs strictNullChecks
- },
- "files": ["./code.ts", "./util.ts"]
-}
diff --git a/internal/lsp/protocol/typescript/util.ts b/internal/lsp/protocol/typescript/util.ts
deleted file mode 100644
index 9475b26a1..000000000
--- a/internal/lsp/protocol/typescript/util.ts
+++ /dev/null
@@ -1,254 +0,0 @@
-
-// for us typescript ignorati, having an import makes this file a module
-import * as fs from 'fs';
-import * as process from 'process';
-import * as ts from 'typescript';
-
-// This file contains various utilities having to do with producing strings
-// and managing output
-
-// ------ create files
-let dir = process.env['HOME'];
-const srcDir = '/vscode-languageserver-node';
-export const fnames = [
- `${dir}${srcDir}/protocol/src/common/protocol.ts`,
- `${dir}/${srcDir}/protocol/src/browser/main.ts`, `${dir}${srcDir}/types/src/main.ts`,
- `${dir}${srcDir}/jsonrpc/src/node/main.ts`
-];
-export const gitHash = '696f9285bf849b73745682fdb1c1feac73eb8772';
-let outFname = 'tsprotocol.go';
-let fda: number, fdb: number, fde: number; // file descriptors
-
-export function createOutputFiles() {
- fda = fs.openSync('/tmp/ts-a', 'w'); // dump of AST
- fdb = fs.openSync('/tmp/ts-b', 'w'); // unused, for debugging
- fde = fs.openSync(outFname, 'w'); // generated Go
-}
-export function pra(s: string) {
- return (fs.writeSync(fda, s));
-}
-export function prb(s: string) {
- return (fs.writeSync(fdb, s));
-}
-export function prgo(s: string) {
- return (fs.writeSync(fde, s));
-}
-
-// Get the hash value of the git commit
-export function git(): string {
- let a = fs.readFileSync(`${dir}${srcDir}/.git/HEAD`).toString();
- // ref: refs/heads/foo, or a hash like
- // cc12d1a1c7df935012cdef5d085cdba04a7c8ebe
- if (a.charAt(a.length - 1) == '\n') {
- a = a.substring(0, a.length - 1);
- }
- if (a.length == 40) {
- return a; // a hash
- }
- if (a.substring(0, 5) == 'ref: ') {
- const fname = `${dir}${srcDir}/.git/` + a.substring(5);
- let b = fs.readFileSync(fname).toString();
- if (b.length == 41) {
- return b.substring(0, 40);
- }
- }
- throw new Error('failed to find the git commit hash');
-}
-
-// Produce a header for Go output files
-export function computeHeader(pkgDoc: boolean): string {
- let lastMod = 0;
- let lastDate = new Date();
- for (const f of fnames) {
- const st = fs.statSync(f);
- if (st.mtimeMs > lastMod) {
- lastMod = st.mtimeMs;
- lastDate = st.mtime;
- }
- }
- const cp = `// 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.
-
- `;
- const a =
- '// Package protocol contains data types and code for LSP json rpcs\n' +
- '// generated automatically from vscode-languageserver-node\n' +
- `// commit: ${gitHash}\n` +
- `// last fetched ${lastDate}\n`;
- const b = 'package protocol\n';
- const c = '\n// Code generated (see typescript/README.md) DO NOT EDIT.\n\n';
- if (pkgDoc) {
- return cp + c + a + b;
- }
- else {
- return cp + c+ b + a;
- }
-}
-
-// Turn a typescript name into an exportable Go name, and appease lint
-export function goName(s: string): string {
- let ans = s;
- if (s.charAt(0) == '_') {
- // in the end, none of these are emitted.
- ans = 'Inner' + s.substring(1);
- }
- else { ans = s.substring(0, 1).toUpperCase() + s.substring(1); }
- ans = ans.replace(/Uri$/, 'URI');
- ans = ans.replace(/Id$/, 'ID');
- return ans;
-}
-
-// Generate JSON tag for a struct field
-export function JSON(n: ts.PropertySignature): string {
- const json = `\`json:"${n.name.getText()}${n.questionToken !== undefined ? ',omitempty' : ''}"\``;
- return json;
-}
-
-// Generate modifying prefixes and suffixes to ensure
-// consts are unique. (Go consts are package-level, but Typescript's are
-// not.) Use suffixes to minimize changes to gopls.
-export function constName(nm: string, type: string): string {
- let pref = new Map<string, string>([
- ['DiagnosticSeverity', 'Severity'], ['WatchKind', 'Watch'],
- ['SignatureHelpTriggerKind', 'Sig'], ['CompletionItemTag', 'Compl'],
- ['Integer', 'INT_'], ['Uinteger', 'UINT_'], ['CodeActionTriggerKind', 'CodeAction']
- ]); // typeName->prefix
- let suff = new Map<string, string>([
- ['CompletionItemKind', 'Completion'], ['InsertTextFormat', 'TextFormat'],
- ['SymbolTag', 'Symbol'], ['FileOperationPatternKind', 'Op'],
- ]);
- let ans = nm;
- if (pref.get(type)) ans = pref.get(type) + ans;
- if (suff.has(type)) ans = ans + suff.get(type);
- return ans;
-}
-
-// Find the comments associated with an AST node
-export function getComments(node: ts.Node): string {
- const sf = node.getSourceFile();
- const start = node.getStart(sf, false);
- const starta = node.getStart(sf, true);
- const x = sf.text.substring(starta, start);
- return x;
-}
-
-
-// --------- printing the AST, for debugging
-
-export function printAST(program: ts.Program) {
- // dump the ast, for debugging
- const f = function (n: ts.Node) {
- describe(n, pra);
- };
- for (const sourceFile of program.getSourceFiles()) {
- if (!sourceFile.isDeclarationFile) {
- // walk the tree to do stuff
- ts.forEachChild(sourceFile, f);
- }
- }
- pra('\n');
- for (const key of Object.keys(seenThings).sort()) {
- pra(`${key}: ${seenThings.get(key)} \n`);
- }
-}
-
-// Used in printing the AST
-let seenThings = new Map<string, number>();
-function seenAdd(x: string) {
- const u = seenThings.get(x);
- seenThings.set(x, u === undefined ? 1 : u + 1);
-}
-
-// eslint-disable-next-line no-unused-vars
-function describe(node: ts.Node, pr: (_: string) => any) {
- if (node === undefined) {
- return;
- }
- let indent = '';
-
- function f(n: ts.Node) {
- seenAdd(kinds(n));
- if (ts.isIdentifier(n)) {
- pr(`${indent} ${loc(n)} ${strKind(n)} ${n.text} \n`);
- }
- else if (ts.isPropertySignature(n) || ts.isEnumMember(n)) {
- pra(`${indent} ${loc(n)} ${strKind(n)} \n`);
- }
- else if (ts.isTypeLiteralNode(n)) {
- let m = n.members;
- pr(`${indent} ${loc(n)} ${strKind(n)} ${m.length} \n`);
- }
- else if (ts.isStringLiteral(n)) {
- pr(`${indent} ${loc(n)} ${strKind(n)} ${n.text} \n`);
- }
- else { pr(`${indent} ${loc(n)} ${strKind(n)} \n`); }
- indent += ' .';
- ts.forEachChild(n, f);
- indent = indent.slice(0, indent.length - 2);
- }
- f(node);
-}
-
-
-// For debugging, say where an AST node is in a file
-export function loc(node: ts.Node | undefined): string {
- if (!node) throw new Error('loc called with undefined (cannot happen!)');
- const sf = node.getSourceFile();
- const start = node.getStart();
- const x = sf.getLineAndCharacterOfPosition(start);
- const full = node.getFullStart();
- const y = sf.getLineAndCharacterOfPosition(full);
- let fn = sf.fileName;
- const n = fn.search(/-node./);
- fn = fn.substring(n + 6);
- return `${fn} ${x.line + 1}: ${x.character + 1} (${y.line + 1}: ${y.character + 1})`;
-}
-
-// --- various string stuff
-
-// return a string of the kinds of the immediate descendants
-// as part of printing the AST tree
-function kinds(n: ts.Node): string {
- let res = 'Seen ' + strKind(n);
- function f(n: ts.Node): void { res += ' ' + strKind(n); }
- ts.forEachChild(n, f);
- return res;
-}
-
-// What kind of AST node is it? This would just be typescript's
-// SyntaxKind[n.kind] except that the default names for some nodes
-// are misleading
-export function strKind(n: ts.Node | undefined): string {
- if (n == null || n == undefined) {
- return 'null';
- }
- return kindToStr(n.kind);
-}
-
-function kindToStr(k: ts.SyntaxKind): string {
- const x = ts.SyntaxKind[k];
- // some of these have two names
- switch (x) {
- default:
- return x;
- case 'FirstAssignment':
- return 'EqualsToken';
- case 'FirstBinaryOperator':
- return 'LessThanToken';
- case 'FirstCompoundAssignment':
- return 'PlusEqualsToken';
- case 'FirstContextualKeyword':
- return 'AbstractKeyword';
- case 'FirstLiteralToken':
- return 'NumericLiteral';
- case 'FirstNode':
- return 'QualifiedName';
- case 'FirstTemplateToken':
- return 'NoSubstitutionTemplateLiteral';
- case 'LastTemplateToken':
- return 'TemplateTail';
- case 'FirstTypeNode':
- return 'TypePredicate';
- }
-}
diff --git a/internal/lsp/references.go b/internal/lsp/references.go
deleted file mode 100644
index f96e5532c..000000000
--- a/internal/lsp/references.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/template"
-)
-
-func (s *Server) references(ctx context.Context, params *protocol.ReferenceParams) ([]protocol.Location, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
- defer release()
- if !ok {
- return nil, err
- }
- if snapshot.View().FileKind(fh) == source.Tmpl {
- return template.References(ctx, snapshot, fh, params)
- }
- references, err := source.References(ctx, snapshot, fh, params.Position, params.Context.IncludeDeclaration)
- if err != nil {
- return nil, err
- }
- var locations []protocol.Location
- for _, ref := range references {
- refRange, err := ref.Range()
- if err != nil {
- return nil, err
- }
- locations = append(locations, protocol.Location{
- URI: protocol.URIFromSpanURI(ref.URI()),
- Range: refRange,
- })
- }
- return locations, nil
-}
diff --git a/internal/lsp/regtest/doc.go b/internal/lsp/regtest/doc.go
deleted file mode 100644
index 3994e54cb..000000000
--- a/internal/lsp/regtest/doc.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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 regtest provides a framework for writing gopls regression tests.
-//
-// User reported regressions are often 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". In these cases reproducing, diagnosing, and writing a test to
-// protect against this regression can be difficult.
-//
-// The regtest package provides an API for developers to express these types of
-// user interactions in ordinary Go tests, validate them, and run them in a
-// variety of execution modes (see gopls/doc/daemon.md for more information on
-// execution modes). This is achieved roughly as follows:
-// + the Runner type starts and connects to a gopls instance for each
-// configured execution mode.
-// + the Env type provides a collection of resources to use in writing tests
-// (for example a temporary working directory and fake text editor)
-// + user interactions with these resources are scripted using test wrappers
-// around the API provided by the golang.org/x/tools/internal/lsp/fake
-// package.
-//
-// Regressions are expressed in terms of Expectations, which at a high level
-// are conditions that we expect to be met (or not to be met) at some point
-// after performing the interactions in the test. This is necessary because the
-// LSP is by construction asynchronous: both client and server can send
-// eachother notifications without formal acknowledgement that they have been
-// fully processed.
-//
-// Simple Expectations may be combined to match specific conditions reported by
-// the user. In the example above, a regtest validating that the user-reported
-// bug had been fixed would "expect" that the editor never displays the
-// confusing diagnostic.
-package regtest
diff --git a/internal/lsp/regtest/env.go b/internal/lsp/regtest/env.go
deleted file mode 100644
index b6b163a87..000000000
--- a/internal/lsp/regtest/env.go
+++ /dev/null
@@ -1,318 +0,0 @@
-// 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 regtest
-
-import (
- "context"
- "fmt"
- "strings"
- "sync"
- "testing"
-
- "golang.org/x/tools/internal/jsonrpc2/servertest"
- "golang.org/x/tools/internal/lsp/fake"
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-// Env holds an initialized fake Editor, Workspace, and Server, which may be
-// used for writing tests. It also provides adapter methods that call t.Fatal
-// on any error, so that tests for the happy path may be written without
-// checking errors.
-type Env struct {
- T testing.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
- Editor *fake.Editor
- Server servertest.Connector
-
- // mu guards the fields below, for the purpose of checking conditions on
- // every change to diagnostics.
- mu sync.Mutex
- // For simplicity, each waiter gets a unique ID.
- nextWaiterID int
- state State
- waiters map[int]*condition
-}
-
-// 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
- showMessage []*protocol.ShowMessageParams
- showMessageRequest []*protocol.ShowMessageRequestParams
-
- registrations []*protocol.RegistrationParams
- 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. When work
- // completes, it is deleted from this map.
- outstandingWork map[protocol.ProgressToken]*workProgress
- startedWork map[string]uint64
- completedWork map[string]uint64
-}
-
-type workProgress struct {
- title, msg string
- percent float64
-}
-
-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, int(params.Version))
- for _, d := range params.Diagnostics {
- fmt.Fprintf(&b, "\t\t(%d, %d): %s\n", int(d.Range.Start.Line), int(d.Range.Start.Character), d.Message)
- }
- }
- b.WriteString("\n")
- b.WriteString("#### outstanding work:\n")
- for token, state := range s.outstandingWork {
- 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
-}
-
-// NewEnv creates a new test environment using the given scratch environment
-// and gopls server.
-func NewEnv(ctx context.Context, tb testing.TB, sandbox *fake.Sandbox, ts servertest.Connector, editorConfig fake.EditorConfig, withHooks bool) *Env {
- tb.Helper()
- conn := ts.Connect(ctx)
- env := &Env{
- T: tb,
- Ctx: ctx,
- Sandbox: sandbox,
- Server: ts,
- state: State{
- diagnostics: make(map[string]*protocol.PublishDiagnosticsParams),
- outstandingWork: make(map[protocol.ProgressToken]*workProgress),
- startedWork: make(map[string]uint64),
- completedWork: make(map[string]uint64),
- },
- waiters: make(map[int]*condition),
- }
- var hooks fake.ClientHooks
- if withHooks {
- hooks = fake.ClientHooks{
- OnDiagnostics: env.onDiagnostics,
- OnLogMessage: env.onLogMessage,
- OnWorkDoneProgressCreate: env.onWorkDoneProgressCreate,
- OnProgress: env.onProgress,
- OnShowMessage: env.onShowMessage,
- OnShowMessageRequest: env.onShowMessageRequest,
- OnRegistration: env.onRegistration,
- OnUnregistration: env.onUnregistration,
- }
- }
- editor, err := fake.NewEditor(sandbox, editorConfig).Connect(ctx, conn, hooks)
- if err != nil {
- tb.Fatal(err)
- }
- env.Editor = editor
- return env
-}
-
-func (e *Env) onDiagnostics(_ context.Context, d *protocol.PublishDiagnosticsParams) error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- pth := e.Sandbox.Workdir.URIToPath(d.URI)
- e.state.diagnostics[pth] = d
- e.checkConditionsLocked()
- return nil
-}
-
-func (e *Env) onShowMessage(_ context.Context, m *protocol.ShowMessageParams) error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- e.state.showMessage = append(e.state.showMessage, m)
- e.checkConditionsLocked()
- return nil
-}
-
-func (e *Env) onShowMessageRequest(_ context.Context, m *protocol.ShowMessageRequestParams) error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- e.state.showMessageRequest = append(e.state.showMessageRequest, m)
- e.checkConditionsLocked()
- return nil
-}
-
-func (e *Env) onLogMessage(_ context.Context, m *protocol.LogMessageParams) error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- e.state.logs = append(e.state.logs, m)
- e.checkConditionsLocked()
- return nil
-}
-
-func (e *Env) onWorkDoneProgressCreate(_ context.Context, m *protocol.WorkDoneProgressCreateParams) error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- e.state.outstandingWork[m.Token] = &workProgress{}
- return nil
-}
-
-func (e *Env) onProgress(_ context.Context, m *protocol.ProgressParams) error {
- e.mu.Lock()
- defer e.mu.Unlock()
- work, ok := e.state.outstandingWork[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)
- e.state.startedWork[work.title] = e.state.startedWork[work.title] + 1
- 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":
- title := e.state.outstandingWork[m.Token].title
- e.state.completedWork[title] = e.state.completedWork[title] + 1
- delete(e.state.outstandingWork, m.Token)
- }
- e.checkConditionsLocked()
- return nil
-}
-
-func (e *Env) onRegistration(_ context.Context, m *protocol.RegistrationParams) error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- e.state.registrations = append(e.state.registrations, m)
- e.checkConditionsLocked()
- return nil
-}
-
-func (e *Env) onUnregistration(_ context.Context, m *protocol.UnregistrationParams) error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- e.state.unregistrations = append(e.state.unregistrations, m)
- e.checkConditionsLocked()
- return nil
-}
-
-func (e *Env) checkConditionsLocked() {
- for id, condition := range e.waiters {
- if v, _ := checkExpectations(e.state, condition.expectations); v != Unmet {
- delete(e.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
- }
- summary.WriteString(fmt.Sprintf("\t%v: %s\n", v, e.Description()))
- }
- return finalVerdict, summary.String()
-}
-
-// DiagnosticsFor returns the current diagnostics for the file. It is useful
-// after waiting on AnyDiagnosticAtCurrentVersion, when the desired diagnostic
-// is not simply described by DiagnosticAt.
-func (e *Env) DiagnosticsFor(name string) *protocol.PublishDiagnosticsParams {
- e.mu.Lock()
- defer e.mu.Unlock()
- return e.state.diagnostics[name]
-}
-
-// Await waits for all expectations to simultaneously be met. It should only be
-// called from the main test goroutine.
-func (e *Env) Await(expectations ...Expectation) {
- e.T.Helper()
- e.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(e.state, expectations); verdict {
- case Met:
- e.mu.Unlock()
- return
- case Unmeetable:
- failure := fmt.Sprintf("unmeetable expectations:\n%s\nstate:\n%v", summary, e.state)
- e.mu.Unlock()
- e.T.Fatal(failure)
- }
- cond := &condition{
- expectations: expectations,
- verdict: make(chan Verdict),
- }
- e.waiters[e.nextWaiterID] = cond
- e.nextWaiterID++
- e.mu.Unlock()
-
- var err error
- select {
- case <-e.Ctx.Done():
- err = e.Ctx.Err()
- case v := <-cond.verdict:
- if v != Met {
- err = fmt.Errorf("condition has final verdict %v", v)
- }
- }
- e.mu.Lock()
- defer e.mu.Unlock()
- _, summary := checkExpectations(e.state, expectations)
-
- // Debugging an unmet expectation can be tricky, so we put some effort into
- // nicely formatting the failure.
- if err != nil {
- e.T.Fatalf("waiting on:\n%s\nerr:%v\n\nstate:\n%v", summary, err, e.state)
- }
-}
diff --git a/internal/lsp/regtest/env_test.go b/internal/lsp/regtest/env_test.go
deleted file mode 100644
index fe5864ca7..000000000
--- a/internal/lsp/regtest/env_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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 regtest
-
-import (
- "context"
- "encoding/json"
- "testing"
-
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-func TestProgressUpdating(t *testing.T) {
- e := &Env{
- state: State{
- outstandingWork: make(map[protocol.ProgressToken]*workProgress),
- startedWork: make(map[string]uint64),
- completedWork: make(map[string]uint64),
- },
- }
- ctx := context.Background()
- if err := e.onWorkDoneProgressCreate(ctx, &protocol.WorkDoneProgressCreateParams{
- Token: "foo",
- }); err != nil {
- t.Fatal(err)
- }
- if err := e.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 := e.onProgress(ctx, &unmarshaled); err != nil {
- t.Fatal(err)
- }
- }
- if _, ok := e.state.outstandingWork["foo"]; ok {
- t.Error("got work entry for \"foo\", want none")
- }
- got := *e.state.outstandingWork["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/internal/lsp/regtest/expectation.go b/internal/lsp/regtest/expectation.go
deleted file mode 100644
index 5cf2b6c15..000000000
--- a/internal/lsp/regtest/expectation.go
+++ /dev/null
@@ -1,668 +0,0 @@
-// 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 regtest
-
-import (
- "fmt"
- "regexp"
- "strings"
-
- "golang.org/x/tools/internal/lsp"
- "golang.org/x/tools/internal/lsp/fake"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/testenv"
-)
-
-// An Expectation asserts that the state of the editor at a point in time
-// matches an expected condition. This is used for signaling in tests when
-// certain conditions in the editor are met.
-type Expectation interface {
- // Check determines whether the state of the editor satisfies the
- // expectation, returning the results that met the condition.
- Check(State) Verdict
- // Description is a human-readable description of the expectation.
- Description() string
-}
-
-var (
- // InitialWorkspaceLoad is an expectation that the workspace initial load has
- // completed. It is verified via workdone reporting.
- InitialWorkspaceLoad = CompletedWork(lsp.DiagnosticWorkTitle(lsp.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)
-}
-
-// SimpleExpectation holds an arbitrary check func, and implements the Expectation interface.
-type SimpleExpectation struct {
- check func(State) Verdict
- description string
-}
-
-// Check invokes e.check.
-func (e SimpleExpectation) Check(s State) Verdict {
- return e.check(s)
-}
-
-// Description returns e.descriptin.
-func (e SimpleExpectation) Description() string {
- return e.description
-}
-
-// OnceMet returns an Expectation that, once the precondition is met, asserts
-// that mustMeet is met.
-func OnceMet(precondition Expectation, mustMeets ...Expectation) *SimpleExpectation {
- 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
- }
- }
- var descriptions []string
- for _, mustMeet := range mustMeets {
- descriptions = append(descriptions, mustMeet.Description())
- }
- return &SimpleExpectation{
- check: check,
- description: fmt.Sprintf("once %q is met, must have %q", precondition.Description(), strings.Join(descriptions, "\n")),
- }
-}
-
-// ReadDiagnostics is an 'expectation' that is used to read diagnostics
-// atomically. It is intended to be used with 'OnceMet'.
-func ReadDiagnostics(fileName string, into *protocol.PublishDiagnosticsParams) *SimpleExpectation {
- check := func(s State) Verdict {
- diags, ok := s.diagnostics[fileName]
- if !ok {
- return Unmeetable
- }
- *into = *diags
- return Met
- }
- return &SimpleExpectation{
- check: check,
- description: fmt.Sprintf("read diagnostics for %q", fileName),
- }
-}
-
-// NoOutstandingWork asserts that there is no work initiated using the LSP
-// $/progress API that has not completed.
-func NoOutstandingWork() SimpleExpectation {
- check := func(s State) Verdict {
- if len(s.outstandingWork) == 0 {
- return Met
- }
- return Unmet
- }
- return SimpleExpectation{
- check: check,
- description: "no outstanding work",
- }
-}
-
-// NoShowMessage asserts that the editor has not received a ShowMessage.
-func NoShowMessage() SimpleExpectation {
- check := func(s State) Verdict {
- if len(s.showMessage) == 0 {
- return Met
- }
- return Unmeetable
- }
- return SimpleExpectation{
- check: check,
- description: "no ShowMessage received",
- }
-}
-
-// ShownMessage asserts that the editor has received a ShownMessage with the
-// given title.
-func ShownMessage(title string) SimpleExpectation {
- check := func(s State) Verdict {
- for _, m := range s.showMessage {
- if strings.Contains(m.Message, title) {
- return Met
- }
- }
- return Unmet
- }
- return SimpleExpectation{
- check: check,
- description: "received ShowMessage",
- }
-}
-
-// ShowMessageRequest asserts that the editor has received a ShowMessageRequest
-// with an action item that has the given title.
-func ShowMessageRequest(title string) SimpleExpectation {
- check := func(s State) Verdict {
- if len(s.showMessageRequest) == 0 {
- return Unmet
- }
- // Only check the most recent one.
- m := s.showMessageRequest[len(s.showMessageRequest)-1]
- if len(m.Actions) == 0 || len(m.Actions) > 1 {
- return Unmet
- }
- if m.Actions[0].Title == title {
- return Met
- }
- return Unmet
- }
- return SimpleExpectation{
- check: check,
- description: "received ShowMessageRequest",
- }
-}
-
-// 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(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), opens, true)
-}
-
-// StartedChange expects there to have been i work items started for
-// processing didChange notifications.
-func StartedChange(i uint64) Expectation {
- return StartedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChange), i)
-}
-
-// 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(lsp.DiagnosticWorkTitle(lsp.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(lsp.DiagnosticWorkTitle(lsp.FromDidSave), saves, true)
-}
-
-// 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(lsp.DiagnosticWorkTitle(lsp.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(lsp.DiagnosticWorkTitle(lsp.FromDidClose), changes, true)
-}
-
-// StartedWork expect a work item to have been started >= atLeast times.
-//
-// See CompletedWork.
-func StartedWork(title string, atLeast uint64) SimpleExpectation {
- check := func(s State) Verdict {
- if s.startedWork[title] >= atLeast {
- return Met
- }
- return Unmet
- }
- return SimpleExpectation{
- 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) SimpleExpectation {
- check := func(s State) Verdict {
- if s.completedWork[title] == count || atLeast && s.completedWork[title] > 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 SimpleExpectation{
- 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) SimpleExpectation {
- check := func(s State) Verdict {
- for _, work := range s.outstandingWork {
- if work.title == title && strings.Contains(work.msg, msg) {
- return Met
- }
- }
- return Unmet
- }
- return SimpleExpectation{
- check: check,
- description: fmt.Sprintf("outstanding work: %s", title),
- }
-}
-
-// LogExpectation is an expectation on the log messages received by the editor
-// from gopls.
-type LogExpectation struct {
- check func([]*protocol.LogMessageParams) Verdict
- description string
-}
-
-// Check implements the Expectation interface.
-func (e LogExpectation) Check(s State) Verdict {
- return e.check(s.logs)
-}
-
-// Description implements the Expectation interface.
-func (e LogExpectation) Description() string {
- return e.description
-}
-
-// NoErrorLogs asserts that the client has not received any log messages of
-// error severity.
-func NoErrorLogs() LogExpectation {
- return NoLogMatching(protocol.Error, "")
-}
-
-// LogMatching asserts that the client has received a log message
-// of type typ matching the regexp re.
-func LogMatching(typ protocol.MessageType, re string, count int, atLeast bool) LogExpectation {
- rec, err := regexp.Compile(re)
- if err != nil {
- panic(err)
- }
- check := func(msgs []*protocol.LogMessageParams) Verdict {
- var found int
- for _, msg := range msgs {
- 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
- }
- 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 LogExpectation{
- 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) LogExpectation {
- var r *regexp.Regexp
- if re != "" {
- var err error
- r, err = regexp.Compile(re)
- if err != nil {
- panic(err)
- }
- }
- check := func(msgs []*protocol.LogMessageParams) Verdict {
- for _, msg := range msgs {
- if msg.Type != typ {
- continue
- }
- if r == nil || r.Match([]byte(msg.Message)) {
- return Unmeetable
- }
- }
- return Met
- }
- return LogExpectation{
- check: check,
- description: fmt.Sprintf("no log message matching %q", re),
- }
-}
-
-// RegistrationExpectation is an expectation on the capability registrations
-// received by the editor from gopls.
-type RegistrationExpectation struct {
- check func([]*protocol.RegistrationParams) Verdict
- description string
-}
-
-// Check implements the Expectation interface.
-func (e RegistrationExpectation) Check(s State) Verdict {
- return e.check(s.registrations)
-}
-
-// Description implements the Expectation interface.
-func (e RegistrationExpectation) Description() string {
- return e.description
-}
-
-// RegistrationMatching asserts that the client has received a capability
-// registration matching the given regexp.
-func RegistrationMatching(re string) RegistrationExpectation {
- rec, err := regexp.Compile(re)
- if err != nil {
- panic(err)
- }
- check := func(params []*protocol.RegistrationParams) Verdict {
- for _, p := range params {
- for _, r := range p.Registrations {
- if rec.Match([]byte(r.Method)) {
- return Met
- }
- }
- }
- return Unmet
- }
- return RegistrationExpectation{
- check: check,
- description: fmt.Sprintf("registration matching %q", re),
- }
-}
-
-// UnregistrationExpectation is an expectation on the capability
-// unregistrations received by the editor from gopls.
-type UnregistrationExpectation struct {
- check func([]*protocol.UnregistrationParams) Verdict
- description string
-}
-
-// Check implements the Expectation interface.
-func (e UnregistrationExpectation) Check(s State) Verdict {
- return e.check(s.unregistrations)
-}
-
-// Description implements the Expectation interface.
-func (e UnregistrationExpectation) Description() string {
- return e.description
-}
-
-// UnregistrationMatching asserts that the client has received an
-// unregistration whose ID matches the given regexp.
-func UnregistrationMatching(re string) UnregistrationExpectation {
- rec, err := regexp.Compile(re)
- if err != nil {
- panic(err)
- }
- check := func(params []*protocol.UnregistrationParams) Verdict {
- for _, p := range params {
- for _, r := range p.Unregisterations {
- if rec.Match([]byte(r.Method)) {
- return Met
- }
- }
- }
- return Unmet
- }
- return UnregistrationExpectation{
- check: check,
- description: fmt.Sprintf("unregistration matching %q", re),
- }
-}
-
-// A DiagnosticExpectation is a condition that must be met by the current set
-// of diagnostics for a file.
-type DiagnosticExpectation struct {
- // optionally, the position of the diagnostic and the regex used to calculate it.
- pos *fake.Pos
- re string
-
- // optionally, the message that the diagnostic should contain.
- message string
-
- // whether the expectation is that the diagnostic is present, or absent.
- present bool
-
- // path is the scratch workdir-relative path to the file being asserted on.
- path string
-}
-
-// Check implements the Expectation interface.
-func (e DiagnosticExpectation) Check(s State) Verdict {
- diags, ok := s.diagnostics[e.path]
- if !ok {
- if !e.present {
- return Met
- }
- return Unmet
- }
-
- found := false
- for _, d := range diags.Diagnostics {
- if e.pos != nil {
- if d.Range.Start.Line != uint32(e.pos.Line) || d.Range.Start.Character != uint32(e.pos.Column) {
- continue
- }
- }
- if e.message != "" {
- if !strings.Contains(d.Message, e.message) {
- continue
- }
- }
- found = true
- break
- }
-
- if found == e.present {
- return Met
- }
- return Unmet
-}
-
-// Description implements the Expectation interface.
-func (e DiagnosticExpectation) Description() string {
- desc := e.path + ":"
- if !e.present {
- desc += " no"
- }
- desc += " diagnostic"
- if e.pos != nil {
- desc += fmt.Sprintf(" at {line:%d, column:%d}", e.pos.Line, e.pos.Column)
- if e.re != "" {
- desc += fmt.Sprintf(" (location of %q)", e.re)
- }
- }
- if e.message != "" {
- desc += fmt.Sprintf(" with message %q", e.message)
- }
- return desc
-}
-
-// NoOutstandingDiagnostics asserts that the workspace has no outstanding
-// diagnostic messages.
-func NoOutstandingDiagnostics() Expectation {
- check := func(s State) Verdict {
- for _, diags := range s.diagnostics {
- if len(diags.Diagnostics) > 0 {
- return Unmet
- }
- }
- return Met
- }
- return SimpleExpectation{
- check: check,
- description: "no outstanding diagnostics",
- }
-}
-
-// EmptyDiagnostics asserts that empty diagnostics are sent for the
-// workspace-relative path name.
-func EmptyDiagnostics(name string) Expectation {
- check := func(s State) Verdict {
- if diags := s.diagnostics[name]; diags != nil && len(diags.Diagnostics) == 0 {
- return Met
- }
- return Unmet
- }
- return SimpleExpectation{
- check: check,
- description: fmt.Sprintf("empty diagnostics for %q", name),
- }
-}
-
-// EmptyOrNoDiagnostics asserts that either no diagnostics are sent for the
-// workspace-relative path name, or empty diagnostics are sent.
-// TODO(rFindley): this subtlety shouldn't be necessary. Gopls should always
-// send at least one diagnostic set for open files.
-func EmptyOrNoDiagnostics(name string) Expectation {
- check := func(s State) Verdict {
- if diags := s.diagnostics[name]; diags == nil || len(diags.Diagnostics) == 0 {
- return Met
- }
- return Unmet
- }
- return SimpleExpectation{
- check: check,
- description: fmt.Sprintf("empty or no diagnostics for %q", name),
- }
-}
-
-// NoDiagnostics asserts that no diagnostics are sent for the
-// workspace-relative path name. It should be used primarily in conjunction
-// with a OnceMet, as it has to check that all outstanding diagnostics have
-// already been delivered.
-func NoDiagnostics(name string) Expectation {
- check := func(s State) Verdict {
- if _, ok := s.diagnostics[name]; !ok {
- return Met
- }
- return Unmet
- }
- return SimpleExpectation{
- check: check,
- description: "no diagnostics",
- }
-}
-
-// AnyDiagnosticAtCurrentVersion asserts that there is a diagnostic report for
-// the current edited version of the buffer corresponding to the given
-// workdir-relative pathname.
-func (e *Env) AnyDiagnosticAtCurrentVersion(name string) Expectation {
- version := e.Editor.BufferVersion(name)
- check := func(s State) Verdict {
- diags, ok := s.diagnostics[name]
- if ok && diags.Version == int32(version) {
- return Met
- }
- return Unmet
- }
- return SimpleExpectation{
- check: check,
- description: fmt.Sprintf("any diagnostics at version %d", version),
- }
-}
-
-// DiagnosticAtRegexp expects that there is a diagnostic entry at the start
-// position matching the regexp search string re in the buffer specified by
-// name. Note that this currently ignores the end position.
-func (e *Env) DiagnosticAtRegexp(name, re string) DiagnosticExpectation {
- e.T.Helper()
- pos := e.RegexpSearch(name, re)
- return DiagnosticExpectation{path: name, pos: &pos, re: re, present: true}
-}
-
-// DiagnosticAtRegexpWithMessage is like DiagnosticAtRegexp, but it also
-// checks for the content of the diagnostic message,
-func (e *Env) DiagnosticAtRegexpWithMessage(name, re, msg string) DiagnosticExpectation {
- e.T.Helper()
- pos := e.RegexpSearch(name, re)
- return DiagnosticExpectation{path: name, pos: &pos, re: re, present: true, message: msg}
-}
-
-// DiagnosticAt asserts that there is a diagnostic entry at the position
-// specified by line and col, for the workdir-relative path name.
-func DiagnosticAt(name string, line, col int) DiagnosticExpectation {
- return DiagnosticExpectation{path: name, pos: &fake.Pos{Line: line, Column: col}, present: true}
-}
-
-// NoDiagnosticAtRegexp expects that there is no diagnostic entry at the start
-// position matching the regexp search string re in the buffer specified by
-// name. Note that this currently ignores the end position.
-// This should only be used in combination with OnceMet for a given condition,
-// otherwise it may always succeed.
-func (e *Env) NoDiagnosticAtRegexp(name, re string) DiagnosticExpectation {
- e.T.Helper()
- pos := e.RegexpSearch(name, re)
- return DiagnosticExpectation{path: name, pos: &pos, re: re, present: false}
-}
-
-// NoDiagnosticAt asserts that there is no diagnostic entry at the position
-// specified by line and col, for the workdir-relative path name.
-// This should only be used in combination with OnceMet for a given condition,
-// otherwise it may always succeed.
-func NoDiagnosticAt(name string, line, col int) DiagnosticExpectation {
- return DiagnosticExpectation{path: name, pos: &fake.Pos{Line: line, Column: col}, present: false}
-}
-
-// NoDiagnosticWithMessage asserts that there is no diagnostic entry with the
-// given message.
-//
-// This should only be used in combination with OnceMet for a given condition,
-// otherwise it may always succeed.
-func NoDiagnosticWithMessage(name, msg string) DiagnosticExpectation {
- return DiagnosticExpectation{path: name, message: msg, present: false}
-}
-
-// GoSumDiagnostic asserts that a "go.sum is out of sync" diagnostic for the
-// given module (as formatted in a go.mod file, e.g. "example.com v1.0.0") is
-// present.
-func (e *Env) GoSumDiagnostic(name, module string) Expectation {
- e.T.Helper()
- // In 1.16, go.sum diagnostics should appear on the relevant module. Earlier
- // errors have no information and appear on the module declaration.
- if testenv.Go1Point() >= 16 {
- return e.DiagnosticAtRegexpWithMessage(name, module, "go.sum is out of sync")
- } else {
- return e.DiagnosticAtRegexpWithMessage(name, `module`, "go.sum is out of sync")
- }
-}
diff --git a/internal/lsp/regtest/regtest.go b/internal/lsp/regtest/regtest.go
deleted file mode 100644
index 31806233c..000000000
--- a/internal/lsp/regtest/regtest.go
+++ /dev/null
@@ -1,148 +0,0 @@
-// 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 regtest
-
-import (
- "context"
- "flag"
- "fmt"
- "io/ioutil"
- "os"
- "runtime"
- "testing"
- "time"
-
- "golang.org/x/tools/internal/lsp/cmd"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/testenv"
- "golang.org/x/tools/internal/tool"
-)
-
-var (
- runSubprocessTests = flag.Bool("enable_gopls_subprocess_tests", false, "run regtests against a gopls subprocess")
- 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")
- regtestTimeout = flag.Duration("regtest_timeout", defaultRegtestTimeout(), "if nonzero, default timeout for each regtest; defaults to GOPLS_REGTEST_TIMEOUT")
- skipCleanup = flag.Bool("regtest_skip_cleanup", false, "whether to skip cleaning up temp directories")
- printGoroutinesOnFailure = flag.Bool("regtest_print_goroutines", false, "whether to print goroutines info on failure")
-)
-
-func defaultRegtestTimeout() time.Duration {
- s := os.Getenv("GOPLS_REGTEST_TIMEOUT")
- if s == "" {
- return 0
- }
- d, err := time.ParseDuration(s)
- if err != nil {
- fmt.Fprintf(os.Stderr, "invalid GOPLS_REGTEST_TIMEOUT %q: %v\n", s, err)
- os.Exit(2)
- }
- return d
-}
-
-var runner *Runner
-
-type regtestRunner interface {
- Run(t *testing.T, files string, f TestFunc)
-}
-
-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) {
- runner.Run(t, files, f, r.opts...)
-}
-
-type RunMultiple []struct {
- Name string
- Runner regtestRunner
-}
-
-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)
- })
- }
-}
-
-// The regtests run significantly slower on these operating systems, due to (we
-// believe) kernel locking behavior. Only run in singleton mode on these
-// operating system when using -short.
-var slowGOOS = map[string]bool{
- "darwin": true,
- "openbsd": true,
- "plan9": true,
-}
-
-func DefaultModes() Mode {
- normal := Singleton | Experimental
- if slowGOOS[runtime.GOOS] && testing.Short() {
- normal = Singleton
- }
- if *runSubprocessTests {
- return normal | SeparateProcess
- }
- return normal
-}
-
-// Main sets up and tears down the shared regtest state.
-func Main(m *testing.M, hook func(*source.Options)) {
- testenv.ExitIfSmallMachine()
-
- // Disable GOPACKAGESDRIVER, as it can cause spurious test failures.
- os.Setenv("GOPACKAGESDRIVER", "off")
-
- flag.Parse()
- if os.Getenv("_GOPLS_TEST_BINARY_RUN_AS_GOPLS") == "true" {
- tool.Main(context.Background(), cmd.New("gopls", "", nil, nil), os.Args[1:])
- os.Exit(0)
- }
-
- runner = &Runner{
- DefaultModes: DefaultModes(),
- Timeout: *regtestTimeout,
- PrintGoroutinesOnFailure: *printGoroutinesOnFailure,
- SkipCleanup: *skipCleanup,
- OptionsHook: hook,
- }
- if *runSubprocessTests {
- goplsPath := *goplsBinaryPath
- if goplsPath == "" {
- var err error
- goplsPath, err = os.Executable()
- if err != nil {
- panic(fmt.Sprintf("finding test binary path: %v", err))
- }
- }
- runner.GoplsPath = goplsPath
- }
- dir, err := ioutil.TempDir("", "gopls-regtest-")
- if err != nil {
- panic(fmt.Errorf("creating regtest temp directory: %v", err))
- }
- runner.TempDir = dir
-
- code := m.Run()
- if err := runner.Close(); err != nil {
- fmt.Fprintf(os.Stderr, "closing test runner: %v\n", err)
- // Regtest 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" {
- os.Exit(1)
- }
- }
- os.Exit(code)
-}
diff --git a/internal/lsp/regtest/runner.go b/internal/lsp/regtest/runner.go
deleted file mode 100644
index 822a5a315..000000000
--- a/internal/lsp/regtest/runner.go
+++ /dev/null
@@ -1,533 +0,0 @@
-// 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 regtest
-
-import (
- "bytes"
- "context"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "os"
- "path/filepath"
- "runtime/pprof"
- "strings"
- "sync"
- "testing"
- "time"
-
- exec "golang.org/x/sys/execabs"
-
- "golang.org/x/tools/internal/jsonrpc2"
- "golang.org/x/tools/internal/jsonrpc2/servertest"
- "golang.org/x/tools/internal/lsp/cache"
- "golang.org/x/tools/internal/lsp/debug"
- "golang.org/x/tools/internal/lsp/fake"
- "golang.org/x/tools/internal/lsp/lsprpc"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "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.
-type Mode int
-
-const (
- // Singleton mode uses a separate in-process gopls instance for each test,
- // and communicates over pipes to mimic the gopls sidecar execution mode,
- // which communicates over stdin/stderr.
- Singleton Mode = 1 << iota
- // Forwarded forwards connections to a shared in-process gopls instance.
- Forwarded
- // SeparateProcess forwards connection to a shared separate gopls process.
- SeparateProcess
- // Experimental enables all of the experimental configurations that are
- // being developed.
- Experimental
-)
-
-// 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 {
- DefaultModes Mode
- Timeout time.Duration
- GoplsPath string
- PrintGoroutinesOnFailure bool
- TempDir string
- SkipCleanup bool
- OptionsHook func(*source.Options)
-
- mu sync.Mutex
- ts *servertest.TCPServer
- socketDir string
- // closers is a queue of clean-up functions to run at the end of the entire
- // test suite.
- closers []io.Closer
-}
-
-type runConfig struct {
- editor fake.EditorConfig
- sandbox fake.SandboxConfig
- modes Mode
- noDefaultTimeout bool
- debugAddr string
- skipLogs bool
- skipHooks bool
- optionsHook func(*source.Options)
-}
-
-func (r *Runner) defaultConfig() *runConfig {
- return &runConfig{
- modes: r.DefaultModes,
- optionsHook: r.OptionsHook,
- }
-}
-
-// 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)
-}
-
-// NoDefaultTimeout removes the timeout set by the -regtest_timeout flag, for
-// individual tests that are expected to run longer than is reasonable for
-// ordinary regression tests.
-func NoDefaultTimeout() RunOption {
- return optionSetter(func(opts *runConfig) {
- opts.noDefaultTimeout = true
- })
-}
-
-// 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)
- })
-}
-
-// Modes configures the execution modes that the test should run in.
-func Modes(modes Mode) RunOption {
- return optionSetter(func(opts *runConfig) {
- opts.modes = modes
- })
-}
-
-// Options configures the various server and user options.
-func Options(hook func(*source.Options)) RunOption {
- return optionSetter(func(opts *runConfig) {
- old := opts.optionsHook
- opts.optionsHook = func(o *source.Options) {
- if old != nil {
- old(o)
- }
- hook(o)
- }
- })
-}
-
-func SendPID() RunOption {
- return optionSetter(func(opts *runConfig) {
- opts.editor.SendPID = true
- })
-}
-
-// EditorConfig is a RunOption option that configured the regtest editor.
-type EditorConfig fake.EditorConfig
-
-func (c EditorConfig) set(opts *runConfig) {
- opts.editor = fake.EditorConfig(c)
-}
-
-// WorkspaceFolders configures the workdir-relative workspace folders 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
- })
-}
-
-// 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
- })
-}
-
-// DebugAddress configures a debug server bound to addr. This option is
-// currently only supported when executing in Singleton mode. It is intended to
-// be used for long-running stress tests.
-func DebugAddress(addr string) RunOption {
- return optionSetter(func(opts *runConfig) {
- opts.debugAddr = addr
- })
-}
-
-// SkipLogs skips the buffering of logs during test execution. It is intended
-// for long-running stress tests.
-func SkipLogs() RunOption {
- return optionSetter(func(opts *runConfig) {
- opts.skipLogs = true
- })
-}
-
-// InExistingDir runs the test in a pre-existing directory. If set, no initial
-// files may be passed to the runner. It is intended for long-running stress
-// tests.
-func InExistingDir(dir string) RunOption {
- return optionSetter(func(opts *runConfig) {
- opts.sandbox.Workdir = dir
- })
-}
-
-// SkipHooks allows for disabling the test runner's client hooks that are used
-// for instrumenting expectations (tracking diagnostics, logs, work done,
-// etc.). It is intended for performance-sensitive stress tests or benchmarks.
-func SkipHooks(skip bool) RunOption {
- return optionSetter(func(opts *runConfig) {
- opts.skipHooks = skip
- })
-}
-
-// GOPROXY configures the test environment to have an explicit proxy value.
-// This is intended for stress tests -- to ensure their isolation, regtests
-// should instead use WithProxyFiles.
-func GOPROXY(goproxy string) RunOption {
- return optionSetter(func(opts *runConfig) {
- opts.sandbox.GOPROXY = goproxy
- })
-}
-
-// LimitWorkspaceScope sets the LimitWorkspaceScope configuration.
-func LimitWorkspaceScope() RunOption {
- return optionSetter(func(opts *runConfig) {
- opts.editor.LimitWorkspaceScope = true
- })
-}
-
-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) {
- t.Helper()
- checkBuilder(t)
-
- tests := []struct {
- name string
- mode Mode
- getServer func(context.Context, *testing.T, func(*source.Options)) jsonrpc2.StreamServer
- }{
- {"singleton", Singleton, singletonServer},
- {"forwarded", Forwarded, r.forwardedServer},
- {"separate_process", SeparateProcess, r.separateProcessServer},
- {"experimental", Experimental, experimentalServer},
- }
-
- for _, tc := range tests {
- tc := tc
- config := r.defaultConfig()
- for _, opt := range opts {
- opt.set(config)
- }
- if config.modes&tc.mode == 0 {
- continue
- }
- if config.debugAddr != "" && tc.mode != Singleton {
- // Debugging is useful for running stress tests, but since the daemon has
- // likely already been started, it would be too late to debug.
- t.Fatalf("debugging regtest servers only works in Singleton mode, "+
- "got debug addr %q and mode %v", config.debugAddr, tc.mode)
- }
-
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- if r.Timeout != 0 && !config.noDefaultTimeout {
- 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()
- }
-
- ctx = debug.WithInstance(ctx, "", "off")
- if config.debugAddr != "" {
- di := debug.GetInstance(ctx)
- di.Serve(ctx, config.debugAddr)
- di.MonitorMemory(ctx)
- }
-
- 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)
- }
- // Deferring the closure of ws until the end of the entire test suite
- // has, in testing, given the LSP server time to properly shutdown and
- // release any file locks held in workspace, which is a problem on
- // Windows. This may still be flaky however, and in the future we need a
- // better solution to ensure that all Go processes started by gopls have
- // exited before we clean up.
- r.AddCloser(sandbox)
- ss := tc.getServer(ctx, t, config.optionsHook)
- framer := jsonrpc2.NewRawStream
- ls := &loggingFramer{}
- if !config.skipLogs {
- framer = ls.framer(jsonrpc2.NewRawStream)
- }
- ts := servertest.NewPipeServer(ctx, ss, framer)
- env := NewEnv(ctx, t, sandbox, ts, config.editor, !config.skipHooks)
- defer func() {
- if t.Failed() && r.PrintGoroutinesOnFailure {
- pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
- }
- if t.Failed() || testing.Verbose() {
- ls.printBuffers(t.Name(), os.Stderr)
- }
- // For tests that failed due to a timeout, don't fail to shutdown
- // because ctx is done.
- closeCtx, cancel := context.WithTimeout(xcontext.Detach(ctx), 5*time.Second)
- defer cancel()
- if err := env.Editor.Close(closeCtx); err != nil {
- t.Errorf("closing editor: %v", err)
- }
- }()
- // Always await the initial workspace load.
- env.Await(InitialWorkspaceLoad)
- test(t, env)
- })
- }
-}
-
-// longBuilders maps builders that are skipped when -short is set to a
-// (possibly empty) justification.
-var longBuilders = map[string]string{
- "openbsd-amd64-64": "golang.org/issues/42789",
- "openbsd-386-64": "golang.org/issues/42789",
- "openbsd-386-68": "golang.org/issues/42789",
- "openbsd-amd64-68": "golang.org/issues/42789",
- "darwin-amd64-10_12": "",
- "freebsd-amd64-race": "",
- "illumos-amd64": "",
- "netbsd-arm-bsiegert": "",
- "solaris-amd64-oraclerel": "",
- "windows-arm-zx2c4": "",
-}
-
-func checkBuilder(t *testing.T) {
- t.Helper()
- builder := os.Getenv("GO_BUILDER_NAME")
- if reason, ok := longBuilders[builder]; ok && testing.Short() {
- if reason != "" {
- t.Skipf("Skipping %s with -short due to %s", builder, reason)
- } else {
- t.Skipf("Skipping %s with -short", builder)
- }
- }
-}
-
-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)
-}
-
-func singletonServer(ctx context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
- return lsprpc.NewStreamServer(cache.New(optsHook), false)
-}
-
-func experimentalServer(_ context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
- options := func(o *source.Options) {
- optsHook(o)
- o.EnableAllExperiments()
- // ExperimentalWorkspaceModule is not (as of writing) enabled by
- // source.Options.EnableAllExperiments, but we want to test it.
- o.ExperimentalWorkspaceModule = true
- }
- return lsprpc.NewStreamServer(cache.New(options), false)
-}
-
-func (r *Runner) forwardedServer(ctx context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
- ts := r.getTestServer(optsHook)
- return newForwarder("tcp", ts.Addr)
-}
-
-// getTestServer gets the shared test server instance to connect to, or creates
-// one if it doesn't exist.
-func (r *Runner) getTestServer(optsHook func(*source.Options)) *servertest.TCPServer {
- r.mu.Lock()
- defer r.mu.Unlock()
- if r.ts == nil {
- ctx := context.Background()
- ctx = debug.WithInstance(ctx, "", "off")
- ss := lsprpc.NewStreamServer(cache.New(optsHook), false)
- r.ts = servertest.NewTCPServer(ctx, ss, nil)
- }
- return r.ts
-}
-
-func (r *Runner) separateProcessServer(ctx context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
- // TODO(rfindley): can we use the autostart behavior here, instead of
- // pre-starting the remote?
- socket := r.getRemoteSocket(t)
- return newForwarder("unix", socket)
-}
-
-func newForwarder(network, address string) *lsprpc.Forwarder {
- 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
-}
-
-// 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"
-
-func (r *Runner) getRemoteSocket(t *testing.T) string {
- t.Helper()
- r.mu.Lock()
- defer r.mu.Unlock()
- const daemonFile = "gopls-test-daemon"
- if r.socketDir != "" {
- return filepath.Join(r.socketDir, daemonFile)
- }
-
- if r.GoplsPath == "" {
- t.Fatal("cannot run tests with a separate process unless a path to a gopls binary is configured")
- }
- var err error
- r.socketDir, err = ioutil.TempDir(r.TempDir, "gopls-regtest-socket")
- if err != nil {
- t.Fatalf("creating tempdir: %v", err)
- }
- socket := filepath.Join(r.socketDir, daemonFile)
- args := []string{"serve", "-listen", "unix;" + socket, "-listen.timeout", "10s"}
- cmd := exec.Command(r.GoplsPath, args...)
- cmd.Env = append(os.Environ(), runTestAsGoplsEnvvar+"=true")
- var stderr bytes.Buffer
- cmd.Stderr = &stderr
- go func() {
- if err := cmd.Run(); err != nil {
- panic(fmt.Sprintf("error running external gopls: %v\nstderr:\n%s", err, stderr.String()))
- }
- }()
- return socket
-}
-
-// AddCloser schedules a closer to be closed at the end of the test run. This
-// is useful for Windows in particular, as
-func (r *Runner) AddCloser(closer io.Closer) {
- r.mu.Lock()
- defer r.mu.Unlock()
- r.closers = append(r.closers, closer)
-}
-
-// Close cleans up resource that have been allocated to this workspace.
-func (r *Runner) Close() error {
- r.mu.Lock()
- defer r.mu.Unlock()
-
- var errmsgs []string
- if r.ts != nil {
- if err := r.ts.Close(); err != nil {
- errmsgs = append(errmsgs, err.Error())
- }
- }
- if r.socketDir != "" {
- if err := os.RemoveAll(r.socketDir); err != nil {
- errmsgs = append(errmsgs, err.Error())
- }
- }
- if !r.SkipCleanup {
- for _, closer := range r.closers {
- if err := closer.Close(); err != nil {
- errmsgs = append(errmsgs, err.Error())
- }
- }
- 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/internal/lsp/regtest/wrappers.go b/internal/lsp/regtest/wrappers.go
deleted file mode 100644
index 9031e71f1..000000000
--- a/internal/lsp/regtest/wrappers.go
+++ /dev/null
@@ -1,446 +0,0 @@
-// 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 regtest
-
-import (
- "encoding/json"
- "path"
- "testing"
-
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/fake"
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-func (e *Env) ChangeFilesOnDisk(events []fake.FileEvent) {
- e.T.Helper()
- if err := e.Sandbox.Workdir.ChangeFilesOnDisk(e.Ctx, events); err != nil {
- e.T.Fatal(err)
- }
-}
-
-// 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 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)
- }
-}
-
-// 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)
- }
-}
-
-// 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 ...fake.Edit) {
- 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)
- }
-}
-
-// RegexpRange returns the range 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) RegexpRange(name, re string) (fake.Pos, fake.Pos) {
- e.T.Helper()
- start, end, err := e.Editor.RegexpRange(name, re)
- if err == fake.ErrUnknownBuffer {
- start, end, err = e.Sandbox.Workdir.RegexpRange(name, re)
- }
- if err != nil {
- e.T.Fatalf("RegexpRange: %v, %v", name, err)
- }
- return 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) fake.Pos {
- e.T.Helper()
- pos, err := e.Editor.RegexpSearch(name, re)
- if err == fake.ErrUnknownBuffer {
- pos, err = e.Sandbox.Workdir.RegexpSearch(name, re)
- }
- if err != nil {
- e.T.Fatalf("RegexpSearch: %v, %v", name, err)
- }
- return pos
-}
-
-// 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.
-func (e *Env) GoToDefinition(name string, pos fake.Pos) (string, fake.Pos) {
- e.T.Helper()
- n, p, err := e.Editor.GoToDefinition(e.Ctx, name, pos)
- if err != nil {
- e.T.Fatal(err)
- }
- return n, p
-}
-
-// Symbol returns symbols matching query
-func (e *Env) Symbol(query string) []fake.SymbolInformation {
- e.T.Helper()
- r, err := e.Editor.Symbol(e.Ctx, query)
- if err != nil {
- e.T.Fatal(err)
- }
- return r
-}
-
-// 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()
- if err := e.Editor.ApplyQuickFixes(e.Ctx, path, nil, 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()
- actions, err := e.Editor.GetQuickFixes(e.Ctx, path, nil, diagnostics)
- if err != nil {
- e.T.Fatal(err)
- }
- return actions
-}
-
-// Hover in the editor, calling t.Fatal on any error.
-func (e *Env) Hover(name string, pos fake.Pos) (*protocol.MarkupContent, fake.Pos) {
- e.T.Helper()
- c, p, err := e.Editor.Hover(e.Ctx, name, pos)
- if err != nil {
- e.T.Fatal(err)
- }
- return c, p
-}
-
-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(name string, pos fake.Pos) []protocol.DocumentHighlight {
- e.T.Helper()
- highlights, err := e.Editor.DocumentHighlight(e.Ctx, name, pos)
- if err != nil {
- e.T.Fatal(err)
- }
- return highlights
-}
-
-// RunGenerate runs go:generate on 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())
- // Ideally the fake.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, 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, true); err != nil {
- e.T.Fatal(err)
- }
-}
-
-// DumpGoSum prints the correct go.sum contents for dir in txtar format,
-// for use in creating regtests.
-func (e *Env) DumpGoSum(dir string) {
- e.T.Helper()
-
- if err := e.Sandbox.RunGoCommand(e.Ctx, dir, "list", []string{"-mod=mod", "..."}, 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) {
- e.T.Helper()
- lenses := e.CodeLens(path)
- var lens protocol.CodeLens
- var found bool
- for _, l := range lenses {
- if l.Command.Command == cmd.ID() {
- lens = l
- found = true
- }
- }
- if !found {
- e.T.Fatalf("found no command with the ID %s", cmd.ID())
- }
- e.ExecuteCommand(&protocol.ExecuteCommandParams{
- Command: lens.Command.Command,
- Arguments: lens.Command.Arguments,
- }, nil)
-}
-
-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)
- }
-}
-
-// WorkspaceSymbol calls workspace/symbol
-func (e *Env) WorkspaceSymbol(sym string) []protocol.SymbolInformation {
- e.T.Helper()
- ans, err := e.Editor.Symbols(e.Ctx, sym)
- if err != nil {
- e.T.Fatal(err)
- }
- return ans
-}
-
-// References calls textDocument/references for the given path at the given
-// position.
-func (e *Env) References(path string, pos fake.Pos) []protocol.Location {
- e.T.Helper()
- locations, err := e.Editor.References(e.Ctx, path, pos)
- if err != nil {
- e.T.Fatal(err)
- }
- return locations
-}
-
-func (e *Env) Rename(path string, pos fake.Pos, newName string) {
- e.T.Helper()
- if err := e.Editor.Rename(e.Ctx, path, pos, newName); err != nil {
- e.T.Fatal(err)
- }
-}
-
-// Completion executes a completion request on the server.
-func (e *Env) Completion(path string, pos fake.Pos) *protocol.CompletionList {
- e.T.Helper()
- completions, err := e.Editor.Completion(e.Ctx, path, pos)
- if err != nil {
- e.T.Fatal(err)
- }
- return completions
-}
-
-// AcceptCompletion accepts a completion for the given item at the given
-// position.
-func (e *Env) AcceptCompletion(path string, pos fake.Pos, item protocol.CompletionItem) {
- e.T.Helper()
- if err := e.Editor.AcceptCompletion(e.Ctx, path, pos, item); err != nil {
- e.T.Fatal(err)
- }
-}
-
-// CodeAction calls testDocument/codeAction for the given path, and calls
-// t.Fatal if there are errors.
-func (e *Env) CodeAction(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction {
- e.T.Helper()
- actions, err := e.Editor.CodeAction(e.Ctx, path, nil, diagnostics)
- if err != nil {
- e.T.Fatal(err)
- }
- return actions
-}
-
-func (e *Env) ChangeConfiguration(t *testing.T, config *fake.EditorConfig) {
- e.Editor.Config = *config
- if err := e.Editor.Server.DidChangeConfiguration(e.Ctx, &protocol.DidChangeConfigurationParams{
- // gopls currently ignores the Settings field
- }); err != nil {
- t.Fatal(err)
- }
-}
-
-// ChangeEnv modifies the editor environment and reconfigures the LSP client.
-// TODO: extend this to "ChangeConfiguration", once we refactor the way editor
-// configuration is defined.
-func (e *Env) ChangeEnv(overlay map[string]string) {
- e.T.Helper()
- // TODO: to be correct, this should probably be synchronized, but right now
- // configuration is only ever modified synchronously in a regtest, so this
- // correctness can wait for the previously mentioned refactoring.
- if e.Editor.Config.Env == nil {
- e.Editor.Config.Env = make(map[string]string)
- }
- for k, v := range overlay {
- e.Editor.Config.Env[k] = v
- }
- var params protocol.DidChangeConfigurationParams
- if err := e.Editor.Server.DidChangeConfiguration(e.Ctx, &params); err != nil {
- e.T.Fatal(err)
- }
-}
diff --git a/internal/lsp/rename.go b/internal/lsp/rename.go
deleted file mode 100644
index 739ae906b..000000000
--- a/internal/lsp/rename.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-func (s *Server) rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.Go)
- defer release()
- if !ok {
- return nil, err
- }
- edits, err := source.Rename(ctx, snapshot, fh, params.Position, params.NewName)
- if err != nil {
- return nil, err
- }
-
- var docChanges []protocol.TextDocumentEdit
- for uri, e := range edits {
- fh, err := snapshot.GetVersionedFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- docChanges = append(docChanges, documentChanges(fh, e)...)
- }
- return &protocol.WorkspaceEdit{
- DocumentChanges: docChanges,
- }, nil
-}
-
-func (s *Server) prepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.PrepareRename2Gn, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.Go)
- defer release()
- if !ok {
- return nil, err
- }
- // Do not return errors here, as it adds clutter.
- // Returning a nil result means there is not a valid rename.
- item, usererr, err := source.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.PrepareRename2Gn{
- Range: item.Range,
- Placeholder: item.Text,
- }, nil
-}
diff --git a/internal/lsp/reset_golden.sh b/internal/lsp/reset_golden.sh
deleted file mode 100755
index 2689407ca..000000000
--- a/internal/lsp/reset_golden.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-find ./internal/lsp/ -name *.golden -delete
-go test ./internal/lsp/source -golden
-go test ./internal/lsp/ -golden
-go test ./internal/lsp/cmd -golden
diff --git a/internal/lsp/semantic.go b/internal/lsp/semantic.go
deleted file mode 100644
index 7c0419c20..000000000
--- a/internal/lsp/semantic.go
+++ /dev/null
@@ -1,958 +0,0 @@
-// 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 lsp
-
-import (
- "bytes"
- "context"
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
- "log"
- "path/filepath"
- "sort"
- "strings"
- "time"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/template"
- "golang.org/x/tools/internal/typeparams"
- errors "golang.org/x/xerrors"
-)
-
-// 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.
-
-// reject full semantic token requests for large files
-const maxFullFileSize int = 100000
-
-// to control comprehensive logging of decisions (gopls semtok foo.go > /dev/null shows log output)
-// semDebug should NEVER be true in checked-in code
-const semDebug = false
-
-func (s *Server) semanticTokensFull(ctx context.Context, p *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) {
- ret, err := s.computeSemanticTokens(ctx, p.TextDocument, nil)
- return ret, err
-}
-
-func (s *Server) semanticTokensFullDelta(ctx context.Context, p *protocol.SemanticTokensDeltaParams) (interface{}, error) {
- return nil, errors.Errorf("implement SemanticTokensFullDelta")
-}
-
-func (s *Server) semanticTokensRange(ctx context.Context, p *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) {
- ret, err := s.computeSemanticTokens(ctx, p.TextDocument, &p.Range)
- return ret, err
-}
-
-func (s *Server) semanticTokensRefresh(ctx context.Context) error {
- // in the code, but not in the protocol spec
- return errors.Errorf("implement SemanticTokensRefresh")
-}
-
-func (s *Server) computeSemanticTokens(ctx context.Context, td protocol.TextDocumentIdentifier, rng *protocol.Range) (*protocol.SemanticTokens, error) {
- ans := protocol.SemanticTokens{
- Data: []uint32{},
- }
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, td.URI, source.UnknownKind)
- defer release()
- if !ok {
- return nil, err
- }
- vv := snapshot.View()
- if !vv.Options().SemanticTokens {
- // return an error, so if the option changes
- // the client won't remember the wrong answer
- return nil, errors.Errorf("semantictokens are disabled")
- }
- kind := snapshot.View().FileKind(fh)
- if kind == source.Tmpl {
- // this is a little cumbersome to avoid both exporting 'encoded' and its methods
- // and to avoid import cycles
- e := &encoded{
- ctx: ctx,
- rng: rng,
- tokTypes: s.session.Options().SemanticTypes,
- tokMods: s.session.Options().SemanticMods,
- }
- add := func(line, start uint32, len uint32) {
- e.add(line, start, len, tokMacro, nil)
- }
- data := func() []uint32 {
- return e.Data()
- }
- return template.SemanticTokens(ctx, snapshot, fh.URI(), add, data)
- }
- if kind != source.Go {
- return nil, nil
- }
- pkg, err := snapshot.PackageForFile(ctx, fh.URI(), source.TypecheckFull, source.WidestPackage)
- if err != nil {
- return nil, err
- }
- pgf, err := pkg.File(fh.URI())
- if err != nil {
- return nil, err
- }
- // ignore pgf.ParseErr. Do what we can.
- if rng == nil && len(pgf.Src) > maxFullFileSize {
- err := fmt.Errorf("semantic tokens: file %s too large for full (%d>%d)",
- fh.URI().Filename(), len(pgf.Src), maxFullFileSize)
- return nil, err
- }
- e := &encoded{
- ctx: ctx,
- pgf: pgf,
- rng: rng,
- ti: pkg.GetTypesInfo(),
- pkg: pkg,
- fset: snapshot.FileSet(),
- tokTypes: s.session.Options().SemanticTypes,
- tokMods: s.session.Options().SemanticMods,
- }
- if err := e.init(); err != nil {
- // e.init should never return an error, unless there's some
- // seemingly impossible race condition
- return nil, err
- }
- e.semantics()
- ans.Data = e.Data()
- // For delta requests, but we've never seen any.
- ans.ResultID = fmt.Sprintf("%v", time.Now())
- return &ans, nil
-}
-
-func (e *encoded) semantics() {
- f := e.pgf.File
- // may not be in range, but harmless
- e.token(f.Package, len("package"), tokKeyword, nil)
- e.token(f.Name.NamePos, len(f.Name.Name), tokNamespace, nil)
- inspect := func(n ast.Node) bool {
- return e.inspector(n)
- }
- for _, d := range f.Decls {
- // only look at the decls that overlap the range
- start, end := d.Pos(), d.End()
- if end <= e.start || start >= e.end {
- continue
- }
- ast.Inspect(d, inspect)
- }
- for _, cg := range f.Comments {
- for _, c := range cg.List {
- if !strings.Contains(c.Text, "\n") {
- e.token(c.Pos(), len(c.Text), tokComment, nil)
- continue
- }
- e.multiline(c.Pos(), c.End(), c.Text, tokComment)
- }
- }
-}
-
-type tokenType string
-
-const (
- 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
-)
-
-func (e *encoded) token(start token.Pos, leng int, typ tokenType, mods []string) {
-
- if !start.IsValid() {
- // This is not worth reporting
- return
- }
- if start >= e.end || start+token.Pos(leng) <= e.start {
- return
- }
- // want a line and column from start (in LSP coordinates)
- // [//line directives should be ignored]
- rng := source.NewMappedRange(e.fset, e.pgf.Mapper, start, start+token.Pos(leng))
- lspRange, err := rng.Range()
- if err != nil {
- // possibly a //line directive. TODO(pjw): fix this somehow
- // "column mapper is for file...instead of..."
- // "line is beyond end of file..."
- // see line 116 of internal/span/token.go which uses Position not PositionFor
- // (it is too verbose to print the error on every token. some other RPC will fail)
- // event.Error(e.ctx, "failed to convert to range", err)
- return
- }
- if lspRange.End.Line != lspRange.Start.Line {
- // this happens if users are typing at the end of the file, but report nothing
- return
- }
- // token is all on one line
- length := lspRange.End.Character - lspRange.Start.Character
- e.add(lspRange.Start.Line, lspRange.Start.Character, length, typ, mods)
-}
-
-func (e *encoded) add(line, start uint32, len uint32, tok tokenType, mod []string) {
- x := semItem{line, start, len, tok, mod}
- e.items = append(e.items, x)
-}
-
-// semItem represents a token found walking the parse tree
-type semItem struct {
- line, start uint32
- len uint32
- typeStr tokenType
- mods []string
-}
-
-type encoded struct {
- // the generated data
- items []semItem
-
- ctx context.Context
- tokTypes, tokMods []string
- pgf *source.ParsedGoFile
- rng *protocol.Range
- ti *types.Info
- pkg source.Package
- fset *token.FileSet
- // allowed starting and ending token.Pos, set by init
- // used to avoid looking at declarations not in range
- start, end token.Pos
- // path from the root of the parse tree, used for debugging
- stack []ast.Node
-}
-
-// convert the stack to a string, for debugging
-func (e *encoded) strStack() string {
- msg := []string{"["}
- for i := len(e.stack) - 1; i >= 0; i-- {
- s := e.stack[i]
- msg = append(msg, fmt.Sprintf("%T", s)[5:])
- }
- if len(e.stack) > 0 {
- loc := e.stack[len(e.stack)-1].Pos()
- if !source.InRange(e.pgf.Tok, loc) {
- msg = append(msg, fmt.Sprintf("invalid position %v for %s", loc, e.pgf.URI))
- } else if locInRange(e.pgf.Tok, loc) {
- add := e.pgf.Tok.PositionFor(loc, false)
- nm := filepath.Base(add.Filename)
- msg = append(msg, fmt.Sprintf("(%s:%d,col:%d)", nm, add.Line, add.Column))
- } else {
- msg = append(msg, fmt.Sprintf("(loc %d out of range)", loc))
- }
- }
- msg = append(msg, "]")
- return strings.Join(msg, " ")
-}
-
-// avoid panic in token.PostionFor() when typing at the end of the file
-func locInRange(f *token.File, loc token.Pos) bool {
- return f.Base() <= int(loc) && int(loc) < f.Base()+f.Size()
-}
-
-// find the line in the source
-func (e *encoded) srcLine(x ast.Node) string {
- file := e.pgf.Tok
- line := file.Line(x.Pos())
- start, err := source.Offset(file, file.LineStart(line))
- if err != nil {
- return ""
- }
- end := start
- for ; end < len(e.pgf.Src) && e.pgf.Src[end] != '\n'; end++ {
-
- }
- ans := e.pgf.Src[start:end]
- return string(ans)
-}
-
-func (e *encoded) inspector(n ast.Node) bool {
- pop := func() {
- e.stack = e.stack[:len(e.stack)-1]
- }
- if n == nil {
- pop()
- return true
- }
- e.stack = append(e.stack, n)
- switch x := n.(type) {
- case *ast.ArrayType:
- case *ast.AssignStmt:
- e.token(x.TokPos, len(x.Tok.String()), tokOperator, nil)
- case *ast.BasicLit:
- if strings.Contains(x.Value, "\n") {
- // has to be a string
- e.multiline(x.Pos(), x.End(), x.Value, tokString)
- break
- }
- ln := len(x.Value)
- what := tokNumber
- if x.Kind == token.STRING {
- what = tokString
- if _, ok := e.stack[len(e.stack)-2].(*ast.Field); ok {
- // struct tags (this is probably pointless, as the
- // TextMate grammar will treat all the other comments the same)
- what = tokComment
- }
- }
- e.token(x.Pos(), ln, what, nil)
- case *ast.BinaryExpr:
- e.token(x.OpPos, len(x.Op.String()), tokOperator, nil)
- case *ast.BlockStmt:
- case *ast.BranchStmt:
- e.token(x.TokPos, len(x.Tok.String()), tokKeyword, nil)
- // There's no semantic encoding for labels
- case *ast.CallExpr:
- if x.Ellipsis != token.NoPos {
- e.token(x.Ellipsis, len("..."), tokOperator, nil)
- }
- case *ast.CaseClause:
- iam := "case"
- if x.List == nil {
- iam = "default"
- }
- e.token(x.Case, len(iam), tokKeyword, nil)
- case *ast.ChanType:
- // chan | chan <- | <- chan
- if x.Arrow == token.NoPos || x.Arrow != x.Begin {
- e.token(x.Begin, len("chan"), tokKeyword, nil)
- break
- }
- pos := e.findKeyword("chan", x.Begin+2, x.Value.Pos())
- e.token(pos, len("chan"), tokKeyword, nil)
- case *ast.CommClause:
- iam := len("case")
- if x.Comm == nil {
- iam = len("default")
- }
- e.token(x.Case, iam, tokKeyword, nil)
- case *ast.CompositeLit:
- case *ast.DeclStmt:
- case *ast.DeferStmt:
- e.token(x.Defer, len("defer"), tokKeyword, nil)
- case *ast.Ellipsis:
- e.token(x.Ellipsis, len("..."), tokOperator, nil)
- case *ast.EmptyStmt:
- case *ast.ExprStmt:
- case *ast.Field:
- case *ast.FieldList:
- case *ast.ForStmt:
- e.token(x.For, len("for"), tokKeyword, nil)
- case *ast.FuncDecl:
- case *ast.FuncLit:
- case *ast.FuncType:
- if x.Func != token.NoPos {
- e.token(x.Func, len("func"), tokKeyword, nil)
- }
- case *ast.GenDecl:
- e.token(x.TokPos, len(x.Tok.String()), tokKeyword, nil)
- case *ast.GoStmt:
- e.token(x.Go, len("go"), tokKeyword, nil)
- case *ast.Ident:
- e.ident(x)
- case *ast.IfStmt:
- e.token(x.If, len("if"), tokKeyword, nil)
- if x.Else != nil {
- // x.Body.End() or x.Body.End()+1, not that it matters
- pos := e.findKeyword("else", x.Body.End(), x.Else.Pos())
- e.token(pos, len("else"), tokKeyword, nil)
- }
- case *ast.ImportSpec:
- e.importSpec(x)
- pop()
- return false
- case *ast.IncDecStmt:
- e.token(x.TokPos, len(x.Tok.String()), tokOperator, nil)
- case *ast.IndexExpr:
- case *typeparams.IndexListExpr: // accommodate generics
- case *ast.InterfaceType:
- e.token(x.Interface, len("interface"), tokKeyword, nil)
- case *ast.KeyValueExpr:
- case *ast.LabeledStmt:
- case *ast.MapType:
- e.token(x.Map, len("map"), tokKeyword, nil)
- case *ast.ParenExpr:
- case *ast.RangeStmt:
- e.token(x.For, len("for"), tokKeyword, nil)
- // x.TokPos == token.NoPos is legal (for range foo {})
- offset := x.TokPos
- if offset == token.NoPos {
- offset = x.For
- }
- pos := e.findKeyword("range", offset, x.X.Pos())
- e.token(pos, len("range"), tokKeyword, nil)
- case *ast.ReturnStmt:
- e.token(x.Return, len("return"), tokKeyword, nil)
- case *ast.SelectStmt:
- e.token(x.Select, len("select"), tokKeyword, nil)
- case *ast.SelectorExpr:
- case *ast.SendStmt:
- e.token(x.Arrow, len("<-"), tokOperator, nil)
- case *ast.SliceExpr:
- case *ast.StarExpr:
- e.token(x.Star, len("*"), tokOperator, nil)
- case *ast.StructType:
- e.token(x.Struct, len("struct"), tokKeyword, nil)
- case *ast.SwitchStmt:
- e.token(x.Switch, len("switch"), tokKeyword, nil)
- case *ast.TypeAssertExpr:
- if x.Type == nil {
- pos := e.findKeyword("type", x.Lparen, x.Rparen)
- e.token(pos, len("type"), tokKeyword, nil)
- }
- case *ast.TypeSpec:
- case *ast.TypeSwitchStmt:
- e.token(x.Switch, len("switch"), tokKeyword, nil)
- case *ast.UnaryExpr:
- e.token(x.OpPos, len(x.Op.String()), tokOperator, nil)
- case *ast.ValueSpec:
- // things only seen with parsing or type errors, so ignore them
- case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:
- return true
- // not going to see these
- case *ast.File, *ast.Package:
- e.unexpected(fmt.Sprintf("implement %T %s", x, e.pgf.Tok.PositionFor(x.Pos(), false)))
- // other things we knowingly ignore
- case *ast.Comment, *ast.CommentGroup:
- pop()
- return false
- default:
- e.unexpected(fmt.Sprintf("failed to implement %T", x))
- }
- return true
-}
-
-func (e *encoded) ident(x *ast.Ident) {
- if e.ti == nil {
- what, mods := e.unkIdent(x)
- if what != "" {
- e.token(x.Pos(), len(x.String()), what, mods)
- }
- if semDebug {
- log.Printf(" nil %s/nil/nil %q %v %s", x.String(), what, mods, e.strStack())
- }
- return
- }
- def := e.ti.Defs[x]
- if def != nil {
- what, mods := e.definitionFor(x, def)
- if what != "" {
- e.token(x.Pos(), len(x.String()), what, mods)
- }
- if semDebug {
- log.Printf(" for %s/%T/%T got %s %v (%s)", x.String(), def, def.Type(), what, mods, e.strStack())
- }
- return
- }
- use := e.ti.Uses[x]
- tok := func(pos token.Pos, lng int, tok tokenType, mods []string) {
- e.token(pos, lng, tok, mods)
- q := "nil"
- if use != nil {
- q = fmt.Sprintf("%T", use.Type())
- }
- if semDebug {
- log.Printf(" use %s/%T/%s got %s %v (%s)", x.String(), use, q, tok, mods, e.strStack())
- }
- }
-
- switch y := use.(type) {
- case nil:
- what, mods := e.unkIdent(x)
- if what != "" {
- tok(x.Pos(), len(x.String()), what, mods)
- } else if semDebug {
- // tok() wasn't called, so didn't log
- log.Printf(" nil %s/%T/nil %q %v (%s)", x.String(), use, what, mods, e.strStack())
- }
- return
- case *types.Builtin:
- tok(x.NamePos, len(x.Name), tokFunction, []string{"defaultLibrary"})
- case *types.Const:
- mods := []string{"readonly"}
- tt := y.Type()
- if _, ok := tt.(*types.Basic); ok {
- tok(x.Pos(), len(x.String()), tokVariable, mods)
- break
- }
- if ttx, ok := tt.(*types.Named); ok {
- if x.String() == "iota" {
- e.unexpected(fmt.Sprintf("iota:%T", ttx))
- }
- if _, ok := ttx.Underlying().(*types.Basic); ok {
- tok(x.Pos(), len(x.String()), tokVariable, mods)
- break
- }
- e.unexpected(fmt.Sprintf("%q/%T", x.String(), tt))
- }
- // can this happen? Don't think so
- e.unexpected(fmt.Sprintf("%s %T %#v", x.String(), tt, tt))
- case *types.Func:
- tok(x.Pos(), len(x.Name), tokFunction, nil)
- case *types.Label:
- // nothing to map it to
- case *types.Nil:
- // nil is a predeclared identifier
- tok(x.Pos(), len("nil"), tokVariable, []string{"readonly", "defaultLibrary"})
- case *types.PkgName:
- tok(x.Pos(), len(x.Name), tokNamespace, nil)
- case *types.TypeName: // could be a tokTpeParam
- var mods []string
- if _, ok := y.Type().(*types.Basic); ok {
- mods = []string{"defaultLibrary"}
- } else if _, ok := y.Type().(*typeparams.TypeParam); ok {
- tok(x.Pos(), len(x.String()), tokTypeParam, mods)
- break
- }
- tok(x.Pos(), len(x.String()), tokType, mods)
- case *types.Var:
- if isSignature(y) {
- tok(x.Pos(), len(x.Name), tokFunction, nil)
- } else if _, ok := y.Type().(*typeparams.TypeParam); ok {
- tok(x.Pos(), len(x.Name), tokTypeParam, nil)
- } else {
- tok(x.Pos(), len(x.Name), tokVariable, nil)
- }
- default:
- // can't happen
- if use == nil {
- msg := fmt.Sprintf("%#v/%#v %#v %#v", x, x.Obj, e.ti.Defs[x], e.ti.Uses[x])
- e.unexpected(msg)
- }
- if use.Type() != nil {
- e.unexpected(fmt.Sprintf("%s %T/%T,%#v", x.String(), use, use.Type(), use))
- } else {
- e.unexpected(fmt.Sprintf("%s %T", x.String(), use))
- }
- }
-}
-
-func isSignature(use types.Object) bool {
- if true {
- return false //PJW: fix after generics seem ok
- }
- if _, ok := use.(*types.Var); !ok {
- return false
- }
- v := use.Type()
- if v == nil {
- return false
- }
- if _, ok := v.(*types.Signature); ok {
- return true
- }
- return false
-}
-
-// both e.ti.Defs and e.ti.Uses are nil. 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 (e *encoded) unkIdent(x *ast.Ident) (tokenType, []string) {
- def := []string{"definition"}
- n := len(e.stack) - 2 // parent of Ident
- if n < 0 {
- e.unexpected("no stack?")
- return "", nil
- }
- switch nd := e.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
- return tokVariable, nil
- case *typeparams.IndexListExpr: // generic?
- return tokVariable, nil
- case *ast.Ellipsis:
- return tokType, nil
- case *ast.CaseClause:
- if n-2 >= 0 {
- if _, ok := e.stack[n-2].(*ast.TypeSwitchStmt); ok {
- return tokType, nil
- }
- }
- return tokVariable, nil
- case *ast.ArrayType:
- if x == nd.Len {
- // or maybe a Type Param, but we can't just from the parse tree
- return tokVariable, nil
- } else {
- return tokType, nil
- }
- case *ast.MapType:
- return tokType, nil
- case *ast.CallExpr:
- if x == nd.Fun {
- return tokFunction, nil
- }
- return tokVariable, nil
- case *ast.SwitchStmt:
- return tokVariable, nil
- case *ast.TypeAssertExpr:
- if x == nd.X {
- return tokVariable, nil
- } else if x == nd.Type {
- return tokType, nil
- }
- case *ast.ValueSpec:
- for _, p := range nd.Names {
- if p == x {
- return tokVariable, def
- }
- }
- for _, p := range nd.Values {
- if p == x {
- return tokVariable, nil
- }
- }
- return tokType, nil
- case *ast.SelectorExpr: // e.ti.Selections[nd] is nil, so no help
- if n-1 >= 0 {
- if ce, ok := e.stack[n-1].(*ast.CallExpr); ok {
- // ... CallExpr SelectorExpr Ident (_.x())
- if ce.Fun == nd && nd.Sel == x {
- return tokFunction, nil
- }
- }
- }
- return tokVariable, nil
- case *ast.AssignStmt:
- for _, p := range nd.Lhs {
- // x := ..., or x = ...
- if p == x {
- if nd.Tok != token.DEFINE {
- def = nil
- }
- return tokVariable, def
- }
- }
- // RHS, = x
- return tokVariable, nil
- case *ast.TypeSpec: // it's a type if it is either the Name or the Type
- if x == nd.Type {
- def = nil
- }
- return tokType, def
- case *ast.Field:
- // ident could be type in a field, or a method in an interface type, or a variable
- if x == nd.Type {
- return tokType, nil
- }
- if n-2 >= 0 {
- _, okit := e.stack[n-2].(*ast.InterfaceType)
- _, okfl := e.stack[n-1].(*ast.FieldList)
- if okit && okfl {
- return tokMethod, def
- }
- }
- return tokVariable, nil
- case *ast.LabeledStmt, *ast.BranchStmt:
- // nothing to report
- case *ast.CompositeLit:
- if nd.Type == x {
- return tokType, nil
- }
- return tokVariable, nil
- case *ast.RangeStmt:
- if nd.Tok != token.DEFINE {
- def = nil
- }
- return tokVariable, def
- case *ast.FuncDecl:
- return tokFunction, def
- default:
- msg := fmt.Sprintf("%T undexpected: %s %s%q", nd, x.Name, e.strStack(), e.srcLine(x))
- e.unexpected(msg)
- }
- return "", nil
-}
-
-func isDeprecated(n *ast.CommentGroup) bool {
- if n == nil {
- return false
- }
- for _, c := range n.List {
- if strings.HasPrefix(c.Text, "// Deprecated") {
- return true
- }
- }
- return false
-}
-
-func (e *encoded) definitionFor(x *ast.Ident, def types.Object) (tokenType, []string) {
- // PJW: def == types.Label? probably a nothing
- // PJW: look into replaceing these syntactic tests with types more generally
- mods := []string{"definition"}
- for i := len(e.stack) - 1; i >= 0; i-- {
- s := e.stack[i]
- switch y := s.(type) {
- case *ast.AssignStmt, *ast.RangeStmt:
- if x.Name == "_" {
- return "", nil // not really a variable
- }
- return tokVariable, mods
- case *ast.GenDecl:
- if isDeprecated(y.Doc) {
- mods = append(mods, "deprecated")
- }
- if y.Tok == token.CONST {
- mods = append(mods, "readonly")
- }
- return tokVariable, mods
- case *ast.FuncDecl:
- // If x is immediately under a FuncDecl, it is a function or method
- if i == len(e.stack)-2 {
- if isDeprecated(y.Doc) {
- mods = append(mods, "deprecated")
- }
- if y.Recv != nil {
- return tokMethod, mods
- }
- return tokFunction, mods
- }
- // if x < ... < FieldList < FuncDecl, this is the receiver, a variable
- if _, ok := e.stack[i+1].(*ast.FieldList); ok {
- return tokVariable, nil
- }
- // if x < ... < FieldList < FuncType < FuncDecl, this is a param
- return tokParameter, mods
- case *ast.FuncType:
- return tokParameter, mods
- case *ast.InterfaceType:
- return tokMethod, mods
- case *ast.TypeSpec:
- // GenDecl/Typespec/FuncType/FieldList/Field/Ident
- // (type A func(b uint64)) (err error)
- // b and err should not be tokType, but tokVaraible
- // 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 _, ok := e.stack[i+1].(*ast.FieldList); ok {
- return tokTypeParam, mods
- }
- fldm := e.stack[len(e.stack)-2]
- if fld, ok := fldm.(*ast.Field); ok {
- // if len(fld.names) == 0 this is a tokType, being used
- if len(fld.Names) == 0 {
- return tokType, nil
- }
- return tokVariable, mods
- }
- return tokType, mods
- }
- }
- // can't happen
- msg := fmt.Sprintf("failed to find the decl for %s", e.pgf.Tok.PositionFor(x.Pos(), false))
- e.unexpected(msg)
- return "", []string{""}
-}
-
-func (e *encoded) multiline(start, end token.Pos, val string, tok tokenType) {
- f := e.fset.File(start)
- // the hard part is finding the lengths of lines. include the \n
- leng := 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 := e.fset.PositionFor(start, false)
- epos := e.fset.PositionFor(end, false)
- sline := spos.Line
- eline := epos.Line
- // first line is from spos.Column to end
- e.token(start, leng(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
- e.token(f.LineStart(i), leng(i)-1, tok, nil) // avoid the newline
- }
- // last line is from 1 to epos.Column
- e.token(f.LineStart(eline), epos.Column-1, tok, nil) // columns are 1-based
-}
-
-// findKeyword finds a keyword rather than guessing its location
-func (e *encoded) findKeyword(keyword string, start, end token.Pos) token.Pos {
- offset := int(start) - e.pgf.Tok.Base()
- last := int(end) - e.pgf.Tok.Base()
- buf := e.pgf.Src
- idx := bytes.Index(buf[offset:last], []byte(keyword))
- if idx != -1 {
- return start + token.Pos(idx)
- }
- //(in unparsable programs: type _ <-<-chan int)
- e.unexpected(fmt.Sprintf("not found:%s %v", keyword, e.fset.PositionFor(start, false)))
- return token.NoPos
-}
-
-func (e *encoded) init() error {
- e.start = token.Pos(e.pgf.Tok.Base())
- e.end = e.start + token.Pos(e.pgf.Tok.Size())
- if e.rng == nil {
- return nil
- }
- span, err := e.pgf.Mapper.RangeSpan(*e.rng)
- if err != nil {
- return errors.Errorf("range span (%w) error for %s", err, e.pgf.File.Name)
- }
- e.end = e.start + token.Pos(span.End().Offset())
- e.start += token.Pos(span.Start().Offset())
- return nil
-}
-
-func (e *encoded) Data() []uint32 {
- // binary operators, at least, will be out of order
- sort.Slice(e.items, func(i, j int) bool {
- if e.items[i].line != e.items[j].line {
- return e.items[i].line < e.items[j].line
- }
- return e.items[i].start < e.items[j].start
- })
- typeMap, modMap := e.maps()
- // each semantic token needs five values
- // (see Integer Encoding for Tokens in the LSP spec)
- x := make([]uint32, 5*len(e.items))
- var j int
- for i := 0; i < len(e.items); i++ {
- typ, ok := typeMap[e.items[i].typeStr]
- if !ok {
- continue // client doesn't want typeStr
- }
- if i == 0 {
- x[0] = e.items[0].line
- } else {
- x[j] = e.items[i].line - e.items[i-1].line
- }
- x[j+1] = e.items[i].start
- if i > 0 && e.items[i].line == e.items[i-1].line {
- x[j+1] = e.items[i].start - e.items[i-1].start
- }
- x[j+2] = e.items[i].len
- x[j+3] = uint32(typ)
- mask := 0
- for _, s := range e.items[i].mods {
- // modMap[s] is 0 if the client doesn't want this modifier
- mask |= modMap[s]
- }
- x[j+4] = uint32(mask)
- j += 5
- }
- return x[:j]
-}
-
-func (e *encoded) importSpec(d *ast.ImportSpec) {
- // a local package name or the last component of the Path
- if d.Name != nil {
- nm := d.Name.String()
- if nm != "_" && nm != "." {
- e.token(d.Name.Pos(), len(nm), tokNamespace, nil)
- }
- return // don't mark anything for . or _
- }
- val := d.Path.Value
- if len(val) < 2 || val[0] != '"' || val[len(val)-1] != '"' {
- // avoid panics on imports without a properly quoted string
- return
- }
- nm := val[1 : len(val)-1] // remove surrounding "s
- // Import strings are implementation defined. Try to match with parse information.
- x, err := e.pkg.GetImport(nm)
- if err != nil {
- // unexpected, but impact is that maybe some import is not colored
- return
- }
- // expect that nm is x.PkgPath and that x.Name() is a component of it
- if x.PkgPath() != nm {
- // don't know how or what to color (if this can happen at all)
- return
- }
- // this is not a precise test: imagine "github.com/nasty/v/v2"
- j := strings.LastIndex(nm, x.Name())
- if j == -1 {
- // name doesn't show up, for whatever reason, so nothing to report
- return
- }
- start := d.Path.Pos() + 1 + token.Pos(j) // skip the initial quote
- e.token(start, len(x.Name()), tokNamespace, nil)
-}
-
-// log unexpected state
-func (e *encoded) unexpected(msg string) {
- if semDebug {
- panic(msg)
- }
- event.Error(e.ctx, e.strStack(), errors.New(msg))
-}
-
-// 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]
- }
- 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
-}
-
-func (e *encoded) maps() (map[tokenType]int, map[string]int) {
- tmap := make(map[tokenType]int)
- mmap := make(map[string]int)
- for i, t := range e.tokTypes {
- tmap[tokenType(t)] = i
- }
- for i, m := range e.tokMods {
- mmap[m] = 1 << uint(i) // go 1.12 compatibility
- }
- return tmap, mmap
-}
-
-// 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[:]
-}
-
-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",
- }
-)
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
deleted file mode 100644
index becfc718e..000000000
--- a/internal/lsp/server.go
+++ /dev/null
@@ -1,168 +0,0 @@
-// 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 lsp implements LSP for gopls.
-package lsp
-
-import (
- "context"
- "fmt"
- "sync"
-
- "golang.org/x/tools/internal/jsonrpc2"
- "golang.org/x/tools/internal/lsp/progress"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-const concurrentAnalyses = 1
-
-// NewServer creates an LSP server and binds it to handle incoming client
-// messages on on the supplied stream.
-func NewServer(session source.Session, client protocol.ClientCloser) *Server {
- tracker := progress.NewTracker(client)
- session.SetProgressTracker(tracker)
- return &Server{
- diagnostics: map[span.URI]*fileReports{},
- gcOptimizationDetails: make(map[string]struct{}),
- watchedGlobPatterns: make(map[string]struct{}),
- changedFiles: make(map[span.URI]struct{}),
- session: session,
- client: client,
- diagnosticsSema: make(chan struct{}, concurrentAnalyses),
- progress: tracker,
- diagDebouncer: newDebouncer(),
- watchedFileDebouncer: newDebouncer(),
- }
-}
-
-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 source.Session
-
- tempDir string
-
- // changedFiles tracks files for which there has been a textDocument/didChange.
- changedFilesMu sync.Mutex
- changedFiles map[span.URI]struct{}
-
- // folders is only valid between initialize and initialized, and holds the
- // set of folders to build views for when we are ready
- 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.
- watchedGlobPatternsMu sync.Mutex
- watchedGlobPatterns map[string]struct{}
- watchRegistrationCount int
-
- diagnosticsMu sync.Mutex
- diagnostics map[span.URI]*fileReports
-
- // gcOptimizationDetails describes the packages for which we want
- // optimization details to be included in the diagnostics. The key is the
- // ID of the package.
- gcOptimizationDetailsMu sync.Mutex
- gcOptimizationDetails map[string]struct{}
-
- // diagnosticsSema limits the concurrency of diagnostics runs, which can be
- // expensive.
- diagnosticsSema chan struct{}
-
- progress *progress.Tracker
-
- // diagDebouncer is used for debouncing diagnostics.
- diagDebouncer *debouncer
-
- // watchedFileDebouncer is used for batching didChangeWatchedFiles notifications.
- watchedFileDebouncer *debouncer
- fileChangeMu sync.Mutex
- pendingOnDiskChanges []*pendingModificationSet
-
- // When the workspace fails to load, we show its status through a progress
- // report with an error message.
- criticalErrorStatusMu sync.Mutex
- criticalErrorStatus *progress.WorkDone
-}
-
-type pendingModificationSet struct {
- diagnoseDone chan struct{}
- changes []source.FileModification
-}
-
-func (s *Server) workDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error {
- return s.progress.Cancel(ctx, params.Token)
-}
-
-func (s *Server) nonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
- switch method {
- case "gopls/diagnoseFiles":
- paramMap := params.(map[string]interface{})
- for _, file := range paramMap["files"].([]interface{}) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, protocol.DocumentURI(file.(string)), source.UnknownKind)
- defer release()
- if !ok {
- return nil, err
- }
-
- fileID, diagnostics, err := source.FileDiagnostics(ctx, snapshot, fh.URI())
- if err != nil {
- return nil, err
- }
- if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
- URI: protocol.URIFromSpanURI(fh.URI()),
- Diagnostics: toProtocolDiagnostics(diagnostics),
- Version: fileID.Version,
- }); err != nil {
- return nil, err
- }
- }
- if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
- URI: "gopls://diagnostics-done",
- }); err != nil {
- return nil, err
- }
- return struct{}{}, nil
- }
- return nil, notImplemented(method)
-}
-
-func notImplemented(method string) error {
- return errors.Errorf("%w: %q not yet implemented", jsonrpc2.ErrMethodNotFound, method)
-}
-
-//go:generate helper/helper -d protocol/tsserver.go -o server_gen.go -u .
diff --git a/internal/lsp/server_gen.go b/internal/lsp/server_gen.go
deleted file mode 100644
index 2062693db..000000000
--- a/internal/lsp/server_gen.go
+++ /dev/null
@@ -1,321 +0,0 @@
-// 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 lsp
-
-// code generated by helper. DO NOT EDIT.
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-func (s *Server) CodeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
- return s.codeAction(ctx, params)
-}
-
-func (s *Server) CodeLens(ctx context.Context, params *protocol.CodeLensParams) ([]protocol.CodeLens, error) {
- return s.codeLens(ctx, params)
-}
-
-func (s *Server) CodeLensRefresh(context.Context) error {
- return notImplemented("CodeLensRefresh")
-}
-
-func (s *Server) ColorPresentation(context.Context, *protocol.ColorPresentationParams) ([]protocol.ColorPresentation, error) {
- return nil, notImplemented("ColorPresentation")
-}
-
-func (s *Server) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) {
- return s.completion(ctx, params)
-}
-
-func (s *Server) Declaration(context.Context, *protocol.DeclarationParams) (protocol.Declaration, error) {
- return nil, notImplemented("Declaration")
-}
-
-func (s *Server) Definition(ctx context.Context, params *protocol.DefinitionParams) (protocol.Definition, error) {
- return s.definition(ctx, params)
-}
-
-func (s *Server) Diagnostic(context.Context, *string) (*string, error) {
- return nil, notImplemented("Diagnostic")
-}
-
-func (s *Server) DiagnosticRefresh(context.Context) error {
- return notImplemented("DiagnosticRefresh")
-}
-
-func (s *Server) DiagnosticWorkspace(context.Context, *protocol.WorkspaceDiagnosticParams) (*protocol.WorkspaceDiagnosticReport, error) {
- return nil, notImplemented("DiagnosticWorkspace")
-}
-
-func (s *Server) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error {
- return s.didChange(ctx, params)
-}
-
-func (s *Server) DidChangeConfiguration(ctx context.Context, _gen *protocol.DidChangeConfigurationParams) error {
- return s.didChangeConfiguration(ctx, _gen)
-}
-
-func (s *Server) DidChangeNotebookDocument(context.Context, *protocol.DidChangeNotebookDocumentParams) error {
- return notImplemented("DidChangeNotebookDocument")
-}
-
-func (s *Server) DidChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error {
- return s.didChangeWatchedFiles(ctx, params)
-}
-
-func (s *Server) DidChangeWorkspaceFolders(ctx context.Context, params *protocol.DidChangeWorkspaceFoldersParams) error {
- return s.didChangeWorkspaceFolders(ctx, params)
-}
-
-func (s *Server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
- return s.didClose(ctx, params)
-}
-
-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) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
- return s.didOpen(ctx, params)
-}
-
-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) DidSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error {
- return s.didSave(ctx, params)
-}
-
-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) DocumentHighlight(ctx context.Context, params *protocol.DocumentHighlightParams) ([]protocol.DocumentHighlight, error) {
- return s.documentHighlight(ctx, params)
-}
-
-func (s *Server) DocumentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) {
- return s.documentLink(ctx, params)
-}
-
-func (s *Server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]interface{}, error) {
- return s.documentSymbol(ctx, params)
-}
-
-func (s *Server) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) {
- return s.executeCommand(ctx, params)
-}
-
-func (s *Server) Exit(ctx context.Context) error {
- return s.exit(ctx)
-}
-
-func (s *Server) FoldingRange(ctx context.Context, params *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) {
- return s.foldingRange(ctx, params)
-}
-
-func (s *Server) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) {
- return s.formatting(ctx, params)
-}
-
-func (s *Server) Hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
- return s.hover(ctx, params)
-}
-
-func (s *Server) Implementation(ctx context.Context, params *protocol.ImplementationParams) (protocol.Definition, error) {
- return s.implementation(ctx, params)
-}
-
-func (s *Server) IncomingCalls(ctx context.Context, params *protocol.CallHierarchyIncomingCallsParams) ([]protocol.CallHierarchyIncomingCall, error) {
- return s.incomingCalls(ctx, params)
-}
-
-func (s *Server) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
- return s.initialize(ctx, params)
-}
-
-func (s *Server) Initialized(ctx context.Context, params *protocol.InitializedParams) error {
- return s.initialized(ctx, params)
-}
-
-func (s *Server) InlayHint(context.Context, *protocol.InlayHintParams) ([]protocol.InlayHint, error) {
- return nil, notImplemented("InlayHint")
-}
-
-func (s *Server) InlayHintRefresh(context.Context) error {
- return notImplemented("InlayHintRefresh")
-}
-
-func (s *Server) InlineValue(context.Context, *protocol.InlineValueParams) ([]protocol.InlineValue, error) {
- return nil, notImplemented("InlineValue")
-}
-
-func (s *Server) InlineValueRefresh(context.Context) error {
- return notImplemented("InlineValueRefresh")
-}
-
-func (s *Server) LinkedEditingRange(context.Context, *protocol.LinkedEditingRangeParams) (*protocol.LinkedEditingRanges, error) {
- return nil, notImplemented("LinkedEditingRange")
-}
-
-func (s *Server) LogTrace(context.Context, *protocol.LogTraceParams) error {
- return notImplemented("LogTrace")
-}
-
-func (s *Server) Moniker(context.Context, *protocol.MonikerParams) ([]protocol.Moniker, error) {
- return nil, notImplemented("Moniker")
-}
-
-func (s *Server) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
- return s.nonstandardRequest(ctx, method, params)
-}
-
-func (s *Server) OnTypeFormatting(context.Context, *protocol.DocumentOnTypeFormattingParams) ([]protocol.TextEdit, error) {
- return nil, notImplemented("OnTypeFormatting")
-}
-
-func (s *Server) OutgoingCalls(ctx context.Context, params *protocol.CallHierarchyOutgoingCallsParams) ([]protocol.CallHierarchyOutgoingCall, error) {
- return s.outgoingCalls(ctx, params)
-}
-
-func (s *Server) PrepareCallHierarchy(ctx context.Context, params *protocol.CallHierarchyPrepareParams) ([]protocol.CallHierarchyItem, error) {
- return s.prepareCallHierarchy(ctx, params)
-}
-
-func (s *Server) PrepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.PrepareRename2Gn, error) {
- return s.prepareRename(ctx, params)
-}
-
-func (s *Server) PrepareTypeHierarchy(context.Context, *protocol.TypeHierarchyPrepareParams) ([]protocol.TypeHierarchyItem, error) {
- return nil, notImplemented("PrepareTypeHierarchy")
-}
-
-func (s *Server) RangeFormatting(context.Context, *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) {
- return nil, notImplemented("RangeFormatting")
-}
-
-func (s *Server) References(ctx context.Context, params *protocol.ReferenceParams) ([]protocol.Location, error) {
- return s.references(ctx, params)
-}
-
-func (s *Server) Rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) {
- return s.rename(ctx, params)
-}
-
-func (s *Server) Resolve(context.Context, *protocol.InlayHint) (*protocol.InlayHint, error) {
- return nil, notImplemented("Resolve")
-}
-
-func (s *Server) ResolveCodeAction(context.Context, *protocol.CodeAction) (*protocol.CodeAction, error) {
- return nil, notImplemented("ResolveCodeAction")
-}
-
-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) SelectionRange(context.Context, *protocol.SelectionRangeParams) ([]protocol.SelectionRange, error) {
- return nil, notImplemented("SelectionRange")
-}
-
-func (s *Server) SemanticTokensFull(ctx context.Context, p *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) {
- return s.semanticTokensFull(ctx, p)
-}
-
-func (s *Server) SemanticTokensFullDelta(ctx context.Context, p *protocol.SemanticTokensDeltaParams) (interface{}, error) {
- return s.semanticTokensFullDelta(ctx, p)
-}
-
-func (s *Server) SemanticTokensRange(ctx context.Context, p *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) {
- return s.semanticTokensRange(ctx, p)
-}
-
-func (s *Server) SemanticTokensRefresh(ctx context.Context) error {
- return s.semanticTokensRefresh(ctx)
-}
-
-func (s *Server) SetTrace(context.Context, *protocol.SetTraceParams) error {
- return notImplemented("SetTrace")
-}
-
-func (s *Server) Shutdown(ctx context.Context) error {
- return s.shutdown(ctx)
-}
-
-func (s *Server) SignatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) {
- return s.signatureHelp(ctx, params)
-}
-
-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) Symbol(ctx context.Context, params *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) {
- return s.symbol(ctx, params)
-}
-
-func (s *Server) TypeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) (protocol.Definition, error) {
- return s.typeDefinition(ctx, params)
-}
-
-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 (s *Server) WorkDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error {
- return s.workDoneProgressCancel(ctx, params)
-}
diff --git a/internal/lsp/signature_help.go b/internal/lsp/signature_help.go
deleted file mode 100644
index 24dee1b9a..000000000
--- a/internal/lsp/signature_help.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-func (s *Server) signatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) {
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.Go)
- defer release()
- if !ok {
- return nil, err
- }
- info, activeParameter, err := source.SignatureHelp(ctx, snapshot, fh, params.Position)
- if err != nil {
- event.Error(ctx, "no signature help", err, tag.Position.Of(params.Position))
- return nil, nil
- }
- return &protocol.SignatureHelp{
- Signatures: []protocol.SignatureInformation{*info},
- ActiveParameter: uint32(activeParameter),
- }, nil
-}
diff --git a/internal/lsp/snippet/snippet_builder.go b/internal/lsp/snippet/snippet_builder.go
deleted file mode 100644
index f7fc5b445..000000000
--- a/internal/lsp/snippet/snippet_builder.go
+++ /dev/null
@@ -1,104 +0,0 @@
-// 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()
-}
-
-// 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/internal/lsp/snippet/snippet_builder_test.go b/internal/lsp/snippet/snippet_builder_test.go
deleted file mode 100644
index bc814b16d..000000000
--- a/internal/lsp/snippet/snippet_builder_test.go
+++ /dev/null
@@ -1,62 +0,0 @@
-// 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/internal/lsp/source/add_import.go b/internal/lsp/source/add_import.go
deleted file mode 100644
index 816acc2c2..000000000
--- a/internal/lsp/source/add_import.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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 source
-
-import (
- "context"
-
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-// AddImport adds a single import statement to the given file
-func AddImport(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, importPath string) ([]protocol.TextEdit, error) {
- _, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
- if err != nil {
- return nil, err
- }
- return ComputeOneImportFixEdits(snapshot, pgf, &imports.ImportFix{
- StmtInfo: imports.ImportInfo{
- ImportPath: importPath,
- },
- FixType: imports.AddImport,
- })
-}
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
deleted file mode 100755
index 14140bb63..000000000
--- a/internal/lsp/source/api_json.go
+++ /dev/null
@@ -1,972 +0,0 @@
-// Code generated by "golang.org/x/tools/gopls/doc/generate"; DO NOT EDIT.
-
-package source
-
-var GeneratedAPIJSON = &APIJSON{
- Options: map[string][]*OptionJSON{
- "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",
- Default: "[]",
- Hierarchy: "build",
- },
- {
- Name: "env",
- Type: "map[string]string",
- Doc: "env adds environment variables to external commands run by `gopls`, most notably `go list`.\n",
- Default: "{}",
- 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\nExamples:\n\nExclude node_modules: `-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",
- Default: "[\"-node_modules\"]",
- Hierarchy: "build",
- },
- {
- Name: "templateExtensions",
- Type: "[]string",
- Doc: "templateExtensions gives the extensions of file names that are treateed\nas template files. (The extension\nis the part of the file name after the final dot.)\n",
- Default: "[]",
- Hierarchy: "build",
- },
- {
- Name: "memoryMode",
- Type: "enum",
- Doc: "memoryMode controls the tradeoff `gopls` makes between memory usage and\ncorrectness.\n\nValues other than `Normal` are untested and may break in surprising ways.\n",
- EnumValues: []EnumValue{
- {
- Value: "\"DegradeClosed\"",
- Doc: "`\"DegradeClosed\"`: In DegradeClosed mode, `gopls` will collect less information about\npackages without open files. As a result, features like Find\nReferences and Rename will miss results in such packages.\n",
- },
- {Value: "\"Normal\""},
- },
- Default: "\"Normal\"",
- Status: "experimental",
- Hierarchy: "build",
- },
- {
- Name: "expandWorkspaceToModule",
- Type: "bool",
- Doc: "expandWorkspaceToModule instructs `gopls` to adjust the scope of the\nworkspace to find the best available module root. `gopls` first looks for\na go.mod file in any parent directory of the workspace folder, expanding\nthe scope to that directory if it exists. If no viable parent directory is\nfound, gopls will check if there is exactly one child directory containing\na go.mod file, narrowing the scope to that directory if it exists.\n",
- Default: "true",
- Status: "experimental",
- Hierarchy: "build",
- },
- {
- Name: "experimentalWorkspaceModule",
- Type: "bool",
- Doc: "experimentalWorkspaceModule opts a user into the experimental support\nfor multi-module workspaces.\n",
- Default: "false",
- Status: "experimental",
- Hierarchy: "build",
- },
- {
- Name: "experimentalPackageCacheKey",
- Type: "bool",
- Doc: "experimentalPackageCacheKey controls whether to use a coarser cache key\nfor package type information to increase cache hits. This setting removes\nthe user's environment, build flags, and working directory from the cache\nkey, which should be a safe change as all relevant inputs into the type\nchecking pass are already hashed into the key. This is temporarily guarded\nby an experiment because caching behavior is subtle and difficult to\ncomprehensively test.\n",
- Default: "true",
- Status: "experimental",
- Hierarchy: "build",
- },
- {
- Name: "allowModfileModifications",
- Type: "bool",
- Doc: "allowModfileModifications disables -mod=readonly, allowing imports from\nout-of-scope modules. This option will eventually be removed.\n",
- Default: "false",
- 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",
- Default: "false",
- Status: "experimental",
- Hierarchy: "build",
- },
- {
- Name: "experimentalUseInvalidMetadata",
- Type: "bool",
- Doc: "experimentalUseInvalidMetadata enables gopls to fall back on outdated\npackage metadata to provide editor features if the go command fails to\nload packages for some reason (like an invalid go.mod file). This will\neventually be the default behavior, and this setting will be removed.\n",
- Default: "false",
- Status: "experimental",
- 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",
- EnumValues: []EnumValue{
- {Value: "\"FullDocumentation\""},
- {Value: "\"NoDocumentation\""},
- {Value: "\"SingleLine\""},
- {
- 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\""},
- },
- Default: "\"FullDocumentation\"",
- Hierarchy: "ui.documentation",
- },
- {
- Name: "linkTarget",
- Type: "string",
- Doc: "linkTarget controls where documentation links go.\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",
- Default: "\"pkg.go.dev\"",
- Hierarchy: "ui.documentation",
- },
- {
- Name: "linksInHover",
- Type: "bool",
- Doc: "linksInHover toggles the presence of links to documentation in hover.\n",
- Default: "true",
- Hierarchy: "ui.documentation",
- },
- {
- Name: "usePlaceholders",
- Type: "bool",
- Doc: "placeholders enables placeholders for function parameters or struct\nfields in completion responses.\n",
- Default: "false",
- 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",
- Default: "\"100ms\"",
- Status: "debug",
- Hierarchy: "ui.completion",
- },
- {
- Name: "matcher",
- Type: "enum",
- Doc: "matcher sets the algorithm that is used when calculating completion\ncandidates.\n",
- EnumValues: []EnumValue{
- {Value: "\"CaseInsensitive\""},
- {Value: "\"CaseSensitive\""},
- {Value: "\"Fuzzy\""},
- },
- Default: "\"Fuzzy\"",
- Status: "advanced",
- Hierarchy: "ui.completion",
- },
- {
- Name: "experimentalPostfixCompletions",
- Type: "bool",
- Doc: "experimentalPostfixCompletions enables artificial method snippets\nsuch as \"someSlice.sort!\".\n",
- Default: "true",
- Status: "experimental",
- Hierarchy: "ui.completion",
- },
- {
- Name: "importShortcut",
- Type: "enum",
- Doc: "importShortcut specifies whether import statements should link to\ndocumentation or go to definitions.\n",
- EnumValues: []EnumValue{
- {Value: "\"Both\""},
- {Value: "\"Definition\""},
- {Value: "\"Link\""},
- },
- Default: "\"Both\"",
- Hierarchy: "ui.navigation",
- },
- {
- Name: "symbolMatcher",
- Type: "enum",
- Doc: "symbolMatcher sets the algorithm that is used when finding workspace symbols.\n",
- EnumValues: []EnumValue{
- {Value: "\"CaseInsensitive\""},
- {Value: "\"CaseSensitive\""},
- {Value: "\"FastFuzzy\""},
- {Value: "\"Fuzzy\""},
- },
- 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",
- EnumValues: []EnumValue{
- {
- 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: "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\n[here](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 \"unusedparams\": true // Enable the unusedparams analyzer.\n}\n...\n```\n",
- EnumKeys: EnumKeys{
- ValueType: "bool",
- Keys: []EnumKey{
- {
- 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(&x, 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 that +build tags are well-formed and correctly located",
- 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 = &net.DNSConfigError{err}\n\nshould be replaced by:\n\n\terr = &net.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: "\"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: "\"fieldalignment\"",
- Doc: "find structs that would use less memory if their fields were sorted\n\nThis analyzer find structs that can be rearranged to use less memory, and provides\na suggested edit with the optimal order.\n\nNote that there are two different diagnostics reported. One checks struct size,\nand the other reports \"pointer bytes\" used. Pointer bytes is how many bytes of the\nobject that the garbage collector has to potentially scan for pointers, for example:\n\n\tstruct { uint32; string }\n\nhave 16 pointer bytes because the garbage collector has to scan up through the string's\ninner pointer.\n\n\tstruct { string; *uint32 }\n\nhas 24 pointer bytes because it has to scan further through the *uint32.\n\n\tstruct { string; uint32 }\n\nhas 8 because it can stop immediately after the string pointer.\n",
- Default: "false",
- },
- {
- 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.\n",
- 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 checks for references to loop variables from within a\nfunction literal inside the loop body. It checks only instances where\nthe function literal is called in a defer or go statement that is the\nlast statement in the loop body, as otherwise we would need whole\nprogram analysis.\n\nFor example:\n\n\tfor i, v := range s {\n\t\tgo func() {\n\t\t\tprintln(i, v) // not what you might expect\n\t\t}()\n\t}\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 := &v\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",
- Default: "false",
- },
- {
- Name: "\"printf\"",
- Doc: "check consistency of Printf format strings and arguments\n\nThe check applies to known functions (for example, those in package fmt)\nas well as any detected wrappers of known functions.\n\nA function that wants to avail itself of printf checking but is not\nfound by this analyzer's heuristics (for example, due to use of\ndynamic calls) can insert a bogus call:\n\n\tif false {\n\t\t_ = fmt.Sprintf(format, args...) // enable printf checking\n\t}\n\nThe -funcs flag specifies a comma-separated list of names of additional\nknown formatting functions or methods. If the name contains a period,\nit must denote a specific function using one of the following forms:\n\n\tdir/pkg.Function\n\tdir/pkg.Type.Method\n\t(*dir/pkg.Type).Method\n\nOtherwise the name is interpreted as a case-insensitive unqualified\nidentifier such as \"errorf\". Either way, if a listed name ends in f, the\nfunction is assumed to be Printf-like, taking a format string before the\nargument list. Otherwise it is assumed to be Print-like, taking a list\nof arguments with no format string.\n",
- 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}\n",
- Default: "false",
- },
- {
- Name: "\"shift\"",
- Doc: "check for shifts that equal or exceed the width of the integer",
- Default: "true",
- },
- {
- Name: "\"simplifycompositelit\"",
- Doc: "check for composite literal simplifications\n\nAn array, slice, or map composite literal of the form:\n\t[]T{T{}, T{}}\nwill be simplified to:\n\t[]T{{}, {}}\n\nThis is one of the simplifications that \"gofmt -s\" applies.",
- Default: "true",
- },
- {
- Name: "\"simplifyrange\"",
- Doc: "check for range statement simplifications\n\nA range of the form:\n\tfor x, _ = range v {...}\nwill be simplified to:\n\tfor x = range v {...}\n\nA range of the form:\n\tfor _ = range v {...}\nwill be simplified to:\n\tfor range v {...}\n\nThis is one of the simplifications that \"gofmt -s\" applies.",
- Default: "true",
- },
- {
- Name: "\"simplifyslice\"",
- Doc: "check for slice simplifications\n\nA slice expression of the form:\n\ts[a:len(s)]\nwill be simplified to:\n\ts[a:]\n\nThis is one of the simplifications that \"gofmt -s\" applies.",
- 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 func (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\tFormat GobEncode GobDecode MarshalJSON MarshalXML\n\tPeek ReadByte ReadFrom ReadRune Scan Seek\n\tUnmarshalJSON UnreadByte UnreadRune WriteByte\n\tWriteTo\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.\n",
- 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: "\"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\nfunc TestFoo(t *testing.T) {\n go func() {\n t.Fatal(\"oops\") // error: (*T).Fatal called from non-test goroutine\n }()\n}\n",
- Default: "true",
- },
- {
- Name: "\"tests\"",
- Doc: "check for common mistaken usages of tests and examples\n\nThe tests checker walks Test, Benchmark 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: "\"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 reduce false positives it ignores:\n- methods\n- parameters that do not have a name or are underscored\n- functions in test files\n- functions with empty bodies or those with just a return stmt",
- Default: "false",
- },
- {
- 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 effects,\nso it is always a mistake to discard the result. This analyzer reports\ncalls to certain functions in which the result of the call is ignored.\n\nThe set of functions may be controlled using flags.",
- Default: "true",
- },
- {
- 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\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\tfunc (t T) f() { // t is a copy\n\t\tt.x = i // unused write to field x\n\t}\n",
- Default: "false",
- },
- {
- Name: "\"useany\"",
- Doc: "check for constraints that could be simplified to \"any\"",
- Default: "false",
- },
- {
- 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\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\nwill turn into\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.\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\tz := 1\n\tz := 2\nwill turn into\n\tz := 1\n\tz = 2\n",
- 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\tfunc z() { return nil }\nwill turn into\n\tfunc z() { return }\n",
- Default: "true",
- },
- {
- Name: "\"undeclaredname\"",
- Doc: "suggested fixes for \"undeclared name: <>\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: <>\". It will either insert a new statement,\nsuch as:\n\n\"<> := \"\n\nor a new function declaration, such as:\n\nfunc <>(inferred parameters) {\n\tpanic(\"implement me!\")\n}\n",
- Default: "true",
- },
- {
- Name: "\"fillstruct\"",
- Doc: "note incomplete struct initializations\n\nThis analyzer provides diagnostics for any struct literals that do not have\nany fields initialized. Because the suggested fix for this analysis is\nexpensive to compute, callers should compute it separately, using the\nSuggestedFix function below.\n",
- Default: "true",
- },
- {
- Name: "\"stubmethods\"",
- Doc: "stub methods analyzer\n\nThis analyzer generates method stubs for concrete types\nin order to implement a target interface",
- Default: "true",
- },
- },
- },
- Default: "{}",
- Hierarchy: "ui.diagnostic",
- },
- {
- Name: "staticcheck",
- Type: "bool",
- Doc: "staticcheck enables additional analyses from staticcheck.io.\n",
- Default: "false",
- Status: "experimental",
- Hierarchy: "ui.diagnostic",
- },
- {
- Name: "annotations",
- Type: "map[string]bool",
- Doc: "annotations specifies the various kinds of optimization diagnostics\nthat should be reported by the gc_details command.\n",
- EnumKeys: EnumKeys{
- ValueType: "bool",
- Keys: []EnumKey{
- {
- 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",
- },
- },
- },
- Default: "{\"bounds\":true,\"escape\":true,\"inline\":true,\"nil\":true}",
- 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",
- Default: "\"250ms\"",
- Status: "advanced",
- Hierarchy: "ui.diagnostic",
- },
- {
- Name: "experimentalWatchedFileDelay",
- Type: "time.Duration",
- Doc: "experimentalWatchedFileDelay controls the amount of time that gopls waits\nfor additional workspace/didChangeWatchedFiles notifications to arrive,\nbefore processing all such notifications in a single batch. This is\nintended for use by LSP clients that don't support their own batching of\nfile system notifications.\n\nThis option must be set to a valid duration string, for example `\"100ms\"`.\n",
- Default: "\"0s\"",
- Status: "experimental",
- Hierarchy: "ui.diagnostic",
- },
- {
- Name: "codelenses",
- Type: "map[string]bool",
- Doc: "codelenses overrides the enabled/disabled state of code lenses. See the\n\"Code Lenses\" section of the\n[Settings page](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#code-lenses)\nfor the list of supported lenses.\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: EnumKeys{
- ValueType: "bool",
- Keys: []EnumKey{
- {
- Name: "\"gc_details\"",
- Doc: "Toggle the calculation of gc annotations.",
- Default: "false",
- },
- {
- Name: "\"generate\"",
- Doc: "Runs `go generate` for a given directory.",
- Default: "true",
- },
- {
- Name: "\"regenerate_cgo\"",
- Doc: "Regenerates cgo definitions.",
- Default: "true",
- },
- {
- Name: "\"test\"",
- Doc: "Runs `go test` for a specific set of test or benchmark functions.",
- Default: "false",
- },
- {
- Name: "\"tidy\"",
- Doc: "Runs `go mod tidy` for a module.",
- Default: "true",
- },
- {
- Name: "\"upgrade_dependency\"",
- Doc: "Upgrades a dependency in the go.mod file for a module.",
- Default: "true",
- },
- {
- Name: "\"vendor\"",
- Doc: "Runs `go mod vendor` for a module.",
- Default: "true",
- },
- },
- },
- Default: "{\"gc_details\":false,\"generate\":true,\"regenerate_cgo\":true,\"tidy\":true,\"upgrade_dependency\":true,\"vendor\":true}",
- Hierarchy: "ui",
- },
- {
- Name: "semanticTokens",
- Type: "bool",
- Doc: "semanticTokens controls whether the LSP server will send\nsemantic tokens to the client.\n",
- 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",
- Default: "\"\"",
- Hierarchy: "formatting",
- },
- {
- Name: "gofumpt",
- Type: "bool",
- Doc: "gofumpt indicates if we should run gofumpt formatting.\n",
- Default: "false",
- Hierarchy: "formatting",
- },
- {
- Name: "verboseOutput",
- Type: "bool",
- Doc: "verboseOutput enables additional debug logging.\n",
- Default: "false",
- Status: "debug",
- },
- },
- },
- Commands: []*CommandJSON{
- {
- Command: "gopls.add_dependency",
- Title: "Add a dependency",
- Doc: "Adds a dependency to the go.mod file for a module.",
- ArgDoc: "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// Additional args to pass to the go command.\n\t\"GoCmdArgs\": []string,\n\t// Whether to add a require directive.\n\t\"AddRequire\": bool,\n}",
- },
- {
- Command: "gopls.add_import",
- Title: "Add an import",
- Doc: "Ask the server to add an import path to a given Go file. The method will\ncall applyEdit on the client so that clients don't have to apply the edit\nthemselves.",
- ArgDoc: "{\n\t// ImportPath is the target import path that should\n\t// be added to the URI file\n\t\"ImportPath\": string,\n\t// URI is the file that the ImportPath should be\n\t// added to\n\t\"URI\": string,\n}",
- },
- {
- Command: "gopls.apply_fix",
- Title: "Apply a fix",
- Doc: "Applies a fix to a region of source code.",
- ArgDoc: "{\n\t// The fix to apply.\n\t\"Fix\": string,\n\t// The file URI for the document to fix.\n\t\"URI\": string,\n\t// The document range to scan for fixes.\n\t\"Range\": {\n\t\t\"start\": {\n\t\t\t\"line\": uint32,\n\t\t\t\"character\": uint32,\n\t\t},\n\t\t\"end\": {\n\t\t\t\"line\": uint32,\n\t\t\t\"character\": uint32,\n\t\t},\n\t},\n}",
- },
- {
- Command: "gopls.check_upgrades",
- Title: "Check for upgrades",
- Doc: "Checks for module upgrades.",
- ArgDoc: "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// The modules to check.\n\t\"Modules\": []string,\n}",
- },
- {
- Command: "gopls.edit_go_directive",
- Title: "Run go mod edit -go=version",
- Doc: "Runs `go mod edit -go=version` for a module.",
- ArgDoc: "{\n\t// Any document URI within the relevant module.\n\t\"URI\": string,\n\t// The version to pass to `go mod edit -go`.\n\t\"Version\": string,\n}",
- },
- {
- Command: "gopls.gc_details",
- Title: "Toggle gc_details",
- Doc: "Toggle the calculation of gc annotations.",
- ArgDoc: "string",
- },
- {
- Command: "gopls.generate",
- Title: "Run go generate",
- Doc: "Runs `go generate` for a given directory.",
- ArgDoc: "{\n\t// URI for the directory to generate.\n\t\"Dir\": string,\n\t// Whether to generate recursively (go generate ./...)\n\t\"Recursive\": bool,\n}",
- },
- {
- Command: "gopls.generate_gopls_mod",
- Title: "Generate gopls.mod",
- Doc: "(Re)generate the gopls.mod file for a workspace.",
- ArgDoc: "{\n\t// The file URI.\n\t\"URI\": string,\n}",
- },
- {
- Command: "gopls.go_get_package",
- Title: "go get a package",
- Doc: "Runs `go get` to fetch a package.",
- ArgDoc: "{\n\t// Any document URI within the relevant module.\n\t\"URI\": string,\n\t// The package to go get.\n\t\"Pkg\": string,\n\t\"AddRequire\": bool,\n}",
- },
- {
- Command: "gopls.list_imports",
- Title: "List imports of a file and its package",
- Doc: "Retrieve a list of imports in the given Go file, and the package it\nbelongs to.",
- ArgDoc: "{\n\t// The file URI.\n\t\"URI\": string,\n}",
- ResultDoc: "{\n\t// Imports is a list of imports in the requested file.\n\t\"Imports\": []{\n\t\t\"Path\": string,\n\t\t\"Name\": string,\n\t},\n\t// PackageImports is a list of all imports in the requested file's package.\n\t\"PackageImports\": []{\n\t\t\"Path\": string,\n\t},\n}",
- },
- {
- Command: "gopls.list_known_packages",
- Title: "List known packages",
- Doc: "Retrieve a list of packages that are importable from the given URI.",
- ArgDoc: "{\n\t// The file URI.\n\t\"URI\": string,\n}",
- ResultDoc: "{\n\t// Packages is a list of packages relative\n\t// to the URIArg passed by the command request.\n\t// In other words, it omits paths that are already\n\t// imported or cannot be imported due to compiler\n\t// restrictions.\n\t\"Packages\": []string,\n}",
- },
- {
- Command: "gopls.regenerate_cgo",
- Title: "Regenerate cgo",
- Doc: "Regenerates cgo definitions.",
- ArgDoc: "{\n\t// The file URI.\n\t\"URI\": string,\n}",
- },
- {
- Command: "gopls.remove_dependency",
- Title: "Remove a dependency",
- Doc: "Removes a dependency from the go.mod file of a module.",
- ArgDoc: "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// The module path to remove.\n\t\"ModulePath\": string,\n\t\"OnlyDiagnostic\": bool,\n}",
- },
- {
- Command: "gopls.run_tests",
- Title: "Run test(s)",
- Doc: "Runs `go test` for a specific set of test or benchmark functions.",
- ArgDoc: "{\n\t// The test file containing the tests to run.\n\t\"URI\": string,\n\t// Specific test names to run, e.g. TestFoo.\n\t\"Tests\": []string,\n\t// Specific benchmarks to run, e.g. BenchmarkFoo.\n\t\"Benchmarks\": []string,\n}",
- },
- {
- Command: "gopls.run_vulncheck_exp",
- Title: "Run vulncheck (experimental)",
- Doc: "Run vulnerability check (`govulncheck`).",
- ArgDoc: "{\n\t// Dir is the directory from which vulncheck will run from.\n\t\"Dir\": string,\n\t// Package pattern. E.g. \"\", \".\", \"./...\".\n\t\"Pattern\": string,\n}",
- ResultDoc: "{\n\t\"Vuln\": []{\n\t\t\"ID\": string,\n\t\t\"Details\": string,\n\t\t\"Aliases\": []string,\n\t\t\"Symbol\": string,\n\t\t\"PkgPath\": string,\n\t\t\"ModPath\": string,\n\t\t\"URL\": string,\n\t\t\"CurrentVersion\": string,\n\t\t\"FixedVersion\": string,\n\t\t\"CallStacks\": [][]golang.org/x/tools/internal/lsp/command.StackEntry,\n\t},\n}",
- },
- {
- Command: "gopls.start_debugging",
- Title: "Start the gopls debug server",
- Doc: "Start the gopls debug server if it isn't running, and return the debug\naddress.",
- ArgDoc: "{\n\t// Optional: the address (including port) for the debug server to listen on.\n\t// If not provided, the debug server will bind to \"localhost:0\", and the\n\t// full debug URL will be contained in the result.\n\t// \n\t// If there is more than one gopls instance along the serving path (i.e. you\n\t// are using a daemon), each gopls instance will attempt to start debugging.\n\t// If Addr specifies a port, only the daemon will be able to bind to that\n\t// port, and each intermediate gopls instance will fail to start debugging.\n\t// For this reason it is recommended not to specify a port (or equivalently,\n\t// to specify \":0\").\n\t// \n\t// If the server was already debugging this field has no effect, and the\n\t// result will contain the previously configured debug URL(s).\n\t\"Addr\": string,\n}",
- ResultDoc: "{\n\t// The URLs to use to access the debug servers, for all gopls instances in\n\t// the serving path. For the common case of a single gopls instance (i.e. no\n\t// daemon), this will be exactly one address.\n\t// \n\t// In the case of one or more gopls instances forwarding the LSP to a daemon,\n\t// URLs will contain debug addresses for each server in the serving path, in\n\t// serving order. The daemon debug address will be the last entry in the\n\t// slice. If any intermediate gopls instance fails to start debugging, no\n\t// error will be returned but the debug URL for that server in the URLs slice\n\t// will be empty.\n\t\"URLs\": []string,\n}",
- },
- {
- Command: "gopls.test",
- Title: "Run test(s) (legacy)",
- Doc: "Runs `go test` for a specific set of test or benchmark functions.",
- ArgDoc: "string,\n[]string,\n[]string",
- },
- {
- Command: "gopls.tidy",
- Title: "Run go mod tidy",
- Doc: "Runs `go mod tidy` for a module.",
- ArgDoc: "{\n\t// The file URIs.\n\t\"URIs\": []string,\n}",
- },
- {
- Command: "gopls.toggle_gc_details",
- Title: "Toggle gc_details",
- Doc: "Toggle the calculation of gc annotations.",
- ArgDoc: "{\n\t// The file URI.\n\t\"URI\": string,\n}",
- },
- {
- Command: "gopls.update_go_sum",
- Title: "Update go.sum",
- Doc: "Updates the go.sum file for a module.",
- ArgDoc: "{\n\t// The file URIs.\n\t\"URIs\": []string,\n}",
- },
- {
- Command: "gopls.upgrade_dependency",
- Title: "Upgrade a dependency",
- Doc: "Upgrades a dependency in the go.mod file for a module.",
- ArgDoc: "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// Additional args to pass to the go command.\n\t\"GoCmdArgs\": []string,\n\t// Whether to add a require directive.\n\t\"AddRequire\": bool,\n}",
- },
- {
- Command: "gopls.vendor",
- Title: "Run go mod vendor",
- Doc: "Runs `go mod vendor` for a module.",
- ArgDoc: "{\n\t// The file URI.\n\t\"URI\": string,\n}",
- },
- },
- Lenses: []*LensJSON{
- {
- Lens: "gc_details",
- Title: "Toggle gc_details",
- Doc: "Toggle the calculation of gc annotations.",
- },
- {
- Lens: "generate",
- Title: "Run go generate",
- Doc: "Runs `go generate` for a given directory.",
- },
- {
- Lens: "regenerate_cgo",
- Title: "Regenerate cgo",
- Doc: "Regenerates cgo definitions.",
- },
- {
- Lens: "test",
- Title: "Run test(s) (legacy)",
- Doc: "Runs `go test` for a specific set of test or benchmark functions.",
- },
- {
- Lens: "tidy",
- Title: "Run go mod tidy",
- Doc: "Runs `go mod tidy` for a module.",
- },
- {
- Lens: "upgrade_dependency",
- Title: "Upgrade a dependency",
- Doc: "Upgrades a dependency in the go.mod file for a module.",
- },
- {
- Lens: "vendor",
- Title: "Run go mod vendor",
- Doc: "Runs `go mod vendor` for a module.",
- },
- },
- Analyzers: []*AnalyzerJSON{
- {
- 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(&x, 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 that +build tags are well-formed and correctly located",
- 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 = &net.DNSConfigError{err}\n\nshould be replaced by:\n\n\terr = &net.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: "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: "fieldalignment",
- Doc: "find structs that would use less memory if their fields were sorted\n\nThis analyzer find structs that can be rearranged to use less memory, and provides\na suggested edit with the optimal order.\n\nNote that there are two different diagnostics reported. One checks struct size,\nand the other reports \"pointer bytes\" used. Pointer bytes is how many bytes of the\nobject that the garbage collector has to potentially scan for pointers, for example:\n\n\tstruct { uint32; string }\n\nhave 16 pointer bytes because the garbage collector has to scan up through the string's\ninner pointer.\n\n\tstruct { string; *uint32 }\n\nhas 24 pointer bytes because it has to scan further through the *uint32.\n\n\tstruct { string; uint32 }\n\nhas 8 because it can stop immediately after the string pointer.\n",
- },
- {
- 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.\n",
- 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 checks for references to loop variables from within a\nfunction literal inside the loop body. It checks only instances where\nthe function literal is called in a defer or go statement that is the\nlast statement in the loop body, as otherwise we would need whole\nprogram analysis.\n\nFor example:\n\n\tfor i, v := range s {\n\t\tgo func() {\n\t\t\tprintln(i, v) // not what you might expect\n\t\t}()\n\t}\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 := &v\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",
- },
- {
- Name: "printf",
- Doc: "check consistency of Printf format strings and arguments\n\nThe check applies to known functions (for example, those in package fmt)\nas well as any detected wrappers of known functions.\n\nA function that wants to avail itself of printf checking but is not\nfound by this analyzer's heuristics (for example, due to use of\ndynamic calls) can insert a bogus call:\n\n\tif false {\n\t\t_ = fmt.Sprintf(format, args...) // enable printf checking\n\t}\n\nThe -funcs flag specifies a comma-separated list of names of additional\nknown formatting functions or methods. If the name contains a period,\nit must denote a specific function using one of the following forms:\n\n\tdir/pkg.Function\n\tdir/pkg.Type.Method\n\t(*dir/pkg.Type).Method\n\nOtherwise the name is interpreted as a case-insensitive unqualified\nidentifier such as \"errorf\". Either way, if a listed name ends in f, the\nfunction is assumed to be Printf-like, taking a format string before the\nargument list. Otherwise it is assumed to be Print-like, taking a list\nof arguments with no format string.\n",
- 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}\n",
- },
- {
- Name: "shift",
- Doc: "check for shifts that equal or exceed the width of the integer",
- Default: true,
- },
- {
- Name: "simplifycompositelit",
- Doc: "check for composite literal simplifications\n\nAn array, slice, or map composite literal of the form:\n\t[]T{T{}, T{}}\nwill be simplified to:\n\t[]T{{}, {}}\n\nThis is one of the simplifications that \"gofmt -s\" applies.",
- Default: true,
- },
- {
- Name: "simplifyrange",
- Doc: "check for range statement simplifications\n\nA range of the form:\n\tfor x, _ = range v {...}\nwill be simplified to:\n\tfor x = range v {...}\n\nA range of the form:\n\tfor _ = range v {...}\nwill be simplified to:\n\tfor range v {...}\n\nThis is one of the simplifications that \"gofmt -s\" applies.",
- Default: true,
- },
- {
- Name: "simplifyslice",
- Doc: "check for slice simplifications\n\nA slice expression of the form:\n\ts[a:len(s)]\nwill be simplified to:\n\ts[a:]\n\nThis is one of the simplifications that \"gofmt -s\" applies.",
- 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 func (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\tFormat GobEncode GobDecode MarshalJSON MarshalXML\n\tPeek ReadByte ReadFrom ReadRune Scan Seek\n\tUnmarshalJSON UnreadByte UnreadRune WriteByte\n\tWriteTo\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.\n",
- 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: "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\nfunc TestFoo(t *testing.T) {\n go func() {\n t.Fatal(\"oops\") // error: (*T).Fatal called from non-test goroutine\n }()\n}\n",
- Default: true,
- },
- {
- Name: "tests",
- Doc: "check for common mistaken usages of tests and examples\n\nThe tests checker walks Test, Benchmark 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: "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 reduce false positives it ignores:\n- methods\n- parameters that do not have a name or are underscored\n- functions in test files\n- functions with empty bodies or those with just a return stmt",
- },
- {
- 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 effects,\nso it is always a mistake to discard the result. This analyzer reports\ncalls to certain functions in which the result of the call is ignored.\n\nThe set of functions may be controlled using flags.",
- Default: true,
- },
- {
- 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\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\tfunc (t T) f() { // t is a copy\n\t\tt.x = i // unused write to field x\n\t}\n",
- },
- {
- Name: "useany",
- Doc: "check for constraints that could be simplified to \"any\"",
- },
- {
- 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\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\nwill turn into\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.\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\tz := 1\n\tz := 2\nwill turn into\n\tz := 1\n\tz = 2\n",
- 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\tfunc z() { return nil }\nwill turn into\n\tfunc z() { return }\n",
- Default: true,
- },
- {
- Name: "undeclaredname",
- Doc: "suggested fixes for \"undeclared name: <>\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: <>\". It will either insert a new statement,\nsuch as:\n\n\"<> := \"\n\nor a new function declaration, such as:\n\nfunc <>(inferred parameters) {\n\tpanic(\"implement me!\")\n}\n",
- Default: true,
- },
- {
- Name: "fillstruct",
- Doc: "note incomplete struct initializations\n\nThis analyzer provides diagnostics for any struct literals that do not have\nany fields initialized. Because the suggested fix for this analysis is\nexpensive to compute, callers should compute it separately, using the\nSuggestedFix function below.\n",
- Default: true,
- },
- {
- Name: "stubmethods",
- Doc: "stub methods analyzer\n\nThis analyzer generates method stubs for concrete types\nin order to implement a target interface",
- Default: true,
- },
- },
-}
diff --git a/internal/lsp/source/call_hierarchy.go b/internal/lsp/source/call_hierarchy.go
deleted file mode 100644
index 991c30aeb..000000000
--- a/internal/lsp/source/call_hierarchy.go
+++ /dev/null
@@ -1,310 +0,0 @@
-// 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 source
-
-import (
- "context"
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
- "path/filepath"
-
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// PrepareCallHierarchy returns an array of CallHierarchyItem for a file and the position within the file.
-func PrepareCallHierarchy(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyItem, error) {
- ctx, done := event.Start(ctx, "source.PrepareCallHierarchy")
- defer done()
-
- identifier, err := Identifier(ctx, snapshot, fh, pos)
- if err != nil {
- if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
- return nil, nil
- }
- return nil, err
- }
-
- // The identifier can be nil if it is an import spec.
- if identifier == nil || identifier.Declaration.obj == nil {
- return nil, nil
- }
-
- if _, ok := identifier.Declaration.obj.Type().Underlying().(*types.Signature); !ok {
- return nil, nil
- }
-
- if len(identifier.Declaration.MappedRange) == 0 {
- return nil, nil
- }
- declMappedRange := identifier.Declaration.MappedRange[0]
- rng, err := declMappedRange.Range()
- if err != nil {
- return nil, err
- }
-
- callHierarchyItem := protocol.CallHierarchyItem{
- Name: identifier.Name,
- Kind: protocol.Function,
- Tags: []protocol.SymbolTag{},
- Detail: fmt.Sprintf("%s • %s", identifier.Declaration.obj.Pkg().Path(), filepath.Base(declMappedRange.URI().Filename())),
- URI: protocol.DocumentURI(declMappedRange.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 Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyIncomingCall, error) {
- ctx, done := event.Start(ctx, "source.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
- }
-
- return toProtocolIncomingCalls(ctx, snapshot, refs)
-}
-
-// toProtocolIncomingCalls returns an array of protocol.CallHierarchyIncomingCall for ReferenceInfo's.
-// References inside same enclosure are assigned to the same enclosing function.
-func toProtocolIncomingCalls(ctx context.Context, snapshot Snapshot, refs []*ReferenceInfo) ([]protocol.CallHierarchyIncomingCall, error) {
- // an enclosing node could have multiple calls to a reference, we only show the enclosure
- // once in the result but highlight all calls using FromRanges (ranges at which the calls occur)
- var incomingCalls = map[protocol.Location]*protocol.CallHierarchyIncomingCall{}
- for _, ref := range refs {
- refRange, err := ref.Range()
- if err != nil {
- return nil, err
- }
-
- callItem, err := enclosingNodeCallItem(snapshot, ref.pkg, ref.URI(), ref.ident.NamePos)
- if err != nil {
- event.Error(ctx, "error getting enclosing node", err, tag.Method.Of(ref.Name))
- continue
- }
- loc := protocol.Location{
- URI: callItem.URI,
- Range: callItem.Range,
- }
-
- if incomingCall, ok := incomingCalls[loc]; ok {
- incomingCall.FromRanges = append(incomingCall.FromRanges, refRange)
- continue
- }
- incomingCalls[loc] = &protocol.CallHierarchyIncomingCall{
- From: callItem,
- FromRanges: []protocol.Range{refRange},
- }
- }
-
- 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 pos
-func enclosingNodeCallItem(snapshot Snapshot, pkg Package, uri span.URI, pos token.Pos) (protocol.CallHierarchyItem, error) {
- pgf, err := pkg.File(uri)
- if err != nil {
- return protocol.CallHierarchyItem{}, err
- }
-
- var funcDecl *ast.FuncDecl
- var funcLit *ast.FuncLit // innermost function literal
- var litCount int
- // Find the enclosing function, if any, and the number of func literals in between.
- path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos)
-outer:
- for _, node := range path {
- switch n := node.(type) {
- case *ast.FuncDecl:
- funcDecl = n
- break outer
- case *ast.FuncLit:
- litCount++
- if litCount > 1 {
- continue
- }
- funcLit = n
- }
- }
-
- nameIdent := path[len(path)-1].(*ast.File).Name
- kind := protocol.Package
- if funcDecl != nil {
- nameIdent = funcDecl.Name
- kind = protocol.Function
- }
-
- nameStart, nameEnd := nameIdent.NamePos, nameIdent.NamePos+token.Pos(len(nameIdent.Name))
- if funcLit != nil {
- nameStart, nameEnd = funcLit.Type.Func, funcLit.Type.Params.Pos()
- kind = protocol.Function
- }
- rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, nameStart, nameEnd).Range()
- if err != nil {
- return protocol.CallHierarchyItem{}, err
- }
-
- name := nameIdent.Name
- for i := 0; i < litCount; i++ {
- name += ".func()"
- }
-
- return protocol.CallHierarchyItem{
- Name: name,
- Kind: kind,
- Tags: []protocol.SymbolTag{},
- Detail: fmt.Sprintf("%s • %s", pkg.PkgPath(), filepath.Base(uri.Filename())),
- URI: protocol.DocumentURI(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 Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyOutgoingCall, error) {
- ctx, done := event.Start(ctx, "source.OutgoingCalls")
- defer done()
-
- identifier, err := Identifier(ctx, snapshot, fh, pos)
- if err != nil {
- if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
- return nil, nil
- }
- return nil, err
- }
-
- if _, ok := identifier.Declaration.obj.Type().Underlying().(*types.Signature); !ok {
- return nil, nil
- }
- if identifier.Declaration.node == nil {
- return nil, nil
- }
- if len(identifier.Declaration.MappedRange) == 0 {
- return nil, nil
- }
- declMappedRange := identifier.Declaration.MappedRange[0]
- callExprs, err := collectCallExpressions(snapshot.FileSet(), declMappedRange.m, identifier.Declaration.node)
- if err != nil {
- return nil, err
- }
-
- return toProtocolOutgoingCalls(ctx, snapshot, fh, callExprs)
-}
-
-// collectCallExpressions collects call expression ranges inside a function.
-func collectCallExpressions(fset *token.FileSet, mapper *protocol.ColumnMapper, node ast.Node) ([]protocol.Range, error) {
- type callPos struct {
- start, end token.Pos
- }
- callPositions := []callPos{}
-
- ast.Inspect(node, 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
- }
- callPositions = append(callPositions, callPos{start: start, end: end})
- }
- return true
- })
-
- callRanges := []protocol.Range{}
- for _, call := range callPositions {
- callRange, err := NewMappedRange(fset, mapper, call.start, call.end).Range()
- if err != nil {
- return nil, err
- }
- callRanges = append(callRanges, callRange)
- }
- return callRanges, nil
-}
-
-// toProtocolOutgoingCalls returns an array of protocol.CallHierarchyOutgoingCall for ast call expressions.
-// Calls to the same function are assigned to the same declaration.
-func toProtocolOutgoingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, callRanges []protocol.Range) ([]protocol.CallHierarchyOutgoingCall, error) {
- // Multiple calls could be made to the same function, defined by "same declaration
- // AST node & same idenfitier name" to provide a unique identifier key even when
- // the func is declared in a struct or interface.
- type key struct {
- decl ast.Node
- name string
- }
- outgoingCalls := map[key]*protocol.CallHierarchyOutgoingCall{}
- for _, callRange := range callRanges {
- identifier, err := Identifier(ctx, snapshot, fh, callRange.Start)
- if err != nil {
- if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
- continue
- }
- return nil, err
- }
-
- // ignore calls to builtin functions
- if identifier.Declaration.obj.Pkg() == nil {
- continue
- }
-
- if outgoingCall, ok := outgoingCalls[key{identifier.Declaration.node, identifier.Name}]; ok {
- outgoingCall.FromRanges = append(outgoingCall.FromRanges, callRange)
- continue
- }
-
- if len(identifier.Declaration.MappedRange) == 0 {
- continue
- }
- declMappedRange := identifier.Declaration.MappedRange[0]
- rng, err := declMappedRange.Range()
- if err != nil {
- return nil, err
- }
-
- outgoingCalls[key{identifier.Declaration.node, identifier.Name}] = &protocol.CallHierarchyOutgoingCall{
- To: protocol.CallHierarchyItem{
- Name: identifier.Name,
- Kind: protocol.Function,
- Tags: []protocol.SymbolTag{},
- Detail: fmt.Sprintf("%s • %s", identifier.Declaration.obj.Pkg().Path(), filepath.Base(declMappedRange.URI().Filename())),
- URI: protocol.DocumentURI(declMappedRange.URI()),
- Range: rng,
- SelectionRange: rng,
- },
- FromRanges: []protocol.Range{callRange},
- }
- }
-
- outgoingCallItems := make([]protocol.CallHierarchyOutgoingCall, 0, len(outgoingCalls))
- for _, callItem := range outgoingCalls {
- outgoingCallItems = append(outgoingCallItems, *callItem)
- }
- return outgoingCallItems, nil
-}
diff --git a/internal/lsp/source/code_lens.go b/internal/lsp/source/code_lens.go
deleted file mode 100644
index 0ab857ac6..000000000
--- a/internal/lsp/source/code_lens.go
+++ /dev/null
@@ -1,244 +0,0 @@
-// 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 source
-
-import (
- "context"
- "go/ast"
- "go/token"
- "go/types"
- "path/filepath"
- "regexp"
- "strings"
-
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
-)
-
-type LensFunc func(context.Context, Snapshot, FileHandle) ([]protocol.CodeLens, error)
-
-// LensFuncs returns the supported lensFuncs for Go files.
-func LensFuncs() map[command.Command]LensFunc {
- return map[command.Command]LensFunc{
- command.Generate: goGenerateCodeLens,
- command.Test: runTestCodeLens,
- command.RegenerateCgo: regenerateCgoLens,
- command.GCDetails: toggleDetailsCodeLens,
- }
-}
-
-var (
- testRe = regexp.MustCompile("^Test[^a-z]")
- benchmarkRe = regexp.MustCompile("^Benchmark[^a-z]")
-)
-
-func runTestCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
- codeLens := make([]protocol.CodeLens, 0)
-
- fns, err := TestsAndBenchmarks(ctx, snapshot, fh)
- if err != nil {
- return nil, err
- }
- puri := protocol.URIFromSpanURI(fh.URI())
- for _, fn := range fns.Tests {
- cmd, err := command.NewTestCommand("run test", puri, []string{fn.Name}, nil)
- if err != nil {
- return nil, err
- }
- rng := protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start}
- codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
- }
-
- for _, fn := range fns.Benchmarks {
- cmd, err := command.NewTestCommand("run benchmark", puri, nil, []string{fn.Name})
- if err != nil {
- return nil, err
- }
- rng := protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start}
- codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
- }
-
- if len(fns.Benchmarks) > 0 {
- _, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
- 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 := NewMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
- if err != nil {
- return nil, err
- }
- var benches []string
- for _, fn := range fns.Benchmarks {
- benches = append(benches, fn.Name)
- }
- cmd, err := command.NewTestCommand("run file benchmarks", puri, nil, benches)
- if err != nil {
- return nil, err
- }
- codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
- }
- return codeLens, nil
-}
-
-type testFn struct {
- Name string
- Rng protocol.Range
-}
-
-type testFns struct {
- Tests []testFn
- Benchmarks []testFn
-}
-
-func TestsAndBenchmarks(ctx context.Context, snapshot Snapshot, fh FileHandle) (testFns, error) {
- var out testFns
-
- if !strings.HasSuffix(fh.URI().Filename(), "_test.go") {
- return out, nil
- }
- pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
- if err != nil {
- return out, err
- }
-
- for _, d := range pgf.File.Decls {
- fn, ok := d.(*ast.FuncDecl)
- if !ok {
- continue
- }
-
- rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, d.Pos(), fn.End()).Range()
- if err != nil {
- return out, err
- }
-
- if matchTestFunc(fn, pkg, testRe, "T") {
- out.Tests = append(out.Tests, testFn{fn.Name.Name, rng})
- }
-
- if matchTestFunc(fn, pkg, benchmarkRe, "B") {
- out.Benchmarks = append(out.Benchmarks, testFn{fn.Name.Name, rng})
- }
- }
-
- return out, nil
-}
-
-func matchTestFunc(fn *ast.FuncDecl, pkg Package, nameRe *regexp.Regexp, paramID string) bool {
- // Make sure that the function name matches a test function.
- if !nameRe.MatchString(fn.Name.Name) {
- return false
- }
- info := pkg.GetTypesInfo()
- if info == nil {
- return false
- }
- obj := info.ObjectOf(fn.Name)
- if obj == nil {
- return false
- }
- sig, ok := obj.Type().(*types.Signature)
- if !ok {
- return false
- }
- // Test functions should have only one parameter.
- if sig.Params().Len() != 1 {
- return false
- }
-
- // Check the type of the only parameter
- 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 Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
- pgf, err := snapshot.ParseGo(ctx, fh, ParseFull)
- 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 := NewMappedRange(snapshot.FileSet(), pgf.Mapper, l.Pos(), l.Pos()+token.Pos(len(ggDirective))).Range()
- if err != nil {
- return nil, err
- }
- dir := protocol.URIFromSpanURI(span.URIFromPath(filepath.Dir(fh.URI().Filename())))
- nonRecursiveCmd, err := command.NewGenerateCommand("run go generate", command.GenerateArgs{Dir: dir, Recursive: false})
- if err != nil {
- return nil, err
- }
- recursiveCmd, err := command.NewGenerateCommand("run go generate ./...", command.GenerateArgs{Dir: dir, Recursive: true})
- if err != nil {
- return nil, err
- }
- return []protocol.CodeLens{
- {Range: rng, Command: recursiveCmd},
- {Range: rng, Command: nonRecursiveCmd},
- }, nil
-
- }
- }
- return nil, nil
-}
-
-func regenerateCgoLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
- pgf, err := snapshot.ParseGo(ctx, fh, ParseFull)
- 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 := NewMappedRange(snapshot.FileSet(), pgf.Mapper, c.Pos(), c.EndPos).Range()
- if err != nil {
- return nil, err
- }
- puri := protocol.URIFromSpanURI(fh.URI())
- cmd, err := command.NewRegenerateCgoCommand("regenerate cgo definitions", command.URIArg{URI: puri})
- if err != nil {
- return nil, err
- }
- return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil
-}
-
-func toggleDetailsCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
- _, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
- if err != nil {
- return nil, err
- }
- rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
- if err != nil {
- return nil, err
- }
- puri := protocol.URIFromSpanURI(fh.URI())
- cmd, err := command.NewGCDetailsCommand("Toggle gc annotation details", puri)
- if err != nil {
- return nil, err
- }
- return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil
-}
diff --git a/internal/lsp/source/comment.go b/internal/lsp/source/comment.go
deleted file mode 100644
index d88471e42..000000000
--- a/internal/lsp/source/comment.go
+++ /dev/null
@@ -1,381 +0,0 @@
-// 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 source
-
-import (
- "bytes"
- "io"
- "regexp"
- "strings"
- "unicode"
- "unicode/utf8"
-)
-
-// 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.
-//
-// Each line is converted into a markdown line and empty lines are just converted to
-// newlines. Heading are prefixed with `### ` to make it a markdown heading.
-//
-// A span of indented lines retains a 4 space prefix block, with the common indent
-// prefix removed unless empty, in which case it will be converted to a newline.
-//
-// URLs in the comment text are converted into links.
-func CommentToMarkdown(text string) string {
- buf := &bytes.Buffer{}
- commentToMarkdown(buf, text)
- return buf.String()
-}
-
-var (
- mdNewline = []byte("\n")
- mdHeader = []byte("### ")
- mdIndent = []byte(" ")
- mdLinkStart = []byte("[")
- mdLinkDiv = []byte("](")
- mdLinkEnd = []byte(")")
-)
-
-func commentToMarkdown(w io.Writer, text string) {
- blocks := blocks(text)
- for i, b := range blocks {
- switch b.op {
- case opPara:
- for _, line := range b.lines {
- emphasize(w, line, true)
- }
- case opHead:
- // The header block can consist of only one line.
- // However, check the number of lines, just in case.
- if len(b.lines) == 0 {
- // Skip this block.
- continue
- }
- header := b.lines[0]
-
- w.Write(mdHeader)
- commentEscape(w, header, true)
- // Header doesn't end with \n unlike the lines of other blocks.
- w.Write(mdNewline)
- case opPre:
- for _, line := range b.lines {
- if isBlank(line) {
- w.Write(mdNewline)
- continue
- }
- w.Write(mdIndent)
- w.Write([]byte(line))
- }
- }
-
- if i < len(blocks)-1 {
- w.Write(mdNewline)
- }
- }
-}
-
-const (
- ulquo = "“"
- urquo = "”"
-)
-
-var (
- markdownEscape = regexp.MustCompile(`([\\\x60*{}[\]()#+\-.!_>~|"$%&'\/:;<=?@^])`)
-
- unicodeQuoteReplacer = strings.NewReplacer("``", ulquo, "''", urquo)
-)
-
-// commentEscape escapes comment text for markdown. If nice is set,
-// also turn `` into “; and '' into ”;.
-func commentEscape(w io.Writer, text string, nice bool) {
- if nice {
- text = convertQuotes(text)
- }
- text = escapeRegex(text)
- w.Write([]byte(text))
-}
-
-func convertQuotes(text string) string {
- return unicodeQuoteReplacer.Replace(text)
-}
-
-func escapeRegex(text string) string {
- return markdownEscape.ReplaceAllString(text, `\$1`)
-}
-
-func emphasize(w io.Writer, line string, nice bool) {
- for {
- m := matchRx.FindStringSubmatchIndex(line)
- if m == nil {
- break
- }
- // m >= 6 (two parenthesized sub-regexps in matchRx, 1st one is urlRx)
-
- // write text before match
- commentEscape(w, line[0:m[0]], nice)
-
- // adjust match for URLs
- match := line[m[0]:m[1]]
- if strings.Contains(match, "://") {
- m0, m1 := m[0], m[1]
- for _, s := range []string{"()", "{}", "[]"} {
- open, close := s[:1], s[1:] // E.g., "(" and ")"
- // require opening parentheses before closing parentheses (#22285)
- if i := strings.Index(match, close); i >= 0 && i < strings.Index(match, open) {
- m1 = m0 + i
- match = line[m0:m1]
- }
- // require balanced pairs of parentheses (#5043)
- for i := 0; strings.Count(match, open) != strings.Count(match, close) && i < 10; i++ {
- m1 = strings.LastIndexAny(line[:m1], s)
- match = line[m0:m1]
- }
- }
- if m1 != m[1] {
- // redo matching with shortened line for correct indices
- m = matchRx.FindStringSubmatchIndex(line[:m[0]+len(match)])
- }
- }
-
- // Following code has been modified from go/doc since words is always
- // nil. All html formatting has also been transformed into markdown formatting
-
- // analyze match
- url := ""
- if m[2] >= 0 {
- url = match
- }
-
- // write match
- if len(url) > 0 {
- w.Write(mdLinkStart)
- }
-
- commentEscape(w, match, nice)
-
- if len(url) > 0 {
- w.Write(mdLinkDiv)
- w.Write([]byte(urlReplacer.Replace(url)))
- w.Write(mdLinkEnd)
- }
-
- // advance
- line = line[m[1]:]
- }
- commentEscape(w, line, nice)
-}
-
-// Everything from here on is a copy of go/doc/comment.go
-
-const (
- // Regexp for Go identifiers
- identRx = `[\pL_][\pL_0-9]*`
-
- // Regexp for URLs
- // Match parens, and check later for balance - see #5043, #22285
- // Match .,:;?! within path, but not at end - see #18139, #16565
- // This excludes some rare yet valid urls ending in common punctuation
- // in order to allow sentences ending in URLs.
-
- // protocol (required) e.g. http
- protoPart = `(https?|ftp|file|gopher|mailto|nntp)`
- // host (required) e.g. www.example.com or [::1]:8080
- hostPart = `([a-zA-Z0-9_@\-.\[\]:]+)`
- // path+query+fragment (optional) e.g. /path/index.html?q=foo#bar
- pathPart = `([.,:;?!]*[a-zA-Z0-9$'()*+&#=@~_/\-\[\]%])*`
-
- urlRx = protoPart + `://` + hostPart + pathPart
-)
-
-var (
- matchRx = regexp.MustCompile(`(` + urlRx + `)|(` + identRx + `)`)
- urlReplacer = strings.NewReplacer(`(`, `\(`, `)`, `\)`)
-)
-
-func indentLen(s string) int {
- i := 0
- for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
- i++
- }
- return i
-}
-
-func isBlank(s string) bool {
- return len(s) == 0 || (len(s) == 1 && s[0] == '\n')
-}
-
-func commonPrefix(a, b string) string {
- i := 0
- for i < len(a) && i < len(b) && a[i] == b[i] {
- i++
- }
- return a[0:i]
-}
-
-func unindent(block []string) {
- if len(block) == 0 {
- return
- }
-
- // compute maximum common white prefix
- prefix := block[0][0:indentLen(block[0])]
- for _, line := range block {
- if !isBlank(line) {
- prefix = commonPrefix(prefix, line[0:indentLen(line)])
- }
- }
- n := len(prefix)
-
- // remove
- for i, line := range block {
- if !isBlank(line) {
- block[i] = line[n:]
- }
- }
-}
-
-// heading returns the trimmed line if it passes as a section heading;
-// otherwise it returns the empty string.
-func heading(line string) string {
- line = strings.TrimSpace(line)
- if len(line) == 0 {
- return ""
- }
-
- // a heading must start with an uppercase letter
- r, _ := utf8.DecodeRuneInString(line)
- if !unicode.IsLetter(r) || !unicode.IsUpper(r) {
- return ""
- }
-
- // it must end in a letter or digit:
- r, _ = utf8.DecodeLastRuneInString(line)
- if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
- return ""
- }
-
- // exclude lines with illegal characters. we allow "(),"
- if strings.ContainsAny(line, ";:!?+*/=[]{}_^°&§~%#@<\">\\") {
- return ""
- }
-
- // allow "'" for possessive "'s" only
- for b := line; ; {
- i := strings.IndexRune(b, '\'')
- if i < 0 {
- break
- }
- if i+1 >= len(b) || b[i+1] != 's' || (i+2 < len(b) && b[i+2] != ' ') {
- return "" // not followed by "s "
- }
- b = b[i+2:]
- }
-
- // allow "." when followed by non-space
- for b := line; ; {
- i := strings.IndexRune(b, '.')
- if i < 0 {
- break
- }
- if i+1 >= len(b) || b[i+1] == ' ' {
- return "" // not followed by non-space
- }
- b = b[i+1:]
- }
-
- return line
-}
-
-type op int
-
-const (
- opPara op = iota
- opHead
- opPre
-)
-
-type block struct {
- op op
- lines []string
-}
-
-func blocks(text string) []block {
- var (
- out []block
- para []string
-
- lastWasBlank = false
- lastWasHeading = false
- )
-
- close := func() {
- if para != nil {
- out = append(out, block{opPara, para})
- para = nil
- }
- }
-
- lines := strings.SplitAfter(text, "\n")
- unindent(lines)
- for i := 0; i < len(lines); {
- line := lines[i]
- if isBlank(line) {
- // close paragraph
- close()
- i++
- lastWasBlank = true
- continue
- }
- if indentLen(line) > 0 {
- // close paragraph
- close()
-
- // count indented or blank lines
- j := i + 1
- for j < len(lines) && (isBlank(lines[j]) || indentLen(lines[j]) > 0) {
- j++
- }
- // but not trailing blank lines
- for j > i && isBlank(lines[j-1]) {
- j--
- }
- pre := lines[i:j]
- i = j
-
- unindent(pre)
-
- // put those lines in a pre block
- out = append(out, block{opPre, pre})
- lastWasHeading = false
- continue
- }
-
- if lastWasBlank && !lastWasHeading && i+2 < len(lines) &&
- isBlank(lines[i+1]) && !isBlank(lines[i+2]) && indentLen(lines[i+2]) == 0 {
- // current line is non-blank, surrounded by blank lines
- // and the next non-blank line is not indented: this
- // might be a heading.
- if head := heading(line); head != "" {
- close()
- out = append(out, block{opHead, []string{head}})
- i += 2
- lastWasHeading = true
- continue
- }
- }
-
- // open paragraph
- lastWasBlank = false
- lastWasHeading = false
- para = append(para, lines[i])
- i++
- }
- close()
-
- return out
-}
diff --git a/internal/lsp/source/comment_test.go b/internal/lsp/source/comment_test.go
deleted file mode 100644
index 9efde16ef..000000000
--- a/internal/lsp/source/comment_test.go
+++ /dev/null
@@ -1,368 +0,0 @@
-// 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 source
-
-import (
- "bytes"
- "reflect"
- "strings"
- "testing"
-)
-
-// This file is a copy of go/doc/comment_test.go with the exception for
-// the test cases for TestEmphasize and TestCommentEscape
-
-var headingTests = []struct {
- line string
- ok bool
-}{
- {"Section", true},
- {"A typical usage", true},
- {"ΔΛΞ is Greek", true},
- {"Foo 42", true},
- {"", false},
- {"section", false},
- {"A typical usage:", false},
- {"This code:", false},
- {"δ is Greek", false},
- {"Foo §", false},
- {"Fermat's Last Sentence", true},
- {"Fermat's", true},
- {"'sX", false},
- {"Ted 'Too' Bar", false},
- {"Use n+m", false},
- {"Scanning:", false},
- {"N:M", false},
-}
-
-func TestIsHeading(t *testing.T) {
- for _, tt := range headingTests {
- if h := heading(tt.line); (len(h) > 0) != tt.ok {
- t.Errorf("isHeading(%q) = %v, want %v", tt.line, h, tt.ok)
- }
- }
-}
-
-var blocksTests = []struct {
- in string
- out []block
- text string
-}{
- {
- in: `Para 1.
-Para 1 line 2.
-
-Para 2.
-
-Section
-
-Para 3.
-
- pre
- pre1
-
-Para 4.
-
- pre
- pre1
-
- pre2
-
-Para 5.
-
-
- pre
-
-
- pre1
- pre2
-
-Para 6.
- pre
- pre2
-`,
- out: []block{
- {opPara, []string{"Para 1.\n", "Para 1 line 2.\n"}},
- {opPara, []string{"Para 2.\n"}},
- {opHead, []string{"Section"}},
- {opPara, []string{"Para 3.\n"}},
- {opPre, []string{"pre\n", "pre1\n"}},
- {opPara, []string{"Para 4.\n"}},
- {opPre, []string{"pre\n", "pre1\n", "\n", "pre2\n"}},
- {opPara, []string{"Para 5.\n"}},
- {opPre, []string{"pre\n", "\n", "\n", "pre1\n", "pre2\n"}},
- {opPara, []string{"Para 6.\n"}},
- {opPre, []string{"pre\n", "pre2\n"}},
- },
- text: `. Para 1. Para 1 line 2.
-
-. Para 2.
-
-
-. Section
-
-. Para 3.
-
-$ pre
-$ pre1
-
-. Para 4.
-
-$ pre
-$ pre1
-
-$ pre2
-
-. Para 5.
-
-$ pre
-
-
-$ pre1
-$ pre2
-
-. Para 6.
-
-$ pre
-$ pre2
-`,
- },
- {
- in: "Para.\n\tshould not be ``escaped''",
- out: []block{
- {opPara, []string{"Para.\n"}},
- {opPre, []string{"should not be ``escaped''"}},
- },
- text: ". Para.\n\n$ should not be ``escaped''",
- },
- {
- in: "// A very long line of 46 char for line wrapping.",
- out: []block{
- {opPara, []string{"// A very long line of 46 char for line wrapping."}},
- },
- text: `. // A very long line of 46 char for line
-. // wrapping.
-`,
- },
- {
- in: `/* A very long line of 46 char for line wrapping.
-A very long line of 46 char for line wrapping. */`,
- out: []block{
- {opPara, []string{"/* A very long line of 46 char for line wrapping.\n", "A very long line of 46 char for line wrapping. */"}},
- },
- text: `. /* A very long line of 46 char for line
-. wrapping. A very long line of 46 char
-. for line wrapping. */
-`,
- },
-}
-
-func TestBlocks(t *testing.T) {
- for i, tt := range blocksTests {
- b := blocks(tt.in)
- if !reflect.DeepEqual(b, tt.out) {
- t.Errorf("#%d: mismatch\nhave: %v\nwant: %v", i, b, tt.out)
- }
- }
-}
-
-// This has been modified from go/doc to use markdown links instead of html ones
-// and use markdown escaping instead oh html
-var emphasizeTests = []struct {
- in, out string
-}{
- {"", ""},
- {"http://[::1]:8080/foo.txt", `[http\:\/\/\[\:\:1\]\:8080\/foo\.txt](http://[::1]:8080/foo.txt)`},
- {"before (https://www.google.com) after", `before \([https\:\/\/www\.google\.com](https://www.google.com)\) after`},
- {"before https://www.google.com:30/x/y/z:b::c. After", `before [https\:\/\/www\.google\.com\:30\/x\/y\/z\:b\:\:c](https://www.google.com:30/x/y/z:b::c)\. After`},
- {"http://www.google.com/path/:;!-/?query=%34b#093124", `[http\:\/\/www\.google\.com\/path\/\:\;\!\-\/\?query\=\%34b\#093124](http://www.google.com/path/:;!-/?query=%34b#093124)`},
- {"http://www.google.com/path/:;!-/?query=%34bar#093124", `[http\:\/\/www\.google\.com\/path\/\:\;\!\-\/\?query\=\%34bar\#093124](http://www.google.com/path/:;!-/?query=%34bar#093124)`},
- {"http://www.google.com/index.html! After", `[http\:\/\/www\.google\.com\/index\.html](http://www.google.com/index.html)\! After`},
- {"http://www.google.com/", `[http\:\/\/www\.google\.com\/](http://www.google.com/)`},
- {"https://www.google.com/", `[https\:\/\/www\.google\.com\/](https://www.google.com/)`},
- {"http://www.google.com/path.", `[http\:\/\/www\.google\.com\/path](http://www.google.com/path)\.`},
- {"http://en.wikipedia.org/wiki/Camellia_(cipher)", `[http\:\/\/en\.wikipedia\.org\/wiki\/Camellia\_\(cipher\)](http://en.wikipedia.org/wiki/Camellia_\(cipher\))`},
- {"(http://www.google.com/)", `\([http\:\/\/www\.google\.com\/](http://www.google.com/)\)`},
- {"http://gmail.com)", `[http\:\/\/gmail\.com](http://gmail.com)\)`},
- {"((http://gmail.com))", `\(\([http\:\/\/gmail\.com](http://gmail.com)\)\)`},
- {"http://gmail.com ((http://gmail.com)) ()", `[http\:\/\/gmail\.com](http://gmail.com) \(\([http\:\/\/gmail\.com](http://gmail.com)\)\) \(\)`},
- {"Foo bar http://example.com/ quux!", `Foo bar [http\:\/\/example\.com\/](http://example.com/) quux\!`},
- {"Hello http://example.com/%2f/ /world.", `Hello [http\:\/\/example\.com\/\%2f\/](http://example.com/%2f/) \/world\.`},
- {"Lorem http: ipsum //host/path", `Lorem http\: ipsum \/\/host\/path`},
- {"javascript://is/not/linked", `javascript\:\/\/is\/not\/linked`},
- {"http://foo", `[http\:\/\/foo](http://foo)`},
- {"art by [[https://www.example.com/person/][Person Name]]", `art by \[\[[https\:\/\/www\.example\.com\/person\/](https://www.example.com/person/)\]\[Person Name\]\]`},
- {"please visit (http://golang.org/)", `please visit \([http\:\/\/golang\.org\/](http://golang.org/)\)`},
- {"please visit http://golang.org/hello())", `please visit [http\:\/\/golang\.org\/hello\(\)](http://golang.org/hello\(\))\)`},
- {"http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD", `[http\:\/\/git\.qemu\.org\/\?p\=qemu\.git\;a\=blob\;f\=qapi\-schema\.json\;hb\=HEAD](http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD)`},
- {"https://foo.bar/bal/x(])", `[https\:\/\/foo\.bar\/bal\/x\(](https://foo.bar/bal/x\()\]\)`},
- {"foo [ http://bar(])", `foo \[ [http\:\/\/bar\(](http://bar\()\]\)`},
-}
-
-func TestEmphasize(t *testing.T) {
- for i, tt := range emphasizeTests {
- var buf bytes.Buffer
- emphasize(&buf, tt.in, true)
- out := buf.String()
- if out != tt.out {
- t.Errorf("#%d: mismatch\nhave: %v\nwant: %v", i, out, tt.out)
- }
- }
-}
-
-func TestCommentEscape(t *testing.T) {
- //ldquo -> ulquo and rdquo -> urquo
- commentTests := []struct {
- in, out string
- }{
- {"typically invoked as ``go tool asm'',", "typically invoked as " + ulquo + "go tool asm" + urquo + ","},
- {"For more detail, run ``go help test'' and ``go help testflag''", "For more detail, run " + ulquo + "go help test" + urquo + " and " + ulquo + "go help testflag" + urquo}}
- for i, tt := range commentTests {
- var buf strings.Builder
- commentEscape(&buf, tt.in, true)
- out := buf.String()
- if out != tt.out {
- t.Errorf("#%d: mismatch\nhave: %q\nwant: %q", i, out, tt.out)
- }
- }
-}
-
-func TestCommentToMarkdown(t *testing.T) {
- tests := []struct {
- in, out string
- }{
- {
- in: "F declaration.\n",
- out: "F declaration\\.\n",
- },
- {
- in: `
-F declaration. Lorem ipsum dolor sit amet.
-Etiam mattis eros at orci mollis molestie.
-`,
- out: `
-F declaration\. Lorem ipsum dolor sit amet\.
-Etiam mattis eros at orci mollis molestie\.
-`,
- },
- {
- in: `
-F declaration.
-
-Lorem ipsum dolor sit amet.
-Sed id dui turpis.
-
-
-
-
-Aenean tempus velit non auctor eleifend.
-Aenean efficitur a sem id ultricies.
-
-
-Phasellus efficitur mauris et viverra bibendum.
-`,
- out: `
-F declaration\.
-
-Lorem ipsum dolor sit amet\.
-Sed id dui turpis\.
-
-Aenean tempus velit non auctor eleifend\.
-Aenean efficitur a sem id ultricies\.
-
-Phasellus efficitur mauris et viverra bibendum\.
-`,
- },
- {
- in: `
-F declaration.
-
-Aenean tempus velit non auctor eleifend.
-
-Section
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit.
-
- func foo() {}
-
-
- func bar() {}
-
-Fusce lorem lacus.
-
- func foo() {}
-
- func bar() {}
-
-Maecenas in lobortis lectus.
-
- func foo() {}
-
- func bar() {}
-
-Phasellus efficitur mauris et viverra bibendum.
-`,
- out: `
-F declaration\.
-
-Aenean tempus velit non auctor eleifend\.
-
-### Section
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit\.
-
- func foo() {}
-
-
- func bar() {}
-
-Fusce lorem lacus\.
-
- func foo() {}
-
- func bar() {}
-
-Maecenas in lobortis lectus\.
-
- func foo() {}
-
- func bar() {}
-
-Phasellus efficitur mauris et viverra bibendum\.
-`,
- },
- {
- in: `
-F declaration.
-
- func foo() {
- fmt.Println("foo")
- }
- func bar() {
- fmt.Println("bar")
- }
-`,
- out: `
-F declaration\.
-
- func foo() {
- fmt.Println("foo")
- }
- func bar() {
- fmt.Println("bar")
- }
-`,
- },
- }
- for i, tt := range tests {
- // Comments start with new lines for better readability. So, we should trim them.
- tt.in = strings.TrimPrefix(tt.in, "\n")
- tt.out = strings.TrimPrefix(tt.out, "\n")
-
- if out := CommentToMarkdown(tt.in); out != tt.out {
- t.Errorf("#%d: mismatch\nhave: %q\nwant: %q", i, out, tt.out)
- }
- }
-}
diff --git a/internal/lsp/source/completion/builtin.go b/internal/lsp/source/completion/builtin.go
deleted file mode 100644
index 39732d864..000000000
--- a/internal/lsp/source/completion/builtin.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// 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.GetTypesInfo().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.GetTypesInfo(), 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.GetTypesInfo().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.GetTypesInfo().TypeOf(call.Args[0])
- if len(call.Args) > 1 {
- t2 = c.pkg.GetTypesInfo().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/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go
deleted file mode 100644
index 60c404dc5..000000000
--- a/internal/lsp/source/completion/completion.go
+++ /dev/null
@@ -1,2967 +0,0 @@
-// 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/constant"
- "go/scanner"
- "go/token"
- "go/types"
- "math"
- "sort"
- "strconv"
- "strings"
- "sync"
- "time"
- "unicode"
-
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/fuzzy"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/snippet"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/typeparams"
- errors "golang.org/x/xerrors"
-)
-
-type CompletionItem struct {
- // 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
-
- // obj is the object from which this candidate was derived, if any.
- // obj is for internal use only.
- obj types.Object
-}
-
-// completionOptions holds completion specific configuration.
-type completionOptions struct {
- unimported bool
- documentation bool
- fullDocumentation bool
- placeholders bool
- literal bool
- snippets bool
- postfix bool
- matcher source.Matcher
- budget time.Duration
-}
-
-// 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 source.Snapshot
- pkg source.Package
- qf types.Qualifier
- 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 source.FileHandle
-
- // 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
-
- // pos is the position at which the request was triggered.
- 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(opts *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
-
- // mapper converts the positions in the file from which the completion originated.
- mapper *protocol.ColumnMapper
-
- // startTime is when we started processing this completion request. It does
- // not include any time the request spent in the queue.
- startTime time.Time
-}
-
-// 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
- pkg source.Package
-}
-
-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
- cursor token.Pos
- source.MappedRange
-}
-
-func (p Selection) Content() string {
- return p.content
-}
-
-func (p Selection) Start() token.Pos {
- return p.MappedRange.SpanRange().Start
-}
-
-func (p Selection) End() token.Pos {
- return p.MappedRange.SpanRange().End
-}
-
-func (p Selection) Prefix() string {
- return p.content[:p.cursor-p.SpanRange().Start]
-}
-
-func (p Selection) Suffix() string {
- return p.content[p.cursor-p.SpanRange().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.
- MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, ident.Pos(), ident.End()),
- }
-
- c.setMatcherFromPrefix(c.surrounding.Prefix())
-}
-
-func (c *completer) setMatcherFromPrefix(prefix string) {
- switch c.opts.matcher {
- case source.Fuzzy:
- c.matcher = fuzzy.NewMatcher(prefix)
- case source.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,
- MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, c.pos, c.pos),
- }
- }
- return c.surrounding
-}
-
-// candidate represents a completion candidate.
-type candidate struct {
- // obj is the types.Object to complete to.
- 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 insterted 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
-}
-
-// ErrIsDefinition is an error that informs the user they got no
-// completions because they tried to complete the name of a new object
-// being defined.
-type ErrIsDefinition struct {
- objStr string
-}
-
-func (e ErrIsDefinition) Error() string {
- msg := "this is a definition"
- if e.objStr != "" {
- msg += " of " + e.objStr
- }
- return msg
-}
-
-// 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 source.Snapshot, fh source.FileHandle, protoPos protocol.Position, protoContext protocol.CompletionContext) ([]CompletionItem, *Selection, error) {
- ctx, done := event.Start(ctx, "completion.Completion")
- defer done()
-
- startTime := time.Now()
-
- pkg, pgf, err := source.GetParsedFile(ctx, snapshot, fh, source.NarrowestPackage)
- 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, errors.Errorf("getting file for Completion: %w (package completions: %v)", err, innerErr)
- }
- return items, surrounding, nil
- }
- spn, err := pgf.Mapper.PointSpan(protoPos)
- if err != nil {
- return nil, nil, err
- }
- rng, err := spn.Range(pgf.Mapper.Converter)
- 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, rng.Start-1, rng.Start-1)
- if path == nil {
- return nil, nil, errors.Errorf("cannot find node enclosing position")
- }
-
- pos := rng.Start
-
- // 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:
- // reject defining identifiers
- if obj, ok := pkg.GetTypesInfo().Defs[n]; ok {
- if v, ok := obj.(*types.Var); ok && v.IsField() && v.Embedded() {
- // An anonymous field is also a reference to a type.
- } else if pgf.File.Name == n {
- // Don't skip completions if Ident is for package name.
- break
- } else {
- objStr := ""
- if obj != nil {
- qual := types.RelativeTo(pkg.GetTypes())
- objStr = types.ObjectString(obj, qual)
- }
- ans, sel := definition(path, obj, snapshot.FileSet(), pgf.Mapper, fh)
- 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, ErrIsDefinition{objStr: objStr}
- }
- }
- }
-
- opts := snapshot.View().Options()
- c := &completer{
- pkg: pkg,
- snapshot: snapshot,
- qf: source.Qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo()),
- completionContext: completionContext{
- triggerCharacter: protoContext.TriggerCharacter,
- triggerKind: protoContext.TriggerKind,
- },
- fh: fh,
- filename: fh.URI().Filename(),
- file: pgf.File,
- path: path,
- pos: pos,
- seen: make(map[types.Object]bool),
- enclosingFunc: enclosingFunction(path, pkg.GetTypesInfo()),
- enclosingCompositeLiteral: enclosingCompositeLiteral(path, rng.Start, pkg.GetTypesInfo()),
- deepState: deepCompletionState{
- enabled: opts.DeepCompletion,
- },
- opts: &completionOptions{
- matcher: opts.Matcher,
- unimported: opts.CompleteUnimported,
- documentation: opts.CompletionDocumentation && opts.HoverKind != source.NoDocumentation,
- fullDocumentation: opts.HoverKind == source.FullDocumentation,
- placeholders: opts.UsePlaceholders,
- literal: opts.LiteralCompletions && opts.InsertTextFormat == protocol.SnippetTextFormat,
- budget: opts.CompletionBudget,
- snippets: opts.InsertTextFormat == protocol.SnippetTextFormat,
- postfix: opts.ExperimentalPostfixCompletions,
- },
- // default to a matcher that always matches
- matcher: prefixMatcher(""),
- methodSetCache: make(map[methodSetKey]*types.MethodSet),
- mapper: pgf.Mapper,
- startTime: startTime,
- }
-
- var cancel context.CancelFunc
- if c.opts.budget == 0 {
- ctx, cancel = context.WithCancel(ctx)
- } else {
- // timeoutDuration is the completion budget remaining. If less than
- // 10ms, set to 10ms
- timeoutDuration := time.Until(c.startTime.Add(c.opts.budget))
- if timeoutDuration < 10*time.Millisecond {
- timeoutDuration = 10 * time.Millisecond
- }
- ctx, cancel = context.WithTimeout(ctx, timeoutDuration)
- }
- defer cancel()
-
- 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)
-
- for _, callback := range c.completionCallbacks {
- 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)
-
- // 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(ctx, 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(ctx, 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.
- return c.selector(ctx, &ast.SelectorExpr{X: n.X})
- 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 identifer 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.snapshot.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(ctx context.Context, 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,
- MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, start, end),
- }
-
- 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: fmt.Sprintf("%q", pkgToConsider),
- score: score,
- })
- }
-
- c.completionCallbacks = append(c.completionCallbacks, func(opts *imports.Options) error {
- return imports.GetImportPaths(ctx, searchImports, prefix, c.filename, c.pkg.GetTypes().Name(), opts.Env)
- })
- return nil
-}
-
-// populateCommentCompletions yields completions for comments preceding or in declarations.
-func (c *completer) populateCommentCompletions(ctx context.Context, 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.snapshot.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 := file.Line(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 := file.Line(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.GetTypesInfo().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(ctx, typeNode.Fields)
- case *ast.FuncType:
- c.addFieldItems(ctx, typeNode.Params)
- c.addFieldItems(ctx, typeNode.Results)
- case *ast.InterfaceType:
- c.addFieldItems(ctx, typeNode.Methods)
- }
-
- if spec.Name.String() == "_" {
- continue
- }
-
- obj := c.pkg.GetTypesInfo().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(ctx, node.Recv)
- c.addFieldItems(ctx, node.Type.Params)
- c.addFieldItems(ctx, node.Type.Results)
-
- // collect receiver struct fields
- if node.Recv != nil {
- for _, fields := range node.Recv.List {
- for _, name := range fields.Names {
- obj := c.pkg.GetTypesInfo().ObjectOf(name)
- if obj == nil {
- continue
- }
-
- recvType := obj.Type().Underlying()
- if ptr, ok := recvType.(*types.Pointer); ok {
- recvType = ptr.Elem()
- }
- recvStruct, ok := recvType.Underlying().(*types.Struct)
- if !ok {
- continue
- }
- 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.GetTypesInfo().ObjectOf(node.Name)
- if obj == nil || obj.Pkg() != nil && obj.Pkg() != c.pkg.GetTypes() {
- 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,
- MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper,
- token.Pos(int(cursorComment.Slash)+start), token.Pos(int(cursorComment.Slash)+end)),
- }
- 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(ctx context.Context, 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.GetTypesInfo().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 clInfo.isStruct() && (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.GetTypesInfo(), sel.X)
-
- // Is sel a qualified identifier?
- if id, ok := sel.X.(*ast.Ident); ok {
- if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok {
- var pkg source.Package
- for _, imp := range c.pkg.Imports() {
- if imp.PkgPath() == pkgName.Imported().Path() {
- pkg = imp
- }
- }
- // If the package is not imported, try searching for unimported
- // completions.
- if pkg == nil && c.opts.unimported {
- if err := c.unimportedMembers(ctx, id); err != nil {
- return err
- }
- }
- c.packageMembers(pkgName.Imported(), stdScore, nil, func(cand candidate) {
- c.deepState.enqueue(cand)
- })
- return nil
- }
- }
-
- // Invariant: sel is a true selector.
- tv, ok := c.pkg.GetTypesInfo().Types[sel.X]
- if ok {
- c.methodsAndFields(tv.Type, tv.Addressable(), nil, func(cand candidate) {
- c.deepState.enqueue(cand)
- })
-
- c.addPostfixSnippetCandidates(ctx, sel)
-
- return nil
- }
-
- // Try unimported packages.
- if id, ok := sel.X.(*ast.Ident); ok && c.opts.unimported {
- if err := c.unimportedMembers(ctx, id); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (c *completer) unimportedMembers(ctx context.Context, id *ast.Ident) error {
- // Try loaded packages first. They're relevant, fast, and fully typed.
- known, err := c.snapshot.CachedImportPaths(ctx)
- if err != nil {
- return err
- }
-
- var paths []string
- for path, pkg := range known {
- if pkg.GetTypes().Name() != id.Name {
- continue
- }
- paths = append(paths, path)
- }
-
- var relevances map[string]float64
- if len(paths) != 0 {
- if err := c.snapshot.RunProcessEnvFunc(ctx, func(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]]
- })
-
- for _, path := range paths {
- pkg := known[path]
- if pkg.GetTypes().Name() != id.Name {
- continue
- }
- imp := &importInfo{
- importPath: path,
- pkg: pkg,
- }
- if imports.ImportPathToAssumedName(path) != pkg.GetTypes().Name() {
- imp.name = pkg.GetTypes().Name()
- }
- c.packageMembers(pkg.GetTypes(), unimportedScore(relevances[path]), imp, func(cand candidate) {
- c.deepState.enqueue(cand)
- })
- if len(c.items) >= unimportedMemberTarget {
- return nil
- }
- }
-
- ctx, cancel := context.WithCancel(ctx)
-
- var mu sync.Mutex
- add := func(pkgExport imports.PackageExport) {
- mu.Lock()
- defer mu.Unlock()
- if _, ok := known[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 _, export := range pkgExport.Exports {
- score := unimportedScore(pkgExport.Fix.Relevance)
- c.deepState.enqueue(candidate{
- obj: types.NewVar(0, pkg, export, 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(opts *imports.Options) error {
- defer cancel()
- return imports.GetPackageExports(ctx, add, id.Name, c.filename, c.pkg.GetTypes().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)
- cb(candidate{
- obj: obj,
- score: score,
- imp: imp,
- addressable: isVar(obj),
- })
- }
-}
-
-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 typ.String() == "*testing.F" && addressable {
- // is that a sufficient test? (or is more care needed?)
- if c.fuzz(typ, mset, imp, cb, c.snapshot.FileSet()) {
- return
- }
- }
-
- for i := 0; i < mset.Len(); i++ {
- 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) {
- cb(candidate{
- obj: v,
- score: stdScore - 0.01,
- imp: imp,
- addressable: addressable || isPointer(typ),
- })
- })
-}
-
-// lexical finds completions in the lexical environment.
-func (c *completer) lexical(ctx context.Context) error {
- scopes := source.CollectScopes(c.pkg.GetTypesInfo(), c.path, c.pos)
- scopes = append(scopes, c.pkg.GetTypes().Scope(), types.Universe)
-
- var (
- builtinIota = types.Universe.Lookup("iota")
- builtinNil = types.Universe.Lookup("nil")
- // comparable is an interface that exists on the dev.typeparams Go branch.
- // Filter it out from completion results to stabilize tests.
- // TODO(rFindley) update (or remove) our handling for comparable once the
- // type parameter API has stabilized.
- builtinAny = types.Universe.Lookup("any")
- 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 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 == builtinComparable || obj == builtinAny {
- continue
- }
-
- // 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.snapshot.FileSet(), obj, node, c.pkg.GetTypesInfo()); 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, _ := source.Deref(c.inference.objType).(*types.Named); named != nil {
- // 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.
- if _, ok := seen[pkg.Name()]; !ok && pkg != c.pkg.GetTypes() && !alreadyImports(c.file, 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, _ := c.inference.objType.(*types.Interface); t != nil {
- terms, _ := typeparams.InterfaceTermSet(t)
- 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
-}
-
-// injectInferredType manufacters candidates based on the given type.
-// 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 = source.Deref(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.
- if _, named := t.(*types.Named); !named {
- // 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
-
- known, err := c.snapshot.CachedImportPaths(ctx)
- if err != nil {
- return err
- }
- var paths []string
- for path, pkg := range known {
- if !strings.HasPrefix(pkg.GetTypes().Name(), prefix) {
- continue
- }
- paths = append(paths, path)
- }
-
- var relevances map[string]float64
- if len(paths) != 0 {
- if err := c.snapshot.RunProcessEnvFunc(ctx, func(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 {
- pkg := known[path]
- if _, ok := seen[pkg.GetTypes().Name()]; ok {
- continue
- }
- imp := &importInfo{
- importPath: path,
- pkg: pkg,
- }
- if imports.ImportPathToAssumedName(path) != pkg.GetTypes().Name() {
- imp.name = pkg.GetTypes().Name()
- }
- if count >= maxUnimportedPackageNames {
- return nil
- }
- c.deepState.enqueue(candidate{
- // Pass an empty *types.Package to disable deep completions.
- obj: types.NewPkgName(0, nil, pkg.GetTypes().Name(), types.NewPackage(path, pkg.Name())),
- score: unimportedScore(relevances[path]),
- imp: imp,
- })
- count++
- }
-
- ctx, cancel := context.WithCancel(ctx)
-
- var mu sync.Mutex
- add := func(pkg imports.ImportFix) {
- mu.Lock()
- defer mu.Unlock()
- if _, ok := seen[pkg.IdentName]; ok {
- return
- }
- if _, ok := relevances[pkg.StmtInfo.ImportPath]; ok {
- return
- }
-
- if count >= maxUnimportedPackageNames {
- cancel()
- 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(opts *imports.Options) error {
- defer cancel()
- return imports.GetAllCandidates(ctx, add, prefix, c.filename, c.pkg.GetTypes().Name(), opts.Env)
- })
- return nil
-}
-
-// alreadyImports reports whether f has an import with the specified path.
-func alreadyImports(f *ast.File, path string) bool {
- for _, s := range f.Imports {
- if source.ImportPath(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.GetTypesInfo().Uses[key]; ok {
- if usedVar, ok := used.(*types.Var); ok {
- addedFields[usedVar] = true
- }
- }
- }
- }
- }
-
- deltaScore := 0.0001
- switch t := clInfo.clType.(type) {
- case *types.Struct:
- 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,
- })
- }
- }
-
- // Add lexical completions if we aren't certain we are in the key part of a
- // key-value pair.
- if clInfo.maybeInFieldName {
- return c.lexical(ctx)
- }
- default:
- return c.lexical(ctx)
- }
-
- return nil
-}
-
-func (cl *compLitInfo) isStruct() bool {
- _, ok := cl.clType.(*types.Struct)
- return ok
-}
-
-// 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: source.Deref(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
- kindFloat
- kindComplex
- kindError
- kindStringer
- kindFunc
-)
-
-// 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, <invalid>].
- 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.GetTypesInfo().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.GetTypesInfo().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.GetTypesInfo().TypeOf(lhs))
- }
- } else {
- // Otherwse, 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.GetTypesInfo().TypeOf(node.Lhs[i]))
- }
- }
- return inf
- case *ast.ValueSpec:
- if node.Type != nil && c.pos > node.Type.End() {
- inf.objType = c.pkg.GetTypesInfo().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.GetTypesInfo()); typ != nil {
- inf.convertibleTo = typ
- break Nodes
- }
-
- if tv, ok := c.pkg.GetTypesInfo().Types[node.Fun]; ok {
- if sig, ok := tv.Type.(*types.Signature); ok {
- 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.GetTypesInfo(), node, exprIdx)
- }
- }
- }
-
- if funIdent, ok := node.Fun.(*ast.Ident); ok {
- obj := c.pkg.GetTypesInfo().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.GetTypesInfo().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.GetTypesInfo(), 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.GetTypesInfo().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 *typeparams.IndexListExpr:
- if node.Lbrack < c.pos && c.pos <= node.Rbrack {
- if tv, ok := c.pkg.GetTypesInfo().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.GetTypesInfo().Types[node.Chan]; ok {
- if ch, ok := tv.Type.Underlying().(*types.Chan); ok {
- inf.objType = ch.Elem()
- }
- }
- }
- return inf
- case *ast.RangeStmt:
- if source.NodeContains(node.X, c.pos) {
- inf.objKind |= kindSlice | kindArray | kindMap | kindString
- if node.Value == nil {
- inf.objKind |= kindChan
- }
- }
- 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 expectedConstraint(t types.Type, idx int) types.Type {
- var tp *typeparams.TypeParamList
- if named, _ := t.(*types.Named); named != nil {
- tp = typeparams.ForNamed(named)
- } else if sig, _ := t.Underlying().(*types.Signature); sig != nil {
- tp = typeparams.ForSignature(sig)
- }
- 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 && types.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 !source.NodeContains(n.Type, pos)
- case *ast.CallExpr:
- // Doesn't break inference if pos is in func name.
- // For example: "Foo<>(123)"
- return !source.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.GetTypesInfo().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.GetTypesInfo().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.GetTypesInfo().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.GetTypesInfo().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 = source.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 = source.NodeContains(n.Type, c.pos)
- break Nodes
- case *ast.TypeSpec:
- inf.wantTypeName = source.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.GetTypes(), "", 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 && types.ConvertibleTo(candType, ci.convertibleTo) {
- return true
- }
-
- for _, expType := range expTypes {
- if isEmptyInterface(expType) {
- 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.(*typeparams.TypeParam); ok {
- return false
- }
-
- if !types.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 types.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 wouuld 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 {
- 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 types.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 !source.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.NewSignature(
- nil,
- nil,
- types.NewTuple(types.NewParam(token.NoPos, nil, "", types.Typ[types.String])),
- false,
- )),
- }, nil).Complete()
-
- byteType = types.Universe.Lookup("byte").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.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:
- return kindFunc
- }
-
- if types.Implements(candType, errorIntf) {
- kind |= kindError
- }
-
- if types.Implements(candType, stringerIntf) {
- kind |= kindStringer
- }
-
- return kind
-}
diff --git a/internal/lsp/source/completion/deep_completion.go b/internal/lsp/source/completion/deep_completion.go
deleted file mode 100644
index a13d807d4..000000000
--- a/internal/lsp/source/completion/deep_completion.go
+++ /dev/null
@@ -1,362 +0,0 @@
-// 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 wether 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)
-}
-
-// dequeue removes and returns the leftmost element from the search queue.
-func (s *deepCompletionState) dequeue() *candidate {
- var cand *candidate
- cand, s.thisQueue = &s.thisQueue[len(s.thisQueue)-1], s.thisQueue[:len(s.thisQueue)-1]
- return 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) {
- 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]
- }()
-
- for len(c.deepState.nextQueue) > 0 {
- 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.GetTypes() && !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 {
- spent := float64(time.Since(c.startTime)) / float64(c.opts.budget)
- select {
- case <-ctx.Done():
- return
- default:
- // 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<<uint16(i)) > 0 {
- totalLen += 2
- }
- }
-
- var buf strings.Builder
- buf.Grow(totalLen)
-
- for i, obj := range cand.path {
- buf.WriteString(obj.Name())
- if cand.pathInvokeMask&(1<<uint16(i)) > 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/internal/lsp/source/completion/deep_completion_test.go b/internal/lsp/source/completion/deep_completion_test.go
deleted file mode 100644
index 27009af1b..000000000
--- a/internal/lsp/source/completion/deep_completion_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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/internal/lsp/source/completion/definition.go b/internal/lsp/source/completion/definition.go
deleted file mode 100644
index 17b251cb0..000000000
--- a/internal/lsp/source/completion/definition.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// 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/token"
- "go/types"
- "strings"
- "unicode"
- "unicode/utf8"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/snippet"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-// some definitions 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, fset *token.FileSet, mapper *protocol.ColumnMapper, fh source.FileHandle) ([]CompletionItem, *Selection) {
- if _, ok := obj.(*types.Func); !ok {
- return nil, nil // not a function at all
- }
- if !strings.HasSuffix(fh.URI().Filename(), "_test.go") {
- return nil, nil
- }
-
- name := path[0].(*ast.Ident).Name
- if len(name) == 0 {
- // can't happen
- return nil, nil
- }
- pos := path[0].Pos()
- sel := &Selection{
- content: "",
- cursor: pos,
- MappedRange: source.NewMappedRange(fset, mapper, pos, pos),
- }
- var ans []CompletionItem
-
- // Always suggest TestMain, if possible
- if strings.HasPrefix("TestMain", name) {
- ans = []CompletionItem{defItem("TestMain(m *testing.M)", obj)}
- }
-
- // If a snippet is possible, suggest it
- if strings.HasPrefix("Test", name) {
- ans = append(ans, defSnippet("Test", "Xxx", "(t *testing.T)", obj))
- return ans, sel
- } else if strings.HasPrefix("Benchmark", name) {
- ans = append(ans, defSnippet("Benchmark", "Xxx", "(b *testing.B)", obj))
- return ans, sel
- } else if strings.HasPrefix("Fuzz", name) {
- ans = append(ans, defSnippet("Fuzz", "Xxx", "(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
-}
-
-func defMatches(name, pat string, path []ast.Node, arg string) string {
- idx := strings.Index(name, pat)
- if idx < 0 {
- 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 fp != nil && len(fp.List) > 0 {
- // signature already there, minimal suggestion
- return name
- }
- // suggesting signature too
- return name + arg
-}
-
-func defSnippet(prefix, placeholder, suffix string, obj types.Object) CompletionItem {
- var sn snippet.Builder
- sn.WriteText(prefix)
- if placeholder != "" {
- sn.WritePlaceholder(func(b *snippet.Builder) { b.WriteText(placeholder) })
- }
- sn.WriteText(suffix + " {\n")
- sn.WriteFinalTabstop()
- sn.WriteText("\n}")
- return CompletionItem{
- Label: prefix + placeholder + suffix,
- Detail: "tab, type the rest of the name, then tab",
- Kind: protocol.FunctionCompletion,
- Depth: 0,
- Score: 10,
- snippet: &sn,
- Documentation: prefix + " test function",
- obj: 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 parameter",
- obj: obj,
- }
-}
diff --git a/internal/lsp/source/completion/format.go b/internal/lsp/source/completion/format.go
deleted file mode 100644
index e67456911..000000000
--- a/internal/lsp/source/completion/format.go
+++ /dev/null
@@ -1,340 +0,0 @@
-// 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/ast"
- "go/doc"
- "go/types"
- "strings"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/snippet"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/typeparams"
- errors "golang.org/x/xerrors"
-)
-
-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 wont 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 := x.Type().(*types.Named); ok {
- tp := typeparams.ForNamed(named)
- label += source.FormatTypeParams(tp)
- insert = label // maintain invariant above (label == insert)
- }
- }
-
- snip.WriteText(insert)
-
- switch obj := obj.(type) {
- case *types.TypeName:
- detail, kind = source.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 structs
- } else if obj.IsField() {
- detail = source.FormatVarType(ctx, c.snapshot, c.pkg, obj, c.qf)
- }
- if obj.IsField() {
- kind = protocol.FieldCompletion
- c.structFieldSnippet(cand, detail, &snip)
- } else {
- kind = protocol.VariableCompletion
- }
- if obj.Type() == nil {
- break
- }
- case *types.Func:
- sig, ok := obj.Type().Underlying().(*types.Signature)
- if !ok {
- break
- }
- kind = protocol.FunctionCompletion
- if sig != nil && sig.Recv() != nil {
- 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 := source.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf)
- c.functionCallSnippet("", s.TypeParams(), 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 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 + ")"
- }
-
- 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,
- obj: obj,
- }
- // If the user doesn't want documentation for completion items.
- if !c.opts.documentation {
- return item, nil
- }
- pos := c.snapshot.FileSet().Position(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
- }
- uri := span.URIFromPath(pos.Filename)
-
- // Find the source file of the candidate.
- pkg, err := source.FindPackageFromPos(ctx, c.snapshot, obj.Pos())
- if err != nil {
- return item, nil
- }
-
- decl, err := c.snapshot.PosToDecl(ctx, pkg, obj.Pos())
- if err != nil {
- return CompletionItem{}, err
- }
- hover, err := source.FindHoverContext(ctx, c.snapshot, pkg, obj, decl, nil)
- if err != nil {
- event.Error(ctx, "failed to find Hover", err, tag.URI.Of(uri))
- return item, nil
- }
- if c.opts.fullDocumentation {
- item.Documentation = hover.Comment.Text()
- } else {
- item.Documentation = doc.Synopsis(hover.Comment.Text())
- }
- // The desired pattern is `^// Deprecated`, but the prefix has been removed
- if strings.HasPrefix(hover.Comment.Text(), "Deprecated") {
- if c.snapshot.View().Options().CompletionTags {
- item.Tags = []protocol.CompletionItemTag{protocol.ComplDeprecated}
- } else if c.snapshot.View().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(span.URIFromPath(c.filename))
- if err != nil {
- return nil, err
- }
-
- return source.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 := source.NewBuiltinSignature(ctx, c.snapshot, obj.Name())
- if err != nil {
- return CompletionItem{}, err
- }
- item.Detail = "func" + sig.Format()
- item.snippet = &snippet.Builder{}
- 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
-}
diff --git a/internal/lsp/source/completion/fuzz.go b/internal/lsp/source/completion/fuzz.go
deleted file mode 100644
index 92349ab93..000000000
--- a/internal/lsp/source/completion/fuzz.go
+++ /dev/null
@@ -1,142 +0,0 @@
-// 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/token"
- "go/types"
- "strings"
-
- "golang.org/x/tools/internal/lsp/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(typ types.Type, mset *types.MethodSet, imp *importInfo, cb func(candidate), fset *token.FileSet) 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.GetTypesInfo().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.GetTypesInfo().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",
- obj: nil,
- }
- 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/internal/lsp/source/completion/keywords.go b/internal/lsp/source/completion/keywords.go
deleted file mode 100644
index bbf59b022..000000000
--- a/internal/lsp/source/completion/keywords.go
+++ /dev/null
@@ -1,154 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-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 || isASTFile(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 && source.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/internal/lsp/source/completion/labels.go b/internal/lsp/source/completion/labels.go
deleted file mode 100644
index e4fd961e3..000000000
--- a/internal/lsp/source/completion/labels.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// 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.GetTypesInfo().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/internal/lsp/source/completion/literal.go b/internal/lsp/source/completion/literal.go
deleted file mode 100644
index 5025f1f74..000000000
--- a/internal/lsp/source/completion/literal.go
+++ /dev/null
@@ -1,440 +0,0 @@
-// 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/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/snippet"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-// 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.literal {
- 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{}".
- if _, named := literalType.(*types.Named); named && expType != nil {
- if _, named := source.Deref(expType).(*types.Named); !named {
- 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 "" }
- }
-
- typeName := types.TypeString(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
- }
- }
-
- switch t := literalType.Underlying().(type) {
- case *types.Struct, *types.Array, *types.Slice, *types.Map:
- c.compositeLiteral(t, 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 source.IsInterface(expType) {
- c.basicLiteral(t, 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 source.IsInterface(expType) || types.Identical(expType, literalType) {
- c.basicLiteral(t, 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(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(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 && !source.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)
- )
- for i := 0; i < sig.Params().Len(); i++ {
- var (
- p = sig.Params().At(i)
- name = p.Name()
- )
- 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".
- name = source.FormatVarType(ctx, c.snapshot, c.pkg, p, func(p *types.Package) string {
- return ""
- })
- name = abbreviateTypeName(name)
- }
- 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 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 := source.FormatVarType(ctx, c.snapshot, c.pkg, p, c.qf)
- if sig.Variadic() && i == sig.Params().Len()-1 {
- typeStr = strings.Replace(typeStr, "[]", "...", 1)
- }
- 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() != ""
-
- if resultsNeedParens {
- snip.WriteText("(")
- }
- for i := 0; i < results.Len(); i++ {
- if i > 0 {
- snip.WriteText(", ")
- }
- r := results.At(i)
- if name := r.Name(); name != "" {
- snip.WriteText(name + " ")
- }
- snip.WriteText(source.FormatVarType(ctx, c.snapshot, c.pkg, r, c.qf))
- }
- 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.
-func (c *completer) compositeLiteral(T types.Type, typeName string, matchScore float64, edits []protocol.TextEdit) {
- snip := &snippet.Builder{}
- snip.WriteText(typeName + "{")
- // 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.GetTypes()) {
- 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, typeName string, matchScore float64, edits []protocol.TextEdit) {
- // Never give type conversions like "untyped int()".
- if isUntyped(T) {
- return
- }
-
- snip := &snippet.Builder{}
- snip.WriteText(typeName + "(")
- 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(typeName string, secondArg string, matchScore float64, edits []protocol.TextEdit) {
- // Keep it simple and don't add any placeholders for optional "make()" arguments.
-
- snip := &snippet.Builder{}
- snip.WriteText("make(" + typeName)
- 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,
- })
-}
diff --git a/internal/lsp/source/completion/package.go b/internal/lsp/source/completion/package.go
deleted file mode 100644
index c7e52d718..000000000
--- a/internal/lsp/source/completion/package.go
+++ /dev/null
@@ -1,364 +0,0 @@
-// 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"
- "fmt"
- "go/ast"
- "go/parser"
- "go/scanner"
- "go/token"
- "go/types"
- "path/filepath"
- "strings"
- "unicode"
-
- "golang.org/x/tools/internal/lsp/debug"
- "golang.org/x/tools/internal/lsp/fuzzy"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// packageClauseCompletions offers completions for a package declaration when
-// one is not present in the given file.
-func packageClauseCompletions(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, pos 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.
- pgf, err := snapshot.ParseGo(ctx, fh, source.ParseFull)
- if err != nil {
- return nil, nil, err
- }
-
- cursorSpan, err := pgf.Mapper.PointSpan(pos)
- if err != nil {
- return nil, nil, err
- }
- rng, err := cursorSpan.Range(pgf.Mapper.Converter)
- if err != nil {
- return nil, nil, err
- }
-
- surrounding, err := packageCompletionSurrounding(ctx, snapshot.FileSet(), pgf, rng.Start)
- if err != nil {
- return nil, nil, errors.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 position. A valid location
-// for package completion is above any declarations or import statements.
-func packageCompletionSurrounding(ctx context.Context, fset *token.FileSet, pgf *source.ParsedGoFile, pos token.Pos) (*Selection, error) {
- // 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.
- filename := pgf.URI.Filename()
- expr, _ := parser.ParseExprFrom(fset, filename, pgf.Src, parser.Mode(0))
- if expr == nil {
- return nil, fmt.Errorf("unparseable file (%s)", pgf.URI)
- }
- tok := fset.File(expr.Pos())
- offset, err := source.Offset(pgf.Tok, pos)
- if err != nil {
- return nil, err
- }
- if offset > tok.Size() {
- debug.Bug(ctx, "out of bounds cursor", "cursor offset (%d) out of bounds for %s (size: %d)", offset, pgf.URI, tok.Size())
- return nil, fmt.Errorf("cursor out of bounds")
- }
- cursor := tok.Pos(offset)
- m := &protocol.ColumnMapper{
- URI: pgf.URI,
- Content: pgf.Src,
- Converter: span.NewContentConverter(filename, pgf.Src),
- }
-
- // 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,
- MappedRange: source.NewMappedRange(fset, m, name.Pos(), name.End()),
- }, 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. This logic would allow "package" to
- // appear on any line of the file as long as it's the first code expression
- // in the file.
- lines := strings.Split(string(pgf.Src), "\n")
- cursorLine := tok.Line(cursor)
- if cursorLine <= 0 || cursorLine > len(lines) {
- return nil, fmt.Errorf("invalid line number")
- }
- if fset.Position(expr.Pos()).Line == cursorLine {
- words := strings.Fields(lines[cursorLine-1])
- if len(words) > 0 && words[0] == PACKAGE {
- content := PACKAGE
- // Account for spaces if there are any.
- if len(words) > 1 {
- content += " "
- }
-
- start := expr.Pos()
- end := token.Pos(int(expr.Pos()) + len(content) + 1)
- // We have verified that we have a valid 'package' keyword as our
- // first expression. Ensure that cursor is in this keyword or
- // otherwise fallback to the general case.
- if cursor >= start && cursor <= end {
- return &Selection{
- content: content,
- cursor: cursor,
- MappedRange: source.NewMappedRange(fset, m, start, end),
- }, 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(fset, cursor, pgf.Src) {
- return nil, fmt.Errorf("cursor in comment")
- }
-
- // The surrounding range in this case is the cursor except for empty file,
- // in which case it's end of file - 1
- start, end := cursor, cursor
- if tok.Size() == 0 {
- start, end = tok.Pos(0)-1, tok.Pos(0)-1
- }
-
- return &Selection{
- content: "",
- cursor: cursor,
- MappedRange: source.NewMappedRange(fset, m, start, end),
- }, nil
-}
-
-func cursorInComment(fset *token.FileSet, cursor token.Pos, src []byte) bool {
- var s scanner.Scanner
- s.Init(fset.File(cursor), 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 span.URI, 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 (<pkg>_test) and
-// the directory name itself.
-func packageSuggestions(ctx context.Context, snapshot source.Snapshot, fileURI span.URI, prefix string) (packages []candidate, err error) {
- workspacePackages, err := snapshot.ActivePackages(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.Filename())
- dirName := filepath.Base(dirPath)
- if !isValidDirName(dirName) {
- return packages, nil
- }
- pkgName := convertDirNameToPkgName(dirName)
-
- seenPkgs := make(map[string]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 _, pkg := range workspacePackages {
- if pkg.Name() == "main" || pkg.Name() == "" {
- continue
- }
- if _, ok := seenPkgs[pkg.Name()]; ok {
- continue
- }
-
- // Only add packages that are previously used in the current directory.
- var relevantPkg bool
- for _, pgf := range pkg.CompiledGoFiles() {
- if filepath.Dir(pgf.URI.Filename()) == 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(pkg.Name())); score > 0 {
- packages = append(packages, toCandidate(pkg.Name(), score*highScore))
- }
- seenPkgs[pkg.Name()] = struct{}{}
-
- testPkgName := pkg.Name() + "_test"
- if _, ok := seenPkgs[testPkgName]; ok || strings.HasSuffix(pkg.Name(), "_test") {
- continue
- }
- if score := float64(matcher.Score(testPkgName)); score > 0 {
- packages = append(packages, toCandidate(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(pkgName)); score > 0 {
- packages = append(packages, toCandidate(pkgName, score*lowScore))
- }
-
- testPkgName := pkgName + "_test"
- if score := float64(matcher.Score(testPkgName)); score > 0 {
- packages = append(packages, toCandidate(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) string {
- 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 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/internal/lsp/source/completion/package_test.go b/internal/lsp/source/completion/package_test.go
deleted file mode 100644
index 6436984fd..000000000
--- a/internal/lsp/source/completion/package_test.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// 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"
-
-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 string
- }{
- {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/internal/lsp/source/completion/postfix_snippets.go b/internal/lsp/source/completion/postfix_snippets.go
deleted file mode 100644
index 7ea962118..000000000
--- a/internal/lsp/source/completion/postfix_snippets.go
+++ /dev/null
@@ -1,461 +0,0 @@
-// 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/internal/event"
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/snippet"
- "golang.org/x/tools/internal/lsp/source"
- errors "golang.org/x/xerrors"
-)
-
-// 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
-
- 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
-}
-
-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 -}}
-{{$i := .VarName nil "i"}}{{$j := .VarName nil "j" -}}
-for {{$i}}, {{$j}} := 0, len({{.X}})-1; {{$i}} < {{$j}}; {{$i}}, {{$j}} = {{$i}}+1, {{$j}}-1 {
- {{.X}}[{{$i}}], {{.X}}[{{$j}}] = {{.X}}[{{$j}}], {{.X}}[{{$i}}]
-}
-{{end}}`,
-}, {
- label: "range",
- details: "range over slice",
- body: `{{if and (eq .Kind "slice") .StmtOK -}}
-for {{.VarName nil "i"}}, {{.VarName .ElemType "v"}} := 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"}}, {{.VarName .ElemType "v"}} := 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: "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}}{{end}} := {{.X}}
-{{- end}}`,
-}, {
- label: "var",
- details: "assign to variable",
- body: `{{if and (ne .Kind "tuple") .StmtOK -}}
-{{.VarName .Type ""}} := {{.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}}`,
-}}
-
-// Cursor indicates where the client's cursor should end up after the
-// snippet is done.
-func (a *postfixTmplArgs) Cursor() string {
- a.snip.WriteFinalTabstop()
- return ""
-}
-
-// 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 "", errors.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 {
- if e, _ := a.Type.(interface{ Elem() types.Type }); e != nil {
- 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 X is a call expression.
-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
-}
-
-// 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
-}
-
-// 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
- if types.Implements(t, errorIntf) {
- name = "err"
- } else if _, isNamed := source.Deref(t).(*types.Named); !isNamed {
- 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.GetTypesInfo().TypeOf(sel.X)
- if selType == nil {
- return
- }
-
- // Skip empty tuples since there is no value to operate on.
- if tuple, ok := selType.Underlying().(*types.Tuple); ok && tuple == nil {
- return
- }
-
- tokFile := c.snapshot.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 = tokFile.Line(c.pos) < tokFile.Line(p.TokPos)
- }
- break
- }
- }
-
- scope := c.pkg.GetTypes().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 := tokFile.Line(sel.Pos()); startLine != tokFile.Line(afterDot) {
- if tokFile.Line(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: source.FormatNode(c.snapshot.FileSet(), sel.X),
- StmtOK: stmtOK,
- Obj: exprObj(c.pkg.GetTypesInfo(), sel.X),
- Type: selType,
- qf: c.qf,
- importIfNeeded: c.importIfNeeded,
- scope: scope,
- varNames: make(map[string]bool),
- }
-
- // 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").Parse(rule.body)
- if err != nil {
- log.Panicf("error parsing postfix snippet template: %v", err)
- }
- postfixTmpls[idx] = rule
- idx++
- }
- postfixTmpls = postfixTmpls[:idx]
- })
-}
-
-// 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 {
- if source.ImportPath(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/internal/lsp/source/completion/printf.go b/internal/lsp/source/completion/printf.go
deleted file mode 100644
index ce74af53b..000000000
--- a/internal/lsp/source/completion/printf.go
+++ /dev/null
@@ -1,172 +0,0 @@
-// 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().(*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':
- addVerb(kindPtr | kindSlice)
- case 'w':
- addVerb(kindError)
- case '+', '-', '#', ' ', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
- // Flag or numeric width/precicision 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/internal/lsp/source/completion/printf_test.go b/internal/lsp/source/completion/printf_test.go
deleted file mode 100644
index 19d295b8d..000000000
--- a/internal/lsp/source/completion/printf_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// 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},
- {"%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/internal/lsp/source/completion/snippet.go b/internal/lsp/source/completion/snippet.go
deleted file mode 100644
index 72c351f94..000000000
--- a/internal/lsp/source/completion/snippet.go
+++ /dev/null
@@ -1,115 +0,0 @@
-// 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/internal/lsp/snippet"
-)
-
-// structFieldSnippets 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.snapshot.FileSet()
-
- // If the cursor position is on a different line from the literal's opening brace,
- // we are in a multiline literal.
- if fset.Position(c.pos).Line != fset.Position(clInfo.cl.Lbrace).Line {
- snip.WriteText(",")
- }
-}
-
-// functionCallSnippets calculates the snippet for function calls.
-func (c *completer) functionCallSnippet(name string, tparams, params []string, snip *snippet.Builder) {
- // 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/internal/lsp/source/completion/statements.go b/internal/lsp/source/completion/statements.go
deleted file mode 100644
index 3280bb52c..000000000
--- a/internal/lsp/source/completion/statements.go
+++ /dev/null
@@ -1,360 +0,0 @@
-// 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"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/snippet"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-// addStatementCandidates adds full statement completion candidates
-// appropriate for the current context.
-func (c *completer) addStatementCandidates() {
- c.addErrCheck()
- c.addAssignAppend()
-}
-
-// addAssignAppend offers a completion candidate of the form:
-//
-// someSlice = append(someSlice, )
-//
-// It will offer the "append" completion in 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, )".
-//
-// Or
-//
-// 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.snapshot.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.GetTypesInfo().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 = source.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 = source.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.obj == nil || bestItem.obj.Type() == nil {
- return
- }
-
- if _, isSlice := bestItem.obj.Type().Underlying().(*types.Slice); !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.GetTypesInfo().TypeOf(lastAssignee), errorType) {
- return
- }
-
- var (
- // errVar is e.g. "err" in "foo, err := bar()".
- errVar = source.FormatNode(c.snapshot.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 <zero value>, ..., ${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,
- // There doesn't seem to be a more appropriate kind.
- Kind: protocol.KeywordCompletion,
- 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 source.Package) string {
- if enclosingFunc == nil || enclosingFunc.sig == nil {
- return ""
- }
-
- sig := enclosingFunc.sig
- for i := 0; i < sig.Params().Len(); i++ {
- param := sig.Params().At(i)
- if param.Name() == "_" {
- continue
- }
- testingPkg, err := pkg.GetImport("testing")
- if err != nil {
- continue
- }
- tbObj := testingPkg.GetTypes().Scope().Lookup("TB")
- if tbObj == nil {
- continue
- }
- iface, ok := tbObj.Type().Underlying().(*types.Interface)
- if !ok {
- continue
- }
- if !types.Implements(param.Type(), iface) {
- continue
- }
- return param.Name()
- }
-
- return ""
-}
diff --git a/internal/lsp/source/completion/util.go b/internal/lsp/source/completion/util.go
deleted file mode 100644
index 505c7e256..000000000
--- a/internal/lsp/source/completion/util.go
+++ /dev/null
@@ -1,326 +0,0 @@
-// 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/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-// 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 map[*types.Struct]bool
-
- var visit func(T types.Type)
- visit = func(T types.Type) {
- if T, ok := source.Deref(T).Underlying().(*types.Struct); ok {
- if seen[T] {
- return
- }
-
- for i := 0; i < T.NumFields(); i++ {
- f := T.Field(i)
- fn(f)
- if f.Anonymous() {
- if seen == nil {
- // Lazily create "seen" since it is only needed for
- // embedded structs.
- seen = make(map[*types.Struct]bool)
- }
- seen[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 := 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 := source.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)
-}
-
-func isPointer(T types.Type) bool {
- _, ok := T.(*types.Pointer)
- return ok
-}
-
-func isVar(obj types.Object) bool {
- _, ok := obj.(*types.Var)
- return ok
-}
-
-func isTypeName(obj types.Object) bool {
- _, ok := obj.(*types.TypeName)
- return ok
-}
-
-func isFunc(obj types.Object) bool {
- _, ok := obj.(*types.Func)
- return ok
-}
-
-func isEmptyInterface(T types.Type) bool {
- intf, _ := T.(*types.Interface)
- return intf != nil && intf.NumMethods() == 0
-}
-
-func isUntyped(T types.Type) bool {
- if basic, ok := T.(*types.Basic); ok {
- return basic.Info()&types.IsUntyped > 0
- }
- return false
-}
-
-func isPkgName(obj types.Object) bool {
- _, ok := obj.(*types.PkgName)
- return ok
-}
-
-func isASTFile(n ast.Node) bool {
- _, ok := n.(*ast.File)
- return ok
-}
-
-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
- }
-
- 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) {
- rng := source.NewMappedRange(c.snapshot.FileSet(), c.mapper, from, to)
- spn, err := rng.Span()
- if err != nil {
- return nil, err
- }
- return source.ToProtocolEdits(c.mapper, []diff.TextEdit{{
- Span: spn,
- NewText: newText,
- }})
-}
diff --git a/internal/lsp/source/completion/util_test.go b/internal/lsp/source/completion/util_test.go
deleted file mode 100644
index c94d279fb..000000000
--- a/internal/lsp/source/completion/util_test.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
deleted file mode 100644
index e393c2f94..000000000
--- a/internal/lsp/source/diagnostics.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// 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 source
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
-)
-
-type SuggestedFix struct {
- Title string
- Edits map[span.URI][]protocol.TextEdit
- Command *protocol.Command
- ActionKind protocol.CodeActionKind
-}
-
-type RelatedInformation struct {
- URI span.URI
- Range protocol.Range
- Message string
-}
-
-func Analyze(ctx context.Context, snapshot Snapshot, pkg Package, includeConvenience bool) (map[span.URI][]*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()
- }
-
- categories := []map[string]*Analyzer{}
- if includeConvenience {
- categories = append(categories, snapshot.View().Options().ConvenienceAnalyzers)
- }
- // If we had type errors, don't run any other analyzers.
- if !pkg.HasTypeErrors() {
- categories = append(categories, snapshot.View().Options().DefaultAnalyzers, snapshot.View().Options().StaticcheckAnalyzers)
- }
- var analyzers []*Analyzer
- for _, cat := range categories {
- for _, a := range cat {
- analyzers = append(analyzers, a)
- }
- }
-
- analysisDiagnostics, err := snapshot.Analyze(ctx, pkg.ID(), analyzers)
- if err != nil {
- return nil, err
- }
-
- reports := map[span.URI][]*Diagnostic{}
- // Report diagnostics and errors from root analyzers.
- for _, diag := range analysisDiagnostics {
- reports[diag.URI] = append(reports[diag.URI], diag)
- }
- return reports, nil
-}
-
-func FileDiagnostics(ctx context.Context, snapshot Snapshot, uri span.URI) (VersionedFileIdentity, []*Diagnostic, error) {
- fh, err := snapshot.GetVersionedFile(ctx, uri)
- if err != nil {
- return VersionedFileIdentity{}, nil, err
- }
- pkg, _, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
- if err != nil {
- return VersionedFileIdentity{}, nil, err
- }
- diagnostics, err := snapshot.DiagnosePackage(ctx, pkg)
- if err != nil {
- return VersionedFileIdentity{}, nil, err
- }
- fileDiags := diagnostics[fh.URI()]
- if !pkg.HasListOrParseErrors() {
- analysisDiags, err := Analyze(ctx, snapshot, pkg, false)
- if err != nil {
- return VersionedFileIdentity{}, nil, err
- }
- fileDiags = append(fileDiags, analysisDiags[fh.URI()]...)
- }
- return fh.VersionedFileIdentity(), fileDiags, nil
-}
diff --git a/internal/lsp/source/extract.go b/internal/lsp/source/extract.go
deleted file mode 100644
index 43b414add..000000000
--- a/internal/lsp/source/extract.go
+++ /dev/null
@@ -1,1307 +0,0 @@
-// 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 source
-
-import (
- "bytes"
- "fmt"
- "go/ast"
- "go/format"
- "go/parser"
- "go/token"
- "go/types"
- "strings"
- "unicode"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/span"
-)
-
-func extractVariable(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, _ *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
- expr, path, ok, err := CanExtractVariable(rng, file)
- if !ok {
- return nil, fmt.Errorf("extractVariable: cannot extract %s: %v", fset.Position(rng.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(), file, path, 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(), file, path, 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(), file, path, info, "x", idx)
- lhsNames = append(lhsNames, lhsName)
- }
- default:
- return nil, fmt.Errorf("cannot extract %T", expr)
- }
-
- insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
- if insertBeforeStmt == nil {
- return nil, fmt.Errorf("cannot find location to insert extraction")
- }
- tok := fset.File(expr.Pos())
- if tok == nil {
- return nil, fmt.Errorf("no file for pos %v", fset.Position(file.Pos()))
- }
- indent, err := calculateIndentation(src, tok, insertBeforeStmt)
- if err != nil {
- return 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, err
- }
- assignment := strings.ReplaceAll(buf.String(), "\n", newLineIndent) + newLineIndent
-
- return &analysis.SuggestedFix{
- TextEdits: []analysis.TextEdit{
- {
- Pos: insertBeforeStmt.Pos(),
- End: insertBeforeStmt.Pos(),
- NewText: []byte(assignment),
- },
- {
- Pos: rng.Start,
- End: rng.End,
- NewText: []byte(lhs),
- },
- },
- }, nil
-}
-
-// CanExtractVariable reports whether the code in the given range can be
-// extracted to a variable.
-func CanExtractVariable(rng span.Range, file *ast.File) (ast.Expr, []ast.Node, bool, error) {
- if rng.Start == rng.End {
- return nil, nil, false, fmt.Errorf("start and end are equal")
- }
- path, _ := astutil.PathEnclosingInterval(file, rng.Start, rng.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 rng.Start != node.Pos() || rng.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 := tok.Line(insertBeforeStmt.Pos())
- lineOffset, err := Offset(tok, tok.LineStart(line))
- if err != nil {
- return "", err
- }
- stmtOffset, err := Offset(tok, insertBeforeStmt.Pos())
- if err != nil {
- return "", err
- }
- return string(content[lineOffset:stmtOffset]), nil
-}
-
-// generateAvailableIdentifier adjusts the new function name until there are no collisons in scope.
-// Possible collisions include other function and variable names. Returns the next index to check for prefix.
-func generateAvailableIdentifier(pos token.Pos, file *ast.File, path []ast.Node, info *types.Info, prefix string, idx int) (string, int) {
- scopes := CollectScopes(info, path, pos)
- return generateIdentifier(idx, prefix, func(name string) bool {
- return file.Scope.Lookup(name) != nil || !isValidName(name, scopes)
- })
-}
-
-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
-}
-
-// isValidName checks for variable collision in scope.
-func isValidName(name string, scopes []*types.Scope) bool {
- for _, scope := range scopes {
- if scope == nil {
- continue
- }
- if scope.Lookup(name) != nil {
- return false
- }
- }
- return true
-}
-
-// 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, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
- return extractFunctionMethod(fset, rng, src, file, pkg, info, true)
-}
-
-// extractFunction refactors the selected block of code into a new function.
-func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
- return extractFunctionMethod(fset, rng, 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, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info, isMethod bool) (*analysis.SuggestedFix, error) {
- errorPrefix := "extractFunction"
- if isMethod {
- errorPrefix = "extractMethod"
- }
- p, ok, methodOk, err := CanExtractFunction(fset, rng, src, file)
- if (!ok && !isMethod) || (!methodOk && isMethod) {
- return nil, fmt.Errorf("%s: cannot extract %s: %v", errorPrefix,
- fset.Position(rng.Start), err)
- }
- tok, path, rng, outer, start := p.tok, p.path, p.rng, p.outer, p.start
- fileScope := info.Scopes[file]
- if fileScope == nil {
- return nil, fmt.Errorf("%s: file scope is empty", errorPrefix)
- }
- pkgScope := fileScope.Parent()
- if pkgScope == nil {
- return 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, start)
- ast.Inspect(outer, func(n ast.Node) bool {
- if n == nil {
- return false
- }
- if n.Pos() < rng.Start || n.End() > rng.End {
- return n.Pos() <= rng.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, rng, path[0])
- if err != nil {
- return 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, 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, 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 uninitialzed.
- 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(fset, file, pkg, v.obj.Type())
- if typ == nil {
- return 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).
- if v.obj.Parent() == nil {
- return nil, fmt.Errorf("parent nil")
- }
- isUsed, firstUseAfter := objUsed(info, span.NewRange(fset, rng.End, v.obj.Parent().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 if v.obj.Parent().Pos() == startParent.Pos() {
- 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,
- })
- }
- }
-
- // 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, err := Offset(tok, rng.Start)
- if err != nil {
- return nil, err
- }
- endOffset, err := Offset(tok, rng.End)
- if err != nil {
- return nil, err
- }
- selection := src[startOffset:endOffset]
- extractedBlock, err := parseBlockStmt(fset, selection)
- if err != nil {
- return 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, fset, file,
- pkg, extractedBlock); err != nil {
- return 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, fset, rng.Start, hasNonNestedReturn)
- if err != nil {
- return 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 uninitailized (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(rng.Start, file, path, 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 unitialized. 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, err
- }
- if err := format.Node(&replaceBuf, fset, extractedFunCall); err != nil {
- return nil, err
- }
- if ifReturn != nil {
- if err := format.Node(&ifBuf, fset, ifReturn); err != nil {
- return nil, err
- }
- }
- if err := format.Node(&newFuncBuf, fset, newFunc); err != nil {
- return 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() < rng.End && cg.Pos() >= rng.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, err := Offset(tok, outer.Pos())
- if err != nil {
- return nil, err
- }
- outerEnd, err := Offset(tok, outer.End())
- if err != nil {
- return nil, err
- }
- before := src[outerStart:startOffset]
- after := src[endOffset:outerEnd]
- indent, err := calculateIndentation(src, tok, start)
- if err != nil {
- return 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 &analysis.SuggestedFix{
- TextEdits: []analysis.TextEdit{{
- Pos: outer.Pos(),
- End: outer.End(),
- NewText: []byte(fullReplacement.String()),
- }},
- }, nil
-}
-
-// adjustRangeForWhitespace adjusts the given range to exclude unnecessary leading or
-// trailing whitespace characters from selection. In the following example, each line
-// of the if statement is indented once. There are also two extra spaces after the
-// closing bracket before the line break.
-//
-// \tif (true) {
-// \t _ = 1
-// \t} \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 by one byte, and rng.End backwards by two bytes.
-func adjustRangeForWhitespace(rng span.Range, tok *token.File, content []byte) (span.Range, error) {
- offset, err := Offset(tok, rng.Start)
- if err != nil {
- return span.Range{}, err
- }
- for offset < len(content) {
- if !unicode.IsSpace(rune(content[offset])) {
- break
- }
- // Move forwards one byte to find a non-whitespace character.
- offset += 1
- }
- rng.Start = tok.Pos(offset)
-
- // Move backwards to find a non-whitespace character.
- offset, err = Offset(tok, rng.End)
- if err != nil {
- return span.Range{}, err
- }
- for o := offset - 1; 0 <= o && o < len(content); o-- {
- if !unicode.IsSpace(rune(content[o])) {
- break
- }
- offset = o
- }
- rng.End = tok.Pos(offset)
- return rng, nil
-}
-
-// findParent finds the parent AST node of the given target node, if the target is a
-// descendant of the starting node.
-func findParent(start ast.Node, target ast.Node) ast.Node {
- var parent ast.Node
- analysisinternal.WalkASTWithParent(start, func(n, p ast.Node) bool {
- if n == target {
- parent = p
- return false
- }
- return true
- })
- return parent
-}
-
-// variable describes the status of a variable within a selection.
-type variable struct {
- obj types.Object
-
- // free reports whether the variable is a free variable, meaning it should
- // be a parameter to the extracted function.
- free bool
-
- // assigned reports whether the variable is assigned to in the selection.
- assigned bool
-
- // defined reports whether the variable is defined in the selection.
- defined bool
-}
-
-// collectFreeVars maps each identifier in the given range to whether it is "free."
-// Given a range, a variable in that range is defined as "free" if it is declared
-// outside of the range and neither at the file scope nor package scope. These free
-// variables will be used as arguments in the extracted function. It also returns a
-// list of identifiers that may need to be returned by the extracted function.
-// Some of the code in this function has been adapted from tools/cmd/guru/freevars.go.
-func collectFreeVars(info *types.Info, file *ast.File, fileScope, pkgScope *types.Scope, rng span.Range, node ast.Node) ([]*variable, error) {
- // id returns non-nil if n denotes an object 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.
- id := func(n *ast.Ident) (types.Object, bool) {
- obj := info.Uses[n]
- if obj == nil {
- return info.Defs[n], false
- }
- if obj.Name() == "_" {
- return nil, false // exclude objects denoting '_'
- }
- if _, ok := obj.(*types.PkgName); ok {
- return nil, false // imported package
- }
- if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
- return nil, false // not defined in this file
- }
- scope := obj.Parent()
- if scope == nil {
- return nil, false // e.g. interface method, struct field
- }
- if scope == fileScope || scope == pkgScope {
- return nil, false // defined at file or package scope
- }
- if rng.Start <= obj.Pos() && obj.Pos() <= rng.End {
- return obj, false // defined within selection => 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 rng.Start <= n.Pos() && n.End() <= rng.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() <= rng.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() < rng.Start || n.End() > rng.End {
- return n.Pos() <= rng.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
- path []ast.Node
- rng span.Range
- outer *ast.FuncDecl
- start ast.Node
-}
-
-// CanExtractFunction reports whether the code in the given range can be
-// extracted to a function.
-func CanExtractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.File) (*fnExtractParams, bool, bool, error) {
- if rng.Start == rng.End {
- return nil, false, false, fmt.Errorf("start and end are equal")
- }
- tok := fset.File(file.Pos())
- if tok == nil {
- return nil, false, false, fmt.Errorf("no file for pos %v", fset.Position(file.Pos()))
- }
- var err error
- rng, err = adjustRangeForWhitespace(rng, tok, src)
- if err != nil {
- return nil, false, false, err
- }
- path, _ := astutil.PathEnclosingInterval(file, rng.Start, rng.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 start, end 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 start == nil && n.Pos() == rng.Start && n.End() <= rng.End {
- start = n
- }
- if end == nil && n.End() == rng.End && n.Pos() >= rng.Start {
- end = n
- }
- return n.Pos() <= rng.End
- })
- if start == nil || end == 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.
- // <rng.start>{ ... }<rng.end> => { <rng.start>...<rng.end> }
- if blockStmt, ok := start.(*ast.BlockStmt); ok {
- if len(blockStmt.List) == 0 {
- return nil, false, false, fmt.Errorf("range maps to empty block statement")
- }
- start, end = blockStmt.List[0], blockStmt.List[len(blockStmt.List)-1]
- rng.Start, rng.End = start.Pos(), end.End()
- }
- return &fnExtractParams{
- tok: tok,
- path: path,
- rng: rng,
- outer: outer,
- start: start,
- }, 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, rng span.Range, obj types.Object) (bool, *ast.Ident) {
- var firstUse *ast.Ident
- for id, objUse := range info.Uses {
- if obj != objUse {
- continue
- }
- if id.Pos() < rng.Start || id.End() > rng.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
-}
-
-// parseExtraction 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, fset *token.FileSet, 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, file, path, 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(fset, file, pkg, typ)
- if expr == nil {
- return nil, nil, fmt.Errorf("nil AST expression")
- }
- var name string
- name, idx = generateAvailableIdentifier(pos, file,
- path, info, "returnValue", idx)
- retVars = append(retVars, &returnVariable{
- name: ast.NewIdent(name),
- decl: &ast.Field{Type: expr},
- zeroVal: analysisinternal.ZeroValue(
- fset, 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, fset *token.FileSet, 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(fset, 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/internal/lsp/source/fix.go b/internal/lsp/source/fix.go
deleted file mode 100644
index 2f921ad0c..000000000
--- a/internal/lsp/source/fix.go
+++ /dev/null
@@ -1,140 +0,0 @@
-// 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 source
-
-import (
- "context"
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/internal/lsp/analysis/fillstruct"
- "golang.org/x/tools/internal/lsp/analysis/undeclaredname"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-type (
- // SuggestedFixFunc is a function used to get the suggested fixes for a given
- // gopls command, some of which are provided by go/analysis.Analyzers. Some of
- // the analyzers in internal/lsp/analysis are not efficient enough to include
- // suggested fixes with their diagnostics, so we have to compute them
- // separately. Such analyzers should provide a function with a signature of
- // SuggestedFixFunc.
- SuggestedFixFunc func(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) (*analysis.SuggestedFix, error)
- singleFileFixFunc func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error)
-)
-
-const (
- FillStruct = "fill_struct"
- StubMethods = "stub_methods"
- UndeclaredName = "undeclared_name"
- ExtractVariable = "extract_variable"
- ExtractFunction = "extract_function"
- ExtractMethod = "extract_method"
-)
-
-// suggestedFixes maps a suggested fix command id to its handler.
-var suggestedFixes = map[string]SuggestedFixFunc{
- FillStruct: singleFile(fillstruct.SuggestedFix),
- UndeclaredName: singleFile(undeclaredname.SuggestedFix),
- ExtractVariable: singleFile(extractVariable),
- ExtractFunction: singleFile(extractFunction),
- ExtractMethod: singleFile(extractMethod),
- StubMethods: stubSuggestedFixFunc,
-}
-
-// singleFile calls analyzers that expect inputs for a single file
-func singleFile(sf singleFileFixFunc) SuggestedFixFunc {
- return func(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) (*analysis.SuggestedFix, error) {
- fset, rng, src, file, pkg, info, err := getAllSuggestedFixInputs(ctx, snapshot, fh, pRng)
- if err != nil {
- return nil, err
- }
- return sf(fset, rng, src, file, pkg, info)
- }
-}
-
-func SuggestedFixFromCommand(cmd protocol.Command, kind protocol.CodeActionKind) SuggestedFix {
- return SuggestedFix{
- Title: cmd.Title,
- Command: &cmd,
- ActionKind: kind,
- }
-}
-
-// ApplyFix applies the command's suggested fix to the given file and
-// range, returning the resulting edits.
-func ApplyFix(ctx context.Context, fix string, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) ([]protocol.TextDocumentEdit, error) {
- handler, ok := suggestedFixes[fix]
- if !ok {
- return nil, fmt.Errorf("no suggested fix function for %s", fix)
- }
- suggestion, err := handler(ctx, snapshot, fh, pRng)
- if err != nil {
- return nil, err
- }
- if suggestion == nil {
- return nil, nil
- }
- fset := snapshot.FileSet()
- editsPerFile := map[span.URI]*protocol.TextDocumentEdit{}
- for _, edit := range suggestion.TextEdits {
- spn, err := span.NewRange(fset, edit.Pos, edit.End).Span()
- if err != nil {
- return nil, err
- }
- fh, err := snapshot.GetVersionedFile(ctx, spn.URI())
- if err != nil {
- return nil, err
- }
- te, ok := editsPerFile[spn.URI()]
- if !ok {
- te = &protocol.TextDocumentEdit{
- TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
- Version: fh.Version(),
- TextDocumentIdentifier: protocol.TextDocumentIdentifier{
- URI: protocol.URIFromSpanURI(fh.URI()),
- },
- },
- }
- editsPerFile[spn.URI()] = te
- }
- _, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
- if err != nil {
- return nil, err
- }
- rng, err := pgf.Mapper.Range(spn)
- if err != nil {
- return nil, err
- }
- te.Edits = append(te.Edits, protocol.TextEdit{
- Range: rng,
- NewText: string(edit.NewText),
- })
- }
- var edits []protocol.TextDocumentEdit
- for _, edit := range editsPerFile {
- edits = append(edits, *edit)
- }
- return edits, nil
-}
-
-// getAllSuggestedFixInputs is a helper function to collect all possible needed
-// inputs for an AppliesFunc or SuggestedFixFunc.
-func getAllSuggestedFixInputs(ctx context.Context, snapshot Snapshot, fh FileHandle, pRng protocol.Range) (*token.FileSet, span.Range, []byte, *ast.File, *types.Package, *types.Info, error) {
- pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
- if err != nil {
- return nil, span.Range{}, nil, nil, nil, nil, errors.Errorf("getting file for Identifier: %w", err)
- }
- rng, err := pgf.Mapper.RangeToSpanRange(pRng)
- if err != nil {
- return nil, span.Range{}, nil, nil, nil, nil, err
- }
- return snapshot.FileSet(), rng, pgf.Src, pgf.File, pkg.GetTypes(), pkg.GetTypesInfo(), nil
-}
diff --git a/internal/lsp/source/folding_range.go b/internal/lsp/source/folding_range.go
deleted file mode 100644
index 576308f99..000000000
--- a/internal/lsp/source/folding_range.go
+++ /dev/null
@@ -1,185 +0,0 @@
-// 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 source
-
-import (
- "context"
- "go/ast"
- "go/token"
- "sort"
- "strings"
-
- "golang.org/x/tools/internal/lsp/protocol"
-)
-
-// FoldingRangeInfo holds range and kind info of folding for an ast.Node
-type FoldingRangeInfo struct {
- MappedRange
- Kind protocol.FoldingRangeKind
-}
-
-// FoldingRange gets all of the folding range for f.
-func FoldingRange(ctx context.Context, snapshot Snapshot, fh FileHandle, 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, ParseFull)
- 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
- }
-
- fset := snapshot.FileSet()
-
- // Get folding ranges for comments separately as they are not walked by ast.Inspect.
- ranges = append(ranges, commentsFoldingRange(fset, pgf.Mapper, pgf.File)...)
-
- visit := func(n ast.Node) bool {
- rng := foldingRangeFunc(fset, pgf.Mapper, 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].Range()
- jrng, _ := ranges[j].Range()
- return protocol.CompareRange(irng, jrng) < 0
- })
-
- return ranges, nil
-}
-
-// foldingRangeFunc calculates the line folding range for ast.Node n
-func foldingRangeFunc(fset *token.FileSet, m *protocol.ColumnMapper, 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(fset, 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(fset, 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(fset, 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(fset, 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 && fset.Position(start).Line == fset.Position(end).Line {
- return nil
- }
- return &FoldingRangeInfo{
- MappedRange: NewMappedRange(fset, m, start, end),
- 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(fset *token.FileSet, 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 fset.Position(open).Line == fset.Position(start).Line ||
- fset.Position(close).Line == fset.Position(end).Line {
- 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(fset *token.FileSet, m *protocol.ColumnMapper, file *ast.File) (comments []*FoldingRangeInfo) {
- for _, commentGrp := range file.Comments {
- startGrp, endGrp := fset.Position(commentGrp.Pos()), fset.Position(commentGrp.End())
- if startGrp.Line == endGrp.Line {
- // Don't fold single line comments.
- continue
- }
-
- firstComment := commentGrp.List[0]
- startPos, endLinePos := firstComment.Pos(), firstComment.End()
- startCmmnt, endCmmnt := fset.Position(startPos), fset.Position(endLinePos)
- if startCmmnt.Line != endCmmnt.Line {
- // 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]))
- }
- comments = append(comments, &FoldingRangeInfo{
- // Fold from the end of the first line comment to the end of the comment block.
- MappedRange: NewMappedRange(fset, m, endLinePos, commentGrp.End()),
- Kind: protocol.Comment,
- })
- }
- return comments
-}
diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go
deleted file mode 100644
index 79da0b3ad..000000000
--- a/internal/lsp/source/format.go
+++ /dev/null
@@ -1,387 +0,0 @@
-// 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 source provides core features for use by Go editors and tools.
-package source
-
-import (
- "bytes"
- "context"
- "fmt"
- "go/ast"
- "go/format"
- "go/parser"
- "go/token"
- "strings"
- "text/scanner"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/lsppos"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
-)
-
-// Format formats a file with a given range.
-func Format(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.TextEdit, error) {
- ctx, done := event.Start(ctx, "source.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().Filename())
- }
-
- pgf, err := snapshot.ParseGo(ctx, fh, ParseFull)
- 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, snapshot, pgf, string(formatted))
- }
-
- fset := snapshot.FileSet()
-
- // 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{}
- 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 format := snapshot.View().Options().GofumptFormat; snapshot.View().Options().Gofumpt && format != nil {
- // 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 langVersion, modulePath string
- mds, err := snapshot.MetadataForFile(ctx, fh.URI())
- if err == nil && len(mds) > 0 {
- if mi := mds[0].ModuleInfo(); mi != nil {
- langVersion = mi.GoVersion
- modulePath = mi.Path
- }
- }
- b, err := format(ctx, langVersion, modulePath, buf.Bytes())
- if err != nil {
- return nil, err
- }
- formatted = string(b)
- }
- return computeTextEdits(ctx, snapshot, pgf, formatted)
-}
-
-func formatSource(ctx context.Context, fh FileHandle) ([]byte, error) {
- _, done := event.Start(ctx, "source.formatSource")
- defer done()
-
- data, err := fh.Read()
- 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 Snapshot, fh FileHandle) (allFixEdits []protocol.TextEdit, editsPerFix []*ImportFix, err error) {
- ctx, done := event.Start(ctx, "source.AllImportsFixes")
- defer done()
-
- pgf, err := snapshot.ParseGo(ctx, fh, ParseFull)
- if err != nil {
- return nil, nil, err
- }
- if err := snapshot.RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
- allFixEdits, editsPerFix, err = computeImportEdits(snapshot, 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(snapshot Snapshot, pgf *ParsedGoFile, options *imports.Options) (allFixEdits []protocol.TextEdit, editsPerFix []*ImportFix, err error) {
- filename := pgf.URI.Filename()
-
- // Build up basic information about the original file.
- allFixes, err := imports.FixImports(filename, pgf.Src, options)
- if err != nil {
- return nil, nil, err
- }
-
- allFixEdits, err = computeFixEdits(snapshot, 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(snapshot, 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 Snapshot, pgf *ParsedGoFile, fix *imports.ImportFix) ([]protocol.TextEdit, error) {
- options := &imports.Options{
- LocalPrefix: snapshot.View().Options().Local,
- // Defaults.
- AllErrors: true,
- Comments: true,
- Fragment: true,
- FormatOnly: false,
- TabIndent: true,
- TabWidth: 8,
- }
- return computeFixEdits(snapshot, pgf, options, []*imports.ImportFix{fix})
-}
-
-func computeFixEdits(snapshot Snapshot, pgf *ParsedGoFile, 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, err := snapshot.View().Options().ComputeEdits(pgf.URI, left, string(fixedData))
- if err != nil {
- return nil, err
- }
- return ProtocolEditsFromSource([]byte(left), edits, pgf.Mapper.Converter)
-}
-
-// 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 := 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 := 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 := tok.Line(tok.Pos(offset)); {
- case line < tok.LineCount():
- nextLineOffset, err := 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 := Offset(tok, c.End()); err != nil {
- return "", err
- } else if end > importEnd {
- startLine := tok.Position(c.Pos()).Line
- endLine := tok.Position(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 := 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, snapshot Snapshot, pgf *ParsedGoFile, formatted string) ([]protocol.TextEdit, error) {
- _, done := event.Start(ctx, "source.computeTextEdits")
- defer done()
-
- edits, err := snapshot.View().Options().ComputeEdits(pgf.URI, string(pgf.Src), formatted)
- if err != nil {
- return nil, err
- }
- return ToProtocolEdits(pgf.Mapper, edits)
-}
-
-// ProtocolEditsFromSource converts text edits to LSP edits using the original
-// source.
-func ProtocolEditsFromSource(src []byte, edits []diff.TextEdit, converter span.Converter) ([]protocol.TextEdit, error) {
- m := lsppos.NewMapper(src)
- var result []protocol.TextEdit
- for _, edit := range edits {
- spn, err := edit.Span.WithOffset(converter)
- if err != nil {
- return nil, fmt.Errorf("computing offsets: %v", err)
- }
- startLine, startChar := m.Position(spn.Start().Offset())
- endLine, endChar := m.Position(spn.End().Offset())
- if startLine < 0 || endLine < 0 {
- return nil, fmt.Errorf("out of bound span: %v", spn)
- }
-
- pstart := protocol.Position{Line: uint32(startLine), Character: uint32(startChar)}
- pend := protocol.Position{Line: uint32(endLine), Character: uint32(endChar)}
- if pstart == pend && edit.NewText == "" {
- // 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: protocol.Range{Start: pstart, End: pend},
- NewText: edit.NewText,
- })
- }
- return result, nil
-}
-
-func ToProtocolEdits(m *protocol.ColumnMapper, edits []diff.TextEdit) ([]protocol.TextEdit, error) {
- if edits == nil {
- return nil, nil
- }
- result := make([]protocol.TextEdit, len(edits))
- for i, edit := range edits {
- rng, err := m.Range(edit.Span)
- if err != nil {
- return nil, err
- }
- result[i] = protocol.TextEdit{
- Range: rng,
- NewText: edit.NewText,
- }
- }
- return result, nil
-}
-
-func FromProtocolEdits(m *protocol.ColumnMapper, edits []protocol.TextEdit) ([]diff.TextEdit, error) {
- if edits == nil {
- return nil, nil
- }
- result := make([]diff.TextEdit, len(edits))
- for i, edit := range edits {
- spn, err := m.RangeSpan(edit.Range)
- if err != nil {
- return nil, err
- }
- result[i] = diff.TextEdit{
- Span: spn,
- NewText: edit.NewText,
- }
- }
- return result, nil
-}
diff --git a/internal/lsp/source/format_test.go b/internal/lsp/source/format_test.go
deleted file mode 100644
index eac78d979..000000000
--- a/internal/lsp/source/format_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// 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 source
-
-import (
- "fmt"
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/diff/myers"
-)
-
-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 got != tt.want {
- t.Errorf("%d: failed for %q:\n%s", i, tt.input, diffStr(t, tt.want, got))
- }
- }
-}
-
-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 got != want {
- t.Errorf("%d: failed for %q:\n%s", i, tt.input, diffStr(t, want, got))
- }
- }
-}
-
-func diffStr(t *testing.T, want, got string) string {
- if want == got {
- return ""
- }
- // Add newlines to avoid newline messages in diff.
- want += "\n"
- got += "\n"
- d, err := myers.ComputeEdits("", want, got)
- if err != nil {
- t.Fatal(err)
- }
- return fmt.Sprintf("%q", diff.ToUnified("want", "got", want, d))
-}
diff --git a/internal/lsp/source/gc_annotations.go b/internal/lsp/source/gc_annotations.go
deleted file mode 100644
index 3616bbfb1..000000000
--- a/internal/lsp/source/gc_annotations.go
+++ /dev/null
@@ -1,214 +0,0 @@
-// 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 source
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
-
- "golang.org/x/tools/internal/gocommand"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
-)
-
-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"
-)
-
-func GCOptimizationDetails(ctx context.Context, snapshot Snapshot, pkg Package) (map[VersionedFileIdentity][]*Diagnostic, error) {
- if len(pkg.CompiledGoFiles()) == 0 {
- return nil, nil
- }
- pkgDir := filepath.Dir(pkg.CompiledGoFiles()[0].URI.Filename())
- outDir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.details", os.Getpid()))
-
- if err := os.MkdirAll(outDir, 0700); err != nil {
- return nil, err
- }
- tmpFile, err := ioutil.TempFile(os.TempDir(), "gopls-x")
- if err != nil {
- return nil, err
- }
- defer os.Remove(tmpFile.Name())
-
- outDirURI := span.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 = span.URI(strings.Replace(string(outDirURI), "file:///", "file://", 1))
- }
- inv := &gocommand.Invocation{
- Verb: "build",
- Args: []string{
- fmt.Sprintf("-gcflags=-json=0,%s", outDirURI),
- fmt.Sprintf("-o=%s", tmpFile.Name()),
- ".",
- },
- WorkingDir: pkgDir,
- }
- _, err = snapshot.RunGoCommandDirect(ctx, Normal, inv)
- if err != nil {
- return nil, err
- }
- files, err := findJSONFiles(outDir)
- if err != nil {
- return nil, err
- }
- reports := make(map[VersionedFileIdentity][]*Diagnostic)
- opts := snapshot.View().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().Filename()) {
- // 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.VersionedFileIdentity()] = diagnostics
- }
- return reports, parseError
-}
-
-func parseDetailsFile(filename string, options *Options) (span.URI, []*Diagnostic, error) {
- buf, err := ioutil.ReadFile(filename)
- if err != nil {
- return "", nil, err
- }
- var (
- uri span.URI
- i int
- diagnostics []*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 // <autogenerated>
- }
- uri = span.URIFromPath(m.File)
- continue
- }
- d := new(protocol.Diagnostic)
- if err := dec.Decode(d); err != nil {
- return "", nil, err
- }
- msg := d.Code.(string)
- if msg != "" {
- msg = fmt.Sprintf("%s(%s)", msg, d.Message)
- }
- if !showDiagnostic(msg, d.Source, options) {
- continue
- }
- var related []RelatedInformation
- for _, ri := range d.RelatedInformation {
- related = append(related, RelatedInformation{
- URI: ri.Location.URI.SpanURI(),
- Range: zeroIndexedRange(ri.Location.Range),
- Message: ri.Message,
- })
- }
- diagnostic := &Diagnostic{
- URI: uri,
- Range: zeroIndexedRange(d.Range),
- Message: msg,
- Severity: d.Severity,
- Source: 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 *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[Inline]
- case strings.HasPrefix(msg, "escape") || msg == "leak":
- return o.Annotations[Escape]
- case strings.HasPrefix(msg, "nilcheck"):
- return o.Annotations[Nil]
- case strings.HasPrefix(msg, "isInBounds") ||
- strings.HasPrefix(msg, "isSliceInBounds"):
- return o.Annotations[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/internal/lsp/source/highlight.go b/internal/lsp/source/highlight.go
deleted file mode 100644
index 7cdb484a8..000000000
--- a/internal/lsp/source/highlight.go
+++ /dev/null
@@ -1,509 +0,0 @@
-// 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 source
-
-import (
- "context"
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
- "strings"
-
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- errors "golang.org/x/xerrors"
-)
-
-func Highlight(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.Range, error) {
- ctx, done := event.Start(ctx, "source.Highlight")
- defer done()
-
- // Don't use GetParsedFile because it uses TypecheckWorkspace, and we
- // always want fully parsed files for highlight, regardless of whether
- // the file belongs to a workspace package.
- pkg, err := snapshot.PackageForFile(ctx, fh.URI(), TypecheckFull, WidestPackage)
- if err != nil {
- return nil, errors.Errorf("getting package for Highlight: %w", err)
- }
- pgf, err := pkg.File(fh.URI())
- if err != nil {
- return nil, errors.Errorf("getting file for Highlight: %w", err)
- }
-
- spn, err := pgf.Mapper.PointSpan(pos)
- if err != nil {
- return nil, err
- }
- rng, err := spn.Range(pgf.Mapper.Converter)
- if err != nil {
- return nil, err
- }
- path, _ := astutil.PathEnclosingInterval(pgf.File, rng.Start, rng.Start)
- if len(path) == 0 {
- return nil, fmt.Errorf("no enclosing position found for %v:%v", int(pos.Line), int(pos.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, rng.Start-1, rng.Start-1); p != nil {
- switch p[0].(type) {
- case *ast.Ident, *ast.SelectorExpr:
- path = p // use preceding ident/selector
- }
- }
- }
- result, err := highlightPath(pkg, path)
- if err != nil {
- return nil, err
- }
- var ranges []protocol.Range
- for rng := range result {
- mRng, err := posToMappedRange(snapshot, pkg, rng.start, rng.end)
- if err != nil {
- return nil, err
- }
- pRng, err := mRng.Range()
- if err != nil {
- return nil, err
- }
- ranges = append(ranges, pRng)
- }
- return ranges, nil
-}
-
-func highlightPath(pkg Package, path []ast.Node) (map[posRange]struct{}, error) {
- result := make(map[posRange]struct{})
- switch node := path[0].(type) {
- case *ast.BasicLit:
- if len(path) > 1 {
- if _, ok := path[1].(*ast.ImportSpec); ok {
- err := highlightImportUses(pkg, path, result)
- return result, err
- }
- }
- highlightFuncControlFlow(path, result)
- case *ast.ReturnStmt, *ast.FuncDecl, *ast.FuncType:
- highlightFuncControlFlow(path, result)
- case *ast.Ident:
- highlightIdentifiers(pkg, path, result)
- case *ast.ForStmt, *ast.RangeStmt:
- highlightLoopControlFlow(path, result)
- case *ast.SwitchStmt:
- highlightSwitchFlow(path, 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.
- if node.Label != nil {
- highlightLabeledFlow(node, result)
- } else {
- switch node.Tok {
- case token.BREAK:
- highlightUnlabeledBreakFlow(path, result)
- case token.CONTINUE:
- highlightLoopControlFlow(path, result)
- }
- }
- default:
- // If the cursor is in an unidentified area, return empty results.
- return nil, nil
- }
- return result, nil
-}
-
-type posRange struct {
- start, end token.Pos
-}
-
-func highlightFuncControlFlow(path []ast.Node, result map[posRange]struct{}) {
- var enclosingFunc ast.Node
- var returnStmt *ast.ReturnStmt
- var resultsList *ast.FieldList
- inReturnList := false
-
-Outer:
- // Reverse walk the path till we get to the func block.
- for i, n := range path {
- switch node := n.(type) {
- case *ast.KeyValueExpr:
- // If cursor is in a key: value expr, we don't want control flow highlighting
- return
- case *ast.CallExpr:
- // If cusor is an arg in a callExpr, we don't want control flow highlighting.
- if i > 0 {
- for _, arg := range node.Args {
- if arg == path[i-1] {
- return
- }
- }
- }
- case *ast.Field:
- inReturnList = true
- case *ast.FuncLit:
- enclosingFunc = n
- resultsList = node.Type.Results
- break Outer
- case *ast.FuncDecl:
- enclosingFunc = n
- resultsList = node.Type.Results
- break Outer
- case *ast.ReturnStmt:
- returnStmt = node
- // If the cursor is not directly in a *ast.ReturnStmt, then
- // we need to know if it is within one of the values that is being returned.
- inReturnList = inReturnList || path[0] != returnStmt
- }
- }
- // Cursor is not in a function.
- if enclosingFunc == nil {
- return
- }
- // If the cursor is on a "return" or "func" keyword, we should highlight all of the exit
- // points of the function, including the "return" and "func" keywords.
- highlightAllReturnsAndFunc := path[0] == returnStmt || path[0] == enclosingFunc
- switch path[0].(type) {
- case *ast.Ident, *ast.BasicLit:
- // Cursor is in an identifier and not in a return statement or in the results list.
- if returnStmt == nil && !inReturnList {
- return
- }
- case *ast.FuncType:
- highlightAllReturnsAndFunc = true
- }
- // The user's cursor may be within the return statement of a function,
- // or within the result section of a function's signature.
- // index := -1
- var nodes []ast.Node
- if returnStmt != nil {
- for _, n := range returnStmt.Results {
- nodes = append(nodes, n)
- }
- } else if resultsList != nil {
- for _, n := range resultsList.List {
- nodes = append(nodes, n)
- }
- }
- _, index := nodeAtPos(nodes, path[0].Pos())
-
- // Highlight the correct argument in the function declaration return types.
- if resultsList != nil && -1 < index && index < len(resultsList.List) {
- rng := posRange{
- start: resultsList.List[index].Pos(),
- end: resultsList.List[index].End(),
- }
- result[rng] = struct{}{}
- }
- // Add the "func" part of the func declaration.
- if highlightAllReturnsAndFunc {
- r := posRange{
- start: enclosingFunc.Pos(),
- end: enclosingFunc.Pos() + token.Pos(len("func")),
- }
- result[r] = struct{}{}
- }
- ast.Inspect(enclosingFunc, func(n ast.Node) bool {
- // Don't traverse any other functions.
- switch n.(type) {
- case *ast.FuncDecl, *ast.FuncLit:
- return enclosingFunc == n
- }
- ret, ok := n.(*ast.ReturnStmt)
- if !ok {
- return true
- }
- var toAdd ast.Node
- // Add the entire return statement, applies when highlight the word "return" or "func".
- if highlightAllReturnsAndFunc {
- toAdd = n
- }
- // Add the relevant field within the entire return statement.
- if -1 < index && index < len(ret.Results) {
- toAdd = ret.Results[index]
- }
- if toAdd != nil {
- result[posRange{start: toAdd.Pos(), end: toAdd.End()}] = struct{}{}
- }
- return false
- })
-}
-
-func highlightUnlabeledBreakFlow(path []ast.Node, result map[posRange]struct{}) {
- // 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, result)
- return // only highlight the innermost statement
- case *ast.SwitchStmt:
- highlightSwitchFlow(path, result)
- return
- case *ast.SelectStmt:
- // TODO: add highlight when breaking a select.
- return
- }
- }
-}
-
-func highlightLabeledFlow(node *ast.BranchStmt, result map[posRange]struct{}) {
- obj := node.Label.Obj
- if obj == nil || obj.Decl == nil {
- return
- }
- label, ok := obj.Decl.(*ast.LabeledStmt)
- if !ok {
- return
- }
- switch label.Stmt.(type) {
- case *ast.ForStmt, *ast.RangeStmt:
- highlightLoopControlFlow([]ast.Node{label.Stmt, label}, result)
- case *ast.SwitchStmt:
- highlightSwitchFlow([]ast.Node{label.Stmt, label}, result)
- }
-}
-
-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, result map[posRange]struct{}) {
- 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.
- rng := posRange{
- start: loop.Pos(),
- end: loop.Pos() + token.Pos(len("for")),
- }
- result[rng] = struct{}{}
-
- // 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.SelectStmt:
- return false
- }
- b, ok := n.(*ast.BranchStmt)
- if !ok {
- return true
- }
- if b.Label == nil || labelDecl(b.Label) == loopLabel {
- result[posRange{start: b.Pos(), end: b.End()}] = struct{}{}
- }
- 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 {
- result[posRange{start: n.Pos(), end: n.End()}] = struct{}{}
- }
- 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 && labelDecl(b.Label) == loopLabel {
- result[posRange{start: b.Pos(), end: b.End()}] = struct{}{}
- }
- return true
- })
-}
-
-func highlightSwitchFlow(path []ast.Node, result map[posRange]struct{}) {
- 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:
- 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.
- rng := posRange{
- start: switchNode.Pos(),
- end: switchNode.Pos() + token.Pos(len("switch")),
- }
- result[rng] = struct{}{}
-
- // Traverse AST to find break statements within the same switch.
- ast.Inspect(switchNode, func(n ast.Node) bool {
- switch n.(type) {
- case *ast.SwitchStmt:
- 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 || labelDecl(b.Label) == switchNodeLabel {
- result[posRange{start: b.Pos(), end: b.End()}] = struct{}{}
- }
- 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 && labelDecl(b.Label) == switchNodeLabel {
- result[posRange{start: b.Pos(), end: b.End()}] = struct{}{}
- }
-
- return true
- })
-}
-
-func labelDecl(n *ast.Ident) *ast.Ident {
- if n == nil {
- return nil
- }
- if n.Obj == nil {
- return nil
- }
- if n.Obj.Decl == nil {
- return nil
- }
- stmt, ok := n.Obj.Decl.(*ast.LabeledStmt)
- if !ok {
- return nil
- }
- return stmt.Label
-}
-
-func highlightImportUses(pkg Package, path []ast.Node, result map[posRange]struct{}) error {
- basicLit, ok := path[0].(*ast.BasicLit)
- if !ok {
- return errors.Errorf("highlightImportUses called with an ast.Node of type %T", basicLit)
- }
- ast.Inspect(path[len(path)-1], func(node ast.Node) bool {
- if imp, ok := node.(*ast.ImportSpec); ok && imp.Path == basicLit {
- result[posRange{start: node.Pos(), end: node.End()}] = struct{}{}
- return false
- }
- n, ok := node.(*ast.Ident)
- if !ok {
- return true
- }
- obj, ok := pkg.GetTypesInfo().ObjectOf(n).(*types.PkgName)
- if !ok {
- return true
- }
- if !strings.Contains(basicLit.Value, obj.Name()) {
- return true
- }
- result[posRange{start: n.Pos(), end: n.End()}] = struct{}{}
- return false
- })
- return nil
-}
-
-func highlightIdentifiers(pkg Package, path []ast.Node, result map[posRange]struct{}) error {
- id, ok := path[0].(*ast.Ident)
- if !ok {
- return errors.Errorf("highlightIdentifiers called with an ast.Node of type %T", id)
- }
- // Check if ident is inside return or func decl.
- highlightFuncControlFlow(path, result)
-
- // TODO: maybe check if ident is a reserved word, if true then don't continue and return results.
-
- idObj := pkg.GetTypesInfo().ObjectOf(id)
- pkgObj, isImported := idObj.(*types.PkgName)
- ast.Inspect(path[len(path)-1], func(node ast.Node) bool {
- if imp, ok := node.(*ast.ImportSpec); ok && isImported {
- highlightImport(pkgObj, imp, result)
- }
- n, ok := node.(*ast.Ident)
- if !ok {
- return true
- }
- if n.Name != id.Name {
- return false
- }
- if nObj := pkg.GetTypesInfo().ObjectOf(n); nObj == idObj {
- result[posRange{start: n.Pos(), end: n.End()}] = struct{}{}
- }
- return false
- })
- return nil
-}
-
-func highlightImport(obj *types.PkgName, imp *ast.ImportSpec, result map[posRange]struct{}) {
- if imp.Name != nil || imp.Path == nil {
- return
- }
- if !strings.Contains(imp.Path.Value, obj.Name()) {
- return
- }
- result[posRange{start: imp.Path.Pos(), end: imp.Path.End()}] = struct{}{}
-}
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
deleted file mode 100644
index b6fd9acf9..000000000
--- a/internal/lsp/source/hover.go
+++ /dev/null
@@ -1,870 +0,0 @@
-// 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 source
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "go/ast"
- "go/constant"
- "go/doc"
- "go/format"
- "go/token"
- "go/types"
- "strconv"
- "strings"
- "time"
- "unicode/utf8"
-
- "golang.org/x/text/unicode/runenames"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/typeparams"
- errors "golang.org/x/xerrors"
-)
-
-// HoverContext contains context extracted from the syntax and type information
-// of a given node, for use in various summaries (hover, autocomplete,
-// signature help).
-type HoverContext struct {
- // signatureSource is the object or node use to derive the hover signature.
- //
- // It may also hold a precomputed string.
- // TODO(rfindley): pre-compute all signatures to avoid this indirection.
- signatureSource interface{}
-
- // comment is the most relevant comment group associated with the hovered object.
- Comment *ast.CommentGroup
-}
-
-// HoverJSON contains information used by hover. It is also the JSON returned
-// for the "structured" hover format
-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 types.Object.Name for the given symbol.
- 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".
- 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"`
-}
-
-func Hover(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) {
- ident, err := Identifier(ctx, snapshot, fh, position)
- if err != nil {
- if hover, innerErr := hoverRune(ctx, snapshot, fh, position); innerErr == nil {
- return hover, nil
- }
- return nil, nil
- }
- h, err := HoverIdentifier(ctx, ident)
- if err != nil {
- return nil, err
- }
- rng, err := ident.Range()
- if err != nil {
- return nil, err
- }
- hover, err := FormatHover(h, snapshot.View().Options())
- if err != nil {
- return nil, err
- }
- return &protocol.Hover{
- Contents: protocol.MarkupContent{
- Kind: snapshot.View().Options().PreferredContentFormat,
- Value: hover,
- },
- Range: rng,
- }, nil
-}
-
-func hoverRune(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) {
- ctx, done := event.Start(ctx, "source.hoverRune")
- defer done()
-
- r, mrng, err := findRune(ctx, snapshot, fh, position)
- if err != nil {
- return nil, err
- }
- rng, err := mrng.Range()
- if err != nil {
- return nil, err
- }
-
- var desc string
- 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 strconv.IsPrint(r) {
- desc = fmt.Sprintf("'%s', U+%04X, %s", string(r), uint32(r), runeName)
- } else {
- desc = fmt.Sprintf("U+%04X, %s", uint32(r), runeName)
- }
- return &protocol.Hover{
- Contents: protocol.MarkupContent{
- Kind: snapshot.View().Options().PreferredContentFormat,
- Value: desc,
- },
- Range: rng,
- }, nil
-}
-
-// ErrNoRuneFound is the error returned when no rune is found at a particular position.
-var ErrNoRuneFound = errors.New("no rune found")
-
-// findRune returns rune information for a position in a file.
-func findRune(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (rune, MappedRange, error) {
- pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
- if err != nil {
- return 0, MappedRange{}, err
- }
- spn, err := pgf.Mapper.PointSpan(position)
- if err != nil {
- return 0, MappedRange{}, err
- }
- rng, err := spn.Range(pgf.Mapper.Converter)
- if err != nil {
- return 0, MappedRange{}, err
- }
- pos := rng.Start
-
- // Find the basic literal enclosing the given position, if there is one.
- var lit *ast.BasicLit
- var found bool
- ast.Inspect(pgf.File, func(n ast.Node) bool {
- if found {
- return false
- }
- if n, ok := n.(*ast.BasicLit); ok && pos >= n.Pos() && pos <= n.End() {
- lit = n
- found = true
- }
- return !found
- })
- if !found {
- return 0, MappedRange{}, ErrNoRuneFound
- }
-
- var r rune
- var start, end token.Pos
- 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 0, MappedRange{}, ErrNoRuneFound
- }
- r, _ = utf8.DecodeRuneInString(s)
- if r == utf8.RuneError {
- return 0, MappedRange{}, fmt.Errorf("rune error")
- }
- start, end = lit.Pos(), lit.End()
- case token.INT:
- // It's an integer, scan only if it is a hex litteral whose bitsize in
- // ranging from 8 to 32.
- if !(strings.HasPrefix(lit.Value, "0x") && len(lit.Value[2:]) >= 2 && len(lit.Value[2:]) <= 8) {
- return 0, MappedRange{}, ErrNoRuneFound
- }
- v, err := strconv.ParseUint(lit.Value[2:], 16, 32)
- if err != nil {
- return 0, MappedRange{}, err
- }
- r = rune(v)
- if r == utf8.RuneError {
- return 0, MappedRange{}, fmt.Errorf("rune error")
- }
- start, end = lit.Pos(), lit.End()
- case token.STRING:
- // It's a string, scan only if it contains a unicode escape sequence under or before the
- // current cursor position.
- var found bool
- litOffset, err := Offset(pgf.Tok, lit.Pos())
- if err != nil {
- return 0, MappedRange{}, err
- }
- offset, err := Offset(pgf.Tok, pos)
- if err != nil {
- return 0, MappedRange{}, 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 0, MappedRange{}, 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 0, MappedRange{}, ErrNoRuneFound
- }
- // Only the rune escape sequence part of the string has to be highlighted, recompute the range.
- runeLen := len(lit.Value) - (int(i) + len(tail))
- start = token.Pos(int(lit.Pos()) + int(i))
- end = token.Pos(int(start) + runeLen)
- found = true
- break
- }
- }
- if !found {
- // No escape sequence found
- return 0, MappedRange{}, ErrNoRuneFound
- }
- default:
- return 0, MappedRange{}, ErrNoRuneFound
- }
-
- mappedRange, err := posToMappedRange(snapshot, pkg, start, end)
- if err != nil {
- return 0, MappedRange{}, err
- }
- return r, mappedRange, nil
-}
-
-func HoverIdentifier(ctx context.Context, i *IdentifierInfo) (*HoverJSON, error) {
- ctx, done := event.Start(ctx, "source.Hover")
- defer done()
-
- hoverCtx, err := FindHoverContext(ctx, i.Snapshot, i.pkg, i.Declaration.obj, i.Declaration.node, i.Declaration.fullDecl)
- if err != nil {
- return nil, err
- }
-
- h := &HoverJSON{
- FullDocumentation: hoverCtx.Comment.Text(),
- Synopsis: doc.Synopsis(hoverCtx.Comment.Text()),
- }
-
- fset := i.Snapshot.FileSet()
- // Determine the symbol's signature.
- switch x := hoverCtx.signatureSource.(type) {
- case string:
- h.Signature = x // a pre-computed signature
-
- case *ast.TypeSpec:
- x2 := *x
- // Don't duplicate comments when formatting type specs.
- x2.Doc = nil
- x2.Comment = nil
- var b strings.Builder
- b.WriteString("type ")
- if err := format.Node(&b, fset, &x2); err != nil {
- return nil, err
- }
- h.Signature = b.String()
-
- case ast.Node:
- var b strings.Builder
- if err := format.Node(&b, fset, x); err != nil {
- return nil, err
- }
- h.Signature = b.String()
-
- // Check if the variable is an integer whose value we can present in a more
- // user-friendly way, i.e. `var hex = 0xe34e` becomes `var hex = 58190`
- if spec, ok := x.(*ast.ValueSpec); ok && len(spec.Values) > 0 {
- if lit, ok := spec.Values[0].(*ast.BasicLit); ok && len(spec.Names) > 0 {
- val := constant.MakeFromLiteral(types.ExprString(lit), lit.Kind, 0)
- h.Signature = fmt.Sprintf("var %s = %s", spec.Names[0], val)
- }
- }
-
- case types.Object:
- // If the variable is implicitly declared in a type switch, we need to
- // manually generate its object string.
- if typ := i.Declaration.typeSwitchImplicit; typ != nil {
- if v, ok := x.(*types.Var); ok {
- h.Signature = fmt.Sprintf("var %s %s", v.Name(), types.TypeString(typ, i.qf))
- break
- }
- }
- h.Signature = objectString(x, i.qf, i.Inferred)
- }
- if obj := i.Declaration.obj; obj != nil {
- h.SingleLine = objectString(obj, i.qf, nil)
- }
- obj := i.Declaration.obj
- if obj == nil {
- return h, nil
- }
-
- // Check if the identifier is test-only (and is therefore not part of a
- // package's API). This is true if the request originated in a test package,
- // and if the declaration is also found in the same test package.
- if i.pkg != nil && obj.Pkg() != nil && i.pkg.ForTest() != "" {
- if _, err := i.pkg.File(i.Declaration.MappedRange[0].URI()); err == nil {
- return h, nil
- }
- }
-
- h.SymbolName, h.LinkPath, h.LinkAnchor = linkData(obj, i.enclosing)
-
- // See golang/go#36998: don't link to modules matching GOPRIVATE.
- //
- // The path returned by linkData is an import path.
- if i.Snapshot.View().IsGoPrivatePath(h.LinkPath) {
- h.LinkPath = ""
- } else if mod, version, ok := moduleAtVersion(h.LinkPath, i); ok {
- h.LinkPath = strings.Replace(h.LinkPath, mod, mod+"@"+version, 1)
- }
-
- return h, nil
-}
-
-// linkData returns the name, import path, and anchor to use in building links
-// to obj.
-//
-// If obj is not visible in documentation, the returned name will be empty.
-func linkData(obj types.Object, enclosing *types.TypeName) (name, importPath, anchor string) {
- // Package names simply link to the package.
- if obj, ok := obj.(*types.PkgName); ok {
- return obj.Name(), obj.Imported().Path(), ""
- }
-
- // Builtins link to the special builtin package.
- if obj.Parent() == types.Universe {
- return obj.Name(), "builtin", obj.Name()
- }
-
- // In all other cases, the object must be exported.
- if !obj.Exported() {
- return "", "", ""
- }
-
- var recv types.Object // If non-nil, the field or method receiver base.
-
- switch obj := obj.(type) {
- case *types.Var:
- // If the object is a field, and we have an associated selector
- // composite literal, or struct, we can determine the link.
- if obj.IsField() && enclosing != nil {
- recv = enclosing
- }
- case *types.Func:
- typ, ok := obj.Type().(*types.Signature)
- if !ok {
- // Note: this should never happen. go/types guarantees that the type of
- // *Funcs are Signatures.
- //
- // TODO(rfindley): given a 'debug' mode, we should panic here.
- return "", "", ""
- }
- if r := typ.Recv(); r != nil {
- if rtyp, _ := Deref(r.Type()).(*types.Named); rtyp != nil {
- // If we have an unexported type, see if the enclosing type is
- // exported (we may have an interface or struct we can link
- // to). If not, don't show any link.
- if !rtyp.Obj().Exported() {
- if enclosing != nil {
- recv = enclosing
- } else {
- return "", "", ""
- }
- } else {
- recv = rtyp.Obj()
- }
- }
- }
- }
-
- if recv != nil && !recv.Exported() {
- return "", "", ""
- }
-
- // Either the object or its receiver must be in the package scope.
- scopeObj := obj
- if recv != nil {
- scopeObj = recv
- }
- if scopeObj.Pkg() == nil || scopeObj.Pkg().Scope().Lookup(scopeObj.Name()) != scopeObj {
- return "", "", ""
- }
-
- importPath = obj.Pkg().Path()
- if recv != nil {
- anchor = fmt.Sprintf("%s.%s", recv.Name(), obj.Name())
- name = fmt.Sprintf("(%s.%s).%s", obj.Pkg().Name(), recv.Name(), obj.Name())
- } else {
- // For most cases, the link is "package/path#symbol".
- anchor = obj.Name()
- name = fmt.Sprintf("%s.%s", obj.Pkg().Name(), obj.Name())
- }
- return name, importPath, anchor
-}
-
-func moduleAtVersion(path string, i *IdentifierInfo) (string, string, bool) {
- // TODO(rfindley): moduleAtVersion should not be responsible for deciding
- // whether or not the link target supports module version links.
- if strings.ToLower(i.Snapshot.View().Options().LinkTarget) != "pkg.go.dev" {
- return "", "", false
- }
- impPkg, err := i.pkg.GetImport(path)
- if err != nil {
- return "", "", false
- }
- if impPkg.Version() == nil {
- return "", "", false
- }
- version, modpath := impPkg.Version().Version, impPkg.Version().Path
- if modpath == "" || version == "" {
- return "", "", false
- }
- return modpath, version, true
-}
-
-// objectString is a wrapper around the types.ObjectString function.
-// It handles adding more information to the object string.
-func objectString(obj types.Object, qf types.Qualifier, inferred *types.Signature) string {
- // If the signature type was inferred, prefer the preferred signature with a
- // comment showing the generic signature.
- if sig, _ := obj.Type().(*types.Signature); sig != nil && typeparams.ForSignature(sig).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
- }
- str := types.ObjectString(obj, qf)
- switch obj := obj.(type) {
- case *types.Const:
- str = fmt.Sprintf("%s = %s", str, obj.Val())
-
- // Try to add a formatted duration as an inline comment
- typ, ok := obj.Type().(*types.Named)
- if !ok {
- break
- }
- pkg := typ.Obj().Pkg()
- if pkg.Path() == "time" && typ.Obj().Name() == "Duration" {
- if d, ok := constant.Int64Val(obj.Val()); ok {
- str += " // " + time.Duration(d).String()
- }
- }
- }
- return str
-}
-
-// FindHoverContext returns a HoverContext struct for an AST node and its
-// declaration object. node should be the actual node used in type checking,
-// while fullNode could be a separate node with more complete syntactic
-// information.
-func FindHoverContext(ctx context.Context, s Snapshot, pkg Package, obj types.Object, pkgNode ast.Node, fullDecl ast.Decl) (*HoverContext, error) {
- var info *HoverContext
-
- // Type parameters get their signature from their declaration object.
- if _, isTypeName := obj.(*types.TypeName); isTypeName {
- if _, isTypeParam := obj.Type().(*typeparams.TypeParam); isTypeParam {
- return &HoverContext{signatureSource: obj}, nil
- }
- }
-
- // This is problematic for a number of reasons. We really need to have a more
- // general mechanism to validate the coherency of AST with type information,
- // but absent that we must do our best to ensure that we don't use fullNode
- // when we actually need the node that was type checked.
- //
- // pkgNode may be nil, if it was eliminated from the type-checked syntax. In
- // that case, use fullDecl if available.
- node := pkgNode
- if node == nil && fullDecl != nil {
- node = fullDecl
- }
-
- switch node := node.(type) {
- case *ast.Ident:
- // The package declaration.
- for _, f := range pkg.GetSyntax() {
- if f.Name == pkgNode {
- info = &HoverContext{Comment: f.Doc}
- }
- }
- case *ast.ImportSpec:
- // Try to find the package documentation for an imported package.
- if pkgName, ok := obj.(*types.PkgName); ok {
- imp, err := pkg.GetImport(pkgName.Imported().Path())
- if err != nil {
- return nil, err
- }
- // Assume that only one file will contain package documentation,
- // so pick the first file that has a doc comment.
- for _, file := range imp.GetSyntax() {
- if file.Doc != nil {
- info = &HoverContext{signatureSource: obj, Comment: file.Doc}
- break
- }
- }
- }
- info = &HoverContext{signatureSource: node}
- case *ast.GenDecl:
- switch obj := obj.(type) {
- case *types.TypeName, *types.Var, *types.Const, *types.Func:
- // Always use the full declaration here if we have it, because the
- // dependent code doesn't rely on pointer identity. This is fragile.
- if d, _ := fullDecl.(*ast.GenDecl); d != nil {
- node = d
- }
- // obj may not have been produced by type checking the AST containing
- // node, so we need to be careful about using token.Pos.
- tok := s.FileSet().File(obj.Pos())
- offset, err := Offset(tok, obj.Pos())
- if err != nil {
- return nil, err
- }
-
- // fullTok and fullPos are the *token.File and object position in for the
- // full AST.
- fullTok := s.FileSet().File(node.Pos())
- fullPos, err := Pos(fullTok, offset)
- if err != nil {
- return nil, err
- }
-
- var spec ast.Spec
- for _, s := range node.Specs {
- // Avoid panics by guarding the calls to token.Offset (golang/go#48249).
- start, err := Offset(fullTok, s.Pos())
- if err != nil {
- return nil, err
- }
- end, err := Offset(fullTok, s.End())
- if err != nil {
- return nil, err
- }
- if start <= offset && offset <= end {
- spec = s
- break
- }
- }
-
- info, err = hoverGenDecl(node, spec, fullPos, obj)
- if err != nil {
- return nil, err
- }
- }
- case *ast.TypeSpec:
- if obj.Parent() == types.Universe {
- if genDecl, ok := fullDecl.(*ast.GenDecl); ok {
- info = hoverTypeSpec(node, genDecl)
- }
- }
- case *ast.FuncDecl:
- switch obj.(type) {
- case *types.Func:
- info = &HoverContext{signatureSource: obj, Comment: node.Doc}
- case *types.Builtin:
- info = &HoverContext{Comment: node.Doc}
- if sig, err := NewBuiltinSignature(ctx, s, obj.Name()); err == nil {
- info.signatureSource = "func " + sig.name + sig.Format()
- } else {
- // Fall back on the object as a signature source.
-
- // TODO(rfindley): refactor so that we can report bugs from the source
- // package.
-
- // debug.Bug(ctx, "invalid builtin hover", "did not find builtin signature: %v", err)
- info.signatureSource = obj
- }
- case *types.Var:
- // Object is a function param or the field of an anonymous struct
- // declared with ':='. Skip the first one because only fields
- // can have docs.
- if isFunctionParam(obj, node) {
- break
- }
-
- field, err := s.PosToField(ctx, pkg, obj.Pos())
- if err != nil {
- return nil, err
- }
-
- if field != nil {
- comment := field.Doc
- if comment.Text() == "" {
- comment = field.Comment
- }
- info = &HoverContext{signatureSource: obj, Comment: comment}
- }
- }
- }
-
- if info == nil {
- info = &HoverContext{signatureSource: obj}
- }
-
- return info, nil
-}
-
-// isFunctionParam returns true if the passed object is either an incoming
-// or an outgoing function param
-func isFunctionParam(obj types.Object, node *ast.FuncDecl) bool {
- for _, f := range node.Type.Params.List {
- if f.Pos() == obj.Pos() {
- return true
- }
- }
- if node.Type.Results != nil {
- for _, f := range node.Type.Results.List {
- if f.Pos() == obj.Pos() {
- return true
- }
- }
- }
- return false
-}
-
-// hoverGenDecl returns hover information an object declared via spec inside
-// of the GenDecl node. obj is the type-checked object corresponding to the
-// declaration, but may have been type-checked using a different AST than the
-// given nodes; fullPos is the position of obj in node's AST.
-func hoverGenDecl(node *ast.GenDecl, spec ast.Spec, fullPos token.Pos, obj types.Object) (*HoverContext, error) {
- if spec == nil {
- return nil, errors.Errorf("no spec for node %v at position %v", node, fullPos)
- }
-
- // If we have a field or method.
- switch obj.(type) {
- case *types.Var, *types.Const, *types.Func:
- return hoverVar(spec, fullPos, obj, node), nil
- }
- // Handle types.
- switch spec := spec.(type) {
- case *ast.TypeSpec:
- return hoverTypeSpec(spec, node), nil
- case *ast.ValueSpec:
- return &HoverContext{signatureSource: spec, Comment: spec.Doc}, nil
- case *ast.ImportSpec:
- return &HoverContext{signatureSource: spec, Comment: spec.Doc}, nil
- }
- return nil, errors.Errorf("unable to format spec %v (%T)", spec, spec)
-}
-
-// TODO(rfindley): rename this function.
-func hoverTypeSpec(spec *ast.TypeSpec, decl *ast.GenDecl) *HoverContext {
- comment := spec.Doc
- if comment == nil && decl != nil {
- comment = decl.Doc
- }
- if comment == nil {
- comment = spec.Comment
- }
- return &HoverContext{
- signatureSource: spec,
- Comment: comment,
- }
-}
-
-func hoverVar(node ast.Spec, fullPos token.Pos, obj types.Object, decl *ast.GenDecl) *HoverContext {
- var fieldList *ast.FieldList
- switch spec := node.(type) {
- case *ast.TypeSpec:
- switch t := spec.Type.(type) {
- case *ast.StructType:
- fieldList = t.Fields
- case *ast.InterfaceType:
- fieldList = t.Methods
- }
- case *ast.ValueSpec:
- // Try to extract the field list of an anonymous struct
- if fieldList = extractFieldList(spec.Type); fieldList != nil {
- break
- }
-
- comment := spec.Doc
- if comment == nil {
- comment = decl.Doc
- }
- if comment == nil {
- comment = spec.Comment
- }
-
- // We need the AST nodes for variable declarations of basic literals with
- // associated values so that we can augment their hover with more information.
- if _, ok := obj.(*types.Var); ok && spec.Type == nil && len(spec.Values) > 0 {
- if _, ok := spec.Values[0].(*ast.BasicLit); ok {
- return &HoverContext{signatureSource: spec, Comment: comment}
- }
- }
-
- return &HoverContext{signatureSource: obj, Comment: comment}
- }
-
- if fieldList != nil {
- comment := findFieldComment(fullPos, fieldList)
- return &HoverContext{signatureSource: obj, Comment: comment}
- }
- return &HoverContext{signatureSource: obj, Comment: decl.Doc}
-}
-
-// extractFieldList recursively tries to extract a field list.
-// If it is not found, nil is returned.
-func extractFieldList(specType ast.Expr) *ast.FieldList {
- switch t := specType.(type) {
- case *ast.StructType:
- return t.Fields
- case *ast.InterfaceType:
- return t.Methods
- case *ast.ArrayType:
- return extractFieldList(t.Elt)
- case *ast.MapType:
- // Map value has a greater chance to be a struct
- if fields := extractFieldList(t.Value); fields != nil {
- return fields
- }
- return extractFieldList(t.Key)
- case *ast.ChanType:
- return extractFieldList(t.Value)
- }
- return nil
-}
-
-// findFieldComment visits all fields in depth-first order and returns
-// the comment of a field with passed position. If no comment is found,
-// nil is returned.
-func findFieldComment(pos token.Pos, fieldList *ast.FieldList) *ast.CommentGroup {
- for _, field := range fieldList.List {
- if field.Pos() == pos {
- if field.Doc.Text() != "" {
- return field.Doc
- }
- return field.Comment
- }
-
- if nestedFieldList := extractFieldList(field.Type); nestedFieldList != nil {
- if c := findFieldComment(pos, nestedFieldList); c != nil {
- return c
- }
- }
- }
- return nil
-}
-
-func FormatHover(h *HoverJSON, options *Options) (string, error) {
- signature := formatSignature(h, options)
-
- switch options.HoverKind {
- case SingleLine:
- return h.SingleLine, nil
- case NoDocumentation:
- return signature, nil
- case Structured:
- b, err := json.Marshal(h)
- if err != nil {
- return "", err
- }
- return string(b), nil
- }
-
- link := formatLink(h, options)
- doc := formatDoc(h, options)
-
- var b strings.Builder
- parts := []string{signature, doc, link}
- for i, el := range parts {
- if el != "" {
- b.WriteString(el)
-
- // Don't write out final newline.
- if i == len(parts) {
- continue
- }
- // If any elements of the remainder of the list are non-empty,
- // write a newline.
- if anyNonEmpty(parts[i+1:]) {
- if options.PreferredContentFormat == protocol.Markdown {
- b.WriteString("\n\n")
- } else {
- b.WriteRune('\n')
- }
- }
- }
- }
- return b.String(), nil
-}
-
-func formatSignature(h *HoverJSON, options *Options) string {
- signature := h.Signature
- if signature != "" && options.PreferredContentFormat == protocol.Markdown {
- signature = fmt.Sprintf("```go\n%s\n```", signature)
- }
- return signature
-}
-
-func formatLink(h *HoverJSON, options *Options) string {
- if !options.LinksInHover || options.LinkTarget == "" || h.LinkPath == "" {
- return ""
- }
- plainLink := BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor)
- switch options.PreferredContentFormat {
- case protocol.Markdown:
- return fmt.Sprintf("[`%s` on %s](%s)", h.SymbolName, options.LinkTarget, plainLink)
- case protocol.PlainText:
- return ""
- default:
- return plainLink
- }
-}
-
-// BuildLink constructs a link with the given target, path, and anchor.
-func BuildLink(target, path, anchor string) string {
- link := fmt.Sprintf("https://%s/%s", target, path)
- if target == "pkg.go.dev" {
- link += "?utm_source=gopls"
- }
- if anchor == "" {
- return link
- }
- return link + "#" + anchor
-}
-
-func formatDoc(h *HoverJSON, options *Options) string {
- var doc string
- switch options.HoverKind {
- case SynopsisDocumentation:
- doc = h.Synopsis
- case FullDocumentation:
- doc = h.FullDocumentation
- }
- if options.PreferredContentFormat == protocol.Markdown {
- return CommentToMarkdown(doc)
- }
- return doc
-}
-
-func anyNonEmpty(x []string) bool {
- for _, el := range x {
- if el != "" {
- return true
- }
- }
- return false
-}
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
deleted file mode 100644
index bf4941f18..000000000
--- a/internal/lsp/source/identifier.go
+++ /dev/null
@@ -1,576 +0,0 @@
-// 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 source
-
-import (
- "context"
- "fmt"
- "go/ast"
- "go/parser"
- "go/token"
- "go/types"
- "sort"
- "strconv"
-
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/typeparams"
- errors "golang.org/x/xerrors"
-)
-
-// IdentifierInfo holds information about an identifier in Go source.
-type IdentifierInfo struct {
- Name string
- Snapshot Snapshot
- MappedRange
-
- Type struct {
- MappedRange
- Object types.Object
- }
-
- Inferred *types.Signature
-
- Declaration Declaration
-
- ident *ast.Ident
-
- // For struct fields or embedded interfaces, enclosing is the object
- // corresponding to the outer type declaration, if it is exported, for use in
- // documentation links.
- enclosing *types.TypeName
-
- pkg Package
- qf types.Qualifier
-}
-
-func (i *IdentifierInfo) IsImport() bool {
- _, ok := i.Declaration.node.(*ast.ImportSpec)
- return ok
-}
-
-type Declaration struct {
- MappedRange []MappedRange
-
- // The typechecked node.
- node ast.Node
-
- // Optional: the fully parsed node, to be used for formatting in cases where
- // node has missing information. This could be the case when node was parsed
- // in ParseExported mode.
- fullDecl ast.Decl
-
- // The typechecked object.
- obj types.Object
-
- // typeSwitchImplicit indicates that the declaration is in an implicit
- // type switch. Its type is the type of the variable on the right-hand
- // side of the type switch.
- typeSwitchImplicit types.Type
-}
-
-// Identifier returns identifier information for a position
-// in a file, accounting for a potentially incomplete selector.
-func Identifier(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) (*IdentifierInfo, error) {
- ctx, done := event.Start(ctx, "source.Identifier")
- defer done()
-
- pkgs, err := snapshot.PackagesForFile(ctx, fh.URI(), TypecheckAll, false)
- if err != nil {
- return nil, err
- }
- if len(pkgs) == 0 {
- return nil, fmt.Errorf("no packages for file %v", fh.URI())
- }
- sort.Slice(pkgs, func(i, j int) bool {
- // Prefer packages with a more complete parse mode.
- if pkgs[i].ParseMode() != pkgs[j].ParseMode() {
- return pkgs[i].ParseMode() > pkgs[j].ParseMode()
- }
- return len(pkgs[i].CompiledGoFiles()) < len(pkgs[j].CompiledGoFiles())
- })
- var findErr error
- for _, pkg := range pkgs {
- pgf, err := pkg.File(fh.URI())
- if err != nil {
- return nil, err
- }
- spn, err := pgf.Mapper.PointSpan(pos)
- if err != nil {
- return nil, err
- }
- rng, err := spn.Range(pgf.Mapper.Converter)
- if err != nil {
- return nil, err
- }
- var ident *IdentifierInfo
- ident, findErr = findIdentifier(ctx, snapshot, pkg, pgf, rng.Start)
- if findErr == nil {
- return ident, nil
- }
- }
- return nil, findErr
-}
-
-// ErrNoIdentFound is error returned when no identifer is found at a particular position
-var ErrNoIdentFound = errors.New("no identifier found")
-
-func findIdentifier(ctx context.Context, snapshot Snapshot, pkg Package, pgf *ParsedGoFile, pos token.Pos) (*IdentifierInfo, error) {
- file := pgf.File
- // Handle import specs separately, as there is no formal position for a
- // package declaration.
- if result, err := importSpec(snapshot, pkg, file, pos); result != nil || err != nil {
- return result, err
- }
- path := pathEnclosingObjNode(file, pos)
- if path == nil {
- return nil, ErrNoIdentFound
- }
-
- qf := Qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo())
-
- ident, _ := path[0].(*ast.Ident)
- if ident == nil {
- return nil, ErrNoIdentFound
- }
- // Special case for package declarations, since they have no
- // corresponding types.Object.
- if ident == file.Name {
- rng, err := posToMappedRange(snapshot, pkg, file.Name.Pos(), file.Name.End())
- if err != nil {
- return nil, err
- }
- var declAST *ast.File
- for _, pgf := range pkg.CompiledGoFiles() {
- if pgf.File.Doc != nil {
- declAST = pgf.File
- }
- }
- // If there's no package documentation, just use current file.
- if declAST == nil {
- declAST = file
- }
- declRng, err := posToMappedRange(snapshot, pkg, declAST.Name.Pos(), declAST.Name.End())
- if err != nil {
- return nil, err
- }
- return &IdentifierInfo{
- Name: file.Name.Name,
- ident: file.Name,
- MappedRange: rng,
- pkg: pkg,
- qf: qf,
- Snapshot: snapshot,
- Declaration: Declaration{
- node: declAST.Name,
- MappedRange: []MappedRange{declRng},
- },
- }, nil
- }
-
- result := &IdentifierInfo{
- Snapshot: snapshot,
- qf: qf,
- pkg: pkg,
- ident: ident,
- enclosing: searchForEnclosing(pkg.GetTypesInfo(), path),
- }
-
- result.Name = result.ident.Name
- var err error
- if result.MappedRange, err = posToMappedRange(snapshot, pkg, result.ident.Pos(), result.ident.End()); err != nil {
- return nil, err
- }
-
- result.Declaration.obj = pkg.GetTypesInfo().ObjectOf(result.ident)
- if result.Declaration.obj == nil {
- // If there was no types.Object for the declaration, there might be an
- // implicit local variable declaration in a type switch.
- if objs, typ := typeSwitchImplicits(pkg, path); len(objs) > 0 {
- // There is no types.Object for the declaration of an implicit local variable,
- // but all of the types.Objects associated with the usages of this variable can be
- // used to connect it back to the declaration.
- // Preserve the first of these objects and treat it as if it were the declaring object.
- result.Declaration.obj = objs[0]
- result.Declaration.typeSwitchImplicit = typ
- } else {
- // Probably a type error.
- return nil, errors.Errorf("%w for ident %v", errNoObjectFound, result.Name)
- }
- }
-
- // Handle builtins separately.
- if result.Declaration.obj.Parent() == types.Universe {
- builtin, err := snapshot.BuiltinFile(ctx)
- if err != nil {
- return nil, err
- }
- builtinObj := builtin.File.Scope.Lookup(result.Name)
- if builtinObj == nil {
- return nil, fmt.Errorf("no builtin object for %s", result.Name)
- }
- decl, ok := builtinObj.Decl.(ast.Node)
- if !ok {
- return nil, errors.Errorf("no declaration for %s", result.Name)
- }
- result.Declaration.node = decl
- if typeSpec, ok := decl.(*ast.TypeSpec); ok {
- // Find the GenDecl (which has the doc comments) for the TypeSpec.
- result.Declaration.fullDecl = findGenDecl(builtin.File, typeSpec)
- }
-
- // The builtin package isn't in the dependency graph, so the usual
- // utilities won't work here.
- rng := NewMappedRange(snapshot.FileSet(), builtin.Mapper, decl.Pos(), decl.Pos()+token.Pos(len(result.Name)))
- result.Declaration.MappedRange = append(result.Declaration.MappedRange, rng)
- return result, nil
- }
-
- // (error).Error is a special case of builtin. Lots of checks to confirm
- // that this is the builtin Error.
- if obj := result.Declaration.obj; obj.Parent() == nil && obj.Pkg() == nil && obj.Name() == "Error" {
- if _, ok := obj.Type().(*types.Signature); ok {
- builtin, err := snapshot.BuiltinFile(ctx)
- if err != nil {
- return nil, err
- }
- // Look up "error" and then navigate to its only method.
- // The Error method does not appear in the builtin package's scope.log.Pri
- const errorName = "error"
- builtinObj := builtin.File.Scope.Lookup(errorName)
- if builtinObj == nil {
- return nil, fmt.Errorf("no builtin object for %s", errorName)
- }
- decl, ok := builtinObj.Decl.(ast.Node)
- if !ok {
- return nil, errors.Errorf("no declaration for %s", errorName)
- }
- spec, ok := decl.(*ast.TypeSpec)
- if !ok {
- return nil, fmt.Errorf("no type spec for %s", errorName)
- }
- iface, ok := spec.Type.(*ast.InterfaceType)
- if !ok {
- return nil, fmt.Errorf("%s is not an interface", errorName)
- }
- if iface.Methods.NumFields() != 1 {
- return nil, fmt.Errorf("expected 1 method for %s, got %v", errorName, iface.Methods.NumFields())
- }
- method := iface.Methods.List[0]
- if len(method.Names) != 1 {
- return nil, fmt.Errorf("expected 1 name for %v, got %v", method, len(method.Names))
- }
- name := method.Names[0].Name
- result.Declaration.node = method
- rng := NewMappedRange(snapshot.FileSet(), builtin.Mapper, method.Pos(), method.Pos()+token.Pos(len(name)))
- result.Declaration.MappedRange = append(result.Declaration.MappedRange, rng)
- return result, nil
- }
- }
-
- // 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 := result.Declaration.obj.(*types.Var); ok && v.Embedded() {
- // types.Info.Uses contains the embedded field's *types.TypeName.
- if typeName := pkg.GetTypesInfo().Uses[ident]; typeName != nil {
- result.Declaration.obj = typeName
- }
- }
-
- rng, err := objToMappedRange(snapshot, pkg, result.Declaration.obj)
- if err != nil {
- return nil, err
- }
- result.Declaration.MappedRange = append(result.Declaration.MappedRange, rng)
-
- declPkg, err := FindPackageFromPos(ctx, snapshot, result.Declaration.obj.Pos())
- if err != nil {
- return nil, err
- }
- if result.Declaration.node, err = snapshot.PosToDecl(ctx, declPkg, result.Declaration.obj.Pos()); err != nil {
- return nil, err
- }
- // Ensure that we have the full declaration, in case the declaration was
- // parsed in ParseExported and therefore could be missing information.
- if result.Declaration.fullDecl, err = fullNode(snapshot, result.Declaration.obj, declPkg); err != nil {
- return nil, err
- }
- typ := pkg.GetTypesInfo().TypeOf(result.ident)
- if typ == nil {
- return result, nil
- }
-
- result.Inferred = inferredSignature(pkg.GetTypesInfo(), ident)
-
- result.Type.Object = typeToObject(typ)
- if result.Type.Object != nil {
- // Identifiers with the type "error" are a special case with no position.
- if hasErrorType(result.Type.Object) {
- return result, nil
- }
- if result.Type.MappedRange, err = objToMappedRange(snapshot, pkg, result.Type.Object); err != nil {
- return nil, err
- }
- }
- return result, nil
-}
-
-// findGenDecl determines the parent ast.GenDecl for a given ast.Spec.
-func findGenDecl(f *ast.File, spec ast.Spec) *ast.GenDecl {
- for _, decl := range f.Decls {
- if genDecl, ok := decl.(*ast.GenDecl); ok {
- if genDecl.Pos() <= spec.Pos() && genDecl.End() >= spec.End() {
- return genDecl
- }
- }
- }
- return nil
-}
-
-// fullNode tries to extract the full spec corresponding to obj's declaration.
-// If the package was not parsed in full, the declaration file will be
-// re-parsed to ensure it has complete syntax.
-func fullNode(snapshot Snapshot, obj types.Object, pkg Package) (ast.Decl, error) {
- // declaration in a different package... make sure we have full AST information.
- tok := snapshot.FileSet().File(obj.Pos())
- uri := span.URIFromPath(tok.Name())
- pgf, err := pkg.File(uri)
- if err != nil {
- return nil, err
- }
- file := pgf.File
- pos := obj.Pos()
- if pgf.Mode != ParseFull {
- fset := snapshot.FileSet()
- file2, _ := parser.ParseFile(fset, tok.Name(), pgf.Src, parser.AllErrors|parser.ParseComments)
- if file2 != nil {
- offset, err := Offset(tok, obj.Pos())
- if err != nil {
- return nil, err
- }
- file = file2
- tok2 := fset.File(file2.Pos())
- pos = tok2.Pos(offset)
- }
- }
- path, _ := astutil.PathEnclosingInterval(file, pos, pos)
- for _, n := range path {
- if decl, ok := n.(ast.Decl); ok {
- return decl, nil
- }
- }
- return nil, nil
-}
-
-// 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 := typeparams.GetInstances(info)[id]
- sig, _ := inst.Type.(*types.Signature)
- return sig
-}
-
-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 := Deref(sel.Recv())
-
- // Keep track of the last exported type seen.
- var exported *types.TypeName
- if named, ok := 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 = Deref(r.Field(index).Type())
- if named, ok := recv.(*types.Named); ok && named.Obj().Exported() {
- exported = named.Obj()
- }
- }
- }
- return exported
- }
- case *ast.CompositeLit:
- if t, ok := info.Types[n]; ok {
- if named, _ := t.Type.(*types.Named); named != nil {
- return named.Obj()
- }
- }
- case *ast.TypeSpec:
- if _, ok := n.Type.(*ast.StructType); ok {
- if t, ok := info.Defs[n.Name]; ok {
- if tname, _ := t.(*types.TypeName); tname != nil {
- return tname
- }
- }
- }
- }
- }
- return nil
-}
-
-func typeToObject(typ types.Type) types.Object {
- switch typ := typ.(type) {
- case *types.Named:
- 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.Object
-
- results := typ.Results()
- for i := 0; i < results.Len(); i++ {
- obj := typeToObject(results.At(i).Type())
- if obj == nil || hasErrorType(obj) {
- // Skip builtins.
- 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"
-}
-
-// importSpec handles positions inside of an *ast.ImportSpec.
-func importSpec(snapshot Snapshot, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
- var imp *ast.ImportSpec
- for _, spec := range file.Imports {
- if spec.Path.Pos() <= pos && pos < spec.Path.End() {
- imp = spec
- }
- }
- if imp == nil {
- return nil, nil
- }
- importPath, err := strconv.Unquote(imp.Path.Value)
- if err != nil {
- return nil, errors.Errorf("import path not quoted: %s (%v)", imp.Path.Value, err)
- }
- result := &IdentifierInfo{
- Snapshot: snapshot,
- Name: importPath,
- pkg: pkg,
- }
- if result.MappedRange, err = posToMappedRange(snapshot, pkg, imp.Path.Pos(), imp.Path.End()); err != nil {
- return nil, err
- }
- // Consider the "declaration" of an import spec to be the imported package.
- importedPkg, err := pkg.GetImport(importPath)
- if err != nil {
- return nil, err
- }
- // Return all of the files in the package as the definition of the import spec.
- for _, dst := range importedPkg.GetSyntax() {
- rng, err := posToMappedRange(snapshot, pkg, dst.Pos(), dst.End())
- if err != nil {
- return nil, err
- }
- result.Declaration.MappedRange = append(result.Declaration.MappedRange, rng)
- }
-
- result.Declaration.node = imp
- return result, nil
-}
-
-// 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(pkg Package, 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 = pkg.GetTypesInfo().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 && pkg.GetTypesInfo().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 := pkg.GetTypesInfo().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 = pkg.GetTypesInfo().TypeOf(rhs.X)
- }
- }
- return objs, typ
-}
diff --git a/internal/lsp/source/identifier_test.go b/internal/lsp/source/identifier_test.go
deleted file mode 100644
index 9bbdf58de..000000000
--- a/internal/lsp/source/identifier_test.go
+++ /dev/null
@@ -1,128 +0,0 @@
-// 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 source
-
-import (
- "bytes"
- "go/ast"
- "go/parser"
- "go/token"
- "go/types"
- "testing"
-)
-
-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
- }{
- {
- desc: "self enclosing",
- src: `package a; type X struct {}`,
- wantTypeName: "X",
- },
- {
- // 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",
- },
- {
- desc: "struct field",
- src: `package a; type T struct { X int }`,
- wantTypeName: "T",
- },
- {
- desc: "nested struct field",
- src: `package a; type T struct { E struct { X int } }`,
- wantTypeName: "T",
- },
- {
- desc: "slice entry",
- src: `package a; type T []int; var S = T{X}; var X int = 2`,
- wantTypeName: "T",
- },
- {
- desc: "struct pointer literal",
- src: `package a; type T struct {i int}; var L = &T{X}; const X = 2`,
- wantTypeName: "T",
- },
- }
-
- 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(...) = <nil>, 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(f *token.File) bool {
- if f.Name() == fname {
- tok = f
- 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 {
- return &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),
- }
-}
diff --git a/internal/lsp/source/implementation.go b/internal/lsp/source/implementation.go
deleted file mode 100644
index b53d7c994..000000000
--- a/internal/lsp/source/implementation.go
+++ /dev/null
@@ -1,446 +0,0 @@
-// 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 source
-
-import (
- "context"
- "errors"
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
- "sort"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "golang.org/x/xerrors"
-)
-
-func Implementation(ctx context.Context, snapshot Snapshot, f FileHandle, pp protocol.Position) ([]protocol.Location, error) {
- ctx, done := event.Start(ctx, "source.Implementation")
- defer done()
-
- impls, err := implementations(ctx, snapshot, f, pp)
- if err != nil {
- return nil, err
- }
- var locations []protocol.Location
- for _, impl := range impls {
- if impl.pkg == nil || len(impl.pkg.CompiledGoFiles()) == 0 {
- continue
- }
- rng, err := objToMappedRange(snapshot, impl.pkg, impl.obj)
- if err != nil {
- return nil, err
- }
- pr, err := rng.Range()
- if err != nil {
- return nil, err
- }
- locations = append(locations, protocol.Location{
- URI: protocol.URIFromSpanURI(rng.URI()),
- Range: pr,
- })
- }
- sort.Slice(locations, func(i, j int) bool {
- li, lj := locations[i], locations[j]
- if li.URI == lj.URI {
- return protocol.CompareRange(li.Range, lj.Range) < 0
- }
- return li.URI < lj.URI
- })
- return locations, nil
-}
-
-var ErrNotAType = errors.New("not a type name or method")
-
-// implementations returns the concrete implementations of the specified
-// interface, or the interfaces implemented by the specified concrete type.
-func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]qualifiedObject, error) {
- var (
- impls []qualifiedObject
- seen = make(map[token.Position]bool)
- fset = s.FileSet()
- )
-
- qos, err := qualifiedObjsAtProtocolPos(ctx, s, f.URI(), pp)
- if err != nil {
- return nil, err
- }
- for _, qo := range qos {
- var (
- queryType types.Type
- queryMethod *types.Func
- )
-
- switch obj := qo.obj.(type) {
- case *types.Func:
- queryMethod = obj
- if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
- queryType = ensurePointer(recv.Type())
- }
- case *types.TypeName:
- queryType = ensurePointer(obj.Type())
- }
-
- if queryType == nil {
- return nil, ErrNotAType
- }
-
- if types.NewMethodSet(queryType).Len() == 0 {
- return nil, nil
- }
-
- // Find all named types, even local types (which can have methods
- // due to promotion).
- var (
- allNamed []*types.Named
- pkgs = make(map[*types.Package]Package)
- )
- knownPkgs, err := s.KnownPackages(ctx)
- if err != nil {
- return nil, err
- }
- for _, pkg := range knownPkgs {
- pkgs[pkg.GetTypes()] = pkg
- info := pkg.GetTypesInfo()
- for _, obj := range info.Defs {
- obj, ok := obj.(*types.TypeName)
- // We ignore aliases 'type M = N' to avoid duplicate reporting
- // of the Named type N.
- if !ok || obj.IsAlias() {
- continue
- }
- if named, ok := obj.Type().(*types.Named); ok {
- allNamed = append(allNamed, named)
- }
- }
- }
-
- // Find all the named types that match our query.
- for _, named := range allNamed {
- var (
- candObj types.Object = named.Obj()
- candType = ensurePointer(named)
- )
-
- if !concreteImplementsIntf(candType, queryType) {
- continue
- }
-
- ms := types.NewMethodSet(candType)
- if ms.Len() == 0 {
- // Skip empty interfaces.
- continue
- }
-
- // If client queried a method, look up corresponding candType method.
- if queryMethod != nil {
- sel := ms.Lookup(queryMethod.Pkg(), queryMethod.Name())
- if sel == nil {
- continue
- }
- candObj = sel.Obj()
- }
-
- pos := fset.Position(candObj.Pos())
- if candObj == queryMethod || seen[pos] {
- continue
- }
-
- seen[pos] = true
-
- impls = append(impls, qualifiedObject{
- obj: candObj,
- pkg: pkgs[candObj.Pkg()],
- })
- }
- }
-
- return impls, nil
-}
-
-// 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 := IsInterface(a), 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
- }
-
- return types.AssignableTo(a, b)
-}
-
-// 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 := T.(*types.Named); ok && !IsInterface(T) {
- return types.NewPointer(T)
- }
-
- return T
-}
-
-type qualifiedObject struct {
- obj types.Object
-
- // pkg is the Package that contains obj's definition.
- pkg Package
-
- // node is the *ast.Ident or *ast.ImportSpec we followed to find obj, if any.
- node ast.Node
-
- // sourcePkg is the Package that contains node, if any.
- sourcePkg Package
-}
-
-var (
- errBuiltin = errors.New("builtin object")
- errNoObjectFound = errors.New("no object found")
-)
-
-// qualifiedObjsAtProtocolPos returns info for all the type.Objects
-// referenced at the given position. An object will be returned for
-// every package that the file belongs to, in every typechecking mode
-// applicable.
-func qualifiedObjsAtProtocolPos(ctx context.Context, s Snapshot, uri span.URI, pp protocol.Position) ([]qualifiedObject, error) {
- pkgs, err := s.PackagesForFile(ctx, uri, TypecheckAll, false)
- if err != nil {
- return nil, err
- }
- if len(pkgs) == 0 {
- return nil, errNoObjectFound
- }
- pkg := pkgs[0]
- pgf, err := pkg.File(uri)
- if err != nil {
- return nil, err
- }
- spn, err := pgf.Mapper.PointSpan(pp)
- if err != nil {
- return nil, err
- }
- rng, err := spn.Range(pgf.Mapper.Converter)
- if err != nil {
- return nil, err
- }
- offset, err := Offset(pgf.Tok, rng.Start)
- if err != nil {
- return nil, err
- }
- return qualifiedObjsAtLocation(ctx, s, objSearchKey{uri, offset}, map[objSearchKey]bool{})
-}
-
-type objSearchKey struct {
- uri span.URI
- offset int
-}
-
-// qualifiedObjsAtLocation finds all objects referenced at offset in uri, across
-// all packages in the snapshot.
-func qualifiedObjsAtLocation(ctx context.Context, s Snapshot, key objSearchKey, seen map[objSearchKey]bool) ([]qualifiedObject, error) {
- if seen[key] {
- return nil, nil
- }
- seen[key] = true
-
- // We search for referenced objects starting with all packages containing the
- // current location, and then repeating the search for every distinct object
- // location discovered.
- //
- // In the common case, there should be at most one additional location to
- // consider: the definition of the object referenced by the location. But we
- // try to be comprehensive in case we ever support variations on build
- // constraints.
-
- pkgs, err := s.PackagesForFile(ctx, key.uri, TypecheckAll, false)
- if err != nil {
- return nil, err
- }
-
- // report objects in the order we encounter them. This ensures that the first
- // result is at the cursor...
- var qualifiedObjs []qualifiedObject
- // ...but avoid duplicates.
- seenObjs := map[types.Object]bool{}
-
- for _, searchpkg := range pkgs {
- pgf, err := searchpkg.File(key.uri)
- if err != nil {
- return nil, err
- }
- pos := pgf.Tok.Pos(key.offset)
- path := pathEnclosingObjNode(pgf.File, pos)
- if path == nil {
- continue
- }
- var objs []types.Object
- 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(searchpkg, path); len(implicits) > 0 {
- objs = append(objs, implicits...)
- } else {
- obj := searchpkg.GetTypesInfo().ObjectOf(leaf)
- if obj == nil {
- return nil, xerrors.Errorf("%w for %q", errNoObjectFound, leaf.Name)
- }
- objs = append(objs, obj)
- }
- case *ast.ImportSpec:
- // Look up the implicit *types.PkgName.
- obj := searchpkg.GetTypesInfo().Implicits[leaf]
- if obj == nil {
- return nil, xerrors.Errorf("%w for import %q", errNoObjectFound, ImportPath(leaf))
- }
- objs = append(objs, obj)
- }
- // Get all of the transitive dependencies of the search package.
- pkgs := make(map[*types.Package]Package)
- var addPkg func(pkg Package)
- addPkg = func(pkg Package) {
- pkgs[pkg.GetTypes()] = pkg
- for _, imp := range pkg.Imports() {
- if _, ok := pkgs[imp.GetTypes()]; !ok {
- addPkg(imp)
- }
- }
- }
- addPkg(searchpkg)
- for _, obj := range objs {
- if obj.Parent() == types.Universe {
- return nil, xerrors.Errorf("%q: %w", obj.Name(), errBuiltin)
- }
- pkg, ok := pkgs[obj.Pkg()]
- if !ok {
- event.Error(ctx, fmt.Sprintf("no package for obj %s: %v", obj, obj.Pkg()), err)
- continue
- }
- qualifiedObjs = append(qualifiedObjs, qualifiedObject{
- obj: obj,
- pkg: pkg,
- sourcePkg: searchpkg,
- node: path[0],
- })
- seenObjs[obj] = true
-
- // If the qualified object is in another file (or more likely, another
- // package), it's possible that there is another copy of it in a package
- // that we haven't searched, e.g. a test variant. See golang/go#47564.
- //
- // In order to be sure we've considered all packages, call
- // qualifiedObjsAtLocation recursively for all locations we encounter. We
- // could probably be more precise here, only continuing the search if obj
- // is in another package, but this should be good enough to find all
- // uses.
-
- pos := obj.Pos()
- var uri span.URI
- offset := -1
- for _, pgf := range pkg.CompiledGoFiles() {
- if pgf.Tok.Base() <= int(pos) && int(pos) <= pgf.Tok.Base()+pgf.Tok.Size() {
- var err error
- offset, err = Offset(pgf.Tok, pos)
- if err != nil {
- return nil, err
- }
- uri = pgf.URI
- }
- }
- if offset >= 0 {
- otherObjs, err := qualifiedObjsAtLocation(ctx, s, objSearchKey{uri, offset}, seen)
- if err != nil {
- return nil, err
- }
- for _, other := range otherObjs {
- if !seenObjs[other.obj] {
- qualifiedObjs = append(qualifiedObjs, other)
- seenObjs[other.obj] = true
- }
- }
- } else {
- return nil, fmt.Errorf("missing file for position of %q in %q", obj.Name(), obj.Pkg().Name())
- }
- }
- }
- // Return an error if no objects were found since callers will assume that
- // the slice has at least 1 element.
- if len(qualifiedObjs) == 0 {
- return nil, errNoObjectFound
- }
- return qualifiedObjs, nil
-}
-
-// 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/internal/lsp/source/known_packages.go b/internal/lsp/source/known_packages.go
deleted file mode 100644
index 49ede162b..000000000
--- a/internal/lsp/source/known_packages.go
+++ /dev/null
@@ -1,118 +0,0 @@
-// 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 source
-
-import (
- "context"
- "sort"
- "strings"
- "sync"
- "time"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/imports"
- errors "golang.org/x/xerrors"
-)
-
-// KnownPackages returns a list of all known packages
-// in the package graph that could potentially be imported
-// by the given file.
-func KnownPackages(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle) ([]string, error) {
- pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
- if err != nil {
- return nil, errors.Errorf("GetParsedFile: %w", err)
- }
- alreadyImported := map[string]struct{}{}
- for _, imp := range pgf.File.Imports {
- alreadyImported[imp.Path.Value] = struct{}{}
- }
- pkgs, err := snapshot.CachedImportPaths(ctx)
- if err != nil {
- return nil, err
- }
- var (
- seen = make(map[string]struct{})
- paths []string
- )
- for path, knownPkg := range pkgs {
- gofiles := knownPkg.CompiledGoFiles()
- if len(gofiles) == 0 || gofiles[0].File.Name == nil {
- continue
- }
- pkgName := gofiles[0].File.Name.Name
- // package main cannot be imported
- if pkgName == "main" {
- continue
- }
- // test packages cannot be imported
- if knownPkg.ForTest() != "" {
- continue
- }
- // no need to import what the file already imports
- if _, ok := alreadyImported[path]; ok {
- continue
- }
- // snapshot.KnownPackages could have multiple versions of a pkg
- if _, ok := seen[path]; ok {
- continue
- }
- seen[path] = struct{}{}
- // make sure internal packages are importable by the file
- if !IsValidImport(pkg.PkgPath(), path) {
- continue
- }
- // naive check on cyclical imports
- if isDirectlyCyclical(pkg, knownPkg) {
- continue
- }
- paths = append(paths, path)
- seen[path] = struct{}{}
- }
- err = snapshot.RunProcessEnvFunc(ctx, func(o *imports.Options) error {
- var mu sync.Mutex
- ctx, cancel := context.WithTimeout(ctx, time.Millisecond*80)
- defer cancel()
- return imports.GetAllCandidates(ctx, func(ifix imports.ImportFix) {
- mu.Lock()
- defer mu.Unlock()
- if _, ok := seen[ifix.StmtInfo.ImportPath]; ok {
- return
- }
- paths = append(paths, ifix.StmtInfo.ImportPath)
- }, "", pgf.URI.Filename(), pkg.GetTypes().Name(), o.Env)
- })
- if err != nil {
- // if an error occurred, we stil have a decent list we can
- // show to the user through snapshot.CachedImportPaths
- event.Error(ctx, "imports.GetAllCandidates", err)
- }
- sort.Slice(paths, func(i, j int) bool {
- importI, importJ := paths[i], paths[j]
- iHasDot := strings.Contains(importI, ".")
- jHasDot := strings.Contains(importJ, ".")
- if iHasDot && !jHasDot {
- return false
- }
- if jHasDot && !iHasDot {
- return true
- }
- 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.
-func isDirectlyCyclical(pkg, imported Package) bool {
- for _, imp := range imported.Imports() {
- if imp.PkgPath() == pkg.PkgPath() {
- return true
- }
- }
- return false
-}
diff --git a/internal/lsp/source/offset_test.go b/internal/lsp/source/offset_test.go
deleted file mode 100644
index 10076773a..000000000
--- a/internal/lsp/source/offset_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// 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 source_test
-
-import (
- "go/token"
- "go/types"
- "testing"
-
- "golang.org/x/tools/go/packages"
-)
-
-// This test reports any unexpected uses of (*go/token.File).Offset within
-// the gopls codebase to ensure that we don't check in more code that is prone
-// to panicking. All calls to (*go/token.File).Offset should be replaced with
-// calls to source.Offset.
-func TestTokenOffset(t *testing.T) {
- fset := token.NewFileSet()
- pkgs, err := packages.Load(&packages.Config{
- Fset: fset,
- Mode: packages.NeedName | packages.NeedModule | packages.NeedCompiledGoFiles | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps,
- }, "go/token", "golang.org/x/tools/internal/lsp/...", "golang.org/x/tools/gopls/...")
- if err != nil {
- t.Fatal(err)
- }
- var tokPkg *packages.Package
- for _, pkg := range pkgs {
- if pkg.PkgPath == "go/token" {
- tokPkg = pkg
- break
- }
- }
- typname, ok := tokPkg.Types.Scope().Lookup("File").(*types.TypeName)
- if !ok {
- t.Fatal("expected go/token.File typename, got none")
- }
- named, ok := typname.Type().(*types.Named)
- if !ok {
- t.Fatalf("expected named type, got %T", typname.Type)
- }
- var offset *types.Func
- for i := 0; i < named.NumMethods(); i++ {
- meth := named.Method(i)
- if meth.Name() == "Offset" {
- offset = meth
- break
- }
- }
- for _, pkg := range pkgs {
- for ident, obj := range pkg.TypesInfo.Uses {
- if ident.Name != "Offset" {
- continue
- }
- if pkg.PkgPath == "go/token" {
- continue
- }
- if !types.Identical(offset.Type(), obj.Type()) {
- continue
- }
- // The only permitted use is in golang.org/x/tools/internal/lsp/source.Offset,
- // so check the enclosing function.
- sourceOffset := pkg.Types.Scope().Lookup("Offset").(*types.Func)
- if sourceOffset.Pos() <= ident.Pos() && ident.Pos() <= sourceOffset.Scope().End() {
- continue // accepted usage
- }
- t.Errorf(`%s: Unexpected use of (*go/token.File).Offset. Please use golang.org/x/tools/internal/lsp/source.Offset instead.`, fset.Position(ident.Pos()))
- }
- }
-}
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
deleted file mode 100644
index 8e262c63b..000000000
--- a/internal/lsp/source/options.go
+++ /dev/null
@@ -1,1449 +0,0 @@
-// 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 source
-
-import (
- "context"
- "fmt"
- "io"
- "path/filepath"
- "regexp"
- "strings"
- "sync"
- "time"
-
- "golang.org/x/tools/go/analysis"
- "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/errorsas"
- "golang.org/x/tools/go/analysis/passes/fieldalignment"
- "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/sortslice"
- "golang.org/x/tools/go/analysis/passes/stdmethods"
- "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/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/go/packages"
- "golang.org/x/tools/internal/lsp/analysis/fillreturns"
- "golang.org/x/tools/internal/lsp/analysis/fillstruct"
- "golang.org/x/tools/internal/lsp/analysis/infertypeargs"
- "golang.org/x/tools/internal/lsp/analysis/nonewvars"
- "golang.org/x/tools/internal/lsp/analysis/noresultvalues"
- "golang.org/x/tools/internal/lsp/analysis/simplifycompositelit"
- "golang.org/x/tools/internal/lsp/analysis/simplifyrange"
- "golang.org/x/tools/internal/lsp/analysis/simplifyslice"
- "golang.org/x/tools/internal/lsp/analysis/stubmethods"
- "golang.org/x/tools/internal/lsp/analysis/undeclaredname"
- "golang.org/x/tools/internal/lsp/analysis/unusedparams"
- "golang.org/x/tools/internal/lsp/analysis/useany"
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/diff/myers"
- "golang.org/x/tools/internal/lsp/protocol"
- errors "golang.org/x/xerrors"
-)
-
-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.).
-func DefaultOptions() *Options {
- optionsOnce.Do(func() {
- var commands []string
- for _, c := range command.Commands {
- commands = append(commands, c.ID())
- }
- 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[FileKind]map[protocol.CodeActionKind]bool{
- Go: {
- protocol.SourceFixAll: true,
- protocol.SourceOrganizeImports: true,
- protocol.QuickFix: true,
- protocol.RefactorRewrite: true,
- protocol.RefactorExtract: true,
- },
- Mod: {
- protocol.SourceOrganizeImports: true,
- protocol.QuickFix: true,
- },
- Work: {},
- Sum: {},
- Tmpl: {},
- },
- SupportedCommands: commands,
- },
- UserOptions: UserOptions{
- BuildOptions: BuildOptions{
- ExpandWorkspaceToModule: true,
- ExperimentalPackageCacheKey: true,
- MemoryMode: ModeNormal,
- DirectoryFilters: []string{"-node_modules"},
- TemplateExtensions: []string{},
- },
- UIOptions: UIOptions{
- DiagnosticOptions: DiagnosticOptions{
- DiagnosticsDelay: 250 * time.Millisecond,
- Annotations: map[Annotation]bool{
- Bounds: true,
- Escape: true,
- Inline: true,
- Nil: true,
- },
- },
- DocumentationOptions: DocumentationOptions{
- HoverKind: FullDocumentation,
- LinkTarget: "pkg.go.dev",
- LinksInHover: true,
- },
- NavigationOptions: NavigationOptions{
- ImportShortcut: Both,
- SymbolMatcher: SymbolFastFuzzy,
- SymbolStyle: DynamicSymbols,
- },
- CompletionOptions: CompletionOptions{
- Matcher: Fuzzy,
- CompletionBudget: 100 * time.Millisecond,
- ExperimentalPostfixCompletions: true,
- },
- Codelenses: map[string]bool{
- string(command.Generate): true,
- string(command.RegenerateCgo): true,
- string(command.Tidy): true,
- string(command.GCDetails): false,
- string(command.UpgradeDependency): true,
- string(command.Vendor): true,
- },
- },
- },
- InternalOptions: InternalOptions{
- LiteralCompletions: true,
- TempModfile: true,
- CompleteUnimported: true,
- CompletionDocumentation: true,
- DeepCompletion: true,
- },
- Hooks: Hooks{
- ComputeEdits: myers.ComputeEdits,
- URLRegexp: urlRegexp(),
- DefaultAnalyzers: defaultAnalyzers(),
- TypeErrorAnalyzers: typeErrorAnalyzers(),
- ConvenienceAnalyzers: convenienceAnalyzers(),
- StaticcheckAnalyzers: map[string]*Analyzer{},
- GoDiff: true,
- },
- }
- })
- return defaultOptions
-}
-
-// Options holds various configuration that affects Gopls execution, organized
-// by the nature or origin of the settings.
-type Options struct {
- ClientOptions
- ServerOptions
- UserOptions
- InternalOptions
- Hooks
-}
-
-// ClientOptions holds LSP-specific configuration that is provided by the
-// client.
-type ClientOptions struct {
- InsertTextFormat protocol.InsertTextFormat
- ConfigurationSupported bool
- DynamicConfigurationSupported bool
- DynamicRegistrationSemanticTokensSupported bool
- DynamicWatchedFilesSupported bool
- PreferredContentFormat protocol.MarkupKind
- LineFoldingOnly bool
- HierarchicalDocumentSymbolSupport bool
- SemanticTypes []string
- SemanticMods []string
- RelatedInformationSupported bool
- CompletionTags bool
- CompletionDeprecated bool
-}
-
-// ServerOptions holds LSP-specific configuration that is provided by the
-// server.
-type ServerOptions struct {
- SupportedCodeActions map[FileKind]map[protocol.CodeActionKind]bool
- SupportedCommands []string
-}
-
-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.
- //
- // Examples:
- //
- // Exclude node_modules: `-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 treateed
- // as template files. (The extension
- // is the part of the file name after the final dot.)
- TemplateExtensions []string
-
- // MemoryMode controls the tradeoff `gopls` makes between memory usage and
- // correctness.
- //
- // Values other than `Normal` are untested and may break in surprising ways.
- MemoryMode MemoryMode `status:"experimental"`
-
- // ExpandWorkspaceToModule instructs `gopls` to adjust the scope of the
- // workspace to find the best available module root. `gopls` first looks for
- // a go.mod file in any parent directory of the workspace folder, expanding
- // the scope to that directory if it exists. If no viable parent directory is
- // found, gopls will check if there is exactly one child directory containing
- // a go.mod file, narrowing the scope to that directory if it exists.
- ExpandWorkspaceToModule bool `status:"experimental"`
-
- // ExperimentalWorkspaceModule opts a user into the experimental support
- // for multi-module workspaces.
- ExperimentalWorkspaceModule bool `status:"experimental"`
-
- // ExperimentalPackageCacheKey controls whether to use a coarser cache key
- // for package type information to increase cache hits. This setting removes
- // the user's environment, build flags, and working directory from the cache
- // key, which should be a safe change as all relevant inputs into the type
- // checking pass are already hashed into the key. This is temporarily guarded
- // by an experiment because caching behavior is subtle and difficult to
- // comprehensively test.
- ExperimentalPackageCacheKey bool `status:"experimental"`
-
- // AllowModfileModifications disables -mod=readonly, allowing imports from
- // out-of-scope modules. This option will eventually be removed.
- AllowModfileModifications 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"`
-
- // ExperimentalUseInvalidMetadata enables gopls to fall back on outdated
- // package metadata to provide editor features if the go command fails to
- // load packages for some reason (like an invalid go.mod file). This will
- // eventually be the default behavior, and this setting will be removed.
- ExperimentalUseInvalidMetadata bool `status:"experimental"`
-}
-
-type UIOptions struct {
- DocumentationOptions
- CompletionOptions
- NavigationOptions
- DiagnosticOptions
-
- // Codelenses overrides the enabled/disabled state of code lenses. See the
- // "Code Lenses" section of the
- // [Settings page](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#code-lenses)
- // for the list of supported lenses.
- //
- // 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[string]bool
-
- // SemanticTokens controls whether the LSP server will send
- // semantic tokens to the client.
- SemanticTokens bool `status:"experimental"`
-}
-
-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"`
-}
-
-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 controls where documentation links go.
- // It might be one of:
- //
- // * `"godoc.org"`
- // * `"pkg.go.dev"`
- //
- // If company chooses to use its own `godoc.org`, its address can be used as well.
- LinkTarget string
-
- // LinksInHover toggles the presence of links to documentation in hover.
- LinksInHover bool
-}
-
-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.
- Local string
-
- // Gofumpt indicates if we should run gofumpt formatting.
- Gofumpt bool
-}
-
-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
- // [here](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md).
- //
- // Example Usage:
- //
- // ```json5
- // ...
- // "analyses": {
- // "unreachable": false, // Disable the unreachable analyzer.
- // "unusedparams": true // Enable the unusedparams analyzer.
- // }
- // ...
- // ```
- Analyses map[string]bool
-
- // Staticcheck enables additional analyses from staticcheck.io.
- 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"`
-
- // 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"`
-
- // ExperimentalWatchedFileDelay controls the amount of time that gopls waits
- // for additional workspace/didChangeWatchedFiles notifications to arrive,
- // before processing all such notifications in a single batch. This is
- // intended for use by LSP clients that don't support their own batching of
- // file system notifications.
- //
- // This option must be set to a valid duration string, for example `"100ms"`.
- ExperimentalWatchedFileDelay time.Duration `status:"experimental"`
-}
-
-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"`
-}
-
-// UserOptions holds custom Gopls configuration (not part of the LSP) that is
-// modified by the client.
-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]
- }
-}
-
-// Hooks contains configuration that is provided to the Gopls command by the
-// main package.
-type Hooks struct {
- LicensesText string
- GoDiff bool
- ComputeEdits diff.ComputeEdits
- URLRegexp *regexp.Regexp
-
- // GofumptFormat allows the gopls module to wire-in a call to
- // gofumpt/format.Source. langVersion and modulePath are used for some
- // Gofumpt formatting rules -- see the Gofumpt documentation for details.
- GofumptFormat func(ctx context.Context, langVersion, modulePath string, src []byte) ([]byte, error)
-
- DefaultAnalyzers map[string]*Analyzer
- TypeErrorAnalyzers map[string]*Analyzer
- ConvenienceAnalyzers map[string]*Analyzer
- StaticcheckAnalyzers map[string]*Analyzer
-
- // Govulncheck is the implementation of the Govulncheck gopls command.
- Govulncheck func(context.Context, *packages.Config, command.VulncheckArgs) (command.VulncheckResult, error)
-}
-
-// 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.
-type InternalOptions struct {
- // LiteralCompletions controls whether literal candidates such as
- // "&someStruct{}" are offered. Tests disable this flag to simplify
- // their expected values.
- LiteralCompletions bool
-
- // 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
-
- // TempModfile controls the use of the -modfile flag in Go 1.14.
- TempModfile bool
-}
-
-type ImportShortcut string
-
-const (
- Both ImportShortcut = "Both"
- Link ImportShortcut = "Link"
- Definition ImportShortcut = "Definition"
-)
-
-func (s ImportShortcut) ShowLinks() bool {
- return s == Both || s == Link
-}
-
-func (s ImportShortcut) ShowDefinition() bool {
- return s == Both || s == Definition
-}
-
-type Matcher string
-
-const (
- Fuzzy Matcher = "Fuzzy"
- CaseInsensitive Matcher = "CaseInsensitive"
- CaseSensitive Matcher = "CaseSensitive"
-)
-
-type SymbolMatcher string
-
-const (
- SymbolFuzzy SymbolMatcher = "Fuzzy"
- SymbolFastFuzzy SymbolMatcher = "FastFuzzy"
- SymbolCaseInsensitive SymbolMatcher = "CaseInsensitive"
- SymbolCaseSensitive SymbolMatcher = "CaseSensitive"
-)
-
-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"
-)
-
-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 MemoryMode string
-
-const (
- ModeNormal MemoryMode = "Normal"
- // In DegradeClosed mode, `gopls` will collect less information about
- // packages without open files. As a result, features like Find
- // References and Rename will miss results in such packages.
- ModeDegradeClosed MemoryMode = "DegradeClosed"
-)
-
-type OptionResults []OptionResult
-
-type OptionResult struct {
- Name string
- Value interface{}
- Error error
-
- State OptionState
- Replacement string
-}
-
-type OptionState int
-
-const (
- OptionHandled = OptionState(iota)
- OptionDeprecated
- OptionUnexpected
-)
-
-type LinkTarget string
-
-func SetOptions(options *Options, opts interface{}) OptionResults {
- var results OptionResults
- switch opts := opts.(type) {
- case nil:
- case map[string]interface{}:
- // If the user's settings contains "allExperiments", set that first,
- // and then let them override individual settings independently.
- var enableExperiments bool
- for name, value := range opts {
- if b, ok := value.(bool); name == "allExperiments" && ok && b {
- enableExperiments = true
- options.EnableAllExperiments()
- }
- }
- seen := map[string]struct{}{}
- for name, value := range opts {
- results = append(results, options.set(name, value, seen))
- }
- // Finally, enable any experimental features that are specified in
- // maps, which allows users to individually toggle them on or off.
- if enableExperiments {
- options.enableAllExperimentMaps()
- }
- default:
- results = append(results, OptionResult{
- Value: opts,
- Error: errors.Errorf("Invalid options type %T", opts),
- })
- }
- return results
-}
-
-func (o *Options) ForClientCapabilities(caps protocol.ClientCapabilities) {
- // Check if the client supports snippets in completion items.
- if c := caps.TextDocument.Completion; c.CompletionItem.SnippetSupport {
- o.InsertTextFormat = protocol.SnippetTextFormat
- }
- // 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
-
- // Check which types of content format are supported by this client.
- if hover := caps.TextDocument.Hover; len(hover.ContentFormat) > 0 {
- o.PreferredContentFormat = hover.ContentFormat[0]
- }
- // Check if the client supports only line folding.
- fr := caps.TextDocument.FoldingRange
- o.LineFoldingOnly = fr.LineFoldingOnly
- // Check if the client supports hierarchical document symbols.
- o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport
- // Check if the client supports 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 incliudes tags (preferred) or deprecation
- if caps.TextDocument.Completion.CompletionItem.TagSupport.ValueSet != nil {
- o.CompletionTags = true
- } else if caps.TextDocument.Completion.CompletionItem.DeprecatedSupport {
- o.CompletionDeprecated = true
- }
-}
-
-func (o *Options) Clone() *Options {
- result := &Options{
- ClientOptions: o.ClientOptions,
- InternalOptions: o.InternalOptions,
- Hooks: Hooks{
- GoDiff: o.GoDiff,
- ComputeEdits: o.ComputeEdits,
- GofumptFormat: o.GofumptFormat,
- URLRegexp: o.URLRegexp,
- Govulncheck: o.Govulncheck,
- },
- ServerOptions: o.ServerOptions,
- UserOptions: o.UserOptions,
- }
- // Fully clone any slice or map fields. Only Hooks, ExperimentalOptions,
- // and UserOptions can be modified.
- copyStringMap := func(src map[string]bool) map[string]bool {
- dst := make(map[string]bool)
- for k, v := range src {
- dst[k] = v
- }
- return dst
- }
- result.Analyses = copyStringMap(o.Analyses)
- result.Codelenses = copyStringMap(o.Codelenses)
-
- copySlice := func(src []string) []string {
- dst := make([]string, len(src))
- copy(dst, src)
- return dst
- }
- result.SetEnvSlice(o.EnvSlice())
- result.BuildFlags = copySlice(o.BuildFlags)
- result.DirectoryFilters = copySlice(o.DirectoryFilters)
-
- copyAnalyzerMap := func(src map[string]*Analyzer) map[string]*Analyzer {
- dst := make(map[string]*Analyzer)
- for k, v := range src {
- dst[k] = v
- }
- return dst
- }
- result.DefaultAnalyzers = copyAnalyzerMap(o.DefaultAnalyzers)
- result.TypeErrorAnalyzers = copyAnalyzerMap(o.TypeErrorAnalyzers)
- result.ConvenienceAnalyzers = copyAnalyzerMap(o.ConvenienceAnalyzers)
- result.StaticcheckAnalyzers = copyAnalyzerMap(o.StaticcheckAnalyzers)
- return result
-}
-
-func (o *Options) AddStaticcheckAnalyzer(a *analysis.Analyzer, enabled bool, severity protocol.DiagnosticSeverity) {
- o.StaticcheckAnalyzers[a.Name] = &Analyzer{
- Analyzer: a,
- Enabled: enabled,
- Severity: severity,
- }
-}
-
-// EnableAllExperiments turns on all of the experimental "off-by-default"
-// features offered by gopls. Any experimental features specified in maps
-// should be enabled in enableAllExperimentMaps.
-func (o *Options) EnableAllExperiments() {
- o.SemanticTokens = true
- o.ExperimentalPostfixCompletions = true
- o.ExperimentalUseInvalidMetadata = true
- o.ExperimentalWatchedFileDelay = 50 * time.Millisecond
- o.SymbolMatcher = SymbolFastFuzzy
-}
-
-func (o *Options) enableAllExperimentMaps() {
- if _, ok := o.Codelenses[string(command.GCDetails)]; !ok {
- o.Codelenses[string(command.GCDetails)] = true
- }
- if _, ok := o.Analyses[unusedparams.Analyzer.Name]; !ok {
- o.Analyses[unusedparams.Analyzer.Name] = true
- }
-}
-
-func (o *Options) set(name string, value interface{}, seen map[string]struct{}) OptionResult {
- // Flatten the name in case we get options with a hierarchy.
- split := strings.Split(name, ".")
- name = split[len(split)-1]
-
- result := OptionResult{Name: name, Value: value}
- if _, ok := seen[name]; ok {
- result.errorf("duplicate configuration for %s", name)
- }
- seen[name] = struct{}{}
-
- switch name {
- case "env":
- menv, ok := value.(map[string]interface{})
- if !ok {
- result.errorf("invalid type %T, expect map", value)
- break
- }
- if o.Env == nil {
- o.Env = make(map[string]string)
- }
- for k, v := range menv {
- o.Env[k] = fmt.Sprint(v)
- }
-
- case "buildFlags":
- iflags, ok := value.([]interface{})
- if !ok {
- result.errorf("invalid type %T, expect list", value)
- break
- }
- flags := make([]string, 0, len(iflags))
- for _, flag := range iflags {
- flags = append(flags, fmt.Sprintf("%s", flag))
- }
- o.BuildFlags = flags
- case "directoryFilters":
- ifilters, ok := value.([]interface{})
- if !ok {
- result.errorf("invalid type %T, expect list", value)
- break
- }
- var filters []string
- for _, ifilter := range ifilters {
- filter := fmt.Sprint(ifilter)
- if filter == "" || (filter[0] != '+' && filter[0] != '-') {
- result.errorf("invalid filter %q, must start with + or -", filter)
- return result
- }
- filters = append(filters, strings.TrimRight(filepath.FromSlash(filter), "/"))
- }
- o.DirectoryFilters = filters
- case "memoryMode":
- if s, ok := result.asOneOf(
- string(ModeNormal),
- string(ModeDegradeClosed),
- ); ok {
- o.MemoryMode = MemoryMode(s)
- }
- case "completionDocumentation":
- result.setBool(&o.CompletionDocumentation)
- case "usePlaceholders":
- result.setBool(&o.UsePlaceholders)
- case "deepCompletion":
- result.setBool(&o.DeepCompletion)
- case "completeUnimported":
- result.setBool(&o.CompleteUnimported)
- case "completionBudget":
- result.setDuration(&o.CompletionBudget)
- case "matcher":
- if s, ok := result.asOneOf(
- string(Fuzzy),
- string(CaseSensitive),
- string(CaseInsensitive),
- ); ok {
- o.Matcher = Matcher(s)
- }
-
- case "symbolMatcher":
- if s, ok := result.asOneOf(
- string(SymbolFuzzy),
- string(SymbolFastFuzzy),
- string(SymbolCaseInsensitive),
- string(SymbolCaseSensitive),
- ); ok {
- o.SymbolMatcher = SymbolMatcher(s)
- }
-
- case "symbolStyle":
- if s, ok := result.asOneOf(
- string(FullyQualifiedSymbols),
- string(PackageQualifiedSymbols),
- string(DynamicSymbols),
- ); ok {
- o.SymbolStyle = SymbolStyle(s)
- }
-
- case "hoverKind":
- if s, ok := result.asOneOf(
- string(NoDocumentation),
- string(SingleLine),
- string(SynopsisDocumentation),
- string(FullDocumentation),
- string(Structured),
- ); ok {
- o.HoverKind = HoverKind(s)
- }
-
- case "linkTarget":
- result.setString(&o.LinkTarget)
-
- case "linksInHover":
- result.setBool(&o.LinksInHover)
-
- case "importShortcut":
- if s, ok := result.asOneOf(string(Both), string(Link), string(Definition)); ok {
- o.ImportShortcut = ImportShortcut(s)
- }
-
- case "analyses":
- result.setBoolMap(&o.Analyses)
-
- case "annotations":
- result.setAnnotationMap(&o.Annotations)
-
- case "codelenses", "codelens":
- var lensOverrides map[string]bool
- result.setBoolMap(&lensOverrides)
- if result.Error == nil {
- if o.Codelenses == nil {
- o.Codelenses = make(map[string]bool)
- }
- for lens, enabled := range lensOverrides {
- o.Codelenses[lens] = enabled
- }
- }
-
- // codelens is deprecated, but still works for now.
- // TODO(rstambler): Remove this for the gopls/v0.7.0 release.
- if name == "codelens" {
- result.State = OptionDeprecated
- result.Replacement = "codelenses"
- }
-
- case "staticcheck":
- result.setBool(&o.Staticcheck)
-
- case "local":
- result.setString(&o.Local)
-
- case "verboseOutput":
- result.setBool(&o.VerboseOutput)
-
- case "verboseWorkDoneProgress":
- result.setBool(&o.VerboseWorkDoneProgress)
-
- case "tempModfile":
- result.setBool(&o.TempModfile)
-
- case "gofumpt":
- result.setBool(&o.Gofumpt)
-
- case "semanticTokens":
- result.setBool(&o.SemanticTokens)
-
- case "expandWorkspaceToModule":
- result.setBool(&o.ExpandWorkspaceToModule)
-
- case "experimentalPostfixCompletions":
- result.setBool(&o.ExperimentalPostfixCompletions)
-
- case "experimentalWorkspaceModule":
- result.setBool(&o.ExperimentalWorkspaceModule)
-
- case "experimentalTemplateSupport": // remove after June 2022
- result.State = OptionDeprecated
-
- case "templateExtensions":
- if iexts, ok := value.([]interface{}); ok {
- ans := []string{}
- for _, x := range iexts {
- ans = append(ans, fmt.Sprint(x))
- }
- o.TemplateExtensions = ans
- break
- }
- if value == nil {
- o.TemplateExtensions = nil
- break
- }
- result.errorf(fmt.Sprintf("unexpected type %T not []string", value))
- case "experimentalDiagnosticsDelay", "diagnosticsDelay":
- if name == "experimentalDiagnosticsDelay" {
- result.State = OptionDeprecated
- result.Replacement = "diagnosticsDelay"
- }
- result.setDuration(&o.DiagnosticsDelay)
-
- case "experimentalWatchedFileDelay":
- result.setDuration(&o.ExperimentalWatchedFileDelay)
-
- case "experimentalPackageCacheKey":
- result.setBool(&o.ExperimentalPackageCacheKey)
-
- case "allowModfileModifications":
- result.setBool(&o.AllowModfileModifications)
-
- case "allowImplicitNetworkAccess":
- result.setBool(&o.AllowImplicitNetworkAccess)
-
- case "experimentalUseInvalidMetadata":
- result.setBool(&o.ExperimentalUseInvalidMetadata)
-
- case "allExperiments":
- // This setting should be handled before all of the other options are
- // processed, so do nothing here.
-
- // Replaced settings.
- case "experimentalDisabledAnalyses":
- result.State = OptionDeprecated
- result.Replacement = "analyses"
-
- case "disableDeepCompletion":
- result.State = OptionDeprecated
- result.Replacement = "deepCompletion"
-
- case "disableFuzzyMatching":
- result.State = OptionDeprecated
- result.Replacement = "fuzzyMatching"
-
- case "wantCompletionDocumentation":
- result.State = OptionDeprecated
- result.Replacement = "completionDocumentation"
-
- case "wantUnimportedCompletions":
- result.State = OptionDeprecated
- result.Replacement = "completeUnimported"
-
- case "fuzzyMatching":
- result.State = OptionDeprecated
- result.Replacement = "matcher"
-
- case "caseSensitiveCompletion":
- result.State = OptionDeprecated
- result.Replacement = "matcher"
-
- // Deprecated settings.
- case "wantSuggestedFixes":
- result.State = OptionDeprecated
-
- case "noIncrementalSync":
- result.State = OptionDeprecated
-
- case "watchFileChanges":
- result.State = OptionDeprecated
-
- case "go-diff":
- result.State = OptionDeprecated
-
- default:
- result.State = OptionUnexpected
- }
- return result
-}
-
-func (r *OptionResult) errorf(msg string, values ...interface{}) {
- prefix := fmt.Sprintf("parsing setting %q: ", r.Name)
- r.Error = errors.Errorf(prefix+msg, values...)
-}
-
-func (r *OptionResult) asBool() (bool, bool) {
- b, ok := r.Value.(bool)
- if !ok {
- r.errorf("invalid type %T, expect bool", r.Value)
- return false, false
- }
- return b, true
-}
-
-func (r *OptionResult) setBool(b *bool) {
- if v, ok := r.asBool(); ok {
- *b = v
- }
-}
-
-func (r *OptionResult) setDuration(d *time.Duration) {
- if v, ok := r.asString(); ok {
- parsed, err := time.ParseDuration(v)
- if err != nil {
- r.errorf("failed to parse duration %q: %v", v, err)
- return
- }
- *d = parsed
- }
-}
-
-func (r *OptionResult) setBoolMap(bm *map[string]bool) {
- m := r.asBoolMap()
- *bm = m
-}
-
-func (r *OptionResult) setAnnotationMap(bm *map[Annotation]bool) {
- all := r.asBoolMap()
- if all == nil {
- return
- }
- // Default to everything enabled by default.
- m := make(map[Annotation]bool)
- for k, enabled := range all {
- a, err := asOneOf(
- k,
- string(Nil),
- string(Escape),
- string(Inline),
- string(Bounds),
- )
- if err != nil {
- // In case of an error, process any legacy values.
- switch k {
- case "noEscape":
- m[Escape] = false
- r.errorf(`"noEscape" is deprecated, set "Escape: false" instead`)
- case "noNilcheck":
- m[Nil] = false
- r.errorf(`"noNilcheck" is deprecated, set "Nil: false" instead`)
- case "noInline":
- m[Inline] = false
- r.errorf(`"noInline" is deprecated, set "Inline: false" instead`)
- case "noBounds":
- m[Bounds] = false
- r.errorf(`"noBounds" is deprecated, set "Bounds: false" instead`)
- default:
- r.errorf(err.Error())
- }
- continue
- }
- m[Annotation(a)] = enabled
- }
- *bm = m
-}
-
-func (r *OptionResult) asBoolMap() map[string]bool {
- all, ok := r.Value.(map[string]interface{})
- if !ok {
- r.errorf("invalid type %T for map[string]bool option", r.Value)
- return nil
- }
- m := make(map[string]bool)
- for a, enabled := range all {
- if enabled, ok := enabled.(bool); ok {
- m[a] = enabled
- } else {
- r.errorf("invalid type %T for map key %q", enabled, a)
- return m
- }
- }
- return m
-}
-
-func (r *OptionResult) asString() (string, bool) {
- b, ok := r.Value.(string)
- if !ok {
- r.errorf("invalid type %T, expect string", r.Value)
- return "", false
- }
- return b, true
-}
-
-func (r *OptionResult) asOneOf(options ...string) (string, bool) {
- s, ok := r.asString()
- if !ok {
- return "", false
- }
- s, err := asOneOf(s, options...)
- if err != nil {
- r.errorf(err.Error())
- }
- return s, err == nil
-}
-
-func asOneOf(str string, options ...string) (string, error) {
- lower := strings.ToLower(str)
- for _, opt := range options {
- if strings.ToLower(opt) == lower {
- return opt, nil
- }
- }
- return "", fmt.Errorf("invalid option %q for enum", str)
-}
-
-func (r *OptionResult) setString(s *string) {
- if v, ok := r.asString(); ok {
- *s = v
- }
-}
-
-// EnabledAnalyzers returns all of the analyzers enabled for the given
-// snapshot.
-func EnabledAnalyzers(snapshot Snapshot) (analyzers []*Analyzer) {
- for _, a := range snapshot.View().Options().DefaultAnalyzers {
- if a.IsEnabled(snapshot.View()) {
- analyzers = append(analyzers, a)
- }
- }
- for _, a := range snapshot.View().Options().TypeErrorAnalyzers {
- if a.IsEnabled(snapshot.View()) {
- analyzers = append(analyzers, a)
- }
- }
- for _, a := range snapshot.View().Options().ConvenienceAnalyzers {
- if a.IsEnabled(snapshot.View()) {
- analyzers = append(analyzers, a)
- }
- }
- for _, a := range snapshot.View().Options().StaticcheckAnalyzers {
- if a.IsEnabled(snapshot.View()) {
- analyzers = append(analyzers, a)
- }
- }
- return analyzers
-}
-
-func typeErrorAnalyzers() map[string]*Analyzer {
- return map[string]*Analyzer{
- fillreturns.Analyzer.Name: {
- Analyzer: fillreturns.Analyzer,
- ActionKind: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
- Enabled: true,
- },
- nonewvars.Analyzer.Name: {
- Analyzer: nonewvars.Analyzer,
- Enabled: true,
- },
- noresultvalues.Analyzer.Name: {
- Analyzer: noresultvalues.Analyzer,
- Enabled: true,
- },
- undeclaredname.Analyzer.Name: {
- Analyzer: undeclaredname.Analyzer,
- Fix: UndeclaredName,
- Enabled: true,
- },
- }
-}
-
-func convenienceAnalyzers() map[string]*Analyzer {
- return map[string]*Analyzer{
- fillstruct.Analyzer.Name: {
- Analyzer: fillstruct.Analyzer,
- Fix: FillStruct,
- Enabled: true,
- ActionKind: []protocol.CodeActionKind{protocol.RefactorRewrite},
- },
- stubmethods.Analyzer.Name: {
- Analyzer: stubmethods.Analyzer,
- ActionKind: []protocol.CodeActionKind{protocol.RefactorRewrite},
- Fix: StubMethods,
- Enabled: true,
- },
- }
-}
-
-func defaultAnalyzers() map[string]*Analyzer {
- return map[string]*Analyzer{
- // The traditional vet suite:
- asmdecl.Analyzer.Name: {Analyzer: asmdecl.Analyzer, Enabled: true},
- assign.Analyzer.Name: {Analyzer: assign.Analyzer, Enabled: true},
- atomic.Analyzer.Name: {Analyzer: atomic.Analyzer, Enabled: true},
- bools.Analyzer.Name: {Analyzer: bools.Analyzer, Enabled: true},
- buildtag.Analyzer.Name: {Analyzer: buildtag.Analyzer, Enabled: true},
- cgocall.Analyzer.Name: {Analyzer: cgocall.Analyzer, Enabled: true},
- composite.Analyzer.Name: {Analyzer: composite.Analyzer, Enabled: true},
- copylock.Analyzer.Name: {Analyzer: copylock.Analyzer, Enabled: true},
- errorsas.Analyzer.Name: {Analyzer: errorsas.Analyzer, Enabled: true},
- httpresponse.Analyzer.Name: {Analyzer: httpresponse.Analyzer, Enabled: true},
- ifaceassert.Analyzer.Name: {Analyzer: ifaceassert.Analyzer, Enabled: true},
- loopclosure.Analyzer.Name: {Analyzer: loopclosure.Analyzer, Enabled: true},
- lostcancel.Analyzer.Name: {Analyzer: lostcancel.Analyzer, Enabled: true},
- nilfunc.Analyzer.Name: {Analyzer: nilfunc.Analyzer, Enabled: true},
- printf.Analyzer.Name: {Analyzer: printf.Analyzer, Enabled: true},
- shift.Analyzer.Name: {Analyzer: shift.Analyzer, Enabled: true},
- stdmethods.Analyzer.Name: {Analyzer: stdmethods.Analyzer, Enabled: true},
- stringintconv.Analyzer.Name: {Analyzer: stringintconv.Analyzer, Enabled: true},
- structtag.Analyzer.Name: {Analyzer: structtag.Analyzer, Enabled: true},
- tests.Analyzer.Name: {Analyzer: tests.Analyzer, Enabled: true},
- unmarshal.Analyzer.Name: {Analyzer: unmarshal.Analyzer, Enabled: true},
- unreachable.Analyzer.Name: {Analyzer: unreachable.Analyzer, Enabled: true},
- unsafeptr.Analyzer.Name: {Analyzer: unsafeptr.Analyzer, Enabled: true},
- unusedresult.Analyzer.Name: {Analyzer: unusedresult.Analyzer, Enabled: true},
-
- // Non-vet analyzers:
- atomicalign.Analyzer.Name: {Analyzer: atomicalign.Analyzer, Enabled: true},
- deepequalerrors.Analyzer.Name: {Analyzer: deepequalerrors.Analyzer, Enabled: true},
- fieldalignment.Analyzer.Name: {Analyzer: fieldalignment.Analyzer, Enabled: false},
- nilness.Analyzer.Name: {Analyzer: nilness.Analyzer, Enabled: false},
- shadow.Analyzer.Name: {Analyzer: shadow.Analyzer, Enabled: false},
- sortslice.Analyzer.Name: {Analyzer: sortslice.Analyzer, Enabled: true},
- testinggoroutine.Analyzer.Name: {Analyzer: testinggoroutine.Analyzer, Enabled: true},
- unusedparams.Analyzer.Name: {Analyzer: unusedparams.Analyzer, Enabled: false},
- unusedwrite.Analyzer.Name: {Analyzer: unusedwrite.Analyzer, Enabled: false},
- useany.Analyzer.Name: {Analyzer: useany.Analyzer, Enabled: false},
- infertypeargs.Analyzer.Name: {Analyzer: infertypeargs.Analyzer, Enabled: true},
-
- // gofmt -s suite:
- simplifycompositelit.Analyzer.Name: {
- Analyzer: simplifycompositelit.Analyzer,
- Enabled: true,
- ActionKind: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
- },
- simplifyrange.Analyzer.Name: {
- Analyzer: simplifyrange.Analyzer,
- Enabled: true,
- ActionKind: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
- },
- simplifyslice.Analyzer.Name: {
- Analyzer: simplifyslice.Analyzer,
- Enabled: true,
- ActionKind: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
- },
- }
-}
-
-func urlRegexp() *regexp.Regexp {
- // Ensure links are matched as full words, not anywhere.
- re := regexp.MustCompile(`\b(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?\b`)
- re.Longest()
- return re
-}
-
-type APIJSON struct {
- Options map[string][]*OptionJSON
- Commands []*CommandJSON
- Lenses []*LensJSON
- Analyzers []*AnalyzerJSON
-}
-
-type OptionJSON struct {
- Name string
- Type string
- Doc string
- EnumKeys EnumKeys
- EnumValues []EnumValue
- Default string
- Status string
- Hierarchy string
-}
-
-func (o *OptionJSON) String() string {
- return o.Name
-}
-
-func (o *OptionJSON) Write(w io.Writer) {
- fmt.Fprintf(w, "**%v** *%v*\n\n", o.Name, o.Type)
- writeStatus(w, o.Status)
- enumValues := collectEnums(o)
- fmt.Fprintf(w, "%v%v\nDefault: `%v`.\n\n", o.Doc, enumValues, o.Default)
-}
-
-func writeStatus(section io.Writer, status string) {
- switch status {
- case "":
- case "advanced":
- fmt.Fprint(section, "**This is an advanced setting and should not be configured by most `gopls` users.**\n\n")
- case "debug":
- fmt.Fprint(section, "**This setting is for debugging purposes only.**\n\n")
- case "experimental":
- fmt.Fprint(section, "**This setting is experimental and may be deleted.**\n\n")
- default:
- fmt.Fprintf(section, "**Status: %s.**\n\n", status)
- }
-}
-
-var parBreakRE = regexp.MustCompile("\n{2,}")
-
-func collectEnums(opt *OptionJSON) string {
- var b strings.Builder
- write := func(name, doc string, index, len int) {
- if doc != "" {
- unbroken := parBreakRE.ReplaceAllString(doc, "\\\n")
- fmt.Fprintf(&b, "* %s\n", strings.TrimSpace(unbroken))
- } else {
- fmt.Fprintf(&b, "* `%s`\n", name)
- }
- }
- if len(opt.EnumValues) > 0 && opt.Type == "enum" {
- b.WriteString("\nMust be one of:\n\n")
- for i, val := range opt.EnumValues {
- write(val.Value, val.Doc, i, len(opt.EnumValues))
- }
- } else if len(opt.EnumKeys.Keys) > 0 && shouldShowEnumKeysInSettings(opt.Name) {
- b.WriteString("\nCan contain any of:\n\n")
- for i, val := range opt.EnumKeys.Keys {
- write(val.Name, val.Doc, i, len(opt.EnumKeys.Keys))
- }
- }
- return b.String()
-}
-
-func shouldShowEnumKeysInSettings(name string) bool {
- // Both of these fields have too many possible options to print.
- return !hardcodedEnumKeys(name)
-}
-
-func hardcodedEnumKeys(name string) bool {
- return name == "analyses" || name == "codelenses"
-}
-
-type EnumKeys struct {
- ValueType string
- Keys []EnumKey
-}
-
-type EnumKey struct {
- Name string
- Doc string
- Default string
-}
-
-type EnumValue struct {
- Value string
- Doc string
-}
-
-type CommandJSON struct {
- Command string
- Title string
- Doc string
- ArgDoc string
- ResultDoc string
-}
-
-func (c *CommandJSON) String() string {
- return c.Command
-}
-
-func (c *CommandJSON) Write(w io.Writer) {
- fmt.Fprintf(w, "### **%v**\nIdentifier: `%v`\n\n%v\n\n", c.Title, c.Command, c.Doc)
- if c.ArgDoc != "" {
- fmt.Fprintf(w, "Args:\n\n```\n%s\n```\n\n", c.ArgDoc)
- }
- if c.ResultDoc != "" {
- fmt.Fprintf(w, "Result:\n\n```\n%s\n```\n\n", c.ResultDoc)
- }
-}
-
-type LensJSON struct {
- Lens string
- Title string
- Doc string
-}
-
-func (l *LensJSON) String() string {
- return l.Title
-}
-
-func (l *LensJSON) Write(w io.Writer) {
- fmt.Fprintf(w, "%s (%s): %s", l.Title, l.Lens, l.Doc)
-}
-
-type AnalyzerJSON struct {
- Name string
- Doc string
- Default bool
-}
-
-func (a *AnalyzerJSON) String() string {
- return a.Name
-}
-
-func (a *AnalyzerJSON) Write(w io.Writer) {
- fmt.Fprintf(w, "%s (%s): %v", a.Name, a.Doc, a.Default)
-}
diff --git a/internal/lsp/source/options_test.go b/internal/lsp/source/options_test.go
deleted file mode 100644
index f8260c1dd..000000000
--- a/internal/lsp/source/options_test.go
+++ /dev/null
@@ -1,183 +0,0 @@
-// 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 source
-
-import (
- "testing"
- "time"
-)
-
-func TestSetOption(t *testing.T) {
- tests := []struct {
- name string
- value interface{}
- wantError bool
- check func(Options) bool
- }{
- {
- 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: "staticcheck",
- value: true,
- check: func(o Options) bool { return o.Staticcheck == true },
- },
- {
- name: "codelenses",
- value: map[string]interface{}{"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]interface{}{"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: []interface{}{"-node_modules", "+project_a"},
- check: func(o Options) bool {
- return len(o.DirectoryFilters) == 2
- },
- },
- {
- name: "directoryFilters",
- value: []interface{}{"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]interface{}{
- "Nil": false,
- "noBounds": true,
- },
- wantError: true,
- check: func(o Options) bool {
- return !o.Annotations[Nil] && !o.Annotations[Bounds]
- },
- },
- }
-
- for _, test := range tests {
- var opts Options
- result := opts.set(test.name, test.value, map[string]struct{}{})
- if (result.Error != nil) != test.wantError {
- t.Fatalf("Options.set(%q, %v): result.Error = %v, want error: %t", test.name, test.value, result.Error, test.wantError)
- }
- // 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)
- }
- }
-}
diff --git a/internal/lsp/source/references.go b/internal/lsp/source/references.go
deleted file mode 100644
index 5d3eac337..000000000
--- a/internal/lsp/source/references.go
+++ /dev/null
@@ -1,200 +0,0 @@
-// 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 source
-
-import (
- "context"
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
- "sort"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// ReferenceInfo holds information about reference to an identifier in Go source.
-type ReferenceInfo struct {
- Name string
- MappedRange
- ident *ast.Ident
- obj types.Object
- pkg Package
- isDeclaration bool
-}
-
-// References returns a list of references for a given identifier within the packages
-// containing i.File. Declarations appear first in the result.
-func References(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position, includeDeclaration bool) ([]*ReferenceInfo, error) {
- ctx, done := event.Start(ctx, "source.References")
- defer done()
-
- qualifiedObjs, err := qualifiedObjsAtProtocolPos(ctx, s, f.URI(), pp)
- // Don't return references for builtin types.
- if errors.Is(err, errBuiltin) {
- return nil, nil
- }
- if err != nil {
- return nil, err
- }
-
- refs, err := references(ctx, s, qualifiedObjs, includeDeclaration, true, false)
- if err != nil {
- return nil, err
- }
-
- toSort := refs
- if includeDeclaration {
- toSort = refs[1:]
- }
- sort.Slice(toSort, func(i, j int) bool {
- x := CompareURI(toSort[i].URI(), toSort[j].URI())
- if x == 0 {
- return toSort[i].ident.Pos() < toSort[j].ident.Pos()
- }
- return x < 0
- })
- return refs, nil
-}
-
-// references is a helper function to avoid recomputing qualifiedObjsAtProtocolPos.
-func references(ctx context.Context, snapshot Snapshot, qos []qualifiedObject, includeDeclaration, includeInterfaceRefs, includeEmbeddedRefs bool) ([]*ReferenceInfo, error) {
- var (
- references []*ReferenceInfo
- seen = make(map[token.Pos]bool)
- )
-
- pos := qos[0].obj.Pos()
- if pos == token.NoPos {
- return nil, fmt.Errorf("no position for %s", qos[0].obj)
- }
- filename := snapshot.FileSet().Position(pos).Filename
- pgf, err := qos[0].pkg.File(span.URIFromPath(filename))
- if err != nil {
- return nil, err
- }
- declIdent, err := findIdentifier(ctx, snapshot, qos[0].pkg, pgf, qos[0].obj.Pos())
- if err != nil {
- return nil, err
- }
- // Make sure declaration is the first item in the response.
- if includeDeclaration {
- references = append(references, &ReferenceInfo{
- MappedRange: declIdent.MappedRange,
- Name: qos[0].obj.Name(),
- ident: declIdent.ident,
- obj: qos[0].obj,
- pkg: declIdent.pkg,
- isDeclaration: true,
- })
- }
-
- for _, qo := range qos {
- var searchPkgs []Package
-
- // Only search dependents if the object is exported.
- if qo.obj.Exported() {
- reverseDeps, err := snapshot.GetReverseDependencies(ctx, qo.pkg.ID())
- if err != nil {
- return nil, err
- }
- searchPkgs = append(searchPkgs, reverseDeps...)
- }
- // Add the package in which the identifier is declared.
- searchPkgs = append(searchPkgs, qo.pkg)
- for _, pkg := range searchPkgs {
- for ident, obj := range pkg.GetTypesInfo().Uses {
- // For instantiated objects (as in methods or fields on instantiated
- // types), we may not have pointer-identical objects but still want to
- // consider them references.
- if !equalOrigin(obj, qo.obj) {
- // If ident is not a use of qo.obj, skip it, with one exception:
- // uses of an embedded field can be considered references of the
- // embedded type name
- if !includeEmbeddedRefs {
- continue
- }
- v, ok := obj.(*types.Var)
- if !ok || !v.Embedded() {
- continue
- }
- named, ok := v.Type().(*types.Named)
- if !ok || named.Obj() != qo.obj {
- continue
- }
- }
- if seen[ident.Pos()] {
- continue
- }
- seen[ident.Pos()] = true
- rng, err := posToMappedRange(snapshot, pkg, ident.Pos(), ident.End())
- if err != nil {
- return nil, err
- }
- references = append(references, &ReferenceInfo{
- Name: ident.Name,
- ident: ident,
- pkg: pkg,
- obj: obj,
- MappedRange: rng,
- })
- }
- }
- }
-
- // When searching on type name, don't include interface references -- they
- // would be things like all references to Stringer for any type that
- // happened to have a String method.
- _, isType := declIdent.Declaration.obj.(*types.TypeName)
- if includeInterfaceRefs && !isType {
- declRange, err := declIdent.Range()
- if err != nil {
- return nil, err
- }
- fh, err := snapshot.GetFile(ctx, declIdent.URI())
- if err != nil {
- return nil, err
- }
- interfaceRefs, err := interfaceReferences(ctx, snapshot, fh, declRange.Start)
- if err != nil {
- return nil, err
- }
- references = append(references, interfaceRefs...)
- }
-
- return references, nil
-}
-
-// equalOrigin reports whether obj1 and obj2 have equivalent origin object.
-// This may be the case even if obj1 != obj2, if one or both of them is
-// instantiated.
-func equalOrigin(obj1, obj2 types.Object) bool {
- return obj1.Pkg() == obj2.Pkg() && obj1.Pos() == obj2.Pos() && obj1.Name() == obj2.Name()
-}
-
-// interfaceReferences returns the references to the interfaces implemented by
-// the type or method at the given position.
-func interfaceReferences(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]*ReferenceInfo, error) {
- implementations, err := implementations(ctx, s, f, pp)
- if err != nil {
- if errors.Is(err, ErrNotAType) {
- return nil, nil
- }
- return nil, err
- }
-
- var refs []*ReferenceInfo
- for _, impl := range implementations {
- implRefs, err := references(ctx, s, []qualifiedObject{impl}, false, false, false)
- if err != nil {
- return nil, err
- }
- refs = append(refs, implRefs...)
- }
- return refs, nil
-}
diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go
deleted file mode 100644
index 2ad5d265f..000000000
--- a/internal/lsp/source/rename.go
+++ /dev/null
@@ -1,371 +0,0 @@
-// 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 source
-
-import (
- "bytes"
- "context"
- "go/ast"
- "go/format"
- "go/token"
- "go/types"
- "regexp"
- "strings"
-
- "golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/refactor/satisfy"
- errors "golang.org/x/xerrors"
-)
-
-type renamer struct {
- ctx context.Context
- fset *token.FileSet
- refs []*ReferenceInfo
- objsToUpdate map[types.Object]bool
- hadConflicts bool
- errors string
- from, to string
- satisfyConstraints map[satisfy.Constraint]bool
- packages map[*types.Package]Package // may include additional packages that are a rdep of pkg
- msets typeutil.MethodSetCache
- changeMethods bool
-}
-
-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 Snapshot, f FileHandle, pp protocol.Position) (_ *PrepareItem, usererr, err error) {
- ctx, done := event.Start(ctx, "source.PrepareRename")
- defer done()
-
- qos, err := qualifiedObjsAtProtocolPos(ctx, snapshot, f.URI(), pp)
- if err != nil {
- return nil, nil, err
- }
- node, obj, pkg := qos[0].node, qos[0].obj, qos[0].sourcePkg
- if err := checkRenamable(obj); err != nil {
- return nil, err, err
- }
- mr, err := posToMappedRange(snapshot, pkg, node.Pos(), node.End())
- if err != nil {
- return nil, nil, err
- }
- rng, err := mr.Range()
- 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
-}
-
-// checkRenamable verifies if an obj may be renamed.
-func checkRenamable(obj types.Object) error {
- if v, ok := obj.(*types.Var); ok && v.Embedded() {
- return errors.New("can't rename embedded fields: rename the type directly or name the field")
- }
- 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.
-func Rename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position, newName string) (map[span.URI][]protocol.TextEdit, error) {
- ctx, done := event.Start(ctx, "source.Rename")
- defer done()
-
- qos, err := qualifiedObjsAtProtocolPos(ctx, s, f.URI(), pp)
- if err != nil {
- return nil, err
- }
-
- obj, pkg := qos[0].obj, qos[0].pkg
-
- if err := checkRenamable(obj); err != nil {
- return nil, err
- }
- if obj.Name() == newName {
- return nil, errors.Errorf("old and new names are the same: %s", newName)
- }
- if !isValidIdentifier(newName) {
- return nil, errors.Errorf("invalid identifier to rename: %q", newName)
- }
- if pkg == nil || pkg.IsIllTyped() {
- return nil, errors.Errorf("package for %s is ill typed", f.URI())
- }
- refs, err := references(ctx, s, qos, true, false, true)
- if err != nil {
- return nil, err
- }
- r := renamer{
- ctx: ctx,
- fset: s.FileSet(),
- refs: refs,
- objsToUpdate: make(map[types.Object]bool),
- from: obj.Name(),
- to: newName,
- packages: make(map[*types.Package]Package),
- }
-
- // A renaming initiated at an interface method indicates the
- // intention to rename abstract and concrete methods as needed
- // to preserve assignability.
- for _, ref := range refs {
- if obj, ok := ref.obj.(*types.Func); ok {
- recv := obj.Type().(*types.Signature).Recv()
- if recv != nil && IsInterface(recv.Type().Underlying()) {
- r.changeMethods = true
- break
- }
- }
- }
- for _, from := range refs {
- r.packages[from.pkg.GetTypes()] = from.pkg
- }
-
- // Check that the renaming of the identifier is ok.
- for _, ref := range refs {
- r.check(ref.obj)
- if r.hadConflicts { // one error is enough.
- break
- }
- }
- if r.hadConflicts {
- return nil, errors.Errorf(r.errors)
- }
-
- changes, err := r.update()
- if err != nil {
- return nil, err
- }
- result := make(map[span.URI][]protocol.TextEdit)
- for uri, edits := range changes {
- // These edits should really be associated with FileHandles for maximal correctness.
- // For now, this is good enough.
- fh, err := s.GetFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- data, err := fh.Read()
- if err != nil {
- return nil, err
- }
- converter := span.NewContentConverter(uri.Filename(), data)
- m := &protocol.ColumnMapper{
- URI: uri,
- Converter: converter,
- Content: data,
- }
- // Sort the edits first.
- diff.SortTextEdits(edits)
- protocolEdits, err := ToProtocolEdits(m, edits)
- if err != nil {
- return nil, err
- }
- result[uri] = protocolEdits
- }
- return result, nil
-}
-
-// Rename all references to the identifier.
-func (r *renamer) update() (map[span.URI][]diff.TextEdit, error) {
- result := make(map[span.URI][]diff.TextEdit)
- seen := make(map[span.Span]bool)
-
- docRegexp, err := regexp.Compile(`\b` + r.from + `\b`)
- if err != nil {
- return nil, err
- }
- for _, ref := range r.refs {
- refSpan, err := ref.spanRange.Span()
- if err != nil {
- return nil, err
- }
- if seen[refSpan] {
- continue
- }
- seen[refSpan] = true
-
- // Renaming a types.PkgName may result in the addition or removal of an identifier,
- // so we deal with this separately.
- if pkgName, ok := ref.obj.(*types.PkgName); ok && ref.isDeclaration {
- edit, err := r.updatePkgName(pkgName)
- if err != nil {
- return nil, err
- }
- result[refSpan.URI()] = append(result[refSpan.URI()], *edit)
- continue
- }
-
- // Replace the identifier with r.to.
- edit := diff.TextEdit{
- Span: refSpan,
- NewText: r.to,
- }
-
- result[refSpan.URI()] = append(result[refSpan.URI()], edit)
-
- if !ref.isDeclaration || ref.ident == nil { // uses do not have doc comments to update.
- continue
- }
-
- doc := r.docComment(ref.pkg, ref.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.
- for _, comment := range doc.List {
- if isDirective(comment.Text) {
- continue
- }
- lines := strings.Split(comment.Text, "\n")
- tok := r.fset.File(comment.Pos())
- commentLine := tok.Position(comment.Pos()).Line
- for i, line := range lines {
- lineStart := comment.Pos()
- if i > 0 {
- lineStart = tok.LineStart(commentLine + i)
- }
- for _, locs := range docRegexp.FindAllIndex([]byte(line), -1) {
- rng := span.NewRange(r.fset, lineStart+token.Pos(locs[0]), lineStart+token.Pos(locs[1]))
- spn, err := rng.Span()
- if err != nil {
- return nil, err
- }
- result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{
- Span: spn,
- NewText: r.to,
- })
- }
- }
- }
- }
-
- return result, nil
-}
-
-// docComment returns the doc for an identifier.
-func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup {
- _, nodes, _ := pathEnclosingInterval(r.fset, pkg, 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
- }
-
- var file *ast.File
- for _, f := range pkg.GetSyntax() {
- if f.Pos() <= id.Pos() && id.Pos() <= f.End() {
- file = f
- break
- }
- }
- if file == nil {
- return nil
- }
-
- identLine := r.fset.Position(id.Pos()).Line
- for _, comment := range file.Comments {
- if comment.Pos() > id.Pos() {
- // Comment is after the identifier.
- continue
- }
-
- lastCommentLine := r.fset.Position(comment.End()).Line
- if lastCommentLine+1 == identLine {
- return comment
- }
- }
- default:
- return nil
- }
- }
- return nil
-}
-
-// updatePkgName returns the updates to rename a pkgName in the import spec
-func (r *renamer) updatePkgName(pkgName *types.PkgName) (*diff.TextEdit, error) {
- // Modify ImportSpec syntax to add or remove the Name as needed.
- pkg := r.packages[pkgName.Pkg()]
- _, path, _ := pathEnclosingInterval(r.fset, pkg, pkgName.Pos(), pkgName.Pos())
- if len(path) < 2 {
- return nil, errors.Errorf("no path enclosing interval for %s", pkgName.Name())
- }
- spec, ok := path[1].(*ast.ImportSpec)
- if !ok {
- return nil, errors.Errorf("failed to update PkgName for %s", pkgName.Name())
- }
-
- var astIdent *ast.Ident // will be nil if ident is removed
- if pkgName.Imported().Name() != r.to {
- // ImportSpec.Name needed
- astIdent = &ast.Ident{NamePos: spec.Path.Pos(), Name: r.to}
- }
-
- // Make a copy of the ident that just has the name and path.
- updated := &ast.ImportSpec{
- Name: astIdent,
- Path: spec.Path,
- EndPos: spec.EndPos,
- }
-
- rng := span.NewRange(r.fset, spec.Pos(), spec.End())
- spn, err := rng.Span()
- if err != nil {
- return nil, err
- }
-
- var buf bytes.Buffer
- format.Node(&buf, r.fset, updated)
- newText := buf.String()
-
- return &diff.TextEdit{
- Span: spn,
- NewText: newText,
- }, nil
-}
diff --git a/internal/lsp/source/rename_check.go b/internal/lsp/source/rename_check.go
deleted file mode 100644
index 3aafc391e..000000000
--- a/internal/lsp/source/rename_check.go
+++ /dev/null
@@ -1,936 +0,0 @@
-// 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 source
-
-import (
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
- "reflect"
- "strconv"
- "strings"
- "unicode"
-
- "golang.org/x/tools/go/ast/astutil"
- "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{}) {
- r.hadConflicts = true
- r.errors += fmt.Sprintf(format, args...)
-}
-
-// 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.checkInLocalScope(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, r.packages[from.Pkg()])
-}
-
-// 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 ast.IsExported(from.Name()) && !ast.IsExported(r.to) {
- for typ, pkg := range r.packages {
- if typ == from.Pkg() {
- continue
- }
- if id := someUse(pkg.GetTypesInfo(), from); id != nil &&
- !r.checkExport(id, typ, from) {
- break
- }
- }
- }
-
- pkg := r.packages[from.Pkg()]
- if pkg == nil {
- return
- }
-
- // 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 pkg.GetTypesInfo().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)
- }
- }
-
- // Check for conflicts between package block and all file blocks.
- for _, f := range pkg.GetSyntax() {
- fileScope := pkg.GetTypesInfo().Scopes[f]
- 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)
- var prevPos token.Pos
- if prev != nil {
- prevPos = prev.Pos()
- }
- r.errorf(prevPos, "\twith this %s", objectKind(prev))
- return // since checkInPackageBlock would report redundant errors
- }
- }
-
- // Check for conflicts in lexical scope.
- if from.Exported() {
- for _, pkg := range r.packages {
- r.checkInLexicalScope(from, pkg)
- }
- } else {
- r.checkInLexicalScope(from, pkg)
- }
-}
-
-func (r *renamer) checkInLocalScope(from types.Object) {
- pkg := r.packages[from.Pkg()]
- r.checkInLexicalScope(from, pkg)
-}
-
-// 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 casts a shadow in scope of 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, pkg Package) {
- 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(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(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 pkg.GetTypesInfo().Uses {
- if obj == from {
- if field := pkg.GetTypesInfo().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())
- }
-}
-
-// 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 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.GetTypesInfo().Uses[n] == obj {
- block := enclosingBlock(pkg.GetTypesInfo(), 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.GetTypesInfo().Types[n]
- if !ok {
- return visit(nil) // pop stack, don't descend
- }
- if _, ok := Deref(tv.Type).Underlying().(*types.Struct); ok {
- 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.GetSyntax() {
- ast.Inspect(f, visit)
- if len(stack) != 0 {
- panic(stack)
- }
- if !ok {
- break
- }
- }
- return ok
-}
-
-// enclosingBlock returns the innermost block enclosing the specified
-// AST node, 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.
- // TODO(adonovan): feature or a bug?
- 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) {
- // 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.
- fromPkg, ok := r.packages[from.Pkg()]
- if !ok {
- return
- }
- pkg, path, _ := pathEnclosingInterval(r.fset, fromPkg, from.Pos(), from.Pos())
- if pkg == nil || path == nil {
- return
- }
- // 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 := pkg.GetTypesInfo().Defs[spec.Name].Type()
- prev, indices, _ := types.LookupFieldOrMethod(named, true, pkg.GetTypes(), 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 := pkg.GetTypesInfo().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 := Deref(from.Type()).(*types.Named); ok {
- r.check(named.Obj())
- }
- }
-
- // Check integrity of existing (field and method) selections.
- r.checkSelections(from)
-}
-
-// checkSelection 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) {
- for typ, pkg := range r.packages {
- if id := someUse(pkg.GetTypesInfo(), from); id != nil {
- if !r.checkExport(id, typ, from) {
- return
- }
- }
-
- for syntax, sel := range pkg.GetTypesInfo().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 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.
- for _, pkg := range r.packages {
- // Start with named interface types (better errors)
- for _, obj := range pkg.GetTypesInfo().Defs {
- if obj, ok := obj.(*types.TypeName); ok && 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 pkg.GetTypesInfo().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 !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 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 := 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
- for _, pkg := range r.packages {
- // 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 pkg.HasListOrParseErrors() || pkg.HasTypeErrors() {
- 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.PkgPath())
- return nil
- }
- f.Find(pkg.GetTypesInfo(), pkg.GetSyntax())
- }
- r.satisfyConstraints = f.Result
- }
- return r.satisfyConstraints
-}
-
-// -- helpers ----------------------------------------------------------
-
-// recv returns the method's receiver.
-func recv(meth *types.Func) *types.Var {
- return meth.Type().(*types.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
-}
-
-// pathEnclosingInterval returns the Package and ast.Node that
-// contain source interval [start, end), and all the node's ancestors
-// up to the AST root. It searches all ast.Files of all packages.
-// exact is defined as for astutil.PathEnclosingInterval.
-//
-// The zero value is returned if not found.
-//
-func pathEnclosingInterval(fset *token.FileSet, pkg Package, start, end token.Pos) (resPkg Package, path []ast.Node, exact bool) {
- pkgs := []Package{pkg}
- for _, f := range pkg.GetSyntax() {
- for _, imp := range f.Imports {
- if imp == nil {
- continue
- }
- importPath, err := strconv.Unquote(imp.Path.Value)
- if err != nil {
- continue
- }
- importPkg, err := pkg.GetImport(importPath)
- if err != nil {
- return nil, nil, false
- }
- pkgs = append(pkgs, importPkg)
- }
- }
- for _, p := range pkgs {
- for _, f := range p.GetSyntax() {
- if f.Pos() == token.NoPos {
- // This can happen if the parser saw
- // too many errors and bailed out.
- // (Use parser.AllErrors to prevent that.)
- continue
- }
- if !tokenFileContainsPos(fset.File(f.Pos()), start) {
- continue
- }
- if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil {
- return pkg, path, exact
- }
- }
- }
- return nil, nil, false
-}
-
-// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos)
-func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
- p := int(pos)
- base := f.Base()
- return base <= p && p < base+f.Size()
-}
-
-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 obj.Type().(*types.Signature).Recv() != 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/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go
deleted file mode 100644
index e7ed9cc8b..000000000
--- a/internal/lsp/source/signature_help.go
+++ /dev/null
@@ -1,181 +0,0 @@
-// 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 source
-
-import (
- "context"
- "go/ast"
- "go/token"
- "go/types"
-
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- errors "golang.org/x/xerrors"
-)
-
-func SignatureHelp(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) (*protocol.SignatureInformation, int, error) {
- ctx, done := event.Start(ctx, "source.SignatureHelp")
- defer done()
-
- pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
- if err != nil {
- return nil, 0, errors.Errorf("getting file for SignatureHelp: %w", err)
- }
- spn, err := pgf.Mapper.PointSpan(pos)
- if err != nil {
- return nil, 0, err
- }
- rng, err := spn.Range(pgf.Mapper.Converter)
- if err != nil {
- return nil, 0, err
- }
- // Find a call expression surrounding the query position.
- var callExpr *ast.CallExpr
- path, _ := astutil.PathEnclosingInterval(pgf.File, rng.Start, rng.Start)
- if path == nil {
- return nil, 0, errors.Errorf("cannot find node enclosing position")
- }
-FindCall:
- for _, node := range path {
- switch node := node.(type) {
- case *ast.CallExpr:
- if rng.Start >= node.Lparen && rng.Start <= node.Rparen {
- callExpr = node
- break FindCall
- }
- 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, errors.Errorf("no signature help within a function declaration")
- case *ast.BasicLit:
- if node.Kind == token.STRING {
- return nil, 0, errors.Errorf("no signature help within a string literal")
- }
- }
-
- }
- if callExpr == nil || callExpr.Fun == nil {
- return nil, 0, errors.Errorf("cannot find an enclosing function")
- }
-
- qf := Qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo())
-
- // 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 := callExpr.Fun.(type) {
- case *ast.Ident:
- obj = pkg.GetTypesInfo().ObjectOf(t)
- case *ast.SelectorExpr:
- obj = pkg.GetTypesInfo().ObjectOf(t.Sel)
- }
-
- // Handle builtin functions separately.
- if obj, ok := obj.(*types.Builtin); ok {
- return builtinSignature(ctx, snapshot, callExpr, obj.Name(), rng.Start)
- }
-
- // Get the type information for the function being called.
- sigType := pkg.GetTypesInfo().TypeOf(callExpr.Fun)
- if sigType == nil {
- return nil, 0, errors.Errorf("cannot get type for Fun %[1]T (%[1]v)", callExpr.Fun)
- }
-
- sig, _ := sigType.Underlying().(*types.Signature)
- if sig == nil {
- return nil, 0, errors.Errorf("cannot find signature for Fun %[1]T (%[1]v)", callExpr.Fun)
- }
-
- activeParam := activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), rng.Start)
-
- var (
- name string
- comment *ast.CommentGroup
- )
- if obj != nil {
- declPkg, err := FindPackageFromPos(ctx, snapshot, obj.Pos())
- if err != nil {
- return nil, 0, err
- }
- node, err := snapshot.PosToDecl(ctx, declPkg, obj.Pos())
- if err != nil {
- return nil, 0, err
- }
- rng, err := objToMappedRange(snapshot, pkg, obj)
- if err != nil {
- return nil, 0, err
- }
- decl := Declaration{
- obj: obj,
- node: node,
- }
- decl.MappedRange = append(decl.MappedRange, rng)
- d, err := FindHoverContext(ctx, snapshot, pkg, decl.obj, decl.node, nil)
- if err != nil {
- return nil, 0, err
- }
- name = obj.Name()
- comment = d.Comment
- } else {
- name = "func"
- }
- s := NewSignature(ctx, snapshot, pkg, sig, comment, qf)
- 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: s.doc,
- Parameters: paramInfo,
- }, activeParam, nil
-}
-
-func builtinSignature(ctx context.Context, snapshot 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: sig.doc,
- 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
-}
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
deleted file mode 100644
index dc5fe53b5..000000000
--- a/internal/lsp/source/source_test.go
+++ /dev/null
@@ -1,984 +0,0 @@
-// 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 source_test
-
-import (
- "context"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "sort"
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/lsp/cache"
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/diff/myers"
- "golang.org/x/tools/internal/lsp/fuzzy"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/source/completion"
- "golang.org/x/tools/internal/lsp/tests"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/testenv"
- errors "golang.org/x/xerrors"
-)
-
-func TestMain(m *testing.M) {
- testenv.ExitIfSmallMachine()
- os.Exit(m.Run())
-}
-
-func TestSource(t *testing.T) {
- tests.RunTests(t, "../testdata", true, testSource)
-}
-
-type runner struct {
- snapshot source.Snapshot
- view source.View
- data *tests.Data
- ctx context.Context
- normalizers []tests.Normalizer
-}
-
-func testSource(t *testing.T, datum *tests.Data) {
- ctx := tests.Context(t)
-
- cache := cache.New(nil)
- session := cache.NewSession(ctx)
- options := source.DefaultOptions().Clone()
- tests.DefaultOptions(options)
- options.SetEnvSlice(datum.Config.Env)
- view, _, release, err := session.NewView(ctx, "source_test", span.URIFromPath(datum.Config.Dir), options)
- release()
- if err != nil {
- t.Fatal(err)
- }
- defer view.Shutdown(ctx)
-
- // Enable type error analyses for tests.
- // TODO(golang/go#38212): Delete this once they are enabled by default.
- tests.EnableAllAnalyzers(view, options)
- view.SetOptions(ctx, options)
-
- var modifications []source.FileModification
- for filename, content := range datum.Config.Overlay {
- if filepath.Ext(filename) != ".go" {
- continue
- }
- modifications = append(modifications, source.FileModification{
- URI: span.URIFromPath(filename),
- Action: source.Open,
- Version: -1,
- Text: content,
- LanguageID: "go",
- })
- }
- if err := session.ModifyFiles(ctx, modifications); err != nil {
- t.Fatal(err)
- }
- snapshot, release := view.Snapshot(ctx)
- defer release()
- r := &runner{
- view: view,
- snapshot: snapshot,
- data: datum,
- ctx: ctx,
- normalizers: tests.CollectNormalizers(datum.Exported),
- }
- tests.Run(t, r, datum)
-}
-
-func (r *runner) CallHierarchy(t *testing.T, spn span.Span, expectedCalls *tests.CallHierarchyResult) {
- mapper, err := r.data.Mapper(spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- loc, err := mapper.Location(spn)
- if err != nil {
- t.Fatalf("failed for %v: %v", spn, err)
- }
- fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
- if err != nil {
- t.Fatal(err)
- }
-
- items, err := source.PrepareCallHierarchy(r.ctx, r.snapshot, fh, loc.Range.Start)
- if err != nil {
- t.Fatal(err)
- }
- if len(items) == 0 {
- t.Fatalf("expected call hierarchy item to be returned for identifier at %v\n", loc.Range)
- }
-
- callLocation := protocol.Location{
- URI: items[0].URI,
- Range: items[0].Range,
- }
- if callLocation != loc {
- t.Fatalf("expected source.PrepareCallHierarchy to return identifier at %v but got %v\n", loc, callLocation)
- }
-
- incomingCalls, err := source.IncomingCalls(r.ctx, r.snapshot, fh, loc.Range.Start)
- if err != nil {
- t.Error(err)
- }
- var incomingCallItems []protocol.CallHierarchyItem
- for _, item := range incomingCalls {
- incomingCallItems = append(incomingCallItems, item.From)
- }
- msg := tests.DiffCallHierarchyItems(incomingCallItems, expectedCalls.IncomingCalls)
- if msg != "" {
- t.Error(fmt.Sprintf("incoming calls differ: %s", msg))
- }
-
- outgoingCalls, err := source.OutgoingCalls(r.ctx, r.snapshot, fh, loc.Range.Start)
- if err != nil {
- t.Error(err)
- }
- var outgoingCallItems []protocol.CallHierarchyItem
- for _, item := range outgoingCalls {
- outgoingCallItems = append(outgoingCallItems, item.To)
- }
- msg = tests.DiffCallHierarchyItems(outgoingCallItems, expectedCalls.OutgoingCalls)
- if msg != "" {
- t.Error(fmt.Sprintf("outgoing calls differ: %s", msg))
- }
-}
-
-func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []*source.Diagnostic) {
- fileID, got, err := source.FileDiagnostics(r.ctx, r.snapshot, uri)
- if err != nil {
- t.Fatal(err)
- }
- // A special case to test that there are no diagnostics for a file.
- if len(want) == 1 && want[0].Source == "no_diagnostics" {
- if len(got) != 0 {
- t.Errorf("expected no diagnostics for %s, got %v", uri, got)
- }
- return
- }
- if diff := tests.DiffDiagnostics(fileID.URI, want, got); diff != "" {
- t.Error(diff)
- }
-}
-
-func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- var want []protocol.CompletionItem
- for _, pos := range test.CompletionItems {
- want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
- }
- _, got := r.callCompletion(t, src, func(opts *source.Options) {
- opts.Matcher = source.CaseInsensitive
- opts.DeepCompletion = false
- opts.CompleteUnimported = false
- opts.InsertTextFormat = protocol.SnippetTextFormat
- opts.LiteralCompletions = strings.Contains(string(src.URI()), "literal")
- opts.ExperimentalPostfixCompletions = strings.Contains(string(src.URI()), "postfix")
- })
- got = tests.FilterBuiltins(src, got)
- if diff := tests.DiffCompletionItems(want, got); diff != "" {
- t.Errorf("%s: %s", src, diff)
- }
-}
-
-func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) {
- _, list := r.callCompletion(t, src, func(opts *source.Options) {
- opts.UsePlaceholders = placeholders
- opts.DeepCompletion = true
- opts.CompleteUnimported = false
- })
- got := tests.FindItem(list, *items[expected.CompletionItem])
- want := expected.PlainSnippet
- if placeholders {
- want = expected.PlaceholderSnippet
- }
- if diff := tests.DiffSnippets(want, got); diff != "" {
- t.Errorf("%s: %s", src, diff)
- }
-}
-
-func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- var want []protocol.CompletionItem
- for _, pos := range test.CompletionItems {
- want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
- }
- _, got := r.callCompletion(t, src, func(opts *source.Options) {})
- got = tests.FilterBuiltins(src, got)
- if diff := tests.CheckCompletionOrder(want, got, false); diff != "" {
- t.Errorf("%s: %s", src, diff)
- }
-}
-
-func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- var want []protocol.CompletionItem
- for _, pos := range test.CompletionItems {
- want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
- }
- prefix, list := r.callCompletion(t, src, func(opts *source.Options) {
- opts.DeepCompletion = true
- opts.Matcher = source.CaseInsensitive
- opts.CompleteUnimported = false
- })
- list = tests.FilterBuiltins(src, list)
- fuzzyMatcher := fuzzy.NewMatcher(prefix)
- var got []protocol.CompletionItem
- for _, item := range list {
- if fuzzyMatcher.Score(item.Label) <= 0 {
- continue
- }
- got = append(got, item)
- }
- if msg := tests.DiffCompletionItems(want, got); msg != "" {
- t.Errorf("%s: %s", src, msg)
- }
-}
-
-func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- var want []protocol.CompletionItem
- for _, pos := range test.CompletionItems {
- want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
- }
- _, got := r.callCompletion(t, src, func(opts *source.Options) {
- opts.DeepCompletion = true
- opts.Matcher = source.Fuzzy
- opts.CompleteUnimported = false
- })
- got = tests.FilterBuiltins(src, got)
- if msg := tests.DiffCompletionItems(want, got); msg != "" {
- t.Errorf("%s: %s", src, msg)
- }
-}
-
-func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- var want []protocol.CompletionItem
- for _, pos := range test.CompletionItems {
- want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
- }
- _, list := r.callCompletion(t, src, func(opts *source.Options) {
- opts.Matcher = source.CaseSensitive
- opts.CompleteUnimported = false
- })
- list = tests.FilterBuiltins(src, list)
- if diff := tests.DiffCompletionItems(want, list); diff != "" {
- t.Errorf("%s: %s", src, diff)
- }
-}
-
-func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
- var want []protocol.CompletionItem
- for _, pos := range test.CompletionItems {
- want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
- }
- _, got := r.callCompletion(t, src, func(opts *source.Options) {
- opts.DeepCompletion = true
- opts.Matcher = source.Fuzzy
- opts.ExperimentalPostfixCompletions = true
- })
- if msg := tests.CheckCompletionOrder(want, got, true); msg != "" {
- t.Errorf("%s: %s", src, msg)
- }
-}
-
-func (r *runner) callCompletion(t *testing.T, src span.Span, options func(*source.Options)) (string, []protocol.CompletionItem) {
- fh, err := r.snapshot.GetFile(r.ctx, src.URI())
- if err != nil {
- t.Fatal(err)
- }
- original := r.view.Options()
- modified := original.Clone()
- options(modified)
- newView, err := r.view.SetOptions(r.ctx, modified)
- if newView != r.view {
- t.Fatalf("options change unexpectedly created new view")
- }
- if err != nil {
- t.Fatal(err)
- }
- defer r.view.SetOptions(r.ctx, original)
-
- list, surrounding, err := completion.Completion(r.ctx, r.snapshot, fh, protocol.Position{
- Line: uint32(src.Start().Line() - 1),
- Character: uint32(src.Start().Column() - 1),
- }, protocol.CompletionContext{})
- if err != nil && !errors.As(err, &completion.ErrIsDefinition{}) {
- t.Fatalf("failed for %v: %v", src, err)
- }
- var prefix string
- if surrounding != nil {
- prefix = strings.ToLower(surrounding.Prefix())
- }
-
- var numDeepCompletionsSeen int
- var items []completion.CompletionItem
- // Apply deep completion filtering.
- for _, item := range list {
- if item.Depth > 0 {
- if !modified.DeepCompletion {
- continue
- }
- if numDeepCompletionsSeen >= completion.MaxDeepCompletions {
- continue
- }
- numDeepCompletionsSeen++
- }
- items = append(items, item)
- }
- return prefix, tests.ToProtocolCompletionItems(items)
-}
-
-func (r *runner) FoldingRanges(t *testing.T, spn span.Span) {
- uri := spn.URI()
-
- fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- data, err := fh.Read()
- if err != nil {
- t.Error(err)
- return
- }
-
- // Test all folding ranges.
- ranges, err := source.FoldingRange(r.ctx, r.snapshot, fh, false)
- if err != nil {
- t.Error(err)
- return
- }
- r.foldingRanges(t, "foldingRange", uri, string(data), ranges)
-
- // Test folding ranges with lineFoldingOnly
- ranges, err = source.FoldingRange(r.ctx, r.snapshot, fh, true)
- if err != nil {
- t.Error(err)
- return
- }
- r.foldingRanges(t, "foldingRange-lineFolding", uri, string(data), ranges)
-}
-
-func (r *runner) foldingRanges(t *testing.T, prefix string, uri span.URI, data string, ranges []*source.FoldingRangeInfo) {
- t.Helper()
- // Fold all ranges.
- nonOverlapping := nonOverlappingRanges(t, ranges)
- for i, rngs := range nonOverlapping {
- got, err := foldRanges(string(data), rngs)
- if err != nil {
- t.Error(err)
- continue
- }
- tag := fmt.Sprintf("%s-%d", prefix, i)
- want := string(r.data.Golden(tag, uri.Filename(), func() ([]byte, error) {
- return []byte(got), nil
- }))
-
- if diff := tests.Diff(t, want, got); diff != "" {
- t.Errorf("%s: foldingRanges failed for %s, diff:\n%v", tag, uri.Filename(), diff)
- }
- }
-
- // Filter by kind.
- kinds := []protocol.FoldingRangeKind{protocol.Imports, protocol.Comment}
- for _, kind := range kinds {
- var kindOnly []*source.FoldingRangeInfo
- for _, fRng := range ranges {
- if fRng.Kind == kind {
- kindOnly = append(kindOnly, fRng)
- }
- }
-
- nonOverlapping := nonOverlappingRanges(t, kindOnly)
- for i, rngs := range nonOverlapping {
- got, err := foldRanges(string(data), rngs)
- if err != nil {
- t.Error(err)
- continue
- }
- tag := fmt.Sprintf("%s-%s-%d", prefix, kind, i)
- want := string(r.data.Golden(tag, uri.Filename(), func() ([]byte, error) {
- return []byte(got), nil
- }))
-
- if diff := tests.Diff(t, want, got); diff != "" {
- t.Errorf("%s: failed for %s, diff:\n%v", tag, uri.Filename(), diff)
- }
- }
-
- }
-}
-
-func nonOverlappingRanges(t *testing.T, ranges []*source.FoldingRangeInfo) (res [][]*source.FoldingRangeInfo) {
- for _, fRng := range ranges {
- setNum := len(res)
- for i := 0; i < len(res); i++ {
- canInsert := true
- for _, rng := range res[i] {
- if conflict(t, rng, fRng) {
- canInsert = false
- break
- }
- }
- if canInsert {
- setNum = i
- break
- }
- }
- if setNum == len(res) {
- res = append(res, []*source.FoldingRangeInfo{})
- }
- res[setNum] = append(res[setNum], fRng)
- }
- return res
-}
-
-func conflict(t *testing.T, a, b *source.FoldingRangeInfo) bool {
- arng, err := a.Range()
- if err != nil {
- t.Fatal(err)
- }
- brng, err := b.Range()
- if err != nil {
- t.Fatal(err)
- }
- // a start position is <= b start positions
- return protocol.ComparePosition(arng.Start, brng.Start) <= 0 && protocol.ComparePosition(arng.End, brng.Start) > 0
-}
-
-func foldRanges(contents string, ranges []*source.FoldingRangeInfo) (string, error) {
- foldedText := "<>"
- res := contents
- // Apply the folds from the end of the file forward
- // to preserve the offsets.
- for i := len(ranges) - 1; i >= 0; i-- {
- fRange := ranges[i]
- spn, err := fRange.Span()
- if err != nil {
- return "", err
- }
- start := spn.Start().Offset()
- end := spn.End().Offset()
-
- tmp := res[0:start] + foldedText
- res = tmp + res[end:]
- }
- return res, nil
-}
-
-func (r *runner) Format(t *testing.T, spn span.Span) {
- gofmted := string(r.data.Golden("gofmt", spn.URI().Filename(), func() ([]byte, error) {
- cmd := exec.Command("gofmt", spn.URI().Filename())
- out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files
- return out, nil
- }))
- fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- edits, err := source.Format(r.ctx, r.snapshot, fh)
- if err != nil {
- if gofmted != "" {
- t.Error(err)
- }
- return
- }
- data, err := fh.Read()
- if err != nil {
- t.Fatal(err)
- }
- m, err := r.data.Mapper(spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- diffEdits, err := source.FromProtocolEdits(m, edits)
- if err != nil {
- t.Error(err)
- }
- got := diff.ApplyEdits(string(data), diffEdits)
- if gofmted != got {
- t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", spn.URI().Filename(), gofmted, got)
- }
-}
-
-func (r *runner) SemanticTokens(t *testing.T, spn span.Span) {
- t.Skip("nothing to test in source")
-}
-
-func (r *runner) Import(t *testing.T, spn span.Span) {
- fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- edits, _, err := source.AllImportsFixes(r.ctx, r.snapshot, fh)
- if err != nil {
- t.Error(err)
- }
- data, err := fh.Read()
- if err != nil {
- t.Fatal(err)
- }
- m, err := r.data.Mapper(fh.URI())
- if err != nil {
- t.Fatal(err)
- }
- diffEdits, err := source.FromProtocolEdits(m, edits)
- if err != nil {
- t.Error(err)
- }
- got := diff.ApplyEdits(string(data), diffEdits)
- want := string(r.data.Golden("goimports", spn.URI().Filename(), func() ([]byte, error) {
- return []byte(got), nil
- }))
- if want != got {
- d, err := myers.ComputeEdits(spn.URI(), want, got)
- if err != nil {
- t.Fatal(err)
- }
- t.Errorf("import failed for %s: %s", spn.URI().Filename(), diff.ToUnified("want", "got", want, d))
- }
-}
-
-func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) {
- _, srcRng, err := spanToRange(r.data, d.Src)
- if err != nil {
- t.Fatal(err)
- }
- fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- ident, err := source.Identifier(r.ctx, r.snapshot, fh, srcRng.Start)
- if err != nil {
- t.Fatalf("failed for %v: %v", d.Src, err)
- }
- h, err := source.HoverIdentifier(r.ctx, ident)
- if err != nil {
- t.Fatalf("failed for %v: %v", d.Src, err)
- }
- hover, err := source.FormatHover(h, r.view.Options())
- if err != nil {
- t.Fatal(err)
- }
- rng, err := ident.Declaration.MappedRange[0].Range()
- if err != nil {
- t.Fatal(err)
- }
- if d.IsType {
- rng, err = ident.Type.Range()
- if err != nil {
- t.Fatal(err)
- }
- hover = ""
- }
- didSomething := false
- if hover != "" {
- didSomething = true
- tag := fmt.Sprintf("%s-hoverdef", d.Name)
- expectHover := string(r.data.Golden(tag, d.Src.URI().Filename(), func() ([]byte, error) {
- return []byte(hover), nil
- }))
- hover = tests.StripSubscripts(hover)
- expectHover = tests.StripSubscripts(expectHover)
- if hover != expectHover {
- t.Errorf("hoverdef for %s failed:\n%s", d.Src, tests.Diff(t, expectHover, hover))
- }
- }
- if !d.OnlyHover {
- didSomething = true
- if _, defRng, err := spanToRange(r.data, d.Def); err != nil {
- t.Fatal(err)
- } else if rng != defRng {
- t.Errorf("for %v got %v want %v", d.Src, rng, defRng)
- }
- }
- if !didSomething {
- t.Errorf("no tests ran for %s", d.Src.URI())
- }
-}
-
-func (r *runner) Implementation(t *testing.T, spn span.Span, impls []span.Span) {
- sm, err := r.data.Mapper(spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- loc, err := sm.Location(spn)
- if err != nil {
- t.Fatalf("failed for %v: %v", spn, err)
- }
- fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- locs, err := source.Implementation(r.ctx, r.snapshot, fh, loc.Range.Start)
- if err != nil {
- t.Fatalf("failed for %v: %v", spn, err)
- }
- if len(locs) != len(impls) {
- t.Fatalf("got %d locations for implementation, expected %d", len(locs), len(impls))
- }
- var results []span.Span
- for i := range locs {
- locURI := locs[i].URI.SpanURI()
- lm, err := r.data.Mapper(locURI)
- if err != nil {
- t.Fatal(err)
- }
- imp, err := lm.Span(locs[i])
- if err != nil {
- t.Fatalf("failed for %v: %v", locs[i], err)
- }
- results = append(results, imp)
- }
- // Sort results and expected to make tests deterministic.
- sort.SliceStable(results, func(i, j int) bool {
- return span.Compare(results[i], results[j]) == -1
- })
- sort.SliceStable(impls, func(i, j int) bool {
- return span.Compare(impls[i], impls[j]) == -1
- })
- for i := range results {
- if results[i] != impls[i] {
- t.Errorf("for %dth implementation of %v got %v want %v", i, spn, results[i], impls[i])
- }
- }
-}
-
-func (r *runner) Highlight(t *testing.T, src span.Span, locations []span.Span) {
- ctx := r.ctx
- m, srcRng, err := spanToRange(r.data, src)
- if err != nil {
- t.Fatal(err)
- }
- fh, err := r.snapshot.GetFile(r.ctx, src.URI())
- if err != nil {
- t.Fatal(err)
- }
- highlights, err := source.Highlight(ctx, r.snapshot, fh, srcRng.Start)
- if err != nil {
- t.Errorf("highlight failed for %s: %v", src.URI(), err)
- }
- if len(highlights) != len(locations) {
- t.Fatalf("got %d highlights for highlight at %v:%v:%v, expected %d", len(highlights), src.URI().Filename(), src.Start().Line(), src.Start().Column(), len(locations))
- }
- // Check to make sure highlights have a valid range.
- var results []span.Span
- for i := range highlights {
- h, err := m.RangeSpan(highlights[i])
- if err != nil {
- t.Fatalf("failed for %v: %v", highlights[i], err)
- }
- results = append(results, h)
- }
- // Sort results to make tests deterministic since DocumentHighlight uses a map.
- sort.SliceStable(results, func(i, j int) bool {
- return span.Compare(results[i], results[j]) == -1
- })
- // Check to make sure all the expected highlights are found.
- for i := range results {
- if results[i] != locations[i] {
- t.Errorf("want %v, got %v\n", locations[i], results[i])
- }
- }
-}
-
-func (r *runner) Hover(t *testing.T, src span.Span, text string) {
- ctx := r.ctx
- _, srcRng, err := spanToRange(r.data, src)
- if err != nil {
- t.Fatal(err)
- }
- fh, err := r.snapshot.GetFile(r.ctx, src.URI())
- if err != nil {
- t.Fatal(err)
- }
- hover, err := source.Hover(ctx, r.snapshot, fh, srcRng.Start)
- if err != nil {
- t.Errorf("hover failed for %s: %v", src.URI(), err)
- }
- if text == "" {
- if hover != nil {
- t.Errorf("want nil, got %v\n", hover)
- }
- } else {
- if hover == nil {
- t.Fatalf("want hover result to not be nil")
- }
- if got := hover.Contents.Value; got != text {
- t.Errorf("want %v, got %v\n", got, text)
- }
- if want, got := srcRng, hover.Range; want != got {
- t.Errorf("want range %v, got %v instead", want, got)
- }
- }
-}
-
-func (r *runner) References(t *testing.T, src span.Span, itemList []span.Span) {
- ctx := r.ctx
- _, srcRng, err := spanToRange(r.data, src)
- if err != nil {
- t.Fatal(err)
- }
- snapshot := r.snapshot
- fh, err := snapshot.GetFile(r.ctx, src.URI())
- if err != nil {
- t.Fatal(err)
- }
- for _, includeDeclaration := range []bool{true, false} {
- t.Run(fmt.Sprintf("refs-declaration-%v", includeDeclaration), func(t *testing.T) {
- want := make(map[span.Span]bool)
- for i, pos := range itemList {
- // We don't want the first result if we aren't including the declaration.
- if i == 0 && !includeDeclaration {
- continue
- }
- want[pos] = true
- }
- refs, err := source.References(ctx, snapshot, fh, srcRng.Start, includeDeclaration)
- if err != nil {
- t.Fatalf("failed for %s: %v", src, err)
- }
- got := make(map[span.Span]bool)
- for _, refInfo := range refs {
- refSpan, err := refInfo.Span()
- if err != nil {
- t.Fatal(err)
- }
- got[refSpan] = true
- }
- if len(got) != len(want) {
- t.Errorf("references failed: different lengths got %v want %v", len(got), len(want))
- }
- for spn := range got {
- if !want[spn] {
- t.Errorf("references failed: incorrect references got %v want locations %v", got, want)
- }
- }
- })
- }
-}
-
-func (r *runner) Rename(t *testing.T, spn span.Span, newText string) {
- tag := fmt.Sprintf("%s-rename", newText)
-
- _, srcRng, err := spanToRange(r.data, spn)
- if err != nil {
- t.Fatal(err)
- }
- fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- changes, err := source.Rename(r.ctx, r.snapshot, fh, srcRng.Start, newText)
- if err != nil {
- renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) {
- return []byte(err.Error()), nil
- }))
- if err.Error() != renamed {
- t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v\n", newText, renamed, err)
- }
- return
- }
-
- var res []string
- for editURI, edits := range changes {
- fh, err := r.snapshot.GetFile(r.ctx, editURI)
- if err != nil {
- t.Fatal(err)
- }
- data, err := fh.Read()
- if err != nil {
- t.Fatal(err)
- }
- m, err := r.data.Mapper(fh.URI())
- if err != nil {
- t.Fatal(err)
- }
- diffEdits, err := source.FromProtocolEdits(m, edits)
- if err != nil {
- t.Fatal(err)
- }
- contents := applyEdits(string(data), diffEdits)
- if len(changes) > 1 {
- filename := filepath.Base(editURI.Filename())
- contents = fmt.Sprintf("%s:\n%s", filename, contents)
- }
- res = append(res, contents)
- }
-
- // Sort on filename
- sort.Strings(res)
-
- var got string
- for i, val := range res {
- if i != 0 {
- got += "\n"
- }
- got += val
- }
-
- renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) {
- return []byte(got), nil
- }))
-
- if renamed != got {
- t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v", newText, renamed, got)
- }
-}
-
-func applyEdits(contents string, edits []diff.TextEdit) string {
- res := contents
-
- // Apply the edits from the end of the file forward
- // to preserve the offsets
- for i := len(edits) - 1; i >= 0; i-- {
- edit := edits[i]
- start := edit.Span.Start().Offset()
- end := edit.Span.End().Offset()
- tmp := res[0:start] + edit.NewText
- res = tmp + res[end:]
- }
- return res
-}
-
-func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) {
- _, srcRng, err := spanToRange(r.data, src)
- if err != nil {
- t.Fatal(err)
- }
- // Find the identifier at the position.
- fh, err := r.snapshot.GetFile(r.ctx, src.URI())
- if err != nil {
- t.Fatal(err)
- }
- item, _, err := source.PrepareRename(r.ctx, r.snapshot, fh, srcRng.Start)
- if err != nil {
- if want.Text != "" { // expected an ident.
- t.Errorf("prepare rename failed for %v: got error: %v", src, err)
- }
- return
- }
- if item == nil {
- if want.Text != "" {
- t.Errorf("prepare rename failed for %v: got nil", src)
- }
- return
- }
- if want.Text == "" {
- t.Errorf("prepare rename failed for %v: expected nil, got %v", src, item)
- return
- }
- if item.Range.Start == item.Range.End {
- // Special case for 0-length ranges. Marks can't specify a 0-length range,
- // so just compare the start.
- if item.Range.Start != want.Range.Start {
- t.Errorf("prepare rename failed: incorrect point, got %v want %v", item.Range.Start, want.Range.Start)
- }
- } else {
- if protocol.CompareRange(item.Range, want.Range) != 0 {
- t.Errorf("prepare rename failed: incorrect range got %v want %v", item.Range, want.Range)
- }
- }
-}
-
-func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) {
- fh, err := r.snapshot.GetFile(r.ctx, uri)
- if err != nil {
- t.Fatal(err)
- }
- symbols, err := source.DocumentSymbols(r.ctx, r.snapshot, fh)
- if err != nil {
- t.Errorf("symbols failed for %s: %v", uri, err)
- }
- if len(symbols) != len(expectedSymbols) {
- t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols))
- return
- }
- if diff := tests.DiffSymbols(t, uri, expectedSymbols, symbols); diff != "" {
- t.Error(diff)
- }
-}
-
-func (r *runner) WorkspaceSymbols(t *testing.T, uri span.URI, query string, typ tests.WorkspaceSymbolsTestType) {
- r.callWorkspaceSymbols(t, uri, query, typ)
-}
-
-func (r *runner) callWorkspaceSymbols(t *testing.T, uri span.URI, query string, typ tests.WorkspaceSymbolsTestType) {
- t.Helper()
-
- matcher := tests.WorkspaceSymbolsTestTypeToMatcher(typ)
- gotSymbols, err := source.WorkspaceSymbols(r.ctx, matcher, r.view.Options().SymbolStyle, []source.View{r.view}, query)
- if err != nil {
- t.Fatal(err)
- }
- got, err := tests.WorkspaceSymbolsString(r.ctx, r.data, uri, gotSymbols)
- if err != nil {
- t.Fatal(err)
- }
- got = filepath.ToSlash(tests.Normalize(got, r.normalizers))
- want := string(r.data.Golden(fmt.Sprintf("workspace_symbol-%s-%s", strings.ToLower(string(matcher)), query), uri.Filename(), func() ([]byte, error) {
- return []byte(got), nil
- }))
- if diff := tests.Diff(t, want, got); diff != "" {
- t.Error(diff)
- }
-}
-
-func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.SignatureHelp) {
- _, rng, err := spanToRange(r.data, spn)
- if err != nil {
- t.Fatal(err)
- }
- fh, err := r.snapshot.GetFile(r.ctx, spn.URI())
- if err != nil {
- t.Fatal(err)
- }
- gotSignature, gotActiveParameter, err := source.SignatureHelp(r.ctx, r.snapshot, fh, rng.Start)
- if err != nil {
- // Only fail if we got an error we did not expect.
- if want != nil {
- t.Fatalf("failed for %v: %v", spn, err)
- }
- return
- }
- if gotSignature == nil {
- if want != nil {
- t.Fatalf("got nil signature, but expected %v", want)
- }
- return
- }
- got := &protocol.SignatureHelp{
- Signatures: []protocol.SignatureInformation{*gotSignature},
- ActiveParameter: uint32(gotActiveParameter),
- }
- diff, err := tests.DiffSignatures(spn, want, got)
- if err != nil {
- t.Fatal(err)
- }
- if diff != "" {
- t.Error(diff)
- }
-}
-
-// These are pure LSP features, no source level functionality to be tested.
-func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {}
-
-func (r *runner) SuggestedFix(t *testing.T, spn span.Span, actionKinds []string, expectedActions int) {
-}
-func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span) {}
-func (r *runner) MethodExtraction(t *testing.T, start span.Span, end span.Span) {}
-func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) {}
-func (r *runner) AddImport(t *testing.T, uri span.URI, expectedImport string) {}
-
-func spanToRange(data *tests.Data, spn span.Span) (*protocol.ColumnMapper, protocol.Range, error) {
- m, err := data.Mapper(spn.URI())
- if err != nil {
- return nil, protocol.Range{}, err
- }
- srcRng, err := m.Range(spn)
- if err != nil {
- return nil, protocol.Range{}, err
- }
- return m, srcRng, nil
-}
diff --git a/internal/lsp/source/stub.go b/internal/lsp/source/stub.go
deleted file mode 100644
index 6810f1d20..000000000
--- a/internal/lsp/source/stub.go
+++ /dev/null
@@ -1,330 +0,0 @@
-// 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 source
-
-import (
- "bytes"
- "context"
- "fmt"
- "go/ast"
- "go/format"
- "go/parser"
- "go/token"
- "go/types"
- "strings"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/lsp/analysis/stubmethods"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/typeparams"
-)
-
-func stubSuggestedFixFunc(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, rng protocol.Range) (*analysis.SuggestedFix, error) {
- pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
- if err != nil {
- return nil, fmt.Errorf("GetParsedFile: %w", err)
- }
- nodes, pos, err := getStubNodes(pgf, rng)
- if err != nil {
- return nil, fmt.Errorf("getNodes: %w", err)
- }
- si := stubmethods.GetStubInfo(pkg.GetTypesInfo(), nodes, pos)
- if si == nil {
- return nil, fmt.Errorf("nil interface request")
- }
- parsedConcreteFile, concreteFH, err := getStubFile(ctx, si.Concrete.Obj(), snapshot)
- if err != nil {
- return nil, fmt.Errorf("getFile(concrete): %w", err)
- }
- var (
- methodsSrc []byte
- stubImports []*stubImport // additional imports needed for method stubs
- )
- if si.Interface.Pkg() == nil && si.Interface.Name() == "error" && si.Interface.Parent() == types.Universe {
- methodsSrc = stubErr(ctx, parsedConcreteFile.File, si, snapshot)
- } else {
- methodsSrc, stubImports, err = stubMethods(ctx, parsedConcreteFile.File, si, snapshot)
- }
- if err != nil {
- return nil, fmt.Errorf("stubMethods: %w", err)
- }
- nodes, _ = astutil.PathEnclosingInterval(parsedConcreteFile.File, si.Concrete.Obj().Pos(), si.Concrete.Obj().Pos())
- concreteSrc, err := concreteFH.Read()
- if err != nil {
- return nil, fmt.Errorf("error reading concrete file source: %w", err)
- }
- insertPos := snapshot.FileSet().Position(nodes[1].End()).Offset
- if insertPos >= len(concreteSrc) {
- return nil, fmt.Errorf("insertion position is past the end of the file")
- }
- var buf bytes.Buffer
- buf.Write(concreteSrc[:insertPos])
- buf.WriteByte('\n')
- buf.Write(methodsSrc)
- buf.Write(concreteSrc[insertPos:])
- fset := token.NewFileSet()
- newF, err := parser.ParseFile(fset, parsedConcreteFile.File.Name.Name, buf.Bytes(), parser.ParseComments)
- if err != nil {
- return nil, fmt.Errorf("could not reparse file: %w", err)
- }
- for _, imp := range stubImports {
- astutil.AddNamedImport(fset, newF, imp.Name, imp.Path)
- }
- var source bytes.Buffer
- err = format.Node(&source, fset, newF)
- if err != nil {
- return nil, fmt.Errorf("format.Node: %w", err)
- }
- diffEdits, err := snapshot.View().Options().ComputeEdits(parsedConcreteFile.URI, string(parsedConcreteFile.Src), source.String())
- if err != nil {
- return nil, err
- }
- var edits []analysis.TextEdit
- for _, edit := range diffEdits {
- rng, err := edit.Span.Range(parsedConcreteFile.Mapper.Converter)
- if err != nil {
- return nil, err
- }
- edits = append(edits, analysis.TextEdit{
- Pos: rng.Start,
- End: rng.End,
- NewText: []byte(edit.NewText),
- })
- }
- return &analysis.SuggestedFix{
- TextEdits: edits,
- }, nil
-}
-
-// stubMethods returns the Go code of all methods
-// that implement the given interface
-func stubMethods(ctx context.Context, concreteFile *ast.File, si *stubmethods.StubInfo, snapshot Snapshot) ([]byte, []*stubImport, error) {
- ifacePkg, err := deducePkgFromTypes(ctx, snapshot, si.Interface)
- if err != nil {
- return nil, nil, err
- }
- si.Concrete.Obj().Type()
- concMS := types.NewMethodSet(types.NewPointer(si.Concrete.Obj().Type()))
- missing, err := missingMethods(ctx, snapshot, concMS, si.Concrete.Obj().Pkg(), si.Interface, ifacePkg, map[string]struct{}{})
- if err != nil {
- return nil, nil, fmt.Errorf("missingMethods: %w", err)
- }
- if len(missing) == 0 {
- return nil, nil, fmt.Errorf("no missing methods found")
- }
- var (
- stubImports []*stubImport
- methodsBuffer bytes.Buffer
- )
- for _, mi := range missing {
- for _, m := range mi.missing {
- // TODO(marwan-at-work): this should share the same logic with source.FormatVarType
- // as it also accounts for type aliases.
- sig := types.TypeString(m.Type(), stubmethods.RelativeToFiles(si.Concrete.Obj().Pkg(), concreteFile, mi.file, func(name, path string) {
- for _, imp := range stubImports {
- if imp.Name == name && imp.Path == path {
- return
- }
- }
- stubImports = append(stubImports, &stubImport{name, path})
- }))
- _, err = methodsBuffer.Write(printStubMethod(methodData{
- Method: m.Name(),
- Concrete: getStubReceiver(si),
- Interface: deduceIfaceName(si.Concrete.Obj().Pkg(), si.Interface.Pkg(), si.Interface),
- Signature: strings.TrimPrefix(sig, "func"),
- }))
- if err != nil {
- return nil, nil, fmt.Errorf("error printing method: %w", err)
- }
- methodsBuffer.WriteRune('\n')
- }
- }
- return methodsBuffer.Bytes(), stubImports, nil
-}
-
-// stubErr reurns the Go code implementation
-// of an error interface relevant to the
-// concrete type
-func stubErr(ctx context.Context, concreteFile *ast.File, si *stubmethods.StubInfo, snapshot Snapshot) []byte {
- return printStubMethod(methodData{
- Method: "Error",
- Interface: "error",
- Concrete: getStubReceiver(si),
- Signature: "() string",
- })
-}
-
-// getStubReceiver returns the concrete type's name as a method receiver.
-// It accounts for type parameters if they exist.
-func getStubReceiver(si *stubmethods.StubInfo) string {
- var concrete string
- if si.Pointer {
- concrete += "*"
- }
- concrete += si.Concrete.Obj().Name()
- concrete += FormatTypeParams(typeparams.ForNamed(si.Concrete))
- return concrete
-}
-
-type methodData struct {
- Method string
- Interface string
- Concrete string
- Signature string
-}
-
-// printStubMethod takes methodData and returns Go code that represents the given method such as:
-// // {{ .Method }} implements {{ .Interface }}
-// func ({{ .Concrete }}) {{ .Method }}{{ .Signature }} {
-// panic("unimplemented")
-// }
-func printStubMethod(md methodData) []byte {
- var b bytes.Buffer
- fmt.Fprintf(&b, "// %s implements %s\n", md.Method, md.Interface)
- fmt.Fprintf(&b, "func (%s) %s%s {\n\t", md.Concrete, md.Method, md.Signature)
- fmt.Fprintln(&b, `panic("unimplemented")`)
- fmt.Fprintln(&b, "}")
- return b.Bytes()
-}
-
-func deducePkgFromTypes(ctx context.Context, snapshot Snapshot, ifaceObj types.Object) (Package, error) {
- pkgs, err := snapshot.KnownPackages(ctx)
- if err != nil {
- return nil, err
- }
- for _, p := range pkgs {
- if p.PkgPath() == ifaceObj.Pkg().Path() {
- return p, nil
- }
- }
- return nil, fmt.Errorf("pkg %q not found", ifaceObj.Pkg().Path())
-}
-
-func deduceIfaceName(concretePkg, ifacePkg *types.Package, ifaceObj types.Object) string {
- if concretePkg.Path() == ifacePkg.Path() {
- return ifaceObj.Name()
- }
- return fmt.Sprintf("%s.%s", ifacePkg.Name(), ifaceObj.Name())
-}
-
-func getStubNodes(pgf *ParsedGoFile, pRng protocol.Range) ([]ast.Node, token.Pos, error) {
- spn, err := pgf.Mapper.RangeSpan(pRng)
- if err != nil {
- return nil, 0, err
- }
- rng, err := spn.Range(pgf.Mapper.Converter)
- if err != nil {
- return nil, 0, err
- }
- nodes, _ := astutil.PathEnclosingInterval(pgf.File, rng.Start, rng.End)
- return nodes, rng.Start, nil
-}
-
-/*
-missingMethods takes a concrete type and returns any missing methods for the given interface as well as
-any missing interface that might have been embedded to its parent. For example:
-
-type I interface {
- io.Writer
- Hello()
-}
-returns []*missingInterface{
- {
- iface: *types.Interface (io.Writer),
- file: *ast.File: io.go,
- missing []*types.Func{Write},
- },
- {
- iface: *types.Interface (I),
- file: *ast.File: myfile.go,
- missing: []*types.Func{Hello}
- },
-}
-*/
-func missingMethods(ctx context.Context, snapshot Snapshot, concMS *types.MethodSet, concPkg *types.Package, ifaceObj types.Object, ifacePkg Package, visited map[string]struct{}) ([]*missingInterface, error) {
- iface, ok := ifaceObj.Type().Underlying().(*types.Interface)
- if !ok {
- return nil, fmt.Errorf("expected %v to be an interface but got %T", iface, ifaceObj.Type().Underlying())
- }
- missing := []*missingInterface{}
- for i := 0; i < iface.NumEmbeddeds(); i++ {
- eiface := iface.Embedded(i).Obj()
- depPkg := ifacePkg
- if eiface.Pkg().Path() != ifacePkg.PkgPath() {
- var err error
- depPkg, err = ifacePkg.GetImport(eiface.Pkg().Path())
- if err != nil {
- return nil, err
- }
- }
- em, err := missingMethods(ctx, snapshot, concMS, concPkg, eiface, depPkg, visited)
- if err != nil {
- return nil, err
- }
- missing = append(missing, em...)
- }
- parsedFile, _, err := getStubFile(ctx, ifaceObj, snapshot)
- if err != nil {
- return nil, fmt.Errorf("error getting iface file: %w", err)
- }
- mi := &missingInterface{
- pkg: ifacePkg,
- iface: iface,
- file: parsedFile.File,
- }
- if mi.file == nil {
- return nil, fmt.Errorf("could not find ast.File for %v", ifaceObj.Name())
- }
- for i := 0; i < iface.NumExplicitMethods(); i++ {
- method := iface.ExplicitMethod(i)
- // if the concrete type does not have the interface method
- if concMS.Lookup(concPkg, method.Name()) == nil {
- if _, ok := visited[method.Name()]; !ok {
- mi.missing = append(mi.missing, method)
- visited[method.Name()] = struct{}{}
- }
- }
- if sel := concMS.Lookup(concPkg, method.Name()); sel != nil {
- implSig := sel.Type().(*types.Signature)
- ifaceSig := method.Type().(*types.Signature)
- if !types.Identical(ifaceSig, implSig) {
- return nil, fmt.Errorf("mimsatched %q function signatures:\nhave: %s\nwant: %s", method.Name(), implSig, ifaceSig)
- }
- }
- }
- if len(mi.missing) > 0 {
- missing = append(missing, mi)
- }
- return missing, nil
-}
-
-func getStubFile(ctx context.Context, obj types.Object, snapshot Snapshot) (*ParsedGoFile, VersionedFileHandle, error) {
- objPos := snapshot.FileSet().Position(obj.Pos())
- objFile := span.URIFromPath(objPos.Filename)
- objectFH := snapshot.FindFile(objFile)
- _, goFile, err := GetParsedFile(ctx, snapshot, objectFH, WidestPackage)
- if err != nil {
- return nil, nil, fmt.Errorf("GetParsedFile: %w", err)
- }
- return goFile, objectFH, nil
-}
-
-// missingInterface represents an interface
-// that has all or some of its methods missing
-// from the destination concrete type
-type missingInterface struct {
- iface *types.Interface
- file *ast.File
- pkg Package
- missing []*types.Func
-}
-
-// stubImport represents a newly added import
-// statement to the concrete type. If name is not
-// empty, then that import is required to have that name.
-type stubImport struct{ Name, Path string }
diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go
deleted file mode 100644
index 16fb2223d..000000000
--- a/internal/lsp/source/symbols.go
+++ /dev/null
@@ -1,266 +0,0 @@
-// 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 source
-
-import (
- "context"
- "fmt"
- "go/ast"
- "go/types"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- errors "golang.org/x/xerrors"
-)
-
-func DocumentSymbols(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.DocumentSymbol, error) {
- ctx, done := event.Start(ctx, "source.DocumentSymbols")
- defer done()
-
- pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
- if err != nil {
- return nil, errors.Errorf("getting file for DocumentSymbols: %w", err)
- }
-
- info := pkg.GetTypesInfo()
- q := Qualifier(pgf.File, pkg.GetTypes(), info)
-
- symbolsToReceiver := make(map[types.Type]int)
- var symbols []protocol.DocumentSymbol
- for _, decl := range pgf.File.Decls {
- switch decl := decl.(type) {
- case *ast.FuncDecl:
- if decl.Name.Name == "_" {
- continue
- }
- if obj := info.ObjectOf(decl.Name); obj != nil {
- fs, err := funcSymbol(snapshot, pkg, decl, obj, q)
- if err != nil {
- return nil, err
- }
- // If function is a method, prepend the type of the method.
- if fs.Kind == protocol.Method {
- rtype := obj.Type().(*types.Signature).Recv().Type()
- fs.Name = fmt.Sprintf("(%s).%s", types.TypeString(rtype, q), 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
- }
- if obj := info.ObjectOf(spec.Name); obj != nil {
- ts, err := typeSymbol(snapshot, pkg, info, spec, obj, q)
- if err != nil {
- return nil, err
- }
- symbols = append(symbols, ts)
- symbolsToReceiver[obj.Type()] = len(symbols) - 1
- }
- case *ast.ValueSpec:
- for _, name := range spec.Names {
- if name.Name == "_" {
- continue
- }
- if obj := info.ObjectOf(name); obj != nil {
- vs, err := varSymbol(snapshot, pkg, decl, name, obj, q)
- if err != nil {
- return nil, err
- }
- symbols = append(symbols, vs)
- }
- }
- }
- }
- }
- }
- return symbols, nil
-}
-
-func funcSymbol(snapshot Snapshot, pkg Package, decl *ast.FuncDecl, obj types.Object, q types.Qualifier) (protocol.DocumentSymbol, error) {
- s := protocol.DocumentSymbol{
- Name: obj.Name(),
- Kind: protocol.Function,
- }
- var err error
- s.Range, err = nodeToProtocolRange(snapshot, pkg, decl)
- if err != nil {
- return protocol.DocumentSymbol{}, err
- }
- s.SelectionRange, err = nodeToProtocolRange(snapshot, pkg, decl.Name)
- if err != nil {
- return protocol.DocumentSymbol{}, err
- }
- sig, _ := obj.Type().(*types.Signature)
- if sig != nil {
- if sig.Recv() != nil {
- s.Kind = protocol.Method
- }
- s.Detail += "("
- for i := 0; i < sig.Params().Len(); i++ {
- if i > 0 {
- s.Detail += ", "
- }
- param := sig.Params().At(i)
- label := types.TypeString(param.Type(), q)
- if param.Name() != "" {
- label = fmt.Sprintf("%s %s", param.Name(), label)
- }
- s.Detail += label
- }
- s.Detail += ")"
- }
- return s, nil
-}
-
-func typeSymbol(snapshot Snapshot, pkg Package, info *types.Info, spec *ast.TypeSpec, obj types.Object, qf types.Qualifier) (protocol.DocumentSymbol, error) {
- s := protocol.DocumentSymbol{
- Name: obj.Name(),
- }
- s.Detail, _ = FormatType(obj.Type(), qf)
- s.Kind = typeToKind(obj.Type())
-
- var err error
- s.Range, err = nodeToProtocolRange(snapshot, pkg, spec)
- if err != nil {
- return protocol.DocumentSymbol{}, err
- }
- s.SelectionRange, err = nodeToProtocolRange(snapshot, pkg, spec.Name)
- if err != nil {
- return protocol.DocumentSymbol{}, err
- }
- t, objIsStruct := obj.Type().Underlying().(*types.Struct)
- st, specIsStruct := spec.Type.(*ast.StructType)
- if objIsStruct && specIsStruct {
- for i := 0; i < t.NumFields(); i++ {
- f := t.Field(i)
- child := protocol.DocumentSymbol{
- Name: f.Name(),
- Kind: protocol.Field,
- }
- child.Detail, _ = FormatType(f.Type(), qf)
-
- spanNode, selectionNode := nodesForStructField(i, st)
- if span, err := nodeToProtocolRange(snapshot, pkg, spanNode); err == nil {
- child.Range = span
- }
- if span, err := nodeToProtocolRange(snapshot, pkg, selectionNode); err == nil {
- child.SelectionRange = span
- }
- s.Children = append(s.Children, child)
- }
- }
-
- ti, objIsInterface := obj.Type().Underlying().(*types.Interface)
- ai, specIsInterface := spec.Type.(*ast.InterfaceType)
- if objIsInterface && specIsInterface {
- for i := 0; i < ti.NumExplicitMethods(); i++ {
- method := ti.ExplicitMethod(i)
- child := protocol.DocumentSymbol{
- Name: method.Name(),
- Kind: protocol.Method,
- }
-
- var spanNode, selectionNode ast.Node
- Methods:
- for _, f := range ai.Methods.List {
- for _, id := range f.Names {
- if id.Name == method.Name() {
- spanNode, selectionNode = f, id
- break Methods
- }
- }
- }
- child.Range, err = nodeToProtocolRange(snapshot, pkg, spanNode)
- if err != nil {
- return protocol.DocumentSymbol{}, err
- }
- child.SelectionRange, err = nodeToProtocolRange(snapshot, pkg, selectionNode)
- if err != nil {
- return protocol.DocumentSymbol{}, err
- }
- s.Children = append(s.Children, child)
- }
-
- for i := 0; i < ti.NumEmbeddeds(); i++ {
- embedded := ti.EmbeddedType(i)
- nt, isNamed := embedded.(*types.Named)
- if !isNamed {
- continue
- }
-
- child := protocol.DocumentSymbol{
- Name: types.TypeString(embedded, qf),
- }
- child.Kind = typeToKind(embedded)
- var spanNode, selectionNode ast.Node
- Embeddeds:
- for _, f := range ai.Methods.List {
- if len(f.Names) > 0 {
- continue
- }
-
- if t := info.TypeOf(f.Type); types.Identical(nt, t) {
- spanNode, selectionNode = f, f.Type
- break Embeddeds
- }
- }
- child.Range, err = nodeToProtocolRange(snapshot, pkg, spanNode)
- if err != nil {
- return protocol.DocumentSymbol{}, err
- }
- child.SelectionRange, err = nodeToProtocolRange(snapshot, pkg, selectionNode)
- if err != nil {
- return protocol.DocumentSymbol{}, err
- }
- s.Children = append(s.Children, child)
- }
- }
- return s, nil
-}
-
-func nodesForStructField(i int, st *ast.StructType) (span, selection ast.Node) {
- j := 0
- for _, field := range st.Fields.List {
- if len(field.Names) == 0 {
- if i == j {
- return field, field.Type
- }
- j++
- continue
- }
- for _, name := range field.Names {
- if i == j {
- return field, name
- }
- j++
- }
- }
- return nil, nil
-}
-
-func varSymbol(snapshot Snapshot, pkg Package, decl ast.Node, name *ast.Ident, obj types.Object, q types.Qualifier) (protocol.DocumentSymbol, error) {
- s := protocol.DocumentSymbol{
- Name: obj.Name(),
- Kind: protocol.Variable,
- }
- if _, ok := obj.(*types.Const); ok {
- s.Kind = protocol.Constant
- }
- var err error
- s.Range, err = nodeToProtocolRange(snapshot, pkg, decl)
- if err != nil {
- return protocol.DocumentSymbol{}, err
- }
- s.SelectionRange, err = nodeToProtocolRange(snapshot, pkg, name)
- if err != nil {
- return protocol.DocumentSymbol{}, err
- }
- s.Detail = types.TypeString(obj.Type(), q)
- return s, nil
-}
diff --git a/internal/lsp/source/types_format.go b/internal/lsp/source/types_format.go
deleted file mode 100644
index fcbf228ec..000000000
--- a/internal/lsp/source/types_format.go
+++ /dev/null
@@ -1,459 +0,0 @@
-// 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 source
-
-import (
- "bytes"
- "context"
- "fmt"
- "go/ast"
- "go/doc"
- "go/printer"
- "go/token"
- "go/types"
- "strings"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "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) {
- if types.IsInterface(typ) {
- detail = "interface{...}"
- kind = protocol.InterfaceCompletion
- } else if _, ok := typ.(*types.Struct); ok {
- detail = "struct{...}"
- kind = protocol.StructCompletion
- } else if typ != typ.Underlying() {
- detail, kind = FormatType(typ.Underlying(), qf)
- } 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 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
- }
- }
- params, _ := formatFieldList(ctx, s, decl.Type.Params, variadic)
- results, needResultParens := formatFieldList(ctx, s, decl.Type.Results, false)
- d := decl.Doc.Text()
- switch s.View().Options().HoverKind {
- case SynopsisDocumentation:
- d = doc.Synopsis(d)
- case NoDocumentation:
- d = ""
- }
- return &signature{
- doc: d,
- name: name,
- needResultParens: needResultParens,
- params: params,
- results: results,
- variadic: variadic,
- }, nil
-}
-
-var replacer = strings.NewReplacer(
- `ComplexType`, `complex128`,
- `FloatType`, `float64`,
- `IntegerType`, `int`,
-)
-
-func formatFieldList(ctx context.Context, snapshot Snapshot, 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, snapshot.FileSet(), p.Type); err != nil {
- event.Error(ctx, "unable to print type", nil, tag.Type.Of(p.Type))
- 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 *typeparams.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 Snapshot, pkg Package, sig *types.Signature, comment *ast.CommentGroup, qf types.Qualifier) *signature {
- var tparams []string
- tpList := typeparams.ForSignature(sig)
- 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 := FormatVarType(ctx, s, pkg, el, qf)
- 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 := FormatVarType(ctx, s, pkg, el, qf)
- 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.View().Options().HoverKind {
- case SynopsisDocumentation:
- d = doc.Synopsis(d)
- case NoDocumentation:
- d = ""
- }
- return &signature{
- doc: d,
- typeParams: tparams,
- params: params,
- results: results,
- variadic: sig.Variadic(),
- needResultParens: needResultParens,
- }
-}
-
-// 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.
-func FormatVarType(ctx context.Context, snapshot Snapshot, srcpkg Package, obj *types.Var, qf types.Qualifier) string {
- pkg, err := FindPackageFromPos(ctx, snapshot, obj.Pos())
- if err != nil {
- return types.TypeString(obj.Type(), qf)
- }
-
- expr, err := varType(ctx, snapshot, pkg, obj)
- if err != nil {
- return types.TypeString(obj.Type(), qf)
- }
-
- // 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.
- if typeparams.IsTypeParam(pkg.GetTypesInfo().Types[expr].Type) {
- return types.TypeString(obj.Type(), qf)
- }
-
- // 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.
- clonedInfo := make(map[token.Pos]*types.PkgName)
- qualified := cloneExpr(expr, pkg.GetTypesInfo(), clonedInfo)
-
- // If the request came from a different package than the one in which the
- // types are defined, we may need to modify the qualifiers.
- qualified = qualifyExpr(qualified, srcpkg, pkg, clonedInfo, qf)
- fmted := FormatNode(snapshot.FileSet(), qualified)
- return fmted
-}
-
-// varType returns the type expression for a *types.Var.
-func varType(ctx context.Context, snapshot Snapshot, pkg Package, obj *types.Var) (ast.Expr, error) {
- field, err := snapshot.PosToField(ctx, pkg, obj.Pos())
- if err != nil {
- return nil, err
- }
- if field == nil {
- return nil, fmt.Errorf("no declaration for object %s", obj.Name())
- }
- return field.Type, nil
-}
-
-// qualifyExpr applies the "pkgName." prefix to any *ast.Ident in the expr.
-func qualifyExpr(expr ast.Expr, srcpkg, pkg Package, clonedInfo map[token.Pos]*types.PkgName, qf types.Qualifier) ast.Expr {
- ast.Inspect(expr, func(n ast.Node) bool {
- switch n := n.(type) {
- case *ast.ArrayType, *ast.ChanType, *ast.Ellipsis,
- *ast.FuncType, *ast.MapType, *ast.ParenExpr,
- *ast.StarExpr, *ast.StructType, *ast.FieldList, *ast.Field:
- // These are the only types that are cloned by cloneExpr below,
- // so these are the only types that we can traverse and potentially
- // modify. This is not an ideal approach, but it works for now.
-
- // TODO(rFindley): can we eliminate this filtering entirely? This caused
- // bugs in the past (golang/go#50539)
- return true
- case *ast.SelectorExpr:
- // We may need to change any selectors in which the X is a package
- // name and the Sel is exported.
- x, ok := n.X.(*ast.Ident)
- if !ok {
- return false
- }
- obj, ok := clonedInfo[x.Pos()]
- if !ok {
- return false
- }
- x.Name = qf(obj.Imported())
- return false
- case *ast.Ident:
- if srcpkg == pkg {
- return false
- }
- // Only add the qualifier if the identifier is exported.
- if ast.IsExported(n.Name) {
- pkgName := qf(pkg.GetTypes())
- n.Name = pkgName + "." + n.Name
- }
- }
- return false
- })
- return expr
-}
-
-// cloneExpr only clones expressions that appear in the parameters or return
-// values of a function declaration. The original expression may be returned
-// to the caller in 2 cases:
-// (1) The expression has no pointer fields.
-// (2) The expression cannot appear in an *ast.FuncType, making it
-// unnecessary to clone.
-// This function also keeps track of selector expressions in which the X is a
-// package name and marks them in a map along with their type information, so
-// that this information can be used when rewriting the expression.
-//
-// NOTE: This function is tailored to the use case of qualifyExpr, and should
-// be used with caution.
-func cloneExpr(expr ast.Expr, info *types.Info, clonedInfo map[token.Pos]*types.PkgName) ast.Expr {
- switch expr := expr.(type) {
- case *ast.ArrayType:
- return &ast.ArrayType{
- Lbrack: expr.Lbrack,
- Elt: cloneExpr(expr.Elt, info, clonedInfo),
- Len: expr.Len,
- }
- case *ast.ChanType:
- return &ast.ChanType{
- Arrow: expr.Arrow,
- Begin: expr.Begin,
- Dir: expr.Dir,
- Value: cloneExpr(expr.Value, info, clonedInfo),
- }
- case *ast.Ellipsis:
- return &ast.Ellipsis{
- Ellipsis: expr.Ellipsis,
- Elt: cloneExpr(expr.Elt, info, clonedInfo),
- }
- case *ast.FuncType:
- return &ast.FuncType{
- Func: expr.Func,
- Params: cloneFieldList(expr.Params, info, clonedInfo),
- Results: cloneFieldList(expr.Results, info, clonedInfo),
- }
- case *ast.Ident:
- return cloneIdent(expr)
- case *ast.MapType:
- return &ast.MapType{
- Map: expr.Map,
- Key: cloneExpr(expr.Key, info, clonedInfo),
- Value: cloneExpr(expr.Value, info, clonedInfo),
- }
- case *ast.ParenExpr:
- return &ast.ParenExpr{
- Lparen: expr.Lparen,
- Rparen: expr.Rparen,
- X: cloneExpr(expr.X, info, clonedInfo),
- }
- case *ast.SelectorExpr:
- s := &ast.SelectorExpr{
- Sel: cloneIdent(expr.Sel),
- X: cloneExpr(expr.X, info, clonedInfo),
- }
- if x, ok := expr.X.(*ast.Ident); ok && ast.IsExported(expr.Sel.Name) {
- if obj, ok := info.ObjectOf(x).(*types.PkgName); ok {
- clonedInfo[s.X.Pos()] = obj
- }
- }
- return s
- case *ast.StarExpr:
- return &ast.StarExpr{
- Star: expr.Star,
- X: cloneExpr(expr.X, info, clonedInfo),
- }
- case *ast.StructType:
- return &ast.StructType{
- Struct: expr.Struct,
- Fields: cloneFieldList(expr.Fields, info, clonedInfo),
- Incomplete: expr.Incomplete,
- }
- default:
- return expr
- }
-}
-
-func cloneFieldList(fl *ast.FieldList, info *types.Info, clonedInfo map[token.Pos]*types.PkgName) *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 {
- var names []*ast.Ident
- for _, n := range f.Names {
- names = append(names, cloneIdent(n))
- }
- list = append(list, &ast.Field{
- Comment: f.Comment,
- Doc: f.Doc,
- Names: names,
- Tag: f.Tag,
- Type: cloneExpr(f.Type, info, clonedInfo),
- })
- }
- return &ast.FieldList{
- Closing: fl.Closing,
- Opening: fl.Opening,
- List: list,
- }
-}
-
-func cloneIdent(ident *ast.Ident) *ast.Ident {
- return &ast.Ident{
- NamePos: ident.NamePos,
- Name: ident.Name,
- Obj: ident.Obj,
- }
-}
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
deleted file mode 100644
index 71892eaa1..000000000
--- a/internal/lsp/source/util.go
+++ /dev/null
@@ -1,586 +0,0 @@
-// 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 source
-
-import (
- "context"
- "fmt"
- "go/ast"
- "go/printer"
- "go/token"
- "go/types"
- "path/filepath"
- "regexp"
- "sort"
- "strconv"
- "strings"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// MappedRange provides mapped protocol.Range for a span.Range, accounting for
-// UTF-16 code points.
-type MappedRange struct {
- spanRange span.Range
- m *protocol.ColumnMapper
-
- // protocolRange is the result of converting the spanRange using the mapper.
- // It is computed on-demand.
- protocolRange *protocol.Range
-}
-
-// NewMappedRange returns a MappedRange for the given start and end token.Pos.
-func NewMappedRange(fset *token.FileSet, m *protocol.ColumnMapper, start, end token.Pos) MappedRange {
- return MappedRange{
- spanRange: span.Range{
- FileSet: fset,
- Start: start,
- End: end,
- Converter: m.Converter,
- },
- m: m,
- }
-}
-
-func (s MappedRange) Range() (protocol.Range, error) {
- if s.protocolRange == nil {
- spn, err := s.spanRange.Span()
- if err != nil {
- return protocol.Range{}, err
- }
- prng, err := s.m.Range(spn)
- if err != nil {
- return protocol.Range{}, err
- }
- s.protocolRange = &prng
- }
- return *s.protocolRange, nil
-}
-
-func (s MappedRange) Span() (span.Span, error) {
- return s.spanRange.Span()
-}
-
-func (s MappedRange) SpanRange() span.Range {
- return s.spanRange
-}
-
-func (s MappedRange) URI() span.URI {
- return s.m.URI
-}
-
-// GetParsedFile is a convenience function that extracts the Package and
-// ParsedGoFile for a file in a Snapshot. pkgPolicy is one of NarrowestPackage/
-// WidestPackage.
-func GetParsedFile(ctx context.Context, snapshot Snapshot, fh FileHandle, pkgPolicy PackageFilter) (Package, *ParsedGoFile, error) {
- pkg, err := snapshot.PackageForFile(ctx, fh.URI(), TypecheckWorkspace, pkgPolicy)
- if err != nil {
- return nil, nil, err
- }
- pgh, err := pkg.File(fh.URI())
- return pkg, pgh, err
-}
-
-func IsGenerated(ctx context.Context, snapshot Snapshot, uri span.URI) bool {
- fh, err := snapshot.GetFile(ctx, uri)
- if err != nil {
- return false
- }
- pgf, err := snapshot.ParseGo(ctx, fh, ParseHeader)
- if err != nil {
- return false
- }
- tok := snapshot.FileSet().File(pgf.File.Pos())
- if tok == 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 pos := tok.Position(comment.Slash); pos.Column == 1 {
- return true
- }
- }
- }
- }
- return false
-}
-
-func nodeToProtocolRange(snapshot Snapshot, pkg Package, n ast.Node) (protocol.Range, error) {
- mrng, err := posToMappedRange(snapshot, pkg, n.Pos(), n.End())
- if err != nil {
- return protocol.Range{}, err
- }
- return mrng.Range()
-}
-
-func objToMappedRange(snapshot Snapshot, pkg Package, obj types.Object) (MappedRange, error) {
- 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() {
- return posToMappedRange(snapshot, pkg, obj.Pos(), obj.Pos()+token.Pos(len(pkgName.Imported().Path())+2))
- }
- }
- return nameToMappedRange(snapshot, pkg, obj.Pos(), obj.Name())
-}
-
-func nameToMappedRange(snapshot Snapshot, pkg Package, pos token.Pos, name string) (MappedRange, error) {
- return posToMappedRange(snapshot, pkg, pos, pos+token.Pos(len(name)))
-}
-
-func posToMappedRange(snapshot Snapshot, pkg Package, pos, end token.Pos) (MappedRange, error) {
- logicalFilename := snapshot.FileSet().File(pos).Position(pos).Filename
- pgf, _, err := findFileInDeps(pkg, span.URIFromPath(logicalFilename))
- if err != nil {
- return MappedRange{}, err
- }
- if !pos.IsValid() {
- return MappedRange{}, errors.Errorf("invalid position for %v", pos)
- }
- if !end.IsValid() {
- return MappedRange{}, errors.Errorf("invalid position for %v", end)
- }
- return NewMappedRange(snapshot.FileSet(), pgf.Mapper, pos, end), nil
-}
-
-// Matches cgo generated comment as well as the proposed standard:
-// https://golang.org/s/generatedcode
-var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`)
-
-// FileKindForLang returns the file kind associated with the given language ID,
-// or UnknownKind if the language ID is not recognized.
-func FileKindForLang(langID string) FileKind {
- 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
- }
-}
-
-func (k FileKind) 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("unk%d", k)
- }
-}
-
-// nodeAtPos returns the index and the node whose position is contained inside
-// the node list.
-func nodeAtPos(nodes []ast.Node, pos token.Pos) (ast.Node, int) {
- if nodes == nil {
- return nil, -1
- }
- for i, node := range nodes {
- if node.Pos() <= pos && pos <= node.End() {
- return node, i
- }
- }
- return nil, -1
-}
-
-// IsInterface returns if a types.Type is an interface
-func IsInterface(T types.Type) bool {
- return T != nil && types.IsInterface(T)
-}
-
-// 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 {
- return ""
- }
- return buf.String()
-}
-
-// Deref returns a pointer's element type, traversing as many levels as needed.
-// Otherwise it returns typ.
-//
-// It can return a pointer type for cyclic types (see golang/go#45510).
-func Deref(typ types.Type) types.Type {
- var seen map[types.Type]struct{}
- for {
- p, ok := typ.Underlying().(*types.Pointer)
- if !ok {
- return typ
- }
- if _, ok := seen[p.Elem()]; ok {
- return typ
- }
-
- typ = p.Elem()
-
- if seen == nil {
- seen = make(map[types.Type]struct{})
- }
- seen[typ] = struct{}{}
- }
-}
-
-func SortDiagnostics(d []*Diagnostic) {
- sort.Slice(d, func(i int, j int) bool {
- return CompareDiagnostic(d[i], d[j]) < 0
- })
-}
-
-func CompareDiagnostic(a, b *Diagnostic) int {
- if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
- return r
- }
- if a.Source < b.Source {
- return -1
- }
- if a.Message < b.Message {
- return -1
- }
- if a.Message == b.Message {
- return 0
- }
- return 1
-}
-
-// FindPackageFromPos finds the first package containing pos in its
-// type-checked AST.
-func FindPackageFromPos(ctx context.Context, snapshot Snapshot, pos token.Pos) (Package, error) {
- tok := snapshot.FileSet().File(pos)
- if tok == nil {
- return nil, errors.Errorf("no file for pos %v", pos)
- }
- uri := span.URIFromPath(tok.Name())
- pkgs, err := snapshot.PackagesForFile(ctx, uri, TypecheckAll, true)
- if err != nil {
- return nil, err
- }
- // Only return the package if it actually type-checked the given position.
- for _, pkg := range pkgs {
- parsed, err := pkg.File(uri)
- if err != nil {
- return nil, err
- }
- if parsed == nil {
- continue
- }
- if parsed.Tok.Base() != tok.Base() {
- continue
- }
- return pkg, nil
- }
- return nil, errors.Errorf("no package for given file position")
-}
-
-// findFileInDeps finds uri in pkg or its dependencies.
-func findFileInDeps(pkg Package, uri span.URI) (*ParsedGoFile, Package, error) {
- queue := []Package{pkg}
- seen := make(map[string]bool)
-
- for len(queue) > 0 {
- pkg := queue[0]
- queue = queue[1:]
- seen[pkg.ID()] = true
-
- if pgf, err := pkg.File(uri); err == nil {
- return pgf, pkg, nil
- }
- for _, dep := range pkg.Imports() {
- if !seen[dep.ID()] {
- queue = append(queue, dep)
- }
- }
- }
- return nil, nil, errors.Errorf("no file for %s in package %s", uri, pkg.ID())
-}
-
-// ImportPath returns the unquoted import path of s,
-// or "" if the path is not properly quoted.
-func ImportPath(s *ast.ImportSpec) string {
- t, err := strconv.Unquote(s.Path.Value)
- if err != nil {
- return ""
- }
- return t
-}
-
-// NodeContains returns true if a node encloses a given position pos.
-func NodeContains(n ast.Node, pos token.Pos) bool {
- return n != nil && n.Pos() <= pos && pos <= n.End()
-}
-
-// 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<len(path), is the possibly nil Scope of path[i].
- var scopes []*types.Scope
- for _, n := range path {
- // Include *FuncType scope if pos is inside the function body.
- switch node := n.(type) {
- case *ast.FuncDecl:
- if node.Body != nil && NodeContains(node.Body, pos) {
- n = node.Type
- }
- case *ast.FuncLit:
- if node.Body != nil && NodeContains(node.Body, pos) {
- n = node.Type
- }
- }
- scopes = append(scopes, info.Scopes[n])
- }
- return scopes
-}
-
-// Qualifier returns a function that appropriately formats a types.PkgName
-// appearing in a *ast.File.
-func Qualifier(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 {
- var obj types.Object
- if imp.Name != nil {
- obj = info.Defs[imp.Name]
- } else {
- obj = info.Implicits[imp]
- }
- if pkgname, ok := obj.(*types.PkgName); ok {
- 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()
- }
-}
-
-// 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
-}
-
-// honorSymlinks toggles whether or not we consider symlinks when comparing
-// file or directory URIs.
-const honorSymlinks = false
-
-func CompareURI(left, right span.URI) int {
- if honorSymlinks {
- return span.CompareURI(left, right)
- }
- if left == right {
- return 0
- }
- if left < right {
- return -1
- }
- return 1
-}
-
-// InDir checks whether path is in the file tree rooted at dir.
-// InDir makes some effort to succeed even in the presence of symbolic links.
-//
-// Copied and slightly adjusted from go/src/cmd/go/internal/search/search.go.
-func InDir(dir, path string) bool {
- if inDirLex(dir, path) {
- return true
- }
- if !honorSymlinks {
- return false
- }
- xpath, err := filepath.EvalSymlinks(path)
- if err != nil || xpath == path {
- xpath = ""
- } else {
- if inDirLex(dir, xpath) {
- return true
- }
- }
-
- xdir, err := filepath.EvalSymlinks(dir)
- if err == nil && xdir != dir {
- if inDirLex(xdir, path) {
- return true
- }
- if xpath != "" {
- if inDirLex(xdir, xpath) {
- return true
- }
- }
- }
- return false
-}
-
-// inDirLex is like inDir but only checks the lexical form of the file names.
-// It does not consider symbolic links.
-//
-// Copied from go/src/cmd/go/internal/search/search.go.
-func inDirLex(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
- }
-}
-
-// IsValidImport returns whether importPkgPath is importable
-// by pkgPath
-func IsValidImport(pkgPath, importPkgPath string) bool {
- i := strings.LastIndex(string(importPkgPath), "/internal/")
- if i == -1 {
- return true
- }
- if IsCommandLineArguments(string(pkgPath)) {
- return true
- }
- return strings.HasPrefix(string(pkgPath), string(importPkgPath[:i]))
-}
-
-// 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(s string) bool {
- return strings.Contains(s, "command-line-arguments")
-}
-
-// Offset returns tok.Offset(pos), but first checks that the pos is in range
-// for the given file.
-func Offset(tok *token.File, pos token.Pos) (int, error) {
- if !InRange(tok, pos) {
- return -1, fmt.Errorf("pos %v is not in range for file [%v:%v)", pos, tok.Base(), tok.Base()+tok.Size())
- }
- return tok.Offset(pos), nil
-}
-
-// Pos returns tok.Pos(offset), but first checks that the offset is valid for
-// the given file.
-func Pos(tok *token.File, offset int) (token.Pos, error) {
- if offset < 0 || offset > tok.Size() {
- return token.NoPos, fmt.Errorf("offset %v is not in range for file of size %v", offset, tok.Size())
- }
- return tok.Pos(offset), nil
-}
-
-// InRange reports whether the given position is in the given token.File.
-func InRange(tok *token.File, pos token.Pos) bool {
- size := tok.Pos(tok.Size())
- return int(pos) >= tok.Base() && pos <= size
-}
-
-// LineToRange creates a Range spanning start and end.
-func LineToRange(m *protocol.ColumnMapper, uri span.URI, start, end modfile.Position) (protocol.Range, error) {
- return ByteOffsetsToRange(m, uri, start.Byte, end.Byte)
-}
-
-// ByteOffsetsToRange creates a range spanning start and end.
-func ByteOffsetsToRange(m *protocol.ColumnMapper, uri span.URI, start, end int) (protocol.Range, error) {
- line, col, err := m.Converter.ToPosition(start)
- if err != nil {
- return protocol.Range{}, err
- }
- s := span.NewPoint(line, col, start)
- line, col, err = m.Converter.ToPosition(end)
- if err != nil {
- return protocol.Range{}, err
- }
- e := span.NewPoint(line, col, end)
- return m.Range(span.New(uri, s, e))
-}
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
deleted file mode 100644
index 4d7d411e0..000000000
--- a/internal/lsp/source/view.go
+++ /dev/null
@@ -1,696 +0,0 @@
-// 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 source
-
-import (
- "bytes"
- "context"
- "fmt"
- "go/ast"
- "go/scanner"
- "go/token"
- "go/types"
- "io"
- "strings"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/mod/module"
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/packages"
- "golang.org/x/tools/internal/gocommand"
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/progress"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// Snapshot represents the current state for the given view.
-type Snapshot interface {
- ID() uint64
-
- // View returns the View associated with this snapshot.
- View() View
-
- // BackgroundContext returns a context used for all background processing
- // on behalf of this snapshot.
- BackgroundContext() context.Context
-
- // Fileset returns the Fileset used to parse all the Go files in this snapshot.
- FileSet() *token.FileSet
-
- // ValidBuildConfiguration returns true if there is some error in the
- // user's workspace. In particular, if they are both outside of a module
- // and their GOPATH.
- ValidBuildConfiguration() bool
-
- // WriteEnv writes the view-specific environment to the io.Writer.
- WriteEnv(ctx context.Context, w io.Writer) error
-
- // FindFile returns the FileHandle for the given URI, if it is already
- // in the given snapshot.
- FindFile(uri span.URI) VersionedFileHandle
-
- // GetVersionedFile returns the VersionedFileHandle for a given URI,
- // initializing it if it is not already part of the snapshot.
- GetVersionedFile(ctx context.Context, uri span.URI) (VersionedFileHandle, error)
-
- // GetFile returns the FileHandle for a given URI, initializing it if it is
- // not already part of the snapshot.
- GetFile(ctx context.Context, uri span.URI) (FileHandle, error)
-
- // AwaitInitialized waits until the snapshot's view is initialized.
- AwaitInitialized(ctx context.Context)
-
- // IsOpen returns whether the editor currently has a file open.
- IsOpen(uri span.URI) bool
-
- // IgnoredFile reports if a file would be ignored by a `go list` of the whole
- // workspace.
- IgnoredFile(uri span.URI) bool
-
- // Templates returns the .tmpl files
- Templates() map[span.URI]VersionedFileHandle
-
- // ParseGo returns the parsed AST for the file.
- // If the file is not available, returns nil and an error.
- ParseGo(ctx context.Context, fh FileHandle, mode ParseMode) (*ParsedGoFile, error)
-
- // PosToField is a cache of *ast.Fields by token.Pos. This allows us
- // to quickly find corresponding *ast.Field node given a *types.Var.
- // We must refer to the AST to render type aliases properly when
- // formatting signatures and other types.
- PosToField(ctx context.Context, pkg Package, pos token.Pos) (*ast.Field, error)
-
- // PosToDecl maps certain objects' positions to their surrounding
- // ast.Decl. This mapping is used when building the documentation
- // string for the objects.
- PosToDecl(ctx context.Context, pkg Package, pos token.Pos) (ast.Decl, error)
-
- // DiagnosePackage returns basic diagnostics, including list, parse, and type errors
- // for pkg, grouped by file.
- DiagnosePackage(ctx context.Context, pkg Package) (map[span.URI][]*Diagnostic, error)
-
- // Analyze runs the analyses for the given package at this snapshot.
- Analyze(ctx context.Context, pkgID string, analyzers []*Analyzer) ([]*Diagnostic, error)
-
- // RunGoCommandPiped runs the given `go` command, writing its output
- // to stdout and stderr. Verb, Args, and WorkingDir must be specified.
- RunGoCommandPiped(ctx context.Context, mode InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error
-
- // RunGoCommandDirect runs the given `go` command. Verb, Args, and
- // WorkingDir must be specified.
- RunGoCommandDirect(ctx context.Context, mode InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error)
-
- // RunGoCommands runs a series of `go` commands that updates the go.mod
- // and go.sum file for wd, and returns their updated contents.
- RunGoCommands(ctx context.Context, allowNetwork bool, wd string, run func(invoke func(...string) (*bytes.Buffer, error)) error) (bool, []byte, []byte, error)
-
- // RunProcessEnvFunc runs fn with the process env for this snapshot's view.
- // Note: the process env contains cached module and filesystem state.
- RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error) error
-
- // ModFiles are the go.mod files enclosed in the snapshot's view and known
- // to the snapshot.
- ModFiles() []span.URI
-
- // ParseMod is used to parse go.mod files.
- ParseMod(ctx context.Context, fh FileHandle) (*ParsedModule, error)
-
- // ModWhy returns the results of `go mod why` for the module specified by
- // the given go.mod file.
- ModWhy(ctx context.Context, fh FileHandle) (map[string]string, error)
-
- // ModTidy returns the results of `go mod tidy` for the module specified by
- // the given go.mod file.
- ModTidy(ctx context.Context, pm *ParsedModule) (*TidiedModule, error)
-
- // GoModForFile returns the URI of the go.mod file for the given URI.
- GoModForFile(uri span.URI) span.URI
-
- // WorkFile, if non-empty, is the go.work file for the workspace.
- WorkFile() span.URI
-
- // ParseWork is used to parse go.work files.
- ParseWork(ctx context.Context, fh FileHandle) (*ParsedWorkFile, error)
-
- // BuiltinFile returns information about the special builtin package.
- BuiltinFile(ctx context.Context) (*ParsedGoFile, error)
-
- // IsBuiltin reports whether uri is part of the builtin package.
- IsBuiltin(ctx context.Context, uri span.URI) bool
-
- // PackagesForFile returns the packages that this file belongs to, checked
- // in mode.
- PackagesForFile(ctx context.Context, uri span.URI, mode TypecheckMode, includeTestVariants bool) ([]Package, error)
-
- // PackageForFile returns a single package that this file belongs to,
- // checked in mode and filtered by the package policy.
- PackageForFile(ctx context.Context, uri span.URI, mode TypecheckMode, selectPackage PackageFilter) (Package, error)
-
- // GetActiveReverseDeps returns the active files belonging to the reverse
- // dependencies of this file's package, checked in TypecheckWorkspace mode.
- GetReverseDependencies(ctx context.Context, id string) ([]Package, error)
-
- // CachedImportPaths returns all the imported packages loaded in this
- // snapshot, indexed by their import path and checked in TypecheckWorkspace
- // mode.
- CachedImportPaths(ctx context.Context) (map[string]Package, error)
-
- // KnownPackages returns all the packages loaded in this snapshot, checked
- // in TypecheckWorkspace mode.
- KnownPackages(ctx context.Context) ([]Package, error)
-
- // ActivePackages returns the packages considered 'active' in the workspace.
- //
- // In normal memory mode, this is all workspace packages. In degraded memory
- // mode, this is just the reverse transitive closure of open packages.
- ActivePackages(ctx context.Context) ([]Package, error)
-
- // Symbols returns all symbols in the snapshot.
- Symbols(ctx context.Context) (map[span.URI][]Symbol, error)
-
- // Metadata returns package metadata associated with the given file URI.
- MetadataForFile(ctx context.Context, uri span.URI) ([]Metadata, error)
-
- // GetCriticalError returns any critical errors in the workspace.
- GetCriticalError(ctx context.Context) *CriticalError
-
- // BuildGoplsMod generates a go.mod file for all modules in the workspace.
- // It bypasses any existing gopls.mod.
- BuildGoplsMod(ctx context.Context) (*modfile.File, error)
-}
-
-// PackageFilter sets how a package is filtered out from a set of packages
-// containing a given file.
-type PackageFilter int
-
-const (
- // NarrowestPackage picks the "narrowest" package for a given file.
- // By "narrowest" package, we mean the package 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.
- NarrowestPackage PackageFilter = iota
-
- // WidestPackage returns the Package containing the most files.
- // This is useful for something like diagnostics, where we'd prefer to
- // offer diagnostics for as many files as possible.
- WidestPackage
-)
-
-// InvocationFlags represents the settings of a particular go command invocation.
-// It is a mode, plus a set of flag bits.
-type InvocationFlags int
-
-const (
- // Normal is appropriate for commands that might be run by a user and don't
- // deliberately modify go.mod files, e.g. `go test`.
- Normal InvocationFlags = iota
- // WriteTemporaryModFile is for commands that need information from a
- // modified version of the user's go.mod file, e.g. `go mod tidy` used to
- // generate diagnostics.
- WriteTemporaryModFile
- // LoadWorkspace is for packages.Load, and other operations that should
- // consider the whole workspace at once.
- LoadWorkspace
-
- // AllowNetwork is a flag bit that indicates the invocation should be
- // allowed to access the network.
- AllowNetwork InvocationFlags = 1 << 10
-)
-
-func (m InvocationFlags) Mode() InvocationFlags {
- return m & (AllowNetwork - 1)
-}
-
-func (m InvocationFlags) AllowNetwork() bool {
- return m&AllowNetwork != 0
-}
-
-// View represents a single workspace.
-// This is the level at which we maintain configuration like working directory
-// and build tags.
-type View interface {
- // Name returns the name this view was constructed with.
- Name() string
-
- // Folder returns the folder with which this view was created.
- Folder() span.URI
-
- // Shutdown closes this view, and detaches it from its session.
- Shutdown(ctx context.Context)
-
- // Options returns a copy of the Options for this view.
- Options() *Options
-
- // SetOptions sets the options of this view to new values.
- // Calling this may cause the view to be invalidated and a replacement view
- // added to the session. If so the new view will be returned, otherwise the
- // original one will be.
- SetOptions(context.Context, *Options) (View, error)
-
- // Snapshot returns the current snapshot for the view.
- Snapshot(ctx context.Context) (Snapshot, func())
-
- // Rebuild rebuilds the current view, replacing the original view in its session.
- Rebuild(ctx context.Context) (Snapshot, func(), error)
-
- // IsGoPrivatePath reports whether target is a private import path, as identified
- // by the GOPRIVATE environment variable.
- IsGoPrivatePath(path string) bool
-
- // ModuleUpgrades returns known module upgrades.
- ModuleUpgrades() map[string]string
-
- // RegisterModuleUpgrades registers that upgrades exist for the given modules.
- RegisterModuleUpgrades(upgrades map[string]string)
-
- // FileKind returns the type of a file
- FileKind(FileHandle) FileKind
-}
-
-// A FileSource maps uris to FileHandles. This abstraction exists both for
-// testability, and so that algorithms can be run equally on session and
-// snapshot files.
-type FileSource interface {
- // GetFile returns the FileHandle for a given URI.
- GetFile(ctx context.Context, uri span.URI) (FileHandle, error)
-}
-
-// A ParsedGoFile contains the results of parsing a Go file.
-type ParsedGoFile struct {
- URI span.URI
- Mode ParseMode
- 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
- Mapper *protocol.ColumnMapper
- ParseErr scanner.ErrorList
-}
-
-// A ParsedModule contains the results of parsing a go.mod file.
-type ParsedModule struct {
- URI span.URI
- File *modfile.File
- Mapper *protocol.ColumnMapper
- ParseErrors []*Diagnostic
-}
-
-// A ParsedWorkFile contains the results of parsing a go.work file.
-type ParsedWorkFile struct {
- URI span.URI
- File *modfile.WorkFile
- Mapper *protocol.ColumnMapper
- ParseErrors []*Diagnostic
-}
-
-// 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
-}
-
-// Metadata represents package metadata retrieved from go/packages.
-type Metadata interface {
- // PackageName is the package name.
- PackageName() string
-
- // PackagePath is the package path.
- PackagePath() string
-
- // ModuleInfo returns the go/packages module information for the given package.
- ModuleInfo() *packages.Module
-}
-
-// Session represents a single connection from a client.
-// This is the level at which things like open files are maintained on behalf
-// of the client.
-// A session may have many active views at any given time.
-type Session interface {
- // ID returns the unique identifier for this session on this server.
- ID() string
- // 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.
- NewView(ctx context.Context, name string, folder span.URI, options *Options) (View, Snapshot, func(), error)
-
- // Cache returns the cache that created this session, for debugging only.
- Cache() interface{}
-
- // View returns a view with a matching name, if the session has one.
- View(name string) View
-
- // ViewOf returns a view corresponding to the given URI.
- ViewOf(uri span.URI) (View, error)
-
- // Views returns the set of active views built by this session.
- Views() []View
-
- // Shutdown the session and all views it has created.
- Shutdown(ctx context.Context)
-
- // GetFile returns a handle for the specified file.
- GetFile(ctx context.Context, uri span.URI) (FileHandle, error)
-
- // DidModifyFile 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.
- DidModifyFiles(ctx context.Context, changes []FileModification) (map[Snapshot][]span.URI, []func(), error)
-
- // ExpandModificationsToDirectories returns the set of changes with the
- // directory changes removed and expanded to include all of the files in
- // the directory.
- ExpandModificationsToDirectories(ctx context.Context, changes []FileModification) []FileModification
-
- // Overlays returns a slice of file overlays for the session.
- Overlays() []Overlay
-
- // Options returns a copy of the SessionOptions for this session.
- Options() *Options
-
- // SetOptions sets the options of this session to new values.
- SetOptions(*Options)
-
- // FileWatchingGlobPatterns returns glob patterns to watch every directory
- // known by the view. For views within a module, this is the module root,
- // any directory in the module root, and any replace targets.
- FileWatchingGlobPatterns(ctx context.Context) map[string]struct{}
-
- // SetProgressTracker sets the progress tracker for the session.
- SetProgressTracker(tracker *progress.Tracker)
-}
-
-var ErrViewExists = errors.New("view already exists for session")
-
-// Overlay is the type for a file held in memory on a session.
-type Overlay interface {
- Kind() FileKind
- VersionedFileHandle
-}
-
-// FileModification represents a modification to a file.
-type FileModification struct {
- URI span.URI
- Action FileAction
-
- // 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 string
-}
-
-type FileAction int
-
-const (
- UnknownFileAction = FileAction(iota)
- Open
- Change
- Close
- Save
- Create
- Delete
- InvalidateMetadata
-)
-
-func (a FileAction) 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"
- case InvalidateMetadata:
- return "InvalidateMetadata"
- default:
- return "Unknown"
- }
-}
-
-var ErrTmpModfileUnsupported = errors.New("-modfile is unsupported for this Go version")
-var ErrNoModOnDisk = errors.New("go.mod file is not on disk")
-
-func IsNonFatalGoModError(err error) bool {
- return err == ErrTmpModfileUnsupported || err == ErrNoModOnDisk
-}
-
-// ParseMode controls the content of the AST produced when parsing a source file.
-type ParseMode int
-
-const (
- // ParseHeader specifies that the main package declaration and imports are needed.
- // This is the mode used when attempting to examine the package graph structure.
- ParseHeader ParseMode = iota
-
- // ParseExported specifies that the package is used only as a dependency,
- // and only its exported declarations are needed. More may be included if
- // necessary to avoid type errors.
- ParseExported
-
- // ParseFull specifies the full AST is needed.
- // This is used for files of direct interest where the entire contents must
- // be considered.
- ParseFull
-)
-
-// TypecheckMode controls what kind of parsing should be done (see ParseMode)
-// while type checking a package.
-type TypecheckMode int
-
-const (
- // Invalid default value.
- TypecheckUnknown TypecheckMode = iota
- // TypecheckFull means to use ParseFull.
- TypecheckFull
- // TypecheckWorkspace means to use ParseFull for workspace packages, and
- // ParseExported for others.
- TypecheckWorkspace
- // TypecheckAll means ParseFull for workspace packages, and both Full and
- // Exported for others. Only valid for some functions.
- TypecheckAll
-)
-
-type VersionedFileHandle interface {
- FileHandle
- Version() int32
- Session() string
-
- // LSPIdentity returns the version identity of a file.
- VersionedFileIdentity() VersionedFileIdentity
-}
-
-type VersionedFileIdentity struct {
- URI span.URI
-
- // SessionID is the ID of the LSP session.
- SessionID string
-
- // Version is the version of the file, as specified by the client. It should
- // only be set in combination with SessionID.
- Version int32
-}
-
-// FileHandle represents a handle to a specific version of a single file.
-type FileHandle interface {
- URI() span.URI
-
- // FileIdentity returns a FileIdentity for the file, even if there was an
- // error reading it.
- FileIdentity() FileIdentity
- // Read reads the contents of a file.
- // If the file is not available, returns a nil slice and an error.
- Read() ([]byte, error)
- // Saved reports whether the file has the same content on disk.
- Saved() bool
-}
-
-// FileIdentity uniquely identifies a file at a version from a FileSystem.
-type FileIdentity struct {
- URI span.URI
-
- // Identifier represents a unique identifier for the file's content.
- Hash string
-}
-
-func (id FileIdentity) String() string {
- return fmt.Sprintf("%s%s", id.URI, id.Hash)
-}
-
-// FileKind describes the kind of the file in question.
-// It can be one of Go,mod, Sum, or Tmpl.
-type FileKind int
-
-const (
- // UnknownKind is a file type we don't know about.
- UnknownKind = FileKind(iota)
-
- // Go is a normal 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
-)
-
-// Analyzer represents a go/analysis analyzer with some boolean properties
-// that let the user know how to use the analyzer.
-type Analyzer struct {
- Analyzer *analysis.Analyzer
-
- // Enabled reports whether the analyzer is enabled. This value can be
- // configured per-analysis in user settings. For staticcheck analyzers,
- // the value of the Staticcheck setting overrides this field.
- Enabled bool
-
- // Fix is the name of the suggested fix name used to invoke the suggested
- // fixes for the analyzer. It is non-empty if we expect this analyzer to
- // provide its fix separately from its diagnostics. That is, we should apply
- // the analyzer's suggested fixes through a Command, not a TextEdit.
- Fix string
-
- // ActionKind is the kind of code action this analyzer produces. If
- // unspecified the type defaults to quickfix.
- ActionKind []protocol.CodeActionKind
-
- // Severity is the severity set for diagnostics reported by this
- // analyzer. If left unset it defaults to Warning.
- Severity protocol.DiagnosticSeverity
-}
-
-func (a Analyzer) IsEnabled(view View) bool {
- // Staticcheck analyzers can only be enabled when staticcheck is on.
- if _, ok := view.Options().StaticcheckAnalyzers[a.Analyzer.Name]; ok {
- if !view.Options().Staticcheck {
- return false
- }
- }
- if enabled, ok := view.Options().Analyses[a.Analyzer.Name]; ok {
- return enabled
- }
- return a.Enabled
-}
-
-// Package represents a Go package that has been type-checked. It maintains
-// only the relevant fields of a *go/packages.Package.
-type Package interface {
- ID() string
- Name() string
- PkgPath() string
- CompiledGoFiles() []*ParsedGoFile
- File(uri span.URI) (*ParsedGoFile, error)
- GetSyntax() []*ast.File
- GetTypes() *types.Package
- GetTypesInfo() *types.Info
- GetTypesSizes() types.Sizes
- IsIllTyped() bool
- ForTest() string
- GetImport(pkgPath string) (Package, error)
- MissingDependencies() []string
- Imports() []Package
- Version() *module.Version
- HasListOrParseErrors() bool
- HasTypeErrors() bool
- ParseMode() ParseMode
-}
-
-type CriticalError struct {
- // MainError is the primary error. Must be non-nil.
- MainError error
- // DiagList contains any supplemental (structured) diagnostics.
- DiagList []*Diagnostic
-}
-
-// An Diagnostic corresponds to an LSP Diagnostic.
-// https://microsoft.github.io/language-server-protocol/specification#diagnostic
-type Diagnostic struct {
- URI span.URI
- Range protocol.Range
- Severity protocol.DiagnosticSeverity
- Code string
- 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 []RelatedInformation
-
- // 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
- Analyzer *Analyzer
-}
-
-type DiagnosticSource string
-
-const (
- UnknownError DiagnosticSource = "<Unknown source>"
- ListError DiagnosticSource = "go list"
- ParseError DiagnosticSource = "syntax"
- TypeError DiagnosticSource = "compiler"
- ModTidyError DiagnosticSource = "go mod tidy"
- OptimizationDetailsError DiagnosticSource = "optimizer details"
- UpgradeNotification DiagnosticSource = "upgrade available"
- TemplateError DiagnosticSource = "template"
- WorkFileError DiagnosticSource = "go.work file"
-)
-
-func AnalyzerErrorKind(name string) DiagnosticSource {
- return DiagnosticSource(name)
-}
-
-var (
- PackagesLoadError = errors.New("packages.Load error")
-)
-
-// WorkspaceModuleVersion is the nonexistent pseudoversion suffix used in the
-// construction of the workspace module. It is exported so that we can make
-// sure not to show this version to end users in error messages, to avoid
-// confusion.
-// The major version is not included, as that depends on the module path.
-//
-// If workspace module A is dependent on workspace module B, we need our
-// nonexistant version to be greater than the version A mentions.
-// Otherwise, the go command will try to update to that version. Use a very
-// high minor version to make that more likely.
-const workspaceModuleVersion = ".9999999.0-goplsworkspace"
-
-func IsWorkspaceModuleVersion(version string) bool {
- return strings.HasSuffix(version, workspaceModuleVersion)
-}
-
-func WorkspaceModuleVersion(majorVersion string) string {
- // Use the highest compatible major version to avoid unwanted upgrades.
- // See the comment on workspaceModuleVersion.
- if majorVersion == "v0" {
- majorVersion = "v1"
- }
- return majorVersion + workspaceModuleVersion
-}
diff --git a/internal/lsp/source/workspace_symbol.go b/internal/lsp/source/workspace_symbol.go
deleted file mode 100644
index d9257c983..000000000
--- a/internal/lsp/source/workspace_symbol.go
+++ /dev/null
@@ -1,593 +0,0 @@
-// 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 source
-
-import (
- "context"
- "fmt"
- "go/types"
- "path/filepath"
- "runtime"
- "sort"
- "strings"
- "unicode"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/fuzzy"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/span"
-)
-
-// 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
-}
-
-// 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, matcherType SymbolMatcher, style SymbolStyle, views []View, query string) ([]protocol.SymbolInformation, error) {
- ctx, done := event.Start(ctx, "source.WorkspaceSymbols")
- defer done()
- if query == "" {
- return nil, nil
- }
- sc := newSymbolCollector(matcherType, style, query)
- return sc.walk(ctx, views)
-}
-
-// 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.
-type symbolizer func(name string, pkg Metadata, m matcherFunc) ([]string, float64)
-
-func fullyQualifiedSymbolMatch(name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
- _, score := dynamicSymbolMatch(name, pkg, matcher)
- if score > 0 {
- return []string{pkg.PackagePath(), ".", name}, score
- }
- return nil, 0
-}
-
-func dynamicSymbolMatch(name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
- var score float64
-
- endsInPkgName := strings.HasSuffix(pkg.PackagePath(), pkg.PackageName())
-
- // 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 := []string{pkg.PackageName(), ".", name}
- idx, score := matcher(pkgQualified)
- nameStart := len(pkg.PackageName()) + 1
- if score > 0 {
- // If our match is contained entirely within the unqualified portion,
- // just return that.
- if idx >= nameStart {
- return []string{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 := []string{pkg.PackagePath(), ".", name}
- idx, score := matcher(fullyQualified)
-
- // As above, check if we matched just the unqualified symbol name.
- nameStart := len(pkg.PackagePath()) + 1
- if idx >= nameStart {
- return []string{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.PackagePath()) - len(pkg.PackageName())
- if idx >= pkgStart {
- return []string{pkg.PackageName(), ".", 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(name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
- qualified := []string{pkg.PackageName(), ".", name}
- if _, s := matcher(qualified); s > 0 {
- return qualified, s
- }
- return nil, 0
-}
-
-// symbolCollector holds context as we walk Packages, gathering symbols that
-// match a given query.
-//
-// How we match 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.
-type symbolCollector struct {
- // These types parameterize the symbol-matching pass.
- matchers []matcherFunc
- symbolizer symbolizer
-
- symbolStore
-}
-
-func newSymbolCollector(matcher SymbolMatcher, style SymbolStyle, query string) *symbolCollector {
- var s symbolizer
- switch style {
- case DynamicSymbols:
- s = dynamicSymbolMatch
- case FullyQualifiedSymbols:
- s = fullyQualifiedSymbolMatch
- case PackageQualifiedSymbols:
- s = packageSymbolMatch
- default:
- panic(fmt.Errorf("unknown symbol style: %v", style))
- }
- sc := &symbolCollector{symbolizer: s}
- sc.matchers = make([]matcherFunc, runtime.GOMAXPROCS(-1))
- for i := range sc.matchers {
- sc.matchers[i] = buildMatcher(matcher, query)
- }
- return sc
-}
-
-func buildMatcher(matcher SymbolMatcher, query string) matcherFunc {
- switch matcher {
- case SymbolFuzzy:
- return parseQuery(query, newFuzzyMatcher)
- case SymbolFastFuzzy:
- return parseQuery(query, func(query string) matcherFunc {
- return fuzzy.NewSymbolMatcher(query).Match
- })
- case SymbolCaseSensitive:
- return matchExact(query)
- case 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
-}
-
-func (sc *symbolCollector) walk(ctx context.Context, views []View) ([]protocol.SymbolInformation, error) {
- // Use the root view URIs for determining (lexically) whether a uri is in any
- // open workspace.
- var roots []string
- for _, v := range views {
- roots = append(roots, strings.TrimRight(string(v.Folder()), "/"))
- }
-
- results := make(chan *symbolStore)
- matcherlen := len(sc.matchers)
- files := make(map[span.URI]symbolFile)
-
- for _, v := range views {
- snapshot, release := v.Snapshot(ctx)
- defer release()
- psyms, err := snapshot.Symbols(ctx)
- if err != nil {
- return nil, err
- }
-
- filters := v.Options().DirectoryFilters
- folder := filepath.ToSlash(v.Folder().Filename())
- for uri, syms := range psyms {
- norm := filepath.ToSlash(uri.Filename())
- nm := strings.TrimPrefix(norm, folder)
- if FiltersDisallow(nm, filters) {
- continue
- }
- // Only scan each file once.
- if _, ok := files[uri]; ok {
- continue
- }
- mds, err := snapshot.MetadataForFile(ctx, uri)
- if err != nil {
- event.Error(ctx, fmt.Sprintf("missing metadata for %q", uri), err)
- continue
- }
- if len(mds) == 0 {
- // TODO: should use the bug reporting API
- continue
- }
- files[uri] = symbolFile{uri, mds[0], syms}
- }
- }
-
- var work []symbolFile
- for _, f := range files {
- work = append(work, f)
- }
-
- // Compute matches concurrently. Each symbolWorker has its own symbolStore,
- // which we merge at the end.
- for i, matcher := range sc.matchers {
- go func(i int, matcher matcherFunc) {
- w := &symbolWorker{
- symbolizer: sc.symbolizer,
- matcher: matcher,
- ss: &symbolStore{},
- roots: roots,
- }
- for j := i; j < len(work); j += matcherlen {
- w.matchFile(work[j])
- }
- results <- w.ss
- }(i, matcher)
- }
-
- for i := 0; i < matcherlen; i++ {
- ss := <-results
- for _, si := range ss.res {
- sc.store(si)
- }
- }
- return sc.results(), nil
-}
-
-// FilterDisallow is code from the body of cache.pathExcludedByFilter in cache/view.go
-// Exporting and using that function would cause an import cycle.
-// Moving it here and exporting it would leave behind view_test.go.
-// (This code is exported and used in the body of cache.pathExcludedByFilter)
-func FiltersDisallow(path string, filters []string) bool {
- path = strings.TrimPrefix(path, "/")
- var excluded bool
- for _, filter := range filters {
- op, prefix := filter[0], filter[1:]
- // Non-empty prefixes have to be precise directory matches.
- if prefix != "" {
- prefix = prefix + "/"
- path = path + "/"
- }
- if !strings.HasPrefix(path, prefix) {
- continue
- }
- excluded = op == '-'
- }
- return excluded
-}
-
-// symbolFile holds symbol information for a single file.
-type symbolFile struct {
- uri span.URI
- md Metadata
- syms []Symbol
-}
-
-// symbolWorker matches symbols and captures the highest scoring results.
-type symbolWorker struct {
- symbolizer symbolizer
- matcher matcherFunc
- ss *symbolStore
- roots []string
-}
-
-func (w *symbolWorker) matchFile(i symbolFile) {
- for _, sym := range i.syms {
- symbolParts, score := w.symbolizer(sym.Name, i.md, w.matcher)
-
- // Check if the score is too low before applying any downranking.
- if w.ss.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 of any active
- // 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 of
- // any active 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.
- depthFactor = 0.2
- )
-
- 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
- }
- }
-
- inWorkspace := false
- for _, root := range w.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 w.ss.tooLow(score) {
- continue
- }
-
- si := symbolInformation{
- score: score,
- symbol: strings.Join(symbolParts, ""),
- kind: sym.Kind,
- uri: i.uri,
- rng: sym.Range,
- container: i.md.PackagePath(),
- }
- w.ss.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
-}
-
-func typeToKind(typ types.Type) protocol.SymbolKind {
- switch typ := typ.Underlying().(type) {
- case *types.Interface:
- return protocol.Interface
- case *types.Struct:
- return protocol.Struct
- case *types.Signature:
- if typ.Recv() != nil {
- return protocol.Method
- }
- return protocol.Function
- case *types.Named:
- return typeToKind(typ.Underlying())
- case *types.Basic:
- i := typ.Info()
- switch {
- case i&types.IsNumeric != 0:
- return protocol.Number
- case i&types.IsBoolean != 0:
- return protocol.Boolean
- case i&types.IsString != 0:
- return protocol.String
- }
- }
- return protocol.Variable
-}
-
-// 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 span.URI
- 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: protocol.URIFromSpanURI(s.uri),
- Range: s.rng,
- },
- ContainerName: s.container,
- }
-}
diff --git a/internal/lsp/source/workspace_symbol_test.go b/internal/lsp/source/workspace_symbol_test.go
deleted file mode 100644
index 314ef785d..000000000
--- a/internal/lsp/source/workspace_symbol_test.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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 source
-
-import (
- "testing"
-)
-
-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)
- }
- }
-}
diff --git a/internal/lsp/symbols.go b/internal/lsp/symbols.go
deleted file mode 100644
index f04e4572d..000000000
--- a/internal/lsp/symbols.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/template"
-)
-
-func (s *Server) documentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]interface{}, error) {
- ctx, done := event.Start(ctx, "lsp.Server.documentSymbol")
- defer done()
-
- snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
- defer release()
- if !ok {
- return []interface{}{}, err
- }
- var docSymbols []protocol.DocumentSymbol
- if snapshot.View().FileKind(fh) == source.Tmpl {
- docSymbols, err = template.DocumentSymbols(snapshot, fh)
- } else {
- docSymbols, err = source.DocumentSymbols(ctx, snapshot, fh)
- }
- if err != nil {
- event.Error(ctx, "DocumentSymbols failed", err, tag.URI.Of(fh.URI()))
- return []interface{}{}, nil
- }
- // Convert the symbols to an interface array.
- // TODO: Remove this once the lsp deprecates SymbolInformation.
- symbols := make([]interface{}, len(docSymbols))
- for i, s := range docSymbols {
- if snapshot.View().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/internal/lsp/template/completion.go b/internal/lsp/template/completion.go
deleted file mode 100644
index 13dbdf1e5..000000000
--- a/internal/lsp/template/completion.go
+++ /dev/null
@@ -1,301 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-// 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 source.Snapshot, fh source.VersionedFileHandle, 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.Filename() != fh.URI().Filename() {
- 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.FileIdentity().URI.Filename())
- }
- 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], []byte(Left)) && !bytes.Contains(fc.buf[x:offset], []byte(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 := string(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, string(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, string(pattern)) != 0 {
- ans.Items = append(ans.Items, protocol.CompletionItem{
- Label: kw,
- Kind: protocol.KeywordCompletion,
- Detail: "keyword",
- })
- }
- }
- }
- return ans, nil
-}
-
-// someday think about comments, strings, backslashes, etc
-// this would repeat some of the template parsing, but because the user is typing
-// there may be no parse tree here.
-// (go/scanner will report 2 tokens for $a, as $ is not a legal go identifier character)
-// (go/scanner is about 2.7 times more expensive)
-func (c *completer) analyze(buf []byte) [][]byte {
- // we want to split on whitespace and before dots
- var working []byte
- var ans [][]byte
- for _, ch := range buf {
- if ch == '.' && len(working) > 0 {
- ans = append(ans, working)
- working = []byte{'.'}
- continue
- }
- if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
- if len(working) > 0 {
- ans = append(ans, working)
- working = []byte{}
- continue
- }
- }
- working = append(working, ch)
- }
- if len(working) > 0 {
- ans = append(ans, working)
- }
- ch := buf[len(buf)-1]
- if ch == ' ' || ch == '\t' {
- // avoid completing on whitespace
- ans = append(ans, []byte{ch})
- }
- return ans
-}
-
-// 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
-}
-
-// for debug printing
-func strContext(c protocol.CompletionContext) string {
- switch c.TriggerKind {
- case protocol.Invoked:
- return "invoked"
- case protocol.TriggerCharacter:
- return fmt.Sprintf("triggered(%s)", c.TriggerCharacter)
- case protocol.TriggerForIncompleteCompletions:
- // gopls doesn't seem to handle these explicitly anywhere
- return "incomplete"
- }
- return fmt.Sprintf("?%v", c)
-}
diff --git a/internal/lsp/template/completion_test.go b/internal/lsp/template/completion_test.go
deleted file mode 100644
index bfcdb5372..000000000
--- a/internal/lsp/template/completion_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-// 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/internal/lsp/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
- {`<table class="chroma" data-new-comment-url="{{if $.PageIsPullFiles}}{{$.Issue.HTMLURL}}/files/reviews/new_comment{{else}}{{$.CommitHTML}}/new_comment^{{end}}">`, 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/internal/lsp/template/highlight.go b/internal/lsp/template/highlight.go
deleted file mode 100644
index a45abaf50..000000000
--- a/internal/lsp/template/highlight.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-func Highlight(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, loc protocol.Position) ([]protocol.DocumentHighlight, error) {
- buf, err := fh.Read()
- 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/internal/lsp/template/implementations.go b/internal/lsp/template/implementations.go
deleted file mode 100644
index 1de988871..000000000
--- a/internal/lsp/template/implementations.go
+++ /dev/null
@@ -1,189 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
-)
-
-// line number (1-based) and message
-var errRe = regexp.MustCompile(`template.*:(\d+): (.*)`)
-
-// Diagnose returns parse errors. There is only one.
-// The errors are not always helpful. For instance { {end}}
-// will likely point to the end of the file.
-func Diagnose(f source.VersionedFileHandle) []*source.Diagnostic {
- // no need for skipTemplate check, as Diagnose is called on the
- // snapshot's template files
- buf, err := f.Read()
- if err != nil {
- // Is a Diagnostic with no Range useful? event.Error also?
- msg := fmt.Sprintf("failed to read %s (%v)", f.URI().Filename(), err)
- d := source.Diagnostic{Message: msg, Severity: protocol.SeverityError, URI: f.URI(),
- Source: source.TemplateError}
- return []*source.Diagnostic{&d}
- }
- p := parseBuffer(buf)
- if p.ParseErr == nil {
- return nil
- }
- unknownError := func(msg string) []*source.Diagnostic {
- s := fmt.Sprintf("malformed template error %q: %s", p.ParseErr.Error(), msg)
- d := source.Diagnostic{
- Message: s, Severity: protocol.SeverityError, Range: p.Range(p.nls[0], 1),
- URI: f.URI(), Source: source.TemplateError}
- return []*source.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 := source.Diagnostic{Message: msg, Severity: protocol.SeverityError,
- Source: source.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 []*source.Diagnostic{&d}
-}
-
-// Definition finds the definitions of the symbol at loc. It
-// does not understand scoping (if any) in templates. This code is
-// for defintions, type definitions, and implementations.
-// Results only for variables and templates.
-func Definition(snapshot source.Snapshot, fh source.VersionedFileHandle, 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: protocol.DocumentURI(k), Range: p.Range(s.start, s.length)})
- }
- }
- return ans, nil
-}
-
-func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, 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 source.Snapshot, fh source.FileHandle, 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: protocol.DocumentURI(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 source.Snapshot, spn span.URI, add func(line, start, len uint32), d func() []uint32) (*protocol.SemanticTokens, error) {
- fh, err := snapshot.GetFile(ctx, spn)
- if err != nil {
- return nil, err
- }
- buf, err := fh.Read()
- if err != nil {
- return nil, err
- }
- p := parseBuffer(buf)
-
- 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))
- }
- data := d()
- ans := &protocol.SemanticTokens{
- Data: data,
- // 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/internal/lsp/template/parse.go b/internal/lsp/template/parse.go
deleted file mode 100644
index 194eeb3f5..000000000
--- a/internal/lsp/template/parse.go
+++ /dev/null
@@ -1,520 +0,0 @@
-// 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.
-
-// This file contains the parsing code, some debugging printing, and
-// implementations for Diagnose, Definition, HJover, References
-
-import (
- "bytes"
- "context"
- "fmt"
- "io"
- "log"
- "regexp"
- "runtime"
- "sort"
- "text/template"
- "text/template/parse"
- "unicode/utf8"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
-)
-
-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[span.URI]*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[span.URI]source.VersionedFileHandle) *All {
- all := make(map[span.URI]*Parsed)
- for k, v := range tmpls {
- buf, err := v.Read()
- if err != nil { // PJW: decide what to do with these errors
- log.Printf("failed to read %s (%v)", v.URI().Filename(), 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 "", rather than ``, 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
-}
-
-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 source.FileHandle, loc protocol.Position) (*symbol, *Parsed, error) {
- buf, err := fh.Read()
- 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+". ")
- }
-}
-
-// short prints at most 40 bytes of node.String(), for debugging
-func short(n parse.Node) (ret string) {
- defer func() {
- if x := recover(); x != nil {
- // all because of typed nils
- ret = "NIL"
- }
- }()
- s := n.String()
- if len(s) > 40 {
- return s[:40] + "..."
- }
- return s
-}
-
-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/internal/lsp/template/parse_test.go b/internal/lsp/template/parse_test.go
deleted file mode 100644
index 345f52347..000000000
--- a/internal/lsp/template/parse_test.go
+++ /dev/null
@@ -1,238 +0,0 @@
-// 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/internal/lsp/template/symbols.go b/internal/lsp/template/symbols.go
deleted file mode 100644
index ce5a1e799..000000000
--- a/internal/lsp/template/symbols.go
+++ /dev/null
@@ -1,230 +0,0 @@
-// 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/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-// 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([]byte(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 source.Snapshot, fh source.FileHandle) ([]protocol.DocumentSymbol, error) {
- buf, err := fh.Read()
- 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/internal/lsp/testdata/%percent/perc%ent.go b/internal/lsp/testdata/%percent/perc%ent.go
deleted file mode 100644
index 93b5e5570..000000000
--- a/internal/lsp/testdata/%percent/perc%ent.go
+++ /dev/null
@@ -1 +0,0 @@
-package percent
diff --git a/internal/lsp/testdata/addimport/addimport.go.golden b/internal/lsp/testdata/addimport/addimport.go.golden
deleted file mode 100644
index 9605aa6f9..000000000
--- a/internal/lsp/testdata/addimport/addimport.go.golden
+++ /dev/null
@@ -1,7 +0,0 @@
--- addimport --
-package addimport //@addimport("", "bytes")
-
-import "bytes"
-
-func main() {}
-
diff --git a/internal/lsp/testdata/addimport/addimport.go.in b/internal/lsp/testdata/addimport/addimport.go.in
deleted file mode 100644
index 07b454f52..000000000
--- a/internal/lsp/testdata/addimport/addimport.go.in
+++ /dev/null
@@ -1,3 +0,0 @@
-package addimport //@addimport("", "bytes")
-
-func main() {}
diff --git a/internal/lsp/testdata/address/address.go b/internal/lsp/testdata/address/address.go
deleted file mode 100644
index 3f1c2fa8d..000000000
--- a/internal/lsp/testdata/address/address.go
+++ /dev/null
@@ -1,78 +0,0 @@
-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", "&b")
- wantsPtr(&b) //@snippet(")", addrB, "b", "b")
-
- wantsVariadicPtr() //@rank(")", addrB, addrA),snippet(")", addrB, "&b", "&b")
-
- var s foo
- s.c //@item(addrDeepC, "s.c", "int", "field")
- wantsPtr() //@snippet(")", addrDeepC, "&s.c", "&s.c")
- wantsPtr(s) //@snippet(")", addrDeepC, "&s.c", "&s.c")
- wantsPtr(&s) //@snippet(")", addrDeepC, "s.c", "s.c")
-
- // don't add "&" in item (it gets added as an additional edit)
- wantsPtr(&s.c) //@snippet(")", addrFieldC, "c", "c")
-
- // check dereferencing as well
- var c *int //@item(addrCPtr, "c", "*int", "var")
- var _ int = _ //@rank("_ //", addrCPtr, addrA),snippet("_ //", addrCPtr, "*c", "*c")
-
- wantsVariadic() //@rank(")", addrCPtr, addrA),snippet(")", addrCPtr, "*c", "*c")
-
- var d **int //@item(addrDPtr, "d", "**int", "var")
- var _ int = _ //@rank("_ //", addrDPtr, addrA),snippet("_ //", addrDPtr, "**d", "**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
- var dmu *dontMessUp //@item(addrDMU, "dmu", "*dontMessUp", "var")
-
- var _ int = dmu //@complete(" //", addrDMU)
-}
-
-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() //@rank(addrGetFooPtrC, addrGetFooC),snippet(")", addrGetFooPtrC, "&getFoo().ptr().c", "&getFoo().ptr().c")
- wantsPtr(&g) //@rank(addrGetFooPtrC, addrGetFooC),snippet(")", addrGetFooPtrC, "getFoo().ptr().c", "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) //@fuzzy(")", addrNestedPtrC, addrNestedC)
-}
diff --git a/internal/lsp/testdata/analyzer/bad_test.go b/internal/lsp/testdata/analyzer/bad_test.go
deleted file mode 100644
index c819cbc01..000000000
--- a/internal/lsp/testdata/analyzer/bad_test.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package analyzer
-
-import (
- "fmt"
- "sync"
- "testing"
-)
-
-func Testbad(t *testing.T) { //@diag("", "tests", "Testbad has malformed name: first letter after 'Test' must not be lowercase", "warning")
- var x sync.Mutex
- _ = x //@diag("x", "copylocks", "assignment copies lock value to _: sync.Mutex", "warning")
-
- printfWrapper("%s") //@diag(re`printfWrapper\(.*\)`, "printf", "golang.org/x/tools/internal/lsp/analyzer.printfWrapper format %s reads arg #1, but call has 0 args", "warning")
-}
-
-func printfWrapper(format string, args ...interface{}) {
- fmt.Printf(format, args...)
-}
diff --git a/internal/lsp/testdata/anon/anon.go.in b/internal/lsp/testdata/anon/anon.go.in
deleted file mode 100644
index 36611b268..000000000
--- a/internal/lsp/testdata/anon/anon.go.in
+++ /dev/null
@@ -1,23 +0,0 @@
-package anon
-
-func _() {
- for _, _ := range []struct {
- i, j int //@item(anonI, "i", "int", "field"),item(anonJ, "j", "int", "field")
- }{
- {
- i: 1,
- //@complete("", anonJ)
- },
- {
- //@complete("", anonI, anonJ)
- },
- } {
- continue
- }
-
- s := struct{ f int }{ } //@item(anonF, "f", "int", "field"),item(structS, "s", "struct{...}", "var"),complete(" }", anonF)
-
- _ = map[struct{ x int }]int{ //@item(anonX, "x", "int", "field")
- struct{ x int }{ }: 1, //@complete(" }", anonX, structS)
- }
-}
diff --git a/internal/lsp/testdata/append/append.go b/internal/lsp/testdata/append/append.go
deleted file mode 100644
index 2880e59db..000000000
--- a/internal/lsp/testdata/append/append.go
+++ /dev/null
@@ -1,38 +0,0 @@
-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...", "aStrings...")
- foo(append([]string{}, "", a)) //@rank("))", appendString, appendInt),rank("))", appendString, appendStrings)
-
- // Don't add "..." to append() argument.
- bar(append()) //@snippet("))", appendStrings, "aStrings", "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...", "*aStringsPtr...")
-
- foo(append([]string{}, *a)) //@snippet("))", appendStringsPtr, "aStringsPtr...", "aStringsPtr...")
-}
diff --git a/internal/lsp/testdata/append/append2.go.in b/internal/lsp/testdata/append/append2.go.in
deleted file mode 100644
index 15bd357b2..000000000
--- a/internal/lsp/testdata/append/append2.go.in
+++ /dev/null
@@ -1,5 +0,0 @@
-package append
-
-func _() {
- _ = append(a, struct) //@complete(")")
-} \ No newline at end of file
diff --git a/internal/lsp/testdata/arraytype/array_type.go.in b/internal/lsp/testdata/arraytype/array_type.go.in
deleted file mode 100644
index 7e9a96f7b..000000000
--- a/internal/lsp/testdata/arraytype/array_type.go.in
+++ /dev/null
@@ -1,48 +0,0 @@
-package arraytype
-
-import (
- "golang.org/x/tools/internal/lsp/foo"
-)
-
-func _() {
- var (
- val string //@item(atVal, "val", "string", "var")
- )
-
- [] //@complete(" //", PackageFoo)
-
- []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)
- s = [] //@complete(" //", atMyInt, PackageFoo)
-
- 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[:]", "b[:]")
-}
diff --git a/internal/lsp/testdata/assign/assign.go.in b/internal/lsp/testdata/assign/assign.go.in
deleted file mode 100644
index 8c00ae9e0..000000000
--- a/internal/lsp/testdata/assign/assign.go.in
+++ /dev/null
@@ -1,26 +0,0 @@
-package assign
-
-import "golang.org/x/tools/internal/lsp/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)
- }
-}
diff --git a/internal/lsp/testdata/assign/internal/secret/secret.go b/internal/lsp/testdata/assign/internal/secret/secret.go
deleted file mode 100644
index 5ee1554df..000000000
--- a/internal/lsp/testdata/assign/internal/secret/secret.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package secret
-
-func Hello() {} \ No newline at end of file
diff --git a/internal/lsp/testdata/bad/bad0.go b/internal/lsp/testdata/bad/bad0.go
deleted file mode 100644
index 36a4e6b95..000000000
--- a/internal/lsp/testdata/bad/bad0.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// +build go1.11
-
-package bad
-
-import _ "golang.org/x/tools/internal/lsp/assign/internal/secret" //@diag("\"golang.org/x/tools/internal/lsp/assign/internal/secret\"", "compiler", "could not import golang.org/x/tools/internal/lsp/assign/internal/secret (invalid use of internal package golang.org/x/tools/internal/lsp/assign/internal/secret)", "error")
-
-func stuff() { //@item(stuff, "stuff", "func()", "func")
- x := "heeeeyyyy"
- random2(x) //@diag("x", "compiler", "cannot use x (variable of type string) as int value in argument to random2", "error")
- random2(1) //@complete("dom", random, random2, random3)
- y := 3 //@diag("y", "compiler", "y declared but not used", "error")
-}
-
-type bob struct { //@item(bob, "bob", "struct{...}", "struct")
- x int
-}
-
-func _() {
- var q int
- _ = &bob{
- f: q, //@diag("f: q", "compiler", "unknown field f in struct literal", "error")
- }
-}
diff --git a/internal/lsp/testdata/bad/bad1.go b/internal/lsp/testdata/bad/bad1.go
deleted file mode 100644
index 512f2d986..000000000
--- a/internal/lsp/testdata/bad/bad1.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// +build go1.11
-
-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", "compiler", "undeclared name: unknown", "error")
-
-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", "compiler", "x declared but not used", "error")
- var q blah //@item(q, "q", "blah", "var"),diag("q", "compiler", "q declared but not used", "error"),diag("blah", "compiler", "undeclared name: blah", "error")
- var t **blob //@item(t, "t", "**blob", "var"),diag("t", "compiler", "t declared but not used", "error"),diag("blob", "compiler", "undeclared name: blob", "error")
- //@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", "compiler", "ch declared but not used", "error"),diag("favType1", "compiler", "undeclared name: favType1", "error")
- var m map[keyType]int //@item(m, "m", "map[keyType]int", "var"),diag("m", "compiler", "m declared but not used", "error"),diag("keyType", "compiler", "undeclared name: keyType", "error")
- var arr []favType2 //@item(arr, "arr", "[]favType2", "var"),diag("arr", "compiler", "arr declared but not used", "error"),diag("favType2", "compiler", "undeclared name: favType2", "error")
- var fn1 func() badResult //@item(fn1, "fn1", "func() badResult", "var"),diag("fn1", "compiler", "fn1 declared but not used", "error"),diag("badResult", "compiler", "undeclared name: badResult", "error")
- var fn2 func(badParam) //@item(fn2, "fn2", "func(badParam)", "var"),diag("fn2", "compiler", "fn2 declared but not used", "error"),diag("badParam", "compiler", "undeclared name: badParam", "error")
- //@complete("", arr, ch, fn1, fn2, m, y_variadic_param, global_a, bob, random, random2, random3, stateFunc, stuff)
-}
diff --git a/internal/lsp/testdata/badstmt/badstmt.go.in b/internal/lsp/testdata/badstmt/badstmt.go.in
deleted file mode 100644
index 5a5607910..000000000
--- a/internal/lsp/testdata/badstmt/badstmt.go.in
+++ /dev/null
@@ -1,26 +0,0 @@
-package badstmt
-
-import (
- "golang.org/x/tools/internal/lsp/foo"
-)
-
-func _() {
- defer foo.F //@complete(" //", Foo),diag(" //", "syntax", "function must be invoked in defer statement", "error")
- y := 1
- defer foo.F //@complete(" //", Foo)
-}
-
-func _() {
- switch true {
- case true:
- go foo.F //@complete(" //", Foo)
- }
-}
-
-func _() {
- defer func() {
- foo.F //@complete(" //", Foo),snippet(" //", Foo, "Foo()", "Foo()")
-
- foo. //@rank(" //", Foo)
- }
-}
diff --git a/internal/lsp/testdata/badstmt/badstmt_2.go.in b/internal/lsp/testdata/badstmt/badstmt_2.go.in
deleted file mode 100644
index f754b46aa..000000000
--- a/internal/lsp/testdata/badstmt/badstmt_2.go.in
+++ /dev/null
@@ -1,9 +0,0 @@
-package badstmt
-
-import (
- "golang.org/x/tools/internal/lsp/foo"
-)
-
-func _() {
- defer func() { foo. } //@rank(" }", Foo)
-}
diff --git a/internal/lsp/testdata/badstmt/badstmt_3.go.in b/internal/lsp/testdata/badstmt/badstmt_3.go.in
deleted file mode 100644
index be774e84b..000000000
--- a/internal/lsp/testdata/badstmt/badstmt_3.go.in
+++ /dev/null
@@ -1,9 +0,0 @@
-package badstmt
-
-import (
- "golang.org/x/tools/internal/lsp/foo"
-)
-
-func _() {
- go foo. //@rank(" //", Foo, IntFoo),snippet(" //", Foo, "Foo()", "Foo()")
-}
diff --git a/internal/lsp/testdata/badstmt/badstmt_4.go.in b/internal/lsp/testdata/badstmt/badstmt_4.go.in
deleted file mode 100644
index a9b46fb02..000000000
--- a/internal/lsp/testdata/badstmt/badstmt_4.go.in
+++ /dev/null
@@ -1,11 +0,0 @@
-package badstmt
-
-import (
- "golang.org/x/tools/internal/lsp/foo"
-)
-
-func _() {
- go func() {
- defer foo. //@rank(" //", Foo, IntFoo)
- }
-}
diff --git a/internal/lsp/testdata/bar/bar.go.in b/internal/lsp/testdata/bar/bar.go.in
deleted file mode 100644
index c0f4b4c45..000000000
--- a/internal/lsp/testdata/bar/bar.go.in
+++ /dev/null
@@ -1,47 +0,0 @@
-// +build go1.11
-
-package bar
-
-import (
- "golang.org/x/tools/internal/lsp/foo" //@item(foo, "foo", "\"golang.org/x/tools/internal/lsp/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)
-}
-
-func _() {
- var Valentine int //@item(Valentine, "Valentine", "int", "var")
-
- _ = foo.StructFoo{
- Valu //@complete(" //", Value)
- }
- _ = foo.StructFoo{
- Va //@complete("a", Value, Valentine)
- }
- _ = foo.StructFoo{
- Value: 5, //@complete("a", Value)
- }
- _ = foo.StructFoo{
- //@complete("", Value, Valentine, foo, helper, Bar)
- }
- _ = foo.StructFoo{
- Value: Valen //@complete("le", Valentine)
- }
- _ = foo.StructFoo{
- Value: //@complete(" //", Valentine, foo, helper, Bar)
- }
- _ = foo.StructFoo{
- Value: //@complete(" ", Valentine, foo, helper, Bar)
- }
-}
diff --git a/internal/lsp/testdata/basiclit/basiclit.go b/internal/lsp/testdata/basiclit/basiclit.go
deleted file mode 100644
index 9829003d3..000000000
--- a/internal/lsp/testdata/basiclit/basiclit.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package basiclit
-
-func _() {
- var a int // something for lexical completions
-
- _ = "hello." //@complete(".")
-
- _ = 1 //@complete(" //")
-
- _ = 1. //@complete(".")
-
- _ = 'a' //@complete("' ")
-
- _ = 'a' //@hover("'a'", "'a', U+0061, LATIN SMALL LETTER A")
- _ = 0x61 //@hover("0x61", "'a', U+0061, LATIN SMALL LETTER A")
-
- _ = '\u2211' //@hover("'\\u2211'", "'∑', U+2211, N-ARY SUMMATION")
- _ = 0x2211 //@hover("0x2211", "'∑', U+2211, N-ARY SUMMATION")
- _ = "foo \u2211 bar" //@hover("\\u2211", "'∑', U+2211, N-ARY SUMMATION")
-
- _ = '\a' //@hover("'\\a'", "U+0007, control")
- _ = "foo \a bar" //@hover("\\a", "U+0007, control")
-
- _ = '\U0001F30A' //@hover("'\\U0001F30A'", "'🌊', U+1F30A, WATER WAVE")
- _ = 0x0001F30A //@hover("0x0001F30A", "'🌊', U+1F30A, WATER WAVE")
- _ = "foo \U0001F30A bar" //@hover("\\U0001F30A", "'🌊', U+1F30A, WATER WAVE")
-
- _ = '\x7E' //@hover("'\\x7E'", "'~', U+007E, TILDE")
- _ = "foo \x7E bar" //@hover("\\x7E", "'~', U+007E, TILDE")
- _ = "foo \a bar" //@hover("\\a", "U+0007, control")
-
- _ = '\173' //@hover("'\\173'", "'{', U+007B, LEFT CURLY BRACKET")
- _ = "foo \173 bar" //@hover("\\173", "'{', U+007B, LEFT CURLY BRACKET")
- _ = "foo \173 bar \u2211 baz" //@hover("\\173", "'{', U+007B, LEFT CURLY BRACKET")
- _ = "foo \173 bar \u2211 baz" //@hover("\\u2211", "'∑', U+2211, N-ARY SUMMATION")
- _ = "foo\173bar\u2211baz" //@hover("\\173", "'{', U+007B, LEFT CURLY BRACKET")
- _ = "foo\173bar\u2211baz" //@hover("\\u2211", "'∑', U+2211, N-ARY SUMMATION")
-
- // search for runes in string only if there is an escaped sequence
- _ = "hello" //@hover("\"hello\"", "")
-
- // incorrect escaped rune sequences
- _ = '\0' //@hover("'\\0'", "")
- _ = '\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", "")
- _ = 0x1234567890 //@hover("0x1234567890", "")
-}
diff --git a/internal/lsp/testdata/baz/baz.go.in b/internal/lsp/testdata/baz/baz.go.in
deleted file mode 100644
index 3b74ee580..000000000
--- a/internal/lsp/testdata/baz/baz.go.in
+++ /dev/null
@@ -1,33 +0,0 @@
-// +build go1.11
-
-package baz
-
-import (
- "golang.org/x/tools/internal/lsp/bar"
-
- f "golang.org/x/tools/internal/lsp/foo"
-)
-
-var FooStruct f.StructFoo
-
-func Baz() {
- defer bar.Bar() //@complete("B", Bar)
- // TODO(rstambler): Test completion here.
- defer bar.B
- var x f.IntFoo //@complete("n", IntFoo),typdef("x", IntFoo)
- 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:
- }
-}
diff --git a/internal/lsp/testdata/builtins/builtin_args.go b/internal/lsp/testdata/builtins/builtin_args.go
deleted file mode 100644
index 052777fe9..000000000
--- a/internal/lsp/testdata/builtins/builtin_args.go
+++ /dev/null
@@ -1,62 +0,0 @@
-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", "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)
-}
diff --git a/internal/lsp/testdata/builtins/builtin_types.go b/internal/lsp/testdata/builtins/builtin_types.go
deleted file mode 100644
index 93a4a7095..000000000
--- a/internal/lsp/testdata/builtins/builtin_types.go
+++ /dev/null
@@ -1,11 +0,0 @@
-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)
-}
diff --git a/internal/lsp/testdata/builtins/builtins.go b/internal/lsp/testdata/builtins/builtins.go
deleted file mode 100644
index 25c29f21e..000000000
--- a/internal/lsp/testdata/builtins/builtins.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package builtins
-
-func _() {
- //@complete("", append, bool, byte, cap, close, 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)
-}
-
-/* Create markers for builtin types. Only for use by this test.
-/* append(slice []Type, elems ...Type) []Type */ //@item(append, "append", "func(slice []Type, elems ...Type) []Type", "func")
-/* bool */ //@item(bool, "bool", "", "type")
-/* byte */ //@item(byte, "byte", "", "type")
-/* cap(v Type) int */ //@item(cap, "cap", "func(v Type) int", "func")
-/* close(c chan<- Type) */ //@item(close, "close", "func(c chan<- Type)", "func")
-/* complex(r float64, i float64) */ //@item(complex, "complex", "func(r float64, i float64) complex128", "func")
-/* complex128 */ //@item(complex128, "complex128", "", "type")
-/* complex64 */ //@item(complex64, "complex64", "", "type")
-/* copy(dst []Type, src []Type) int */ //@item(copy, "copy", "func(dst []Type, src []Type) int", "func")
-/* delete(m map[Type]Type1, key Type) */ //@item(delete, "delete", "func(m map[Type]Type1, key Type)", "func")
-/* error */ //@item(error, "error", "", "interface")
-/* false */ //@item(_false, "false", "", "const")
-/* 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")
-/* int16 */ //@item(int16, "int16", "", "type")
-/* int32 */ //@item(int32, "int32", "", "type")
-/* int64 */ //@item(int64, "int64", "", "type")
-/* int8 */ //@item(int8, "int8", "", "type")
-/* iota */ //@item(iota, "iota", "", "const")
-/* len(v Type) int */ //@item(len, "len", "func(v Type) int", "func")
-/* make(t Type, size ...int) Type */ //@item(make, "make", "func(t Type, size ...int) Type", "func")
-/* new(Type) *Type */ //@item(new, "new", "func(Type) *Type", "func")
-/* nil */ //@item(_nil, "nil", "", "var")
-/* panic(v interface{}) */ //@item(panic, "panic", "func(v interface{})", "func")
-/* print(args ...Type) */ //@item(print, "print", "func(args ...Type)", "func")
-/* println(args ...Type) */ //@item(println, "println", "func(args ...Type)", "func")
-/* real(c complex128) float64 */ //@item(real, "real", "func(c complex128) float64", "func")
-/* recover() interface{} */ //@item(recover, "recover", "func() interface{}", "func")
-/* rune */ //@item(rune, "rune", "", "type")
-/* string */ //@item(string, "string", "", "type")
-/* true */ //@item(_true, "true", "", "const")
-/* uint */ //@item(uint, "uint", "", "type")
-/* uint16 */ //@item(uint16, "uint16", "", "type")
-/* uint32 */ //@item(uint32, "uint32", "", "type")
-/* uint64 */ //@item(uint64, "uint64", "", "type")
-/* uint8 */ //@item(uint8, "uint8", "", "type")
-/* uintptr */ //@item(uintptr, "uintptr", "", "type")
diff --git a/internal/lsp/testdata/builtins/constants.go b/internal/lsp/testdata/builtins/constants.go
deleted file mode 100644
index 7ad07bd1f..000000000
--- a/internal/lsp/testdata/builtins/constants.go
+++ /dev/null
@@ -1,19 +0,0 @@
-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/internal/lsp/testdata/callhierarchy/callhierarchy.go b/internal/lsp/testdata/callhierarchy/callhierarchy.go
deleted file mode 100644
index 58c23bdd6..000000000
--- a/internal/lsp/testdata/callhierarchy/callhierarchy.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// 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 callhierarchy
-
-import "golang.org/x/tools/internal/lsp/callhierarchy/outgoing"
-
-func a() { //@mark(hierarchyA, "a")
- D()
-}
-
-func b() { //@mark(hierarchyB, "b")
- D()
-}
-
-// C is an exported function
-func C() { //@mark(hierarchyC, "C")
- D()
- D()
-}
-
-// To test hierarchy across function literals
-var x = func() { //@mark(hierarchyLiteral, "func"),mark(hierarchyLiteralOut, "x")
- D()
-}
-
-// D is exported to test incoming/outgoing calls across packages
-func D() { //@mark(hierarchyD, "D"),incomingcalls(hierarchyD, hierarchyA, hierarchyB, hierarchyC, hierarchyLiteral, incomingA),outgoingcalls(hierarchyD, hierarchyE, hierarchyF, hierarchyG, hierarchyLiteralOut, outgoingB, hierarchyFoo, hierarchyH, hierarchyI, hierarchyJ, hierarchyK)
- e()
- x()
- F()
- outgoing.B()
- foo := func() {} //@mark(hierarchyFoo, "foo"),incomingcalls(hierarchyFoo, hierarchyD),outgoingcalls(hierarchyFoo)
- foo()
-
- func() {
- g()
- }()
-
- var i Interface = impl{}
- i.H()
- i.I()
-
- s := Struct{}
- s.J()
- s.K()
-}
-
-func e() {} //@mark(hierarchyE, "e")
-
-// F is an exported function
-func F() {} //@mark(hierarchyF, "F")
-
-func g() {} //@mark(hierarchyG, "g")
-
-type Interface interface {
- H() //@mark(hierarchyH, "H")
- I() //@mark(hierarchyI, "I")
-}
-
-type impl struct{}
-
-func (i impl) H() {}
-func (i impl) I() {}
-
-type Struct struct {
- J func() //@mark(hierarchyJ, "J")
- K func() //@mark(hierarchyK, "K")
-}
diff --git a/internal/lsp/testdata/callhierarchy/incoming/incoming.go b/internal/lsp/testdata/callhierarchy/incoming/incoming.go
deleted file mode 100644
index 3bfb4ad99..000000000
--- a/internal/lsp/testdata/callhierarchy/incoming/incoming.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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 incoming
-
-import "golang.org/x/tools/internal/lsp/callhierarchy"
-
-// A is exported to test incoming calls across packages
-func A() { //@mark(incomingA, "A")
- callhierarchy.D()
-}
diff --git a/internal/lsp/testdata/callhierarchy/outgoing/outgoing.go b/internal/lsp/testdata/callhierarchy/outgoing/outgoing.go
deleted file mode 100644
index 74362d419..000000000
--- a/internal/lsp/testdata/callhierarchy/outgoing/outgoing.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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 outgoing
-
-// B is exported to test outgoing calls across packages
-func B() { //@mark(outgoingB, "B")
-}
diff --git a/internal/lsp/testdata/casesensitive/casesensitive.go b/internal/lsp/testdata/casesensitive/casesensitive.go
deleted file mode 100644
index 6f49d36ff..000000000
--- a/internal/lsp/testdata/casesensitive/casesensitive.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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 casesensitive
-
-func _() {
- var lower int //@item(lower, "lower", "int", "var")
- var Upper int //@item(upper, "Upper", "int", "var")
-
- l //@casesensitive(" //", lower)
- U //@casesensitive(" //", upper)
-
- L //@casesensitive(" //")
- u //@casesensitive(" //")
-}
diff --git a/internal/lsp/testdata/cast/cast.go.in b/internal/lsp/testdata/cast/cast.go.in
deleted file mode 100644
index 7fe21903c..000000000
--- a/internal/lsp/testdata/cast/cast.go.in
+++ /dev/null
@@ -1,11 +0,0 @@
-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)
-} \ No newline at end of file
diff --git a/internal/lsp/testdata/cgo/declarecgo.go b/internal/lsp/testdata/cgo/declarecgo.go
deleted file mode 100644
index c283cdfb2..000000000
--- a/internal/lsp/testdata/cgo/declarecgo.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package cgo
-
-/*
-#include <stdio.h>
-#include <stdlib.h>
-
-void myprint(char* s) {
- printf("%s\n", s);
-}
-*/
-import "C"
-
-import (
- "fmt"
- "unsafe"
-)
-
-func Example() { //@mark(funccgoexample, "Example"),item(funccgoexample, "Example", "func()", "func")
- fmt.Println()
- cs := C.CString("Hello from stdio\n")
- C.myprint(cs)
- C.free(unsafe.Pointer(cs))
-}
-
-func _() {
- Example() //@godef("ample", funccgoexample),complete("ample", funccgoexample)
-}
diff --git a/internal/lsp/testdata/cgo/declarecgo.go.golden b/internal/lsp/testdata/cgo/declarecgo.go.golden
deleted file mode 100644
index b6d94d0c6..000000000
--- a/internal/lsp/testdata/cgo/declarecgo.go.golden
+++ /dev/null
@@ -1,30 +0,0 @@
--- funccgoexample-definition --
-cgo/declarecgo.go:18:6-13: defined here as ```go
-func Example()
-```
-
-[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo?utm_source=gopls#Example)
--- funccgoexample-definition-json --
-{
- "span": {
- "uri": "file://cgo/declarecgo.go",
- "start": {
- "line": 18,
- "column": 6,
- "offset": 151
- },
- "end": {
- "line": 18,
- "column": 13,
- "offset": 158
- }
- },
- "description": "```go\nfunc Example()\n```\n\n[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo?utm_source=gopls#Example)"
-}
-
--- funccgoexample-hoverdef --
-```go
-func Example()
-```
-
-[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo?utm_source=gopls#Example)
diff --git a/internal/lsp/testdata/cgo/declarecgo_nocgo.go b/internal/lsp/testdata/cgo/declarecgo_nocgo.go
deleted file mode 100644
index a05c01257..000000000
--- a/internal/lsp/testdata/cgo/declarecgo_nocgo.go
+++ /dev/null
@@ -1,6 +0,0 @@
-//+build !cgo
-
-package cgo
-
-// Set a dummy marker to keep the test framework happy. The tests should be skipped.
-var _ = "Example" //@mark(funccgoexample, "Example"),godef("ample", funccgoexample),complete("ample", funccgoexample)
diff --git a/internal/lsp/testdata/cgoimport/usecgo.go.golden b/internal/lsp/testdata/cgoimport/usecgo.go.golden
deleted file mode 100644
index f33f94f84..000000000
--- a/internal/lsp/testdata/cgoimport/usecgo.go.golden
+++ /dev/null
@@ -1,30 +0,0 @@
--- funccgoexample-definition --
-cgo/declarecgo.go:18:6-13: defined here as ```go
-func cgo.Example()
-```
-
-[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo?utm_source=gopls#Example)
--- funccgoexample-definition-json --
-{
- "span": {
- "uri": "file://cgo/declarecgo.go",
- "start": {
- "line": 18,
- "column": 6,
- "offset": 151
- },
- "end": {
- "line": 18,
- "column": 13,
- "offset": 158
- }
- },
- "description": "```go\nfunc cgo.Example()\n```\n\n[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo?utm_source=gopls#Example)"
-}
-
--- funccgoexample-hoverdef --
-```go
-func cgo.Example()
-```
-
-[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo?utm_source=gopls#Example)
diff --git a/internal/lsp/testdata/cgoimport/usecgo.go.in b/internal/lsp/testdata/cgoimport/usecgo.go.in
deleted file mode 100644
index f258682ea..000000000
--- a/internal/lsp/testdata/cgoimport/usecgo.go.in
+++ /dev/null
@@ -1,9 +0,0 @@
-package cgoimport
-
-import (
- "golang.org/x/tools/internal/lsp/cgo"
-)
-
-func _() {
- cgo.Example() //@godef("ample", funccgoexample),complete("ample", funccgoexample)
-}
diff --git a/internal/lsp/testdata/channel/channel.go b/internal/lsp/testdata/channel/channel.go
deleted file mode 100644
index d6bd311e3..000000000
--- a/internal/lsp/testdata/channel/channel.go
+++ /dev/null
@@ -1,25 +0,0 @@
-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/internal/lsp/testdata/codelens/codelens_test.go b/internal/lsp/testdata/codelens/codelens_test.go
deleted file mode 100644
index f6c696416..000000000
--- a/internal/lsp/testdata/codelens/codelens_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package codelens //@codelens("package codelens", "run file benchmarks", "test")
-
-import "testing"
-
-func TestMain(m *testing.M) {} // no code lens for TestMain
-
-func TestFuncWithCodeLens(t *testing.T) { //@codelens("func", "run test", "test")
-}
-
-func thisShouldNotHaveACodeLens(t *testing.T) {
-}
-
-func BenchmarkFuncWithCodeLens(b *testing.B) { //@codelens("func", "run benchmark", "test")
-}
-
-func helper() {} // expect no code lens
diff --git a/internal/lsp/testdata/comment_completion/comment_completion.go.in b/internal/lsp/testdata/comment_completion/comment_completion.go.in
deleted file mode 100644
index dbca0ff17..000000000
--- a/internal/lsp/testdata/comment_completion/comment_completion.go.in
+++ /dev/null
@@ -1,70 +0,0 @@
-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/internal/lsp/testdata/complit/complit.go.in b/internal/lsp/testdata/complit/complit.go.in
deleted file mode 100644
index e819810d8..000000000
--- a/internal/lsp/testdata/complit/complit.go.in
+++ /dev/null
@@ -1,90 +0,0 @@
-package complit
-
-// 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, structPosition)
- }
- _ = position{
- X: 1,
- //@complete("", fieldY)
- }
- _ = position{
- //@complete("", fieldX)
- Y: 1,
- }
- _ = []*position{
- {
- //@complete("", fieldX, fieldY, 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, 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, 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", "foo")
- var _ *foo = fo{} //@snippet("{", complitFoo, "&foo", "&foo")
-
- struct { a, b *foo }{
- a: &fo{}, //@rank("{", complitFoo)
- b: fo{}, //@snippet("{", complitFoo, "&foo", "&foo")
- }
-}
-
-func _() {
- _ := position{
- X: 1, //@complete("X", fieldX),complete(" 1", structPosition)
- Y: , //@complete(":", fieldY),complete(" ,", structPosition)
- }
-}
diff --git a/internal/lsp/testdata/constant/constant.go b/internal/lsp/testdata/constant/constant.go
deleted file mode 100644
index c1c88e16e..000000000
--- a/internal/lsp/testdata/constant/constant.go
+++ /dev/null
@@ -1,14 +0,0 @@
-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/internal/lsp/testdata/danglingstmt/dangling_for.go b/internal/lsp/testdata/danglingstmt/dangling_for.go
deleted file mode 100644
index a16d3bd88..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_for.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package danglingstmt
-
-func _() {
- for bar //@rank(" //", danglingBar)
-}
-
-func bar() bool { //@item(danglingBar, "bar", "func() bool", "func")
- return true
-}
diff --git a/internal/lsp/testdata/danglingstmt/dangling_for_init.go b/internal/lsp/testdata/danglingstmt/dangling_for_init.go
deleted file mode 100644
index e1130bc23..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_for_init.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package danglingstmt
-
-func _() {
- for i := bar //@rank(" //", danglingBar2)
-}
-
-func bar2() int { //@item(danglingBar2, "bar2", "func() int", "func")
- return 0
-}
diff --git a/internal/lsp/testdata/danglingstmt/dangling_for_init_cond.go b/internal/lsp/testdata/danglingstmt/dangling_for_init_cond.go
deleted file mode 100644
index fb0269f16..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_for_init_cond.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package danglingstmt
-
-func _() {
- for i := bar3(); i > bar //@rank(" //", danglingBar3)
-}
-
-func bar3() int { //@item(danglingBar3, "bar3", "func() int", "func")
- return 0
-}
diff --git a/internal/lsp/testdata/danglingstmt/dangling_for_init_cond_post.go b/internal/lsp/testdata/danglingstmt/dangling_for_init_cond_post.go
deleted file mode 100644
index 14f78d392..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_for_init_cond_post.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package danglingstmt
-
-func _() {
- for i := bar4(); i > bar4(); i += bar //@rank(" //", danglingBar4)
-}
-
-func bar4() int { //@item(danglingBar4, "bar4", "func() int", "func")
- return 0
-}
diff --git a/internal/lsp/testdata/danglingstmt/dangling_if.go b/internal/lsp/testdata/danglingstmt/dangling_if.go
deleted file mode 100644
index 91f145ada..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_if.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package danglingstmt
-
-func _() {
- if foo //@rank(" //", danglingFoo)
-}
-
-func foo() bool { //@item(danglingFoo, "foo", "func() bool", "func")
- return true
-}
diff --git a/internal/lsp/testdata/danglingstmt/dangling_if_eof.go b/internal/lsp/testdata/danglingstmt/dangling_if_eof.go
deleted file mode 100644
index 3454c9fa6..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_if_eof.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package danglingstmt
-
-func bar5() bool { //@item(danglingBar5, "bar5", "func() bool", "func")
- return true
-}
-
-func _() {
- if b //@rank(" //", danglingBar5)
diff --git a/internal/lsp/testdata/danglingstmt/dangling_if_init.go b/internal/lsp/testdata/danglingstmt/dangling_if_init.go
deleted file mode 100644
index 887c31860..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_if_init.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package danglingstmt
-
-func _() {
- if i := foo //@rank(" //", danglingFoo2)
-}
-
-func foo2() bool { //@item(danglingFoo2, "foo2", "func() bool", "func")
- return true
-}
diff --git a/internal/lsp/testdata/danglingstmt/dangling_if_init_cond.go b/internal/lsp/testdata/danglingstmt/dangling_if_init_cond.go
deleted file mode 100644
index 5371283e9..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_if_init_cond.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package danglingstmt
-
-func _() {
- if i := 123; foo //@rank(" //", danglingFoo3)
-}
-
-func foo3() bool { //@item(danglingFoo3, "foo3", "func() bool", "func")
- return true
-}
diff --git a/internal/lsp/testdata/danglingstmt/dangling_multiline_if.go b/internal/lsp/testdata/danglingstmt/dangling_multiline_if.go
deleted file mode 100644
index 2213777e1..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_multiline_if.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package danglingstmt
-
-func walrus() bool { //@item(danglingWalrus, "walrus", "func() bool", "func")
- return true
-}
-
-func _() {
- if true &&
- walrus //@complete(" //", danglingWalrus)
-}
diff --git a/internal/lsp/testdata/danglingstmt/dangling_selector_1.go b/internal/lsp/testdata/danglingstmt/dangling_selector_1.go
deleted file mode 100644
index 772152f7b..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_selector_1.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package danglingstmt
-
-func _() {
- x. //@rank(" //", danglingI)
-}
-
-var x struct { i int } //@item(danglingI, "i", "int", "field")
diff --git a/internal/lsp/testdata/danglingstmt/dangling_selector_2.go b/internal/lsp/testdata/danglingstmt/dangling_selector_2.go
deleted file mode 100644
index a9e75e82a..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_selector_2.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package danglingstmt
-
-import "golang.org/x/tools/internal/lsp/foo"
-
-func _() {
- foo. //@rank(" //", Foo)
- var _ = []string{foo.} //@rank("}", Foo)
-}
diff --git a/internal/lsp/testdata/danglingstmt/dangling_switch_init.go b/internal/lsp/testdata/danglingstmt/dangling_switch_init.go
deleted file mode 100644
index 15da3ce10..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_switch_init.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package danglingstmt
-
-func _() {
- switch i := baz //@rank(" //", danglingBaz)
-}
-
-func baz() int { //@item(danglingBaz, "baz", "func() int", "func")
- return 0
-}
diff --git a/internal/lsp/testdata/danglingstmt/dangling_switch_init_tag.go b/internal/lsp/testdata/danglingstmt/dangling_switch_init_tag.go
deleted file mode 100644
index 20b825b2e..000000000
--- a/internal/lsp/testdata/danglingstmt/dangling_switch_init_tag.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package danglingstmt
-
-func _() {
- switch i := 0; baz //@rank(" //", danglingBaz2)
-}
-
-func baz2() int { //@item(danglingBaz2, "baz2", "func() int", "func")
- return 0
-}
diff --git a/internal/lsp/testdata/deep/deep.go b/internal/lsp/testdata/deep/deep.go
deleted file mode 100644
index 6ed5ff839..000000000
--- a/internal/lsp/testdata/deep/deep.go
+++ /dev/null
@@ -1,135 +0,0 @@
-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) //@deep(")", deepABField, deepAVar)
-
- deepA{a} //@snippet("}", deepABField, "a.b", "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 _() {
- // 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 //@deep(" //", deepCircle, deepCircleField),snippet(" //", deepCircleField, "*circle.deepCircle", "*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) //@deep(")", deepEmbedC, deepEmbedA, deepEmbedB)
-}
-
-func _() {
- type nested struct {
- a int
- n *nested //@item(deepNestedField, "n", "*nested", "field")
- }
-
- nested{
- a: 123, //@deep(" //", 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 //@deep(" //", deepAD, deepABC, deepA, deepAB)
-}
-
-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 //@fuzzy(" //", 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/internal/lsp/testdata/errors/errors.go b/internal/lsp/testdata/errors/errors.go
deleted file mode 100644
index 42105629e..000000000
--- a/internal/lsp/testdata/errors/errors.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package errors
-
-import (
- "golang.org/x/tools/internal/lsp/types"
-)
-
-func _() {
- bob.Bob() //@complete(".")
- types.b //@complete(" //", Bob_interface)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_args_returns.go b/internal/lsp/testdata/extract/extract_function/extract_args_returns.go
deleted file mode 100644
index 63d24df00..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_args_returns.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package extract
-
-func _() {
- a := 1
- a = 5 //@mark(exSt0, "a")
- a = a + 2 //@mark(exEn0, "2")
- //@extractfunc(exSt0, exEn0)
- b := a * 2 //@mark(exB, " b")
- _ = 3 + 4 //@mark(exEnd, "4")
- //@extractfunc(exB, exEnd)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_args_returns.go.golden b/internal/lsp/testdata/extract/extract_function/extract_args_returns.go.golden
deleted file mode 100644
index b15345e23..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_args_returns.go.golden
+++ /dev/null
@@ -1,37 +0,0 @@
--- functionextraction_extract_args_returns_5_2 --
-package extract
-
-func _() {
- a := 1
- //@mark(exSt0, "a")
- a = newFunction(a) //@mark(exEn0, "2")
- //@extractfunc(exSt0, exEn0)
- b := a * 2 //@mark(exB, " b")
- _ = 3 + 4 //@mark(exEnd, "4")
- //@extractfunc(exB, exEnd)
-}
-
-func newFunction(a int) int {
- a = 5
- a = a + 2
- return a
-}
-
--- functionextraction_extract_args_returns_8_1 --
-package extract
-
-func _() {
- a := 1
- a = 5 //@mark(exSt0, "a")
- a = a + 2 //@mark(exEn0, "2")
- //@extractfunc(exSt0, exEn0)
- //@mark(exB, " b")
- newFunction(a) //@mark(exEnd, "4")
- //@extractfunc(exB, exEnd)
-}
-
-func newFunction(a int) {
- b := a * 2
- _ = 3 + 4
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_basic.go b/internal/lsp/testdata/extract/extract_function/extract_basic.go
deleted file mode 100644
index 5e44de26f..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_basic.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package extract
-
-func _() { //@mark(exSt25, "{")
- a := 1 //@mark(exSt1, "a")
- _ = 3 + 4 //@mark(exEn1, "4")
- //@extractfunc(exSt1, exEn1)
- //@extractfunc(exSt25, exEn25)
-} //@mark(exEn25, "}")
diff --git a/internal/lsp/testdata/extract/extract_function/extract_basic.go.golden b/internal/lsp/testdata/extract/extract_function/extract_basic.go.golden
deleted file mode 100644
index 18adc4db4..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_basic.go.golden
+++ /dev/null
@@ -1,30 +0,0 @@
--- functionextraction_extract_basic_3_10 --
-package extract
-
-func _() { //@mark(exSt25, "{")
- //@mark(exSt1, "a")
- newFunction() //@mark(exEn1, "4")
- //@extractfunc(exSt1, exEn1)
- //@extractfunc(exSt25, exEn25)
-}
-
-func newFunction() {
- a := 1
- _ = 3 + 4
-} //@mark(exEn25, "}")
-
--- functionextraction_extract_basic_4_2 --
-package extract
-
-func _() { //@mark(exSt25, "{")
- //@mark(exSt1, "a")
- newFunction() //@mark(exEn1, "4")
- //@extractfunc(exSt1, exEn1)
- //@extractfunc(exSt25, exEn25)
-}
-
-func newFunction() {
- a := 1
- _ = 3 + 4
-} //@mark(exEn25, "}")
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_basic_comment.go b/internal/lsp/testdata/extract/extract_function/extract_basic_comment.go
deleted file mode 100644
index 4e2b12fbc..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_basic_comment.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package extract
-
-func _() {
- a := /* comment in the middle of a line */ 1 //@mark(exSt18, "a")
- // Comment on its own line
- _ = 3 + 4 //@mark(exEn18, "4")
- //@extractfunc(exSt18, exEn18)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_basic_comment.go.golden b/internal/lsp/testdata/extract/extract_function/extract_basic_comment.go.golden
deleted file mode 100644
index a43822a90..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_basic_comment.go.golden
+++ /dev/null
@@ -1,17 +0,0 @@
--- functionextraction_extract_basic_comment_4_2 --
-package extract
-
-func _() {
- /* comment in the middle of a line */
- //@mark(exSt18, "a")
- // Comment on its own line
- newFunction() //@mark(exEn18, "4")
- //@extractfunc(exSt18, exEn18)
-}
-
-func newFunction() {
- a := 1
-
- _ = 3 + 4
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_issue_44813.go b/internal/lsp/testdata/extract/extract_function/extract_issue_44813.go
deleted file mode 100644
index 9713b9101..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_issue_44813.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package extract
-
-import "fmt"
-
-func main() {
- x := []rune{} //@mark(exSt9, "x")
- s := "HELLO"
- for _, c := range s {
- x = append(x, c)
- } //@mark(exEn9, "}")
- //@extractfunc(exSt9, exEn9)
- fmt.Printf("%x\n", x)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_issue_44813.go.golden b/internal/lsp/testdata/extract/extract_function/extract_issue_44813.go.golden
deleted file mode 100644
index 3198c9fa2..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_issue_44813.go.golden
+++ /dev/null
@@ -1,21 +0,0 @@
--- functionextraction_extract_issue_44813_6_2 --
-package extract
-
-import "fmt"
-
-func main() {
- //@mark(exSt9, "x")
- x := newFunction() //@mark(exEn9, "}")
- //@extractfunc(exSt9, exEn9)
- 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/internal/lsp/testdata/extract/extract_function/extract_redefine.go b/internal/lsp/testdata/extract/extract_function/extract_redefine.go
deleted file mode 100644
index 604f4757c..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_redefine.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package extract
-
-import "strconv"
-
-func _() {
- i, err := strconv.Atoi("1")
- u, err := strconv.Atoi("2") //@extractfunc("u", ")")
- if i == u || err == nil {
- return
- }
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_redefine.go.golden b/internal/lsp/testdata/extract/extract_function/extract_redefine.go.golden
deleted file mode 100644
index e2ee217d1..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_redefine.go.golden
+++ /dev/null
@@ -1,18 +0,0 @@
--- functionextraction_extract_redefine_7_2 --
-package extract
-
-import "strconv"
-
-func _() {
- i, err := strconv.Atoi("1")
- u, err := newFunction() //@extractfunc("u", ")")
- if i == u || err == nil {
- return
- }
-}
-
-func newFunction() (int, error) {
- u, err := strconv.Atoi("2")
- return u, err
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_basic.go b/internal/lsp/testdata/extract/extract_function/extract_return_basic.go
deleted file mode 100644
index 1ff24daeb..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_basic.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package extract
-
-func _() bool {
- x := 1
- if x == 0 { //@mark(exSt2, "if")
- return true
- } //@mark(exEn2, "}")
- return false
- //@extractfunc(exSt2, exEn2)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_basic.go.golden b/internal/lsp/testdata/extract/extract_function/extract_return_basic.go.golden
deleted file mode 100644
index 6103d1ee9..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_basic.go.golden
+++ /dev/null
@@ -1,21 +0,0 @@
--- functionextraction_extract_return_basic_5_2 --
-package extract
-
-func _() bool {
- x := 1
- //@mark(exSt2, "if")
- shouldReturn, returnValue := newFunction(x)
- if shouldReturn {
- return returnValue
- } //@mark(exEn2, "}")
- return false
- //@extractfunc(exSt2, exEn2)
-}
-
-func newFunction(x int) (bool, bool) {
- if x == 0 {
- return true, true
- }
- return false, false
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_basic_nonnested.go b/internal/lsp/testdata/extract/extract_function/extract_return_basic_nonnested.go
deleted file mode 100644
index 08573acdd..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_basic_nonnested.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package extract
-
-func _() bool {
- x := 1 //@mark(exSt13, "x")
- if x == 0 {
- return true
- }
- return false //@mark(exEn13, "false")
- //@extractfunc(exSt13, exEn13)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_basic_nonnested.go.golden b/internal/lsp/testdata/extract/extract_function/extract_return_basic_nonnested.go.golden
deleted file mode 100644
index 19e48da01..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_basic_nonnested.go.golden
+++ /dev/null
@@ -1,17 +0,0 @@
--- functionextraction_extract_return_basic_nonnested_4_2 --
-package extract
-
-func _() bool {
- //@mark(exSt13, "x")
- return newFunction() //@mark(exEn13, "false")
- //@extractfunc(exSt13, exEn13)
-}
-
-func newFunction() bool {
- x := 1
- if x == 0 {
- return true
- }
- return false
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_complex.go b/internal/lsp/testdata/extract/extract_function/extract_return_complex.go
deleted file mode 100644
index 605c5ec2e..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_complex.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package extract
-
-import "fmt"
-
-func _() (int, string, error) {
- x := 1
- y := "hello"
- z := "bye" //@mark(exSt3, "z")
- if y == z {
- return x, y, fmt.Errorf("same")
- } else {
- z = "hi"
- return x, z, nil
- } //@mark(exEn3, "}")
- return x, z, nil
- //@extractfunc(exSt3, exEn3)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_complex.go.golden b/internal/lsp/testdata/extract/extract_function/extract_return_complex.go.golden
deleted file mode 100644
index 4d201227a..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_complex.go.golden
+++ /dev/null
@@ -1,28 +0,0 @@
--- functionextraction_extract_return_complex_8_2 --
-package extract
-
-import "fmt"
-
-func _() (int, string, error) {
- x := 1
- y := "hello"
- //@mark(exSt3, "z")
- z, shouldReturn, returnValue, returnValue1, returnValue2 := newFunction(y, x)
- if shouldReturn {
- return returnValue, returnValue1, returnValue2
- } //@mark(exEn3, "}")
- return x, z, nil
- //@extractfunc(exSt3, exEn3)
-}
-
-func newFunction(y string, x int) (string, bool, int, string, error) {
- z := "bye"
- if y == z {
- return "", true, x, y, fmt.Errorf("same")
- } else {
- z = "hi"
- return "", true, x, z, nil
- }
- return z, false, 0, "", nil
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_complex_nonnested.go b/internal/lsp/testdata/extract/extract_function/extract_return_complex_nonnested.go
deleted file mode 100644
index 6b2a4d8c0..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_complex_nonnested.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package extract
-
-import "fmt"
-
-func _() (int, string, error) {
- x := 1
- y := "hello"
- z := "bye" //@mark(exSt10, "z")
- if y == z {
- return x, y, fmt.Errorf("same")
- } else {
- z = "hi"
- return x, z, nil
- }
- return x, z, nil //@mark(exEn10, "nil")
- //@extractfunc(exSt10, exEn10)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_complex_nonnested.go.golden b/internal/lsp/testdata/extract/extract_function/extract_return_complex_nonnested.go.golden
deleted file mode 100644
index de54b1534..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_complex_nonnested.go.golden
+++ /dev/null
@@ -1,24 +0,0 @@
--- functionextraction_extract_return_complex_nonnested_8_2 --
-package extract
-
-import "fmt"
-
-func _() (int, string, error) {
- x := 1
- y := "hello"
- //@mark(exSt10, "z")
- return newFunction(y, x) //@mark(exEn10, "nil")
- //@extractfunc(exSt10, exEn10)
-}
-
-func newFunction(y string, x int) (int, string, error) {
- z := "bye"
- if y == z {
- return x, y, fmt.Errorf("same")
- } else {
- z = "hi"
- return x, z, nil
- }
- return x, z, nil
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_func_lit.go b/internal/lsp/testdata/extract/extract_function/extract_return_func_lit.go
deleted file mode 100644
index b3fb4fd21..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_func_lit.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package extract
-
-import "go/ast"
-
-func _() {
- ast.Inspect(ast.NewIdent("a"), func(n ast.Node) bool {
- if n == nil { //@mark(exSt4, "if")
- return true
- } //@mark(exEn4, "}")
- return false
- })
- //@extractfunc(exSt4, exEn4)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_func_lit.go.golden b/internal/lsp/testdata/extract/extract_function/extract_return_func_lit.go.golden
deleted file mode 100644
index 3af747c22..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_func_lit.go.golden
+++ /dev/null
@@ -1,24 +0,0 @@
--- functionextraction_extract_return_func_lit_7_3 --
-package extract
-
-import "go/ast"
-
-func _() {
- ast.Inspect(ast.NewIdent("a"), func(n ast.Node) bool {
- //@mark(exSt4, "if")
- shouldReturn, returnValue := newFunction(n)
- if shouldReturn {
- return returnValue
- } //@mark(exEn4, "}")
- return false
- })
- //@extractfunc(exSt4, exEn4)
-}
-
-func newFunction(n ast.Node) (bool, bool) {
- if n == nil {
- return true, true
- }
- return false, false
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_func_lit_nonnested.go b/internal/lsp/testdata/extract/extract_function/extract_return_func_lit_nonnested.go
deleted file mode 100644
index c22db2a6d..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_func_lit_nonnested.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package extract
-
-import "go/ast"
-
-func _() {
- ast.Inspect(ast.NewIdent("a"), func(n ast.Node) bool {
- if n == nil { //@mark(exSt11, "if")
- return true
- }
- return false //@mark(exEn11, "false")
- })
- //@extractfunc(exSt11, exEn11)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_func_lit_nonnested.go.golden b/internal/lsp/testdata/extract/extract_function/extract_return_func_lit_nonnested.go.golden
deleted file mode 100644
index efa22ba2b..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_func_lit_nonnested.go.golden
+++ /dev/null
@@ -1,20 +0,0 @@
--- functionextraction_extract_return_func_lit_nonnested_7_3 --
-package extract
-
-import "go/ast"
-
-func _() {
- ast.Inspect(ast.NewIdent("a"), func(n ast.Node) bool {
- //@mark(exSt11, "if")
- return newFunction(n) //@mark(exEn11, "false")
- })
- //@extractfunc(exSt11, exEn11)
-}
-
-func newFunction(n ast.Node) bool {
- if n == nil {
- return true
- }
- return false
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_init.go b/internal/lsp/testdata/extract/extract_function/extract_return_init.go
deleted file mode 100644
index c1994c1c1..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_init.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package extract
-
-func _() string {
- x := 1
- if x == 0 { //@mark(exSt5, "if")
- x = 3
- return "a"
- } //@mark(exEn5, "}")
- x = 2
- return "b"
- //@extractfunc(exSt5, exEn5)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_init.go.golden b/internal/lsp/testdata/extract/extract_function/extract_return_init.go.golden
deleted file mode 100644
index 31d1b2ddf..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_init.go.golden
+++ /dev/null
@@ -1,23 +0,0 @@
--- functionextraction_extract_return_init_5_2 --
-package extract
-
-func _() string {
- x := 1
- //@mark(exSt5, "if")
- shouldReturn, returnValue := newFunction(x)
- if shouldReturn {
- return returnValue
- } //@mark(exEn5, "}")
- x = 2
- return "b"
- //@extractfunc(exSt5, exEn5)
-}
-
-func newFunction(x int) (bool, string) {
- if x == 0 {
- x = 3
- return true, "a"
- }
- return false, ""
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_init_nonnested.go b/internal/lsp/testdata/extract/extract_function/extract_return_init_nonnested.go
deleted file mode 100644
index bb5ed083c..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_init_nonnested.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package extract
-
-func _() string {
- x := 1
- if x == 0 { //@mark(exSt12, "if")
- x = 3
- return "a"
- }
- x = 2
- return "b" //@mark(exEn12, "\"b\"")
- //@extractfunc(exSt12, exEn12)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_return_init_nonnested.go.golden b/internal/lsp/testdata/extract/extract_function/extract_return_init_nonnested.go.golden
deleted file mode 100644
index 58bb57325..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_return_init_nonnested.go.golden
+++ /dev/null
@@ -1,19 +0,0 @@
--- functionextraction_extract_return_init_nonnested_5_2 --
-package extract
-
-func _() string {
- x := 1
- //@mark(exSt12, "if")
- return newFunction(x) //@mark(exEn12, "\"b\"")
- //@extractfunc(exSt12, exEn12)
-}
-
-func newFunction(x int) string {
- if x == 0 {
- x = 3
- return "a"
- }
- x = 2
- return "b"
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_scope.go b/internal/lsp/testdata/extract/extract_function/extract_scope.go
deleted file mode 100644
index 6cc141fd1..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_scope.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package extract
-
-func _() {
- newFunction := 1
- a := newFunction //@extractfunc("a", "newFunction")
-}
-
-func newFunction1() int {
- return 1
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_scope.go.golden b/internal/lsp/testdata/extract/extract_function/extract_scope.go.golden
deleted file mode 100644
index a4803b4fe..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_scope.go.golden
+++ /dev/null
@@ -1,16 +0,0 @@
--- functionextraction_extract_scope_5_2 --
-package extract
-
-func _() {
- newFunction := 1
- newFunction2(newFunction) //@extractfunc("a", "newFunction")
-}
-
-func newFunction2(newFunction int) {
- a := newFunction
-}
-
-func newFunction1() int {
- return 1
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_smart_initialization.go b/internal/lsp/testdata/extract/extract_function/extract_smart_initialization.go
deleted file mode 100644
index da2c669a8..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_smart_initialization.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package extract
-
-func _() {
- var a []int
- a = append(a, 2) //@mark(exSt6, "a")
- b := 4 //@mark(exEn6, "4")
- //@extractfunc(exSt6, exEn6)
- a = append(a, b)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_smart_initialization.go.golden b/internal/lsp/testdata/extract/extract_function/extract_smart_initialization.go.golden
deleted file mode 100644
index 8be5040c4..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_smart_initialization.go.golden
+++ /dev/null
@@ -1,17 +0,0 @@
--- functionextraction_extract_smart_initialization_5_2 --
-package extract
-
-func _() {
- var a []int
- //@mark(exSt6, "a")
- a, b := newFunction(a) //@mark(exEn6, "4")
- //@extractfunc(exSt6, exEn6)
- a = append(a, b)
-}
-
-func newFunction(a []int) ([]int, int) {
- a = append(a, 2)
- b := 4
- return a, b
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_smart_return.go b/internal/lsp/testdata/extract/extract_function/extract_smart_return.go
deleted file mode 100644
index 264d680e2..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_smart_return.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package extract
-
-func _() {
- var b []int
- var a int
- a = 2 //@mark(exSt7, "a")
- b = []int{}
- b = append(b, a) //@mark(exEn7, ")")
- b[0] = 1
- //@extractfunc(exSt7, exEn7)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_smart_return.go.golden b/internal/lsp/testdata/extract/extract_function/extract_smart_return.go.golden
deleted file mode 100644
index fdf55ae6d..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_smart_return.go.golden
+++ /dev/null
@@ -1,19 +0,0 @@
--- functionextraction_extract_smart_return_6_2 --
-package extract
-
-func _() {
- var b []int
- var a int
- //@mark(exSt7, "a")
- b = newFunction(a, b) //@mark(exEn7, ")")
- b[0] = 1
- //@extractfunc(exSt7, exEn7)
-}
-
-func newFunction(a int, b []int) []int {
- a = 2
- b = []int{}
- b = append(b, a)
- return b
-}
-
diff --git a/internal/lsp/testdata/extract/extract_function/extract_unnecessary_param.go b/internal/lsp/testdata/extract/extract_function/extract_unnecessary_param.go
deleted file mode 100644
index a6eb1f872..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_unnecessary_param.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package extract
-
-func _() {
- var b []int
- var a int
- a := 2 //@mark(exSt8, "a")
- b = []int{}
- b = append(b, a) //@mark(exEn8, ")")
- b[0] = 1
- if a == 2 {
- return
- }
- //@extractfunc(exSt8, exEn8)
-}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_unnecessary_param.go.golden b/internal/lsp/testdata/extract/extract_function/extract_unnecessary_param.go.golden
deleted file mode 100644
index 4374f3728..000000000
--- a/internal/lsp/testdata/extract/extract_function/extract_unnecessary_param.go.golden
+++ /dev/null
@@ -1,22 +0,0 @@
--- functionextraction_extract_unnecessary_param_6_2 --
-package extract
-
-func _() {
- var b []int
- var a int
- //@mark(exSt8, "a")
- a, b = newFunction(b) //@mark(exEn8, ")")
- b[0] = 1
- if a == 2 {
- return
- }
- //@extractfunc(exSt8, exEn8)
-}
-
-func newFunction(b []int) (int, []int) {
- a := 2
- b = []int{}
- b = append(b, a)
- return a, b
-}
-
diff --git a/internal/lsp/testdata/extract/extract_method/extract_basic.go b/internal/lsp/testdata/extract/extract_method/extract_basic.go
deleted file mode 100644
index c9a8d9dce..000000000
--- a/internal/lsp/testdata/extract/extract_method/extract_basic.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
diff --git a/internal/lsp/testdata/extract/extract_method/extract_basic.go.golden b/internal/lsp/testdata/extract/extract_method/extract_basic.go.golden
deleted file mode 100644
index eab22a673..000000000
--- a/internal/lsp/testdata/extract/extract_method/extract_basic.go.golden
+++ /dev/null
@@ -1,728 +0,0 @@
--- functionextraction_extract_basic_13_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := newFunction(a) //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func newFunction(a *A) int {
- sum := a.x + a.y
- return sum
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- functionextraction_extract_basic_14_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return newFunction(sum) //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func newFunction(sum int) int {
- return sum
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- functionextraction_extract_basic_18_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return newFunction(a) //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func newFunction(a A) bool {
- return a.x < a.y
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- functionextraction_extract_basic_22_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := newFunction(a) //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func newFunction(a A) int {
- sum := a.x + a.y
- return sum
-}
-
--- functionextraction_extract_basic_23_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return newFunction(sum) //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func newFunction(sum int) int {
- return sum
-}
-
--- functionextraction_extract_basic_9_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return newFunction(a) //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func newFunction(a *A) bool {
- return a.x < a.y
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- functionextraction_extract_method_13_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := newFunction(a) //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func newFunction(a *A) int {
- sum := a.x + a.y
- return sum
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- functionextraction_extract_method_14_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return newFunction(sum) //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func newFunction(sum int) int {
- return sum
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- functionextraction_extract_method_18_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return newFunction(a) //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func newFunction(a A) bool {
- return a.x < a.y
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- functionextraction_extract_method_22_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := newFunction(a) //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func newFunction(a A) int {
- sum := a.x + a.y
- return sum
-}
-
--- functionextraction_extract_method_23_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return newFunction(sum) //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func newFunction(sum int) int {
- return sum
-}
-
--- functionextraction_extract_method_9_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return newFunction(a) //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func newFunction(a *A) bool {
- return a.x < a.y
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- methodextraction_extract_basic_13_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.newMethod() //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a *A) newMethod() int {
- sum := a.x + a.y
- return sum
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- methodextraction_extract_basic_14_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return a.newMethod(sum) //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (*A) newMethod(sum int) int {
- return sum
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- methodextraction_extract_basic_18_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.newMethod() //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) newMethod() bool {
- return a.x < a.y
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- methodextraction_extract_basic_22_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.newMethod() //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) newMethod() int {
- sum := a.x + a.y
- return sum
-}
-
--- methodextraction_extract_basic_23_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return a.newMethod(sum) //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (A) newMethod(sum int) int {
- return sum
-}
-
--- methodextraction_extract_basic_9_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.newMethod() //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) newMethod() bool {
- return a.x < a.y
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- methodextraction_extract_method_13_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.newMethod() //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a *A) newMethod() int {
- sum := a.x + a.y
- return sum
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- methodextraction_extract_method_14_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return a.newMethod(sum) //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (*A) newMethod(sum int) int {
- return sum
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- methodextraction_extract_method_18_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.newMethod() //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) newMethod() bool {
- return a.x < a.y
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
--- methodextraction_extract_method_22_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.newMethod() //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) newMethod() int {
- sum := a.x + a.y
- return sum
-}
-
--- methodextraction_extract_method_23_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return a.newMethod(sum) //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (A) newMethod(sum int) int {
- return sum
-}
-
--- methodextraction_extract_method_9_2 --
-package extract
-
-type A struct {
- x int
- y int
-}
-
-func (a *A) XLessThanYP() bool {
- return a.newMethod() //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a *A) newMethod() bool {
- return a.x < a.y
-}
-
-func (a *A) AddP() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
-func (a A) XLessThanY() bool {
- return a.x < a.y //@extractmethod("return", "a.y"),extractfunc("return", "a.y")
-}
-
-func (a A) Add() int {
- sum := a.x + a.y //@extractmethod("sum", "a.y"),extractfunc("sum", "a.y")
- return sum //@extractmethod("return", "sum"),extractfunc("return", "sum")
-}
-
diff --git a/internal/lsp/testdata/extract/extract_variable/extract_basic_lit.go b/internal/lsp/testdata/extract/extract_variable/extract_basic_lit.go
deleted file mode 100644
index c49e5d6a0..000000000
--- a/internal/lsp/testdata/extract/extract_variable/extract_basic_lit.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package extract
-
-func _() {
- var _ = 1 + 2 //@suggestedfix("1", "refactor.extract")
- var _ = 3 + 4 //@suggestedfix("3 + 4", "refactor.extract")
-}
diff --git a/internal/lsp/testdata/extract/extract_variable/extract_basic_lit.go.golden b/internal/lsp/testdata/extract/extract_variable/extract_basic_lit.go.golden
deleted file mode 100644
index 00ee7b4f9..000000000
--- a/internal/lsp/testdata/extract/extract_variable/extract_basic_lit.go.golden
+++ /dev/null
@@ -1,18 +0,0 @@
--- suggestedfix_extract_basic_lit_4_10 --
-package extract
-
-func _() {
- x := 1
- var _ = x + 2 //@suggestedfix("1", "refactor.extract")
- var _ = 3 + 4 //@suggestedfix("3 + 4", "refactor.extract")
-}
-
--- suggestedfix_extract_basic_lit_5_10 --
-package extract
-
-func _() {
- var _ = 1 + 2 //@suggestedfix("1", "refactor.extract")
- x := 3 + 4
- var _ = x //@suggestedfix("3 + 4", "refactor.extract")
-}
-
diff --git a/internal/lsp/testdata/extract/extract_variable/extract_func_call.go b/internal/lsp/testdata/extract/extract_variable/extract_func_call.go
deleted file mode 100644
index badc010dc..000000000
--- a/internal/lsp/testdata/extract/extract_variable/extract_func_call.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package extract
-
-import "strconv"
-
-func _() {
- x0 := append([]int{}, 1) //@suggestedfix("append([]int{}, 1)", "refactor.extract")
- str := "1"
- b, err := strconv.Atoi(str) //@suggestedfix("strconv.Atoi(str)", "refactor.extract")
-}
diff --git a/internal/lsp/testdata/extract/extract_variable/extract_func_call.go.golden b/internal/lsp/testdata/extract/extract_variable/extract_func_call.go.golden
deleted file mode 100644
index 74df67ee6..000000000
--- a/internal/lsp/testdata/extract/extract_variable/extract_func_call.go.golden
+++ /dev/null
@@ -1,36 +0,0 @@
--- suggestedfix_extract_func_call_6_7 --
-package extract
-
-import "strconv"
-
-func _() {
- x0 := append([]int{}, 1)
- a := x0 //@suggestedfix("append([]int{}, 1)", "refactor.extract")
- str := "1"
- b, err := strconv.Atoi(str) //@suggestedfix("strconv.Atoi(str)", "refactor.extract")
-}
-
--- suggestedfix_extract_func_call_6_8 --
-package extract
-
-import "strconv"
-
-func _() {
- x := append([]int{}, 1)
- x0 := x //@suggestedfix("append([]int{}, 1)", "refactor.extract")
- str := "1"
- b, err := strconv.Atoi(str) //@suggestedfix("strconv.Atoi(str)", "refactor.extract")
-}
-
--- suggestedfix_extract_func_call_8_12 --
-package extract
-
-import "strconv"
-
-func _() {
- x0 := append([]int{}, 1) //@suggestedfix("append([]int{}, 1)", "refactor.extract")
- str := "1"
- x, x1 := strconv.Atoi(str)
- b, err := x, x1 //@suggestedfix("strconv.Atoi(str)", "refactor.extract")
-}
-
diff --git a/internal/lsp/testdata/extract/extract_variable/extract_scope.go b/internal/lsp/testdata/extract/extract_variable/extract_scope.go
deleted file mode 100644
index 5dfcc3620..000000000
--- a/internal/lsp/testdata/extract/extract_variable/extract_scope.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package extract
-
-import "go/ast"
-
-func _() {
- x0 := 0
- if true {
- y := ast.CompositeLit{} //@suggestedfix("ast.CompositeLit{}", "refactor.extract")
- }
- if true {
- x1 := !false //@suggestedfix("!false", "refactor.extract")
- }
-}
diff --git a/internal/lsp/testdata/extract/extract_variable/extract_scope.go.golden b/internal/lsp/testdata/extract/extract_variable/extract_scope.go.golden
deleted file mode 100644
index e0e6464b5..000000000
--- a/internal/lsp/testdata/extract/extract_variable/extract_scope.go.golden
+++ /dev/null
@@ -1,32 +0,0 @@
--- suggestedfix_extract_scope_11_9 --
-package extract
-
-import "go/ast"
-
-func _() {
- x0 := 0
- if true {
- y := ast.CompositeLit{} //@suggestedfix("ast.CompositeLit{}", "refactor.extract")
- }
- if true {
- x := !false
- x1 := x //@suggestedfix("!false", "refactor.extract")
- }
-}
-
--- suggestedfix_extract_scope_8_8 --
-package extract
-
-import "go/ast"
-
-func _() {
- x0 := 0
- if true {
- x := ast.CompositeLit{}
- y := x //@suggestedfix("ast.CompositeLit{}", "refactor.extract")
- }
- if true {
- x1 := !false //@suggestedfix("!false", "refactor.extract")
- }
-}
-
diff --git a/internal/lsp/testdata/fieldlist/field_list.go b/internal/lsp/testdata/fieldlist/field_list.go
deleted file mode 100644
index e687defb1..000000000
--- a/internal/lsp/testdata/fieldlist/field_list.go
+++ /dev/null
@@ -1,27 +0,0 @@
-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/internal/lsp/testdata/fillstruct/a.go b/internal/lsp/testdata/fillstruct/a.go
deleted file mode 100644
index 5c6df6c4a..000000000
--- a/internal/lsp/testdata/fillstruct/a.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package fillstruct
-
-import (
- "golang.org/x/tools/internal/lsp/fillstruct/data"
-)
-
-type basicStruct struct {
- foo int
-}
-
-var _ = basicStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type twoArgStruct struct {
- foo int
- bar string
-}
-
-var _ = twoArgStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type nestedStruct struct {
- bar string
- basic basicStruct
-}
-
-var _ = nestedStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-var _ = data.B{} //@suggestedfix("}", "refactor.rewrite")
diff --git a/internal/lsp/testdata/fillstruct/a.go.golden b/internal/lsp/testdata/fillstruct/a.go.golden
deleted file mode 100644
index 5d6dbceb2..000000000
--- a/internal/lsp/testdata/fillstruct/a.go.golden
+++ /dev/null
@@ -1,126 +0,0 @@
--- suggestedfix_a_11_21 --
-package fillstruct
-
-import (
- "golang.org/x/tools/internal/lsp/fillstruct/data"
-)
-
-type basicStruct struct {
- foo int
-}
-
-var _ = basicStruct{
- foo: 0,
-} //@suggestedfix("}", "refactor.rewrite")
-
-type twoArgStruct struct {
- foo int
- bar string
-}
-
-var _ = twoArgStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type nestedStruct struct {
- bar string
- basic basicStruct
-}
-
-var _ = nestedStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-var _ = data.B{} //@suggestedfix("}", "refactor.rewrite")
-
--- suggestedfix_a_18_22 --
-package fillstruct
-
-import (
- "golang.org/x/tools/internal/lsp/fillstruct/data"
-)
-
-type basicStruct struct {
- foo int
-}
-
-var _ = basicStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type twoArgStruct struct {
- foo int
- bar string
-}
-
-var _ = twoArgStruct{
- foo: 0,
- bar: "",
-} //@suggestedfix("}", "refactor.rewrite")
-
-type nestedStruct struct {
- bar string
- basic basicStruct
-}
-
-var _ = nestedStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-var _ = data.B{} //@suggestedfix("}", "refactor.rewrite")
-
--- suggestedfix_a_25_22 --
-package fillstruct
-
-import (
- "golang.org/x/tools/internal/lsp/fillstruct/data"
-)
-
-type basicStruct struct {
- foo int
-}
-
-var _ = basicStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type twoArgStruct struct {
- foo int
- bar string
-}
-
-var _ = twoArgStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type nestedStruct struct {
- bar string
- basic basicStruct
-}
-
-var _ = nestedStruct{
- bar: "",
- basic: basicStruct{},
-} //@suggestedfix("}", "refactor.rewrite")
-
-var _ = data.B{} //@suggestedfix("}", "refactor.rewrite")
-
--- suggestedfix_a_27_16 --
-package fillstruct
-
-import (
- "golang.org/x/tools/internal/lsp/fillstruct/data"
-)
-
-type basicStruct struct {
- foo int
-}
-
-var _ = basicStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type twoArgStruct struct {
- foo int
- bar string
-}
-
-var _ = twoArgStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type nestedStruct struct {
- bar string
- basic basicStruct
-}
-
-var _ = nestedStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-var _ = data.B{
- ExportedInt: 0,
-} //@suggestedfix("}", "refactor.rewrite")
-
diff --git a/internal/lsp/testdata/fillstruct/a2.go b/internal/lsp/testdata/fillstruct/a2.go
deleted file mode 100644
index 8e12a6b54..000000000
--- a/internal/lsp/testdata/fillstruct/a2.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package fillstruct
-
-type typedStruct struct {
- m map[string]int
- s []int
- c chan int
- c1 <-chan int
- a [2]string
-}
-
-var _ = typedStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type funStruct struct {
- fn func(i int) int
-}
-
-var _ = funStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type funStructCompex struct {
- fn func(i int, s string) (string, int)
-}
-
-var _ = funStructCompex{} //@suggestedfix("}", "refactor.rewrite")
-
-type funStructEmpty struct {
- fn func()
-}
-
-var _ = funStructEmpty{} //@suggestedfix("}", "refactor.rewrite")
diff --git a/internal/lsp/testdata/fillstruct/a2.go.golden b/internal/lsp/testdata/fillstruct/a2.go.golden
deleted file mode 100644
index 78a6ee2b6..000000000
--- a/internal/lsp/testdata/fillstruct/a2.go.golden
+++ /dev/null
@@ -1,139 +0,0 @@
--- suggestedfix_a2_11_21 --
-package fillstruct
-
-type typedStruct struct {
- m map[string]int
- s []int
- c chan int
- c1 <-chan int
- a [2]string
-}
-
-var _ = typedStruct{
- m: map[string]int{},
- s: []int{},
- c: make(chan int),
- c1: make(<-chan int),
- a: [2]string{},
-} //@suggestedfix("}", "refactor.rewrite")
-
-type funStruct struct {
- fn func(i int) int
-}
-
-var _ = funStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type funStructCompex struct {
- fn func(i int, s string) (string, int)
-}
-
-var _ = funStructCompex{} //@suggestedfix("}", "refactor.rewrite")
-
-type funStructEmpty struct {
- fn func()
-}
-
-var _ = funStructEmpty{} //@suggestedfix("}", "refactor.rewrite")
-
--- suggestedfix_a2_17_19 --
-package fillstruct
-
-type typedStruct struct {
- m map[string]int
- s []int
- c chan int
- c1 <-chan int
- a [2]string
-}
-
-var _ = typedStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type funStruct struct {
- fn func(i int) int
-}
-
-var _ = funStruct{
- fn: func(i int) int {
- },
-} //@suggestedfix("}", "refactor.rewrite")
-
-type funStructCompex struct {
- fn func(i int, s string) (string, int)
-}
-
-var _ = funStructCompex{} //@suggestedfix("}", "refactor.rewrite")
-
-type funStructEmpty struct {
- fn func()
-}
-
-var _ = funStructEmpty{} //@suggestedfix("}", "refactor.rewrite")
-
--- suggestedfix_a2_23_25 --
-package fillstruct
-
-type typedStruct struct {
- m map[string]int
- s []int
- c chan int
- c1 <-chan int
- a [2]string
-}
-
-var _ = typedStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type funStruct struct {
- fn func(i int) int
-}
-
-var _ = funStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type funStructCompex struct {
- fn func(i int, s string) (string, int)
-}
-
-var _ = funStructCompex{
- fn: func(i int, s string) (string, int) {
- },
-} //@suggestedfix("}", "refactor.rewrite")
-
-type funStructEmpty struct {
- fn func()
-}
-
-var _ = funStructEmpty{} //@suggestedfix("}", "refactor.rewrite")
-
--- suggestedfix_a2_29_24 --
-package fillstruct
-
-type typedStruct struct {
- m map[string]int
- s []int
- c chan int
- c1 <-chan int
- a [2]string
-}
-
-var _ = typedStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type funStruct struct {
- fn func(i int) int
-}
-
-var _ = funStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-type funStructCompex struct {
- fn func(i int, s string) (string, int)
-}
-
-var _ = funStructCompex{} //@suggestedfix("}", "refactor.rewrite")
-
-type funStructEmpty struct {
- fn func()
-}
-
-var _ = funStructEmpty{
- fn: func() {
- },
-} //@suggestedfix("}", "refactor.rewrite")
-
diff --git a/internal/lsp/testdata/fillstruct/a3.go b/internal/lsp/testdata/fillstruct/a3.go
deleted file mode 100644
index 730db3054..000000000
--- a/internal/lsp/testdata/fillstruct/a3.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package fillstruct
-
-import (
- "go/ast"
- "go/token"
-)
-
-type Foo struct {
- A int
-}
-
-type Bar struct {
- X *Foo
- Y *Foo
-}
-
-var _ = Bar{} //@suggestedfix("}", "refactor.rewrite")
-
-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{} //@suggestedfix("}", "refactor.rewrite")
-
-type pointerBuiltinStruct struct {
- b *bool
- s *string
- i *int
-}
-
-var _ = pointerBuiltinStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-var _ = []ast.BasicLit{
- {}, //@suggestedfix("}", "refactor.rewrite")
-}
-
-var _ = []ast.BasicLit{{}} //@suggestedfix("}", "refactor.rewrite")
diff --git a/internal/lsp/testdata/fillstruct/a3.go.golden b/internal/lsp/testdata/fillstruct/a3.go.golden
deleted file mode 100644
index 1d8672927..000000000
--- a/internal/lsp/testdata/fillstruct/a3.go.golden
+++ /dev/null
@@ -1,243 +0,0 @@
--- suggestedfix_a3_17_13 --
-package fillstruct
-
-import (
- "go/ast"
- "go/token"
-)
-
-type Foo struct {
- A int
-}
-
-type Bar struct {
- X *Foo
- Y *Foo
-}
-
-var _ = Bar{
- X: &Foo{},
- Y: &Foo{},
-} //@suggestedfix("}", "refactor.rewrite")
-
-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{} //@suggestedfix("}", "refactor.rewrite")
-
-type pointerBuiltinStruct struct {
- b *bool
- s *string
- i *int
-}
-
-var _ = pointerBuiltinStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-var _ = []ast.BasicLit{
- {}, //@suggestedfix("}", "refactor.rewrite")
-}
-
-var _ = []ast.BasicLit{{}} //@suggestedfix("}", "refactor.rewrite")
-
--- suggestedfix_a3_28_24 --
-package fillstruct
-
-import (
- "go/ast"
- "go/token"
-)
-
-type Foo struct {
- A int
-}
-
-type Bar struct {
- X *Foo
- Y *Foo
-}
-
-var _ = Bar{} //@suggestedfix("}", "refactor.rewrite")
-
-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{
- 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{},
-} //@suggestedfix("}", "refactor.rewrite")
-
-type pointerBuiltinStruct struct {
- b *bool
- s *string
- i *int
-}
-
-var _ = pointerBuiltinStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-var _ = []ast.BasicLit{
- {}, //@suggestedfix("}", "refactor.rewrite")
-}
-
-var _ = []ast.BasicLit{{}} //@suggestedfix("}", "refactor.rewrite")
-
--- suggestedfix_a3_36_30 --
-package fillstruct
-
-import (
- "go/ast"
- "go/token"
-)
-
-type Foo struct {
- A int
-}
-
-type Bar struct {
- X *Foo
- Y *Foo
-}
-
-var _ = Bar{} //@suggestedfix("}", "refactor.rewrite")
-
-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{} //@suggestedfix("}", "refactor.rewrite")
-
-type pointerBuiltinStruct struct {
- b *bool
- s *string
- i *int
-}
-
-var _ = pointerBuiltinStruct{
- b: new(bool),
- s: new(string),
- i: new(int),
-} //@suggestedfix("}", "refactor.rewrite")
-
-var _ = []ast.BasicLit{
- {}, //@suggestedfix("}", "refactor.rewrite")
-}
-
-var _ = []ast.BasicLit{{}} //@suggestedfix("}", "refactor.rewrite")
-
--- suggestedfix_a3_39_3 --
-package fillstruct
-
-import (
- "go/ast"
- "go/token"
-)
-
-type Foo struct {
- A int
-}
-
-type Bar struct {
- X *Foo
- Y *Foo
-}
-
-var _ = Bar{} //@suggestedfix("}", "refactor.rewrite")
-
-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{} //@suggestedfix("}", "refactor.rewrite")
-
-type pointerBuiltinStruct struct {
- b *bool
- s *string
- i *int
-}
-
-var _ = pointerBuiltinStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-var _ = []ast.BasicLit{
- {
- ValuePos: 0,
- Kind: 0,
- Value: "",
- }, //@suggestedfix("}", "refactor.rewrite")
-}
-
-var _ = []ast.BasicLit{{}} //@suggestedfix("}", "refactor.rewrite")
-
--- suggestedfix_a3_42_25 --
-package fillstruct
-
-import (
- "go/ast"
- "go/token"
-)
-
-type Foo struct {
- A int
-}
-
-type Bar struct {
- X *Foo
- Y *Foo
-}
-
-var _ = Bar{} //@suggestedfix("}", "refactor.rewrite")
-
-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{} //@suggestedfix("}", "refactor.rewrite")
-
-type pointerBuiltinStruct struct {
- b *bool
- s *string
- i *int
-}
-
-var _ = pointerBuiltinStruct{} //@suggestedfix("}", "refactor.rewrite")
-
-var _ = []ast.BasicLit{
- {}, //@suggestedfix("}", "refactor.rewrite")
-}
-
-var _ = []ast.BasicLit{{
- ValuePos: 0,
- Kind: 0,
- Value: "",
-}} //@suggestedfix("}", "refactor.rewrite")
-
diff --git a/internal/lsp/testdata/fillstruct/a4.go b/internal/lsp/testdata/fillstruct/a4.go
deleted file mode 100644
index 7833d338c..000000000
--- a/internal/lsp/testdata/fillstruct/a4.go
+++ /dev/null
@@ -1,39 +0,0 @@
-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{} //@suggestedfix("}", "refactor.rewrite")
-
- var s string
- var _ = sStruct{} //@suggestedfix("}", "refactor.rewrite")
-
- var n int
- _ = []int{}
- if true {
- arr := []int{1, 2}
- }
- var _ = multiFill{} //@suggestedfix("}", "refactor.rewrite")
-
- var node *ast.CompositeLit
- var _ = assignStruct{} //@suggestedfix("}", "refactor.rewrite")
-}
diff --git a/internal/lsp/testdata/fillstruct/a4.go.golden b/internal/lsp/testdata/fillstruct/a4.go.golden
deleted file mode 100644
index 109c6b5ea..000000000
--- a/internal/lsp/testdata/fillstruct/a4.go.golden
+++ /dev/null
@@ -1,174 +0,0 @@
--- suggestedfix_a4_25_18 --
-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{
- X: x,
- } //@suggestedfix("}", "refactor.rewrite")
-
- var s string
- var _ = sStruct{} //@suggestedfix("}", "refactor.rewrite")
-
- var n int
- _ = []int{}
- if true {
- arr := []int{1, 2}
- }
- var _ = multiFill{} //@suggestedfix("}", "refactor.rewrite")
-
- var node *ast.CompositeLit
- var _ = assignStruct{} //@suggestedfix("}", "refactor.rewrite")
-}
-
--- suggestedfix_a4_28_18 --
-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{} //@suggestedfix("}", "refactor.rewrite")
-
- var s string
- var _ = sStruct{
- str: s,
- } //@suggestedfix("}", "refactor.rewrite")
-
- var n int
- _ = []int{}
- if true {
- arr := []int{1, 2}
- }
- var _ = multiFill{} //@suggestedfix("}", "refactor.rewrite")
-
- var node *ast.CompositeLit
- var _ = assignStruct{} //@suggestedfix("}", "refactor.rewrite")
-}
-
--- suggestedfix_a4_35_20 --
-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{} //@suggestedfix("}", "refactor.rewrite")
-
- var s string
- var _ = sStruct{} //@suggestedfix("}", "refactor.rewrite")
-
- var n int
- _ = []int{}
- if true {
- arr := []int{1, 2}
- }
- var _ = multiFill{
- num: n,
- strin: s,
- arr: []int{},
- } //@suggestedfix("}", "refactor.rewrite")
-
- var node *ast.CompositeLit
- var _ = assignStruct{} //@suggestedfix("}", "refactor.rewrite")
-}
-
--- suggestedfix_a4_38_23 --
-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{} //@suggestedfix("}", "refactor.rewrite")
-
- var s string
- var _ = sStruct{} //@suggestedfix("}", "refactor.rewrite")
-
- var n int
- _ = []int{}
- if true {
- arr := []int{1, 2}
- }
- var _ = multiFill{} //@suggestedfix("}", "refactor.rewrite")
-
- var node *ast.CompositeLit
- var _ = assignStruct{
- n: node,
- } //@suggestedfix("}", "refactor.rewrite")
-}
-
diff --git a/internal/lsp/testdata/fillstruct/data/a.go b/internal/lsp/testdata/fillstruct/data/a.go
deleted file mode 100644
index 7ca37736b..000000000
--- a/internal/lsp/testdata/fillstruct/data/a.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package data
-
-type B struct {
- ExportedInt int
- unexportedInt int
-}
diff --git a/internal/lsp/testdata/fillstruct/fill_struct.go b/internal/lsp/testdata/fillstruct/fill_struct.go
deleted file mode 100644
index fccec1353..000000000
--- a/internal/lsp/testdata/fillstruct/fill_struct.go
+++ /dev/null
@@ -1,26 +0,0 @@
-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{} //@suggestedfix("}", "refactor.rewrite")
- b := StructA2{} //@suggestedfix("}", "refactor.rewrite")
- c := StructA3{} //@suggestedfix("}", "refactor.rewrite")
- if true {
- _ = StructA3{} //@suggestedfix("}", "refactor.rewrite")
- }
-}
diff --git a/internal/lsp/testdata/fillstruct/fill_struct.go.golden b/internal/lsp/testdata/fillstruct/fill_struct.go.golden
deleted file mode 100644
index 8d9970315..000000000
--- a/internal/lsp/testdata/fillstruct/fill_struct.go.golden
+++ /dev/null
@@ -1,124 +0,0 @@
--- suggestedfix_fill_struct_20_15 --
-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{
- unexportedIntField: 0,
- ExportedIntField: 0,
- MapA: map[int]string{},
- Array: []int{},
- StructB: StructB{},
- } //@suggestedfix("}", "refactor.rewrite")
- b := StructA2{} //@suggestedfix("}", "refactor.rewrite")
- c := StructA3{} //@suggestedfix("}", "refactor.rewrite")
- if true {
- _ = StructA3{} //@suggestedfix("}", "refactor.rewrite")
- }
-}
-
--- suggestedfix_fill_struct_21_16 --
-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{} //@suggestedfix("}", "refactor.rewrite")
- b := StructA2{
- B: &StructB{},
- } //@suggestedfix("}", "refactor.rewrite")
- c := StructA3{} //@suggestedfix("}", "refactor.rewrite")
- if true {
- _ = StructA3{} //@suggestedfix("}", "refactor.rewrite")
- }
-}
-
--- suggestedfix_fill_struct_22_16 --
-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{} //@suggestedfix("}", "refactor.rewrite")
- b := StructA2{} //@suggestedfix("}", "refactor.rewrite")
- c := StructA3{
- B: StructB{},
- } //@suggestedfix("}", "refactor.rewrite")
- if true {
- _ = StructA3{} //@suggestedfix("}", "refactor.rewrite")
- }
-}
-
--- suggestedfix_fill_struct_24_16 --
-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{} //@suggestedfix("}", "refactor.rewrite")
- b := StructA2{} //@suggestedfix("}", "refactor.rewrite")
- c := StructA3{} //@suggestedfix("}", "refactor.rewrite")
- if true {
- _ = StructA3{
- B: StructB{},
- } //@suggestedfix("}", "refactor.rewrite")
- }
-}
-
diff --git a/internal/lsp/testdata/fillstruct/fill_struct_anon.go b/internal/lsp/testdata/fillstruct/fill_struct_anon.go
deleted file mode 100644
index b5d2337fd..000000000
--- a/internal/lsp/testdata/fillstruct/fill_struct_anon.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package fillstruct
-
-type StructAnon struct {
- a struct{}
- b map[string]interface{}
- c map[string]struct {
- d int
- e bool
- }
-}
-
-func fill() {
- _ := StructAnon{} //@suggestedfix("}", "refactor.rewrite")
-}
diff --git a/internal/lsp/testdata/fillstruct/fill_struct_anon.go.golden b/internal/lsp/testdata/fillstruct/fill_struct_anon.go.golden
deleted file mode 100644
index eb6ffd661..000000000
--- a/internal/lsp/testdata/fillstruct/fill_struct_anon.go.golden
+++ /dev/null
@@ -1,20 +0,0 @@
--- suggestedfix_fill_struct_anon_13_18 --
-package fillstruct
-
-type StructAnon struct {
- a struct{}
- b map[string]interface{}
- c map[string]struct {
- d int
- e bool
- }
-}
-
-func fill() {
- _ := StructAnon{
- a: struct{}{},
- b: map[string]interface{}{},
- c: map[string]struct{d int; e bool}{},
- } //@suggestedfix("}", "refactor.rewrite")
-}
-
diff --git a/internal/lsp/testdata/fillstruct/fill_struct_nested.go b/internal/lsp/testdata/fillstruct/fill_struct_nested.go
deleted file mode 100644
index 79eb84b74..000000000
--- a/internal/lsp/testdata/fillstruct/fill_struct_nested.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package fillstruct
-
-type StructB struct {
- StructC
-}
-
-type StructC struct {
- unexportedInt int
-}
-
-func nested() {
- c := StructB{
- StructC: StructC{}, //@suggestedfix("}", "refactor.rewrite")
- }
-}
diff --git a/internal/lsp/testdata/fillstruct/fill_struct_nested.go.golden b/internal/lsp/testdata/fillstruct/fill_struct_nested.go.golden
deleted file mode 100644
index 30061a5d7..000000000
--- a/internal/lsp/testdata/fillstruct/fill_struct_nested.go.golden
+++ /dev/null
@@ -1,19 +0,0 @@
--- suggestedfix_fill_struct_nested_13_20 --
-package fillstruct
-
-type StructB struct {
- StructC
-}
-
-type StructC struct {
- unexportedInt int
-}
-
-func nested() {
- c := StructB{
- StructC: StructC{
- unexportedInt: 0,
- }, //@suggestedfix("}", "refactor.rewrite")
- }
-}
-
diff --git a/internal/lsp/testdata/fillstruct/fill_struct_package.go b/internal/lsp/testdata/fillstruct/fill_struct_package.go
deleted file mode 100644
index 71f124858..000000000
--- a/internal/lsp/testdata/fillstruct/fill_struct_package.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package fillstruct
-
-import (
- h2 "net/http"
-
- "golang.org/x/tools/internal/lsp/fillstruct/data"
-)
-
-func unexported() {
- a := data.B{} //@suggestedfix("}", "refactor.rewrite")
- _ = h2.Client{} //@suggestedfix("}", "refactor.rewrite")
-}
diff --git a/internal/lsp/testdata/fillstruct/fill_struct_package.go.golden b/internal/lsp/testdata/fillstruct/fill_struct_package.go.golden
deleted file mode 100644
index 13c857025..000000000
--- a/internal/lsp/testdata/fillstruct/fill_struct_package.go.golden
+++ /dev/null
@@ -1,36 +0,0 @@
--- suggestedfix_fill_struct_package_10_14 --
-package fillstruct
-
-import (
- h2 "net/http"
-
- "golang.org/x/tools/internal/lsp/fillstruct/data"
-)
-
-func unexported() {
- a := data.B{
- ExportedInt: 0,
- } //@suggestedfix("}", "refactor.rewrite")
- _ = h2.Client{} //@suggestedfix("}", "refactor.rewrite")
-}
-
--- suggestedfix_fill_struct_package_11_16 --
-package fillstruct
-
-import (
- h2 "net/http"
-
- "golang.org/x/tools/internal/lsp/fillstruct/data"
-)
-
-func unexported() {
- a := data.B{} //@suggestedfix("}", "refactor.rewrite")
- _ = h2.Client{
- Transport: nil,
- CheckRedirect: func(req *h2.Request, via []*h2.Request) error {
- },
- Jar: nil,
- Timeout: 0,
- } //@suggestedfix("}", "refactor.rewrite")
-}
-
diff --git a/internal/lsp/testdata/fillstruct/fill_struct_partial.go b/internal/lsp/testdata/fillstruct/fill_struct_partial.go
deleted file mode 100644
index 97b517dcd..000000000
--- a/internal/lsp/testdata/fillstruct/fill_struct_partial.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package fillstruct
-
-type StructPartialA struct {
- PrefilledInt int
- UnfilledInt int
- StructPartialB
-}
-
-type StructPartialB struct {
- PrefilledInt int
- UnfilledInt int
-}
-
-func fill() {
- a := StructPartialA{
- PrefilledInt: 5,
- } //@suggestedfix("}", "refactor.rewrite")
- b := StructPartialB{
- /* this comment should disappear */
- PrefilledInt: 7, // This comment should be blown away.
- /* As should
- this one */
- } //@suggestedfix("}", "refactor.rewrite")
-}
diff --git a/internal/lsp/testdata/fillstruct/fill_struct_partial.go.golden b/internal/lsp/testdata/fillstruct/fill_struct_partial.go.golden
deleted file mode 100644
index 2d063c14d..000000000
--- a/internal/lsp/testdata/fillstruct/fill_struct_partial.go.golden
+++ /dev/null
@@ -1,52 +0,0 @@
--- suggestedfix_fill_struct_partial_17_2 --
-package fillstruct
-
-type StructPartialA struct {
- PrefilledInt int
- UnfilledInt int
- StructPartialB
-}
-
-type StructPartialB struct {
- PrefilledInt int
- UnfilledInt int
-}
-
-func fill() {
- a := StructPartialA{
- PrefilledInt: 5,
- UnfilledInt: 0,
- StructPartialB: StructPartialB{},
- } //@suggestedfix("}", "refactor.rewrite")
- b := StructPartialB{
- /* this comment should disappear */
- PrefilledInt: 7, // This comment should be blown away.
- /* As should
- this one */
- } //@suggestedfix("}", "refactor.rewrite")
-}
-
--- suggestedfix_fill_struct_partial_23_2 --
-package fillstruct
-
-type StructPartialA struct {
- PrefilledInt int
- UnfilledInt int
- StructPartialB
-}
-
-type StructPartialB struct {
- PrefilledInt int
- UnfilledInt int
-}
-
-func fill() {
- a := StructPartialA{
- PrefilledInt: 5,
- } //@suggestedfix("}", "refactor.rewrite")
- b := StructPartialB{
- PrefilledInt: 7,
- UnfilledInt: 0,
- } //@suggestedfix("}", "refactor.rewrite")
-}
-
diff --git a/internal/lsp/testdata/fillstruct/fill_struct_spaces.go b/internal/lsp/testdata/fillstruct/fill_struct_spaces.go
deleted file mode 100644
index d5d1bbba5..000000000
--- a/internal/lsp/testdata/fillstruct/fill_struct_spaces.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package fillstruct
-
-type StructD struct {
- ExportedIntField int
-}
-
-func spaces() {
- d := StructD{} //@suggestedfix("}", "refactor.rewrite")
-}
diff --git a/internal/lsp/testdata/fillstruct/fill_struct_spaces.go.golden b/internal/lsp/testdata/fillstruct/fill_struct_spaces.go.golden
deleted file mode 100644
index 0d755334c..000000000
--- a/internal/lsp/testdata/fillstruct/fill_struct_spaces.go.golden
+++ /dev/null
@@ -1,13 +0,0 @@
--- suggestedfix_fill_struct_spaces_8_15 --
-package fillstruct
-
-type StructD struct {
- ExportedIntField int
-}
-
-func spaces() {
- d := StructD{
- ExportedIntField: 0,
- } //@suggestedfix("}", "refactor.rewrite")
-}
-
diff --git a/internal/lsp/testdata/folding/a.go b/internal/lsp/testdata/folding/a.go
deleted file mode 100644
index e07d7e0bf..000000000
--- a/internal/lsp/testdata/folding/a.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package folding //@fold("package")
-
-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`
-}
diff --git a/internal/lsp/testdata/folding/a.go.golden b/internal/lsp/testdata/folding/a.go.golden
deleted file mode 100644
index ce6910233..000000000
--- a/internal/lsp/testdata/folding/a.go.golden
+++ /dev/null
@@ -1,759 +0,0 @@
--- foldingRange-0 --
-package folding //@fold("package")
-
-import (<>)
-
-import _ "os"
-
-// bar is a function.<>
-func bar(<>) string {<>}
-
--- foldingRange-1 --
-package folding //@fold("package")
-
-import (
- "fmt"
- _ "log"
-)
-
-import _ "os"
-
-// bar is a function.
-// With a multiline doc comment.
-func bar() string {
- /* This is a single line comment */
- switch {<>}
- /* This is a multiline<>
-
- /* This is a multiline<>
- _ = []int{<>}
- _ = [2]string{<>}
- _ = map[string]int{<>}
- type T struct {<>}
- _ = T{<>}
- x, y := make(<>), make(<>)
- select {<>}
- // This is a multiline comment<>
- return <>
-}
-
--- foldingRange-2 --
-package folding //@fold("package")
-
-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:<>
- case false:<>
- 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:<>
- case <-y:<>
- default:<>
- }
- // This is a multiline comment
- // that is not a doc comment.
- return `
-this string
-is not indented`
-}
-
--- foldingRange-3 --
-package folding //@fold("package")
-
-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 {<>} else {<>}
- case false:
- fmt.Println(<>)
- default:
- fmt.Println(<>)
- }
- /* 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 {<>} else {<>}
- case <-y:
- fmt.Println(<>)
- default:
- fmt.Println(<>)
- }
- // This is a multiline comment
- // that is not a doc comment.
- return `
-this string
-is not indented`
-}
-
--- foldingRange-4 --
-package folding //@fold("package")
-
-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(<>)
- } else {
- fmt.Println(<>)
- }
- 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(<>)
- } else {
- fmt.Println(<>)
- }
- 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`
-}
-
--- foldingRange-cmd --
-3:9-6:0
-10:22-11:32
-12:10-12:9
-12:20-75:0
-14:10-25:1
-15:12-20:3
-16:12-18:2
-17:16-17:21
-18:11-20:2
-19:16-19:22
-21:13-22:22
-22:15-22:21
-23:10-24:24
-24:15-24:23
-26:24-28:11
-30:24-33:32
-34:12-38:1
-39:16-41:1
-42:21-46:1
-47:17-51:1
-52:8-56:1
-57:15-57:23
-57:32-57:40
-58:10-69:1
-59:18-64:3
-60:11-62:2
-61:16-61:28
-62:11-64:2
-63:16-63:29
-65:11-66:18
-66:15-66:17
-67:10-68:24
-68:15-68:23
-70:32-71:30
-72:9-74:16
-
--- foldingRange-comment-0 --
-package folding //@fold("package")
-
-import (
- "fmt"
- _ "log"
-)
-
-import _ "os"
-
-// bar is a function.<>
-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<>
-
- /* This is a multiline<>
- _ = []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<>
- return `
-this string
-is not indented`
-}
-
--- foldingRange-imports-0 --
-package folding //@fold("package")
-
-import (<>)
-
-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`
-}
-
--- foldingRange-lineFolding-0 --
-package folding //@fold("package")
-
-import (<>
-)
-
-import _ "os"
-
-// bar is a function.<>
-func bar() string {<>
-}
-
--- foldingRange-lineFolding-1 --
-package folding //@fold("package")
-
-import (
- "fmt"
- _ "log"
-)
-
-import _ "os"
-
-// bar is a function.
-// With a multiline doc comment.
-func bar() string {
- /* This is a single line comment */
- switch {<>
- }
- /* This is a multiline<>
-
- /* This is a multiline<>
- _ = []int{<>,
- }
- _ = [2]string{"d",
- "e",
- }
- _ = map[string]int{<>,
- }
- type T struct {<>
- }
- _ = T{<>,
- }
- x, y := make(chan bool), make(chan bool)
- select {<>
- }
- // This is a multiline comment<>
- return <>
-}
-
--- foldingRange-lineFolding-2 --
-package folding //@fold("package")
-
-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:<>
- case false:<>
- 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:<>
- case <-y:<>
- default:<>
- }
- // This is a multiline comment
- // that is not a doc comment.
- return `
-this string
-is not indented`
-}
-
--- foldingRange-lineFolding-3 --
-package folding //@fold("package")
-
-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 {<>
- } else {<>
- }
- 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 {<>
- } else {<>
- }
- 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`
-}
-
--- foldingRange-lineFolding-comment-0 --
-package folding //@fold("package")
-
-import (
- "fmt"
- _ "log"
-)
-
-import _ "os"
-
-// bar is a function.<>
-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<>
-
- /* This is a multiline<>
- _ = []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<>
- return `
-this string
-is not indented`
-}
-
--- foldingRange-lineFolding-imports-0 --
-package folding //@fold("package")
-
-import (<>
-)
-
-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`
-}
-
diff --git a/internal/lsp/testdata/folding/bad.go.golden b/internal/lsp/testdata/folding/bad.go.golden
deleted file mode 100644
index d1bdfec60..000000000
--- a/internal/lsp/testdata/folding/bad.go.golden
+++ /dev/null
@@ -1,91 +0,0 @@
--- foldingRange-0 --
-package folding //@fold("package")
-
-import (<>)
-
-import (<>)
-
-// badBar is a function.
-func badBar(<>) string {<>}
-
--- foldingRange-1 --
-package folding //@fold("package")
-
-import ( "fmt"
- _ "log"
-)
-
-import (
- _ "os" )
-
-// badBar is a function.
-func badBar() string { x := true
- if x {<>} else {<>}
- return
-}
-
--- foldingRange-2 --
-package folding //@fold("package")
-
-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(<>)
- } else {
- fmt.Println(<>) }
- return
-}
-
--- foldingRange-cmd --
-3:9-5:0
-7:9-8:8
-11:13-11:12
-11:23-18:0
-12:8-15:1
-14:15-14:20
-15:10-16:23
-16:15-16:21
-
--- foldingRange-imports-0 --
-package folding //@fold("package")
-
-import (<>)
-
-import (<>)
-
-// 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
-}
-
--- foldingRange-lineFolding-0 --
-package folding //@fold("package")
-
-import ( "fmt"
- _ "log"
-)
-
-import (
- _ "os" )
-
-// badBar is a function.
-func badBar() string { x := true
- if x {<>
- } else {
- fmt.Println("false") }
- return
-}
-
diff --git a/internal/lsp/testdata/folding/bad.go.in b/internal/lsp/testdata/folding/bad.go.in
deleted file mode 100644
index 84fcb740f..000000000
--- a/internal/lsp/testdata/folding/bad.go.in
+++ /dev/null
@@ -1,18 +0,0 @@
-package folding //@fold("package")
-
-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
-}
diff --git a/internal/lsp/testdata/foo/foo.go b/internal/lsp/testdata/foo/foo.go
deleted file mode 100644
index 20ea183e5..000000000
--- a/internal/lsp/testdata/foo/foo.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package foo //@mark(PackageFoo, "foo"),item(PackageFoo, "foo", "\"golang.org/x/tools/internal/lsp/foo\"", "package")
-
-type StructFoo struct { //@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 //@mark(sFoo1, "sFoo"),complete("t", StructFoo)
- if x := sFoo; x.Value == 1 { //@mark(sFoo2, "sFoo"),complete("V", Value),typdef("sFoo", StructFoo),refs("sFo", sFoo1, sFoo2)
- return
- }
-}
-
-func _() {
- shadowed := 123
- {
- shadowed := "hi" //@item(shadowed, "shadowed", "string", "var"),refs("shadowed", shadowed)
- sha //@complete("a", shadowed)
- }
-}
-
-type IntFoo int //@item(IntFoo, "IntFoo", "int", "type")
diff --git a/internal/lsp/testdata/format/bad_format.go.golden b/internal/lsp/testdata/format/bad_format.go.golden
deleted file mode 100644
index c2ac5a1a1..000000000
--- a/internal/lsp/testdata/format/bad_format.go.golden
+++ /dev/null
@@ -1,21 +0,0 @@
--- gofmt --
-package format //@format("package")
-
-import (
- "fmt"
- "log"
- "runtime"
-)
-
-func hello() {
-
- var x int //@diag("x", "compiler", "x declared but not used", "error")
-}
-
-func hi() {
- runtime.GOROOT()
- fmt.Printf("")
-
- log.Printf("")
-}
-
diff --git a/internal/lsp/testdata/format/bad_format.go.in b/internal/lsp/testdata/format/bad_format.go.in
deleted file mode 100644
index 06187238e..000000000
--- a/internal/lsp/testdata/format/bad_format.go.in
+++ /dev/null
@@ -1,22 +0,0 @@
-package format //@format("package")
-
-import (
- "runtime"
- "fmt"
- "log"
-)
-
-func hello() {
-
-
-
-
- var x int //@diag("x", "compiler", "x declared but not used", "error")
-}
-
-func hi() {
- runtime.GOROOT()
- fmt.Printf("")
-
- log.Printf("")
-}
diff --git a/internal/lsp/testdata/format/good_format.go b/internal/lsp/testdata/format/good_format.go
deleted file mode 100644
index 01cb1610c..000000000
--- a/internal/lsp/testdata/format/good_format.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package format //@format("package")
-
-import (
- "log"
-)
-
-func goodbye() {
- log.Printf("byeeeee")
-}
diff --git a/internal/lsp/testdata/format/good_format.go.golden b/internal/lsp/testdata/format/good_format.go.golden
deleted file mode 100644
index 99f47e2e8..000000000
--- a/internal/lsp/testdata/format/good_format.go.golden
+++ /dev/null
@@ -1,11 +0,0 @@
--- gofmt --
-package format //@format("package")
-
-import (
- "log"
-)
-
-func goodbye() {
- log.Printf("byeeeee")
-}
-
diff --git a/internal/lsp/testdata/format/newline_format.go.golden b/internal/lsp/testdata/format/newline_format.go.golden
deleted file mode 100644
index 7c76afdd5..000000000
--- a/internal/lsp/testdata/format/newline_format.go.golden
+++ /dev/null
@@ -1,4 +0,0 @@
--- gofmt --
-package format //@format("package")
-func _() {}
-
diff --git a/internal/lsp/testdata/format/newline_format.go.in b/internal/lsp/testdata/format/newline_format.go.in
deleted file mode 100644
index fe597b90b..000000000
--- a/internal/lsp/testdata/format/newline_format.go.in
+++ /dev/null
@@ -1,2 +0,0 @@
-package format //@format("package")
-func _() {} \ No newline at end of file
diff --git a/internal/lsp/testdata/format/one_line.go.golden b/internal/lsp/testdata/format/one_line.go.golden
deleted file mode 100644
index 4d11f84cb..000000000
--- a/internal/lsp/testdata/format/one_line.go.golden
+++ /dev/null
@@ -1,3 +0,0 @@
--- gofmt --
-package format //@format("package")
-
diff --git a/internal/lsp/testdata/format/one_line.go.in b/internal/lsp/testdata/format/one_line.go.in
deleted file mode 100644
index 30f413755..000000000
--- a/internal/lsp/testdata/format/one_line.go.in
+++ /dev/null
@@ -1 +0,0 @@
-package format //@format("package") \ No newline at end of file
diff --git a/internal/lsp/testdata/func_rank/func_rank.go.in b/internal/lsp/testdata/func_rank/func_rank.go.in
deleted file mode 100644
index 905010b3d..000000000
--- a/internal/lsp/testdata/func_rank/func_rank.go.in
+++ /dev/null
@@ -1,70 +0,0 @@
-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)
-
- _ = 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/internal/lsp/testdata/funcsig/func_sig.go b/internal/lsp/testdata/funcsig/func_sig.go
deleted file mode 100644
index 00f9b575d..000000000
--- a/internal/lsp/testdata/funcsig/func_sig.go
+++ /dev/null
@@ -1,9 +0,0 @@
-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/internal/lsp/testdata/funcvalue/func_value.go b/internal/lsp/testdata/funcvalue/func_value.go
deleted file mode 100644
index 913fcbcfe..000000000
--- a/internal/lsp/testdata/funcvalue/func_value.go
+++ /dev/null
@@ -1,27 +0,0 @@
-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/internal/lsp/testdata/fuzzymatch/fuzzymatch.go b/internal/lsp/testdata/fuzzymatch/fuzzymatch.go
deleted file mode 100644
index 73268f553..000000000
--- a/internal/lsp/testdata/fuzzymatch/fuzzymatch.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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
-
-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 //@fuzzy(" //", fuzzFabarField, fuzzFooBarField)
- afb //@fuzzy(" //", fuzzFooBarField, fuzzFabarField)
-
- fab //@fuzzy(" //", fuzzFabarField)
-
- var myString string
- myString = af //@fuzzy(" //", 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 //@fuzzy(" //", fuzzABCInt, fuzzABCbool, fuzzABCfloat)
-
- // deep candidate that matches expected type should still ranked first
- var s string
- s = abc //@fuzzy(" //", fuzzABCstring, fuzzABCInt, fuzzABCbool)
-}
diff --git a/internal/lsp/testdata/generate/generate.go b/internal/lsp/testdata/generate/generate.go
deleted file mode 100644
index ae5e90d1a..000000000
--- a/internal/lsp/testdata/generate/generate.go
+++ /dev/null
@@ -1,4 +0,0 @@
-package generate
-
-//go:generate echo Hi //@ codelens("//go:generate", "run go generate", "generate"), codelens("//go:generate", "run go generate ./...", "generate")
-//go:generate echo I shall have no CodeLens
diff --git a/internal/lsp/testdata/generated/generated.go b/internal/lsp/testdata/generated/generated.go
deleted file mode 100644
index c92bd9eb8..000000000
--- a/internal/lsp/testdata/generated/generated.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package generated
-
-// Code generated by generator.go. DO NOT EDIT.
-
-func _() {
- var y int //@diag("y", "compiler", "y declared but not used", "error")
-}
diff --git a/internal/lsp/testdata/generated/generator.go b/internal/lsp/testdata/generated/generator.go
deleted file mode 100644
index f26e33c80..000000000
--- a/internal/lsp/testdata/generated/generator.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package generated
-
-func _() {
- var x int //@diag("x", "compiler", "x declared but not used", "error")
-}
diff --git a/internal/lsp/testdata/godef/a/a.go b/internal/lsp/testdata/godef/a/a.go
deleted file mode 100644
index 5cc85527a..000000000
--- a/internal/lsp/testdata/godef/a/a.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Package a is a package for testing go to definition.
-package a //@mark(aPackage, "a "),hoverdef("a ", aPackage)
-
-import (
- "fmt"
- "go/types"
- "sync"
-)
-
-var (
- // x is a variable.
- x string //@x,hoverdef("x", x)
-)
-
-// 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 //@g,hoverdef("g", g)
-
- h = 2 //@h,hoverdef("h", h)
-)
-
-// z is a variable too.
-var z string //@z,hoverdef("z", z)
-
-type A string //@mark(AString, "A")
-
-func AStuff() { //@AStuff
- x := 5
- Random2(x) //@godef("dom2", Random2)
- Random() //@godef("()", Random)
-
- var err error //@err
- fmt.Printf("%v", err) //@godef("err", err)
-
- var y string //@string,hoverdef("string", string)
- _ = make([]int, 0) //@make,hoverdef("make", make)
-
- var mu sync.Mutex
- mu.Lock() //@Lock,hoverdef("Lock", Lock)
-
- var typ *types.Named //@mark(typesImport, "types"),hoverdef("types", typesImport)
- typ.Obj().Name() //@Name,hoverdef("Name", Name)
-}
-
-type A struct {
-}
-
-func (_ A) Hi() {} //@mark(AHi, "Hi")
-
-type S struct {
- Field int //@mark(AField, "Field")
- R // embed a struct
- H // embed an interface
-}
-
-type R struct {
- Field2 int //@mark(AField2, "Field2")
-}
-
-func (_ R) Hey() {} //@mark(AHey, "Hey")
-
-type H interface {
- Goodbye() //@mark(AGoodbye, "Goodbye")
-}
-
-type I interface {
- B() //@mark(AB, "B")
- J
-}
-
-type J interface {
- Hello() //@mark(AHello, "Hello")
-}
-
-func _() {
- // 1st type declaration block
- type (
- a struct { //@mark(declBlockA, "a"),hoverdef("a", declBlockA)
- x string
- }
- )
-
- // 2nd type declaration block
- type (
- // b has a comment
- b struct{} //@mark(declBlockB, "b"),hoverdef("b", declBlockB)
- )
-
- // 3rd type declaration block
- type (
- // c is a struct
- c struct { //@mark(declBlockC, "c"),hoverdef("c", declBlockC)
- f string
- }
-
- d string //@mark(declBlockD, "d"),hoverdef("d", declBlockD)
- )
-
- type (
- e struct { //@mark(declBlockE, "e"),hoverdef("e", declBlockE)
- f float64
- } // e has a comment
- )
-}
diff --git a/internal/lsp/testdata/godef/a/a.go.golden b/internal/lsp/testdata/godef/a/a.go.golden
deleted file mode 100644
index 9f67a147d..000000000
--- a/internal/lsp/testdata/godef/a/a.go.golden
+++ /dev/null
@@ -1,190 +0,0 @@
--- Lock-hoverdef --
-```go
-func (*sync.Mutex).Lock()
-```
-
-Lock locks m\.
-
-[`(sync.Mutex).Lock` on pkg.go.dev](https://pkg.go.dev/sync?utm_source=gopls#Mutex.Lock)
--- Name-hoverdef --
-```go
-func (*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?utm_source=gopls#TypeName.Name)
--- Random-definition --
-godef/a/random.go:3:6-12: defined here as ```go
-func Random() int
-```
-
-[`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Random)
--- Random-definition-json --
-{
- "span": {
- "uri": "file://godef/a/random.go",
- "start": {
- "line": 3,
- "column": 6,
- "offset": 16
- },
- "end": {
- "line": 3,
- "column": 12,
- "offset": 22
- }
- },
- "description": "```go\nfunc Random() int\n```\n\n[`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Random)"
-}
-
--- Random-hoverdef --
-```go
-func Random() int
-```
-
-[`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Random)
--- Random2-definition --
-godef/a/random.go:8:6-13: defined here as ```go
-func Random2(y int) int
-```
-
-[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Random2)
--- Random2-definition-json --
-{
- "span": {
- "uri": "file://godef/a/random.go",
- "start": {
- "line": 8,
- "column": 6,
- "offset": 71
- },
- "end": {
- "line": 8,
- "column": 13,
- "offset": 78
- }
- },
- "description": "```go\nfunc Random2(y int) int\n```\n\n[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Random2)"
-}
-
--- Random2-hoverdef --
-```go
-func Random2(y int) int
-```
-
-[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Random2)
--- aPackage-hoverdef --
-Package a is a package for testing go to definition\.
--- declBlockA-hoverdef --
-```go
-type a struct {
- x string
-}
-```
-
-1st type declaration block
--- declBlockB-hoverdef --
-```go
-type b struct{}
-```
-
-b has a comment
--- declBlockC-hoverdef --
-```go
-type c struct {
- f string
-}
-```
-
-c is a struct
--- declBlockD-hoverdef --
-```go
-type d string
-```
-
-3rd type declaration block
--- declBlockE-hoverdef --
-```go
-type e struct {
- f float64
-}
-```
-
-e has a comment
--- err-definition --
-godef/a/a.go:33:6-9: defined here as ```go
-var err error
-```
-
-\@err
--- err-definition-json --
-{
- "span": {
- "uri": "file://godef/a/a.go",
- "start": {
- "line": 33,
- "column": 6,
- "offset": 612
- },
- "end": {
- "line": 33,
- "column": 9,
- "offset": 615
- }
- },
- "description": "```go\nvar err error\n```\n\n\\@err"
-}
-
--- err-hoverdef --
-```go
-var err error
-```
-
-\@err
--- g-hoverdef --
-```go
-const g untyped int = 1
-```
-
-When I hover on g, I should see this comment\.
--- h-hoverdef --
-```go
-const h untyped int = 2
-```
-
-Constant block\.
--- make-hoverdef --
-```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?utm_source=gopls#make)
--- string-hoverdef --
-```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?utm_source=gopls#string)
--- typesImport-hoverdef --
-```go
-package types ("go/types")
-```
-
-[`types` on pkg.go.dev](https://pkg.go.dev/go/types?utm_source=gopls)
--- x-hoverdef --
-```go
-var x string
-```
-
-x is a variable\.
--- z-hoverdef --
-```go
-var z string
-```
-
-z is a variable too\.
diff --git a/internal/lsp/testdata/godef/a/a_test.go b/internal/lsp/testdata/godef/a/a_test.go
deleted file mode 100644
index 77bd633b6..000000000
--- a/internal/lsp/testdata/godef/a/a_test.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package a
-
-import (
- "testing"
-)
-
-func TestA(t *testing.T) { //@TestA,godef(TestA, TestA)
-}
diff --git a/internal/lsp/testdata/godef/a/a_test.go.golden b/internal/lsp/testdata/godef/a/a_test.go.golden
deleted file mode 100644
index e5cb3d799..000000000
--- a/internal/lsp/testdata/godef/a/a_test.go.golden
+++ /dev/null
@@ -1,26 +0,0 @@
--- TestA-definition --
-godef/a/a_test.go:7:6-11: defined here as ```go
-func TestA(t *testing.T)
-```
--- TestA-definition-json --
-{
- "span": {
- "uri": "file://godef/a/a_test.go",
- "start": {
- "line": 7,
- "column": 6,
- "offset": 39
- },
- "end": {
- "line": 7,
- "column": 11,
- "offset": 44
- }
- },
- "description": "```go\nfunc TestA(t *testing.T)\n```"
-}
-
--- TestA-hoverdef --
-```go
-func TestA(t *testing.T)
-```
diff --git a/internal/lsp/testdata/godef/a/a_x_test.go b/internal/lsp/testdata/godef/a/a_x_test.go
deleted file mode 100644
index 4631eba2c..000000000
--- a/internal/lsp/testdata/godef/a/a_x_test.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package a_test
-
-import (
- "testing"
-)
-
-func TestA2(t *testing.T) { //@TestA2,godef(TestA2, TestA2)
- Nonexistant() //@diag("Nonexistant", "compiler", "undeclared name: Nonexistant", "error")
-}
diff --git a/internal/lsp/testdata/godef/a/a_x_test.go.golden b/internal/lsp/testdata/godef/a/a_x_test.go.golden
deleted file mode 100644
index 2e3064794..000000000
--- a/internal/lsp/testdata/godef/a/a_x_test.go.golden
+++ /dev/null
@@ -1,26 +0,0 @@
--- TestA2-definition --
-godef/a/a_x_test.go:7:6-12: defined here as ```go
-func TestA2(t *testing.T)
-```
--- TestA2-definition-json --
-{
- "span": {
- "uri": "file://godef/a/a_x_test.go",
- "start": {
- "line": 7,
- "column": 6,
- "offset": 44
- },
- "end": {
- "line": 7,
- "column": 12,
- "offset": 50
- }
- },
- "description": "```go\nfunc TestA2(t *testing.T)\n```"
-}
-
--- TestA2-hoverdef --
-```go
-func TestA2(t *testing.T)
-```
diff --git a/internal/lsp/testdata/godef/a/d.go b/internal/lsp/testdata/godef/a/d.go
deleted file mode 100644
index 2da8d058e..000000000
--- a/internal/lsp/testdata/godef/a/d.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package a //@mark(a, "a "),hoverdef("a ", a)
-
-import "fmt"
-
-type Thing struct { //@Thing
- Member string //@Member
-}
-
-var Other Thing //@Other
-
-func Things(val []string) []Thing { //@Things
- return nil
-}
-
-func (t Thing) Method(i int) string { //@Method
- return t.Member
-}
-
-func useThings() {
- t := Thing{ //@mark(aStructType, "ing")
- Member: "string", //@mark(fMember, "ember")
- }
- fmt.Print(t.Member) //@mark(aMember, "ember")
- fmt.Print(Other) //@mark(aVar, "ther")
- Things() //@mark(aFunc, "ings")
- t.Method() //@mark(aMethod, "eth")
-}
-
-/*@
-godef(aStructType, Thing)
-godef(aMember, Member)
-godef(aVar, Other)
-godef(aFunc, Things)
-godef(aMethod, Method)
-godef(fMember, Member)
-godef(Member, Member)
-
-//param
-//package name
-//const
-//anon field
-
-*/
diff --git a/internal/lsp/testdata/godef/a/d.go.golden b/internal/lsp/testdata/godef/a/d.go.golden
deleted file mode 100644
index 47723b045..000000000
--- a/internal/lsp/testdata/godef/a/d.go.golden
+++ /dev/null
@@ -1,164 +0,0 @@
--- Member-definition --
-godef/a/d.go:6:2-8: defined here as ```go
-field Member string
-```
-
-\@Member
-
-[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing.Member)
--- Member-definition-json --
-{
- "span": {
- "uri": "file://godef/a/d.go",
- "start": {
- "line": 6,
- "column": 2,
- "offset": 90
- },
- "end": {
- "line": 6,
- "column": 8,
- "offset": 96
- }
- },
- "description": "```go\nfield Member string\n```\n\n\\@Member\n\n[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing.Member)"
-}
-
--- Member-hoverdef --
-```go
-field Member string
-```
-
-\@Member
-
-[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing.Member)
--- Method-definition --
-godef/a/d.go:15:16-22: defined here as ```go
-func (Thing).Method(i int) string
-```
-
-[`(a.Thing).Method` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing.Method)
--- Method-definition-json --
-{
- "span": {
- "uri": "file://godef/a/d.go",
- "start": {
- "line": 15,
- "column": 16,
- "offset": 219
- },
- "end": {
- "line": 15,
- "column": 22,
- "offset": 225
- }
- },
- "description": "```go\nfunc (Thing).Method(i int) string\n```\n\n[`(a.Thing).Method` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing.Method)"
-}
-
--- Method-hoverdef --
-```go
-func (Thing).Method(i int) string
-```
-
-[`(a.Thing).Method` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing.Method)
--- Other-definition --
-godef/a/d.go:9:5-10: defined here as ```go
-var Other Thing
-```
-
-\@Other
-
-[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Other)
--- Other-definition-json --
-{
- "span": {
- "uri": "file://godef/a/d.go",
- "start": {
- "line": 9,
- "column": 5,
- "offset": 121
- },
- "end": {
- "line": 9,
- "column": 10,
- "offset": 126
- }
- },
- "description": "```go\nvar Other Thing\n```\n\n\\@Other\n\n[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Other)"
-}
-
--- Other-hoverdef --
-```go
-var Other Thing
-```
-
-\@Other
-
-[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Other)
--- Thing-definition --
-godef/a/d.go:5:6-11: defined here as ```go
-type Thing struct {
- Member string //@Member
-}
-```
-
-[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing)
--- Thing-definition-json --
-{
- "span": {
- "uri": "file://godef/a/d.go",
- "start": {
- "line": 5,
- "column": 6,
- "offset": 65
- },
- "end": {
- "line": 5,
- "column": 11,
- "offset": 70
- }
- },
- "description": "```go\ntype Thing struct {\n\tMember string //@Member\n}\n```\n\n[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing)"
-}
-
--- Thing-hoverdef --
-```go
-type Thing struct {
- Member string //@Member
-}
-```
-
-[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing)
--- Things-definition --
-godef/a/d.go:11:6-12: defined here as ```go
-func Things(val []string) []Thing
-```
-
-[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Things)
--- Things-definition-json --
-{
- "span": {
- "uri": "file://godef/a/d.go",
- "start": {
- "line": 11,
- "column": 6,
- "offset": 148
- },
- "end": {
- "line": 11,
- "column": 12,
- "offset": 154
- }
- },
- "description": "```go\nfunc Things(val []string) []Thing\n```\n\n[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Things)"
-}
-
--- Things-hoverdef --
-```go
-func Things(val []string) []Thing
-```
-
-[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Things)
--- a-hoverdef --
-Package a is a package for testing go to definition\.
diff --git a/internal/lsp/testdata/godef/a/f.go b/internal/lsp/testdata/godef/a/f.go
deleted file mode 100644
index 589c45fc1..000000000
--- a/internal/lsp/testdata/godef/a/f.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package a
-
-import "fmt"
-
-func TypeStuff() { //@Stuff
- var x string
-
- switch y := interface{}(x).(type) { //@mark(switchY, "y"),godef("y", switchY)
- case int: //@mark(intY, "int")
- fmt.Printf("%v", y) //@hoverdef("y", intY)
- case string: //@mark(stringY, "string")
- fmt.Printf("%v", y) //@hoverdef("y", stringY)
- }
-
-}
diff --git a/internal/lsp/testdata/godef/a/f.go.golden b/internal/lsp/testdata/godef/a/f.go.golden
deleted file mode 100644
index a084356c0..000000000
--- a/internal/lsp/testdata/godef/a/f.go.golden
+++ /dev/null
@@ -1,34 +0,0 @@
--- intY-hoverdef --
-```go
-var y int
-```
--- stringY-hoverdef --
-```go
-var y string
-```
--- switchY-definition --
-godef/a/f.go:8:9-10: defined here as ```go
-var y interface{}
-```
--- switchY-definition-json --
-{
- "span": {
- "uri": "file://godef/a/f.go",
- "start": {
- "line": 8,
- "column": 9,
- "offset": 76
- },
- "end": {
- "line": 8,
- "column": 10,
- "offset": 77
- }
- },
- "description": "```go\nvar y interface{}\n```"
-}
-
--- switchY-hoverdef --
-```go
-var y interface{}
-```
diff --git a/internal/lsp/testdata/godef/a/g.go b/internal/lsp/testdata/godef/a/g.go
deleted file mode 100644
index dfef2fb80..000000000
--- a/internal/lsp/testdata/godef/a/g.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package a
-
-import "time"
-
-// dur is a constant of type time.Duration.
-const dur = 15*time.Minute + 10*time.Second + 350*time.Millisecond //@dur,hoverdef("dur", dur)
diff --git a/internal/lsp/testdata/godef/a/g.go.golden b/internal/lsp/testdata/godef/a/g.go.golden
deleted file mode 100644
index b7ed73928..000000000
--- a/internal/lsp/testdata/godef/a/g.go.golden
+++ /dev/null
@@ -1,6 +0,0 @@
--- dur-hoverdef --
-```go
-const dur time.Duration = 910350000000 // 15m10.35s
-```
-
-dur is a constant of type time\.Duration\.
diff --git a/internal/lsp/testdata/godef/a/h.go b/internal/lsp/testdata/godef/a/h.go
deleted file mode 100644
index 5a5dcc678..000000000
--- a/internal/lsp/testdata/godef/a/h.go
+++ /dev/null
@@ -1,147 +0,0 @@
-package a
-
-func _() {
- type s struct {
- nested struct {
- // nested number
- number int64 //@mark(nestedNumber, "number")
- }
- nested2 []struct {
- // nested string
- str string //@mark(nestedString, "str")
- }
- x struct {
- x struct {
- x struct {
- x struct {
- x struct {
- // nested map
- m map[string]float64 //@mark(nestedMap, "m")
- }
- }
- }
- }
- }
- }
-
- var t s
- _ = t.nested.number //@hoverdef("number", nestedNumber)
- _ = t.nested2[0].str //@hoverdef("str", nestedString)
- _ = t.x.x.x.x.x.m //@hoverdef("m", nestedMap)
-}
-
-func _() {
- var s struct {
- // a field
- a int //@mark(structA, "a")
- // b nested struct
- b struct { //@mark(structB, "b")
- // c field of nested struct
- c int //@mark(structC, "c")
- }
- }
- _ = s.a //@hoverdef("a", structA)
- _ = s.b //@hoverdef("b", structB)
- _ = s.b.c //@hoverdef("c", structC)
-
- var arr []struct {
- // d field
- d int //@mark(arrD, "d")
- // e nested struct
- e struct { //@mark(arrE, "e")
- // f field of nested struct
- f int //@mark(arrF, "f")
- }
- }
- _ = arr[0].d //@hoverdef("d", arrD)
- _ = arr[0].e //@hoverdef("e", arrE)
- _ = arr[0].e.f //@hoverdef("f", arrF)
-
- var complex []struct {
- c <-chan map[string][]struct {
- // h field
- h int //@mark(complexH, "h")
- // i nested struct
- i struct { //@mark(complexI, "i")
- // j field of nested struct
- j int //@mark(complexJ, "j")
- }
- }
- }
- _ = (<-complex[0].c)["0"][0].h //@hoverdef("h", complexH)
- _ = (<-complex[0].c)["0"][0].i //@hoverdef("i", complexI)
- _ = (<-complex[0].c)["0"][0].i.j //@hoverdef("j", complexJ)
-
- var mapWithStructKey map[struct {
- // X key field
- x []string //@mark(mapStructKeyX, "x")
- }]int
- for k := range mapWithStructKey {
- _ = k.x //@hoverdef("x", mapStructKeyX)
- }
-
- var mapWithStructKeyAndValue map[struct {
- // Y key field
- y string //@mark(mapStructKeyY, "y")
- }]struct {
- // X value field
- x string //@mark(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 //@hoverdef("y", mapStructKeyY)
- _ = v.x //@hoverdef("x", mapStructValueX)
- }
-
- var i []map[string]interface {
- // open method comment
- open() error //@mark(openMethod, "open")
- }
- i[0]["1"].open() //@hoverdef("open", openMethod)
-}
-
-func _() {
- test := struct {
- // test description
- desc string //@mark(testDescription, "desc")
- }{}
- _ = test.desc //@hoverdef("desc", testDescription)
-
- for _, tt := range []struct {
- // test input
- in map[string][]struct { //@mark(testInput, "in")
- // test key
- key string //@mark(testInputKey, "key")
- // test value
- value interface{} //@mark(testInputValue, "value")
- }
- result struct {
- v <-chan struct {
- // expected test value
- value int //@mark(testResultValue, "value")
- }
- }
- }{} {
- _ = tt.in //@hoverdef("in", testInput)
- _ = tt.in["0"][0].key //@hoverdef("key", testInputKey)
- _ = tt.in["0"][0].value //@hoverdef("value", testInputValue)
-
- _ = (<-tt.result.v).value //@hoverdef("value", testResultValue)
- }
-}
-
-func _() {
- getPoints := func() []struct {
- // X coord
- x int //@mark(returnX, "x")
- // Y coord
- y int //@mark(returnY, "y")
- } {
- return nil
- }
-
- r := getPoints()
- r[0].x //@hoverdef("x", returnX)
- r[0].y //@hoverdef("y", returnY)
-}
diff --git a/internal/lsp/testdata/godef/a/h.go.golden b/internal/lsp/testdata/godef/a/h.go.golden
deleted file mode 100644
index 4b27211e9..000000000
--- a/internal/lsp/testdata/godef/a/h.go.golden
+++ /dev/null
@@ -1,136 +0,0 @@
--- arrD-hoverdef --
-```go
-field d int
-```
-
-d field
--- arrE-hoverdef --
-```go
-field e struct{f int}
-```
-
-e nested struct
--- arrF-hoverdef --
-```go
-field f int
-```
-
-f field of nested struct
--- complexH-hoverdef --
-```go
-field h int
-```
-
-h field
--- complexI-hoverdef --
-```go
-field i struct{j int}
-```
-
-i nested struct
--- complexJ-hoverdef --
-```go
-field j int
-```
-
-j field of nested struct
--- mapStructKeyX-hoverdef --
-```go
-field x []string
-```
-
-X key field
--- mapStructKeyY-hoverdef --
-```go
-field y string
-```
--- mapStructValueX-hoverdef --
-```go
-field x string
-```
-
-X value field
--- nestedMap-hoverdef --
-```go
-field m map[string]float64
-```
-
-nested map
--- nestedNumber-hoverdef --
-```go
-field number int64
-```
-
-nested number
--- nestedString-hoverdef --
-```go
-field str string
-```
-
-nested string
--- openMethod-hoverdef --
-```go
-func (interface).open() error
-```
-
-open method comment
--- returnX-hoverdef --
-```go
-field x int
-```
-
-X coord
--- returnY-hoverdef --
-```go
-field y int
-```
-
-Y coord
--- structA-hoverdef --
-```go
-field a int
-```
-
-a field
--- structB-hoverdef --
-```go
-field b struct{c int}
-```
-
-b nested struct
--- structC-hoverdef --
-```go
-field c int
-```
-
-c field of nested struct
--- testDescription-hoverdef --
-```go
-field desc string
-```
-
-test description
--- testInput-hoverdef --
-```go
-field in map[string][]struct{key string; value interface{}}
-```
-
-test input
--- testInputKey-hoverdef --
-```go
-field key string
-```
-
-test key
--- testInputValue-hoverdef --
-```go
-field value interface{}
-```
-
-test value
--- testResultValue-hoverdef --
-```go
-field value int
-```
-
-expected test value
diff --git a/internal/lsp/testdata/godef/a/random.go b/internal/lsp/testdata/godef/a/random.go
deleted file mode 100644
index 62055c1fc..000000000
--- a/internal/lsp/testdata/godef/a/random.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package a
-
-func Random() int { //@Random
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@Random2,mark(RandomParamY, "y")
- return y //@godef("y", RandomParamY)
-}
-
-type Pos struct {
- x, y int //@mark(PosX, "x"),mark(PosY, "y")
-}
-
-// Typ has a comment. Its fields do not.
-type Typ struct{ field string } //@mark(TypField, "field")
-
-func _() {
- x := &Typ{}
- x.field //@godef("field", TypField)
-}
-
-func (p *Pos) Sum() int { //@mark(PosSum, "Sum")
- return p.x + p.y //@godef("x", PosX)
-}
-
-func _() {
- var p Pos
- _ = p.Sum() //@godef("()", PosSum)
-}
diff --git a/internal/lsp/testdata/godef/a/random.go.golden b/internal/lsp/testdata/godef/a/random.go.golden
deleted file mode 100644
index 381a11ace..000000000
--- a/internal/lsp/testdata/godef/a/random.go.golden
+++ /dev/null
@@ -1,112 +0,0 @@
--- PosSum-definition --
-godef/a/random.go:24:15-18: defined here as ```go
-func (*Pos).Sum() int
-```
-
-[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Pos.Sum)
--- PosSum-definition-json --
-{
- "span": {
- "uri": "file://godef/a/random.go",
- "start": {
- "line": 24,
- "column": 15,
- "offset": 413
- },
- "end": {
- "line": 24,
- "column": 18,
- "offset": 416
- }
- },
- "description": "```go\nfunc (*Pos).Sum() int\n```\n\n[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Pos.Sum)"
-}
-
--- PosSum-hoverdef --
-```go
-func (*Pos).Sum() int
-```
-
-[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Pos.Sum)
--- PosX-definition --
-godef/a/random.go:13:2-3: defined here as ```go
-field x int
-```
-
-\@mark\(PosX, \"x\"\),mark\(PosY, \"y\"\)
--- PosX-definition-json --
-{
- "span": {
- "uri": "file://godef/a/random.go",
- "start": {
- "line": 13,
- "column": 2,
- "offset": 187
- },
- "end": {
- "line": 13,
- "column": 3,
- "offset": 188
- }
- },
- "description": "```go\nfield x int\n```\n\n\\@mark\\(PosX, \\\"x\\\"\\),mark\\(PosY, \\\"y\\\"\\)"
-}
-
--- PosX-hoverdef --
-```go
-field x int
-```
-
-\@mark\(PosX, \"x\"\),mark\(PosY, \"y\"\)
--- RandomParamY-definition --
-godef/a/random.go:8:14-15: defined here as ```go
-var y int
-```
--- RandomParamY-definition-json --
-{
- "span": {
- "uri": "file://godef/a/random.go",
- "start": {
- "line": 8,
- "column": 14,
- "offset": 79
- },
- "end": {
- "line": 8,
- "column": 15,
- "offset": 80
- }
- },
- "description": "```go\nvar y int\n```"
-}
-
--- RandomParamY-hoverdef --
-```go
-var y int
-```
--- TypField-definition --
-godef/a/random.go:17:18-23: defined here as ```go
-field field string
-```
--- TypField-definition-json --
-{
- "span": {
- "uri": "file://godef/a/random.go",
- "start": {
- "line": 17,
- "column": 18,
- "offset": 292
- },
- "end": {
- "line": 17,
- "column": 23,
- "offset": 297
- }
- },
- "description": "```go\nfield field string\n```"
-}
-
--- TypField-hoverdef --
-```go
-field field string
-```
diff --git a/internal/lsp/testdata/godef/b/b.go b/internal/lsp/testdata/godef/b/b.go
deleted file mode 100644
index f9c1d6402..000000000
--- a/internal/lsp/testdata/godef/b/b.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package b
-
-import (
- myFoo "golang.org/x/tools/internal/lsp/foo" //@mark(myFoo, "myFoo"),godef("myFoo", myFoo)
- "golang.org/x/tools/internal/lsp/godef/a" //@mark(AImport, re"\".*\"")
-)
-
-type Embed struct {
- *a.A
- a.I
- a.S
-}
-
-func _() {
- e := Embed{}
- e.Hi() //@hoverdef("Hi", AHi)
- e.B() //@hoverdef("B", AB)
- e.Field //@hoverdef("Field", AField)
- e.Field2 //@hoverdef("Field2", AField2)
- e.Hello() //@hoverdef("Hello", AHello)
- e.Hey() //@hoverdef("Hey", AHey)
- e.Goodbye() //@hoverdef("Goodbye", AGoodbye)
-}
-
-type aAlias = a.A //@mark(aAlias, "aAlias")
-
-type S1 struct { //@S1
- F1 int //@mark(S1F1, "F1")
- S2 //@godef("S2", S2),mark(S1S2, "S2")
- a.A //@godef("A", AString)
- aAlias //@godef("a", aAlias)
-}
-
-type S2 struct { //@S2
- F1 string //@mark(S2F1, "F1")
- F2 int //@mark(S2F2, "F2")
- *a.A //@godef("A", AString),godef("a",AImport)
-}
-
-type S3 struct {
- F1 struct {
- a.A //@godef("A", AString)
- }
-}
-
-func Bar() {
- a.AStuff() //@godef("AStuff", AStuff)
- var x S1 //@godef("S1", S1)
- _ = x.S2 //@godef("S2", S1S2)
- _ = x.F1 //@godef("F1", S1F1)
- _ = x.F2 //@godef("F2", S2F2)
- _ = x.S2.F1 //@godef("F1", S2F1)
-
- var _ *myFoo.StructFoo //@godef("myFoo", myFoo)
-}
-
-const X = 0 //@mark(bX, "X"),godef("X", bX)
diff --git a/internal/lsp/testdata/godef/b/b.go.golden b/internal/lsp/testdata/godef/b/b.go.golden
deleted file mode 100644
index 5f7669b77..000000000
--- a/internal/lsp/testdata/godef/b/b.go.golden
+++ /dev/null
@@ -1,454 +0,0 @@
--- AB-hoverdef --
-```go
-func (a.I).B()
-```
-
-\@mark\(AB, \"B\"\)
-
-[`(a.I).B` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#I.B)
--- AField-hoverdef --
-```go
-field Field int
-```
-
-\@mark\(AField, \"Field\"\)
-
-[`(a.S).Field` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#S.Field)
--- AField2-hoverdef --
-```go
-field Field2 int
-```
-
-\@mark\(AField2, \"Field2\"\)
-
-[`(a.R).Field2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#R.Field2)
--- AGoodbye-hoverdef --
-```go
-func (a.H).Goodbye()
-```
-
-\@mark\(AGoodbye, \"Goodbye\"\)
-
-[`(a.H).Goodbye` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#H.Goodbye)
--- AHello-hoverdef --
-```go
-func (a.J).Hello()
-```
-
-\@mark\(AHello, \"Hello\"\)
-
-[`(a.J).Hello` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#J.Hello)
--- AHey-hoverdef --
-```go
-func (a.R).Hey()
-```
-
-[`(a.R).Hey` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#R.Hey)
--- AHi-hoverdef --
-```go
-func (a.A).Hi()
-```
-
-[`(a.A).Hi` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#A.Hi)
--- AImport-definition --
-godef/b/b.go:5:2-43: defined here as ```go
-package a ("golang.org/x/tools/internal/lsp/godef/a")
-```
-
-[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls)
--- AImport-definition-json --
-{
- "span": {
- "uri": "file://godef/b/b.go",
- "start": {
- "line": 5,
- "column": 2,
- "offset": 112
- },
- "end": {
- "line": 5,
- "column": 43,
- "offset": 153
- }
- },
- "description": "```go\npackage a (\"golang.org/x/tools/internal/lsp/godef/a\")\n```\n\n[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls)"
-}
-
--- AImport-hoverdef --
-```go
-package a ("golang.org/x/tools/internal/lsp/godef/a")
-```
-
-[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls)
--- AString-definition --
-godef/a/a.go:26:6-7: defined here as ```go
-type A string
-```
-
-\@mark\(AString, \"A\"\)
-
-[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#A)
--- AString-definition-json --
-{
- "span": {
- "uri": "file://godef/a/a.go",
- "start": {
- "line": 26,
- "column": 6,
- "offset": 467
- },
- "end": {
- "line": 26,
- "column": 7,
- "offset": 468
- }
- },
- "description": "```go\ntype A string\n```\n\n\\@mark\\(AString, \\\"A\\\"\\)\n\n[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#A)"
-}
-
--- AString-hoverdef --
-```go
-type A string
-```
-
-\@mark\(AString, \"A\"\)
-
-[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#A)
--- AStuff-definition --
-godef/a/a.go:28:6-12: defined here as ```go
-func a.AStuff()
-```
-
-[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#AStuff)
--- AStuff-definition-json --
-{
- "span": {
- "uri": "file://godef/a/a.go",
- "start": {
- "line": 28,
- "column": 6,
- "offset": 504
- },
- "end": {
- "line": 28,
- "column": 12,
- "offset": 510
- }
- },
- "description": "```go\nfunc a.AStuff()\n```\n\n[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#AStuff)"
-}
-
--- AStuff-hoverdef --
-```go
-func a.AStuff()
-```
-
-[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#AStuff)
--- S1-definition --
-godef/b/b.go:27:6-8: defined here as ```go
-type S1 struct {
- F1 int //@mark(S1F1, "F1")
- S2 //@godef("S2", S2),mark(S1S2, "S2")
- a.A //@godef("A", AString)
- aAlias //@godef("a", aAlias)
-}
-```
-
-[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1)
--- S1-definition-json --
-{
- "span": {
- "uri": "file://godef/b/b.go",
- "start": {
- "line": 27,
- "column": 6,
- "offset": 587
- },
- "end": {
- "line": 27,
- "column": 8,
- "offset": 589
- }
- },
- "description": "```go\ntype S1 struct {\n\tF1 int //@mark(S1F1, \"F1\")\n\tS2 //@godef(\"S2\", S2),mark(S1S2, \"S2\")\n\ta.A //@godef(\"A\", AString)\n\taAlias //@godef(\"a\", aAlias)\n}\n```\n\n[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1)"
-}
-
--- S1-hoverdef --
-```go
-type S1 struct {
- F1 int //@mark(S1F1, "F1")
- S2 //@godef("S2", S2),mark(S1S2, "S2")
- a.A //@godef("A", AString)
- aAlias //@godef("a", aAlias)
-}
-```
-
-[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1)
--- S1F1-definition --
-godef/b/b.go:28:2-4: defined here as ```go
-field F1 int
-```
-
-\@mark\(S1F1, \"F1\"\)
-
-[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1.F1)
--- S1F1-definition-json --
-{
- "span": {
- "uri": "file://godef/b/b.go",
- "start": {
- "line": 28,
- "column": 2,
- "offset": 606
- },
- "end": {
- "line": 28,
- "column": 4,
- "offset": 608
- }
- },
- "description": "```go\nfield F1 int\n```\n\n\\@mark\\(S1F1, \\\"F1\\\"\\)\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1.F1)"
-}
-
--- S1F1-hoverdef --
-```go
-field F1 int
-```
-
-\@mark\(S1F1, \"F1\"\)
-
-[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1.F1)
--- S1S2-definition --
-godef/b/b.go:29:2-4: defined here as ```go
-field S2 S2
-```
-
-\@godef\(\"S2\", S2\),mark\(S1S2, \"S2\"\)
-
-[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1.S2)
--- S1S2-definition-json --
-{
- "span": {
- "uri": "file://godef/b/b.go",
- "start": {
- "line": 29,
- "column": 2,
- "offset": 638
- },
- "end": {
- "line": 29,
- "column": 4,
- "offset": 640
- }
- },
- "description": "```go\nfield S2 S2\n```\n\n\\@godef\\(\\\"S2\\\", S2\\),mark\\(S1S2, \\\"S2\\\"\\)\n\n[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1.S2)"
-}
-
--- S1S2-hoverdef --
-```go
-field S2 S2
-```
-
-\@godef\(\"S2\", S2\),mark\(S1S2, \"S2\"\)
-
-[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1.S2)
--- S2-definition --
-godef/b/b.go:34:6-8: defined here as ```go
-type S2 struct {
- F1 string //@mark(S2F1, "F1")
- F2 int //@mark(S2F2, "F2")
- *a.A //@godef("A", AString),godef("a",AImport)
-}
-```
-
-[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S2)
--- S2-definition-json --
-{
- "span": {
- "uri": "file://godef/b/b.go",
- "start": {
- "line": 34,
- "column": 6,
- "offset": 762
- },
- "end": {
- "line": 34,
- "column": 8,
- "offset": 764
- }
- },
- "description": "```go\ntype S2 struct {\n\tF1 string //@mark(S2F1, \"F1\")\n\tF2 int //@mark(S2F2, \"F2\")\n\t*a.A //@godef(\"A\", AString),godef(\"a\",AImport)\n}\n```\n\n[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S2)"
-}
-
--- S2-hoverdef --
-```go
-type S2 struct {
- F1 string //@mark(S2F1, "F1")
- F2 int //@mark(S2F2, "F2")
- *a.A //@godef("A", AString),godef("a",AImport)
-}
-```
-
-[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S2)
--- S2F1-definition --
-godef/b/b.go:35:2-4: defined here as ```go
-field F1 string
-```
-
-\@mark\(S2F1, \"F1\"\)
-
-[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S2.F1)
--- S2F1-definition-json --
-{
- "span": {
- "uri": "file://godef/b/b.go",
- "start": {
- "line": 35,
- "column": 2,
- "offset": 781
- },
- "end": {
- "line": 35,
- "column": 4,
- "offset": 783
- }
- },
- "description": "```go\nfield F1 string\n```\n\n\\@mark\\(S2F1, \\\"F1\\\"\\)\n\n[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S2.F1)"
-}
-
--- S2F1-hoverdef --
-```go
-field F1 string
-```
-
-\@mark\(S2F1, \"F1\"\)
-
-[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S2.F1)
--- S2F2-definition --
-godef/b/b.go:36:2-4: defined here as ```go
-field F2 int
-```
-
-\@mark\(S2F2, \"F2\"\)
-
-[`(b.S2).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S2.F2)
--- S2F2-definition-json --
-{
- "span": {
- "uri": "file://godef/b/b.go",
- "start": {
- "line": 36,
- "column": 2,
- "offset": 814
- },
- "end": {
- "line": 36,
- "column": 4,
- "offset": 816
- }
- },
- "description": "```go\nfield F2 int\n```\n\n\\@mark\\(S2F2, \\\"F2\\\"\\)\n\n[`(b.S2).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S2.F2)"
-}
-
--- S2F2-hoverdef --
-```go
-field F2 int
-```
-
-\@mark\(S2F2, \"F2\"\)
-
-[`(b.S2).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S2.F2)
--- aAlias-definition --
-godef/b/b.go:25:6-12: defined here as ```go
-type aAlias = a.A
-```
-
-\@mark\(aAlias, \"aAlias\"\)
--- aAlias-definition-json --
-{
- "span": {
- "uri": "file://godef/b/b.go",
- "start": {
- "line": 25,
- "column": 6,
- "offset": 542
- },
- "end": {
- "line": 25,
- "column": 12,
- "offset": 548
- }
- },
- "description": "```go\ntype aAlias = a.A\n```\n\n\\@mark\\(aAlias, \\\"aAlias\\\"\\)"
-}
-
--- aAlias-hoverdef --
-```go
-type aAlias = a.A
-```
-
-\@mark\(aAlias, \"aAlias\"\)
--- bX-definition --
-godef/b/b.go:57:7-8: defined here as ```go
-const X untyped int = 0
-```
-
-\@mark\(bX, \"X\"\),godef\(\"X\", bX\)
-
-[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#X)
--- bX-definition-json --
-{
- "span": {
- "uri": "file://godef/b/b.go",
- "start": {
- "line": 57,
- "column": 7,
- "offset": 1249
- },
- "end": {
- "line": 57,
- "column": 8,
- "offset": 1250
- }
- },
- "description": "```go\nconst X untyped int = 0\n```\n\n\\@mark\\(bX, \\\"X\\\"\\),godef\\(\\\"X\\\", bX\\)\n\n[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#X)"
-}
-
--- bX-hoverdef --
-```go
-const X untyped int = 0
-```
-
-\@mark\(bX, \"X\"\),godef\(\"X\", bX\)
-
-[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#X)
--- myFoo-definition --
-godef/b/b.go:4:2-7: defined here as ```go
-package myFoo ("golang.org/x/tools/internal/lsp/foo")
-```
-
-[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo?utm_source=gopls)
--- myFoo-definition-json --
-{
- "span": {
- "uri": "file://godef/b/b.go",
- "start": {
- "line": 4,
- "column": 2,
- "offset": 21
- },
- "end": {
- "line": 4,
- "column": 7,
- "offset": 26
- }
- },
- "description": "```go\npackage myFoo (\"golang.org/x/tools/internal/lsp/foo\")\n```\n\n[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo?utm_source=gopls)"
-}
-
--- myFoo-hoverdef --
-```go
-package myFoo ("golang.org/x/tools/internal/lsp/foo")
-```
-
-[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo?utm_source=gopls)
diff --git a/internal/lsp/testdata/godef/b/c.go b/internal/lsp/testdata/godef/b/c.go
deleted file mode 100644
index c8daf6242..000000000
--- a/internal/lsp/testdata/godef/b/c.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package b
-
-// This is the in-editor version of the file.
-// The on-disk version is in c.go.saved.
-
-var _ = S1{ //@godef("S1", S1)
- F1: 99, //@godef("F1", S1F1)
-}
diff --git a/internal/lsp/testdata/godef/b/c.go.golden b/internal/lsp/testdata/godef/b/c.go.golden
deleted file mode 100644
index e6205b726..000000000
--- a/internal/lsp/testdata/godef/b/c.go.golden
+++ /dev/null
@@ -1,74 +0,0 @@
--- S1-definition --
-godef/b/b.go:27:6-8: defined here as ```go
-type S1 struct {
- F1 int //@mark(S1F1, "F1")
- S2 //@godef("S2", S2),mark(S1S2, "S2")
- a.A //@godef("A", AString)
- aAlias //@godef("a", aAlias)
-}
-```
-
-[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1)
--- S1-definition-json --
-{
- "span": {
- "uri": "file://godef/b/b.go",
- "start": {
- "line": 27,
- "column": 6,
- "offset": 587
- },
- "end": {
- "line": 27,
- "column": 8,
- "offset": 589
- }
- },
- "description": "```go\ntype S1 struct {\n\tF1 int //@mark(S1F1, \"F1\")\n\tS2 //@godef(\"S2\", S2),mark(S1S2, \"S2\")\n\ta.A //@godef(\"A\", AString)\n\taAlias //@godef(\"a\", aAlias)\n}\n```\n\n[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1)"
-}
-
--- S1-hoverdef --
-```go
-type S1 struct {
- F1 int //@mark(S1F1, "F1")
- S2 //@godef("S2", S2),mark(S1S2, "S2")
- a.A //@godef("A", AString)
- aAlias //@godef("a", aAlias)
-}
-```
-
-[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1)
--- S1F1-definition --
-godef/b/b.go:28:2-4: defined here as ```go
-field F1 int
-```
-
-\@mark\(S1F1, \"F1\"\)
-
-[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1.F1)
--- S1F1-definition-json --
-{
- "span": {
- "uri": "file://godef/b/b.go",
- "start": {
- "line": 28,
- "column": 2,
- "offset": 606
- },
- "end": {
- "line": 28,
- "column": 4,
- "offset": 608
- }
- },
- "description": "```go\nfield F1 int\n```\n\n\\@mark\\(S1F1, \\\"F1\\\"\\)\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1.F1)"
-}
-
--- S1F1-hoverdef --
-```go
-field F1 int
-```
-
-\@mark\(S1F1, \"F1\"\)
-
-[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1.F1)
diff --git a/internal/lsp/testdata/godef/b/c.go.saved b/internal/lsp/testdata/godef/b/c.go.saved
deleted file mode 100644
index ff1a8794b..000000000
--- a/internal/lsp/testdata/godef/b/c.go.saved
+++ /dev/null
@@ -1,7 +0,0 @@
-package b
-
-// This is the on-disk version of c.go, which represents
-// the in-editor version of the file.
-
-}
-
diff --git a/internal/lsp/testdata/godef/b/e.go b/internal/lsp/testdata/godef/b/e.go
deleted file mode 100644
index 7b96cd7e8..000000000
--- a/internal/lsp/testdata/godef/b/e.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package b
-
-import (
- "fmt"
-
- "golang.org/x/tools/internal/lsp/godef/a"
-)
-
-func useThings() {
- t := a.Thing{} //@mark(bStructType, "ing")
- fmt.Print(t.Member) //@mark(bMember, "ember")
- fmt.Print(a.Other) //@mark(bVar, "ther")
- a.Things() //@mark(bFunc, "ings")
-}
-
-/*@
-godef(bStructType, Thing)
-godef(bMember, Member)
-godef(bVar, Other)
-godef(bFunc, Things)
-*/
-
-func _() {
- var x interface{} //@mark(eInterface, "interface{}")
- switch x := x.(type) { //@hoverdef("x", eInterface)
- case string: //@mark(eString, "string")
- fmt.Println(x) //@hoverdef("x", eString)
- case int: //@mark(eInt, "int")
- fmt.Println(x) //@hoverdef("x", eInt)
- }
-}
diff --git a/internal/lsp/testdata/godef/b/e.go.golden b/internal/lsp/testdata/godef/b/e.go.golden
deleted file mode 100644
index f9af7b743..000000000
--- a/internal/lsp/testdata/godef/b/e.go.golden
+++ /dev/null
@@ -1,144 +0,0 @@
--- Member-definition --
-godef/a/d.go:6:2-8: defined here as ```go
-field Member string
-```
-
-\@Member
-
-[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing.Member)
--- Member-definition-json --
-{
- "span": {
- "uri": "file://godef/a/d.go",
- "start": {
- "line": 6,
- "column": 2,
- "offset": 90
- },
- "end": {
- "line": 6,
- "column": 8,
- "offset": 96
- }
- },
- "description": "```go\nfield Member string\n```\n\n\\@Member\n\n[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing.Member)"
-}
-
--- Member-hoverdef --
-```go
-field Member string
-```
-
-\@Member
-
-[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing.Member)
--- Other-definition --
-godef/a/d.go:9:5-10: defined here as ```go
-var a.Other a.Thing
-```
-
-\@Other
-
-[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Other)
--- Other-definition-json --
-{
- "span": {
- "uri": "file://godef/a/d.go",
- "start": {
- "line": 9,
- "column": 5,
- "offset": 121
- },
- "end": {
- "line": 9,
- "column": 10,
- "offset": 126
- }
- },
- "description": "```go\nvar a.Other a.Thing\n```\n\n\\@Other\n\n[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Other)"
-}
-
--- Other-hoverdef --
-```go
-var a.Other a.Thing
-```
-
-\@Other
-
-[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Other)
--- Thing-definition --
-godef/a/d.go:5:6-11: defined here as ```go
-type Thing struct {
- Member string //@Member
-}
-```
-
-[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing)
--- Thing-definition-json --
-{
- "span": {
- "uri": "file://godef/a/d.go",
- "start": {
- "line": 5,
- "column": 6,
- "offset": 65
- },
- "end": {
- "line": 5,
- "column": 11,
- "offset": 70
- }
- },
- "description": "```go\ntype Thing struct {\n\tMember string //@Member\n}\n```\n\n[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing)"
-}
-
--- Thing-hoverdef --
-```go
-type Thing struct {
- Member string //@Member
-}
-```
-
-[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing)
--- Things-definition --
-godef/a/d.go:11:6-12: defined here as ```go
-func a.Things(val []string) []a.Thing
-```
-
-[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Things)
--- Things-definition-json --
-{
- "span": {
- "uri": "file://godef/a/d.go",
- "start": {
- "line": 11,
- "column": 6,
- "offset": 148
- },
- "end": {
- "line": 11,
- "column": 12,
- "offset": 154
- }
- },
- "description": "```go\nfunc a.Things(val []string) []a.Thing\n```\n\n[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Things)"
-}
-
--- Things-hoverdef --
-```go
-func a.Things(val []string) []a.Thing
-```
-
-[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Things)
--- eInt-hoverdef --
-```go
-var x int
-```
--- eInterface-hoverdef --
-```go
-var x interface{}
-```
--- eString-hoverdef --
-```go
-var x string
-```
diff --git a/internal/lsp/testdata/godef/b/h.go b/internal/lsp/testdata/godef/b/h.go
deleted file mode 100644
index c8cbe850f..000000000
--- a/internal/lsp/testdata/godef/b/h.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package b
-
-import . "golang.org/x/tools/internal/lsp/godef/a"
-
-func _() {
- // variable of type a.A
- var _ A //@mark(AVariable, "_"),hoverdef("_", AVariable)
-
- AStuff() //@hoverdef("AStuff", AStuff)
-}
diff --git a/internal/lsp/testdata/godef/b/h.go.golden b/internal/lsp/testdata/godef/b/h.go.golden
deleted file mode 100644
index f32f0264f..000000000
--- a/internal/lsp/testdata/godef/b/h.go.golden
+++ /dev/null
@@ -1,12 +0,0 @@
--- AStuff-hoverdef --
-```go
-func AStuff()
-```
-
-[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#AStuff)
--- AVariable-hoverdef --
-```go
-var _ A
-```
-
-variable of type a\.A
diff --git a/internal/lsp/testdata/godef/broken/unclosedIf.go.golden b/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
deleted file mode 100644
index 5c3329d8b..000000000
--- a/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
+++ /dev/null
@@ -1,30 +0,0 @@
--- myUnclosedIf-definition --
-godef/broken/unclosedIf.go:7:7-19: defined here as ```go
-var myUnclosedIf string
-```
-
-\@myUnclosedIf
--- myUnclosedIf-definition-json --
-{
- "span": {
- "uri": "file://godef/broken/unclosedIf.go",
- "start": {
- "line": 7,
- "column": 7,
- "offset": 68
- },
- "end": {
- "line": 7,
- "column": 19,
- "offset": 80
- }
- },
- "description": "```go\nvar myUnclosedIf string\n```\n\n\\@myUnclosedIf"
-}
-
--- myUnclosedIf-hoverdef --
-```go
-var myUnclosedIf string
-```
-
-\@myUnclosedIf
diff --git a/internal/lsp/testdata/godef/broken/unclosedIf.go.in b/internal/lsp/testdata/godef/broken/unclosedIf.go.in
deleted file mode 100644
index 0f2cf1b1e..000000000
--- a/internal/lsp/testdata/godef/broken/unclosedIf.go.in
+++ /dev/null
@@ -1,9 +0,0 @@
-package broken
-
-import "fmt"
-
-func unclosedIf() {
- if false {
- var myUnclosedIf string //@myUnclosedIf
- fmt.Printf("s = %v\n", myUnclosedIf) //@godef("my", myUnclosedIf)
-}
diff --git a/internal/lsp/testdata/godef/hover_generics/hover.go b/internal/lsp/testdata/godef/hover_generics/hover.go
deleted file mode 100644
index 7400e1acd..000000000
--- a/internal/lsp/testdata/godef/hover_generics/hover.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package hover
-
-type value[T any] struct { //@mark(value, "value"),hoverdef("value", value),mark(valueTdecl, "T"),hoverdef("T",valueTdecl)
- val T //@mark(valueTparam, "T"),hoverdef("T", valueTparam)
- Q int //@mark(valueQfield, "Q"),hoverdef("Q", valueQfield)
-}
-
-type Value[T any] struct { //@mark(ValueTdecl, "T"),hoverdef("T",ValueTdecl)
- val T //@mark(ValueTparam, "T"),hoverdef("T", ValueTparam)
- Q int //@mark(ValueQfield, "Q"),hoverdef("Q", ValueQfield)
-}
-
-func F[P interface{ ~int | string }]() { //@mark(Pparam, "P"),hoverdef("P",Pparam)
- var _ P //@mark(Pvar, "P"),hoverdef("P",Pvar)
-}
diff --git a/internal/lsp/testdata/godef/hover_generics/hover.go.golden b/internal/lsp/testdata/godef/hover_generics/hover.go.golden
deleted file mode 100644
index cfebcc472..000000000
--- a/internal/lsp/testdata/godef/hover_generics/hover.go.golden
+++ /dev/null
@@ -1,45 +0,0 @@
--- Pparam-hoverdef --
-```go
-type parameter P interface{~int|string}
-```
--- Pvar-hoverdef --
-```go
-type parameter P interface{~int|string}
-```
--- ValueQfield-hoverdef --
-```go
-field Q int
-```
-
-\@mark\(ValueQfield, \"Q\"\),hoverdef\(\"Q\", ValueQfield\)
-
-[`(hover.Value).Q` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/hover_generics?utm_source=gopls#Value.Q)
--- ValueTdecl-hoverdef --
-```go
-type parameter T any
-```
--- ValueTparam-hoverdef --
-```go
-type parameter T any
-```
--- value-hoverdef --
-```go
-type value[T any] struct {
- val T //@mark(valueTparam, "T"),hoverdef("T", valueTparam)
- Q int //@mark(valueQfield, "Q"),hoverdef("Q", valueQfield)
-}
-```
--- valueQfield-hoverdef --
-```go
-field Q int
-```
-
-\@mark\(valueQfield, \"Q\"\),hoverdef\(\"Q\", valueQfield\)
--- valueTdecl-hoverdef --
-```go
-type parameter T any
-```
--- valueTparam-hoverdef --
-```go
-type parameter T any
-```
diff --git a/internal/lsp/testdata/godef/infer_generics/inferred.go b/internal/lsp/testdata/godef/infer_generics/inferred.go
deleted file mode 100644
index 2d92a9590..000000000
--- a/internal/lsp/testdata/godef/infer_generics/inferred.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package inferred
-
-func app[S interface{ ~[]E }, E interface{}](s S, e E) S {
- return append(s, e)
-}
-
-func _() {
- _ = app[[]int] //@mark(constrInfer, "app"),hoverdef("app", constrInfer)
- _ = app[[]int, int] //@mark(instance, "app"),hoverdef("app", instance)
- _ = app[[]int]([]int{}, 0) //@mark(partialInfer, "app"),hoverdef("app", partialInfer)
- _ = app([]int{}, 0) //@mark(argInfer, "app"),hoverdef("app", argInfer)
-}
diff --git a/internal/lsp/testdata/godef/infer_generics/inferred.go.golden b/internal/lsp/testdata/godef/infer_generics/inferred.go.golden
deleted file mode 100644
index 4a36ff460..000000000
--- a/internal/lsp/testdata/godef/infer_generics/inferred.go.golden
+++ /dev/null
@@ -1,20 +0,0 @@
--- argInfer-hoverdef --
-```go
-func app(s []int, e int) []int // func[S interface{~[]E}, E interface{}](s S, e E) S
-```
--- constrInf-hoverdef --
-```go
-func app(s []int, e int) []int // func[S₁ interface{~[]E₂}, E₂ interface{}](s S₁, e E₂) S₁
-```
--- constrInfer-hoverdef --
-```go
-func app(s []int, e int) []int // func[S interface{~[]E}, E interface{}](s S, e E) S
-```
--- instance-hoverdef --
-```go
-func app(s []int, e int) []int // func[S interface{~[]E}, E interface{}](s S, e E) S
-```
--- partialInfer-hoverdef --
-```go
-func app(s []int, e int) []int // func[S interface{~[]E}, E interface{}](s S, e E) S
-```
diff --git a/internal/lsp/testdata/good/good0.go b/internal/lsp/testdata/good/good0.go
deleted file mode 100644
index 89450a845..000000000
--- a/internal/lsp/testdata/good/good0.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package good //@diag("package", "no_diagnostics", "", "error")
-
-func stuff() { //@item(good_stuff, "stuff", "func()", "func"),prepare("stu", "stuff", "stuff")
- x := 5
- random2(x) //@prepare("dom", "random2", "random2")
-}
diff --git a/internal/lsp/testdata/good/good1.go b/internal/lsp/testdata/good/good1.go
deleted file mode 100644
index c4664a7e5..000000000
--- a/internal/lsp/testdata/good/good1.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package good //@diag("package", "no_diagnostics", "", "error")
-
-import (
- "golang.org/x/tools/internal/lsp/types" //@item(types_import, "types", "\"golang.org/x/tools/internal/lsp/types\"", "package")
-)
-
-func random() int { //@item(good_random, "random", "func() int", "func")
- _ = "random() int" //@prepare("random", "", "")
- y := 6 + 7 //@prepare("7", "", "")
- return y //@prepare("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{} //@prepare("ypes","types", "types")
- if _, ok := b.(*types.X); ok { //@complete("X", X_struct, Y_struct, Bob_interface, CoolAlias)
- }
-
- return y
-}
diff --git a/internal/lsp/testdata/highlights/highlights.go b/internal/lsp/testdata/highlights/highlights.go
deleted file mode 100644
index 55ae68aa1..000000000
--- a/internal/lsp/testdata/highlights/highlights.go
+++ /dev/null
@@ -1,151 +0,0 @@
-package highlights
-
-import (
- "fmt" //@mark(fmtImp, "\"fmt\""),highlight(fmtImp, fmtImp, fmt1, fmt2, fmt3, fmt4)
- h2 "net/http" //@mark(hImp, "h2"),highlight(hImp, hImp, hUse)
- "sort"
-)
-
-type F struct{ bar int } //@mark(barDeclaration, "bar"),highlight(barDeclaration, barDeclaration, bar1, bar2, bar3)
-
-func _() F {
- return F{
- bar: 123, //@mark(bar1, "bar"),highlight(bar1, barDeclaration, bar1, bar2, bar3)
- }
-}
-
-var foo = F{bar: 52} //@mark(fooDeclaration, "foo"),mark(bar2, "bar"),highlight(fooDeclaration, fooDeclaration, fooUse),highlight(bar2, barDeclaration, bar1, bar2, bar3)
-
-func Print() { //@mark(printFunc, "Print"),highlight(printFunc, printFunc, printTest)
- _ = h2.Client{} //@mark(hUse, "h2"),highlight(hUse, hImp, hUse)
-
- fmt.Println(foo) //@mark(fooUse, "foo"),highlight(fooUse, fooDeclaration, fooUse),mark(fmt1, "fmt"),highlight(fmt1, fmtImp, fmt1, fmt2, fmt3, fmt4)
- fmt.Print("yo") //@mark(printSep, "Print"),highlight(printSep, printSep, print1, print2),mark(fmt2, "fmt"),highlight(fmt2, fmtImp, fmt1, fmt2, fmt3, fmt4)
-}
-
-func (x *F) Inc() { //@mark(xRightDecl, "x"),mark(xLeftDecl, " *"),highlight(xRightDecl, xRightDecl, xUse),highlight(xLeftDecl, xRightDecl, xUse)
- x.bar++ //@mark(xUse, "x"),mark(bar3, "bar"),highlight(xUse, xRightDecl, xUse),highlight(bar3, barDeclaration, bar1, bar2, bar3)
-}
-
-func testFunctions() {
- fmt.Print("main start") //@mark(print1, "Print"),highlight(print1, printSep, print1, print2),mark(fmt3, "fmt"),highlight(fmt3, fmtImp, fmt1, fmt2, fmt3, fmt4)
- fmt.Print("ok") //@mark(print2, "Print"),highlight(print2, printSep, print1, print2),mark(fmt4, "fmt"),highlight(fmt4, fmtImp, fmt1, fmt2, fmt3, fmt4)
- Print() //@mark(printTest, "Print"),highlight(printTest, printFunc, printTest)
-}
-
-func toProtocolHighlight(rngs []int) []DocumentHighlight { //@mark(doc1, "DocumentHighlight"),mark(docRet1, "[]DocumentHighlight"),highlight(doc1, docRet1, doc1, doc2, doc3, result)
- result := make([]DocumentHighlight, 0, len(rngs)) //@mark(doc2, "DocumentHighlight"),highlight(doc2, doc1, doc2, doc3)
- for _, rng := range rngs {
- result = append(result, DocumentHighlight{ //@mark(doc3, "DocumentHighlight"),highlight(doc3, doc1, doc2, doc3)
- Range: rng,
- })
- }
- return result //@mark(result, "result")
-}
-
-func testForLoops() {
- for i := 0; i < 10; i++ { //@mark(forDecl1, "for"),highlight(forDecl1, forDecl1, brk1, cont1)
- if i > 8 {
- break //@mark(brk1, "break"),highlight(brk1, forDecl1, brk1, cont1)
- }
- if i < 2 {
- for j := 1; j < 10; j++ { //@mark(forDecl2, "for"),highlight(forDecl2, forDecl2, cont2)
- if j < 3 {
- for k := 1; k < 10; k++ { //@mark(forDecl3, "for"),highlight(forDecl3, forDecl3, cont3)
- if k < 3 {
- continue //@mark(cont3, "continue"),highlight(cont3, forDecl3, cont3)
- }
- }
- continue //@mark(cont2, "continue"),highlight(cont2, forDecl2, cont2)
- }
- }
- continue //@mark(cont1, "continue"),highlight(cont1, forDecl1, brk1, cont1)
- }
- }
-
- arr := []int{}
- for i := range arr { //@mark(forDecl4, "for"),highlight(forDecl4, forDecl4, brk4, cont4)
- if i > 8 {
- break //@mark(brk4, "break"),highlight(brk4, forDecl4, brk4, cont4)
- }
- if i < 4 {
- continue //@mark(cont4, "continue"),highlight(cont4, forDecl4, brk4, cont4)
- }
- }
-
-Outer:
- for i := 0; i < 10; i++ { //@mark(forDecl5, "for"),highlight(forDecl5, forDecl5, brk5, brk6, brk8)
- break //@mark(brk5, "break"),highlight(brk5, forDecl5, brk5, brk6, brk8)
- for { //@mark(forDecl6, "for"),highlight(forDecl6, forDecl6, cont5)
- if i == 1 {
- break Outer //@mark(brk6, "break Outer"),highlight(brk6, forDecl5, brk5, brk6, brk8)
- }
- switch i { //@mark(switch1, "switch"),highlight(switch1, switch1, brk7)
- case 5:
- break //@mark(brk7, "break"),highlight(brk7, switch1, brk7)
- case 6:
- continue //@mark(cont5, "continue"),highlight(cont5, forDecl6, cont5)
- case 7:
- break Outer //@mark(brk8, "break Outer"),highlight(brk8, forDecl5, brk5, brk6, brk8)
- }
- }
- }
-}
-
-func testSwitch() {
- var i, j int
-
-L1:
- for { //@mark(forDecl7, "for"),highlight(forDecl7, forDecl7, brk10, cont6)
- L2:
- switch i { //@mark(switch2, "switch"),highlight(switch2, switch2, brk11, brk12, brk13)
- case 1:
- switch j { //@mark(switch3, "switch"),highlight(switch3, switch3, brk9)
- case 1:
- break //@mark(brk9, "break"),highlight(brk9, switch3, brk9)
- case 2:
- break L1 //@mark(brk10, "break L1"),highlight(brk10, forDecl7, brk10, cont6)
- case 3:
- break L2 //@mark(brk11, "break L2"),highlight(brk11, switch2, brk11, brk12, brk13)
- default:
- continue //@mark(cont6, "continue"),highlight(cont6, forDecl7, brk10, cont6)
- }
- case 2:
- break //@mark(brk12, "break"),highlight(brk12, switch2, brk11, brk12, brk13)
- default:
- break L2 //@mark(brk13, "break L2"),highlight(brk13, switch2, brk11, brk12, brk13)
- }
- }
-}
-
-func testReturn() bool { //@mark(func1, "func"),mark(bool1, "bool"),highlight(func1, func1, fullRet11, fullRet12),highlight(bool1, bool1, false1, bool2, true1)
- if 1 < 2 {
- return false //@mark(ret11, "return"),mark(fullRet11, "return false"),mark(false1, "false"),highlight(ret11, func1, fullRet11, fullRet12)
- }
- candidates := []int{}
- sort.SliceStable(candidates, func(i, j int) bool { //@mark(func2, "func"),mark(bool2, "bool"),highlight(func2, func2, fullRet2)
- return candidates[i] > candidates[j] //@mark(ret2, "return"),mark(fullRet2, "return candidates[i] > candidates[j]"),highlight(ret2, func2, fullRet2)
- })
- return true //@mark(ret12, "return"),mark(fullRet12, "return true"),mark(true1, "true"),highlight(ret12, func1, fullRet11, fullRet12)
-}
-
-func testReturnFields() float64 { //@mark(retVal1, "float64"),highlight(retVal1, retVal1, retVal11, retVal21)
- if 1 < 2 {
- return 20.1 //@mark(retVal11, "20.1"),highlight(retVal11, retVal1, retVal11, retVal21)
- }
- z := 4.3 //@mark(zDecl, "z")
- return z //@mark(retVal21, "z"),highlight(retVal21, retVal1, retVal11, zDecl, retVal21)
-}
-
-func testReturnMultipleFields() (float32, string) { //@mark(retVal31, "float32"),mark(retVal32, "string"),highlight(retVal31, retVal31, retVal41, retVal51),highlight(retVal32, retVal32, retVal42, retVal52)
- y := "im a var" //@mark(yDecl, "y"),
- if 1 < 2 {
- return 20.1, y //@mark(retVal41, "20.1"),mark(retVal42, "y"),highlight(retVal41, retVal31, retVal41, retVal51),highlight(retVal42, retVal32, yDecl, retVal42, retVal52)
- }
- return 4.9, "test" //@mark(retVal51, "4.9"),mark(retVal52, "\"test\""),highlight(retVal51, retVal31, retVal41, retVal51),highlight(retVal52, retVal32, retVal42, retVal52)
-}
-
-func testReturnFunc() int32 { //@mark(retCall, "int32")
- mulch := 1 //@mark(mulchDec, "mulch"),highlight(mulchDec, mulchDec, mulchRet)
- return int32(mulch) //@mark(mulchRet, "mulch"),mark(retFunc, "int32"),mark(retTotal, "int32(mulch)"),highlight(mulchRet, mulchDec, mulchRet),highlight(retFunc, retCall, retFunc, retTotal)
-}
diff --git a/internal/lsp/testdata/implementation/implementation.go b/internal/lsp/testdata/implementation/implementation.go
deleted file mode 100644
index c3229121a..000000000
--- a/internal/lsp/testdata/implementation/implementation.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package implementation
-
-import "golang.org/x/tools/internal/lsp/implementation/other"
-
-type ImpP struct{} //@ImpP,implementations("ImpP", Laugher, OtherLaugher)
-
-func (*ImpP) Laugh() { //@mark(LaughP, "Laugh"),implementations("Laugh", Laugh, OtherLaugh)
-}
-
-type ImpS struct{} //@ImpS,implementations("ImpS", Laugher, OtherLaugher)
-
-func (ImpS) Laugh() { //@mark(LaughS, "Laugh"),implementations("Laugh", Laugh, OtherLaugh)
-}
-
-type Laugher interface { //@Laugher,implementations("Laugher", ImpP, OtherImpP, ImpS, OtherImpS)
- Laugh() //@Laugh,implementations("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS)
-}
-
-type Foo struct { //@implementations("Foo", Joker)
- other.Foo
-}
-
-type Joker interface { //@Joker
- Joke() //@Joke,implementations("Joke", ImpJoker)
-}
-
-type cryer int //@implementations("cryer", Cryer)
-
-func (cryer) Cry(other.CryType) {} //@mark(CryImpl, "Cry"),implementations("Cry", Cry)
-
-type Empty interface{} //@implementations("Empty")
diff --git a/internal/lsp/testdata/implementation/other/other.go b/internal/lsp/testdata/implementation/other/other.go
deleted file mode 100644
index aff825e91..000000000
--- a/internal/lsp/testdata/implementation/other/other.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package other
-
-type ImpP struct{} //@mark(OtherImpP, "ImpP")
-
-func (*ImpP) Laugh() { //@mark(OtherLaughP, "Laugh")
-}
-
-type ImpS struct{} //@mark(OtherImpS, "ImpS")
-
-func (ImpS) Laugh() { //@mark(OtherLaughS, "Laugh")
-}
-
-type ImpI interface { //@mark(OtherLaugher, "ImpI")
- Laugh() //@mark(OtherLaugh, "Laugh")
-}
-
-type Foo struct { //@implementations("Foo", Joker)
-}
-
-func (Foo) Joke() { //@mark(ImpJoker, "Joke"),implementations("Joke", Joke)
-}
-
-type CryType int
-
-type Cryer interface { //@Cryer
- Cry(CryType) //@Cry,implementations("Cry", CryImpl)
-}
diff --git a/internal/lsp/testdata/implementation/other/other_test.go b/internal/lsp/testdata/implementation/other/other_test.go
deleted file mode 100644
index 846e0d591..000000000
--- a/internal/lsp/testdata/implementation/other/other_test.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package other
-
-import (
- "testing"
-)
-
-// This exists so the other.test package comes into existence.
-
-func TestOther(t *testing.T) {
-}
diff --git a/internal/lsp/testdata/importedcomplit/imported_complit.go.in b/internal/lsp/testdata/importedcomplit/imported_complit.go.in
deleted file mode 100644
index 80d85245c..000000000
--- a/internal/lsp/testdata/importedcomplit/imported_complit.go.in
+++ /dev/null
@@ -1,42 +0,0 @@
-package importedcomplit
-
-import (
- "golang.org/x/tools/internal/lsp/foo"
-
- // import completions
- "fm" //@complete("\" //", fmtImport)
- "go/pars" //@complete("\" //", parserImport)
- "golang.org/x/tools/internal/lsp/signa" //@complete("na\" //", signatureImport)
- "golang.org/x/too" //@complete("\" //", toolsImport)
- "crypto/elli" //@complete("\" //", cryptoImport)
- "golang.org/x/tools/internal/lsp/sign" //@complete("\" //", signatureImport)
- "golang.org/x/tools/internal/lsp/sign" //@complete("ols", toolsImport)
- namedParser "go/pars" //@complete("\" //", parserImport)
-)
-
-func _() {
- var V int //@item(icVVar, "V", "int", "var")
- _ = foo.StructFoo{V} //@complete("}", Value, icVVar)
-}
-
-func _() {
- var (
- aa string //@item(icAAVar, "aa", "string", "var")
- ab int //@item(icABVar, "ab", "int", "var")
- )
-
- _ = foo.StructFoo{a} //@complete("}", abVar, aaVar)
-
- var s struct {
- AA string //@item(icFieldAA, "AA", "string", "field")
- AB int //@item(icFieldAB, "AB", "int", "field")
- }
-
- _ = foo.StructFoo{s.} //@complete("}", icFieldAB, icFieldAA)
-}
-
-/* "fmt" */ //@item(fmtImport, "fmt", "\"fmt\"", "package")
-/* "go/parser" */ //@item(parserImport, "parser", "\"go/parser\"", "package")
-/* "golang.org/x/tools/internal/lsp/signature" */ //@item(signatureImport, "signature", "\"golang.org/x/tools/internal/lsp/signature\"", "package")
-/* "golang.org/x/tools/" */ //@item(toolsImport, "tools/", "\"golang.org/x/tools/\"", "package")
-/* "crypto/elliptic" */ //@item(cryptoImport, "elliptic", "\"crypto/elliptic\"", "package")
diff --git a/internal/lsp/testdata/imports/add_import.go.golden b/internal/lsp/testdata/imports/add_import.go.golden
deleted file mode 100644
index 16af110a0..000000000
--- a/internal/lsp/testdata/imports/add_import.go.golden
+++ /dev/null
@@ -1,13 +0,0 @@
--- goimports --
-package imports //@import("package")
-
-import (
- "bytes"
- "fmt"
-)
-
-func _() {
- fmt.Println("")
- bytes.NewBuffer(nil)
-}
-
diff --git a/internal/lsp/testdata/imports/add_import.go.in b/internal/lsp/testdata/imports/add_import.go.in
deleted file mode 100644
index 7928e6f71..000000000
--- a/internal/lsp/testdata/imports/add_import.go.in
+++ /dev/null
@@ -1,10 +0,0 @@
-package imports //@import("package")
-
-import (
- "fmt"
-)
-
-func _() {
- fmt.Println("")
- bytes.NewBuffer(nil)
-}
diff --git a/internal/lsp/testdata/imports/good_imports.go.golden b/internal/lsp/testdata/imports/good_imports.go.golden
deleted file mode 100644
index 2abdae4d7..000000000
--- a/internal/lsp/testdata/imports/good_imports.go.golden
+++ /dev/null
@@ -1,9 +0,0 @@
--- goimports --
-package imports //@import("package")
-
-import "fmt"
-
-func _() {
-fmt.Println("")
-}
-
diff --git a/internal/lsp/testdata/imports/good_imports.go.in b/internal/lsp/testdata/imports/good_imports.go.in
deleted file mode 100644
index a03c06c6d..000000000
--- a/internal/lsp/testdata/imports/good_imports.go.in
+++ /dev/null
@@ -1,7 +0,0 @@
-package imports //@import("package")
-
-import "fmt"
-
-func _() {
-fmt.Println("")
-}
diff --git a/internal/lsp/testdata/imports/issue35458.go.golden b/internal/lsp/testdata/imports/issue35458.go.golden
deleted file mode 100644
index f0772606b..000000000
--- a/internal/lsp/testdata/imports/issue35458.go.golden
+++ /dev/null
@@ -1,20 +0,0 @@
--- goimports --
-// package doc
-package imports //@import("package")
-
-
-
-
-
-
-func _() {
- println("Hello, world!")
-}
-
-
-
-
-
-
-
-
diff --git a/internal/lsp/testdata/imports/issue35458.go.in b/internal/lsp/testdata/imports/issue35458.go.in
deleted file mode 100644
index 7420c212c..000000000
--- a/internal/lsp/testdata/imports/issue35458.go.in
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-// package doc
-package imports //@import("package")
-
-
-
-
-
-
-func _() {
- println("Hello, world!")
-}
-
-
-
-
-
-
-
diff --git a/internal/lsp/testdata/imports/multiple_blocks.go.golden b/internal/lsp/testdata/imports/multiple_blocks.go.golden
deleted file mode 100644
index d37a6c751..000000000
--- a/internal/lsp/testdata/imports/multiple_blocks.go.golden
+++ /dev/null
@@ -1,9 +0,0 @@
--- goimports --
-package imports //@import("package")
-
-import "fmt"
-
-func _() {
- fmt.Println("")
-}
-
diff --git a/internal/lsp/testdata/imports/multiple_blocks.go.in b/internal/lsp/testdata/imports/multiple_blocks.go.in
deleted file mode 100644
index 3f2fb99ea..000000000
--- a/internal/lsp/testdata/imports/multiple_blocks.go.in
+++ /dev/null
@@ -1,9 +0,0 @@
-package imports //@import("package")
-
-import "fmt"
-
-import "bytes"
-
-func _() {
- fmt.Println("")
-}
diff --git a/internal/lsp/testdata/imports/needs_imports.go.golden b/internal/lsp/testdata/imports/needs_imports.go.golden
deleted file mode 100644
index fd6032874..000000000
--- a/internal/lsp/testdata/imports/needs_imports.go.golden
+++ /dev/null
@@ -1,13 +0,0 @@
--- goimports --
-package imports //@import("package")
-
-import (
- "fmt"
- "log"
-)
-
-func goodbye() {
- fmt.Printf("HI")
- log.Printf("byeeeee")
-}
-
diff --git a/internal/lsp/testdata/imports/needs_imports.go.in b/internal/lsp/testdata/imports/needs_imports.go.in
deleted file mode 100644
index 949d56a64..000000000
--- a/internal/lsp/testdata/imports/needs_imports.go.in
+++ /dev/null
@@ -1,6 +0,0 @@
-package imports //@import("package")
-
-func goodbye() {
- fmt.Printf("HI")
- log.Printf("byeeeee")
-}
diff --git a/internal/lsp/testdata/imports/remove_import.go.golden b/internal/lsp/testdata/imports/remove_import.go.golden
deleted file mode 100644
index 3df80882c..000000000
--- a/internal/lsp/testdata/imports/remove_import.go.golden
+++ /dev/null
@@ -1,11 +0,0 @@
--- goimports --
-package imports //@import("package")
-
-import (
- "fmt"
-)
-
-func _() {
- fmt.Println("")
-}
-
diff --git a/internal/lsp/testdata/imports/remove_import.go.in b/internal/lsp/testdata/imports/remove_import.go.in
deleted file mode 100644
index 09060bada..000000000
--- a/internal/lsp/testdata/imports/remove_import.go.in
+++ /dev/null
@@ -1,10 +0,0 @@
-package imports //@import("package")
-
-import (
- "bytes"
- "fmt"
-)
-
-func _() {
- fmt.Println("")
-}
diff --git a/internal/lsp/testdata/imports/remove_imports.go.golden b/internal/lsp/testdata/imports/remove_imports.go.golden
deleted file mode 100644
index 530c8c09f..000000000
--- a/internal/lsp/testdata/imports/remove_imports.go.golden
+++ /dev/null
@@ -1,6 +0,0 @@
--- goimports --
-package imports //@import("package")
-
-func _() {
-}
-
diff --git a/internal/lsp/testdata/imports/remove_imports.go.in b/internal/lsp/testdata/imports/remove_imports.go.in
deleted file mode 100644
index 44d065f25..000000000
--- a/internal/lsp/testdata/imports/remove_imports.go.in
+++ /dev/null
@@ -1,9 +0,0 @@
-package imports //@import("package")
-
-import (
- "bytes"
- "fmt"
-)
-
-func _() {
-}
diff --git a/internal/lsp/testdata/imports/two_lines.go.golden b/internal/lsp/testdata/imports/two_lines.go.golden
deleted file mode 100644
index ec118a4dd..000000000
--- a/internal/lsp/testdata/imports/two_lines.go.golden
+++ /dev/null
@@ -1,4 +0,0 @@
--- goimports --
-package main
-func main() {} //@import("main")
-
diff --git a/internal/lsp/testdata/imports/two_lines.go.in b/internal/lsp/testdata/imports/two_lines.go.in
deleted file mode 100644
index eee534569..000000000
--- a/internal/lsp/testdata/imports/two_lines.go.in
+++ /dev/null
@@ -1,2 +0,0 @@
-package main
-func main() {} //@import("main")
diff --git a/internal/lsp/testdata/index/index.go b/internal/lsp/testdata/index/index.go
deleted file mode 100644
index a2656893c..000000000
--- a/internal/lsp/testdata/index/index.go
+++ /dev/null
@@ -1,25 +0,0 @@
-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", "mi")
-}
diff --git a/internal/lsp/testdata/interfacerank/interface_rank.go b/internal/lsp/testdata/interfacerank/interface_rank.go
deleted file mode 100644
index acb5a42e0..000000000
--- a/internal/lsp/testdata/interfacerank/interface_rank.go
+++ /dev/null
@@ -1,23 +0,0 @@
-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/internal/lsp/testdata/keywords/accidental_keywords.go.in b/internal/lsp/testdata/keywords/accidental_keywords.go.in
deleted file mode 100644
index 3833081c4..000000000
--- a/internal/lsp/testdata/keywords/accidental_keywords.go.in
+++ /dev/null
@@ -1,31 +0,0 @@
-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)
-}
diff --git a/internal/lsp/testdata/keywords/empty_select.go b/internal/lsp/testdata/keywords/empty_select.go
deleted file mode 100644
index 17ca3ec9d..000000000
--- a/internal/lsp/testdata/keywords/empty_select.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package keywords
-
-func _() {
- select {
- c //@complete(" //", case)
- }
-}
diff --git a/internal/lsp/testdata/keywords/empty_switch.go b/internal/lsp/testdata/keywords/empty_switch.go
deleted file mode 100644
index 2004d5541..000000000
--- a/internal/lsp/testdata/keywords/empty_switch.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package keywords
-
-func _() {
- switch {
- //@complete("", case, default)
- }
-
- switch test.(type) {
- d //@complete(" //", default)
- }
-}
diff --git a/internal/lsp/testdata/keywords/keywords.go b/internal/lsp/testdata/keywords/keywords.go
deleted file mode 100644
index 1fa2c12ba..000000000
--- a/internal/lsp/testdata/keywords/keywords.go
+++ /dev/null
@@ -1,100 +0,0 @@
-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 wont 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")
-/* var */ //@item(var, "var", "", "keyword")
-/* const */ //@item(const, "const", "", "keyword")
-/* goto */ //@item(goto, "goto", "", "keyword")
-/* struct */ //@item(struct, "struct", "", "keyword")
-/* interface */ //@item(interface, "interface", "", "keyword")
-/* map */ //@item(map, "map", "", "keyword")
-/* func */ //@item(func, "func", "", "keyword")
-/* chan */ //@item(chan, "chan", "", "keyword")
-/* range */ //@item(range, "range", "", "keyword")
diff --git a/internal/lsp/testdata/labels/labels.go b/internal/lsp/testdata/labels/labels.go
deleted file mode 100644
index b9effb6d0..000000000
--- a/internal/lsp/testdata/labels/labels.go
+++ /dev/null
@@ -1,49 +0,0 @@
-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/internal/lsp/testdata/links/links.go b/internal/lsp/testdata/links/links.go
deleted file mode 100644
index 89492bafe..000000000
--- a/internal/lsp/testdata/links/links.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package links
-
-import (
- "fmt" //@link(`fmt`,"https://pkg.go.dev/fmt?utm_source=gopls")
-
- "golang.org/x/tools/internal/lsp/foo" //@link(`golang.org/x/tools/internal/lsp/foo`,`https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo?utm_source=gopls`)
-
- _ "database/sql" //@link(`database/sql`, `https://pkg.go.dev/database/sql?utm_source=gopls`)
-)
-
-var (
- _ fmt.Formatter
- _ foo.StructFoo
- _ errors.Formatter
-)
-
-// Foo function
-func Foo() string {
- /*https://example.com/comment */ //@link("https://example.com/comment","https://example.com/comment")
-
- url := "https://example.com/string_literal" //@link("https://example.com/string_literal","https://example.com/string_literal")
- return url
-
- // TODO(golang/go#1234): Link the relevant issue. //@link("golang/go#1234", "https://github.com/golang/go/issues/1234")
- // TODO(microsoft/vscode-go#12): Another issue. //@link("microsoft/vscode-go#12", "https://github.com/microsoft/vscode-go/issues/12")
-}
diff --git a/internal/lsp/testdata/maps/maps.go.in b/internal/lsp/testdata/maps/maps.go.in
deleted file mode 100644
index eeb5576b0..000000000
--- a/internal/lsp/testdata/maps/maps.go.in
+++ /dev/null
@@ -1,18 +0,0 @@
-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", "*aSlice")
-
- map[a]a{} //@complete("]", mapSliceType, mapStructType)
- map[a]a{} //@complete("{", mapSliceType, mapStructType)
-}
diff --git a/internal/lsp/testdata/missingfunction/channels.go b/internal/lsp/testdata/missingfunction/channels.go
deleted file mode 100644
index 436491c19..000000000
--- a/internal/lsp/testdata/missingfunction/channels.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package missingfunction
-
-func channels(s string) {
- undefinedChannels(c()) //@suggestedfix("undefinedChannels", "quickfix")
-}
-
-func c() (<-chan string, chan string) {
- return make(<-chan string), make(chan string)
-}
diff --git a/internal/lsp/testdata/missingfunction/channels.go.golden b/internal/lsp/testdata/missingfunction/channels.go.golden
deleted file mode 100644
index f5078fed1..000000000
--- a/internal/lsp/testdata/missingfunction/channels.go.golden
+++ /dev/null
@@ -1,15 +0,0 @@
--- suggestedfix_channels_4_2 --
-package missingfunction
-
-func channels(s string) {
- undefinedChannels(c()) //@suggestedfix("undefinedChannels", "quickfix")
-}
-
-func undefinedChannels(ch1 <-chan string, ch2 chan string) {
- panic("unimplemented")
-}
-
-func c() (<-chan string, chan string) {
- return make(<-chan string), make(chan string)
-}
-
diff --git a/internal/lsp/testdata/missingfunction/consecutive_params.go b/internal/lsp/testdata/missingfunction/consecutive_params.go
deleted file mode 100644
index d2ec3be32..000000000
--- a/internal/lsp/testdata/missingfunction/consecutive_params.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package missingfunction
-
-func consecutiveParams() {
- var s string
- undefinedConsecutiveParams(s, s) //@suggestedfix("undefinedConsecutiveParams", "quickfix")
-}
diff --git a/internal/lsp/testdata/missingfunction/consecutive_params.go.golden b/internal/lsp/testdata/missingfunction/consecutive_params.go.golden
deleted file mode 100644
index 14a766496..000000000
--- a/internal/lsp/testdata/missingfunction/consecutive_params.go.golden
+++ /dev/null
@@ -1,12 +0,0 @@
--- suggestedfix_consecutive_params_5_2 --
-package missingfunction
-
-func consecutiveParams() {
- var s string
- undefinedConsecutiveParams(s, s) //@suggestedfix("undefinedConsecutiveParams", "quickfix")
-}
-
-func undefinedConsecutiveParams(s1, s2 string) {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/missingfunction/error_param.go b/internal/lsp/testdata/missingfunction/error_param.go
deleted file mode 100644
index 9fd943ffb..000000000
--- a/internal/lsp/testdata/missingfunction/error_param.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package missingfunction
-
-func errorParam() {
- var err error
- undefinedErrorParam(err) //@suggestedfix("undefinedErrorParam", "quickfix")
-}
diff --git a/internal/lsp/testdata/missingfunction/error_param.go.golden b/internal/lsp/testdata/missingfunction/error_param.go.golden
deleted file mode 100644
index 2e1271181..000000000
--- a/internal/lsp/testdata/missingfunction/error_param.go.golden
+++ /dev/null
@@ -1,12 +0,0 @@
--- suggestedfix_error_param_5_2 --
-package missingfunction
-
-func errorParam() {
- var err error
- undefinedErrorParam(err) //@suggestedfix("undefinedErrorParam", "quickfix")
-}
-
-func undefinedErrorParam(err error) {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/missingfunction/literals.go b/internal/lsp/testdata/missingfunction/literals.go
deleted file mode 100644
index e276eae79..000000000
--- a/internal/lsp/testdata/missingfunction/literals.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package missingfunction
-
-type T struct{}
-
-func literals() {
- undefinedLiterals("hey compiler", T{}, &T{}) //@suggestedfix("undefinedLiterals", "quickfix")
-}
diff --git a/internal/lsp/testdata/missingfunction/literals.go.golden b/internal/lsp/testdata/missingfunction/literals.go.golden
deleted file mode 100644
index 04782b9bf..000000000
--- a/internal/lsp/testdata/missingfunction/literals.go.golden
+++ /dev/null
@@ -1,29 +0,0 @@
--- suggestedfix_literals_10_2 --
-// 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 missingfunction
-
-type T struct{}
-
-func literals() {
- undefinedLiterals("hey compiler", T{}, &T{}) //@suggestedfix("undefinedLiterals", "quickfix")
-}
-
-func undefinedLiterals(s string, t1 T, t2 *T) {
- panic("implement me!")
-}
--- suggestedfix_literals_6_2 --
-package missingfunction
-
-type T struct{}
-
-func literals() {
- undefinedLiterals("hey compiler", T{}, &T{}) //@suggestedfix("undefinedLiterals", "quickfix")
-}
-
-func undefinedLiterals(s string, t1 T, t2 *T) {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/missingfunction/operation.go b/internal/lsp/testdata/missingfunction/operation.go
deleted file mode 100644
index 0408219fe..000000000
--- a/internal/lsp/testdata/missingfunction/operation.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package missingfunction
-
-import "time"
-
-func operation() {
- undefinedOperation(10 * time.Second) //@suggestedfix("undefinedOperation", "quickfix")
-}
diff --git a/internal/lsp/testdata/missingfunction/operation.go.golden b/internal/lsp/testdata/missingfunction/operation.go.golden
deleted file mode 100644
index 5e35f3005..000000000
--- a/internal/lsp/testdata/missingfunction/operation.go.golden
+++ /dev/null
@@ -1,29 +0,0 @@
--- suggestedfix_operation_10_2 --
-// 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 missingfunction
-
-import "time"
-
-func operation() {
- undefinedOperation(10 * time.Second) //@suggestedfix("undefinedOperation", "quickfix")
-}
-
-func undefinedOperation(duration time.Duration) {
- panic("implement me!")
-}
--- suggestedfix_operation_6_2 --
-package missingfunction
-
-import "time"
-
-func operation() {
- undefinedOperation(10 * time.Second) //@suggestedfix("undefinedOperation", "quickfix")
-}
-
-func undefinedOperation(duration time.Duration) {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/missingfunction/selector.go b/internal/lsp/testdata/missingfunction/selector.go
deleted file mode 100644
index afd1ab61f..000000000
--- a/internal/lsp/testdata/missingfunction/selector.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package missingfunction
-
-func selector() {
- m := map[int]bool{}
- undefinedSelector(m[1]) //@suggestedfix("undefinedSelector", "quickfix")
-}
diff --git a/internal/lsp/testdata/missingfunction/selector.go.golden b/internal/lsp/testdata/missingfunction/selector.go.golden
deleted file mode 100644
index c48691c4e..000000000
--- a/internal/lsp/testdata/missingfunction/selector.go.golden
+++ /dev/null
@@ -1,12 +0,0 @@
--- suggestedfix_selector_5_2 --
-package missingfunction
-
-func selector() {
- m := map[int]bool{}
- undefinedSelector(m[1]) //@suggestedfix("undefinedSelector", "quickfix")
-}
-
-func undefinedSelector(b bool) {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/missingfunction/slice.go b/internal/lsp/testdata/missingfunction/slice.go
deleted file mode 100644
index 4a562a2e7..000000000
--- a/internal/lsp/testdata/missingfunction/slice.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package missingfunction
-
-func slice() {
- undefinedSlice([]int{1, 2}) //@suggestedfix("undefinedSlice", "quickfix")
-}
diff --git a/internal/lsp/testdata/missingfunction/slice.go.golden b/internal/lsp/testdata/missingfunction/slice.go.golden
deleted file mode 100644
index 0ccb8611b..000000000
--- a/internal/lsp/testdata/missingfunction/slice.go.golden
+++ /dev/null
@@ -1,11 +0,0 @@
--- suggestedfix_slice_4_2 --
-package missingfunction
-
-func slice() {
- undefinedSlice([]int{1, 2}) //@suggestedfix("undefinedSlice", "quickfix")
-}
-
-func undefinedSlice(i []int) {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/missingfunction/tuple.go b/internal/lsp/testdata/missingfunction/tuple.go
deleted file mode 100644
index 1c4782c15..000000000
--- a/internal/lsp/testdata/missingfunction/tuple.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package missingfunction
-
-func tuple() {
- undefinedTuple(b()) //@suggestedfix("undefinedTuple", "quickfix")
-}
-
-func b() (string, error) {
- return "", nil
-}
diff --git a/internal/lsp/testdata/missingfunction/tuple.go.golden b/internal/lsp/testdata/missingfunction/tuple.go.golden
deleted file mode 100644
index 1e12bb708..000000000
--- a/internal/lsp/testdata/missingfunction/tuple.go.golden
+++ /dev/null
@@ -1,15 +0,0 @@
--- suggestedfix_tuple_4_2 --
-package missingfunction
-
-func tuple() {
- undefinedTuple(b()) //@suggestedfix("undefinedTuple", "quickfix")
-}
-
-func undefinedTuple(s string, err error) {
- panic("unimplemented")
-}
-
-func b() (string, error) {
- return "", nil
-}
-
diff --git a/internal/lsp/testdata/missingfunction/unique_params.go b/internal/lsp/testdata/missingfunction/unique_params.go
deleted file mode 100644
index ffaba3f9c..000000000
--- a/internal/lsp/testdata/missingfunction/unique_params.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package missingfunction
-
-func uniqueArguments() {
- var s string
- var i int
- undefinedUniqueArguments(s, i, s) //@suggestedfix("undefinedUniqueArguments", "quickfix")
-}
diff --git a/internal/lsp/testdata/missingfunction/unique_params.go.golden b/internal/lsp/testdata/missingfunction/unique_params.go.golden
deleted file mode 100644
index 74fb91a8e..000000000
--- a/internal/lsp/testdata/missingfunction/unique_params.go.golden
+++ /dev/null
@@ -1,30 +0,0 @@
--- suggestedfix_unique_params_10_2 --
-// 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 missingfunction
-
-func uniqueArguments() {
- var s string
- var i int
- undefinedUniqueArguments(s, i, s) //@suggestedfix("undefinedUniqueArguments", "quickfix")
-}
-
-func undefinedUniqueArguments(s1 string, i int, s2 string) {
- panic("implement me!")
-}
-
--- suggestedfix_unique_params_6_2 --
-package missingfunction
-
-func uniqueArguments() {
- var s string
- var i int
- undefinedUniqueArguments(s, i, s) //@suggestedfix("undefinedUniqueArguments", "quickfix")
-}
-
-func undefinedUniqueArguments(s1 string, i int, s2 string) {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/multireturn/multi_return.go.in b/internal/lsp/testdata/multireturn/multi_return.go.in
deleted file mode 100644
index c302f3815..000000000
--- a/internal/lsp/testdata/multireturn/multi_return.go.in
+++ /dev/null
@@ -1,48 +0,0 @@
-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/internal/lsp/testdata/nested_complit/nested_complit.go.in b/internal/lsp/testdata/nested_complit/nested_complit.go.in
deleted file mode 100644
index 1dddd5b1b..000000000
--- a/internal/lsp/testdata/nested_complit/nested_complit.go.in
+++ /dev/null
@@ -1,14 +0,0 @@
-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")
- _ := ncBar{
- baz: [] //@complete(" //", structNCFoo, structNCBar)
- }
-}
diff --git a/internal/lsp/testdata/nodisk/empty b/internal/lsp/testdata/nodisk/empty
deleted file mode 100644
index 0c10a42f9..000000000
--- a/internal/lsp/testdata/nodisk/empty
+++ /dev/null
@@ -1 +0,0 @@
-an empty file so that this directory exists \ No newline at end of file
diff --git a/internal/lsp/testdata/nodisk/nodisk.overlay.go b/internal/lsp/testdata/nodisk/nodisk.overlay.go
deleted file mode 100644
index f9194be56..000000000
--- a/internal/lsp/testdata/nodisk/nodisk.overlay.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package nodisk
-
-import (
- "golang.org/x/tools/internal/lsp/foo"
-)
-
-func _() {
- foo.Foo() //@complete("F", Foo, IntFoo, StructFoo)
-}
diff --git a/internal/lsp/testdata/noparse/noparse.go.in b/internal/lsp/testdata/noparse/noparse.go.in
deleted file mode 100644
index 7dc23e025..000000000
--- a/internal/lsp/testdata/noparse/noparse.go.in
+++ /dev/null
@@ -1,11 +0,0 @@
-package noparse
-
-func bye(x int) {
- hi()
-}
-
-func stuff() {
- x := 5
-}
-
-func .() {} //@diag(".", "syntax", "expected 'IDENT', found '.'", "error")
diff --git a/internal/lsp/testdata/noparse_format/noparse_format.go.golden b/internal/lsp/testdata/noparse_format/noparse_format.go.golden
deleted file mode 100644
index 0060c5c92..000000000
--- a/internal/lsp/testdata/noparse_format/noparse_format.go.golden
+++ /dev/null
@@ -1,2 +0,0 @@
--- gofmt --
-
diff --git a/internal/lsp/testdata/noparse_format/noparse_format.go.in b/internal/lsp/testdata/noparse_format/noparse_format.go.in
deleted file mode 100644
index 4fc3824d9..000000000
--- a/internal/lsp/testdata/noparse_format/noparse_format.go.in
+++ /dev/null
@@ -1,9 +0,0 @@
-// +build go1.11
-
-package noparse_format //@format("package")
-
-func what() {
- var b int
- if { hi() //@diag("{", "syntax", "missing condition in if statement", "error")
- }
-} \ No newline at end of file
diff --git a/internal/lsp/testdata/noparse_format/parse_format.go.golden b/internal/lsp/testdata/noparse_format/parse_format.go.golden
deleted file mode 100644
index 667c90b22..000000000
--- a/internal/lsp/testdata/noparse_format/parse_format.go.golden
+++ /dev/null
@@ -1,7 +0,0 @@
--- gofmt --
-package noparse_format //@format("package")
-
-func _() {
- f()
-}
-
diff --git a/internal/lsp/testdata/noparse_format/parse_format.go.in b/internal/lsp/testdata/noparse_format/parse_format.go.in
deleted file mode 100644
index 4b98cf8d0..000000000
--- a/internal/lsp/testdata/noparse_format/parse_format.go.in
+++ /dev/null
@@ -1,5 +0,0 @@
-package noparse_format //@format("package")
-
-func _() {
-f()
-} \ No newline at end of file
diff --git a/internal/lsp/testdata/printf/printf.go b/internal/lsp/testdata/printf/printf.go
deleted file mode 100644
index 6e56549c1..000000000
--- a/internal/lsp/testdata/printf/printf.go
+++ /dev/null
@@ -1,33 +0,0 @@
-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)
- )
-}
diff --git a/internal/lsp/testdata/rank/assign_rank.go.in b/internal/lsp/testdata/rank/assign_rank.go.in
deleted file mode 100644
index 5c51910d4..000000000
--- a/internal/lsp/testdata/rank/assign_rank.go.in
+++ /dev/null
@@ -1,19 +0,0 @@
-package rank
-
-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, orange, apple)
-}
-
-func _() {
- var pineapple int //@item(pineapple, "pineapple", "int", "var")
- pineapple = 1 //@complete(" 1", pineapple, apple, pear)
-
- y := //@complete(" /", pineapple, apple, pear)
-}
diff --git a/internal/lsp/testdata/rank/binexpr_rank.go.in b/internal/lsp/testdata/rank/binexpr_rank.go.in
deleted file mode 100644
index 60b2cc1bc..000000000
--- a/internal/lsp/testdata/rank/binexpr_rank.go.in
+++ /dev/null
@@ -1,8 +0,0 @@
-package rank
-
-func _() {
- _ = 5 + ; //@complete(" ;", apple, pear)
- y := + 5; //@complete(" +", apple, pear)
-
- if 6 == {} //@complete(" {", apple, pear)
-}
diff --git a/internal/lsp/testdata/rank/boolexpr_rank.go b/internal/lsp/testdata/rank/boolexpr_rank.go
deleted file mode 100644
index fe512eee1..000000000
--- a/internal/lsp/testdata/rank/boolexpr_rank.go
+++ /dev/null
@@ -1,11 +0,0 @@
-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)
- }
-}
diff --git a/internal/lsp/testdata/rank/convert_rank.go.in b/internal/lsp/testdata/rank/convert_rank.go.in
deleted file mode 100644
index c43004833..000000000
--- a/internal/lsp/testdata/rank/convert_rank.go.in
+++ /dev/null
@@ -1,54 +0,0 @@
-package rank
-
-import "time"
-
-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)", "time.Duration(convE)")
-
- var convP myInt //@item(convertP, "convP", "myInt", "var")
- var _ *int = conv //@snippet(" //", convertP, "(*int)(&convP)", "(*int)(&convP)")
-
- var ff float64 //@item(convertFloat, "ff", "float64", "var")
- f == convD //@snippet(" =", convertFloat, "ff", "ff")
-}
diff --git a/internal/lsp/testdata/rank/struct/struct_rank.go b/internal/lsp/testdata/rank/struct/struct_rank.go
deleted file mode 100644
index e0bdd38a8..000000000
--- a/internal/lsp/testdata/rank/struct/struct_rank.go
+++ /dev/null
@@ -1,11 +0,0 @@
-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)
-}
diff --git a/internal/lsp/testdata/rank/switch_rank.go.in b/internal/lsp/testdata/rank/switch_rank.go.in
deleted file mode 100644
index b828528da..000000000
--- a/internal/lsp/testdata/rank/switch_rank.go.in
+++ /dev/null
@@ -1,29 +0,0 @@
-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:
- case t: //@rank(":", timeFriday, timeMonday)
- case time.: //@rank(":", friday, monday)
-
- case now.Weekday():
- case week: //@rank(":", thenWeekday, nowWeekday)
- }
-}
diff --git a/internal/lsp/testdata/rank/type_assert_rank.go.in b/internal/lsp/testdata/rank/type_assert_rank.go.in
deleted file mode 100644
index 416541cdd..000000000
--- a/internal/lsp/testdata/rank/type_assert_rank.go.in
+++ /dev/null
@@ -1,8 +0,0 @@
-package rank
-
-func _() {
- type flower int //@item(flower, "flower", "int", "type")
- var fig string //@item(fig, "fig", "string", "var")
-
- _ = interface{}(nil).(f) //@complete(") //", flower)
-}
diff --git a/internal/lsp/testdata/rank/type_switch_rank.go.in b/internal/lsp/testdata/rank/type_switch_rank.go.in
deleted file mode 100644
index 1ed12b7c1..000000000
--- a/internal/lsp/testdata/rank/type_switch_rank.go.in
+++ /dev/null
@@ -1,31 +0,0 @@
-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/internal/lsp/testdata/references/another/another.go b/internal/lsp/testdata/references/another/another.go
deleted file mode 100644
index 47bda1e4a..000000000
--- a/internal/lsp/testdata/references/another/another.go
+++ /dev/null
@@ -1,13 +0,0 @@
-// Package another has another type.
-package another
-
-import (
- other "golang.org/x/tools/internal/lsp/references/other"
-)
-
-func _() {
- xes := other.GetXes()
- for _, x := range xes { //@mark(defX, "x")
- _ = x.Y //@mark(useX, "x"),mark(anotherXY, "Y"),refs("Y", typeXY, anotherXY, GetXesY),refs(".", defX, useX),refs("x", defX, useX)
- }
-}
diff --git a/internal/lsp/testdata/references/interfaces/interfaces.go b/internal/lsp/testdata/references/interfaces/interfaces.go
deleted file mode 100644
index 6661dcc5d..000000000
--- a/internal/lsp/testdata/references/interfaces/interfaces.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package interfaces
-
-type first interface {
- common() //@mark(firCommon, "common"),refs("common", firCommon, xCommon, zCommon)
- firstMethod() //@mark(firMethod, "firstMethod"),refs("firstMethod", firMethod, xfMethod, zfMethod)
-}
-
-type second interface {
- common() //@mark(secCommon, "common"),refs("common", secCommon, yCommon, zCommon)
- secondMethod() //@mark(secMethod, "secondMethod"),refs("secondMethod", secMethod, ysMethod, zsMethod)
-}
-
-type s struct {}
-
-func (*s) common() {} //@mark(sCommon, "common"),refs("common", sCommon, xCommon, yCommon, zCommon)
-
-func (*s) firstMethod() {} //@mark(sfMethod, "firstMethod"),refs("firstMethod", sfMethod, xfMethod, zfMethod)
-
-func (*s) secondMethod() {} //@mark(ssMethod, "secondMethod"),refs("secondMethod", ssMethod, ysMethod, zsMethod)
-
-func main() {
- var x first = &s{}
- var y second = &s{}
-
- x.common() //@mark(xCommon, "common"),refs("common", firCommon, xCommon, zCommon)
- x.firstMethod() //@mark(xfMethod, "firstMethod"),refs("firstMethod", firMethod, xfMethod, zfMethod)
- y.common() //@mark(yCommon, "common"),refs("common", secCommon, yCommon, zCommon)
- y.secondMethod() //@mark(ysMethod, "secondMethod"),refs("secondMethod", secMethod, ysMethod, zsMethod)
-
- var z *s = &s{}
- z.firstMethod() //@mark(zfMethod, "firstMethod"),refs("firstMethod", sfMethod, xfMethod, zfMethod)
- z.secondMethod() //@mark(zsMethod, "secondMethod"),refs("secondMethod", ssMethod, ysMethod, zsMethod)
- z.common() //@mark(zCommon, "common"),refs("common", sCommon, xCommon, yCommon, zCommon)
-}
diff --git a/internal/lsp/testdata/references/other/other.go b/internal/lsp/testdata/references/other/other.go
deleted file mode 100644
index de35cc81a..000000000
--- a/internal/lsp/testdata/references/other/other.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package other
-
-import (
- references "golang.org/x/tools/internal/lsp/references"
-)
-
-func GetXes() []references.X {
- return []references.X{
- {
- Y: 1, //@mark(GetXesY, "Y"),refs("Y", typeXY, GetXesY, anotherXY)
- },
- }
-}
-
-func _() {
- references.Q = "hello" //@mark(assignExpQ, "Q")
- bob := func(_ string) {}
- bob(references.Q) //@mark(bobExpQ, "Q")
-}
diff --git a/internal/lsp/testdata/references/refs.go b/internal/lsp/testdata/references/refs.go
deleted file mode 100644
index 933a36f54..000000000
--- a/internal/lsp/testdata/references/refs.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Package refs is a package used to test find references.
-package refs
-
-type i int //@mark(typeI, "i"),refs("i", typeI, argI, returnI, embeddedI)
-
-type X struct {
- Y int //@mark(typeXY, "Y")
-}
-
-func _(_ i) []bool { //@mark(argI, "i")
- return nil
-}
-
-func _(_ []byte) i { //@mark(returnI, "i")
- return 0
-}
-
-var q string //@mark(declQ, "q"),refs("q", declQ, assignQ, bobQ)
-
-var Q string //@mark(declExpQ, "Q"),refs("Q", declExpQ, assignExpQ, bobExpQ)
-
-func _() {
- q = "hello" //@mark(assignQ, "q")
- bob := func(_ string) {}
- bob(q) //@mark(bobQ, "q")
-}
-
-type e struct {
- i //@mark(embeddedI, "i"),refs("i", embeddedI, embeddedIUse)
-}
-
-func _() {
- _ = e{}.i //@mark(embeddedIUse, "i")
-}
-
-const (
- foo = iota //@refs("iota")
-)
diff --git a/internal/lsp/testdata/references/refs_test.go b/internal/lsp/testdata/references/refs_test.go
deleted file mode 100644
index 08c0db1f0..000000000
--- a/internal/lsp/testdata/references/refs_test.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package references
-
-import (
- "testing"
-)
-
-// This test exists to bring the test package into existence.
-
-func TestReferences(t *testing.T) {
-}
diff --git a/internal/lsp/testdata/rename/a/random.go.golden b/internal/lsp/testdata/rename/a/random.go.golden
deleted file mode 100644
index 7459863ec..000000000
--- a/internal/lsp/testdata/rename/a/random.go.golden
+++ /dev/null
@@ -1,616 +0,0 @@
--- GetSum-rename --
-package a
-
-import (
- lg "log"
- "fmt" //@rename("fmt", "fmty")
- f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) GetSum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.GetSum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
- default:
- f2.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- f2name-rename --
-package a
-
-import (
- lg "log"
- "fmt" //@rename("fmt", "fmty")
- f2name "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
- default:
- f2name.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- f2y-rename --
-package a
-
-import (
- lg "log"
- "fmt" //@rename("fmt", "fmty")
- f2y "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
- default:
- f2y.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- fmt2-rename --
-package a
-
-import (
- lg "log"
- "fmt" //@rename("fmt", "fmty")
- fmt2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
- default:
- fmt2.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- fmty-rename --
-package a
-
-import (
- lg "log"
- fmty "fmt" //@rename("fmt", "fmty")
- f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y := x.(type) { //@rename("y", "y0")
- case int:
- fmty.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
- default:
- f2.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- format-rename --
-package a
-
-import (
- lg "log"
- format "fmt" //@rename("fmt", "fmty")
- f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y := x.(type) { //@rename("y", "y0")
- case int:
- format.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
- default:
- f2.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- log-rename --
-package a
-
-import (
- "log"
- "fmt" //@rename("fmt", "fmty")
- f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- log.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
- default:
- f2.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- myX-rename --
-package a
-
-import (
- lg "log"
- "fmt" //@rename("fmt", "fmty")
- f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- myX, y int
-}
-
-func (p *Pos) Sum() int {
- return p.myX + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
- default:
- f2.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- pos-rename --
-package a
-
-import (
- lg "log"
- "fmt" //@rename("fmt", "fmty")
- f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var pos Pos //@rename("p", "pos")
- _ = pos.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
- default:
- f2.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- y0-rename --
-package a
-
-import (
- lg "log"
- "fmt" //@rename("fmt", "fmty")
- f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y0 := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y0) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y0) //@rename("y", "y2"),rename("lg","log")
- default:
- f2.Printf("%v", y0) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- y1-rename --
-package a
-
-import (
- lg "log"
- "fmt" //@rename("fmt", "fmty")
- f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y1 := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y1) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y1) //@rename("y", "y2"),rename("lg","log")
- default:
- f2.Printf("%v", y1) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- y2-rename --
-package a
-
-import (
- lg "log"
- "fmt" //@rename("fmt", "fmty")
- f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y2 := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y2) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y2) //@rename("y", "y2"),rename("lg","log")
- default:
- f2.Printf("%v", y2) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- y3-rename --
-package a
-
-import (
- lg "log"
- "fmt" //@rename("fmt", "fmty")
- f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y3 := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y3) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y3) //@rename("y", "y2"),rename("lg","log")
- default:
- f2.Printf("%v", y3) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
--- z-rename --
-package a
-
-import (
- lg "log"
- "fmt" //@rename("fmt", "fmty")
- f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(z int) int { //@rename("y", "z")
- return z
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
- default:
- f2.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
-
diff --git a/internal/lsp/testdata/rename/a/random.go.in b/internal/lsp/testdata/rename/a/random.go.in
deleted file mode 100644
index 069db27ba..000000000
--- a/internal/lsp/testdata/rename/a/random.go.in
+++ /dev/null
@@ -1,42 +0,0 @@
-package a
-
-import (
- lg "log"
- "fmt" //@rename("fmt", "fmty")
- f2 "fmt" //@rename("f2", "f2name"),rename("fmt","f2y")
-)
-
-func Random() int {
- y := 6 + 7
- return y
-}
-
-func Random2(y int) int { //@rename("y", "z")
- return y
-}
-
-type Pos struct {
- x, y int
-}
-
-func (p *Pos) Sum() int {
- return p.x + p.y //@rename("x", "myX")
-}
-
-func _() {
- var p Pos //@rename("p", "pos")
- _ = p.Sum() //@rename("Sum", "GetSum")
-}
-
-func sw() {
- var x interface{}
-
- switch y := x.(type) { //@rename("y", "y0")
- case int:
- fmt.Printf("%d", y) //@rename("y", "y1"),rename("fmt", "format")
- case string:
- lg.Printf("%s", y) //@rename("y", "y2"),rename("lg","log")
- default:
- f2.Printf("%v", y) //@rename("y", "y3"),rename("f2","fmt2")
- }
-}
diff --git a/internal/lsp/testdata/rename/b/b.go b/internal/lsp/testdata/rename/b/b.go
deleted file mode 100644
index 8455f035b..000000000
--- a/internal/lsp/testdata/rename/b/b.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package b
-
-var c int //@rename("int", "uint")
-
-func _() {
- a := 1 //@rename("a", "error")
- a = 2
- _ = a
-}
-
-var (
- // Hello there.
- // Foo does the thing.
- Foo int //@rename("Foo", "Bob")
-)
-
-/*
-Hello description
-*/
-func Hello() {} //@rename("Hello", "Goodbye")
diff --git a/internal/lsp/testdata/rename/b/b.go.golden b/internal/lsp/testdata/rename/b/b.go.golden
deleted file mode 100644
index 9cdc5677f..000000000
--- a/internal/lsp/testdata/rename/b/b.go.golden
+++ /dev/null
@@ -1,78 +0,0 @@
--- Bob-rename --
-package b
-
-var c int //@rename("int", "uint")
-
-func _() {
- a := 1 //@rename("a", "error")
- a = 2
- _ = a
-}
-
-var (
- // Hello there.
- // Bob does the thing.
- Bob int //@rename("Foo", "Bob")
-)
-
-/*
-Hello description
-*/
-func Hello() {} //@rename("Hello", "Goodbye")
-
--- Goodbye-rename --
-b.go:
-package b
-
-var c int //@rename("int", "uint")
-
-func _() {
- a := 1 //@rename("a", "error")
- a = 2
- _ = a
-}
-
-var (
- // Hello there.
- // Foo does the thing.
- Foo int //@rename("Foo", "Bob")
-)
-
-/*
-Goodbye description
-*/
-func Goodbye() {} //@rename("Hello", "Goodbye")
-
-c.go:
-package c
-
-import "golang.org/x/tools/internal/lsp/rename/b"
-
-func _() {
- b.Goodbye() //@rename("Hello", "Goodbye")
-}
-
--- error-rename --
-package b
-
-var c int //@rename("int", "uint")
-
-func _() {
- error := 1 //@rename("a", "error")
- error = 2
- _ = error
-}
-
-var (
- // Hello there.
- // Foo does the thing.
- Foo int //@rename("Foo", "Bob")
-)
-
-/*
-Hello description
-*/
-func Hello() {} //@rename("Hello", "Goodbye")
-
--- uint-rename --
-"int": builtin object
diff --git a/internal/lsp/testdata/rename/bad/bad.go.golden b/internal/lsp/testdata/rename/bad/bad.go.golden
deleted file mode 100644
index 7f4581392..000000000
--- a/internal/lsp/testdata/rename/bad/bad.go.golden
+++ /dev/null
@@ -1,2 +0,0 @@
--- rFunc-rename --
-renaming "sFunc" to "rFunc" not possible because "golang.org/x/tools/internal/lsp/rename/bad" has errors
diff --git a/internal/lsp/testdata/rename/bad/bad.go.in b/internal/lsp/testdata/rename/bad/bad.go.in
deleted file mode 100644
index 56dbee74e..000000000
--- a/internal/lsp/testdata/rename/bad/bad.go.in
+++ /dev/null
@@ -1,8 +0,0 @@
-package bad
-
-type myStruct struct {
-}
-
-func (s *myStruct) sFunc() bool { //@rename("sFunc", "rFunc")
- return s.Bad
-}
diff --git a/internal/lsp/testdata/rename/bad/bad_test.go.in b/internal/lsp/testdata/rename/bad/bad_test.go.in
deleted file mode 100644
index e695db14b..000000000
--- a/internal/lsp/testdata/rename/bad/bad_test.go.in
+++ /dev/null
@@ -1 +0,0 @@
-package bad \ No newline at end of file
diff --git a/internal/lsp/testdata/rename/c/c.go b/internal/lsp/testdata/rename/c/c.go
deleted file mode 100644
index 519d2f6fc..000000000
--- a/internal/lsp/testdata/rename/c/c.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package c
-
-import "golang.org/x/tools/internal/lsp/rename/b"
-
-func _() {
- b.Hello() //@rename("Hello", "Goodbye")
-}
diff --git a/internal/lsp/testdata/rename/c/c.go.golden b/internal/lsp/testdata/rename/c/c.go.golden
deleted file mode 100644
index 56937420c..000000000
--- a/internal/lsp/testdata/rename/c/c.go.golden
+++ /dev/null
@@ -1,32 +0,0 @@
--- Goodbye-rename --
-b.go:
-package b
-
-var c int //@rename("int", "uint")
-
-func _() {
- a := 1 //@rename("a", "error")
- a = 2
- _ = a
-}
-
-var (
- // Hello there.
- // Foo does the thing.
- Foo int //@rename("Foo", "Bob")
-)
-
-/*
-Goodbye description
-*/
-func Goodbye() {} //@rename("Hello", "Goodbye")
-
-c.go:
-package c
-
-import "golang.org/x/tools/internal/lsp/rename/b"
-
-func _() {
- b.Goodbye() //@rename("Hello", "Goodbye")
-}
-
diff --git a/internal/lsp/testdata/rename/c/c2.go b/internal/lsp/testdata/rename/c/c2.go
deleted file mode 100644
index 4fc484a1a..000000000
--- a/internal/lsp/testdata/rename/c/c2.go
+++ /dev/null
@@ -1,4 +0,0 @@
-package c
-
-//go:embed Static/*
-var Static embed.FS //@rename("Static", "static") \ No newline at end of file
diff --git a/internal/lsp/testdata/rename/c/c2.go.golden b/internal/lsp/testdata/rename/c/c2.go.golden
deleted file mode 100644
index e509227a9..000000000
--- a/internal/lsp/testdata/rename/c/c2.go.golden
+++ /dev/null
@@ -1,5 +0,0 @@
--- static-rename --
-package c
-
-//go:embed Static/*
-var static embed.FS //@rename("Static", "static")
diff --git a/internal/lsp/testdata/rename/crosspkg/another/another.go b/internal/lsp/testdata/rename/crosspkg/another/another.go
deleted file mode 100644
index 9b50af2cb..000000000
--- a/internal/lsp/testdata/rename/crosspkg/another/another.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package another
-
-type (
- I interface{ F() }
- C struct{ I }
-)
-
-func (C) g()
-
-func _() {
- var x I = C{}
- x.F() //@rename("F", "G")
-}
diff --git a/internal/lsp/testdata/rename/crosspkg/another/another.go.golden b/internal/lsp/testdata/rename/crosspkg/another/another.go.golden
deleted file mode 100644
index d3fccdaf1..000000000
--- a/internal/lsp/testdata/rename/crosspkg/another/another.go.golden
+++ /dev/null
@@ -1,15 +0,0 @@
--- G-rename --
-package another
-
-type (
- I interface{ G() }
- C struct{ I }
-)
-
-func (C) g()
-
-func _() {
- var x I = C{}
- x.G() //@rename("F", "G")
-}
-
diff --git a/internal/lsp/testdata/rename/crosspkg/crosspkg.go b/internal/lsp/testdata/rename/crosspkg/crosspkg.go
deleted file mode 100644
index 8510bcfe0..000000000
--- a/internal/lsp/testdata/rename/crosspkg/crosspkg.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package crosspkg
-
-func Foo() { //@rename("Foo", "Dolphin")
-
-}
-
-var Bar int //@rename("Bar", "Tomato")
diff --git a/internal/lsp/testdata/rename/crosspkg/crosspkg.go.golden b/internal/lsp/testdata/rename/crosspkg/crosspkg.go.golden
deleted file mode 100644
index 810926de6..000000000
--- a/internal/lsp/testdata/rename/crosspkg/crosspkg.go.golden
+++ /dev/null
@@ -1,40 +0,0 @@
--- Dolphin-rename --
-crosspkg.go:
-package crosspkg
-
-func Dolphin() { //@rename("Foo", "Dolphin")
-
-}
-
-var Bar int //@rename("Bar", "Tomato")
-
-other.go:
-package other
-
-import "golang.org/x/tools/internal/lsp/rename/crosspkg"
-
-func Other() {
- crosspkg.Bar
- crosspkg.Dolphin() //@rename("Foo", "Flamingo")
-}
-
--- Tomato-rename --
-crosspkg.go:
-package crosspkg
-
-func Foo() { //@rename("Foo", "Dolphin")
-
-}
-
-var Tomato int //@rename("Bar", "Tomato")
-
-other.go:
-package other
-
-import "golang.org/x/tools/internal/lsp/rename/crosspkg"
-
-func Other() {
- crosspkg.Tomato
- crosspkg.Foo() //@rename("Foo", "Flamingo")
-}
-
diff --git a/internal/lsp/testdata/rename/crosspkg/other/other.go b/internal/lsp/testdata/rename/crosspkg/other/other.go
deleted file mode 100644
index 10d17cd34..000000000
--- a/internal/lsp/testdata/rename/crosspkg/other/other.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package other
-
-import "golang.org/x/tools/internal/lsp/rename/crosspkg"
-
-func Other() {
- crosspkg.Bar
- crosspkg.Foo() //@rename("Foo", "Flamingo")
-}
diff --git a/internal/lsp/testdata/rename/crosspkg/other/other.go.golden b/internal/lsp/testdata/rename/crosspkg/other/other.go.golden
deleted file mode 100644
index 2722ad96e..000000000
--- a/internal/lsp/testdata/rename/crosspkg/other/other.go.golden
+++ /dev/null
@@ -1,20 +0,0 @@
--- Flamingo-rename --
-crosspkg.go:
-package crosspkg
-
-func Flamingo() { //@rename("Foo", "Dolphin")
-
-}
-
-var Bar int //@rename("Bar", "Tomato")
-
-other.go:
-package other
-
-import "golang.org/x/tools/internal/lsp/rename/crosspkg"
-
-func Other() {
- crosspkg.Bar
- crosspkg.Flamingo() //@rename("Foo", "Flamingo")
-}
-
diff --git a/internal/lsp/testdata/rename/generics/embedded.go b/internal/lsp/testdata/rename/generics/embedded.go
deleted file mode 100644
index b44bab880..000000000
--- a/internal/lsp/testdata/rename/generics/embedded.go
+++ /dev/null
@@ -1,10 +0,0 @@
-//go:build go1.18
-// +build go1.18
-
-package generics
-
-type foo[P any] int //@rename("foo","bar")
-
-var x struct{ foo[int] }
-
-var _ = x.foo
diff --git a/internal/lsp/testdata/rename/generics/embedded.go.golden b/internal/lsp/testdata/rename/generics/embedded.go.golden
deleted file mode 100644
index faa9afb69..000000000
--- a/internal/lsp/testdata/rename/generics/embedded.go.golden
+++ /dev/null
@@ -1,12 +0,0 @@
--- bar-rename --
-//go:build go1.18
-// +build go1.18
-
-package generics
-
-type bar[P any] int //@rename("foo","bar")
-
-var x struct{ bar[int] }
-
-var _ = x.bar
-
diff --git a/internal/lsp/testdata/rename/generics/generics.go b/internal/lsp/testdata/rename/generics/generics.go
deleted file mode 100644
index 977589c0c..000000000
--- a/internal/lsp/testdata/rename/generics/generics.go
+++ /dev/null
@@ -1,25 +0,0 @@
-//go:build go1.18
-// +build go1.18
-
-package generics
-
-type G[P any] struct {
- F int
-}
-
-func (G[_]) M() {}
-
-func F[P any](P) {
- var p P //@rename("P", "Q")
- _ = p
-}
-
-func _() {
- var x G[int] //@rename("G", "H")
- _ = x.F //@rename("F", "K")
- x.M() //@rename("M", "N")
-
- var y G[string]
- _ = y.F
- y.M()
-}
diff --git a/internal/lsp/testdata/rename/generics/generics.go.golden b/internal/lsp/testdata/rename/generics/generics.go.golden
deleted file mode 100644
index 7d39813e1..000000000
--- a/internal/lsp/testdata/rename/generics/generics.go.golden
+++ /dev/null
@@ -1,108 +0,0 @@
--- H-rename --
-//go:build go1.18
-// +build go1.18
-
-package generics
-
-type H[P any] struct {
- F int
-}
-
-func (H[_]) M() {}
-
-func F[P any](P) {
- var p P //@rename("P", "Q")
- _ = p
-}
-
-func _() {
- var x H[int] //@rename("G", "H")
- _ = x.F //@rename("F", "K")
- x.M() //@rename("M", "N")
-
- var y H[string]
- _ = y.F
- y.M()
-}
-
--- K-rename --
-//go:build go1.18
-// +build go1.18
-
-package generics
-
-type G[P any] struct {
- K int
-}
-
-func (G[_]) M() {}
-
-func F[P any](P) {
- var p P //@rename("P", "Q")
- _ = p
-}
-
-func _() {
- var x G[int] //@rename("G", "H")
- _ = x.K //@rename("F", "K")
- x.M() //@rename("M", "N")
-
- var y G[string]
- _ = y.K
- y.M()
-}
-
--- N-rename --
-//go:build go1.18
-// +build go1.18
-
-package generics
-
-type G[P any] struct {
- F int
-}
-
-func (G[_]) N() {}
-
-func F[P any](P) {
- var p P //@rename("P", "Q")
- _ = p
-}
-
-func _() {
- var x G[int] //@rename("G", "H")
- _ = x.F //@rename("F", "K")
- x.N() //@rename("M", "N")
-
- var y G[string]
- _ = y.F
- y.N()
-}
-
--- Q-rename --
-//go:build go1.18
-// +build go1.18
-
-package generics
-
-type G[P any] struct {
- F int
-}
-
-func (G[_]) M() {}
-
-func F[Q any](Q) {
- var p Q //@rename("P", "Q")
- _ = p
-}
-
-func _() {
- var x G[int] //@rename("G", "H")
- _ = x.F //@rename("F", "K")
- x.M() //@rename("M", "N")
-
- var y G[string]
- _ = y.F
- y.M()
-}
-
diff --git a/internal/lsp/testdata/rename/generics/unions.go b/internal/lsp/testdata/rename/generics/unions.go
deleted file mode 100644
index c737b5c27..000000000
--- a/internal/lsp/testdata/rename/generics/unions.go
+++ /dev/null
@@ -1,10 +0,0 @@
-//go:build go1.18
-// +build go1.18
-
-package generics
-
-type T string //@rename("T", "R")
-
-type C interface {
- T | ~int //@rename("T", "S")
-}
diff --git a/internal/lsp/testdata/rename/generics/unions.go.golden b/internal/lsp/testdata/rename/generics/unions.go.golden
deleted file mode 100644
index 463289629..000000000
--- a/internal/lsp/testdata/rename/generics/unions.go.golden
+++ /dev/null
@@ -1,24 +0,0 @@
--- R-rename --
-//go:build go1.18
-// +build go1.18
-
-package generics
-
-type R string //@rename("T", "R")
-
-type C interface {
- R | ~int //@rename("T", "S")
-}
-
--- S-rename --
-//go:build go1.18
-// +build go1.18
-
-package generics
-
-type S string //@rename("T", "R")
-
-type C interface {
- S | ~int //@rename("T", "S")
-}
-
diff --git a/internal/lsp/testdata/rename/issue39614/issue39614.go.golden b/internal/lsp/testdata/rename/issue39614/issue39614.go.golden
deleted file mode 100644
index d87c58e83..000000000
--- a/internal/lsp/testdata/rename/issue39614/issue39614.go.golden
+++ /dev/null
@@ -1,10 +0,0 @@
--- bar-rename --
-package issue39614
-
-func fn() {
- var bar bool //@rename("foo","bar")
- make(map[string]bool
- if true {
- }
-}
-
diff --git a/internal/lsp/testdata/rename/issue39614/issue39614.go.in b/internal/lsp/testdata/rename/issue39614/issue39614.go.in
deleted file mode 100644
index 8222db2c4..000000000
--- a/internal/lsp/testdata/rename/issue39614/issue39614.go.in
+++ /dev/null
@@ -1,8 +0,0 @@
-package issue39614
-
-func fn() {
- var foo bool //@rename("foo","bar")
- make(map[string]bool
- if true {
- }
-}
diff --git a/internal/lsp/testdata/rename/issue42134/1.go b/internal/lsp/testdata/rename/issue42134/1.go
deleted file mode 100644
index 056f8476a..000000000
--- a/internal/lsp/testdata/rename/issue42134/1.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package issue42134
-
-func _() {
- // foo computes things.
- foo := func() {}
-
- foo() //@rename("foo", "bar")
-}
diff --git a/internal/lsp/testdata/rename/issue42134/1.go.golden b/internal/lsp/testdata/rename/issue42134/1.go.golden
deleted file mode 100644
index 266aeef4b..000000000
--- a/internal/lsp/testdata/rename/issue42134/1.go.golden
+++ /dev/null
@@ -1,10 +0,0 @@
--- bar-rename --
-package issue42134
-
-func _() {
- // bar computes things.
- bar := func() {}
-
- bar() //@rename("foo", "bar")
-}
-
diff --git a/internal/lsp/testdata/rename/issue42134/2.go b/internal/lsp/testdata/rename/issue42134/2.go
deleted file mode 100644
index e9f639575..000000000
--- a/internal/lsp/testdata/rename/issue42134/2.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package issue42134
-
-import "fmt"
-
-func _() {
- // minNumber is a min number.
- // Second line.
- minNumber := min(1, 2)
- fmt.Println(minNumber) //@rename("minNumber", "res")
-}
-
-func min(a, b int) int { return a }
diff --git a/internal/lsp/testdata/rename/issue42134/2.go.golden b/internal/lsp/testdata/rename/issue42134/2.go.golden
deleted file mode 100644
index 406a3833c..000000000
--- a/internal/lsp/testdata/rename/issue42134/2.go.golden
+++ /dev/null
@@ -1,14 +0,0 @@
--- res-rename --
-package issue42134
-
-import "fmt"
-
-func _() {
- // res is a min number.
- // Second line.
- res := min(1, 2)
- fmt.Println(res) //@rename("minNumber", "res")
-}
-
-func min(a, b int) int { return a }
-
diff --git a/internal/lsp/testdata/rename/issue42134/3.go b/internal/lsp/testdata/rename/issue42134/3.go
deleted file mode 100644
index 7666f57d3..000000000
--- a/internal/lsp/testdata/rename/issue42134/3.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package issue42134
-
-func _() {
- /*
- tests contains test cases
- */
- tests := []struct { //@rename("tests", "testCases")
- in, out string
- }{}
- _ = tests
-}
diff --git a/internal/lsp/testdata/rename/issue42134/3.go.golden b/internal/lsp/testdata/rename/issue42134/3.go.golden
deleted file mode 100644
index cdcae1808..000000000
--- a/internal/lsp/testdata/rename/issue42134/3.go.golden
+++ /dev/null
@@ -1,13 +0,0 @@
--- testCases-rename --
-package issue42134
-
-func _() {
- /*
- testCases contains test cases
- */
- testCases := []struct { //@rename("tests", "testCases")
- in, out string
- }{}
- _ = testCases
-}
-
diff --git a/internal/lsp/testdata/rename/issue42134/4.go b/internal/lsp/testdata/rename/issue42134/4.go
deleted file mode 100644
index c953520bc..000000000
--- a/internal/lsp/testdata/rename/issue42134/4.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package issue42134
-
-func _() {
- // a is equal to 5. Comment must stay the same
-
- a := 5
- _ = a //@rename("a", "b")
-}
diff --git a/internal/lsp/testdata/rename/issue42134/4.go.golden b/internal/lsp/testdata/rename/issue42134/4.go.golden
deleted file mode 100644
index 2086cf74c..000000000
--- a/internal/lsp/testdata/rename/issue42134/4.go.golden
+++ /dev/null
@@ -1,10 +0,0 @@
--- b-rename --
-package issue42134
-
-func _() {
- // a is equal to 5. Comment must stay the same
-
- b := 5
- _ = b //@rename("a", "b")
-}
-
diff --git a/internal/lsp/testdata/rename/issue43616/issue43616.go.golden b/internal/lsp/testdata/rename/issue43616/issue43616.go.golden
deleted file mode 100644
index 34d03ba7a..000000000
--- a/internal/lsp/testdata/rename/issue43616/issue43616.go.golden
+++ /dev/null
@@ -1,13 +0,0 @@
--- bar-rename --
-package issue43616
-
-type bar int //@rename("foo","bar"),prepare("oo","foo","foo")
-
-var x struct{ bar } //@rename("foo","baz")
-
-var _ = x.bar //@rename("foo","quux")
-
--- baz-rename --
-can't rename embedded fields: rename the type directly or name the field
--- quux-rename --
-can't rename embedded fields: rename the type directly or name the field
diff --git a/internal/lsp/testdata/rename/issue43616/issue43616.go.in b/internal/lsp/testdata/rename/issue43616/issue43616.go.in
deleted file mode 100644
index aaad531b7..000000000
--- a/internal/lsp/testdata/rename/issue43616/issue43616.go.in
+++ /dev/null
@@ -1,7 +0,0 @@
-package issue43616
-
-type foo int //@rename("foo","bar"),prepare("oo","foo","foo")
-
-var x struct{ foo } //@rename("foo","baz")
-
-var _ = x.foo //@rename("foo","quux")
diff --git a/internal/lsp/testdata/rename/shadow/shadow.go b/internal/lsp/testdata/rename/shadow/shadow.go
deleted file mode 100644
index 38329b4fe..000000000
--- a/internal/lsp/testdata/rename/shadow/shadow.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package shadow
-
-func _() {
- a := true
- b, c, _ := A(), B(), D() //@rename("A", "a"),rename("B", "b"),rename("b", "c"),rename("D", "d")
- d := false
- _, _, _, _ = a, b, c, d
-}
-
-func A() int {
- return 0
-}
-
-func B() int {
- return 0
-}
-
-func D() int {
- return 0
-}
diff --git a/internal/lsp/testdata/rename/shadow/shadow.go.golden b/internal/lsp/testdata/rename/shadow/shadow.go.golden
deleted file mode 100644
index 6281bcdd9..000000000
--- a/internal/lsp/testdata/rename/shadow/shadow.go.golden
+++ /dev/null
@@ -1,48 +0,0 @@
--- a-rename --
-renaming this func "A" to "a" would cause this reference to become shadowed by this intervening var definition
--- b-rename --
-package shadow
-
-func _() {
- a := true
- b, c, _ := A(), b(), D() //@rename("A", "a"),rename("B", "b"),rename("b", "c"),rename("D", "d")
- d := false
- _, _, _, _ = a, b, c, d
-}
-
-func A() int {
- return 0
-}
-
-func b() int {
- return 0
-}
-
-func D() int {
- return 0
-}
-
--- c-rename --
-renaming this var "b" to "c" conflicts with var in same block
--- d-rename --
-package shadow
-
-func _() {
- a := true
- b, c, _ := A(), B(), d() //@rename("A", "a"),rename("B", "b"),rename("b", "c"),rename("D", "d")
- d := false
- _, _, _, _ = a, b, c, d
-}
-
-func A() int {
- return 0
-}
-
-func B() int {
- return 0
-}
-
-func d() int {
- return 0
-}
-
diff --git a/internal/lsp/testdata/rename/testy/testy.go b/internal/lsp/testdata/rename/testy/testy.go
deleted file mode 100644
index e46dc06cd..000000000
--- a/internal/lsp/testdata/rename/testy/testy.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package testy
-
-type tt int //@rename("tt", "testyType")
-
-func a() {
- foo := 42 //@rename("foo", "bar")
-}
diff --git a/internal/lsp/testdata/rename/testy/testy.go.golden b/internal/lsp/testdata/rename/testy/testy.go.golden
deleted file mode 100644
index 288dfee96..000000000
--- a/internal/lsp/testdata/rename/testy/testy.go.golden
+++ /dev/null
@@ -1,18 +0,0 @@
--- bar-rename --
-package testy
-
-type tt int //@rename("tt", "testyType")
-
-func a() {
- bar := 42 //@rename("foo", "bar")
-}
-
--- testyType-rename --
-package testy
-
-type testyType int //@rename("tt", "testyType")
-
-func a() {
- foo := 42 //@rename("foo", "bar")
-}
-
diff --git a/internal/lsp/testdata/rename/testy/testy_test.go b/internal/lsp/testdata/rename/testy/testy_test.go
deleted file mode 100644
index 3d86e8455..000000000
--- a/internal/lsp/testdata/rename/testy/testy_test.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package testy
-
-import "testing"
-
-func TestSomething(t *testing.T) {
- var x int //@rename("x", "testyX")
- a() //@rename("a", "b")
-}
diff --git a/internal/lsp/testdata/rename/testy/testy_test.go.golden b/internal/lsp/testdata/rename/testy/testy_test.go.golden
deleted file mode 100644
index 480c8e995..000000000
--- a/internal/lsp/testdata/rename/testy/testy_test.go.golden
+++ /dev/null
@@ -1,30 +0,0 @@
--- b-rename --
-testy.go:
-package testy
-
-type tt int //@rename("tt", "testyType")
-
-func b() {
- foo := 42 //@rename("foo", "bar")
-}
-
-testy_test.go:
-package testy
-
-import "testing"
-
-func TestSomething(t *testing.T) {
- var x int //@rename("x", "testyX")
- b() //@rename("a", "b")
-}
-
--- testyX-rename --
-package testy
-
-import "testing"
-
-func TestSomething(t *testing.T) {
- var testyX int //@rename("x", "testyX")
- a() //@rename("a", "b")
-}
-
diff --git a/internal/lsp/testdata/selector/selector.go.in b/internal/lsp/testdata/selector/selector.go.in
deleted file mode 100644
index 277f98bde..000000000
--- a/internal/lsp/testdata/selector/selector.go.in
+++ /dev/null
@@ -1,66 +0,0 @@
-// +build go1.11
-
-package selector
-
-import (
- "golang.org/x/tools/internal/lsp/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
-}
diff --git a/internal/lsp/testdata/semantic/README.md b/internal/lsp/testdata/semantic/README.md
deleted file mode 100644
index 00ec19af1..000000000
--- a/internal/lsp/testdata/semantic/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-The golden files are the output of `gopls semtok <src-file>`, with `-- semantic --`
-inserted as the first line (the spaces are mandatory) and an extra newline at the end.
diff --git a/internal/lsp/testdata/semantic/a.go b/internal/lsp/testdata/semantic/a.go
deleted file mode 100644
index 54d6c8a62..000000000
--- a/internal/lsp/testdata/semantic/a.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package semantictokens //@ semantic("")
-
-import (
- _ "encoding/utf8"
- utf "encoding/utf8"
- "fmt" //@ semantic("fmt")
- . "fmt"
- "unicode/utf8"
-)
-
-var (
- a = fmt.Print
- b []string = []string{"foo"}
- c1 chan int
- c2 <-chan int
- c3 = make([]chan<- int)
- b = A{X: 23}
- m map[bool][3]*float64
-)
-
-const (
- xx F = iota
- yy = xx + 3
- zz = ""
- ww = "not " + zz
-)
-
-type A struct {
- X int `foof`
-}
-type B interface {
- A
- sad(int) bool
-}
-
-type F int
-
-func (a *A) f() bool {
- var z string
- x := "foo"
- a(x)
- y := "bar" + x
- switch z {
- case "xx":
- default:
- }
- select {
- case z := <-c3[0]:
- default:
- }
- for k, v := range m {
- return (!k) && v[0] == nil
- }
- c2 <- A.X
- w := b[4:]
- j := len(x)
- j--
- q := []interface{}{j, 23i, &y}
- g(q...)
- return true
-}
-
-func g(vv ...interface{}) {
- ff := func() {}
- defer ff()
- go utf.RuneCount("")
- go utf8.RuneCount(vv.(string))
- if true {
- } else {
- }
-Never:
- for i := 0; i < 10; {
- break Never
- }
- _, ok := vv[0].(A)
- if !ok {
- switch x := vv[0].(type) {
- }
- goto Never
- }
-}
diff --git a/internal/lsp/testdata/semantic/a.go.golden b/internal/lsp/testdata/semantic/a.go.golden
deleted file mode 100644
index 4622ae4d7..000000000
--- a/internal/lsp/testdata/semantic/a.go.golden
+++ /dev/null
@@ -1,83 +0,0 @@
--- semantic --
-/*⇒7,keyword,[]*/package /*⇒14,namespace,[]*/semantictokens /*⇒16,comment,[]*///@ semantic("")
-
-/*⇒6,keyword,[]*/import (
- _ "encoding/utf8"
- /*⇒3,namespace,[]*/utf "encoding/utf8"
- "fmt"/*⇐3,namespace,[]*/ /*⇒19,comment,[]*///@ semantic("fmt")
- . "fmt"
- "unicode/utf8"/*⇐4,namespace,[]*/
-)
-
-/*⇒3,keyword,[]*/var (
- /*⇒1,variable,[definition]*/a = /*⇒3,namespace,[]*/fmt./*⇒5,function,[]*/Print
- /*⇒1,variable,[definition]*/b []/*⇒6,type,[defaultLibrary]*/string = []/*⇒6,type,[defaultLibrary]*/string{/*⇒5,string,[]*/"foo"}
- /*⇒2,variable,[definition]*/c1 /*⇒4,keyword,[]*/chan /*⇒3,type,[defaultLibrary]*/int
- /*⇒2,variable,[definition]*/c2 <-/*⇒4,keyword,[]*/chan /*⇒3,type,[defaultLibrary]*/int
- /*⇒2,variable,[definition]*/c3 = /*⇒4,function,[defaultLibrary]*/make([]/*⇒4,keyword,[]*/chan<- /*⇒3,type,[defaultLibrary]*/int)
- /*⇒1,variable,[definition]*/b = /*⇒1,type,[]*/A{/*⇒1,variable,[]*/X: /*⇒2,number,[]*/23}
- /*⇒1,variable,[definition]*/m /*⇒3,keyword,[]*/map[/*⇒4,type,[defaultLibrary]*/bool][/*⇒1,number,[]*/3]/*⇒1,operator,[]*/*/*⇒7,type,[defaultLibrary]*/float64
-)
-
-/*⇒5,keyword,[]*/const (
- /*⇒2,variable,[definition readonly]*/xx /*⇒1,type,[]*/F = /*⇒4,variable,[readonly]*/iota
- /*⇒2,variable,[definition readonly]*/yy = /*⇒2,variable,[readonly]*/xx /*⇒1,operator,[]*/+ /*⇒1,number,[]*/3
- /*⇒2,variable,[definition readonly]*/zz = /*⇒2,string,[]*/""
- /*⇒2,variable,[definition readonly]*/ww = /*⇒6,string,[]*/"not " /*⇒1,operator,[]*/+ /*⇒2,variable,[readonly]*/zz
-)
-
-/*⇒4,keyword,[]*/type /*⇒1,type,[definition]*/A /*⇒6,keyword,[]*/struct {
- /*⇒1,variable,[definition]*/X /*⇒3,type,[defaultLibrary]*/int /*⇒6,comment,[]*/`foof`
-}
-/*⇒4,keyword,[]*/type /*⇒1,type,[definition]*/B /*⇒9,keyword,[]*/interface {
- /*⇒1,type,[]*/A
- /*⇒3,method,[definition]*/sad(/*⇒3,type,[defaultLibrary]*/int) /*⇒4,type,[defaultLibrary]*/bool
-}
-
-/*⇒4,keyword,[]*/type /*⇒1,type,[definition]*/F /*⇒3,type,[defaultLibrary]*/int
-
-/*⇒4,keyword,[]*/func (/*⇒1,variable,[]*/a /*⇒1,operator,[]*/*/*⇒1,type,[]*/A) /*⇒1,method,[definition]*/f() /*⇒4,type,[defaultLibrary]*/bool {
- /*⇒3,keyword,[]*/var /*⇒1,variable,[definition]*/z /*⇒6,type,[defaultLibrary]*/string
- /*⇒1,variable,[definition]*/x /*⇒2,operator,[]*/:= /*⇒5,string,[]*/"foo"
- /*⇒1,variable,[]*/a(/*⇒1,variable,[]*/x)
- /*⇒1,variable,[definition]*/y /*⇒2,operator,[]*/:= /*⇒5,string,[]*/"bar" /*⇒1,operator,[]*/+ /*⇒1,variable,[]*/x
- /*⇒6,keyword,[]*/switch /*⇒1,variable,[]*/z {
- /*⇒4,keyword,[]*/case /*⇒4,string,[]*/"xx":
- /*⇒7,keyword,[]*/default:
- }
- /*⇒6,keyword,[]*/select {
- /*⇒4,keyword,[]*/case /*⇒1,variable,[definition]*/z /*⇒2,operator,[]*/:= /*⇒2,operator,[]*/<-/*⇒2,variable,[]*/c3[/*⇒1,number,[]*/0]:
- /*⇒7,keyword,[]*/default:
- }
- /*⇒3,keyword,[]*/for /*⇒1,variable,[definition]*/k, /*⇒1,variable,[definition]*/v := /*⇒5,keyword,[]*/range /*⇒1,variable,[]*/m {
- /*⇒6,keyword,[]*/return (/*⇒1,operator,[]*/!/*⇒1,variable,[]*/k) /*⇒2,operator,[]*/&& /*⇒1,variable,[]*/v[/*⇒1,number,[]*/0] /*⇒2,operator,[]*/== /*⇒3,variable,[readonly defaultLibrary]*/nil
- }
- /*⇒2,variable,[]*/c2 /*⇒2,operator,[]*/<- /*⇒1,type,[]*/A./*⇒1,variable,[]*/X
- /*⇒1,variable,[definition]*/w /*⇒2,operator,[]*/:= /*⇒1,variable,[]*/b[/*⇒1,number,[]*/4:]
- /*⇒1,variable,[definition]*/j /*⇒2,operator,[]*/:= /*⇒3,function,[defaultLibrary]*/len(/*⇒1,variable,[]*/x)
- /*⇒1,variable,[]*/j/*⇒2,operator,[]*/--
- /*⇒1,variable,[definition]*/q /*⇒2,operator,[]*/:= []/*⇒9,keyword,[]*/interface{}{/*⇒1,variable,[]*/j, /*⇒3,number,[]*/23i, /*⇒1,operator,[]*/&/*⇒1,variable,[]*/y}
- /*⇒1,function,[]*/g(/*⇒1,variable,[]*/q/*⇒3,operator,[]*/...)
- /*⇒6,keyword,[]*/return /*⇒4,variable,[readonly]*/true
-}
-
-/*⇒4,keyword,[]*/func /*⇒1,function,[definition]*/g(/*⇒2,parameter,[definition]*/vv /*⇒3,operator,[]*/.../*⇒9,keyword,[]*/interface{}) {
- /*⇒2,variable,[definition]*/ff /*⇒2,operator,[]*/:= /*⇒4,keyword,[]*/func() {}
- /*⇒5,keyword,[]*/defer /*⇒2,variable,[]*/ff()
- /*⇒2,keyword,[]*/go /*⇒3,namespace,[]*/utf./*⇒9,function,[]*/RuneCount(/*⇒2,string,[]*/"")
- /*⇒2,keyword,[]*/go /*⇒4,namespace,[]*/utf8./*⇒9,function,[]*/RuneCount(/*⇒2,variable,[]*/vv.(/*⇒6,type,[]*/string))
- /*⇒2,keyword,[]*/if /*⇒4,variable,[readonly]*/true {
- } /*⇒4,keyword,[]*/else {
- }
-/*⇒5,parameter,[definition]*/Never:
- /*⇒3,keyword,[]*/for /*⇒1,variable,[definition]*/i /*⇒2,operator,[]*/:= /*⇒1,number,[]*/0; /*⇒1,variable,[]*/i /*⇒1,operator,[]*/< /*⇒2,number,[]*/10; {
- /*⇒5,keyword,[]*/break Never
- }
- _, /*⇒2,variable,[definition]*/ok /*⇒2,operator,[]*/:= /*⇒2,variable,[]*/vv[/*⇒1,number,[]*/0].(/*⇒1,type,[]*/A)
- /*⇒2,keyword,[]*/if /*⇒1,operator,[]*/!/*⇒2,variable,[]*/ok {
- /*⇒6,keyword,[]*/switch /*⇒1,variable,[definition]*/x /*⇒2,operator,[]*/:= /*⇒2,variable,[]*/vv[/*⇒1,number,[]*/0].(/*⇒4,keyword,[]*/type) {
- }
- /*⇒4,keyword,[]*/goto Never
- }
-}
-
diff --git a/internal/lsp/testdata/semantic/b.go b/internal/lsp/testdata/semantic/b.go
deleted file mode 100644
index a536f97bf..000000000
--- a/internal/lsp/testdata/semantic/b.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package semantictokens //@ semantic("")
-
-func f(x ...interface{}) {
-}
-
-func weirⰀd() { /*😀*/ // comment
- const (
- snil = nil
- nil = true
- true = false
- false = snil
- cmd = `foof`
- double = iota
- iota = copy
- four = (len(cmd)/2 < 5)
- five = four
- )
- f(cmd, nil, double, iota)
-}
-
-/*
-
-multiline */ /*
-multiline
-*/
-type AA int
-type BB struct {
- AA
-}
-type CC struct {
- AA int
-}
-type D func(aa AA) (BB error)
-type E func(AA) BB
diff --git a/internal/lsp/testdata/semantic/b.go.golden b/internal/lsp/testdata/semantic/b.go.golden
deleted file mode 100644
index 203f6b189..000000000
--- a/internal/lsp/testdata/semantic/b.go.golden
+++ /dev/null
@@ -1,36 +0,0 @@
--- semantic --
-/*⇒7,keyword,[]*/package /*⇒14,namespace,[]*/semantictokens /*⇒16,comment,[]*///@ semantic("")
-
-/*⇒4,keyword,[]*/func /*⇒1,function,[definition]*/f(/*⇒1,parameter,[definition]*/x /*⇒3,operator,[]*/.../*⇒9,keyword,[]*/interface{}) {
-}
-
-/*⇒4,keyword,[]*/func /*⇒6,function,[definition]*/weirⰀd() { /*⇒5,comment,[]*//*😀*/ /*⇒10,comment,[]*/// comment
- /*⇒5,keyword,[]*/const (
- /*⇒4,variable,[definition readonly]*/snil = /*⇒3,variable,[readonly defaultLibrary]*/nil
- /*⇒3,variable,[definition readonly]*/nil = /*⇒4,variable,[readonly]*/true
- /*⇒4,variable,[definition readonly]*/true = /*⇒5,variable,[readonly]*/false
- /*⇒5,variable,[definition readonly]*/false = /*⇒4,variable,[readonly]*/snil
- /*⇒3,variable,[definition readonly]*/cmd = /*⇒6,string,[]*/`foof`
- /*⇒6,variable,[definition readonly]*/double = /*⇒4,variable,[readonly]*/iota
- /*⇒4,variable,[definition readonly]*/iota = /*⇒4,function,[defaultLibrary]*/copy
- /*⇒4,variable,[definition readonly]*/four = (/*⇒3,function,[defaultLibrary]*/len(/*⇒3,variable,[readonly]*/cmd)/*⇒1,operator,[]*// /*⇒1,number,[]*/2 /*⇒1,operator,[]*/< /*⇒1,number,[]*/5)
- /*⇒4,variable,[definition readonly]*/five = /*⇒4,variable,[readonly]*/four
- )
- /*⇒1,function,[]*/f(/*⇒3,variable,[readonly]*/cmd, /*⇒3,variable,[readonly]*/nil, /*⇒6,variable,[readonly]*/double, /*⇒4,variable,[readonly]*/iota)
-}
-
-/*⇒2,comment,[]*//*
-/*⇒0,comment,[]*/
-/*⇒12,comment,[]*/multiline */ /*⇒2,comment,[]*//*
-/*⇒9,comment,[]*/multiline
-/*⇒2,comment,[]*/*/
-/*⇒4,keyword,[]*/type /*⇒2,type,[definition]*/AA /*⇒3,type,[defaultLibrary]*/int
-/*⇒4,keyword,[]*/type /*⇒2,type,[definition]*/BB /*⇒6,keyword,[]*/struct {
- /*⇒2,type,[]*/AA
-}
-/*⇒4,keyword,[]*/type /*⇒2,type,[definition]*/CC /*⇒6,keyword,[]*/struct {
- /*⇒2,variable,[definition]*/AA /*⇒3,type,[defaultLibrary]*/int
-}
-/*⇒4,keyword,[]*/type /*⇒1,type,[definition]*/D /*⇒4,keyword,[]*/func(/*⇒2,parameter,[definition]*/aa /*⇒2,type,[]*/AA) (/*⇒2,parameter,[definition]*/BB /*⇒5,type,[]*/error)
-/*⇒4,keyword,[]*/type /*⇒1,type,[definition]*/E /*⇒4,keyword,[]*/func(/*⇒2,type,[]*/AA) /*⇒2,type,[]*/BB
-
diff --git a/internal/lsp/testdata/semantic/semantic_test.go b/internal/lsp/testdata/semantic/semantic_test.go
deleted file mode 100644
index 63d59f666..000000000
--- a/internal/lsp/testdata/semantic/semantic_test.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package semantictokens
-
-import (
- "os"
- "testing"
-)
-
-func TestSemanticTokens(t *testing.T) {
- a, _ := os.Getwd()
- // climb up to find internal/lsp
- // find all the .go files
-
-}
diff --git a/internal/lsp/testdata/signature/signature.go b/internal/lsp/testdata/signature/signature.go
deleted file mode 100644
index 4e2b12bc4..000000000
--- a/internal/lsp/testdata/signature/signature.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// Package signature has tests for signature help.
-package signature
-
-import (
- "bytes"
- "encoding/json"
- "math/big"
-)
-
-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)
-
- 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("myFunc", "Foo(a string, b int) (c bool)", 0)
- Foo(myFunc(123), 456) //@signature("123", "myFunc(foo int) string", 0)
-
- panic("oops!") //@signature(")", "panic(v interface{})", 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()) {}
diff --git a/internal/lsp/testdata/signature/signature.go.golden b/internal/lsp/testdata/signature/signature.go.golden
deleted file mode 100644
index d7a65b3b8..000000000
--- a/internal/lsp/testdata/signature/signature.go.golden
+++ /dev/null
@@ -1,65 +0,0 @@
--- AliasMap(a map[*Alias]StringAlias) (b map[*Alias]StringAlias, c map[*Alias]StringAlias)-signature --
-AliasMap(a map[*Alias]StringAlias) (b map[*Alias]StringAlias, c map[*Alias]StringAlias)
-
--- AliasSlice(a []*Alias) (b Alias)-signature --
-AliasSlice(a []*Alias) (b Alias)
-
--- Bar(float64, ...byte)-signature --
-Bar(float64, ...byte)
-
--- Foo(a string, b int) (c bool)-signature --
-Foo(a string, b int) (c bool)
-
--- GetAlias() Alias-signature --
-GetAlias() Alias
-
--- GetAliasPtr() *Alias-signature --
-GetAliasPtr() *Alias
-
--- Next(n int) []byte-signature --
-Next(n int) []byte
-
-Next returns a slice containing the next n bytes from the buffer, advancing the buffer as if the bytes had been returned by Read.
-
--- OtherAliasMap(a map[Alias]OtherAlias, b map[Alias]OtherAlias) map[Alias]OtherAlias-signature --
-OtherAliasMap(a map[Alias]OtherAlias, b map[Alias]OtherAlias) map[Alias]OtherAlias
-
--- SetAliasSlice(a []*Alias)-signature --
-SetAliasSlice(a []*Alias)
-
--- SetOtherAliasMap(a map[*Alias]OtherAlias)-signature --
-SetOtherAliasMap(a map[*Alias]OtherAlias)
-
--- fn(hi string, there string) func(i int) rune-signature --
-fn(hi string, there string) func(i int) rune
-
--- foo(e *json.Decoder) (*big.Int, error)-signature --
-foo(e *json.Decoder) (*big.Int, error)
-
--- func(hi string, there string) func(i int) rune-signature --
-func(hi string, there string) func(i int) rune
-
--- func(i int) rune-signature --
-func(i int) rune
-
--- func(string, int) bool-signature --
-func(string, int) bool
-
--- make(t Type, size ...int) Type-signature --
-make(t Type, size ...int) Type
-
-The make built-in function allocates and initializes an object of type slice, map, or chan (only).
-
--- myFunc(foo int) string-signature --
-myFunc(foo int) string
-
--- panic(v interface{})-signature --
-panic(v any)
-
-The panic built-in function stops normal execution of the current goroutine.
-
--- println(args ...Type)-signature --
-println(args ...Type)
-
-The println built-in function formats its arguments in an implementation-specific way and writes the result to standard error.
-
diff --git a/internal/lsp/testdata/signature/signature2.go.golden b/internal/lsp/testdata/signature/signature2.go.golden
deleted file mode 100644
index e8102584f..000000000
--- a/internal/lsp/testdata/signature/signature2.go.golden
+++ /dev/null
@@ -1,3 +0,0 @@
--- Foo(a string, b int) (c bool)-signature --
-Foo(a string, b int) (c bool)
-
diff --git a/internal/lsp/testdata/signature/signature2.go.in b/internal/lsp/testdata/signature/signature2.go.in
deleted file mode 100644
index 16355ffc0..000000000
--- a/internal/lsp/testdata/signature/signature2.go.in
+++ /dev/null
@@ -1,5 +0,0 @@
-package signature
-
-func _() {
- Foo(//@signature("//", "Foo(a string, b int) (c bool)", 0)
-}
diff --git a/internal/lsp/testdata/signature/signature3.go.golden b/internal/lsp/testdata/signature/signature3.go.golden
deleted file mode 100644
index e8102584f..000000000
--- a/internal/lsp/testdata/signature/signature3.go.golden
+++ /dev/null
@@ -1,3 +0,0 @@
--- Foo(a string, b int) (c bool)-signature --
-Foo(a string, b int) (c bool)
-
diff --git a/internal/lsp/testdata/signature/signature3.go.in b/internal/lsp/testdata/signature/signature3.go.in
deleted file mode 100644
index 032be1304..000000000
--- a/internal/lsp/testdata/signature/signature3.go.in
+++ /dev/null
@@ -1,5 +0,0 @@
-package signature
-
-func _() {
- Foo("hello",//@signature("//", "Foo(a string, b int) (c bool)", 1)
-} \ No newline at end of file
diff --git a/internal/lsp/testdata/signature/signature_test.go b/internal/lsp/testdata/signature/signature_test.go
deleted file mode 100644
index 62e54a238..000000000
--- a/internal/lsp/testdata/signature/signature_test.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package signature_test
-
-import (
- "testing"
-
- sig "golang.org/x/tools/internal/lsp/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)
-}
diff --git a/internal/lsp/testdata/signature/signature_test.go.golden b/internal/lsp/testdata/signature/signature_test.go.golden
deleted file mode 100644
index 3853dffc9..000000000
--- a/internal/lsp/testdata/signature/signature_test.go.golden
+++ /dev/null
@@ -1,30 +0,0 @@
--- AliasMap(a map[*sig.Alias]sig.StringAlias) (b map[*sig.Alias]sig.StringAlias, c map[*sig.Alias]sig.StringAlias)-signature --
-AliasMap(a map[*sig.Alias]sig.StringAlias) (b map[*sig.Alias]sig.StringAlias, c map[*sig.Alias]sig.StringAlias)
-
--- AliasMap(a map[*signature.Alias]signature.StringAlias) (b map[*signature.Alias]signature.StringAlias, c map[*signature.Alias]signature.StringAlias)-signature --
-AliasMap(a map[*signature.Alias]signature.StringAlias) (b map[*signature.Alias]signature.StringAlias, c map[*signature.Alias]signature.StringAlias)
-
--- AliasSlice(a []*sig.Alias) (b sig.Alias)-signature --
-AliasSlice(a []*sig.Alias) (b sig.Alias)
-
--- AliasSlice(a []*signature.Alias) (b signature.Alias)-signature --
-AliasSlice(a []*signature.Alias) (b signature.Alias)
-
--- GetAlias() signature.Alias-signature --
-GetAlias() signature.Alias
-
--- GetAliasPtr() *signature.Alias-signature --
-GetAliasPtr() *signature.Alias
-
--- OtherAliasMap(a map[sig.Alias]sig.OtherAlias, b map[sig.Alias]sig.OtherAlias) map[sig.Alias]sig.OtherAlias-signature --
-OtherAliasMap(a map[sig.Alias]sig.OtherAlias, b map[sig.Alias]sig.OtherAlias) map[sig.Alias]sig.OtherAlias
-
--- OtherAliasMap(a map[signature.Alias]signature.OtherAlias, b map[signature.Alias]signature.OtherAlias) map[signature.Alias]signature.OtherAlias-signature --
-OtherAliasMap(a map[signature.Alias]signature.OtherAlias, b map[signature.Alias]signature.OtherAlias) map[signature.Alias]signature.OtherAlias
-
--- SetAliasSlice(a []*signature.Alias)-signature --
-SetAliasSlice(a []*signature.Alias)
-
--- SetOtherAliasMap(a map[*signature.Alias]signature.OtherAlias)-signature --
-SetOtherAliasMap(a map[*signature.Alias]signature.OtherAlias)
-
diff --git a/internal/lsp/testdata/snippets/func_snippets118.go.in b/internal/lsp/testdata/snippets/func_snippets118.go.in
deleted file mode 100644
index d4933689d..000000000
--- a/internal/lsp/testdata/snippets/func_snippets118.go.in
+++ /dev/null
@@ -1,19 +0,0 @@
-// +build go1.18
-//go:build go1.18
-
-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:}]()", "NewSyncMap[${1:K comparable}, ${2:V any}]()")
- _ = Identi //@snippet(" //", Identity, "Identity[${1:}](${2:})", "Identity[${1:P ~int}](${2:p P})")
-}
diff --git a/internal/lsp/testdata/snippets/literal.go b/internal/lsp/testdata/snippets/literal.go
deleted file mode 100644
index 43931d18e..000000000
--- a/internal/lsp/testdata/snippets/literal.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package snippets
-
-import (
- "golang.org/x/tools/internal/lsp/signature"
- t "golang.org/x/tools/internal/lsp/types"
-)
-
-type structy struct {
- x signature.MyType
-}
-
-func X(_ map[signature.Alias]t.CoolAlias) (map[signature.Alias]t.CoolAlias) {
- return nil
-}
-
-func _() {
- X() //@signature(")", "X(_ map[signature.Alias]t.CoolAlias) map[signature.Alias]t.CoolAlias", 0)
- _ = signature.MyType{} //@item(literalMyType, "signature.MyType{}", "", "var")
- s := structy{
- x: //@snippet(" //", literalMyType, "signature.MyType{\\}", "signature.MyType{\\}")
- }
-} \ No newline at end of file
diff --git a/internal/lsp/testdata/snippets/literal.go.golden b/internal/lsp/testdata/snippets/literal.go.golden
deleted file mode 100644
index f9725f733..000000000
--- a/internal/lsp/testdata/snippets/literal.go.golden
+++ /dev/null
@@ -1,6 +0,0 @@
--- X(_ map[signature.Alias]t.CoolAlias) map[signature.Alias]t.CoolAlias-signature --
-X(_ map[signature.Alias]t.CoolAlias) map[signature.Alias]t.CoolAlias
-
--- X(_ map[signatures.Alias]types.CoolAlias) map[signatures.Alias]types.CoolAlias-signature --
-X(_ map[signatures.Alias]types.CoolAlias) map[signatures.Alias]types.CoolAlias
-
diff --git a/internal/lsp/testdata/snippets/literal_snippets.go.in b/internal/lsp/testdata/snippets/literal_snippets.go.in
deleted file mode 100644
index 4a2a01dfa..000000000
--- a/internal/lsp/testdata/snippets/literal_snippets.go.in
+++ /dev/null
@@ -1,233 +0,0 @@
-package snippets
-
-import (
- "bytes"
- "context"
- "go/ast"
- "net/http"
- "sort"
-
- "golang.org/x/tools/internal/lsp/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\\}", "&[]int{$0\\}")
- var _ **[]int = in //@complete(" //")
-
- var slice []int
- slice = i //@snippet(" //", litIntSlice, "[]int{$0\\}", "[]int{$0\\}")
- slice = m //@snippet(" //", makeIntSlice, "make([]int, ${1:})", "make([]int, ${1:0})")
-}
-
-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\\}", "namedInt{$0\\}")
- namedSlice = m //@snippet(" //", makeNamedSlice, "make(namedInt, ${1:})", "make(namedInt, ${1:0})")
-}
-
-func _() {
- make(chan int) //@item(makeChan, "make(chan int)", "", "func")
-
- var ch chan int
- ch = m //@snippet(" //", makeChan, "make(chan int)", "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\\}", "map[string]struct{\\}{$0\\}")
- m = m //@snippet(" //", makeMap, "make(map[string]struct{\\})", "make(map[string]struct{\\})")
-
- struct{}{} //@item(litEmptyStruct, "struct{}{}", "", "var")
-
- m["hi"] = s //@snippet(" //", litEmptyStruct, "struct{\\}{\\}", "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\\}", "myStruct{$0\\}")
-
- var msPtr *myStruct
- msPtr = m //@snippet(" //", litStructPtr, "&myStruct{$0\\}", "&myStruct{$0\\}")
-
- msPtr = &m //@snippet(" //", litStruct, "myStruct{$0\\}", "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{\\}", "myImpl{\\}")
-
- myBasicImpl() //@item(litBasicImpl, "myBasicImpl()", "string", "var")
-
- mi = m //@snippet(" //", litBasicImpl, "myBasicImpl($0)", "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{\\}", "&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{\\}", "Buffer{\\}")
-}
-
-func _() {
- _ = "func(...) {}" //@item(litFunc, "func(...) {}", "", "var")
-
- sort.Slice(nil, fun) //@complete(")", litFunc),snippet(")", litFunc, "func(i, j int) bool {$0\\}", "func(i, j int) bool {$0\\}")
-
- http.HandleFunc("", f) //@snippet(")", litFunc, "func(w http.ResponseWriter, r *http.Request) {$0\\}", "func(${1:w} http.ResponseWriter, ${2:r} *http.Request) {$0\\}")
-
- // no literal "func" completions
- http.Handle("", fun) //@complete(")")
-
- http.HandlerFunc() //@item(handlerFunc, "http.HandlerFunc()", "", "var")
- http.Handle("", h) //@snippet(")", handlerFunc, "http.HandlerFunc($0)", "http.HandlerFunc($0)")
- http.Handle("", http.HandlerFunc()) //@snippet("))", litFunc, "func(w http.ResponseWriter, r *http.Request) {$0\\}", "func(${1:w} http.ResponseWriter, ${2:r} *http.Request) {$0\\}")
-
- var namedReturn func(s string) (b bool)
- namedReturn = f //@snippet(" //", litFunc, "func(s string) (b bool) {$0\\}", "func(s string) (b bool) {$0\\}")
-
- var multiReturn func() (bool, int)
- multiReturn = f //@snippet(" //", litFunc, "func() (bool, int) {$0\\}", "func() (bool, int) {$0\\}")
-
- var multiNamedReturn func() (b bool, i int)
- multiNamedReturn = f //@snippet(" //", litFunc, "func() (b bool, i int) {$0\\}", "func() (b bool, i int) {$0\\}")
-
- var duplicateParams func(myImpl, int, myImpl)
- duplicateParams = f //@snippet(" //", litFunc, "func(mi1 myImpl, i int, mi2 myImpl) {$0\\}", "func(${1:mi1} myImpl, ${2:i} int, ${3:mi2} myImpl) {$0\\}")
-
- type aliasImpl = myImpl
- var aliasParams func(aliasImpl) aliasImpl
- aliasParams = f //@snippet(" //", litFunc, "func(ai aliasImpl) aliasImpl {$0\\}", "func(${1: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 [two]bool, m map[string]string, s struct{ i int \\}, i2 interface{ foo() \\}, c <-chan int) {$0\\}", "func(${1:i1} []int, ${2:b} [two]bool, ${3:m} map[string]string, ${4:s} struct{ i int \\}, ${5:i2} interface{ foo() \\}, ${6:c} <-chan int) {$0\\}")
-
- var _ func(ast.Node) = f //@snippet(" //", litFunc, "func(n ast.Node) {$0\\}", "func(${1:n} ast.Node) {$0\\}")
- var _ func(error) = f //@snippet(" //", litFunc, "func(err error) {$0\\}", "func(${1:err} error) {$0\\}")
- var _ func(context.Context) = f //@snippet(" //", litFunc, "func(ctx context.Context) {$0\\}", "func(${1:ctx} context.Context) {$0\\}")
-
- type context struct {}
- var _ func(context) = f //@snippet(" //", litFunc, "func(ctx context) {$0\\}", "func(${1:ctx} context) {$0\\}")
-}
-
-func _() {
- StructFoo{} //@item(litStructFoo, "StructFoo{}", "struct{...}", "struct")
-
- var sfp *foo.StructFoo
- // Don't insert the "&" before "StructFoo{}".
- sfp = foo.Str //@snippet(" //", litStructFoo, "StructFoo{$0\\}", "StructFoo{$0\\}")
-
- var sf foo.StructFoo
- sf = foo.Str //@snippet(" //", litStructFoo, "StructFoo{$0\\}", "StructFoo{$0\\}")
- sf = foo. //@snippet(" //", litStructFoo, "StructFoo{$0\\}", "StructFoo{$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)", "float64($0)")
-
- type myInt int
- myInt() //@item(litMyInt, "myInt()", "", "var")
-
- var mi myInt
- mi = my //@snippet(" //", litMyInt, "myInt($0)", "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\\}", "[]int{$0\\}")
-}
-
-
-func _() {
- // don't complete to "untyped int()"
- []int{}[untyped] //@complete("] //")
-}
diff --git a/internal/lsp/testdata/snippets/literal_snippets118.go.in b/internal/lsp/testdata/snippets/literal_snippets118.go.in
deleted file mode 100644
index 8251a6384..000000000
--- a/internal/lsp/testdata/snippets/literal_snippets118.go.in
+++ /dev/null
@@ -1,14 +0,0 @@
-// +build go1.18
-//go:build go1.18
-
-package snippets
-
-type Tree[T any] struct{}
-
-func (tree Tree[T]) Do(f func(s T)) {}
-
-func _() {
- _ = "func(...) {}" //@item(litFunc, "func(...) {}", "", "var")
- var t Tree[string]
- t.Do(fun) //@complete(")", litFunc),snippet(")", litFunc, "func(s string) {$0\\}", "func(s string) {$0\\}")
-}
diff --git a/internal/lsp/testdata/snippets/postfix.go b/internal/lsp/testdata/snippets/postfix.go
deleted file mode 100644
index d29694e83..000000000
--- a/internal/lsp/testdata/snippets/postfix.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package snippets
-
-// These tests check that postfix completions do and do not show up in
-// certain cases. Tests for the postfix completion contents are under
-// regtest.
-
-func _() {
- /* append! */ //@item(postfixAppend, "append!", "append and re-assign slice", "snippet")
- var foo []int
- foo.append //@rank(" //", postfixAppend)
-
- []int{}.append //@complete(" //")
-
- []int{}.last //@complete(" //")
-
- /* copy! */ //@item(postfixCopy, "copy!", "duplicate slice", "snippet")
-
- 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")
- /* last! */ //@item(postfixLast, "last!", "s[len(s)-1]", "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")
-
- var foo []int
- foo. //@complete(" //", postfixAppend, postfixCopy, postfixLast, postfixPrint, postfixRange, postfixReverse, postfixSort, postfixVar)
-
- foo = nil
-}
diff --git a/internal/lsp/testdata/snippets/snippets.go.golden b/internal/lsp/testdata/snippets/snippets.go.golden
deleted file mode 100644
index 3f20ba50b..000000000
--- a/internal/lsp/testdata/snippets/snippets.go.golden
+++ /dev/null
@@ -1,3 +0,0 @@
--- baz(at AliasType, b bool)-signature --
-baz(at AliasType, b bool)
-
diff --git a/internal/lsp/testdata/snippets/snippets.go.in b/internal/lsp/testdata/snippets/snippets.go.in
deleted file mode 100644
index 58150c644..000000000
--- a/internal/lsp/testdata/snippets/snippets.go.in
+++ /dev/null
@@ -1,61 +0,0 @@
-package snippets
-
-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:})", "foo(${1:i int}, ${2:b bool})")
-
- bar //@snippet(" //", snipBar, "bar(${1:})", "bar(${1:fn func()})")
-
- baz //@snippet(" //", snipBaz, "baz(${1:})", "baz(${1:at AliasType}, ${2:b bool})")
- baz() //@signature("(", "baz(at AliasType, b bool)", 0)
-
- bar(nil) //@snippet("(", snipBar, "bar", "bar")
- bar(ba) //@snippet(")", snipBar, "bar(${1:})", "bar(${1:fn func()})")
- var f Foo
- bar(f.Ba) //@snippet(")", snipMethodBaz, "Baz()", "Baz()")
- (bar)(nil) //@snippet(")", snipBar, "bar(${1:})", "bar(${1:fn func()})")
- (f.Ba)() //@snippet(")", snipMethodBaz, "Baz()", "Baz()")
-
- Foo{
- B //@snippet(" //", snipFieldBar, "Bar: ${1:},", "Bar: ${1:int},")
- }
-
- Foo{
- F //@snippet(" //", snipFieldFunc, "Func: ${1:},", "Func: ${1:func(at AliasType) error},")
- }
-
- Foo{B} //@snippet("}", snipFieldBar, "Bar: ${1:}", "Bar: ${1:int}")
- Foo{} //@snippet("}", snipFieldBar, "Bar: ${1:}", "Bar: ${1:int}")
-
- Foo{Foo{}.B} //@snippet("} ", snipFieldBar, "Bar", "Bar")
-
- var err error
- err.Error() //@snippet("E", Error, "Error()", "Error()")
- f.Baz() //@snippet("B", snipMethodBaz, "Baz()", "Baz()")
-
- f.Baz() //@snippet("(", snipMethodBazBar, "BazBar", "BazBar")
-
- f.Baz() //@snippet("B", snipMethodBazBaz, "BazBaz(${1:})", "BazBaz(${1:at AliasType})")
-}
-
-func _() {
- type bar struct {
- a int
- b float64 //@item(snipBarB, "b", "float64", "field")
- }
- bar{b} //@snippet("}", snipBarB, "b: ${1:}", "b: ${1:float64}")
-}
diff --git a/internal/lsp/testdata/statements/append.go b/internal/lsp/testdata/statements/append.go
deleted file mode 100644
index 0eea85a28..000000000
--- a/internal/lsp/testdata/statements/append.go
+++ /dev/null
@@ -1,42 +0,0 @@
-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:})", "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:})", "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:})", "s.xyz = append(s.xyz, ${1:})")
-}
-
-func _() {
- var foo [][]int
-
- /* append(foo[0], ) */ //@item(stmtFooAppend, "append(foo[0], )", "", "func")
-
- foo[0] = app //@complete(" //"),snippet(" //", stmtFooAppend, "append(foo[0], ${1:})", "append(foo[0], ${1:})")
-}
diff --git a/internal/lsp/testdata/statements/if_err_check_return.go b/internal/lsp/testdata/statements/if_err_check_return.go
deleted file mode 100644
index e82b78333..000000000
--- a/internal/lsp/testdata/statements/if_err_check_return.go
+++ /dev/null
@@ -1,27 +0,0 @@
-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\\}")
-}
diff --git a/internal/lsp/testdata/statements/if_err_check_return_2.go b/internal/lsp/testdata/statements/if_err_check_return_2.go
deleted file mode 100644
index e2dce804f..000000000
--- a/internal/lsp/testdata/statements/if_err_check_return_2.go
+++ /dev/null
@@ -1,12 +0,0 @@
-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\\}")
-}
diff --git a/internal/lsp/testdata/statements/if_err_check_test.go b/internal/lsp/testdata/statements/if_err_check_test.go
deleted file mode 100644
index 6de587879..000000000
--- a/internal/lsp/testdata/statements/if_err_check_test.go
+++ /dev/null
@@ -1,20 +0,0 @@
-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\\}")
-}
diff --git a/internal/lsp/testdata/stub/other/other.go b/internal/lsp/testdata/stub/other/other.go
deleted file mode 100644
index ba3c1747a..000000000
--- a/internal/lsp/testdata/stub/other/other.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package other
-
-import (
- "bytes"
- renamed_context "context"
-)
-
-type Interface interface {
- Get(renamed_context.Context) *bytes.Buffer
-}
diff --git a/internal/lsp/testdata/stub/stub_add_selector.go b/internal/lsp/testdata/stub/stub_add_selector.go
deleted file mode 100644
index a15afd7c2..000000000
--- a/internal/lsp/testdata/stub/stub_add_selector.go
+++ /dev/null
@@ -1,12 +0,0 @@
-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", "refactor.rewrite")
-
-type readerFrom struct{}
diff --git a/internal/lsp/testdata/stub/stub_add_selector.go.golden b/internal/lsp/testdata/stub/stub_add_selector.go.golden
deleted file mode 100644
index e885483ea..000000000
--- a/internal/lsp/testdata/stub/stub_add_selector.go.golden
+++ /dev/null
@@ -1,19 +0,0 @@
--- suggestedfix_stub_add_selector_10_23 --
-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", "refactor.rewrite")
-
-type readerFrom struct{}
-
-// ReadFrom implements io.ReaderFrom
-func (*readerFrom) ReadFrom(r io.Reader) (n int64, err error) {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/stub/stub_assign.go b/internal/lsp/testdata/stub/stub_assign.go
deleted file mode 100644
index 9336361d0..000000000
--- a/internal/lsp/testdata/stub/stub_assign.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package stub
-
-import "io"
-
-func main() {
- var br io.ByteWriter
- br = &byteWriter{} //@suggestedfix("&", "refactor.rewrite")
-}
-
-type byteWriter struct{}
diff --git a/internal/lsp/testdata/stub/stub_assign.go.golden b/internal/lsp/testdata/stub/stub_assign.go.golden
deleted file mode 100644
index a52a82367..000000000
--- a/internal/lsp/testdata/stub/stub_assign.go.golden
+++ /dev/null
@@ -1,17 +0,0 @@
--- suggestedfix_stub_assign_7_7 --
-package stub
-
-import "io"
-
-func main() {
- var br io.ByteWriter
- br = &byteWriter{} //@suggestedfix("&", "refactor.rewrite")
-}
-
-type byteWriter struct{}
-
-// WriteByte implements io.ByteWriter
-func (*byteWriter) WriteByte(c byte) error {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/stub/stub_assign_multivars.go b/internal/lsp/testdata/stub/stub_assign_multivars.go
deleted file mode 100644
index 01b330fda..000000000
--- a/internal/lsp/testdata/stub/stub_assign_multivars.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package stub
-
-import "io"
-
-func main() {
- var br io.ByteWriter
- var i int
- i, br = 1, &multiByteWriter{} //@suggestedfix("&", "refactor.rewrite")
-}
-
-type multiByteWriter struct{}
diff --git a/internal/lsp/testdata/stub/stub_assign_multivars.go.golden b/internal/lsp/testdata/stub/stub_assign_multivars.go.golden
deleted file mode 100644
index e1e71adbd..000000000
--- a/internal/lsp/testdata/stub/stub_assign_multivars.go.golden
+++ /dev/null
@@ -1,18 +0,0 @@
--- suggestedfix_stub_assign_multivars_8_13 --
-package stub
-
-import "io"
-
-func main() {
- var br io.ByteWriter
- var i int
- i, br = 1, &multiByteWriter{} //@suggestedfix("&", "refactor.rewrite")
-}
-
-type multiByteWriter struct{}
-
-// WriteByte implements io.ByteWriter
-func (*multiByteWriter) WriteByte(c byte) error {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/stub/stub_embedded.go b/internal/lsp/testdata/stub/stub_embedded.go
deleted file mode 100644
index 6d6a986bf..000000000
--- a/internal/lsp/testdata/stub/stub_embedded.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package stub
-
-import (
- "io"
- "sort"
-)
-
-var _ embeddedInterface = (*embeddedConcrete)(nil) //@suggestedfix("(", "refactor.rewrite")
-
-type embeddedConcrete struct{}
-
-type embeddedInterface interface {
- sort.Interface
- io.Reader
-}
diff --git a/internal/lsp/testdata/stub/stub_embedded.go.golden b/internal/lsp/testdata/stub/stub_embedded.go.golden
deleted file mode 100644
index c258ebaf4..000000000
--- a/internal/lsp/testdata/stub/stub_embedded.go.golden
+++ /dev/null
@@ -1,37 +0,0 @@
--- suggestedfix_stub_embedded_8_27 --
-package stub
-
-import (
- "io"
- "sort"
-)
-
-var _ embeddedInterface = (*embeddedConcrete)(nil) //@suggestedfix("(", "refactor.rewrite")
-
-type embeddedConcrete struct{}
-
-// Len implements embeddedInterface
-func (*embeddedConcrete) Len() int {
- panic("unimplemented")
-}
-
-// Less implements embeddedInterface
-func (*embeddedConcrete) Less(i int, j int) bool {
- panic("unimplemented")
-}
-
-// Swap implements embeddedInterface
-func (*embeddedConcrete) Swap(i int, j int) {
- panic("unimplemented")
-}
-
-// Read implements embeddedInterface
-func (*embeddedConcrete) Read(p []byte) (n int, err error) {
- panic("unimplemented")
-}
-
-type embeddedInterface interface {
- sort.Interface
- io.Reader
-}
-
diff --git a/internal/lsp/testdata/stub/stub_err.go b/internal/lsp/testdata/stub/stub_err.go
deleted file mode 100644
index 908c7d315..000000000
--- a/internal/lsp/testdata/stub/stub_err.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package stub
-
-func main() {
- var br error = &customErr{} //@suggestedfix("&", "refactor.rewrite")
-}
-
-type customErr struct{}
diff --git a/internal/lsp/testdata/stub/stub_err.go.golden b/internal/lsp/testdata/stub/stub_err.go.golden
deleted file mode 100644
index 717aed862..000000000
--- a/internal/lsp/testdata/stub/stub_err.go.golden
+++ /dev/null
@@ -1,14 +0,0 @@
--- suggestedfix_stub_err_4_17 --
-package stub
-
-func main() {
- var br error = &customErr{} //@suggestedfix("&", "refactor.rewrite")
-}
-
-type customErr struct{}
-
-// Error implements error
-func (*customErr) Error() string {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/stub/stub_function_return.go b/internal/lsp/testdata/stub/stub_function_return.go
deleted file mode 100644
index bbf05885a..000000000
--- a/internal/lsp/testdata/stub/stub_function_return.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package stub
-
-import (
- "io"
-)
-
-func newCloser() io.Closer {
- return closer{} //@suggestedfix("c", "refactor.rewrite")
-}
-
-type closer struct{}
diff --git a/internal/lsp/testdata/stub/stub_function_return.go.golden b/internal/lsp/testdata/stub/stub_function_return.go.golden
deleted file mode 100644
index f80874d2b..000000000
--- a/internal/lsp/testdata/stub/stub_function_return.go.golden
+++ /dev/null
@@ -1,18 +0,0 @@
--- suggestedfix_stub_function_return_8_9 --
-package stub
-
-import (
- "io"
-)
-
-func newCloser() io.Closer {
- return closer{} //@suggestedfix("c", "refactor.rewrite")
-}
-
-type closer struct{}
-
-// Close implements io.Closer
-func (closer) Close() error {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/stub/stub_generic_receiver.go b/internal/lsp/testdata/stub/stub_generic_receiver.go
deleted file mode 100644
index 64e90fcf6..000000000
--- a/internal/lsp/testdata/stub/stub_generic_receiver.go
+++ /dev/null
@@ -1,15 +0,0 @@
-//go:build go1.18
-// +build go1.18
-
-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", "refactor.rewrite")
-
-type genReader[T, Y any] struct {
- T T
- Y Y
-}
diff --git a/internal/lsp/testdata/stub/stub_generic_receiver.go.golden b/internal/lsp/testdata/stub/stub_generic_receiver.go.golden
deleted file mode 100644
index 1fc7157b4..000000000
--- a/internal/lsp/testdata/stub/stub_generic_receiver.go.golden
+++ /dev/null
@@ -1,22 +0,0 @@
--- suggestedfix_stub_generic_receiver_10_23 --
-//go:build go1.18
-// +build go1.18
-
-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", "refactor.rewrite")
-
-type genReader[T, Y any] struct {
- T T
- Y Y
-}
-
-// ReadFrom implements io.ReaderFrom
-func (*genReader[T, Y]) ReadFrom(r io.Reader) (n int64, err error) {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/stub/stub_ignored_imports.go b/internal/lsp/testdata/stub/stub_ignored_imports.go
deleted file mode 100644
index 8f6ec73de..000000000
--- a/internal/lsp/testdata/stub/stub_ignored_imports.go
+++ /dev/null
@@ -1,18 +0,0 @@
-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("(", "refactor.rewrite")
-)
-
-type ignoredResetter struct{}
diff --git a/internal/lsp/testdata/stub/stub_ignored_imports.go.golden b/internal/lsp/testdata/stub/stub_ignored_imports.go.golden
deleted file mode 100644
index a0ddc1793..000000000
--- a/internal/lsp/testdata/stub/stub_ignored_imports.go.golden
+++ /dev/null
@@ -1,26 +0,0 @@
--- suggestedfix_stub_ignored_imports_15_20 --
-package stub
-
-import (
- "compress/zlib"
- "io"
- . "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("(", "refactor.rewrite")
-)
-
-type ignoredResetter struct{}
-
-// Reset implements zlib.Resetter
-func (*ignoredResetter) Reset(r io.Reader, dict []byte) error {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/stub/stub_multi_var.go b/internal/lsp/testdata/stub/stub_multi_var.go
deleted file mode 100644
index 4276b7994..000000000
--- a/internal/lsp/testdata/stub/stub_multi_var.go
+++ /dev/null
@@ -1,11 +0,0 @@
-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("&", "refactor.rewrite")
-
-type multiVar struct{}
diff --git a/internal/lsp/testdata/stub/stub_multi_var.go.golden b/internal/lsp/testdata/stub/stub_multi_var.go.golden
deleted file mode 100644
index b9ac42367..000000000
--- a/internal/lsp/testdata/stub/stub_multi_var.go.golden
+++ /dev/null
@@ -1,18 +0,0 @@
--- suggestedfix_stub_multi_var_9_38 --
-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("&", "refactor.rewrite")
-
-type multiVar struct{}
-
-// Read implements io.Reader
-func (*multiVar) Read(p []byte) (n int, err error) {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/stub/stub_pointer.go b/internal/lsp/testdata/stub/stub_pointer.go
deleted file mode 100644
index 2b3681b83..000000000
--- a/internal/lsp/testdata/stub/stub_pointer.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package stub
-
-import "io"
-
-func getReaderFrom() io.ReaderFrom {
- return &pointerImpl{} //@suggestedfix("&", "refactor.rewrite")
-}
-
-type pointerImpl struct{}
diff --git a/internal/lsp/testdata/stub/stub_pointer.go.golden b/internal/lsp/testdata/stub/stub_pointer.go.golden
deleted file mode 100644
index c4133d7a4..000000000
--- a/internal/lsp/testdata/stub/stub_pointer.go.golden
+++ /dev/null
@@ -1,16 +0,0 @@
--- suggestedfix_stub_pointer_6_9 --
-package stub
-
-import "io"
-
-func getReaderFrom() io.ReaderFrom {
- return &pointerImpl{} //@suggestedfix("&", "refactor.rewrite")
-}
-
-type pointerImpl struct{}
-
-// ReadFrom implements io.ReaderFrom
-func (*pointerImpl) ReadFrom(r io.Reader) (n int64, err error) {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/stub/stub_renamed_import.go b/internal/lsp/testdata/stub/stub_renamed_import.go
deleted file mode 100644
index eaebe2510..000000000
--- a/internal/lsp/testdata/stub/stub_renamed_import.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package stub
-
-import (
- "compress/zlib"
- myio "io"
-)
-
-var _ zlib.Resetter = &myIO{} //@suggestedfix("&", "refactor.rewrite")
-var _ myio.Reader
-
-type myIO struct{}
diff --git a/internal/lsp/testdata/stub/stub_renamed_import.go.golden b/internal/lsp/testdata/stub/stub_renamed_import.go.golden
deleted file mode 100644
index 48ff4f153..000000000
--- a/internal/lsp/testdata/stub/stub_renamed_import.go.golden
+++ /dev/null
@@ -1,18 +0,0 @@
--- suggestedfix_stub_renamed_import_8_23 --
-package stub
-
-import (
- "compress/zlib"
- myio "io"
-)
-
-var _ zlib.Resetter = &myIO{} //@suggestedfix("&", "refactor.rewrite")
-var _ myio.Reader
-
-type myIO struct{}
-
-// Reset implements zlib.Resetter
-func (*myIO) Reset(r myio.Reader, dict []byte) error {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/stub/stub_renamed_import_iface.go b/internal/lsp/testdata/stub/stub_renamed_import_iface.go
deleted file mode 100644
index 96caf540d..000000000
--- a/internal/lsp/testdata/stub/stub_renamed_import_iface.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package stub
-
-import (
- "golang.org/x/tools/internal/lsp/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", "refactor.rewrite")
-
-type otherInterfaceImpl struct{}
diff --git a/internal/lsp/testdata/stub/stub_renamed_import_iface.go.golden b/internal/lsp/testdata/stub/stub_renamed_import_iface.go.golden
deleted file mode 100644
index 9ba2cb440..000000000
--- a/internal/lsp/testdata/stub/stub_renamed_import_iface.go.golden
+++ /dev/null
@@ -1,22 +0,0 @@
--- suggestedfix_stub_renamed_import_iface_11_25 --
-package stub
-
-import (
- "bytes"
- renamed_context "context"
- "golang.org/x/tools/internal/lsp/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", "refactor.rewrite")
-
-type otherInterfaceImpl struct{}
-
-// Get implements other.Interface
-func (*otherInterfaceImpl) Get(renamed_context.Context) *bytes.Buffer {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/stub/stub_stdlib.go b/internal/lsp/testdata/stub/stub_stdlib.go
deleted file mode 100644
index 0d54a6daa..000000000
--- a/internal/lsp/testdata/stub/stub_stdlib.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package stub
-
-import (
- "io"
-)
-
-var _ io.Writer = writer{} //@suggestedfix("w", "refactor.rewrite")
-
-type writer struct{}
diff --git a/internal/lsp/testdata/stub/stub_stdlib.go.golden b/internal/lsp/testdata/stub/stub_stdlib.go.golden
deleted file mode 100644
index 8636cead4..000000000
--- a/internal/lsp/testdata/stub/stub_stdlib.go.golden
+++ /dev/null
@@ -1,16 +0,0 @@
--- suggestedfix_stub_stdlib_7_19 --
-package stub
-
-import (
- "io"
-)
-
-var _ io.Writer = writer{} //@suggestedfix("w", "refactor.rewrite")
-
-type writer struct{}
-
-// Write implements io.Writer
-func (writer) Write(p []byte) (n int, err error) {
- panic("unimplemented")
-}
-
diff --git a/internal/lsp/testdata/suggestedfix/has_suggested_fix.go b/internal/lsp/testdata/suggestedfix/has_suggested_fix.go
deleted file mode 100644
index e06dce0a8..000000000
--- a/internal/lsp/testdata/suggestedfix/has_suggested_fix.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package suggestedfix
-
-import (
- "log"
-)
-
-func goodbye() {
- s := "hiiiiiii"
- s = s //@suggestedfix("s = s", "quickfix")
- log.Print(s)
-}
diff --git a/internal/lsp/testdata/suggestedfix/has_suggested_fix.go.golden b/internal/lsp/testdata/suggestedfix/has_suggested_fix.go.golden
deleted file mode 100644
index 9ccaa1994..000000000
--- a/internal/lsp/testdata/suggestedfix/has_suggested_fix.go.golden
+++ /dev/null
@@ -1,13 +0,0 @@
--- suggestedfix_has_suggested_fix_9_2 --
-package suggestedfix
-
-import (
- "log"
-)
-
-func goodbye() {
- s := "hiiiiiii"
- //@suggestedfix("s = s", "quickfix")
- log.Print(s)
-}
-
diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden
deleted file mode 100644
index 29493920f..000000000
--- a/internal/lsp/testdata/summary.txt.golden
+++ /dev/null
@@ -1,30 +0,0 @@
--- summary --
-CallHierarchyCount = 2
-CodeLensCount = 5
-CompletionsCount = 265
-CompletionSnippetCount = 106
-UnimportedCompletionsCount = 5
-DeepCompletionsCount = 5
-FuzzyCompletionsCount = 8
-RankedCompletionsCount = 163
-CaseSensitiveCompletionsCount = 4
-DiagnosticsCount = 37
-FoldingRangesCount = 2
-FormatCount = 6
-ImportCount = 8
-SemanticTokenCount = 3
-SuggestedFixCount = 61
-FunctionExtractionCount = 25
-MethodExtractionCount = 6
-DefinitionsCount = 95
-TypeDefinitionsCount = 18
-HighlightsCount = 69
-ReferencesCount = 27
-RenamesCount = 41
-PrepareRenamesCount = 7
-SymbolsCount = 5
-WorkspaceSymbolsCount = 20
-SignaturesCount = 33
-LinksCount = 7
-ImplementationsCount = 14
-
diff --git a/internal/lsp/testdata/summary_go1.18.txt.golden b/internal/lsp/testdata/summary_go1.18.txt.golden
deleted file mode 100644
index 48639899e..000000000
--- a/internal/lsp/testdata/summary_go1.18.txt.golden
+++ /dev/null
@@ -1,30 +0,0 @@
--- summary --
-CallHierarchyCount = 2
-CodeLensCount = 5
-CompletionsCount = 266
-CompletionSnippetCount = 110
-UnimportedCompletionsCount = 5
-DeepCompletionsCount = 5
-FuzzyCompletionsCount = 8
-RankedCompletionsCount = 169
-CaseSensitiveCompletionsCount = 4
-DiagnosticsCount = 37
-FoldingRangesCount = 2
-FormatCount = 6
-ImportCount = 8
-SemanticTokenCount = 3
-SuggestedFixCount = 62
-FunctionExtractionCount = 25
-MethodExtractionCount = 6
-DefinitionsCount = 108
-TypeDefinitionsCount = 18
-HighlightsCount = 69
-ReferencesCount = 27
-RenamesCount = 48
-PrepareRenamesCount = 7
-SymbolsCount = 5
-WorkspaceSymbolsCount = 20
-SignaturesCount = 33
-LinksCount = 7
-ImplementationsCount = 14
-
diff --git a/internal/lsp/testdata/symbols/main.go b/internal/lsp/testdata/symbols/main.go
deleted file mode 100644
index 8111250f3..000000000
--- a/internal/lsp/testdata/symbols/main.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package main
-
-import (
- "io"
-)
-
-var _ = 1
-
-var x = 42 //@mark(symbolsx, "x"), symbol("x", "x", "Variable", "", "main.x")
-
-const y = 43 //@symbol("y", "y", "Constant", "", "main.y")
-
-type Number int //@symbol("Number", "Number", "Number", "", "main.Number")
-
-type Alias = string //@symbol("Alias", "Alias", "String", "", "main.Alias")
-
-type NumberAlias = Number //@symbol("NumberAlias", "NumberAlias", "Number", "", "main.NumberAlias")
-
-type (
- Boolean bool //@symbol("Boolean", "Boolean", "Boolean", "", "main.Boolean")
- BoolAlias = bool //@symbol("BoolAlias", "BoolAlias", "Boolean", "", "main.BoolAlias")
-)
-
-type Foo struct { //@mark(symbolsFoo, "Foo"), symbol("Foo", "Foo", "Struct", "", "main.Foo")
- Quux //@mark(fQuux, "Quux"), symbol("Quux", "Quux", "Field", "Foo", "main.Foo.Quux")
- W io.Writer //@symbol("W" , "W", "Field", "Foo", "main.Foo.W")
- Bar int //@mark(fBar, "Bar"), symbol("Bar", "Bar", "Field", "Foo", "main.Foo.Bar")
- baz string //@symbol("baz", "baz", "Field", "Foo", "main.Foo.baz")
-}
-
-type Quux struct { //@symbol("Quux", "Quux", "Struct", "", "main.Quux")
- X, Y float64 //@mark(qX, "X"), symbol("X", "X", "Field", "Quux", "main.X"), symbol("Y", "Y", "Field", "Quux", "main.Y")
-}
-
-func (f Foo) Baz() string { //@symbol("(Foo).Baz", "Baz", "Method", "", "main.Foo.Baz")
- return f.baz
-}
-
-func _() {}
-
-func (q *Quux) Do() {} //@mark(qDo, "Do"), symbol("(*Quux).Do", "Do", "Method", "", "main.Quux.Do")
-
-func main() { //@symbol("main", "main", "Function", "", "main.main")
-
-}
-
-type Stringer interface { //@symbol("Stringer", "Stringer", "Interface", "", "main.Stringer")
- String() string //@symbol("String", "String", "Method", "Stringer", "main.Stringer.String")
-}
-
-type ABer interface { //@mark(ABerInterface, "ABer"), symbol("ABer", "ABer", "Interface", "", "main.ABer")
- B() //@symbol("B", "B", "Method", "ABer", "main.ABer.B")
- A() string //@mark(ABerA, "A"), symbol("A", "A", "Method", "ABer", "main.ABer.A")
-}
-
-type WithEmbeddeds interface { //@symbol("WithEmbeddeds", "WithEmbeddeds", "Interface", "", "main.WithEmbeddeds")
- Do() //@symbol("Do", "Do", "Method", "WithEmbeddeds", "main.WithEmbeddeds.Do")
- ABer //@symbol("ABer", "ABer", "Interface", "WithEmbeddeds", "main.WithEmbeddeds.ABer")
- io.Writer //@mark(ioWriter, "io.Writer"), symbol("io.Writer", "io.Writer", "Interface", "WithEmbeddeds", "main.WithEmbeddeds.Writer")
-}
-
-func Dunk() int { return 0 } //@symbol("Dunk", "Dunk", "Function", "", "main.Dunk")
-
-func dunk() {} //@symbol("dunk", "dunk", "Function", "", "main.dunk")
diff --git a/internal/lsp/testdata/symbols/main.go.golden b/internal/lsp/testdata/symbols/main.go.golden
deleted file mode 100644
index ebb6a8a5d..000000000
--- a/internal/lsp/testdata/symbols/main.go.golden
+++ /dev/null
@@ -1,31 +0,0 @@
--- symbols --
-x Variable 9:5-9:6
-y Constant 11:7-11:8
-Number Number 13:6-13:12
-Alias String 15:6-15:11
-NumberAlias Number 17:6-17:17
-Boolean Boolean 20:2-20:9
-BoolAlias Boolean 21:2-21:11
-Foo Struct 24:6-24:9
- Bar Field 27:2-27:5
- Quux Field 25:2-25:6
- W Field 26:2-26:3
- baz Field 28:2-28:5
-Quux Struct 31:6-31:10
- X Field 32:2-32:3
- Y Field 32:5-32:6
-(Foo).Baz Method 35:14-35:17
-(*Quux).Do Method 41:16-41:18
-main Function 43:6-43:10
-Stringer Interface 47:6-47:14
- String Method 48:2-48:8
-ABer Interface 51:6-51:10
- A Method 53:2-53:3
- B Method 52:2-52:3
-WithEmbeddeds Interface 56:6-56:19
- ABer Interface 58:2-58:6
- Do Method 57:2-57:4
- io.Writer Interface 59:2-59:11
-Dunk Function 62:6-62:10
-dunk Function 64:6-64:10
-
diff --git a/internal/lsp/testdata/testy/testy.go b/internal/lsp/testdata/testy/testy.go
deleted file mode 100644
index 1a738d7d7..000000000
--- a/internal/lsp/testdata/testy/testy.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package testy
-
-func a() { //@mark(identA, "a"),item(funcA, "a", "func()", "func"),refs("a", identA, testyA)
- //@complete("", funcA)
-}
diff --git a/internal/lsp/testdata/testy/testy_test.go b/internal/lsp/testdata/testy/testy_test.go
deleted file mode 100644
index 4939f86b5..000000000
--- a/internal/lsp/testdata/testy/testy_test.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package testy
-
-import (
- "testing"
-
- sig "golang.org/x/tools/internal/lsp/signature"
- "golang.org/x/tools/internal/lsp/snippets"
-)
-
-func TestSomething(t *testing.T) { //@item(TestSomething, "TestSomething(t *testing.T)", "", "func")
- var x int //@mark(testyX, "x"),diag("x", "compiler", "x declared but not used", "error"),refs("x", testyX)
- a() //@mark(testyA, "a")
-}
-
-func _() {
- _ = snippets.X(nil) //@signature("nil", "X(_ map[sig.Alias]types.CoolAlias) map[sig.Alias]types.CoolAlias", 0)
- var _ sig.Alias
-}
diff --git a/internal/lsp/testdata/testy/testy_test.go.golden b/internal/lsp/testdata/testy/testy_test.go.golden
deleted file mode 100644
index cafc380d0..000000000
--- a/internal/lsp/testdata/testy/testy_test.go.golden
+++ /dev/null
@@ -1,3 +0,0 @@
--- X(_ map[sig.Alias]types.CoolAlias) map[sig.Alias]types.CoolAlias-signature --
-X(_ map[sig.Alias]types.CoolAlias) map[sig.Alias]types.CoolAlias
-
diff --git a/internal/lsp/testdata/typdef/typdef.go b/internal/lsp/testdata/typdef/typdef.go
deleted file mode 100644
index bd2ea4b00..000000000
--- a/internal/lsp/testdata/typdef/typdef.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package typdef
-
-type Struct struct { //@item(Struct, "Struct", "struct{...}", "struct")
- Field string
-}
-
-type Int int //@item(Int, "Int", "int", "type")
-
-func _() {
- var (
- value Struct
- point *Struct
- )
- _ = value //@typdef("value", Struct)
- _ = point //@typdef("point", Struct)
-
- var (
- array [3]Struct
- slice []Struct
- ch chan Struct
- complex [3]chan *[5][]Int
- )
- _ = array //@typdef("array", Struct)
- _ = slice //@typdef("slice", Struct)
- _ = ch //@typdef("ch", Struct)
- _ = complex //@typdef("complex", Int)
-
- var s struct {
- x struct {
- xx struct {
- field1 []Struct
- field2 []Int
- }
- }
- }
- s.x.xx.field1 //@typdef("field1", Struct)
- s.x.xx.field2 //@typdef("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, Struct{}, 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() //@typdef("F1", Int)
- F2() //@typdef("F2", Int)
- F3() //@typdef("F3", Struct)
- F4() //@typdef("F4", Int)
- F5() //@typdef("F5", Struct)
- F6() //@typdef("F6", Struct)
-
- f := func() Int { return 0 }
- f() //@typdef("f", Int)
-}
-
-// https://github.com/golang/go/issues/38589#issuecomment-620350922
-func _() {
- type myFunc func(int) Int //@item(myFunc, "myFunc", "func", "type")
-
- var foo myFunc
- bar := foo() //@typdef("foo", myFunc)
-}
diff --git a/internal/lsp/testdata/typeassert/type_assert.go b/internal/lsp/testdata/typeassert/type_assert.go
deleted file mode 100644
index e24b68a07..000000000
--- a/internal/lsp/testdata/typeassert/type_assert.go
+++ /dev/null
@@ -1,24 +0,0 @@
-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/internal/lsp/testdata/typeerrors/noresultvalues.go b/internal/lsp/testdata/typeerrors/noresultvalues.go
deleted file mode 100644
index 84234c4b9..000000000
--- a/internal/lsp/testdata/typeerrors/noresultvalues.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package typeerrors
-
-func x() { return nil } //@suggestedfix("nil", "quickfix")
-
-func y() { return nil, "hello" } //@suggestedfix("nil", "quickfix")
diff --git a/internal/lsp/testdata/typeerrors/noresultvalues.go.golden b/internal/lsp/testdata/typeerrors/noresultvalues.go.golden
deleted file mode 100644
index 07c54d445..000000000
--- a/internal/lsp/testdata/typeerrors/noresultvalues.go.golden
+++ /dev/null
@@ -1,14 +0,0 @@
--- suggestedfix_noresultvalues_3_19 --
-package typeerrors
-
-func x() { return } //@suggestedfix("nil", "quickfix")
-
-func y() { return nil, "hello" } //@suggestedfix("nil", "quickfix")
-
--- suggestedfix_noresultvalues_5_19 --
-package typeerrors
-
-func x() { return nil } //@suggestedfix("nil", "quickfix")
-
-func y() { return } //@suggestedfix("nil", "quickfix")
-
diff --git a/internal/lsp/testdata/typemods/type_mods.go b/internal/lsp/testdata/typemods/type_mods.go
deleted file mode 100644
index f5f0f8076..000000000
--- a/internal/lsp/testdata/typemods/type_mods.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package typemods
-
-func fooFunc() func() int { //@item(modFooFunc, "fooFunc", "func() func() int", "func")
- return func() int {
- return 0
- }
-}
-
-func fooPtr() *int { //@item(modFooPtr, "fooPtr", "func() *int", "func")
- return nil
-}
-
-func _() {
- var _ int = foo //@snippet(" //", modFooFunc, "fooFunc()()", "fooFunc()()"),snippet(" //", modFooPtr, "*fooPtr()", "*fooPtr()")
-}
-
-func _() {
- var m map[int][]chan int //@item(modMapChanPtr, "m", "map[int]chan *int", "var")
-
- var _ int = m //@snippet(" //", modMapChanPtr, "<-m[${1:}][${2:}]", "<-m[${1:}][${2:}]")
-}
diff --git a/internal/lsp/testdata/typeparams/type_params.go b/internal/lsp/testdata/typeparams/type_params.go
deleted file mode 100644
index 1dfb1034a..000000000
--- a/internal/lsp/testdata/typeparams/type_params.go
+++ /dev/null
@@ -1,33 +0,0 @@
-//go:build go1.18
-// +build go1.18
-
-package typeparams
-
-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 returnTP[A int | float64](a A) A { //@item(returnTP, "returnTP", "something", "func")
- return a
-}
-
-func _() {
- var _ int = returnTP //@snippet(" //", returnTP, "returnTP[${1:}](${2:})", "returnTP[${1:A int|float64}](${2:a A})")
-}
diff --git a/internal/lsp/testdata/types/types.go b/internal/lsp/testdata/types/types.go
deleted file mode 100644
index c60d4b2e4..000000000
--- a/internal/lsp/testdata/types/types.go
+++ /dev/null
@@ -1,18 +0,0 @@
-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() {}
diff --git a/internal/lsp/testdata/undeclared/var.go b/internal/lsp/testdata/undeclared/var.go
deleted file mode 100644
index b5f9287d4..000000000
--- a/internal/lsp/testdata/undeclared/var.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package undeclared
-
-func m() int {
- z, _ := 1+y, 11 //@diag("y", "compiler", "undeclared name: y", "error"),suggestedfix("y", "quickfix")
- if 100 < 90 {
- z = 1
- } else if 100 > n+2 { //@diag("n", "compiler", "undeclared name: n", "error"),suggestedfix("n", "quickfix")
- z = 4
- }
- for i < 200 { //@diag("i", "compiler", "undeclared name: i", "error"),suggestedfix("i", "quickfix")
- }
- r() //@diag("r", "compiler", "undeclared name: r", "error")
- return z
-}
diff --git a/internal/lsp/testdata/undeclared/var.go.golden b/internal/lsp/testdata/undeclared/var.go.golden
deleted file mode 100644
index 74adbe8ff..000000000
--- a/internal/lsp/testdata/undeclared/var.go.golden
+++ /dev/null
@@ -1,51 +0,0 @@
--- suggestedfix_var_10_6 --
-package undeclared
-
-func m() int {
- z, _ := 1+y, 11 //@diag("y", "compiler", "undeclared name: y", "error"),suggestedfix("y", "quickfix")
- if 100 < 90 {
- z = 1
- } else if 100 > n+2 { //@diag("n", "compiler", "undeclared name: n", "error"),suggestedfix("n", "quickfix")
- z = 4
- }
- i :=
- for i < 200 { //@diag("i", "compiler", "undeclared name: i", "error"),suggestedfix("i", "quickfix")
- }
- r() //@diag("r", "compiler", "undeclared name: r", "error")
- return z
-}
-
--- suggestedfix_var_4_12 --
-package undeclared
-
-func m() int {
- y :=
- z, _ := 1+y, 11 //@diag("y", "compiler", "undeclared name: y", "error"),suggestedfix("y", "quickfix")
- if 100 < 90 {
- z = 1
- } else if 100 > n+2 { //@diag("n", "compiler", "undeclared name: n", "error"),suggestedfix("n", "quickfix")
- z = 4
- }
- for i < 200 { //@diag("i", "compiler", "undeclared name: i", "error"),suggestedfix("i", "quickfix")
- }
- r() //@diag("r", "compiler", "undeclared name: r", "error")
- return z
-}
-
--- suggestedfix_var_7_18 --
-package undeclared
-
-func m() int {
- z, _ := 1+y, 11 //@diag("y", "compiler", "undeclared name: y", "error"),suggestedfix("y", "quickfix")
- n :=
- if 100 < 90 {
- z = 1
- } else if 100 > n+2 { //@diag("n", "compiler", "undeclared name: n", "error"),suggestedfix("n", "quickfix")
- z = 4
- }
- for i < 200 { //@diag("i", "compiler", "undeclared name: i", "error"),suggestedfix("i", "quickfix")
- }
- r() //@diag("r", "compiler", "undeclared name: r", "error")
- return z
-}
-
diff --git a/internal/lsp/testdata/unimported/export_test.go b/internal/lsp/testdata/unimported/export_test.go
deleted file mode 100644
index 4f85700fa..000000000
--- a/internal/lsp/testdata/unimported/export_test.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package unimported
-
-var TestExport int //@item(testexport, "TestExport", "(from \"golang.org/x/tools/internal/lsp/unimported\")", "var")
diff --git a/internal/lsp/testdata/unimported/unimported.go.in b/internal/lsp/testdata/unimported/unimported.go.in
deleted file mode 100644
index c3c0243d9..000000000
--- a/internal/lsp/testdata/unimported/unimported.go.in
+++ /dev/null
@@ -1,20 +0,0 @@
-package unimported
-
-func _() {
- http //@unimported("p", nethttp)
- // container/ring is extremely unlikely to be imported by anything, so shouldn't have type information.
- ring.Ring //@unimported("Ring", ringring)
- signature.Foo //@unimported("Foo", signaturefoo)
-
- context.Bac //@unimported(" //", contextBackground, contextBackgroundErr)
-}
-
-// Create markers for unimported std lib packages. Only for use by this test.
-/* http */ //@item(nethttp, "http", "\"net/http\"", "package")
-
-/* ring.Ring */ //@item(ringring, "Ring", "(from \"container/ring\")", "var")
-
-/* signature.Foo */ //@item(signaturefoo, "Foo", "func(a string, b int) (c bool) (from \"golang.org/x/tools/internal/lsp/signature\")", "func")
-
-/* context.Background */ //@item(contextBackground, "Background", "func() context.Context (from \"context\")", "func")
-/* context.Background().Err */ //@item(contextBackgroundErr, "Background().Err", "func() error (from \"context\")", "method")
diff --git a/internal/lsp/testdata/unimported/unimported_cand_type.go b/internal/lsp/testdata/unimported/unimported_cand_type.go
deleted file mode 100644
index 531aa2d18..000000000
--- a/internal/lsp/testdata/unimported/unimported_cand_type.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package unimported
-
-import (
- _ "context"
-
- "golang.org/x/tools/internal/lsp/baz"
- _ "golang.org/x/tools/internal/lsp/signature" // provide type information for unimported completions in the other file
-)
-
-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\\}", "foo.StructFoo{$0\\}")
-}
diff --git a/internal/lsp/testdata/unimported/x_test.go b/internal/lsp/testdata/unimported/x_test.go
deleted file mode 100644
index 681dcb253..000000000
--- a/internal/lsp/testdata/unimported/x_test.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package unimported_test
-
-import (
- "testing"
-)
-
-func TestSomething(t *testing.T) {
- _ = unimported.TestExport //@unimported("TestExport", testexport)
-}
diff --git a/internal/lsp/testdata/unresolved/unresolved.go.in b/internal/lsp/testdata/unresolved/unresolved.go.in
deleted file mode 100644
index e1daecc2e..000000000
--- a/internal/lsp/testdata/unresolved/unresolved.go.in
+++ /dev/null
@@ -1,6 +0,0 @@
-package unresolved
-
-func foo(interface{}) {
- // don't crash on fake "resolved" type
- foo(func(i, j f //@complete(" //")
-}
diff --git a/internal/lsp/testdata/unsafe/unsafe.go b/internal/lsp/testdata/unsafe/unsafe.go
deleted file mode 100644
index 5d5e43407..000000000
--- a/internal/lsp/testdata/unsafe/unsafe.go
+++ /dev/null
@@ -1,13 +0,0 @@
-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/internal/lsp/testdata/variadic/variadic.go.in b/internal/lsp/testdata/variadic/variadic.go.in
deleted file mode 100644
index 4787498ce..000000000
--- a/internal/lsp/testdata/variadic/variadic.go.in
+++ /dev/null
@@ -1,38 +0,0 @@
-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...", "ss..."),snippet(")", vFunc, "bar()...", "bar()..."),snippet(")", vStr, "s", "s")
-
- // don't add "..." for interface{}
- foo(123, ) //@snippet(")", vIntf, "v", "v")
-}
-
-func qux(...func()) {}
-func f() {} //@item(vVarArg, "f", "func()", "func")
-
-func _() {
- qux(f) //@snippet(")", vVarArg, "f", "f")
-}
-
-func _() {
- foo(0, []string{}...) //@complete(")")
-}
diff --git a/internal/lsp/testdata/variadic/variadic_intf.go b/internal/lsp/testdata/variadic/variadic_intf.go
deleted file mode 100644
index 6e23fc996..000000000
--- a/internal/lsp/testdata/variadic/variadic_intf.go
+++ /dev/null
@@ -1,21 +0,0 @@
-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/internal/lsp/testdata/workspacesymbol/a/a.go b/internal/lsp/testdata/workspacesymbol/a/a.go
deleted file mode 100644
index 6e5a68b16..000000000
--- a/internal/lsp/testdata/workspacesymbol/a/a.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package a
-
-var RandomGopherVariableA = "a" //@symbol("RandomGopherVariableA", "RandomGopherVariableA", "Variable", "", "a.RandomGopherVariableA")
-
-const RandomGopherConstantA = "a" //@symbol("RandomGopherConstantA", "RandomGopherConstantA", "Constant", "", "a.RandomGopherConstantA")
-
-const (
- randomgopherinvariable = iota //@symbol("randomgopherinvariable", "randomgopherinvariable", "Constant", "", "a.randomgopherinvariable")
-)
diff --git a/internal/lsp/testdata/workspacesymbol/a/a.go.golden b/internal/lsp/testdata/workspacesymbol/a/a.go.golden
deleted file mode 100644
index c3f088577..000000000
--- a/internal/lsp/testdata/workspacesymbol/a/a.go.golden
+++ /dev/null
@@ -1,5 +0,0 @@
--- symbols --
-RandomGopherVariableA Variable 3:5-3:26
-RandomGopherConstantA Constant 5:7-5:28
-randomgopherinvariable Constant 8:2-8:24
-
diff --git a/internal/lsp/testdata/workspacesymbol/a/a_test.go b/internal/lsp/testdata/workspacesymbol/a/a_test.go
deleted file mode 100644
index 30d534097..000000000
--- a/internal/lsp/testdata/workspacesymbol/a/a_test.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package a
-
-var RandomGopherTestVariableA = "a" //@symbol("RandomGopherTestVariableA", "RandomGopherTestVariableA", "Variable", "", "a.RandomGopherTestVariableA")
diff --git a/internal/lsp/testdata/workspacesymbol/a/a_test.go.golden b/internal/lsp/testdata/workspacesymbol/a/a_test.go.golden
deleted file mode 100644
index af7461943..000000000
--- a/internal/lsp/testdata/workspacesymbol/a/a_test.go.golden
+++ /dev/null
@@ -1,3 +0,0 @@
--- symbols --
-RandomGopherTestVariableA Variable 3:5-3:30
-
diff --git a/internal/lsp/testdata/workspacesymbol/a/a_x_test.go b/internal/lsp/testdata/workspacesymbol/a/a_x_test.go
deleted file mode 100644
index 76eb8487d..000000000
--- a/internal/lsp/testdata/workspacesymbol/a/a_x_test.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package a_test
-
-var RandomGopherXTestVariableA = "a" //@symbol("RandomGopherXTestVariableA", "RandomGopherXTestVariableA", "Variable", "", "a_test.RandomGopherXTestVariableA")
diff --git a/internal/lsp/testdata/workspacesymbol/a/a_x_test.go.golden b/internal/lsp/testdata/workspacesymbol/a/a_x_test.go.golden
deleted file mode 100644
index dfd02a5c4..000000000
--- a/internal/lsp/testdata/workspacesymbol/a/a_x_test.go.golden
+++ /dev/null
@@ -1,3 +0,0 @@
--- symbols --
-RandomGopherXTestVariableA Variable 3:5-3:31
-
diff --git a/internal/lsp/testdata/workspacesymbol/b/b.go b/internal/lsp/testdata/workspacesymbol/b/b.go
deleted file mode 100644
index 89ce0d92e..000000000
--- a/internal/lsp/testdata/workspacesymbol/b/b.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package b
-
-var RandomGopherVariableB = "b" //@symbol("RandomGopherVariableB", "RandomGopherVariableB", "Variable", "", "b.RandomGopherVariableB")
-
-type RandomGopherStructB struct { //@symbol("RandomGopherStructB", "RandomGopherStructB", "Struct", "", "b.RandomGopherStructB")
- Bar int //@mark(bBar, "Bar"), symbol("Bar", "Bar", "Field", "RandomGopherStructB", "b.RandomGopherStructB.Bar")
-}
diff --git a/internal/lsp/testdata/workspacesymbol/b/b.go.golden b/internal/lsp/testdata/workspacesymbol/b/b.go.golden
deleted file mode 100644
index 4711c9d91..000000000
--- a/internal/lsp/testdata/workspacesymbol/b/b.go.golden
+++ /dev/null
@@ -1,5 +0,0 @@
--- symbols --
-RandomGopherVariableB Variable 3:5-3:26
-RandomGopherStructB Struct 5:6-5:25
- Bar Field 6:2-6:5
-
diff --git a/internal/lsp/testdata/workspacesymbol/issue44806.go b/internal/lsp/testdata/workspacesymbol/issue44806.go
deleted file mode 100644
index 6a6e03a5f..000000000
--- a/internal/lsp/testdata/workspacesymbol/issue44806.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package main
-
-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() {}
diff --git a/internal/lsp/testdata/workspacesymbol/main.go b/internal/lsp/testdata/workspacesymbol/main.go
deleted file mode 100644
index 36ec8f1a5..000000000
--- a/internal/lsp/testdata/workspacesymbol/main.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package main
-
-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() {}
diff --git a/internal/lsp/testdata/workspacesymbol/p/p.go b/internal/lsp/testdata/workspacesymbol/p/p.go
deleted file mode 100644
index 409cc3547..000000000
--- a/internal/lsp/testdata/workspacesymbol/p/p.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package p
-
-const Message = "Hello World." // constant
diff --git a/internal/lsp/testdata/workspacesymbol/query.go b/internal/lsp/testdata/workspacesymbol/query.go
deleted file mode 100644
index 883aae268..000000000
--- a/internal/lsp/testdata/workspacesymbol/query.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package main
-
-// Contains all of the workspace symbol queries.
-
-// -- Fuzzy matching --
-//@workspacesymbolfuzzy("rgop")
-//@workspacesymbolfuzzy("randoma")
-//@workspacesymbolfuzzy("randomb")
-
-// -- Case sensitive --
-//@workspacesymbolcasesensitive("main.main")
-//@workspacesymbolcasesensitive("p.Message")
-//@workspacesymbolcasesensitive("main.myvar")
-//@workspacesymbolcasesensitive("main.myType")
-//@workspacesymbolcasesensitive("main.myType.Blahblah")
-//@workspacesymbolcasesensitive("main.myStruct")
-//@workspacesymbolcasesensitive("main.myStruct.myStructField")
-//@workspacesymbolcasesensitive("main.myInterface")
-//@workspacesymbolcasesensitive("main.myInterface.DoSomeCoolStuff")
-//@workspacesymbolcasesensitive("main.embed.myStruct")
-//@workspacesymbolcasesensitive("main.embed.nestedStruct.nestedStruct2.int")
-//@workspacesymbolcasesensitive("main.embed.nestedInterface.myInterface")
-//@workspacesymbolcasesensitive("main.embed.nestedInterface.nestedMethod")
-//@workspacesymbolcasesensitive("dunk")
-//@workspacesymbolcasesensitive("Dunk")
-
-// -- Standard --
-//@workspacesymbol("")
-//@workspacesymbol("randomgophervar")
diff --git a/internal/lsp/testdata/workspacesymbol/query.go.golden b/internal/lsp/testdata/workspacesymbol/query.go.golden
deleted file mode 100644
index 4c6d470f7..000000000
--- a/internal/lsp/testdata/workspacesymbol/query.go.golden
+++ /dev/null
@@ -1,83 +0,0 @@
--- workspace_symbol-caseinsensitive- --
-
-
--- workspace_symbol-caseinsensitive-randomgophervar --
-workspacesymbol/a/a.go:3:5-26 RandomGopherVariableA Variable
-workspacesymbol/b/b.go:3:5-26 RandomGopherVariableB Variable
-
--- workspace_symbol-casesensitive-Dunk --
-workspacesymbol/main.go:45:6-10 Dunk Function
-
--- workspace_symbol-casesensitive-dunk --
-workspacesymbol/main.go:47:6-10 dunk Function
-
--- workspace_symbol-casesensitive-main.embed.myStruct --
-workspacesymbol/main.go:29:2-10 main.embed.myStruct Field
-
--- workspace_symbol-casesensitive-main.embed.nestedInterface.myInterface --
-workspacesymbol/main.go:40:3-14 main.embed.nestedInterface.myInterface Interface
-
--- workspace_symbol-casesensitive-main.embed.nestedInterface.nestedMethod --
-workspacesymbol/main.go:41:3-15 main.embed.nestedInterface.nestedMethod Method
-
--- workspace_symbol-casesensitive-main.embed.nestedStruct.nestedStruct2.int --
-workspacesymbol/main.go:35:4-7 main.embed.nestedStruct.nestedStruct2.int Field
-
--- workspace_symbol-casesensitive-main.main --
-workspacesymbol/main.go:8:6-10 main.main Function
-
--- workspace_symbol-casesensitive-main.myInterface --
-workspacesymbol/main.go:24:6-17 main.myInterface Interface
-workspacesymbol/main.go:25:2-17 main.myInterface.DoSomeCoolStuff Method
-
--- workspace_symbol-casesensitive-main.myInterface.DoSomeCoolStuff --
-workspacesymbol/main.go:25:2-17 main.myInterface.DoSomeCoolStuff Method
-
--- workspace_symbol-casesensitive-main.myStruct --
-workspacesymbol/main.go:20:6-14 main.myStruct Struct
-workspacesymbol/main.go:21:2-15 main.myStruct.myStructField Field
-
--- workspace_symbol-casesensitive-main.myStruct.myStructField --
-workspacesymbol/main.go:21:2-15 main.myStruct.myStructField Field
-
--- workspace_symbol-casesensitive-main.myType --
-workspacesymbol/main.go:14:6-12 main.myType Class
-workspacesymbol/main.go:18:18-26 main.myType.Blahblah Method
-
--- workspace_symbol-casesensitive-main.myType.Blahblah --
-workspacesymbol/main.go:18:18-26 main.myType.Blahblah Method
-
--- workspace_symbol-casesensitive-main.myvar --
-workspacesymbol/main.go:12:5-10 main.myvar Variable
-
--- workspace_symbol-casesensitive-p.Message --
-workspacesymbol/p/p.go:3:7-14 p.Message Constant
-
--- workspace_symbol-fuzzy-randoma --
-workspacesymbol/a/a.go:3:5-26 RandomGopherVariableA Variable
-workspacesymbol/a/a.go:5:7-28 RandomGopherConstantA Constant
-workspacesymbol/a/a.go:8:2-24 randomgopherinvariable Constant
-workspacesymbol/a/a_test.go:3:5-30 RandomGopherTestVariableA Variable
-workspacesymbol/a/a_x_test.go:3:5-31 RandomGopherXTestVariableA Variable
-workspacesymbol/b/b.go:3:5-26 RandomGopherVariableB Variable
-workspacesymbol/b/b.go:6:2-5 RandomGopherStructB.Bar Field
-
--- workspace_symbol-fuzzy-randomb --
-workspacesymbol/a/a.go:3:5-26 RandomGopherVariableA Variable
-workspacesymbol/a/a.go:8:2-24 randomgopherinvariable Constant
-workspacesymbol/a/a_test.go:3:5-30 RandomGopherTestVariableA Variable
-workspacesymbol/a/a_x_test.go:3:5-31 RandomGopherXTestVariableA Variable
-workspacesymbol/b/b.go:3:5-26 RandomGopherVariableB Variable
-workspacesymbol/b/b.go:5:6-25 RandomGopherStructB Struct
-workspacesymbol/b/b.go:6:2-5 RandomGopherStructB.Bar Field
-
--- workspace_symbol-fuzzy-rgop --
-workspacesymbol/a/a.go:3:5-26 RandomGopherVariableA Variable
-workspacesymbol/a/a.go:5:7-28 RandomGopherConstantA Constant
-workspacesymbol/a/a.go:8:2-24 randomgopherinvariable Constant
-workspacesymbol/a/a_test.go:3:5-30 RandomGopherTestVariableA Variable
-workspacesymbol/a/a_x_test.go:3:5-31 RandomGopherXTestVariableA Variable
-workspacesymbol/b/b.go:3:5-26 RandomGopherVariableB Variable
-workspacesymbol/b/b.go:5:6-25 RandomGopherStructB Struct
-workspacesymbol/b/b.go:6:2-5 RandomGopherStructB.Bar Field
-
diff --git a/internal/lsp/tests/README.md b/internal/lsp/tests/README.md
deleted file mode 100644
index 2c18675f7..000000000
--- a/internal/lsp/tests/README.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Testing
-
-LSP has "marker tests" defined in `internal/lsp/testdata`, as well as
-traditional tests.
-
-## Marker tests
-
-Marker tests have a standard input file, like
-`internal/lsp/testdata/foo/bar.go`, and some may have a corresponding golden
-file, like `internal/lsp/testdata/foo/bar.go.golden`. The former is the "input"
-and the latter is the expected output.
-
-Each input file contains annotations like
-`//@suggestedfix("}", "refactor.rewrite")`. These annotations are interpreted by
-test runners to perform certain actions. The expected output after those actions
-is encoded in the golden file.
-
-When tests are run, each annotation results in a new subtest, which is encoded
-in the golden file with a heading like,
-
-```bash
--- suggestedfix_bar_11_21 --
-// expected contents go here
--- suggestedfix_bar_13_20 --
-// expected contents go here
-```
-
-The format of these headings vary: they are defined by the
-[`Golden`](https://pkg.go.dev/golang.org/x/tools/internal/lsp/tests#Data.Golden)
-function for each annotation. In the case above, the format is: annotation
-name, file name, annotation line location, annotation character location.
-
-So, if `internal/lsp/testdata/foo/bar.go` has three `suggestedfix` annotations,
-the golden file should have three headers with `suggestedfix_bar_xx_yy`
-headings.
-
-To see a list of all available annotations, see the exported "expectations" in
-[tests.go](https://github.com/golang/tools/blob/299f270db45902e93469b1152fafed034bb3f033/internal/lsp/tests/tests.go#L418-L447).
-
-To run marker tests,
-
-```bash
-cd /path/to/tools
-
-# The marker tests are located in "internal/lsp", "internal/lsp/cmd, and
-# "internal/lsp/source".
-go test ./internal/lsp/...
-```
-
-There are quite a lot of marker tests, so to run one individually, pass the test
-path and heading into a -run argument:
-
-```bash
-cd /path/to/tools
-go test ./internal/lsp/... -v -run TestLSP/Modules/SuggestedFix/bar_11_21
-```
-
-## Resetting marker tests
-
-Sometimes, a change is made to lsp that requires a change to multiple golden
-files. When this happens, you can run,
-
-```bash
-cd /path/to/tools
-./internal/lsp/reset_golden.sh
-```
diff --git a/internal/lsp/tests/normalizer.go b/internal/lsp/tests/normalizer.go
deleted file mode 100644
index 77d9e66a8..000000000
--- a/internal/lsp/tests/normalizer.go
+++ /dev/null
@@ -1,129 +0,0 @@
-// 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 tests
-
-import (
- "path/filepath"
- "strconv"
- "strings"
-
- "golang.org/x/tools/go/packages/packagestest"
-)
-
-type Normalizer struct {
- path string
- slashed string
- escaped string
- fragment string
-}
-
-func CollectNormalizers(exported *packagestest.Exported) []Normalizer {
- // build the path normalizing patterns
- var normalizers []Normalizer
- for _, m := range exported.Modules {
- for fragment := range m.Files {
- n := Normalizer{
- path: exported.File(m.Name, fragment),
- fragment: fragment,
- }
- if n.slashed = filepath.ToSlash(n.path); n.slashed == n.path {
- n.slashed = ""
- }
- quoted := strconv.Quote(n.path)
- if n.escaped = quoted[1 : len(quoted)-1]; n.escaped == n.path {
- n.escaped = ""
- }
- normalizers = append(normalizers, n)
- }
- }
- return normalizers
-}
-
-// NormalizePrefix normalizes a single path at the front of the input string.
-func NormalizePrefix(s string, normalizers []Normalizer) string {
- for _, n := range normalizers {
- if t := strings.TrimPrefix(s, n.path); t != s {
- return n.fragment + t
- }
- if t := strings.TrimPrefix(s, n.slashed); t != s {
- return n.fragment + t
- }
- if t := strings.TrimPrefix(s, n.escaped); t != s {
- return n.fragment + t
- }
- }
- return s
-}
-
-// Normalize replaces all paths present in s with just the fragment portion
-// this is used to make golden files not depend on the temporary paths of the files
-func Normalize(s string, normalizers []Normalizer) string {
- type entry struct {
- path string
- index int
- fragment string
- }
- var match []entry
- // collect the initial state of all the matchers
- for _, n := range normalizers {
- index := strings.Index(s, n.path)
- if index >= 0 {
- match = append(match, entry{n.path, index, n.fragment})
- }
- if n.slashed != "" {
- index := strings.Index(s, n.slashed)
- if index >= 0 {
- match = append(match, entry{n.slashed, index, n.fragment})
- }
- }
- if n.escaped != "" {
- index := strings.Index(s, n.escaped)
- if index >= 0 {
- match = append(match, entry{n.escaped, index, n.fragment})
- }
- }
- }
- // result should be the same or shorter than the input
- var b strings.Builder
- last := 0
- for {
- // find the nearest path match to the start of the buffer
- next := -1
- nearest := len(s)
- for i, c := range match {
- if c.index >= 0 && nearest > c.index {
- nearest = c.index
- next = i
- }
- }
- // if there are no matches, we copy the rest of the string and are done
- if next < 0 {
- b.WriteString(s[last:])
- return b.String()
- }
- // we have a match
- n := &match[next]
- // copy up to the start of the match
- b.WriteString(s[last:n.index])
- // skip over the filename
- last = n.index + len(n.path)
-
- // Hack: In multi-module mode, we add a "testmodule/" prefix, so trim
- // it from the fragment.
- fragment := n.fragment
- if strings.HasPrefix(fragment, "testmodule") {
- split := strings.Split(filepath.ToSlash(fragment), "/")
- fragment = filepath.FromSlash(strings.Join(split[1:], "/"))
- }
-
- // add in the fragment instead
- b.WriteString(fragment)
- // see what the next match for this path is
- n.index = strings.Index(s[last:], n.path)
- if n.index >= 0 {
- n.index += last
- }
- }
-}
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
deleted file mode 100644
index 6a77fc775..000000000
--- a/internal/lsp/tests/tests.go
+++ /dev/null
@@ -1,1458 +0,0 @@
-// 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 tests exports functionality to be used across a variety of gopls tests.
-package tests
-
-import (
- "bytes"
- "context"
- "flag"
- "fmt"
- "go/ast"
- "go/token"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "regexp"
- "sort"
- "strconv"
- "strings"
- "sync"
- "testing"
- "time"
-
- "golang.org/x/tools/go/expect"
- "golang.org/x/tools/go/packages"
- "golang.org/x/tools/go/packages/packagestest"
- "golang.org/x/tools/internal/lsp/command"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/source/completion"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/testenv"
- "golang.org/x/tools/internal/typeparams"
- "golang.org/x/tools/txtar"
-)
-
-const (
- overlayFileSuffix = ".overlay"
- goldenFileSuffix = ".golden"
- inFileSuffix = ".in"
- testModule = "golang.org/x/tools/internal/lsp"
-)
-
-var summaryFile = "summary.txt"
-
-func init() {
- if typeparams.Enabled {
- summaryFile = "summary_go1.18.txt"
- }
-}
-
-var UpdateGolden = flag.Bool("golden", false, "Update golden files")
-
-type CallHierarchy map[span.Span]*CallHierarchyResult
-type CodeLens map[span.URI][]protocol.CodeLens
-type Diagnostics map[span.URI][]*source.Diagnostic
-type CompletionItems map[token.Pos]*completion.CompletionItem
-type Completions map[span.Span][]Completion
-type CompletionSnippets map[span.Span][]CompletionSnippet
-type UnimportedCompletions map[span.Span][]Completion
-type DeepCompletions map[span.Span][]Completion
-type FuzzyCompletions map[span.Span][]Completion
-type CaseSensitiveCompletions map[span.Span][]Completion
-type RankCompletions map[span.Span][]Completion
-type FoldingRanges []span.Span
-type Formats []span.Span
-type Imports []span.Span
-type SemanticTokens []span.Span
-type SuggestedFixes map[span.Span][]string
-type FunctionExtractions map[span.Span]span.Span
-type MethodExtractions map[span.Span]span.Span
-type Definitions map[span.Span]Definition
-type Implementations map[span.Span][]span.Span
-type Highlights map[span.Span][]span.Span
-type References map[span.Span][]span.Span
-type Renames map[span.Span]string
-type PrepareRenames map[span.Span]*source.PrepareItem
-type Symbols map[span.URI][]protocol.DocumentSymbol
-type SymbolsChildren map[string][]protocol.DocumentSymbol
-type SymbolInformation map[span.Span]protocol.SymbolInformation
-type WorkspaceSymbols map[WorkspaceSymbolsTestType]map[span.URI][]string
-type Signatures map[span.Span]*protocol.SignatureHelp
-type Links map[span.URI][]Link
-type AddImport map[span.URI]string
-type Hovers map[span.Span]string
-
-type Data struct {
- Config packages.Config
- Exported *packagestest.Exported
- CallHierarchy CallHierarchy
- CodeLens CodeLens
- Diagnostics Diagnostics
- CompletionItems CompletionItems
- Completions Completions
- CompletionSnippets CompletionSnippets
- UnimportedCompletions UnimportedCompletions
- DeepCompletions DeepCompletions
- FuzzyCompletions FuzzyCompletions
- CaseSensitiveCompletions CaseSensitiveCompletions
- RankCompletions RankCompletions
- FoldingRanges FoldingRanges
- Formats Formats
- Imports Imports
- SemanticTokens SemanticTokens
- SuggestedFixes SuggestedFixes
- FunctionExtractions FunctionExtractions
- MethodExtractions MethodExtractions
- Definitions Definitions
- Implementations Implementations
- Highlights Highlights
- References References
- Renames Renames
- PrepareRenames PrepareRenames
- Symbols Symbols
- symbolsChildren SymbolsChildren
- symbolInformation SymbolInformation
- WorkspaceSymbols WorkspaceSymbols
- Signatures Signatures
- Links Links
- AddImport AddImport
- Hovers Hovers
-
- t testing.TB
- fragments map[string]string
- dir string
- golden map[string]*Golden
- mode string
-
- ModfileFlagAvailable bool
-
- mappersMu sync.Mutex
- mappers map[span.URI]*protocol.ColumnMapper
-}
-
-type Tests interface {
- CallHierarchy(*testing.T, span.Span, *CallHierarchyResult)
- CodeLens(*testing.T, span.URI, []protocol.CodeLens)
- Diagnostics(*testing.T, span.URI, []*source.Diagnostic)
- Completion(*testing.T, span.Span, Completion, CompletionItems)
- CompletionSnippet(*testing.T, span.Span, CompletionSnippet, bool, CompletionItems)
- UnimportedCompletion(*testing.T, span.Span, Completion, CompletionItems)
- DeepCompletion(*testing.T, span.Span, Completion, CompletionItems)
- FuzzyCompletion(*testing.T, span.Span, Completion, CompletionItems)
- CaseSensitiveCompletion(*testing.T, span.Span, Completion, CompletionItems)
- RankCompletion(*testing.T, span.Span, Completion, CompletionItems)
- FoldingRanges(*testing.T, span.Span)
- Format(*testing.T, span.Span)
- Import(*testing.T, span.Span)
- SemanticTokens(*testing.T, span.Span)
- SuggestedFix(*testing.T, span.Span, []string, int)
- FunctionExtraction(*testing.T, span.Span, span.Span)
- MethodExtraction(*testing.T, span.Span, span.Span)
- Definition(*testing.T, span.Span, Definition)
- Implementation(*testing.T, span.Span, []span.Span)
- Highlight(*testing.T, span.Span, []span.Span)
- References(*testing.T, span.Span, []span.Span)
- Rename(*testing.T, span.Span, string)
- PrepareRename(*testing.T, span.Span, *source.PrepareItem)
- Symbols(*testing.T, span.URI, []protocol.DocumentSymbol)
- WorkspaceSymbols(*testing.T, span.URI, string, WorkspaceSymbolsTestType)
- SignatureHelp(*testing.T, span.Span, *protocol.SignatureHelp)
- Link(*testing.T, span.URI, []Link)
- AddImport(*testing.T, span.URI, string)
- Hover(*testing.T, span.Span, string)
-}
-
-type Definition struct {
- Name string
- IsType bool
- OnlyHover bool
- Src, Def span.Span
-}
-
-type CompletionTestType int
-
-const (
- // Default runs the standard completion tests.
- CompletionDefault = CompletionTestType(iota)
-
- // Unimported tests the autocompletion of unimported packages.
- CompletionUnimported
-
- // Deep tests deep completion.
- CompletionDeep
-
- // Fuzzy tests deep completion and fuzzy matching.
- CompletionFuzzy
-
- // CaseSensitive tests case sensitive completion.
- CompletionCaseSensitive
-
- // CompletionRank candidates in test must be valid and in the right relative order.
- CompletionRank
-)
-
-type WorkspaceSymbolsTestType int
-
-const (
- // Default runs the standard workspace symbols tests.
- WorkspaceSymbolsDefault = WorkspaceSymbolsTestType(iota)
-
- // Fuzzy tests workspace symbols with fuzzy matching.
- WorkspaceSymbolsFuzzy
-
- // CaseSensitive tests workspace symbols with case sensitive.
- WorkspaceSymbolsCaseSensitive
-)
-
-type Completion struct {
- CompletionItems []token.Pos
-}
-
-type CompletionSnippet struct {
- CompletionItem token.Pos
- PlainSnippet string
- PlaceholderSnippet string
-}
-
-type CallHierarchyResult struct {
- IncomingCalls, OutgoingCalls []protocol.CallHierarchyItem
-}
-
-type Link struct {
- Src span.Span
- Target string
- NotePosition token.Position
-}
-
-type Golden struct {
- Filename string
- Archive *txtar.Archive
- Modified bool
-}
-
-func Context(t testing.TB) context.Context {
- return context.Background()
-}
-
-func DefaultOptions(o *source.Options) {
- o.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{
- source.Go: {
- protocol.SourceOrganizeImports: true,
- protocol.QuickFix: true,
- protocol.RefactorRewrite: true,
- protocol.RefactorExtract: true,
- protocol.SourceFixAll: true,
- },
- source.Mod: {
- protocol.SourceOrganizeImports: true,
- },
- source.Sum: {},
- source.Work: {},
- source.Tmpl: {},
- }
- o.UserOptions.Codelenses[string(command.Test)] = true
- o.HoverKind = source.SynopsisDocumentation
- o.InsertTextFormat = protocol.SnippetTextFormat
- o.CompletionBudget = time.Minute
- o.HierarchicalDocumentSymbolSupport = true
- o.ExperimentalWorkspaceModule = true
- o.SemanticTokens = true
-}
-
-func RunTests(t *testing.T, dataDir string, includeMultiModule bool, f func(*testing.T, *Data)) {
- t.Helper()
- modes := []string{"Modules", "GOPATH"}
- if includeMultiModule {
- modes = append(modes, "MultiModule")
- }
- for _, mode := range modes {
- t.Run(mode, func(t *testing.T) {
- if mode == "MultiModule" {
- // Some bug in 1.12 breaks reading markers, and it's not worth figuring out.
- testenv.NeedsGo1Point(t, 13)
- }
- datum := load(t, mode, dataDir)
- t.Helper()
- f(t, datum)
- })
- }
-}
-
-func load(t testing.TB, mode string, dir string) *Data {
- datum := &Data{
- CallHierarchy: make(CallHierarchy),
- CodeLens: make(CodeLens),
- Diagnostics: make(Diagnostics),
- CompletionItems: make(CompletionItems),
- Completions: make(Completions),
- CompletionSnippets: make(CompletionSnippets),
- UnimportedCompletions: make(UnimportedCompletions),
- DeepCompletions: make(DeepCompletions),
- FuzzyCompletions: make(FuzzyCompletions),
- RankCompletions: make(RankCompletions),
- CaseSensitiveCompletions: make(CaseSensitiveCompletions),
- Definitions: make(Definitions),
- Implementations: make(Implementations),
- Highlights: make(Highlights),
- References: make(References),
- Renames: make(Renames),
- PrepareRenames: make(PrepareRenames),
- SuggestedFixes: make(SuggestedFixes),
- FunctionExtractions: make(FunctionExtractions),
- MethodExtractions: make(MethodExtractions),
- Symbols: make(Symbols),
- symbolsChildren: make(SymbolsChildren),
- symbolInformation: make(SymbolInformation),
- WorkspaceSymbols: make(WorkspaceSymbols),
- Signatures: make(Signatures),
- Links: make(Links),
- AddImport: make(AddImport),
- Hovers: make(Hovers),
-
- t: t,
- dir: dir,
- fragments: map[string]string{},
- golden: map[string]*Golden{},
- mode: mode,
- mappers: map[span.URI]*protocol.ColumnMapper{},
- }
-
- if !*UpdateGolden {
- summary := filepath.Join(filepath.FromSlash(dir), summaryFile+goldenFileSuffix)
- if _, err := os.Stat(summary); os.IsNotExist(err) {
- t.Fatalf("could not find golden file summary.txt in %#v", dir)
- }
- archive, err := txtar.ParseFile(summary)
- if err != nil {
- t.Fatalf("could not read golden file %v/%v: %v", dir, summary, err)
- }
- datum.golden[summaryFile] = &Golden{
- Filename: summary,
- Archive: archive,
- }
- }
-
- files := packagestest.MustCopyFileTree(dir)
- // Prune test cases that exercise generics.
- if !typeparams.Enabled {
- for name := range files {
- if strings.Contains(name, "_generics") {
- delete(files, name)
- }
- }
- }
- overlays := map[string][]byte{}
- for fragment, operation := range files {
- if trimmed := strings.TrimSuffix(fragment, goldenFileSuffix); trimmed != fragment {
- delete(files, fragment)
- goldFile := filepath.Join(dir, fragment)
- archive, err := txtar.ParseFile(goldFile)
- if err != nil {
- t.Fatalf("could not read golden file %v: %v", fragment, err)
- }
- datum.golden[trimmed] = &Golden{
- Filename: goldFile,
- Archive: archive,
- }
- } else if trimmed := strings.TrimSuffix(fragment, inFileSuffix); trimmed != fragment {
- delete(files, fragment)
- files[trimmed] = operation
- } else if index := strings.Index(fragment, overlayFileSuffix); index >= 0 {
- delete(files, fragment)
- partial := fragment[:index] + fragment[index+len(overlayFileSuffix):]
- contents, err := ioutil.ReadFile(filepath.Join(dir, fragment))
- if err != nil {
- t.Fatal(err)
- }
- overlays[partial] = contents
- }
- }
-
- modules := []packagestest.Module{
- {
- Name: testModule,
- Files: files,
- Overlay: overlays,
- },
- }
- switch mode {
- case "Modules":
- datum.Exported = packagestest.Export(t, packagestest.Modules, modules)
- case "GOPATH":
- datum.Exported = packagestest.Export(t, packagestest.GOPATH, modules)
- case "MultiModule":
- files := map[string]interface{}{}
- for k, v := range modules[0].Files {
- files[filepath.Join("testmodule", k)] = v
- }
- modules[0].Files = files
-
- overlays := map[string][]byte{}
- for k, v := range modules[0].Overlay {
- overlays[filepath.Join("testmodule", k)] = v
- }
- modules[0].Overlay = overlays
-
- golden := map[string]*Golden{}
- for k, v := range datum.golden {
- if k == summaryFile {
- golden[k] = v
- } else {
- golden[filepath.Join("testmodule", k)] = v
- }
- }
- datum.golden = golden
-
- datum.Exported = packagestest.Export(t, packagestest.Modules, modules)
- default:
- panic("unknown mode " + mode)
- }
-
- for _, m := range modules {
- for fragment := range m.Files {
- filename := datum.Exported.File(m.Name, fragment)
- datum.fragments[filename] = fragment
- }
- }
-
- // Turn off go/packages debug logging.
- datum.Exported.Config.Logf = nil
- datum.Config.Logf = nil
-
- // Merge the exported.Config with the view.Config.
- datum.Config = *datum.Exported.Config
- datum.Config.Fset = token.NewFileSet()
- datum.Config.Context = Context(nil)
- datum.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
- panic("ParseFile should not be called")
- }
-
- // Do a first pass to collect special markers for completion and workspace symbols.
- if err := datum.Exported.Expect(map[string]interface{}{
- "item": func(name string, r packagestest.Range, _ []string) {
- datum.Exported.Mark(name, r)
- },
- "symbol": func(name string, r packagestest.Range, _ []string) {
- datum.Exported.Mark(name, r)
- },
- }); err != nil {
- t.Fatal(err)
- }
-
- // Collect any data that needs to be used by subsequent tests.
- if err := datum.Exported.Expect(map[string]interface{}{
- "codelens": datum.collectCodeLens,
- "diag": datum.collectDiagnostics,
- "item": datum.collectCompletionItems,
- "complete": datum.collectCompletions(CompletionDefault),
- "unimported": datum.collectCompletions(CompletionUnimported),
- "deep": datum.collectCompletions(CompletionDeep),
- "fuzzy": datum.collectCompletions(CompletionFuzzy),
- "casesensitive": datum.collectCompletions(CompletionCaseSensitive),
- "rank": datum.collectCompletions(CompletionRank),
- "snippet": datum.collectCompletionSnippets,
- "fold": datum.collectFoldingRanges,
- "format": datum.collectFormats,
- "import": datum.collectImports,
- "semantic": datum.collectSemanticTokens,
- "godef": datum.collectDefinitions,
- "implementations": datum.collectImplementations,
- "typdef": datum.collectTypeDefinitions,
- "hoverdef": datum.collectHoverDefinitions,
- "hover": datum.collectHovers,
- "highlight": datum.collectHighlights,
- "refs": datum.collectReferences,
- "rename": datum.collectRenames,
- "prepare": datum.collectPrepareRenames,
- "symbol": datum.collectSymbols,
- "signature": datum.collectSignatures,
- "link": datum.collectLinks,
- "suggestedfix": datum.collectSuggestedFixes,
- "extractfunc": datum.collectFunctionExtractions,
- "extractmethod": datum.collectMethodExtractions,
- "incomingcalls": datum.collectIncomingCalls,
- "outgoingcalls": datum.collectOutgoingCalls,
- "addimport": datum.collectAddImports,
- }); err != nil {
- t.Fatal(err)
- }
- for _, symbols := range datum.Symbols {
- for i := range symbols {
- children := datum.symbolsChildren[symbols[i].Name]
- symbols[i].Children = children
- }
- }
- // Collect names for the entries that require golden files.
- if err := datum.Exported.Expect(map[string]interface{}{
- "godef": datum.collectDefinitionNames,
- "hoverdef": datum.collectDefinitionNames,
- "workspacesymbol": datum.collectWorkspaceSymbols(WorkspaceSymbolsDefault),
- "workspacesymbolfuzzy": datum.collectWorkspaceSymbols(WorkspaceSymbolsFuzzy),
- "workspacesymbolcasesensitive": datum.collectWorkspaceSymbols(WorkspaceSymbolsCaseSensitive),
- }); err != nil {
- t.Fatal(err)
- }
- if mode == "MultiModule" {
- if err := moveFile(filepath.Join(datum.Config.Dir, "go.mod"), filepath.Join(datum.Config.Dir, "testmodule/go.mod")); err != nil {
- t.Fatal(err)
- }
- }
-
- return datum
-}
-
-// moveFile moves the file at oldpath to newpath, by renaming if possible
-// or copying otherwise.
-func moveFile(oldpath, newpath string) (err error) {
- renameErr := os.Rename(oldpath, newpath)
- if renameErr == nil {
- return nil
- }
-
- src, err := os.Open(oldpath)
- if err != nil {
- return err
- }
- defer func() {
- src.Close()
- if err == nil {
- err = os.Remove(oldpath)
- }
- }()
-
- perm := os.ModePerm
- fi, err := src.Stat()
- if err == nil {
- perm = fi.Mode().Perm()
- }
-
- dst, err := os.OpenFile(newpath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
- if err != nil {
- return err
- }
-
- _, err = io.Copy(dst, src)
- if closeErr := dst.Close(); err == nil {
- err = closeErr
- }
- return err
-}
-
-func Run(t *testing.T, tests Tests, data *Data) {
- t.Helper()
- checkData(t, data)
-
- eachCompletion := func(t *testing.T, cases map[span.Span][]Completion, test func(*testing.T, span.Span, Completion, CompletionItems)) {
- t.Helper()
-
- for src, exp := range cases {
- for i, e := range exp {
- t.Run(SpanName(src)+"_"+strconv.Itoa(i), func(t *testing.T) {
- t.Helper()
- if strings.Contains(t.Name(), "cgo") {
- testenv.NeedsTool(t, "cgo")
- }
- if strings.Contains(t.Name(), "declarecgo") {
- testenv.NeedsGo1Point(t, 15)
- }
- test(t, src, e, data.CompletionItems)
- })
- }
-
- }
- }
-
- t.Run("CallHierarchy", func(t *testing.T) {
- t.Helper()
- for spn, callHierarchyResult := range data.CallHierarchy {
- t.Run(SpanName(spn), func(t *testing.T) {
- t.Helper()
- tests.CallHierarchy(t, spn, callHierarchyResult)
- })
- }
- })
-
- t.Run("Completion", func(t *testing.T) {
- t.Helper()
- eachCompletion(t, data.Completions, tests.Completion)
- })
-
- t.Run("CompletionSnippets", func(t *testing.T) {
- t.Helper()
- for _, placeholders := range []bool{true, false} {
- for src, expecteds := range data.CompletionSnippets {
- for i, expected := range expecteds {
- name := SpanName(src) + "_" + strconv.Itoa(i+1)
- if placeholders {
- name += "_placeholders"
- }
-
- t.Run(name, func(t *testing.T) {
- t.Helper()
- tests.CompletionSnippet(t, src, expected, placeholders, data.CompletionItems)
- })
- }
- }
- }
- })
-
- t.Run("UnimportedCompletion", func(t *testing.T) {
- t.Helper()
- eachCompletion(t, data.UnimportedCompletions, tests.UnimportedCompletion)
- })
-
- t.Run("DeepCompletion", func(t *testing.T) {
- t.Helper()
- eachCompletion(t, data.DeepCompletions, tests.DeepCompletion)
- })
-
- t.Run("FuzzyCompletion", func(t *testing.T) {
- t.Helper()
- eachCompletion(t, data.FuzzyCompletions, tests.FuzzyCompletion)
- })
-
- t.Run("CaseSensitiveCompletion", func(t *testing.T) {
- t.Helper()
- eachCompletion(t, data.CaseSensitiveCompletions, tests.CaseSensitiveCompletion)
- })
-
- t.Run("RankCompletions", func(t *testing.T) {
- t.Helper()
- eachCompletion(t, data.RankCompletions, tests.RankCompletion)
- })
-
- t.Run("CodeLens", func(t *testing.T) {
- t.Helper()
- for uri, want := range data.CodeLens {
- // Check if we should skip this URI if the -modfile flag is not available.
- if shouldSkip(data, uri) {
- continue
- }
- t.Run(uriName(uri), func(t *testing.T) {
- t.Helper()
- tests.CodeLens(t, uri, want)
- })
- }
- })
-
- t.Run("Diagnostics", func(t *testing.T) {
- t.Helper()
- for uri, want := range data.Diagnostics {
- // Check if we should skip this URI if the -modfile flag is not available.
- if shouldSkip(data, uri) {
- continue
- }
- t.Run(uriName(uri), func(t *testing.T) {
- t.Helper()
- tests.Diagnostics(t, uri, want)
- })
- }
- })
-
- t.Run("FoldingRange", func(t *testing.T) {
- t.Helper()
- for _, spn := range data.FoldingRanges {
- t.Run(uriName(spn.URI()), func(t *testing.T) {
- t.Helper()
- tests.FoldingRanges(t, spn)
- })
- }
- })
-
- t.Run("Format", func(t *testing.T) {
- t.Helper()
- for _, spn := range data.Formats {
- t.Run(uriName(spn.URI()), func(t *testing.T) {
- t.Helper()
- tests.Format(t, spn)
- })
- }
- })
-
- t.Run("Import", func(t *testing.T) {
- t.Helper()
- for _, spn := range data.Imports {
- t.Run(uriName(spn.URI()), func(t *testing.T) {
- t.Helper()
- tests.Import(t, spn)
- })
- }
- })
-
- t.Run("SemanticTokens", func(t *testing.T) {
- t.Helper()
- for _, spn := range data.SemanticTokens {
- t.Run(uriName(spn.URI()), func(t *testing.T) {
- t.Helper()
- tests.SemanticTokens(t, spn)
- })
- }
- })
-
- t.Run("SuggestedFix", func(t *testing.T) {
- t.Helper()
- for spn, actionKinds := range data.SuggestedFixes {
- // Check if we should skip this spn if the -modfile flag is not available.
- if shouldSkip(data, spn.URI()) {
- continue
- }
- t.Run(SpanName(spn), func(t *testing.T) {
- t.Helper()
- tests.SuggestedFix(t, spn, actionKinds, 1)
- })
- }
- })
-
- t.Run("FunctionExtraction", func(t *testing.T) {
- t.Helper()
- for start, end := range data.FunctionExtractions {
- // Check if we should skip this spn if the -modfile flag is not available.
- if shouldSkip(data, start.URI()) {
- continue
- }
- t.Run(SpanName(start), func(t *testing.T) {
- t.Helper()
- tests.FunctionExtraction(t, start, end)
- })
- }
- })
-
- t.Run("MethodExtraction", func(t *testing.T) {
- t.Helper()
- for start, end := range data.MethodExtractions {
- // Check if we should skip this spn if the -modfile flag is not available.
- if shouldSkip(data, start.URI()) {
- continue
- }
- t.Run(SpanName(start), func(t *testing.T) {
- t.Helper()
- tests.MethodExtraction(t, start, end)
- })
- }
- })
-
- t.Run("Definition", func(t *testing.T) {
- t.Helper()
- for spn, d := range data.Definitions {
- t.Run(SpanName(spn), func(t *testing.T) {
- t.Helper()
- if strings.Contains(t.Name(), "cgo") {
- testenv.NeedsTool(t, "cgo")
- }
- if strings.Contains(t.Name(), "declarecgo") {
- testenv.NeedsGo1Point(t, 15)
- }
- tests.Definition(t, spn, d)
- })
- }
- })
-
- t.Run("Implementation", func(t *testing.T) {
- t.Helper()
- for spn, m := range data.Implementations {
- t.Run(SpanName(spn), func(t *testing.T) {
- t.Helper()
- tests.Implementation(t, spn, m)
- })
- }
- })
-
- t.Run("Highlight", func(t *testing.T) {
- t.Helper()
- for pos, locations := range data.Highlights {
- t.Run(SpanName(pos), func(t *testing.T) {
- t.Helper()
- tests.Highlight(t, pos, locations)
- })
- }
- })
-
- t.Run("Hover", func(t *testing.T) {
- t.Helper()
- for pos, info := range data.Hovers {
- t.Run(SpanName(pos), func(t *testing.T) {
- t.Helper()
- tests.Hover(t, pos, info)
- })
- }
- })
-
- t.Run("References", func(t *testing.T) {
- t.Helper()
- for src, itemList := range data.References {
- t.Run(SpanName(src), func(t *testing.T) {
- t.Helper()
- tests.References(t, src, itemList)
- })
- }
- })
-
- t.Run("Renames", func(t *testing.T) {
- t.Helper()
- for spn, newText := range data.Renames {
- t.Run(uriName(spn.URI())+"_"+newText, func(t *testing.T) {
- t.Helper()
- tests.Rename(t, spn, newText)
- })
- }
- })
-
- t.Run("PrepareRenames", func(t *testing.T) {
- t.Helper()
- for src, want := range data.PrepareRenames {
- t.Run(SpanName(src), func(t *testing.T) {
- t.Helper()
- tests.PrepareRename(t, src, want)
- })
- }
- })
-
- t.Run("Symbols", func(t *testing.T) {
- t.Helper()
- for uri, expectedSymbols := range data.Symbols {
- t.Run(uriName(uri), func(t *testing.T) {
- t.Helper()
- tests.Symbols(t, uri, expectedSymbols)
- })
- }
- })
-
- t.Run("WorkspaceSymbols", func(t *testing.T) {
- t.Helper()
-
- for _, typ := range []WorkspaceSymbolsTestType{
- WorkspaceSymbolsDefault,
- WorkspaceSymbolsCaseSensitive,
- WorkspaceSymbolsFuzzy,
- } {
- for uri, cases := range data.WorkspaceSymbols[typ] {
- for _, query := range cases {
- name := query
- if name == "" {
- name = "EmptyQuery"
- }
- t.Run(name, func(t *testing.T) {
- t.Helper()
- tests.WorkspaceSymbols(t, uri, query, typ)
- })
- }
- }
- }
-
- })
-
- t.Run("SignatureHelp", func(t *testing.T) {
- t.Helper()
- for spn, expectedSignature := range data.Signatures {
- t.Run(SpanName(spn), func(t *testing.T) {
- t.Helper()
- tests.SignatureHelp(t, spn, expectedSignature)
- })
- }
- })
-
- t.Run("Link", func(t *testing.T) {
- t.Helper()
- for uri, wantLinks := range data.Links {
- // If we are testing GOPATH, then we do not want links with the versions
- // attached (pkg.go.dev/repoa/moda@v1.1.0/pkg), unless the file is a
- // go.mod, then we can skip it altogether.
- if data.Exported.Exporter == packagestest.GOPATH {
- if strings.HasSuffix(uri.Filename(), ".mod") {
- continue
- }
- re := regexp.MustCompile(`@v\d+\.\d+\.[\w-]+`)
- for i, link := range wantLinks {
- wantLinks[i].Target = re.ReplaceAllString(link.Target, "")
- }
- }
- t.Run(uriName(uri), func(t *testing.T) {
- t.Helper()
- tests.Link(t, uri, wantLinks)
- })
- }
- })
-
- t.Run("AddImport", func(t *testing.T) {
- t.Helper()
- for uri, exp := range data.AddImport {
- t.Run(uriName(uri), func(t *testing.T) {
- tests.AddImport(t, uri, exp)
- })
- }
- })
-
- if *UpdateGolden {
- for _, golden := range data.golden {
- if !golden.Modified {
- continue
- }
- sort.Slice(golden.Archive.Files, func(i, j int) bool {
- return golden.Archive.Files[i].Name < golden.Archive.Files[j].Name
- })
- if err := ioutil.WriteFile(golden.Filename, txtar.Format(golden.Archive), 0666); err != nil {
- t.Fatal(err)
- }
- }
- }
-}
-
-func checkData(t *testing.T, data *Data) {
- buf := &bytes.Buffer{}
- diagnosticsCount := 0
- for _, want := range data.Diagnostics {
- diagnosticsCount += len(want)
- }
- linksCount := 0
- for _, want := range data.Links {
- linksCount += len(want)
- }
- definitionCount := 0
- typeDefinitionCount := 0
- for _, d := range data.Definitions {
- if d.IsType {
- typeDefinitionCount++
- } else {
- definitionCount++
- }
- }
-
- snippetCount := 0
- for _, want := range data.CompletionSnippets {
- snippetCount += len(want)
- }
-
- countCompletions := func(c map[span.Span][]Completion) (count int) {
- for _, want := range c {
- count += len(want)
- }
- return count
- }
-
- countCodeLens := func(c map[span.URI][]protocol.CodeLens) (count int) {
- for _, want := range c {
- count += len(want)
- }
- return count
- }
-
- countWorkspaceSymbols := func(c map[WorkspaceSymbolsTestType]map[span.URI][]string) (count int) {
- for _, typs := range c {
- for _, queries := range typs {
- count += len(queries)
- }
- }
- return count
- }
-
- fmt.Fprintf(buf, "CallHierarchyCount = %v\n", len(data.CallHierarchy))
- fmt.Fprintf(buf, "CodeLensCount = %v\n", countCodeLens(data.CodeLens))
- fmt.Fprintf(buf, "CompletionsCount = %v\n", countCompletions(data.Completions))
- fmt.Fprintf(buf, "CompletionSnippetCount = %v\n", snippetCount)
- fmt.Fprintf(buf, "UnimportedCompletionsCount = %v\n", countCompletions(data.UnimportedCompletions))
- fmt.Fprintf(buf, "DeepCompletionsCount = %v\n", countCompletions(data.DeepCompletions))
- fmt.Fprintf(buf, "FuzzyCompletionsCount = %v\n", countCompletions(data.FuzzyCompletions))
- fmt.Fprintf(buf, "RankedCompletionsCount = %v\n", countCompletions(data.RankCompletions))
- fmt.Fprintf(buf, "CaseSensitiveCompletionsCount = %v\n", countCompletions(data.CaseSensitiveCompletions))
- fmt.Fprintf(buf, "DiagnosticsCount = %v\n", diagnosticsCount)
- fmt.Fprintf(buf, "FoldingRangesCount = %v\n", len(data.FoldingRanges))
- fmt.Fprintf(buf, "FormatCount = %v\n", len(data.Formats))
- fmt.Fprintf(buf, "ImportCount = %v\n", len(data.Imports))
- fmt.Fprintf(buf, "SemanticTokenCount = %v\n", len(data.SemanticTokens))
- fmt.Fprintf(buf, "SuggestedFixCount = %v\n", len(data.SuggestedFixes))
- fmt.Fprintf(buf, "FunctionExtractionCount = %v\n", len(data.FunctionExtractions))
- fmt.Fprintf(buf, "MethodExtractionCount = %v\n", len(data.MethodExtractions))
- fmt.Fprintf(buf, "DefinitionsCount = %v\n", definitionCount)
- fmt.Fprintf(buf, "TypeDefinitionsCount = %v\n", typeDefinitionCount)
- fmt.Fprintf(buf, "HighlightsCount = %v\n", len(data.Highlights))
- fmt.Fprintf(buf, "ReferencesCount = %v\n", len(data.References))
- fmt.Fprintf(buf, "RenamesCount = %v\n", len(data.Renames))
- fmt.Fprintf(buf, "PrepareRenamesCount = %v\n", len(data.PrepareRenames))
- fmt.Fprintf(buf, "SymbolsCount = %v\n", len(data.Symbols))
- fmt.Fprintf(buf, "WorkspaceSymbolsCount = %v\n", countWorkspaceSymbols(data.WorkspaceSymbols))
- fmt.Fprintf(buf, "SignaturesCount = %v\n", len(data.Signatures))
- fmt.Fprintf(buf, "LinksCount = %v\n", linksCount)
- fmt.Fprintf(buf, "ImplementationsCount = %v\n", len(data.Implementations))
-
- want := string(data.Golden("summary", summaryFile, func() ([]byte, error) {
- return buf.Bytes(), nil
- }))
- got := buf.String()
- if want != got {
- t.Errorf("test summary does not match:\n%s", Diff(t, want, got))
- }
-}
-
-func (data *Data) Mapper(uri span.URI) (*protocol.ColumnMapper, error) {
- data.mappersMu.Lock()
- defer data.mappersMu.Unlock()
-
- if _, ok := data.mappers[uri]; !ok {
- content, err := data.Exported.FileContents(uri.Filename())
- if err != nil {
- return nil, err
- }
- converter := span.NewContentConverter(uri.Filename(), content)
- data.mappers[uri] = &protocol.ColumnMapper{
- URI: uri,
- Converter: converter,
- Content: content,
- }
- }
- return data.mappers[uri], nil
-}
-
-func (data *Data) Golden(tag string, target string, update func() ([]byte, error)) []byte {
- data.t.Helper()
- fragment, found := data.fragments[target]
- if !found {
- if filepath.IsAbs(target) {
- data.t.Fatalf("invalid golden file fragment %v", target)
- }
- fragment = target
- }
- golden := data.golden[fragment]
- if golden == nil {
- if !*UpdateGolden {
- data.t.Fatalf("could not find golden file %v: %v", fragment, tag)
- }
- golden = &Golden{
- Filename: filepath.Join(data.dir, fragment+goldenFileSuffix),
- Archive: &txtar.Archive{},
- Modified: true,
- }
- data.golden[fragment] = golden
- }
- var file *txtar.File
- for i := range golden.Archive.Files {
- f := &golden.Archive.Files[i]
- if f.Name == tag {
- file = f
- break
- }
- }
- if *UpdateGolden {
- if file == nil {
- golden.Archive.Files = append(golden.Archive.Files, txtar.File{
- Name: tag,
- })
- file = &golden.Archive.Files[len(golden.Archive.Files)-1]
- }
- contents, err := update()
- if err != nil {
- data.t.Fatalf("could not update golden file %v: %v", fragment, err)
- }
- file.Data = append(contents, '\n') // add trailing \n for txtar
- golden.Modified = true
-
- }
- if file == nil {
- data.t.Fatalf("could not find golden contents %v: %v", fragment, tag)
- }
- if len(file.Data) == 0 {
- return file.Data
- }
- return file.Data[:len(file.Data)-1] // drop the trailing \n
-}
-
-func (data *Data) collectCodeLens(spn span.Span, title, cmd string) {
- if _, ok := data.CodeLens[spn.URI()]; !ok {
- data.CodeLens[spn.URI()] = []protocol.CodeLens{}
- }
- m, err := data.Mapper(spn.URI())
- if err != nil {
- return
- }
- rng, err := m.Range(spn)
- if err != nil {
- return
- }
- data.CodeLens[spn.URI()] = append(data.CodeLens[spn.URI()], protocol.CodeLens{
- Range: rng,
- Command: protocol.Command{
- Title: title,
- Command: cmd,
- },
- })
-}
-
-func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg, msgSeverity string) {
- if _, ok := data.Diagnostics[spn.URI()]; !ok {
- data.Diagnostics[spn.URI()] = []*source.Diagnostic{}
- }
- m, err := data.Mapper(spn.URI())
- if err != nil {
- return
- }
- rng, err := m.Range(spn)
- if err != nil {
- return
- }
- severity := protocol.SeverityError
- switch msgSeverity {
- case "error":
- severity = protocol.SeverityError
- case "warning":
- severity = protocol.SeverityWarning
- case "hint":
- severity = protocol.SeverityHint
- case "information":
- severity = protocol.SeverityInformation
- }
- // This is not the correct way to do this, but it seems excessive to do the full conversion here.
- want := &source.Diagnostic{
- Range: rng,
- Severity: severity,
- Source: source.DiagnosticSource(msgSource),
- Message: msg,
- }
- data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want)
-}
-
-func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) {
- result := func(m map[span.Span][]Completion, src span.Span, expected []token.Pos) {
- m[src] = append(m[src], Completion{
- CompletionItems: expected,
- })
- }
- switch typ {
- case CompletionDeep:
- return func(src span.Span, expected []token.Pos) {
- result(data.DeepCompletions, src, expected)
- }
- case CompletionUnimported:
- return func(src span.Span, expected []token.Pos) {
- result(data.UnimportedCompletions, src, expected)
- }
- case CompletionFuzzy:
- return func(src span.Span, expected []token.Pos) {
- result(data.FuzzyCompletions, src, expected)
- }
- case CompletionRank:
- return func(src span.Span, expected []token.Pos) {
- result(data.RankCompletions, src, expected)
- }
- case CompletionCaseSensitive:
- return func(src span.Span, expected []token.Pos) {
- result(data.CaseSensitiveCompletions, src, expected)
- }
- default:
- return func(src span.Span, expected []token.Pos) {
- result(data.Completions, src, expected)
- }
- }
-}
-
-func (data *Data) collectCompletionItems(pos token.Pos, args []string) {
- if len(args) < 3 {
- loc := data.Exported.ExpectFileSet.Position(pos)
- data.t.Fatalf("%s:%d: @item expects at least 3 args, got %d",
- loc.Filename, loc.Line, len(args))
- }
- label, detail, kind := args[0], args[1], args[2]
- var documentation string
- if len(args) == 4 {
- documentation = args[3]
- }
- data.CompletionItems[pos] = &completion.CompletionItem{
- Label: label,
- Detail: detail,
- Kind: protocol.ParseCompletionItemKind(kind),
- Documentation: documentation,
- }
-}
-
-func (data *Data) collectFoldingRanges(spn span.Span) {
- data.FoldingRanges = append(data.FoldingRanges, spn)
-}
-
-func (data *Data) collectFormats(spn span.Span) {
- data.Formats = append(data.Formats, spn)
-}
-
-func (data *Data) collectImports(spn span.Span) {
- data.Imports = append(data.Imports, spn)
-}
-
-func (data *Data) collectAddImports(spn span.Span, imp string) {
- data.AddImport[spn.URI()] = imp
-}
-
-func (data *Data) collectSemanticTokens(spn span.Span) {
- data.SemanticTokens = append(data.SemanticTokens, spn)
-}
-
-func (data *Data) collectSuggestedFixes(spn span.Span, actionKind string) {
- if _, ok := data.SuggestedFixes[spn]; !ok {
- data.SuggestedFixes[spn] = []string{}
- }
- data.SuggestedFixes[spn] = append(data.SuggestedFixes[spn], actionKind)
-}
-
-func (data *Data) collectFunctionExtractions(start span.Span, end span.Span) {
- if _, ok := data.FunctionExtractions[start]; !ok {
- data.FunctionExtractions[start] = end
- }
-}
-
-func (data *Data) collectMethodExtractions(start span.Span, end span.Span) {
- if _, ok := data.MethodExtractions[start]; !ok {
- data.MethodExtractions[start] = end
- }
-}
-
-func (data *Data) collectDefinitions(src, target span.Span) {
- data.Definitions[src] = Definition{
- Src: src,
- Def: target,
- }
-}
-
-func (data *Data) collectImplementations(src span.Span, targets []span.Span) {
- data.Implementations[src] = targets
-}
-
-func (data *Data) collectIncomingCalls(src span.Span, calls []span.Span) {
- for _, call := range calls {
- m, err := data.Mapper(call.URI())
- if err != nil {
- data.t.Fatal(err)
- }
- rng, err := m.Range(call)
- if err != nil {
- data.t.Fatal(err)
- }
- // we're only comparing protocol.range
- if data.CallHierarchy[src] != nil {
- data.CallHierarchy[src].IncomingCalls = append(data.CallHierarchy[src].IncomingCalls,
- protocol.CallHierarchyItem{
- URI: protocol.DocumentURI(call.URI()),
- Range: rng,
- })
- } else {
- data.CallHierarchy[src] = &CallHierarchyResult{
- IncomingCalls: []protocol.CallHierarchyItem{
- {URI: protocol.DocumentURI(call.URI()), Range: rng},
- },
- }
- }
- }
-}
-
-func (data *Data) collectOutgoingCalls(src span.Span, calls []span.Span) {
- if data.CallHierarchy[src] == nil {
- data.CallHierarchy[src] = &CallHierarchyResult{}
- }
- for _, call := range calls {
- m, err := data.Mapper(call.URI())
- if err != nil {
- data.t.Fatal(err)
- }
- rng, err := m.Range(call)
- if err != nil {
- data.t.Fatal(err)
- }
- // we're only comparing protocol.range
- data.CallHierarchy[src].OutgoingCalls = append(data.CallHierarchy[src].OutgoingCalls,
- protocol.CallHierarchyItem{
- URI: protocol.DocumentURI(call.URI()),
- Range: rng,
- })
- }
-}
-
-func (data *Data) collectHoverDefinitions(src, target span.Span) {
- data.Definitions[src] = Definition{
- Src: src,
- Def: target,
- OnlyHover: true,
- }
-}
-
-func (data *Data) collectHovers(src span.Span, expected string) {
- data.Hovers[src] = expected
-}
-
-func (data *Data) collectTypeDefinitions(src, target span.Span) {
- data.Definitions[src] = Definition{
- Src: src,
- Def: target,
- IsType: true,
- }
-}
-
-func (data *Data) collectDefinitionNames(src span.Span, name string) {
- d := data.Definitions[src]
- d.Name = name
- data.Definitions[src] = d
-}
-
-func (data *Data) collectHighlights(src span.Span, expected []span.Span) {
- // Declaring a highlight in a test file: @highlight(src, expected1, expected2)
- data.Highlights[src] = append(data.Highlights[src], expected...)
-}
-
-func (data *Data) collectReferences(src span.Span, expected []span.Span) {
- data.References[src] = expected
-}
-
-func (data *Data) collectRenames(src span.Span, newText string) {
- data.Renames[src] = newText
-}
-
-func (data *Data) collectPrepareRenames(src span.Span, rng span.Range, placeholder string) {
- m, err := data.Mapper(src.URI())
- if err != nil {
- data.t.Fatal(err)
- }
- // Convert range to span and then to protocol.Range.
- spn, err := rng.Span()
- if err != nil {
- data.t.Fatal(err)
- }
- prng, err := m.Range(spn)
- if err != nil {
- data.t.Fatal(err)
- }
- data.PrepareRenames[src] = &source.PrepareItem{
- Range: prng,
- Text: placeholder,
- }
-}
-
-// collectSymbols is responsible for collecting @symbol annotations.
-func (data *Data) collectSymbols(name string, spn span.Span, kind string, parentName string, siName string) {
- m, err := data.Mapper(spn.URI())
- if err != nil {
- data.t.Fatal(err)
- }
- rng, err := m.Range(spn)
- if err != nil {
- data.t.Fatal(err)
- }
- sym := protocol.DocumentSymbol{
- Name: name,
- Kind: protocol.ParseSymbolKind(kind),
- SelectionRange: rng,
- }
- if parentName == "" {
- data.Symbols[spn.URI()] = append(data.Symbols[spn.URI()], sym)
- } else {
- data.symbolsChildren[parentName] = append(data.symbolsChildren[parentName], sym)
- }
-
- // Reuse @symbol in the workspace symbols tests.
- si := protocol.SymbolInformation{
- Name: siName,
- Kind: sym.Kind,
- Location: protocol.Location{
- URI: protocol.URIFromSpanURI(spn.URI()),
- Range: sym.SelectionRange,
- },
- }
- data.symbolInformation[spn] = si
-}
-
-func (data *Data) collectWorkspaceSymbols(typ WorkspaceSymbolsTestType) func(*expect.Note, string) {
- return func(note *expect.Note, query string) {
- if data.WorkspaceSymbols[typ] == nil {
- data.WorkspaceSymbols[typ] = make(map[span.URI][]string)
- }
- pos := data.Exported.ExpectFileSet.Position(note.Pos)
- uri := span.URIFromPath(pos.Filename)
- data.WorkspaceSymbols[typ][uri] = append(data.WorkspaceSymbols[typ][uri], query)
- }
-}
-
-func (data *Data) collectSignatures(spn span.Span, signature string, activeParam int64) {
- data.Signatures[spn] = &protocol.SignatureHelp{
- Signatures: []protocol.SignatureInformation{
- {
- Label: signature,
- },
- },
- ActiveParameter: uint32(activeParam),
- }
- // Hardcode special case to test the lack of a signature.
- if signature == "" && activeParam == 0 {
- data.Signatures[spn] = nil
- }
-}
-
-func (data *Data) collectCompletionSnippets(spn span.Span, item token.Pos, plain, placeholder string) {
- data.CompletionSnippets[spn] = append(data.CompletionSnippets[spn], CompletionSnippet{
- CompletionItem: item,
- PlainSnippet: plain,
- PlaceholderSnippet: placeholder,
- })
-}
-
-func (data *Data) collectLinks(spn span.Span, link string, note *expect.Note, fset *token.FileSet) {
- position := fset.Position(note.Pos)
- uri := spn.URI()
- data.Links[uri] = append(data.Links[uri], Link{
- Src: spn,
- Target: link,
- NotePosition: position,
- })
-}
-
-func uriName(uri span.URI) string {
- return filepath.Base(strings.TrimSuffix(uri.Filename(), ".go"))
-}
-
-func SpanName(spn span.Span) string {
- return fmt.Sprintf("%v_%v_%v", uriName(spn.URI()), spn.Start().Line(), spn.Start().Column())
-}
-
-func CopyFolderToTempDir(folder string) (string, error) {
- if _, err := os.Stat(folder); err != nil {
- return "", err
- }
- dst, err := ioutil.TempDir("", "modfile_test")
- if err != nil {
- return "", err
- }
- fds, err := ioutil.ReadDir(folder)
- if err != nil {
- return "", err
- }
- for _, fd := range fds {
- srcfp := filepath.Join(folder, fd.Name())
- stat, err := os.Stat(srcfp)
- if err != nil {
- return "", err
- }
- if !stat.Mode().IsRegular() {
- return "", fmt.Errorf("cannot copy non regular file %s", srcfp)
- }
- contents, err := ioutil.ReadFile(srcfp)
- if err != nil {
- return "", err
- }
- if err := ioutil.WriteFile(filepath.Join(dst, fd.Name()), contents, stat.Mode()); err != nil {
- return "", err
- }
- }
- return dst, nil
-}
-
-func shouldSkip(data *Data, uri span.URI) bool {
- if data.ModfileFlagAvailable {
- return false
- }
- // If the -modfile flag is not available, then we do not want to run
- // any tests on the go.mod file.
- if strings.HasSuffix(uri.Filename(), ".mod") {
- return true
- }
- // If the -modfile flag is not available, then we do not want to test any
- // uri that contains "go mod tidy".
- m, err := data.Mapper(uri)
- return err == nil && strings.Contains(string(m.Content), ", \"go mod tidy\",")
-}
diff --git a/internal/lsp/tests/util.go b/internal/lsp/tests/util.go
deleted file mode 100644
index 11dda1f8e..000000000
--- a/internal/lsp/tests/util.go
+++ /dev/null
@@ -1,580 +0,0 @@
-// 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 tests
-
-import (
- "bytes"
- "context"
- "fmt"
- "go/token"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/diff/myers"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/lsp/source/completion"
- "golang.org/x/tools/internal/span"
-)
-
-// DiffLinks takes the links we got and checks if they are located within the source or a Note.
-// If the link is within a Note, the link is removed.
-// Returns an diff comment if there are differences and empty string if no diffs.
-func DiffLinks(mapper *protocol.ColumnMapper, wantLinks []Link, gotLinks []protocol.DocumentLink) string {
- var notePositions []token.Position
- links := make(map[span.Span]string, len(wantLinks))
- for _, link := range wantLinks {
- links[link.Src] = link.Target
- notePositions = append(notePositions, link.NotePosition)
- }
- for _, link := range gotLinks {
- spn, err := mapper.RangeSpan(link.Range)
- if err != nil {
- return fmt.Sprintf("%v", err)
- }
- linkInNote := false
- for _, notePosition := range notePositions {
- // Drop the links found inside expectation notes arguments as this links are not collected by expect package.
- if notePosition.Line == spn.Start().Line() &&
- notePosition.Column <= spn.Start().Column() {
- delete(links, spn)
- linkInNote = true
- }
- }
- if linkInNote {
- continue
- }
- if target, ok := links[spn]; ok {
- delete(links, spn)
- if target != link.Target {
- return fmt.Sprintf("for %v want %v, got %v\n", spn, target, link.Target)
- }
- } else {
- return fmt.Sprintf("unexpected link %v:%v\n", spn, link.Target)
- }
- }
- for spn, target := range links {
- return fmt.Sprintf("missing link %v:%v\n", spn, target)
- }
- return ""
-}
-
-// DiffSymbols prints the diff between expected and actual symbols test results.
-func DiffSymbols(t *testing.T, uri span.URI, want, got []protocol.DocumentSymbol) string {
- sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name })
- sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name })
- if len(got) != len(want) {
- return summarizeSymbols(-1, want, got, "different lengths got %v want %v", len(got), len(want))
- }
- for i, w := range want {
- g := got[i]
- if w.Name != g.Name {
- return summarizeSymbols(i, want, got, "incorrect name got %v want %v", g.Name, w.Name)
- }
- if w.Kind != g.Kind {
- return summarizeSymbols(i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind)
- }
- if protocol.CompareRange(w.SelectionRange, g.SelectionRange) != 0 {
- return summarizeSymbols(i, want, got, "incorrect span got %v want %v", g.SelectionRange, w.SelectionRange)
- }
- if msg := DiffSymbols(t, uri, w.Children, g.Children); msg != "" {
- return fmt.Sprintf("children of %s: %s", w.Name, msg)
- }
- }
- return ""
-}
-
-func summarizeSymbols(i int, want, got []protocol.DocumentSymbol, reason string, args ...interface{}) string {
- msg := &bytes.Buffer{}
- fmt.Fprint(msg, "document symbols failed")
- if i >= 0 {
- fmt.Fprintf(msg, " at %d", i)
- }
- fmt.Fprint(msg, " because of ")
- fmt.Fprintf(msg, reason, args...)
- fmt.Fprint(msg, ":\nexpected:\n")
- for _, s := range want {
- fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
- }
- fmt.Fprintf(msg, "got:\n")
- for _, s := range got {
- fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
- }
- return msg.String()
-}
-
-// DiffDiagnostics prints the diff between expected and actual diagnostics test
-// results.
-func DiffDiagnostics(uri span.URI, want, got []*source.Diagnostic) string {
- source.SortDiagnostics(want)
- source.SortDiagnostics(got)
-
- if len(got) != len(want) {
- return summarizeDiagnostics(-1, uri, want, got, "different lengths got %v want %v", len(got), len(want))
- }
- for i, w := range want {
- g := got[i]
- if w.Message != g.Message {
- return summarizeDiagnostics(i, uri, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
- }
- if w.Severity != g.Severity {
- return summarizeDiagnostics(i, uri, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity)
- }
- if w.Source != g.Source {
- return summarizeDiagnostics(i, uri, want, got, "incorrect Source got %v want %v", g.Source, w.Source)
- }
- if !rangeOverlaps(g.Range, w.Range) {
- return summarizeDiagnostics(i, uri, want, got, "range %v does not overlap %v", g.Range, w.Range)
- }
- }
- return ""
-}
-
-// rangeOverlaps reports whether r1 and r2 overlap.
-func rangeOverlaps(r1, r2 protocol.Range) bool {
- if inRange(r2.Start, r1) || inRange(r1.Start, r2) {
- return true
- }
- return false
-}
-
-// inRange reports whether p is contained within [r.Start, r.End), or if p ==
-// r.Start == r.End (special handling for the case where the range is a single
-// point).
-func inRange(p protocol.Position, r protocol.Range) bool {
- if protocol.IsPoint(r) {
- return protocol.ComparePosition(r.Start, p) == 0
- }
- if protocol.ComparePosition(r.Start, p) <= 0 && protocol.ComparePosition(p, r.End) < 0 {
- return true
- }
- return false
-}
-
-func summarizeDiagnostics(i int, uri span.URI, want, got []*source.Diagnostic, reason string, args ...interface{}) string {
- msg := &bytes.Buffer{}
- fmt.Fprint(msg, "diagnostics failed")
- if i >= 0 {
- fmt.Fprintf(msg, " at %d", i)
- }
- fmt.Fprint(msg, " because of ")
- fmt.Fprintf(msg, reason, args...)
- fmt.Fprint(msg, ":\nexpected:\n")
- for _, d := range want {
- fmt.Fprintf(msg, " %s:%v: %s\n", uri, d.Range, d.Message)
- }
- fmt.Fprintf(msg, "got:\n")
- for _, d := range got {
- fmt.Fprintf(msg, " %s:%v: %s\n", uri, d.Range, d.Message)
- }
- return msg.String()
-}
-
-func DiffCodeLens(uri span.URI, want, got []protocol.CodeLens) string {
- sortCodeLens(want)
- sortCodeLens(got)
-
- if len(got) != len(want) {
- return summarizeCodeLens(-1, uri, want, got, "different lengths got %v want %v", len(got), len(want))
- }
- for i, w := range want {
- g := got[i]
- if w.Command.Command != g.Command.Command {
- return summarizeCodeLens(i, uri, want, got, "incorrect Command Name got %v want %v", g.Command.Command, w.Command.Command)
- }
- if w.Command.Title != g.Command.Title {
- return summarizeCodeLens(i, uri, want, got, "incorrect Command Title got %v want %v", g.Command.Title, w.Command.Title)
- }
- if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
- return summarizeCodeLens(i, uri, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
- }
- if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the codelens returns a zero-length range.
- if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 {
- return summarizeCodeLens(i, uri, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
- }
- }
- }
- return ""
-}
-
-func sortCodeLens(c []protocol.CodeLens) {
- sort.Slice(c, func(i int, j int) bool {
- if r := protocol.CompareRange(c[i].Range, c[j].Range); r != 0 {
- return r < 0
- }
- if c[i].Command.Command < c[j].Command.Command {
- return true
- } else if c[i].Command.Command == c[j].Command.Command {
- return c[i].Command.Title < c[j].Command.Title
- } else {
- return false
- }
- })
-}
-
-func summarizeCodeLens(i int, uri span.URI, want, got []protocol.CodeLens, reason string, args ...interface{}) string {
- msg := &bytes.Buffer{}
- fmt.Fprint(msg, "codelens failed")
- if i >= 0 {
- fmt.Fprintf(msg, " at %d", i)
- }
- fmt.Fprint(msg, " because of ")
- fmt.Fprintf(msg, reason, args...)
- fmt.Fprint(msg, ":\nexpected:\n")
- for _, d := range want {
- fmt.Fprintf(msg, " %s:%v: %s | %s\n", uri, d.Range, d.Command.Command, d.Command.Title)
- }
- fmt.Fprintf(msg, "got:\n")
- for _, d := range got {
- fmt.Fprintf(msg, " %s:%v: %s | %s\n", uri, d.Range, d.Command.Command, d.Command.Title)
- }
- return msg.String()
-}
-
-func DiffSignatures(spn span.Span, want, got *protocol.SignatureHelp) (string, error) {
- decorate := func(f string, args ...interface{}) string {
- return fmt.Sprintf("invalid signature at %s: %s", spn, fmt.Sprintf(f, args...))
- }
- if len(got.Signatures) != 1 {
- return decorate("wanted 1 signature, got %d", len(got.Signatures)), nil
- }
- if got.ActiveSignature != 0 {
- return decorate("wanted active signature of 0, got %d", int(got.ActiveSignature)), nil
- }
- if want.ActiveParameter != got.ActiveParameter {
- return decorate("wanted active parameter of %d, got %d", want.ActiveParameter, int(got.ActiveParameter)), nil
- }
- g := got.Signatures[0]
- w := want.Signatures[0]
- if NormalizeAny(w.Label) != NormalizeAny(g.Label) {
- wLabel := w.Label + "\n"
- d, err := myers.ComputeEdits("", wLabel, g.Label+"\n")
- if err != nil {
- return "", err
- }
- return decorate("mismatched labels:\n%q", diff.ToUnified("want", "got", wLabel, d)), err
- }
- var paramParts []string
- for _, p := range g.Parameters {
- paramParts = append(paramParts, p.Label)
- }
- paramsStr := strings.Join(paramParts, ", ")
- if !strings.Contains(g.Label, paramsStr) {
- return decorate("expected signature %q to contain params %q", g.Label, paramsStr), nil
- }
- return "", nil
-}
-
-// NormalizeAny replaces occurrences of interface{} in input with any.
-//
-// In Go 1.18, standard library functions were changed to use the 'any'
-// alias in place of interface{}, which affects their type string.
-func NormalizeAny(input string) string {
- return strings.ReplaceAll(input, "interface{}", "any")
-}
-
-// DiffCallHierarchyItems returns the diff between expected and actual call locations for incoming/outgoing call hierarchies
-func DiffCallHierarchyItems(gotCalls []protocol.CallHierarchyItem, expectedCalls []protocol.CallHierarchyItem) string {
- expected := make(map[protocol.Location]bool)
- for _, call := range expectedCalls {
- expected[protocol.Location{URI: call.URI, Range: call.Range}] = true
- }
-
- got := make(map[protocol.Location]bool)
- for _, call := range gotCalls {
- got[protocol.Location{URI: call.URI, Range: call.Range}] = true
- }
- if len(got) != len(expected) {
- return fmt.Sprintf("expected %d calls but got %d", len(expected), len(got))
- }
- for spn := range got {
- if !expected[spn] {
- return fmt.Sprintf("incorrect calls, expected locations %v but got locations %v", expected, got)
- }
- }
- return ""
-}
-
-func ToProtocolCompletionItems(items []completion.CompletionItem) []protocol.CompletionItem {
- var result []protocol.CompletionItem
- for _, item := range items {
- result = append(result, ToProtocolCompletionItem(item))
- }
- return result
-}
-
-func ToProtocolCompletionItem(item completion.CompletionItem) protocol.CompletionItem {
- pItem := protocol.CompletionItem{
- Label: item.Label,
- Kind: item.Kind,
- Detail: item.Detail,
- Documentation: item.Documentation,
- InsertText: item.InsertText,
- TextEdit: &protocol.TextEdit{
- NewText: item.Snippet(),
- },
- // Negate score so best score has lowest sort text like real API.
- SortText: fmt.Sprint(-item.Score),
- }
- if pItem.InsertText == "" {
- pItem.InsertText = pItem.Label
- }
- return pItem
-}
-
-func FilterBuiltins(src span.Span, items []protocol.CompletionItem) []protocol.CompletionItem {
- var (
- got []protocol.CompletionItem
- wantBuiltins = strings.Contains(string(src.URI()), "builtins")
- wantKeywords = strings.Contains(string(src.URI()), "keywords")
- )
- for _, item := range items {
- if !wantBuiltins && isBuiltin(item.Label, item.Detail, item.Kind) {
- continue
- }
-
- if !wantKeywords && token.Lookup(item.Label).IsKeyword() {
- continue
- }
-
- got = append(got, item)
- }
- return got
-}
-
-func isBuiltin(label, detail string, kind protocol.CompletionItemKind) bool {
- if detail == "" && kind == protocol.ClassCompletion {
- return true
- }
- // Remaining builtin constants, variables, interfaces, and functions.
- trimmed := label
- if i := strings.Index(trimmed, "("); i >= 0 {
- trimmed = trimmed[:i]
- }
- switch trimmed {
- case "append", "cap", "close", "complex", "copy", "delete",
- "error", "false", "imag", "iota", "len", "make", "new",
- "nil", "panic", "print", "println", "real", "recover", "true":
- return true
- }
- return false
-}
-
-func CheckCompletionOrder(want, got []protocol.CompletionItem, strictScores bool) string {
- var (
- matchedIdxs []int
- lastGotIdx int
- lastGotSort float64
- inOrder = true
- errorMsg = "completions out of order"
- )
- for _, w := range want {
- var found bool
- for i, g := range got {
- if w.Label == g.Label && NormalizeAny(w.Detail) == NormalizeAny(g.Detail) && w.Kind == g.Kind {
- matchedIdxs = append(matchedIdxs, i)
- found = true
-
- if i < lastGotIdx {
- inOrder = false
- }
- lastGotIdx = i
-
- sort, _ := strconv.ParseFloat(g.SortText, 64)
- if strictScores && len(matchedIdxs) > 1 && sort <= lastGotSort {
- inOrder = false
- errorMsg = "candidate scores not strictly decreasing"
- }
- lastGotSort = sort
-
- break
- }
- }
- if !found {
- return summarizeCompletionItems(-1, []protocol.CompletionItem{w}, got, "didn't find expected completion")
- }
- }
-
- sort.Ints(matchedIdxs)
- matched := make([]protocol.CompletionItem, 0, len(matchedIdxs))
- for _, idx := range matchedIdxs {
- matched = append(matched, got[idx])
- }
-
- if !inOrder {
- return summarizeCompletionItems(-1, want, matched, errorMsg)
- }
-
- return ""
-}
-
-func DiffSnippets(want string, got *protocol.CompletionItem) string {
- if want == "" {
- if got != nil {
- x := got.TextEdit
- return fmt.Sprintf("expected no snippet but got %s", x.NewText)
- }
- } else {
- if got == nil {
- return fmt.Sprintf("couldn't find completion matching %q", want)
- }
- x := got.TextEdit
- if want != x.NewText {
- return fmt.Sprintf("expected snippet %q, got %q", want, x.NewText)
- }
- }
- return ""
-}
-
-func FindItem(list []protocol.CompletionItem, want completion.CompletionItem) *protocol.CompletionItem {
- for _, item := range list {
- if item.Label == want.Label {
- return &item
- }
- }
- return nil
-}
-
-// DiffCompletionItems prints the diff between expected and actual completion
-// test results.
-func DiffCompletionItems(want, got []protocol.CompletionItem) string {
- if len(got) != len(want) {
- return summarizeCompletionItems(-1, want, got, "different lengths got %v want %v", len(got), len(want))
- }
- for i, w := range want {
- g := got[i]
- if w.Label != g.Label {
- return summarizeCompletionItems(i, want, got, "incorrect Label got %v want %v", g.Label, w.Label)
- }
- if NormalizeAny(w.Detail) != NormalizeAny(g.Detail) {
- return summarizeCompletionItems(i, want, got, "incorrect Detail got %v want %v", g.Detail, w.Detail)
- }
- if w.Documentation != "" && !strings.HasPrefix(w.Documentation, "@") {
- if w.Documentation != g.Documentation {
- return summarizeCompletionItems(i, want, got, "incorrect Documentation got %v want %v", g.Documentation, w.Documentation)
- }
- }
- if w.Kind != g.Kind {
- return summarizeCompletionItems(i, want, got, "incorrect Kind got %v want %v", g.Kind, w.Kind)
- }
- }
- return ""
-}
-
-func summarizeCompletionItems(i int, want, got []protocol.CompletionItem, reason string, args ...interface{}) string {
- msg := &bytes.Buffer{}
- fmt.Fprint(msg, "completion failed")
- if i >= 0 {
- fmt.Fprintf(msg, " at %d", i)
- }
- fmt.Fprint(msg, " because of ")
- fmt.Fprintf(msg, reason, args...)
- fmt.Fprint(msg, ":\nexpected:\n")
- for _, d := range want {
- fmt.Fprintf(msg, " %v\n", d)
- }
- fmt.Fprintf(msg, "got:\n")
- for _, d := range got {
- fmt.Fprintf(msg, " %v\n", d)
- }
- return msg.String()
-}
-
-func EnableAllAnalyzers(view source.View, opts *source.Options) {
- if opts.Analyses == nil {
- opts.Analyses = make(map[string]bool)
- }
- for _, a := range opts.DefaultAnalyzers {
- if !a.IsEnabled(view) {
- opts.Analyses[a.Analyzer.Name] = true
- }
- }
- for _, a := range opts.TypeErrorAnalyzers {
- if !a.IsEnabled(view) {
- opts.Analyses[a.Analyzer.Name] = true
- }
- }
- for _, a := range opts.ConvenienceAnalyzers {
- if !a.IsEnabled(view) {
- opts.Analyses[a.Analyzer.Name] = true
- }
- }
- for _, a := range opts.StaticcheckAnalyzers {
- if !a.IsEnabled(view) {
- opts.Analyses[a.Analyzer.Name] = true
- }
- }
-}
-
-func WorkspaceSymbolsString(ctx context.Context, data *Data, queryURI span.URI, symbols []protocol.SymbolInformation) (string, error) {
- queryDir := filepath.Dir(queryURI.Filename())
- var filtered []string
- for _, s := range symbols {
- uri := s.Location.URI.SpanURI()
- dir := filepath.Dir(uri.Filename())
- if !source.InDir(queryDir, dir) { // assume queries always issue from higher directories
- continue
- }
- m, err := data.Mapper(uri)
- if err != nil {
- return "", err
- }
- spn, err := m.Span(s.Location)
- if err != nil {
- return "", err
- }
- filtered = append(filtered, fmt.Sprintf("%s %s %s", spn, s.Name, s.Kind))
- }
- sort.Strings(filtered)
- return strings.Join(filtered, "\n") + "\n", nil
-}
-
-func WorkspaceSymbolsTestTypeToMatcher(typ WorkspaceSymbolsTestType) source.SymbolMatcher {
- switch typ {
- case WorkspaceSymbolsFuzzy:
- return source.SymbolFuzzy
- case WorkspaceSymbolsCaseSensitive:
- return source.SymbolCaseSensitive
- default:
- return source.SymbolCaseInsensitive
- }
-}
-
-func Diff(t *testing.T, want, got string) string {
- if want == got {
- return ""
- }
- // Add newlines to avoid newline messages in diff.
- want += "\n"
- got += "\n"
- d, err := myers.ComputeEdits("", want, got)
- if err != nil {
- t.Fatal(err)
- }
- return fmt.Sprintf("%q", diff.ToUnified("want", "got", want, d))
-}
-
-// StripSubscripts removes type parameter id subscripts.
-//
-// TODO(rfindley): remove this function once subscripts are removed from the
-// type parameter type string.
-func StripSubscripts(s string) string {
- var runes []rune
- for _, r := range s {
- // For debugging/uniqueness purposes, TypeString on a type parameter adds a
- // subscript corresponding to the type parameter's unique id. This is going
- // to be removed, but in the meantime we skip the subscript runes to get a
- // deterministic output.
- if '₀' <= r && r < '₀'+10 {
- continue // trim type parameter subscripts
- }
- runes = append(runes, r)
- }
- return string(runes)
-}
diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go
deleted file mode 100644
index d9a696140..000000000
--- a/internal/lsp/text_synchronization.go
+++ /dev/null
@@ -1,382 +0,0 @@
-// 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 lsp
-
-import (
- "bytes"
- "context"
- "fmt"
- "path/filepath"
- "time"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/jsonrpc2"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/xcontext"
- errors "golang.org/x/xerrors"
-)
-
-// ModificationSource identifies the originating cause of a file modification.
-type ModificationSource int
-
-const (
- // FromDidOpen is a file modification caused by opening a file.
- FromDidOpen = ModificationSource(iota)
-
- // FromDidChange is a file modification caused by changing a file.
- FromDidChange
-
- // FromDidChangeWatchedFiles is a file modification caused by a change to a
- // watched file.
- FromDidChangeWatchedFiles
-
- // FromDidSave is a file modification caused by a file save.
- FromDidSave
-
- // FromDidClose is a file modification caused by closing a file.
- FromDidClose
-
- // 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
-)
-
-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"
- default:
- return "unknown file modification"
- }
-}
-
-func (s *Server) didOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
- uri := params.TextDocument.URI.SpanURI()
- if !uri.IsFile() {
- return nil
- }
- // 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(rstambler): This seems like it would continuously add new
- // views, but it won't because ViewOf only returns an error when there
- // are no views in the session. I don't know if that logic should go
- // here, or if we can continue to rely on that implementation detail.
- if _, err := s.session.ViewOf(uri); err != nil {
- dir := filepath.Dir(uri.Filename())
- if err := s.addFolders(ctx, []protocol.WorkspaceFolder{{
- URI: string(protocol.URIFromPath(dir)),
- Name: filepath.Base(dir),
- }}); err != nil {
- return err
- }
- }
- return s.didModifyFiles(ctx, []source.FileModification{{
- URI: uri,
- Action: source.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 {
- uri := params.TextDocument.URI.SpanURI()
- if !uri.IsFile() {
- return nil
- }
-
- text, err := s.changedText(ctx, uri, params.ContentChanges)
- if err != nil {
- return err
- }
- c := source.FileModification{
- URI: uri,
- Action: source.Change,
- Version: params.TextDocument.Version,
- Text: text,
- }
- if err := s.didModifyFiles(ctx, []source.FileModification{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 span.URI) 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.
- view, err := s.session.ViewOf(uri)
- if err != nil {
- return err
- }
- snapshot, release := view.Snapshot(ctx)
- isGenerated := source.IsGenerated(ctx, snapshot, uri)
- release()
-
- if !isGenerated {
- return nil
- }
- return s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
- Message: fmt.Sprintf("Do not edit this file! %s is a generated file.", uri.Filename()),
- Type: protocol.Warning,
- })
-}
-
-func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error {
- var modifications []source.FileModification
- for _, change := range params.Changes {
- uri := change.URI.SpanURI()
- if !uri.IsFile() {
- continue
- }
- action := changeTypeToFileAction(change.Type)
- modifications = append(modifications, source.FileModification{
- URI: uri,
- Action: action,
- OnDisk: true,
- })
- }
- return s.didModifyFiles(ctx, modifications, FromDidChangeWatchedFiles)
-}
-
-func (s *Server) didSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error {
- uri := params.TextDocument.URI.SpanURI()
- if !uri.IsFile() {
- return nil
- }
- c := source.FileModification{
- URI: uri,
- Action: source.Save,
- }
- if params.Text != nil {
- c.Text = []byte(*params.Text)
- }
- return s.didModifyFiles(ctx, []source.FileModification{c}, FromDidSave)
-}
-
-func (s *Server) didClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
- uri := params.TextDocument.URI.SpanURI()
- if !uri.IsFile() {
- return nil
- }
- return s.didModifyFiles(ctx, []source.FileModification{
- {
- URI: uri,
- Action: source.Close,
- Version: -1,
- Text: nil,
- },
- }, FromDidClose)
-}
-
-func (s *Server) didModifyFiles(ctx context.Context, modifications []source.FileModification, cause ModificationSource) error {
- diagnoseDone := make(chan struct{})
- if s.session.Options().VerboseWorkDoneProgress {
- work := s.progress.Start(ctx, DiagnosticWorkTitle(cause), "Calculating file diagnostics...", nil, nil)
- defer func() {
- go func() {
- <-diagnoseDone
- work.End("Done.")
- }()
- }()
- }
-
- onDisk := cause == FromDidChangeWatchedFiles
- delay := s.session.Options().ExperimentalWatchedFileDelay
- s.fileChangeMu.Lock()
- defer s.fileChangeMu.Unlock()
- if !onDisk || delay == 0 {
- // No delay: process the modifications immediately.
- return s.processModifications(ctx, modifications, onDisk, diagnoseDone)
- }
- // Debounce and batch up pending modifications from watched files.
- pending := &pendingModificationSet{
- diagnoseDone: diagnoseDone,
- changes: modifications,
- }
- // Invariant: changes appended to s.pendingOnDiskChanges are eventually
- // handled in the order they arrive. This guarantee is only partially
- // enforced here. Specifically:
- // 1. s.fileChangesMu ensures that the append below happens in the order
- // notifications were received, so that the changes within each batch are
- // ordered properly.
- // 2. The debounced func below holds s.fileChangesMu while processing all
- // changes in s.pendingOnDiskChanges, ensuring that no batches are
- // processed out of order.
- // 3. Session.ExpandModificationsToDirectories and Session.DidModifyFiles
- // process changes in order.
- s.pendingOnDiskChanges = append(s.pendingOnDiskChanges, pending)
- ctx = xcontext.Detach(ctx)
- okc := s.watchedFileDebouncer.debounce("", 0, time.After(delay))
- go func() {
- if ok := <-okc; !ok {
- return
- }
- s.fileChangeMu.Lock()
- var allChanges []source.FileModification
- // For accurate progress notifications, we must notify all goroutines
- // waiting for the diagnose pass following a didChangeWatchedFiles
- // notification. This is necessary for regtest assertions.
- var dones []chan struct{}
- for _, pending := range s.pendingOnDiskChanges {
- allChanges = append(allChanges, pending.changes...)
- dones = append(dones, pending.diagnoseDone)
- }
-
- allDone := make(chan struct{})
- if err := s.processModifications(ctx, allChanges, onDisk, allDone); err != nil {
- event.Error(ctx, "processing delayed file changes", err)
- }
- s.pendingOnDiskChanges = nil
- s.fileChangeMu.Unlock()
- <-allDone
- for _, done := range dones {
- close(done)
- }
- }()
- return nil
-}
-
-// processModifications update server state to reflect file changes, and
-// triggers diagnostics to run asynchronously. The diagnoseDone channel will be
-// closed once diagnostics complete.
-func (s *Server) processModifications(ctx context.Context, modifications []source.FileModification, onDisk bool, diagnoseDone chan struct{}) error {
- 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()
- close(diagnoseDone)
- 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)
-
- snapshots, releases, err := s.session.DidModifyFiles(ctx, modifications)
- if err != nil {
- close(diagnoseDone)
- return err
- }
-
- go func() {
- s.diagnoseSnapshots(snapshots, onDisk)
- for _, release := range releases {
- release()
- }
- close(diagnoseDone)
- }()
-
- // 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)
-}
-
-// 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 span.URI, changes []protocol.TextDocumentContentChangeEvent) ([]byte, error) {
- if len(changes) == 0 {
- return nil, errors.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 {
- return []byte(changes[0].Text), nil
- }
- return s.applyIncrementalChanges(ctx, uri, changes)
-}
-
-func (s *Server) applyIncrementalChanges(ctx context.Context, uri span.URI, changes []protocol.TextDocumentContentChangeEvent) ([]byte, error) {
- fh, err := s.session.GetFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- content, err := fh.Read()
- if err != nil {
- return nil, errors.Errorf("%w: file not found (%v)", jsonrpc2.ErrInternal, err)
- }
- for _, change := range changes {
- // Make sure to update column mapper along with the content.
- converter := span.NewContentConverter(uri.Filename(), content)
- m := &protocol.ColumnMapper{
- URI: uri,
- Converter: converter,
- Content: content,
- }
- if change.Range == nil {
- return nil, errors.Errorf("%w: unexpected nil range for change", jsonrpc2.ErrInternal)
- }
- spn, err := m.RangeSpan(*change.Range)
- if err != nil {
- return nil, err
- }
- if !spn.HasOffset() {
- return nil, errors.Errorf("%w: invalid range for content change", jsonrpc2.ErrInternal)
- }
- start, end := spn.Start().Offset(), spn.End().Offset()
- if end < start {
- return nil, errors.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()
- }
- return content, nil
-}
-
-func changeTypeToFileAction(ct protocol.FileChangeType) source.FileAction {
- switch ct {
- case protocol.Changed:
- return source.Change
- case protocol.Created:
- return source.Create
- case protocol.Deleted:
- return source.Delete
- }
- return source.UnknownFileAction
-}
diff --git a/internal/lsp/work/completion.go b/internal/lsp/work/completion.go
deleted file mode 100644
index 60b69f12f..000000000
--- a/internal/lsp/work/completion.go
+++ /dev/null
@@ -1,159 +0,0 @@
-// 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"
- "go/token"
- "os"
- "path/filepath"
- "sort"
- "strings"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- errors "golang.org/x/xerrors"
-)
-
-func Completion(ctx context.Context, snapshot source.Snapshot, fh source.VersionedFileHandle, 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, errors.Errorf("getting go.work file handle: %w", err)
- }
- spn, err := pw.Mapper.PointSpan(position)
- if err != nil {
- return nil, errors.Errorf("computing cursor position: %w", err)
- }
- rng, err := spn.Range(pw.Mapper.Converter)
- if err != nil {
- return nil, errors.Errorf("computing range: %w", err)
- }
-
- // Find the use statement the user is in.
- cursor := rng.Start - 1
- use, pathStart, _ := usePath(pw, cursor)
- if use == nil {
- return &protocol.CompletionList{}, nil
- }
- completingFrom := use.Path[:cursor-token.Pos(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.Filename()), 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.Walk(pathPrefixDir, func(wpath string, info os.FileInfo, err error) error {
- 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 info.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, errors.Errorf("walking to find completions: %w", err)
- }
-
- sort.Strings(completions)
-
- var items []protocol.CompletionItem
- 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/internal/lsp/work/diagnostics.go b/internal/lsp/work/diagnostics.go
deleted file mode 100644
index e583e60fd..000000000
--- a/internal/lsp/work/diagnostics.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// 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/internal/event"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
-)
-
-func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.VersionedFileIdentity][]*source.Diagnostic, error) {
- ctx, done := event.Start(ctx, "work.Diagnostics", tag.Snapshot.Of(snapshot.ID()))
- defer done()
-
- reports := map[source.VersionedFileIdentity][]*source.Diagnostic{}
- uri := snapshot.WorkFile()
- if uri == "" {
- return nil, nil
- }
- fh, err := snapshot.GetVersionedFile(ctx, uri)
- if err != nil {
- return nil, err
- }
- reports[fh.VersionedFileIdentity()] = []*source.Diagnostic{}
- diagnostics, err := DiagnosticsForWork(ctx, snapshot, fh)
- if err != nil {
- return nil, err
- }
- for _, d := range diagnostics {
- fh, err := snapshot.GetVersionedFile(ctx, d.URI)
- if err != nil {
- return nil, err
- }
- reports[fh.VersionedFileIdentity()] = append(reports[fh.VersionedFileIdentity()], d)
- }
-
- return reports, nil
-}
-
-func DiagnosticsForWork(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]*source.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 []*source.Diagnostic
- for _, use := range pw.File.Use {
- rng, err := source.LineToRange(pw.Mapper, fh.URI(), use.Syntax.Start, use.Syntax.End)
- if err != nil {
- return nil, err
- }
-
- modfh, err := snapshot.GetFile(ctx, modFileURI(pw, use))
- if err != nil {
- return nil, err
- }
- if _, err := modfh.Read(); err != nil && os.IsNotExist(err) {
- diagnostics = append(diagnostics, &source.Diagnostic{
- URI: fh.URI(),
- Range: rng,
- Severity: protocol.SeverityError,
- Source: source.UnknownError, // Do we need a new source for this?
- Message: fmt.Sprintf("directory %v does not contain a module", use.Path),
- })
- }
- }
- return diagnostics, nil
-}
-
-func modFileURI(pw *source.ParsedWorkFile, use *modfile.Use) span.URI {
- workdir := filepath.Dir(pw.URI.Filename())
-
- modroot := filepath.FromSlash(use.Path)
- if !filepath.IsAbs(modroot) {
- modroot = filepath.Join(workdir, modroot)
- }
-
- return span.URIFromPath(filepath.Join(modroot, "go.mod"))
-}
diff --git a/internal/lsp/work/format.go b/internal/lsp/work/format.go
deleted file mode 100644
index 35b804a73..000000000
--- a/internal/lsp/work/format.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-func Format(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]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.
- diff, err := snapshot.View().Options().ComputeEdits(fh.URI(), string(pw.Mapper.Content), string(formatted))
- if err != nil {
- return nil, err
- }
- return source.ToProtocolEdits(pw.Mapper, diff)
-}
diff --git a/internal/lsp/work/hover.go b/internal/lsp/work/hover.go
deleted file mode 100644
index 1699c5cba..000000000
--- a/internal/lsp/work/hover.go
+++ /dev/null
@@ -1,94 +0,0 @@
-// 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"
- "go/token"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- errors "golang.org/x/xerrors"
-)
-
-func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, position protocol.Position) (*protocol.Hover, error) {
- // We only provide hover information for the view's go.work file.
- if fh.URI() != snapshot.WorkFile() {
- 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, errors.Errorf("getting go.work file handle: %w", err)
- }
- spn, err := pw.Mapper.PointSpan(position)
- if err != nil {
- return nil, errors.Errorf("computing cursor position: %w", err)
- }
- hoverRng, err := spn.Range(pw.Mapper.Converter)
- if err != nil {
- return nil, errors.Errorf("computing hover range: %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, hoverRng.Start)
-
- // 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.GetFile(ctx, modFileURI(pw, use))
- if err != nil {
- return nil, errors.Errorf("getting modfile handle: %w", err)
- }
- pm, err := snapshot.ParseMod(ctx, modfh)
- if err != nil {
- return nil, errors.Errorf("getting modfile handle: %w", err)
- }
- mod := pm.File.Module.Mod
-
- // Get the range to highlight for the hover.
- rng, err := source.ByteOffsetsToRange(pw.Mapper, fh.URI(), pathStart, pathEnd)
- if err != nil {
- return nil, err
- }
- options := snapshot.View().Options()
- return &protocol.Hover{
- Contents: protocol.MarkupContent{
- Kind: options.PreferredContentFormat,
- Value: mod.Path,
- },
- Range: rng,
- }, nil
-}
-
-func usePath(pw *source.ParsedWorkFile, pos token.Pos) (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 token.Pos(pathStart) <= pos && pos <= token.Pos(pathEnd) {
- return u, pathStart, pathEnd
- }
- }
- return nil, 0, 0
-}
diff --git a/internal/lsp/workspace.go b/internal/lsp/workspace.go
deleted file mode 100644
index 1f01b3b3b..000000000
--- a/internal/lsp/workspace.go
+++ /dev/null
@@ -1,108 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-func (s *Server) didChangeWorkspaceFolders(ctx context.Context, params *protocol.DidChangeWorkspaceFoldersParams) error {
- event := params.Event
- for _, folder := range event.Removed {
- view := s.session.View(folder.Name)
- if view != nil {
- view.Shutdown(ctx)
- } else {
- return errors.Errorf("view %s for %v not found", folder.Name, folder.URI)
- }
- }
- return s.addFolders(ctx, event.Added)
-}
-
-func (s *Server) addView(ctx context.Context, name string, uri span.URI) (source.Snapshot, func(), error) {
- s.stateMu.Lock()
- state := s.state
- s.stateMu.Unlock()
- if state < serverInitialized {
- return nil, func() {}, errors.Errorf("addView called before server initialized")
- }
- options := s.session.Options().Clone()
- if err := s.fetchConfig(ctx, name, uri, options); err != nil {
- return nil, func() {}, err
- }
- _, snapshot, release, err := s.session.NewView(ctx, name, uri, options)
- return snapshot, release, err
-}
-
-func (s *Server) didChangeConfiguration(ctx context.Context, _ *protocol.DidChangeConfigurationParams) error {
- // Apply any changes to the session-level settings.
- options := s.session.Options().Clone()
- semanticTokensRegistered := options.SemanticTokens
- if err := s.fetchConfig(ctx, "", "", options); err != nil {
- return err
- }
- s.session.SetOptions(options)
-
- // Go through each view, getting and updating its configuration.
- for _, view := range s.session.Views() {
- options := s.session.Options().Clone()
- if err := s.fetchConfig(ctx, view.Name(), view.Folder(), options); err != nil {
- return err
- }
- view, err := view.SetOptions(ctx, options)
- if err != nil {
- return err
- }
- go func() {
- snapshot, release := view.Snapshot(ctx)
- defer release()
- s.diagnoseDetached(snapshot)
- }()
- }
-
- registration := semanticTokenRegistration(options.SemanticTypes, options.SemanticMods)
- // Update any session-specific registrations or unregistrations.
- if !semanticTokensRegistered && options.SemanticTokens {
- if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
- Registrations: []protocol.Registration{registration},
- }); err != nil {
- return err
- }
- } else if semanticTokensRegistered && !options.SemanticTokens {
- if err := s.client.UnregisterCapability(ctx, &protocol.UnregistrationParams{
- Unregisterations: []protocol.Unregistration{
- {
- ID: registration.ID,
- Method: registration.Method,
- },
- },
- }); err != nil {
- return err
- }
- }
- return nil
-}
-
-func semanticTokenRegistration(tokenTypes, tokenModifiers []string) protocol.Registration {
- return protocol.Registration{
- ID: "textDocument/semanticTokens",
- Method: "textDocument/semanticTokens",
- RegisterOptions: &protocol.SemanticTokensOptions{
- Legend: protocol.SemanticTokensLegend{
- // TODO(pjw): trim these to what we use (and an unused one
- // at position 0 of TokTypes, to catch typos)
- TokenTypes: tokenTypes,
- TokenModifiers: tokenModifiers,
- },
- Full: true,
- Range: true,
- },
- }
-}
diff --git a/internal/lsp/workspace_symbol.go b/internal/lsp/workspace_symbol.go
deleted file mode 100644
index 20c5763ab..000000000
--- a/internal/lsp/workspace_symbol.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// 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 lsp
-
-import (
- "context"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-func (s *Server) symbol(ctx context.Context, params *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) {
- ctx, done := event.Start(ctx, "lsp.Server.symbol")
- defer done()
-
- views := s.session.Views()
- matcher := s.session.Options().SymbolMatcher
- style := s.session.Options().SymbolStyle
- return source.WorkspaceSymbols(ctx, matcher, style, views, params.Query)
-}