From 23b142199f0089221e38dce435c41ecbd95e3ef0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:21:15 +0000 Subject: [PATCH] fix(deps): update all dependencies --- .github/workflows/build-go.yaml | 2 +- .github/workflows/build-on-hosted-runner.yaml | 2 +- Makefile | 2 +- go.mod | 90 +- go.sum | 210 +- oci/Containerfile | 12 +- tools/go.mod | 23 +- tools/go.sum | 54 +- .../go/exhaustruct/v5}/LICENSE | 2 +- .../go/exhaustruct/v5/analyzer/analyzer.go | 112 + .../go/exhaustruct/v5/analyzer/config.go | 134 + .../v5/analyzer/missing-fields-visitor.go | 381 ++ .../v5/analyzer/tag-migration-visitor.go | 111 + .../v5/internal/astutil/file-parser.go | 213 + .../go/exhaustruct/v5/internal/cache/cache.go | 100 + .../v5/internal/directive/directive.go | 95 + .../v5/internal/directive/scanner.go | 189 + .../exhaustruct/v5/internal/pattern/list.go | 63 + .../v5/internal/structure/origin-scanner.go | 121 + .../v5/internal/structure/processor.go | 254 + .../v5/internal/structure/struct.go | 224 + .../github.com/bombsimon/wsl/v5/.golangci.yml | 3 + .../github.com/bombsimon/wsl/v5/analyzer.go | 2 +- .../vendor/github.com/bombsimon/wsl/v5/wsl.go | 18 +- .../vendor/github.com/golangci/gofmt/Makefile | 9 + .../vendor/github.com/golangci/gofmt/gofmt.go | 73 + .../golangci/gofmt/gofmt/golangci.go | 133 - .../github.com/golangci/gofmt/gofmt/readme.md | 15 - .../golangci/gofmt/internal/.gitattributes | 1 + .../gofmt/{gofmt => internal}/LICENSE | 0 .../golangci/gofmt/{gofmt => internal}/doc.go | 2 +- .../gofmt/{gofmt => internal}/gofmt.go | 102 +- .../golangci/gofmt/internal/golangci.go | 51 + .../gofmt/{gofmt => internal}/internal.go | 9 +- .../gofmt/{gofmt => internal}/rewrite.go | 12 +- .../gofmt/{gofmt => internal}/simplify.go | 2 +- .../github.com/golangci/gofmt/readme.md | 46 + .../golangci-lint/v2/internal/cache/cache.go | 12 +- .../v2/internal/go/base/readme.md | 2 + .../v2/internal/go/cache/cache.go | 45 +- .../v2/internal/go/cache/default.go | 36 +- .../v2/internal/go/cache/hash.go | 3 + .../v2/internal/go/cache/readme.md | 6 +- .../v2/internal/go/cacheprog/cacheprog.go | 13 +- .../v2/internal/go/cacheprog/readme.md | 2 + .../golangci-lint/v2/internal/go/mmap/mmap.go | 19 + .../v2/internal/go/mmap/readme.md | 2 + .../v2/internal/go/quoted/readme.md | 2 + .../v2/internal/x/tools/diff/lcs/old.go | 17 +- .../v2/internal/x/tools/diff/lcs/sequence.go | 79 +- .../v2/internal/x/tools/diff/ndiff.go | 19 + .../v2/internal/x/tools/diff/readme.md | 2 + .../v2/internal/x/tools/diff/unified.go | 79 +- .../v2/internal/x/tools/driverutil/readme.md | 3 + .../v2/jsonschema/golangci.jsonschema.json | 143 +- .../jsonschema/golangci.next.jsonschema.json | 143 +- .../jsonschema/golangci.v2.12.jsonschema.json | 5438 +++++++++++++++++ .../golangci-lint/v2/pkg/commands/cache.go | 16 +- .../pkg/commands/internal/migrate/migrate.go | 3 +- .../internal/migrate/migrate_linters.go | 4 +- .../migrate/migrate_linters_exclusions.go | 10 +- .../migrate/migrate_linters_settings.go | 2 +- .../internal/migrate/migrate_output.go | 30 +- .../pkg/commands/internal/migrate/ptr/ptr.go | 2 - .../migrate/versionone/linters_settings.go | 2 +- .../internal/migrate/versionone/output.go | 6 +- .../golangci-lint/v2/pkg/commands/run.go | 6 +- .../v2/pkg/config/base_loader.go | 3 +- .../v2/pkg/config/formatters_settings.go | 13 +- .../v2/pkg/config/linters_settings.go | 44 +- .../v2/pkg/goanalysis/runner_action_cache.go | 49 +- .../pkg/goanalysis/runner_loadingpackage.go | 4 +- .../gci/internal/section/standard_list.go | 4 +- .../v2/pkg/goformatters/gofmt/gofmt.go | 2 +- .../v2/pkg/goformatters/gofumpt/gofumpt.go | 10 + .../v2/pkg/golinters/dupword/dupword.go | 7 +- .../golinters/exhaustruct/exhaustruct_v5.go | 34 + .../v2/pkg/golinters/fatcontext/fatcontext.go | 4 +- .../v2/pkg/golinters/funcorder/funcorder.go | 1 + .../v2/pkg/golinters/goconst/goconst.go | 48 +- .../v2/pkg/golinters/godoclint/godoclint.go | 18 +- .../v2/pkg/golinters/gofumpt/gofumpt.go | 2 +- .../gomoddirectives/gomoddirectives.go | 2 + .../v2/pkg/golinters/iface/iface.go | 10 +- .../v2/pkg/golinters/modernize/modernize.go | 10 + .../nonamedreturns/nonamedreturns.go | 3 +- .../v2/pkg/lint/lintersdb/builder_linter.go | 10 +- .../pkg/lint/lintersdb/builder_plugin_go.go | 6 + .../mgechev/revive/config/config.go | 97 +- .../mgechev/revive/formatter/checkstyle.go | 23 +- .../mgechev/revive/formatter/friendly.go | 2 +- .../mgechev/revive/formatter/json.go | 2 +- .../mgechev/revive/formatter/ndjson.go | 2 +- .../mgechev/revive/formatter/severity.go | 13 - .../mgechev/revive/formatter/stylish.go | 2 +- .../revive/internal/astutils/ast_utils.go | 17 + .../revive/internal/astutils/exit_funcs.go | 46 + .../mgechev/revive/internal/config/config.go | 12 + .../github.com/mgechev/revive/lint/config.go | 12 +- .../github.com/mgechev/revive/lint/failure.go | 15 + .../github.com/mgechev/revive/lint/file.go | 42 +- .../github.com/mgechev/revive/lint/linter.go | 11 + .../github.com/mgechev/revive/lint/package.go | 14 + .../mgechev/revive/rule/add_constant.go | 4 + .../mgechev/revive/rule/comment_spacings.go | 9 +- .../mgechev/revive/rule/deep_exit.go | 2 +- .../github.com/mgechev/revive/rule/defer.go | 15 +- .../mgechev/revive/rule/empty_block.go | 7 + .../mgechev/revive/rule/exported.go | 2 +- .../mgechev/revive/rule/line_length_limit.go | 99 +- .../mgechev/revive/rule/marshal_receiver.go | 76 + .../mgechev/revive/rule/multiline_if_init.go | 59 + .../mgechev/revive/rule/package_comments.go | 18 +- .../revive/rule/redundant_build_tag.go | 32 +- .../revive/rule/redundant_test_main_exit.go | 3 +- .../mgechev/revive/rule/struct_tag.go | 2 +- .../revive/rule/unconditional_recursion.go | 2 +- .../mgechev/revive/rule/unhandled_error.go | 22 + .../github.com/mgechev/revive/rule/use_any.go | 4 + .../mgechev/revive/rule/use_errors_new.go | 5 + .../mgechev/revive/rule/use_fmt_print.go | 2 +- .../mgechev/revive/rule/use_waitgroup_go.go | 44 +- .../github.com/mgechev/revive/rule/utils.go | 55 +- .../nunnatsa/ginkgolinter/config/config.go | 4 +- .../github.com/securego/gosec/v2/action.yml | 2 +- .../gosec/v2/analyzers/pathtraversal.go | 23 +- .../securego/gosec/v2/analyzers/util.go | 4 +- .../github.com/securego/gosec/v2/config.go | 5 +- .../securego/gosec/v2/issue/issue.go | 4 +- .../github.com/securego/gosec/v2/resolve.go | 151 +- .../github.com/securego/gosec/v2/rules/rsa.go | 2 +- .../securego/gosec/v2/taint/analyzer.go | 4 +- .../stretchr/testify/assert/assertions.go | 2 +- .../testify/assert/yaml/yaml_custom.go | 2 +- .../testify/assert/yaml/yaml_default.go | 6 +- .../stretchr/testify/assert/yaml/yaml_fail.go | 2 +- .../uudashr/iface/unusedmethod/doc.go | 3 + .../iface/unusedmethod/unusedmethod.go | 226 + .../honnef.co/go/tools/analysis/code/code.go | 14 +- .../honnef.co/go/tools/analysis/code/visit.go | 4 +- .../go/tools/analysis/dfa/dense/flow.go | 60 + .../go/tools/analysis/dfa/dense/forward.go | 271 + .../honnef.co/go/tools/analysis/dfa/dot.go | 48 + .../go/tools/analysis/dfa/lattice.go | 147 + .../tools/analysis/facts/nilness/nilness.go | 871 ++- .../analysis/facts/typedness/typedness.go | 253 - .../go/tools/analysis/report/report.go | 3 +- .../honnef.co/go/tools/go/ast/astutil/util.go | 11 - .../vendor/honnef.co/go/tools/go/ir/UPSTREAM | 9 - .../vendor/honnef.co/go/tools/go/ir/block.go | 133 + .../honnef.co/go/tools/go/ir/blockopt.go | 19 +- .../honnef.co/go/tools/go/ir/builder.go | 1427 +++-- .../vendor/honnef.co/go/tools/go/ir/const.go | 276 +- .../vendor/honnef.co/go/tools/go/ir/create.go | 167 +- tools/vendor/honnef.co/go/tools/go/ir/doc.go | 68 +- tools/vendor/honnef.co/go/tools/go/ir/dom.go | 385 +- tools/vendor/honnef.co/go/tools/go/ir/emit.go | 205 +- tools/vendor/honnef.co/go/tools/go/ir/func.go | 566 +- tools/vendor/honnef.co/go/tools/go/ir/html.go | 1126 ---- .../honnef.co/go/tools/go/ir/instantiate.go | 161 + .../honnef.co/go/tools/go/ir/irutil/load.go | 88 +- .../honnef.co/go/tools/go/ir/irutil/stub.go | 73 +- .../honnef.co/go/tools/go/ir/irutil/switch.go | 20 +- .../honnef.co/go/tools/go/ir/irutil/util.go | 63 +- .../honnef.co/go/tools/go/ir/irutil/visit.go | 154 +- tools/vendor/honnef.co/go/tools/go/ir/lift.go | 952 +-- .../vendor/honnef.co/go/tools/go/ir/lvalue.go | 2 +- .../honnef.co/go/tools/go/ir/methods.go | 311 +- tools/vendor/honnef.co/go/tools/go/ir/mode.go | 40 +- .../vendor/honnef.co/go/tools/go/ir/print.go | 335 +- .../vendor/honnef.co/go/tools/go/ir/sanity.go | 253 +- tools/vendor/honnef.co/go/tools/go/ir/scc.go | 106 + .../vendor/honnef.co/go/tools/go/ir/source.go | 96 +- tools/vendor/honnef.co/go/tools/go/ir/ssa.go | 893 +-- .../vendor/honnef.co/go/tools/go/ir/subst.go | 577 ++ tools/vendor/honnef.co/go/tools/go/ir/task.go | 103 + .../honnef.co/go/tools/go/ir/typeset.go | 175 + tools/vendor/honnef.co/go/tools/go/ir/util.go | 366 +- .../honnef.co/go/tools/go/ir/wrappers.go | 303 +- .../vendor/honnef.co/go/tools/go/ir/write.go | 5 - .../go/tools/go/types/typeutil/typeparams.go | 12 +- .../go/tools/go/types/typeutil/unify.go | 345 ++ .../go/tools/go/types/typeutil/util.go | 11 +- .../go/tools/internal/iterutil/iterutil.go | 21 + .../tools/internal/passes/buildir/buildir.go | 17 +- .../go/tools/internal/sharedcheck/lint.go | 3 +- .../xtools-internal/aliases/aliases.go | 18 + .../analysis}/typeindex/typeindex.go | 6 +- .../internal/xtools-internal/astutil/clone.go | 71 + .../xtools-internal/astutil/comment.go | 143 + .../xtools-internal/astutil/cursor.go | 38 + .../internal/xtools-internal/astutil/equal.go | 107 + .../xtools-internal/astutil/fields.go | 35 + .../internal/xtools-internal/astutil/purge.go | 93 + .../xtools-internal/astutil/stringlit.go | 103 + .../xtools-internal/astutil/unpack.go | 61 + .../internal/xtools-internal/astutil/util.go | 261 + .../xtools-internal/graph/allpaths.go | 24 + .../internal/xtools-internal/graph/compact.go | 89 + .../internal/xtools-internal/graph/graph.go | 33 + .../internal/xtools-internal/graph/index.go | 96 + .../internal/xtools-internal/graph/order.go | 94 + .../xtools-internal/graph/reachable.go | 23 + .../internal/xtools-internal/graph/scc.go | 39 + .../xtools-internal/graph/shortest.go | 45 + .../xtools-internal/graph/transpose.go | 51 + .../xtools-internal/moreiters/iters.go | 63 + .../xtools-internal/typeparams/common.go | 68 + .../xtools-internal/typeparams/coretype.go | 157 + .../xtools-internal/typeparams/free.go | 129 + .../xtools-internal/typeparams/normalize.go | 214 + .../xtools-internal/typeparams/termlist.go | 169 + .../xtools-internal/typeparams/typeterm.go | 172 + .../typesinternal/classify_call.go | 137 + .../xtools-internal/typesinternal/element.go | 137 + .../typesinternal/errorcode.go | 1558 +++++ .../typesinternal/errorcode_string.go | 179 + .../xtools-internal/typesinternal/fx.go | 88 + .../xtools-internal/typesinternal/isnamed.go | 71 + .../typesinternal/qualifier.go | 54 + .../xtools-internal/typesinternal/recv.go | 44 + .../typesinternal/typeindex/typeindex.go | 94 +- .../xtools-internal/typesinternal/types.go | 272 + .../xtools-internal/typesinternal/varkind.go | 23 + .../typesinternal/varkind_go124.go | 39 + .../typesinternal/zerovalue.go | 381 ++ .../xtools-internal/versions/features.go | 49 + .../xtools-internal/versions/gover.go | 172 + .../xtools-internal/versions/types.go | 33 + .../xtools-internal/versions/versions.go | 57 + .../honnef.co/go/tools/knowledge/arg.go | 2 + .../go/tools/knowledge/deprecated.go | 5 + .../go/tools/quickfix/qf1002/qf1002.go | 2 +- .../go/tools/quickfix/qf1003/qf1003.go | 2 +- .../go/tools/quickfix/qf1004/qf1004.go | 4 +- .../honnef.co/go/tools/simple/s1005/s1005.go | 10 +- .../go/tools/staticcheck/analysis.go | 4 +- .../go/tools/staticcheck/sa1002/sa1002.go | 5 +- .../go/tools/staticcheck/sa1012/sa1012.go | 5 +- .../go/tools/staticcheck/sa1014/sa1014.go | 6 +- .../go/tools/staticcheck/sa1019/sa1019.go | 82 +- .../go/tools/staticcheck/sa1020/sa1020.go | 7 +- .../go/tools/staticcheck/sa1025/sa1025.go | 2 +- .../go/tools/staticcheck/sa1026/sa1026.go | 2 + .../go/tools/staticcheck/sa1030/sa1030.go | 2 +- .../go/tools/staticcheck/sa2000/sa2000.go | 5 +- .../go/tools/staticcheck/sa2001/sa2001.go | 3 +- .../go/tools/staticcheck/sa2003/sa2003.go | 16 +- .../go/tools/staticcheck/sa4003/sa4003.go | 128 +- .../go/tools/staticcheck/sa4005/sa4005.go | 6 +- .../go/tools/staticcheck/sa4006/sa4006.go | 33 +- .../go/tools/staticcheck/sa4008/sa4008.go | 18 +- .../go/tools/staticcheck/sa4009/sa4009.go | 3 +- .../go/tools/staticcheck/sa4010/sa4010.go | 58 +- .../go/tools/staticcheck/sa4017/sa4017.go | 3 +- .../go/tools/staticcheck/sa4023/sa4023.go | 54 +- .../go/tools/staticcheck/sa4031/sa4031.go | 2 - .../go/tools/staticcheck/sa5007/sa5007.go | 9 +- .../go/tools/staticcheck/sa5008/jsonv2.go | 2 +- .../go/tools/staticcheck/sa5009/sa5009.go | 2 +- .../go/tools/staticcheck/sa5011/sa5011.go | 221 - .../go/tools/staticcheck/sa5012/sa5012.go | 5 +- .../go/tools/staticcheck/sa6001/sa6001.go | 29 +- .../go/tools/staticcheck/sa6002/sa6002.go | 2 +- .../go/tools/staticcheck/sa6005/sa6005.go | 7 +- .../go/tools/staticcheck/sa9005/sa9005.go | 2 + .../go/tools/staticcheck/sa9010/sa9010.go | 60 + .../honnef.co/go/tools/unused/implements.go | 75 +- .../honnef.co/go/tools/unused/unused.go | 16 +- tools/vendor/modules.txt | 70 +- tools/vendor/mvdan.cc/unparam/check/check.go | 22 +- vendor/charm.land/bubbles/v2/LICENSE | 21 + vendor/charm.land/bubbles/v2/cursor/cursor.go | 241 + .../bubbles/v2/internal/runeutil/runeutil.go | 102 + vendor/charm.land/bubbles/v2/key/key.go | 140 + .../charm.land/bubbles/v2/textinput/styles.go | 96 + .../bubbles/v2/textinput/textinput.go | 968 +++ vendor/charm.land/bubbletea/v2/.gitattributes | 1 + vendor/charm.land/bubbletea/v2/.gitignore | 23 + vendor/charm.land/bubbletea/v2/.golangci.yml | 47 + .../charm.land/bubbletea/v2/.goreleaser.yml | 5 + vendor/charm.land/bubbletea/v2/LICENSE | 21 + vendor/charm.land/bubbletea/v2/README.md | 402 ++ vendor/charm.land/bubbletea/v2/Taskfile.yaml | 14 + .../bubbletea/v2/UPGRADE_GUIDE_V2.md | 573 ++ vendor/charm.land/bubbletea/v2/clipboard.go | 70 + vendor/charm.land/bubbletea/v2/color.go | 91 + vendor/charm.land/bubbletea/v2/commands.go | 175 + .../bubbletea/v2/cursed_renderer.go | 854 +++ vendor/charm.land/bubbletea/v2/cursor.go | 28 + vendor/charm.land/bubbletea/v2/environ.go | 34 + vendor/charm.land/bubbletea/v2/exec.go | 129 + vendor/charm.land/bubbletea/v2/focus.go | 9 + vendor/charm.land/bubbletea/v2/input.go | 54 + vendor/charm.land/bubbletea/v2/key.go | 371 ++ vendor/charm.land/bubbletea/v2/keyboard.go | 59 + vendor/charm.land/bubbletea/v2/logging.go | 53 + vendor/charm.land/bubbletea/v2/mod.go | 27 + vendor/charm.land/bubbletea/v2/mouse.go | 144 + .../charm.land/bubbletea/v2/nil_renderer.go | 53 + vendor/charm.land/bubbletea/v2/options.go | 168 + vendor/charm.land/bubbletea/v2/paste.go | 20 + vendor/charm.land/bubbletea/v2/profile.go | 15 + vendor/charm.land/bubbletea/v2/raw.go | 37 + vendor/charm.land/bubbletea/v2/renderer.go | 104 + vendor/charm.land/bubbletea/v2/screen.go | 68 + .../charm.land/bubbletea/v2/signals_unix.go | 33 + .../bubbletea/v2/signals_windows.go | 10 + vendor/charm.land/bubbletea/v2/tea.go | 1437 +++++ vendor/charm.land/bubbletea/v2/termcap.go | 48 + vendor/charm.land/bubbletea/v2/termios_bsd.go | 13 + .../charm.land/bubbletea/v2/termios_other.go | 8 + .../charm.land/bubbletea/v2/termios_unix.go | 14 + .../bubbletea/v2/termios_windows.go | 11 + vendor/charm.land/bubbletea/v2/tty.go | 136 + vendor/charm.land/bubbletea/v2/tty_unix.go | 47 + vendor/charm.land/bubbletea/v2/tty_windows.go | 64 + vendor/charm.land/bubbletea/v2/xterm.go | 22 + vendor/charm.land/lipgloss/v2/.editorconfig | 18 + vendor/charm.land/lipgloss/v2/.gitattributes | 1 + vendor/charm.land/lipgloss/v2/.gitignore | 3 + vendor/charm.land/lipgloss/v2/.golangci.yml | 47 + vendor/charm.land/lipgloss/v2/.goreleaser.yml | 5 + vendor/charm.land/lipgloss/v2/LICENSE | 21 + vendor/charm.land/lipgloss/v2/README.md | 996 +++ vendor/charm.land/lipgloss/v2/Taskfile.yaml | 24 + .../lipgloss/v2/UPGRADE_GUIDE_V2.md | 504 ++ vendor/charm.land/lipgloss/v2/align.go | 82 + vendor/charm.land/lipgloss/v2/ansi_unix.go | 8 + vendor/charm.land/lipgloss/v2/ansi_windows.go | 30 + vendor/charm.land/lipgloss/v2/blending.go | 196 + vendor/charm.land/lipgloss/v2/borders.go | 587 ++ vendor/charm.land/lipgloss/v2/canvas.go | 88 + vendor/charm.land/lipgloss/v2/color.go | 359 ++ vendor/charm.land/lipgloss/v2/get.go | 657 ++ vendor/charm.land/lipgloss/v2/join.go | 175 + vendor/charm.land/lipgloss/v2/layer.go | 327 + vendor/charm.land/lipgloss/v2/lipgloss.go | 3 + vendor/charm.land/lipgloss/v2/position.go | 134 + vendor/charm.land/lipgloss/v2/query.go | 92 + vendor/charm.land/lipgloss/v2/ranges.go | 48 + vendor/charm.land/lipgloss/v2/runes.go | 43 + vendor/charm.land/lipgloss/v2/set.go | 932 +++ vendor/charm.land/lipgloss/v2/size.go | 40 + vendor/charm.land/lipgloss/v2/style.go | 637 ++ vendor/charm.land/lipgloss/v2/terminal.go | 124 + vendor/charm.land/lipgloss/v2/unset.go | 359 ++ vendor/charm.land/lipgloss/v2/whitespace.go | 76 + vendor/charm.land/lipgloss/v2/wrap.go | 107 + vendor/charm.land/lipgloss/v2/writer.go | 160 + .../apps/confidential/confidential.go | 25 +- .../apps/internal/base/storage/items.go | 2 +- .../internal/oauth/ops/authority/authority.go | 19 +- .../ops/wstrust/defs/wstrust_mex_document.go | 17 +- .../apps/internal/version/version.go | 2 +- .../apps/managedidentity/managedidentity.go | 180 +- .../apps/managedidentity/servicefabric.go | 120 +- .../IBM/vpc-go-sdk/common/version.go | 2 +- .../github.com/IBM/vpc-go-sdk/vpcv1/vpc_v1.go | 256 +- .../aws-sdk-go-v2/aws/go_module_metadata.go | 2 +- .../aws/signer/internal/v4/headers.go | 4 + .../internal/configsources/CHANGELOG.md | 9 + .../configsources/go_module_metadata.go | 2 +- .../internal/endpoints/v2/CHANGELOG.md | 9 + .../endpoints/v2/go_module_metadata.go | 2 +- .../aws-sdk-go-v2/service/ec2/CHANGELOG.md | 11 + .../api_op_AssociateApplicationStatusCheck.go | 20 +- .../api_op_CreateApplicationStatusCheck.go | 27 +- .../api_op_DeleteApplicationStatusCheck.go | 6 +- .../ec2/api_op_DescribeApplicationStatus.go | 5 +- .../api_op_DescribeApplicationStatusChecks.go | 7 +- ...isableApplicationStatusCheckSuppression.go | 8 +- ...i_op_DisassociateApplicationStatusCheck.go | 6 +- ...EnableApplicationStatusCheckSuppression.go | 8 +- .../api_op_ModifyApplicationStatusCheck.go | 6 +- .../service/ec2/deserializers.go | 141 + .../service/ec2/go_module_metadata.go | 2 +- .../aws-sdk-go-v2/service/ec2/serializers.go | 80 + .../aws-sdk-go-v2/service/ec2/types/enums.go | 42 + .../aws-sdk-go-v2/service/ec2/types/types.go | 100 +- .../internal/accept-encoding/CHANGELOG.md | 4 + .../accept-encoding/go_module_metadata.go | 2 +- .../internal/presigned-url/CHANGELOG.md | 9 + .../presigned-url/go_module_metadata.go | 2 +- vendor/github.com/aws/smithy-go/CHANGELOG.md | 31 + .../aws/smithy-go/go_module_metadata.go | 2 +- .../smithy-go/transport/http/eventstream.go | 12 +- .../http/middleware_content_length.go | 3 + .../aws/smithy-go/transport/http/request.go | 9 + .../charmbracelet/ultraviolet/.gitattributes | 1 + .../charmbracelet/ultraviolet/.golangci.yml | 47 + .../charmbracelet/ultraviolet/.goreleaser.yml | 5 + .../charmbracelet/ultraviolet/LICENSE | 21 + .../charmbracelet/ultraviolet/README.md | 125 + .../charmbracelet/ultraviolet/TUTORIAL.md | 207 + .../charmbracelet/ultraviolet/border.go | 231 + .../charmbracelet/ultraviolet/buffer.go | 795 +++ .../ultraviolet/cancelreader_other.go | 16 + .../ultraviolet/cancelreader_windows.go | 139 + .../charmbracelet/ultraviolet/cell.go | 459 ++ .../charmbracelet/ultraviolet/console.go | 219 + .../charmbracelet/ultraviolet/console_unix.go | 13 + .../ultraviolet/console_windows.go | 13 + .../charmbracelet/ultraviolet/cursor.go | 22 + .../charmbracelet/ultraviolet/decoder.go | 2103 +++++++ .../charmbracelet/ultraviolet/doc.go | 3 + .../charmbracelet/ultraviolet/environ.go | 32 + .../charmbracelet/ultraviolet/event.go | 567 ++ .../charmbracelet/ultraviolet/key.go | 762 +++ .../charmbracelet/ultraviolet/key_table.go | 664 ++ .../charmbracelet/ultraviolet/logger.go | 6 + .../charmbracelet/ultraviolet/mouse.go | 98 + .../charmbracelet/ultraviolet/poll.go | 38 + .../charmbracelet/ultraviolet/poll_bsd.go | 165 + .../charmbracelet/ultraviolet/poll_default.go | 12 + .../ultraviolet/poll_fallback.go | 158 + .../charmbracelet/ultraviolet/poll_linux.go | 175 + .../charmbracelet/ultraviolet/poll_select.go | 156 + .../charmbracelet/ultraviolet/poll_solaris.go | 12 + .../charmbracelet/ultraviolet/poll_windows.go | 217 + .../charmbracelet/ultraviolet/styled.go | 337 + .../charmbracelet/ultraviolet/tabstop.go | 142 + .../charmbracelet/ultraviolet/terminal.go | 350 ++ .../ultraviolet/terminal_bsdly.go | 10 + .../ultraviolet/terminal_bsdly_other.go | 8 + .../ultraviolet/terminal_other.go | 22 + .../ultraviolet/terminal_reader.go | 503 ++ .../ultraviolet/terminal_reader_other.go | 13 + .../ultraviolet/terminal_reader_windows.go | 336 + .../ultraviolet/terminal_renderer.go | 1590 +++++ .../terminal_renderer_hardscroll.go | 243 + .../ultraviolet/terminal_renderer_hashmap.go | 298 + .../ultraviolet/terminal_screen.go | 825 +++ .../ultraviolet/terminal_tabdly.go | 10 + .../ultraviolet/terminal_tabdly_other.go | 8 + .../ultraviolet/terminal_unix.go | 78 + .../ultraviolet/terminal_windows.go | 85 + .../charmbracelet/ultraviolet/tty.go | 42 + .../charmbracelet/ultraviolet/tty_other.go | 26 + .../charmbracelet/ultraviolet/tty_unix.go | 39 + .../charmbracelet/ultraviolet/tty_windows.go | 39 + .../charmbracelet/ultraviolet/utils.go | 15 + .../charmbracelet/ultraviolet/uv.go | 359 ++ .../charmbracelet/ultraviolet/winch.go | 62 + .../charmbracelet/ultraviolet/winch_other.go | 17 + .../charmbracelet/ultraviolet/winch_unix.go | 50 + .../charmbracelet/ultraviolet/window.go | 134 + .../charmbracelet/x/ansi/kitty/decoder.go | 86 + .../charmbracelet/x/ansi/kitty/encoder.go | 64 + .../charmbracelet/x/ansi/kitty/graphics.go | 414 ++ .../charmbracelet/x/ansi/kitty/options.go | 386 ++ .../charmbracelet/x/ansi/kitty/writer.go | 192 + .../charmbracelet/x/termios/LICENSE | 21 + .../charmbracelet/x/termios/bit_bsd.go | 7 + .../charmbracelet/x/termios/bit_darwin.go | 7 + .../charmbracelet/x/termios/bit_other.go | 7 + .../charmbracelet/x/termios/syscalls_bsd.go | 11 + .../x/termios/syscalls_darwin.go | 10 + .../charmbracelet/x/termios/syscalls_linux.go | 14 + .../charmbracelet/x/termios/termios.go | 249 + .../charmbracelet/x/termios/termios_bsd.go | 13 + .../charmbracelet/x/termios/termios_linux.go | 13 + .../charmbracelet/x/termios/termios_other.go | 15 + .../x/termios/termios_solaris.go | 26 + .../charmbracelet/x/windows/LICENSE | 21 + .../github.com/charmbracelet/x/windows/doc.go | 4 + .../x/windows/syscall_windows.go | 14 + .../charmbracelet/x/windows/types.go | 221 + .../charmbracelet/x/windows/types_windows.go | 142 + .../x/windows/zsyscall_windows.go | 77 + vendor/github.com/cheggaaa/pb/.travis.yml | 12 - vendor/github.com/cheggaaa/pb/LICENSE | 12 - vendor/github.com/cheggaaa/pb/README.md | 129 - vendor/github.com/cheggaaa/pb/README_V1.md | 175 - vendor/github.com/cheggaaa/pb/format.go | 125 - vendor/github.com/cheggaaa/pb/pb.go | 506 -- vendor/github.com/cheggaaa/pb/pb_appengine.go | 11 - vendor/github.com/cheggaaa/pb/pb_plan9.go | 70 - vendor/github.com/cheggaaa/pb/pb_win.go | 143 - vendor/github.com/cheggaaa/pb/pb_x.go | 118 - vendor/github.com/cheggaaa/pb/pool.go | 104 - vendor/github.com/cheggaaa/pb/pool_win.go | 45 - vendor/github.com/cheggaaa/pb/pool_x.go | 29 - vendor/github.com/cheggaaa/pb/reader.go | 26 - vendor/github.com/cheggaaa/pb/runecount.go | 17 - vendor/github.com/cheggaaa/pb/termios_bsd.go | 9 - vendor/github.com/cheggaaa/pb/termios_sysv.go | 13 - vendor/github.com/cheggaaa/pb/writer.go | 26 - .../evertras/bubble-table/table/border.go | 265 +- .../evertras/bubble-table/table/calc.go | 16 - .../evertras/bubble-table/table/cell.go | 49 +- .../evertras/bubble-table/table/column.go | 6 +- .../evertras/bubble-table/table/data.go | 18 +- .../evertras/bubble-table/table/dimensions.go | 77 +- .../evertras/bubble-table/table/events.go | 2 +- .../evertras/bubble-table/table/filter.go | 123 +- .../evertras/bubble-table/table/footer.go | 70 +- .../evertras/bubble-table/table/header.go | 72 +- .../evertras/bubble-table/table/keys.go | 4 +- .../evertras/bubble-table/table/model.go | 52 +- .../evertras/bubble-table/table/options.go | 162 +- .../evertras/bubble-table/table/overflow.go | 2 +- .../evertras/bubble-table/table/pagination.go | 133 +- .../evertras/bubble-table/table/row.go | 147 +- .../evertras/bubble-table/table/sort.go | 22 + .../evertras/bubble-table/table/strlimit.go | 7 +- .../evertras/bubble-table/table/update.go | 18 +- .../evertras/bubble-table/table/view.go | 46 +- .../github.com/go-openapi/swag/.golangci.yml | 2 +- .../go-openapi/swag/CONTRIBUTORS.md | 4 +- .../go-openapi/swag/fileutils/doc.go | 14 +- .../go-openapi/swag/fileutils/file.go | 9 +- .../go-openapi/swag/fileutils/fs.go | 106 + .../go-openapi/swag/fileutils/mapfs.go | 366 ++ .../go-openapi/swag/fileutils/opaque.go | 98 + .../go-openapi/swag/fileutils/overlay.go | 345 ++ .../go-openapi/swag/fileutils/path.go | 20 +- .../jsonutils/adapters/stdlib/json/options.go | 5 + vendor/github.com/go-openapi/swag/revive.toml | 18 + vendor/github.com/muesli/reflow/ansi/ansi.go | 7 - .../github.com/muesli/reflow/ansi/buffer.go | 40 - .../github.com/muesli/reflow/ansi/writer.go | 76 - .../muesli/reflow/truncate/truncate.go | 120 - .../muesli/reflow/wordwrap/wordwrap.go | 167 - .../sdk/go/aws/internal/pulumiUtilities.go | 4 +- .../sdk/go/aws/pulumi-plugin.json | 2 +- .../sdk/v7/go/aws/ec2/amiLaunchPermission.go | 15 + .../sdk/v7/go/aws/internal/pulumiUtilities.go | 4 +- .../sdk/v7/go/aws/lb/pulumiTypes.go | 89 +- .../sdk/v7/go/aws/pulumi-plugin.json | 2 +- .../pulumi-aws/sdk/v7/go/aws/pulumiEnums.go | 10 + .../pulumi-aws/sdk/v7/go/aws/pulumiTypes.go | 36 + .../github.com/pulumi/pulumi/sdk/v3/.version | 2 +- .../sdk/v3/go/common/apitype/service.go | 10 + .../pulumi/pulumi/sdk/v3/go/common/env/env.go | 4 + .../pulumi/sdk/v3/go/common/esc/expr.go | 2 +- .../pulumi/sdk/v3/go/common/esc/value.go | 2 +- .../sdk/v3/go/common/workspace/paths.go | 16 - .../sdk/v3/go/common/workspace/plugins.go | 43 - vendor/go.opentelemetry.io/otel/.golangci.yml | 3 +- vendor/go.opentelemetry.io/otel/CHANGELOG.md | 32 +- vendor/go.opentelemetry.io/otel/Makefile | 30 +- vendor/go.opentelemetry.io/otel/README.md | 7 + vendor/go.opentelemetry.io/otel/VERSIONING.md | 6 + .../otel/attribute/hash.go | 70 +- .../otel/attribute/internal/xxhash/xxhash.go | 5 + .../go.opentelemetry.io/otel/attribute/set.go | 3 + .../otlp/otlplog/otlploggrpc/client.go | 28 +- .../otlp/otlplog/otlploggrpc/config.go | 92 +- .../exporters/otlp/otlplog/otlploggrpc/doc.go | 28 +- .../otlp/otlplog/otlploggrpc/exporter.go | 22 +- .../internal/observ/instrumentation.go | 16 +- .../otlploggrpc/internal/observ/target.go | 18 +- .../otlploggrpc/internal/partialsuccess.go | 11 +- .../otlploggrpc/internal/retry/retry.go | 50 +- .../otlploggrpc/internal/transform/log.go | 46 +- .../otlplog/otlploggrpc/internal/version.go | 2 +- .../otlploggrpc/internal/x/features.go | 6 +- .../otlp/otlplog/otlploggrpc/internal/x/x.go | 4 +- .../otlptracegrpc/internal/observ/target.go | 18 +- .../internal/otlpconfig/envconfig.go | 21 + .../internal/otlpconfig/options.go | 25 + .../internal/otlpconfig/optiontypes.go | 12 + .../otlptracegrpc/internal/retry/retry.go | 50 +- .../otlptracegrpc/internal/version.go | 2 +- .../otlptrace/otlptracegrpc/internal/x/x.go | 4 +- .../otel/exporters/otlp/otlptrace/version.go | 2 +- vendor/go.opentelemetry.io/otel/log/doc.go | 16 +- .../otel/log/embedded/embedded.go | 18 +- .../otel/log/global/log.go | 6 +- .../otel/log/internal/global/log.go | 6 +- vendor/go.opentelemetry.io/otel/log/logger.go | 50 +- .../go.opentelemetry.io/otel/log/noop/noop.go | 9 +- .../go.opentelemetry.io/otel/log/provider.go | 6 +- vendor/go.opentelemetry.io/otel/log/record.go | 32 +- .../go.opentelemetry.io/otel/log/severity.go | 33 +- vendor/go.opentelemetry.io/otel/renovate.json | 7 + .../otel/sdk/internal/attrnorm/truncate.go | 24 +- .../otel/sdk/internal/x/x.go | 4 +- .../go.opentelemetry.io/otel/sdk/log/batch.go | 43 +- .../go.opentelemetry.io/otel/sdk/log/doc.go | 31 +- .../otel/sdk/log/exporter.go | 47 +- .../sdk/log/internal/attrnorm/truncate.go | 24 +- .../internal/observ/batch_log_processor.go | 9 +- .../otel/sdk/log/internal/observ/doc.go | 3 +- .../internal/observ/simple_log_processor.go | 47 +- .../otel/sdk/log/internal/x/features.go | 6 +- .../otel/sdk/log/internal/x/x.go | 4 +- .../otel/sdk/log/logger.go | 28 +- .../otel/sdk/log/processor.go | 40 +- .../otel/sdk/log/provider.go | 60 +- .../otel/sdk/log/record.go | 95 +- .../go.opentelemetry.io/otel/sdk/log/ring.go | 10 +- .../otel/sdk/log/setting.go | 24 +- .../otel/sdk/log/simple.go | 18 +- .../otel/sdk/resource/resource.go | 3 + .../internal/observ/simple_span_processor.go | 40 +- .../otel/sdk/trace/simple_span_processor.go | 20 +- .../otel/sdk/trace/span.go | 31 + .../go.opentelemetry.io/otel/sdk/version.go | 2 +- vendor/go.opentelemetry.io/otel/version.go | 2 +- vendor/go.opentelemetry.io/otel/versions.yaml | 8 +- .../grpc/internal/envconfig/envconfig.go | 12 +- .../grpc/internal/mem/buffer_pool.go | 14 + .../grpc/internal/transport/handler_server.go | 2 +- .../grpc/internal/transport/http2_client.go | 2 +- .../grpc/internal/transport/http2_server.go | 2 +- .../grpc/internal/transport/transport.go | 110 +- .../google.golang.org/grpc/mem/buffer_pool.go | 4 - vendor/google.golang.org/grpc/mem/buffers.go | 6 +- vendor/google.golang.org/grpc/version.go | 2 +- vendor/modules.txt | 116 +- 612 files changed, 55607 insertions(+), 9697 deletions(-) rename {vendor/github.com/muesli/reflow => tools/vendor/dev.gaijin.team/go/exhaustruct/v5}/LICENSE (96%) create mode 100644 tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/analyzer.go create mode 100644 tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/config.go create mode 100644 tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/missing-fields-visitor.go create mode 100644 tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/tag-migration-visitor.go create mode 100644 tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/astutil/file-parser.go create mode 100644 tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/cache/cache.go create mode 100644 tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/directive/directive.go create mode 100644 tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/directive/scanner.go create mode 100644 tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/pattern/list.go create mode 100644 tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/structure/origin-scanner.go create mode 100644 tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/structure/processor.go create mode 100644 tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/structure/struct.go create mode 100644 tools/vendor/github.com/golangci/gofmt/Makefile create mode 100644 tools/vendor/github.com/golangci/gofmt/gofmt.go delete mode 100644 tools/vendor/github.com/golangci/gofmt/gofmt/golangci.go delete mode 100644 tools/vendor/github.com/golangci/gofmt/gofmt/readme.md create mode 100644 tools/vendor/github.com/golangci/gofmt/internal/.gitattributes rename tools/vendor/github.com/golangci/gofmt/{gofmt => internal}/LICENSE (100%) rename tools/vendor/github.com/golangci/gofmt/{gofmt => internal}/doc.go (99%) rename tools/vendor/github.com/golangci/gofmt/{gofmt => internal}/gofmt.go (89%) create mode 100644 tools/vendor/github.com/golangci/gofmt/internal/golangci.go rename tools/vendor/github.com/golangci/gofmt/{gofmt => internal}/internal.go (96%) rename tools/vendor/github.com/golangci/gofmt/{gofmt => internal}/rewrite.go (96%) rename tools/vendor/github.com/golangci/gofmt/{gofmt => internal}/simplify.go (99%) create mode 100644 tools/vendor/github.com/golangci/gofmt/readme.md create mode 100644 tools/vendor/github.com/golangci/golangci-lint/v2/jsonschema/golangci.v2.12.jsonschema.json create mode 100644 tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/exhaustruct/exhaustruct_v5.go delete mode 100644 tools/vendor/github.com/mgechev/revive/formatter/severity.go create mode 100644 tools/vendor/github.com/mgechev/revive/internal/astutils/exit_funcs.go create mode 100644 tools/vendor/github.com/mgechev/revive/internal/config/config.go create mode 100644 tools/vendor/github.com/mgechev/revive/rule/marshal_receiver.go create mode 100644 tools/vendor/github.com/mgechev/revive/rule/multiline_if_init.go create mode 100644 tools/vendor/github.com/uudashr/iface/unusedmethod/doc.go create mode 100644 tools/vendor/github.com/uudashr/iface/unusedmethod/unusedmethod.go create mode 100644 tools/vendor/honnef.co/go/tools/analysis/dfa/dense/flow.go create mode 100644 tools/vendor/honnef.co/go/tools/analysis/dfa/dense/forward.go create mode 100644 tools/vendor/honnef.co/go/tools/analysis/dfa/dot.go create mode 100644 tools/vendor/honnef.co/go/tools/analysis/dfa/lattice.go delete mode 100644 tools/vendor/honnef.co/go/tools/analysis/facts/typedness/typedness.go delete mode 100644 tools/vendor/honnef.co/go/tools/go/ir/UPSTREAM create mode 100644 tools/vendor/honnef.co/go/tools/go/ir/block.go delete mode 100644 tools/vendor/honnef.co/go/tools/go/ir/html.go create mode 100644 tools/vendor/honnef.co/go/tools/go/ir/instantiate.go create mode 100644 tools/vendor/honnef.co/go/tools/go/ir/scc.go create mode 100644 tools/vendor/honnef.co/go/tools/go/ir/subst.go create mode 100644 tools/vendor/honnef.co/go/tools/go/ir/task.go create mode 100644 tools/vendor/honnef.co/go/tools/go/ir/typeset.go delete mode 100644 tools/vendor/honnef.co/go/tools/go/ir/write.go create mode 100644 tools/vendor/honnef.co/go/tools/go/types/typeutil/unify.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/iterutil/iterutil.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/aliases/aliases.go rename tools/vendor/honnef.co/go/tools/internal/{analysisinternal => xtools-internal/analysis}/typeindex/typeindex.go (80%) create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/clone.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/comment.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/cursor.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/equal.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/fields.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/purge.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/stringlit.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/unpack.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/util.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/allpaths.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/compact.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/graph.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/index.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/order.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/reachable.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/scc.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/shortest.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/transpose.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/moreiters/iters.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/common.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/coretype.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/free.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/normalize.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/termlist.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/typeterm.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/classify_call.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/element.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode_string.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/fx.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/isnamed.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/qualifier.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/recv.go rename tools/vendor/honnef.co/go/tools/internal/{ => xtools-internal}/typesinternal/typeindex/typeindex.go (74%) create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/types.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind_go124.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/zerovalue.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/features.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/gover.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/types.go create mode 100644 tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/versions.go delete mode 100644 tools/vendor/honnef.co/go/tools/staticcheck/sa5011/sa5011.go create mode 100644 tools/vendor/honnef.co/go/tools/staticcheck/sa9010/sa9010.go create mode 100644 vendor/charm.land/bubbles/v2/LICENSE create mode 100644 vendor/charm.land/bubbles/v2/cursor/cursor.go create mode 100644 vendor/charm.land/bubbles/v2/internal/runeutil/runeutil.go create mode 100644 vendor/charm.land/bubbles/v2/key/key.go create mode 100644 vendor/charm.land/bubbles/v2/textinput/styles.go create mode 100644 vendor/charm.land/bubbles/v2/textinput/textinput.go create mode 100644 vendor/charm.land/bubbletea/v2/.gitattributes create mode 100644 vendor/charm.land/bubbletea/v2/.gitignore create mode 100644 vendor/charm.land/bubbletea/v2/.golangci.yml create mode 100644 vendor/charm.land/bubbletea/v2/.goreleaser.yml create mode 100644 vendor/charm.land/bubbletea/v2/LICENSE create mode 100644 vendor/charm.land/bubbletea/v2/README.md create mode 100644 vendor/charm.land/bubbletea/v2/Taskfile.yaml create mode 100644 vendor/charm.land/bubbletea/v2/UPGRADE_GUIDE_V2.md create mode 100644 vendor/charm.land/bubbletea/v2/clipboard.go create mode 100644 vendor/charm.land/bubbletea/v2/color.go create mode 100644 vendor/charm.land/bubbletea/v2/commands.go create mode 100644 vendor/charm.land/bubbletea/v2/cursed_renderer.go create mode 100644 vendor/charm.land/bubbletea/v2/cursor.go create mode 100644 vendor/charm.land/bubbletea/v2/environ.go create mode 100644 vendor/charm.land/bubbletea/v2/exec.go create mode 100644 vendor/charm.land/bubbletea/v2/focus.go create mode 100644 vendor/charm.land/bubbletea/v2/input.go create mode 100644 vendor/charm.land/bubbletea/v2/key.go create mode 100644 vendor/charm.land/bubbletea/v2/keyboard.go create mode 100644 vendor/charm.land/bubbletea/v2/logging.go create mode 100644 vendor/charm.land/bubbletea/v2/mod.go create mode 100644 vendor/charm.land/bubbletea/v2/mouse.go create mode 100644 vendor/charm.land/bubbletea/v2/nil_renderer.go create mode 100644 vendor/charm.land/bubbletea/v2/options.go create mode 100644 vendor/charm.land/bubbletea/v2/paste.go create mode 100644 vendor/charm.land/bubbletea/v2/profile.go create mode 100644 vendor/charm.land/bubbletea/v2/raw.go create mode 100644 vendor/charm.land/bubbletea/v2/renderer.go create mode 100644 vendor/charm.land/bubbletea/v2/screen.go create mode 100644 vendor/charm.land/bubbletea/v2/signals_unix.go create mode 100644 vendor/charm.land/bubbletea/v2/signals_windows.go create mode 100644 vendor/charm.land/bubbletea/v2/tea.go create mode 100644 vendor/charm.land/bubbletea/v2/termcap.go create mode 100644 vendor/charm.land/bubbletea/v2/termios_bsd.go create mode 100644 vendor/charm.land/bubbletea/v2/termios_other.go create mode 100644 vendor/charm.land/bubbletea/v2/termios_unix.go create mode 100644 vendor/charm.land/bubbletea/v2/termios_windows.go create mode 100644 vendor/charm.land/bubbletea/v2/tty.go create mode 100644 vendor/charm.land/bubbletea/v2/tty_unix.go create mode 100644 vendor/charm.land/bubbletea/v2/tty_windows.go create mode 100644 vendor/charm.land/bubbletea/v2/xterm.go create mode 100644 vendor/charm.land/lipgloss/v2/.editorconfig create mode 100644 vendor/charm.land/lipgloss/v2/.gitattributes create mode 100644 vendor/charm.land/lipgloss/v2/.gitignore create mode 100644 vendor/charm.land/lipgloss/v2/.golangci.yml create mode 100644 vendor/charm.land/lipgloss/v2/.goreleaser.yml create mode 100644 vendor/charm.land/lipgloss/v2/LICENSE create mode 100644 vendor/charm.land/lipgloss/v2/README.md create mode 100644 vendor/charm.land/lipgloss/v2/Taskfile.yaml create mode 100644 vendor/charm.land/lipgloss/v2/UPGRADE_GUIDE_V2.md create mode 100644 vendor/charm.land/lipgloss/v2/align.go create mode 100644 vendor/charm.land/lipgloss/v2/ansi_unix.go create mode 100644 vendor/charm.land/lipgloss/v2/ansi_windows.go create mode 100644 vendor/charm.land/lipgloss/v2/blending.go create mode 100644 vendor/charm.land/lipgloss/v2/borders.go create mode 100644 vendor/charm.land/lipgloss/v2/canvas.go create mode 100644 vendor/charm.land/lipgloss/v2/color.go create mode 100644 vendor/charm.land/lipgloss/v2/get.go create mode 100644 vendor/charm.land/lipgloss/v2/join.go create mode 100644 vendor/charm.land/lipgloss/v2/layer.go create mode 100644 vendor/charm.land/lipgloss/v2/lipgloss.go create mode 100644 vendor/charm.land/lipgloss/v2/position.go create mode 100644 vendor/charm.land/lipgloss/v2/query.go create mode 100644 vendor/charm.land/lipgloss/v2/ranges.go create mode 100644 vendor/charm.land/lipgloss/v2/runes.go create mode 100644 vendor/charm.land/lipgloss/v2/set.go create mode 100644 vendor/charm.land/lipgloss/v2/size.go create mode 100644 vendor/charm.land/lipgloss/v2/style.go create mode 100644 vendor/charm.land/lipgloss/v2/terminal.go create mode 100644 vendor/charm.land/lipgloss/v2/unset.go create mode 100644 vendor/charm.land/lipgloss/v2/whitespace.go create mode 100644 vendor/charm.land/lipgloss/v2/wrap.go create mode 100644 vendor/charm.land/lipgloss/v2/writer.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/.gitattributes create mode 100644 vendor/github.com/charmbracelet/ultraviolet/.golangci.yml create mode 100644 vendor/github.com/charmbracelet/ultraviolet/.goreleaser.yml create mode 100644 vendor/github.com/charmbracelet/ultraviolet/LICENSE create mode 100644 vendor/github.com/charmbracelet/ultraviolet/README.md create mode 100644 vendor/github.com/charmbracelet/ultraviolet/TUTORIAL.md create mode 100644 vendor/github.com/charmbracelet/ultraviolet/border.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/buffer.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/cancelreader_other.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/cancelreader_windows.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/cell.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/console.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/console_unix.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/console_windows.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/cursor.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/decoder.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/doc.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/environ.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/event.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/key.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/key_table.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/logger.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/mouse.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/poll.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/poll_bsd.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/poll_default.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/poll_fallback.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/poll_linux.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/poll_select.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/poll_solaris.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/poll_windows.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/styled.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/tabstop.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_bsdly.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_bsdly_other.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_other.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_reader_other.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_renderer.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hardscroll.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hashmap.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_screen.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_tabdly.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_tabdly_other.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_unix.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/terminal_windows.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/tty.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/tty_other.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/tty_unix.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/tty_windows.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/utils.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/uv.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/winch.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/winch_other.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/winch_unix.go create mode 100644 vendor/github.com/charmbracelet/ultraviolet/window.go create mode 100644 vendor/github.com/charmbracelet/x/ansi/kitty/decoder.go create mode 100644 vendor/github.com/charmbracelet/x/ansi/kitty/encoder.go create mode 100644 vendor/github.com/charmbracelet/x/ansi/kitty/graphics.go create mode 100644 vendor/github.com/charmbracelet/x/ansi/kitty/options.go create mode 100644 vendor/github.com/charmbracelet/x/ansi/kitty/writer.go create mode 100644 vendor/github.com/charmbracelet/x/termios/LICENSE create mode 100644 vendor/github.com/charmbracelet/x/termios/bit_bsd.go create mode 100644 vendor/github.com/charmbracelet/x/termios/bit_darwin.go create mode 100644 vendor/github.com/charmbracelet/x/termios/bit_other.go create mode 100644 vendor/github.com/charmbracelet/x/termios/syscalls_bsd.go create mode 100644 vendor/github.com/charmbracelet/x/termios/syscalls_darwin.go create mode 100644 vendor/github.com/charmbracelet/x/termios/syscalls_linux.go create mode 100644 vendor/github.com/charmbracelet/x/termios/termios.go create mode 100644 vendor/github.com/charmbracelet/x/termios/termios_bsd.go create mode 100644 vendor/github.com/charmbracelet/x/termios/termios_linux.go create mode 100644 vendor/github.com/charmbracelet/x/termios/termios_other.go create mode 100644 vendor/github.com/charmbracelet/x/termios/termios_solaris.go create mode 100644 vendor/github.com/charmbracelet/x/windows/LICENSE create mode 100644 vendor/github.com/charmbracelet/x/windows/doc.go create mode 100644 vendor/github.com/charmbracelet/x/windows/syscall_windows.go create mode 100644 vendor/github.com/charmbracelet/x/windows/types.go create mode 100644 vendor/github.com/charmbracelet/x/windows/types_windows.go create mode 100644 vendor/github.com/charmbracelet/x/windows/zsyscall_windows.go delete mode 100644 vendor/github.com/cheggaaa/pb/.travis.yml delete mode 100644 vendor/github.com/cheggaaa/pb/LICENSE delete mode 100644 vendor/github.com/cheggaaa/pb/README.md delete mode 100644 vendor/github.com/cheggaaa/pb/README_V1.md delete mode 100644 vendor/github.com/cheggaaa/pb/format.go delete mode 100644 vendor/github.com/cheggaaa/pb/pb.go delete mode 100644 vendor/github.com/cheggaaa/pb/pb_appengine.go delete mode 100644 vendor/github.com/cheggaaa/pb/pb_plan9.go delete mode 100644 vendor/github.com/cheggaaa/pb/pb_win.go delete mode 100644 vendor/github.com/cheggaaa/pb/pb_x.go delete mode 100644 vendor/github.com/cheggaaa/pb/pool.go delete mode 100644 vendor/github.com/cheggaaa/pb/pool_win.go delete mode 100644 vendor/github.com/cheggaaa/pb/pool_x.go delete mode 100644 vendor/github.com/cheggaaa/pb/reader.go delete mode 100644 vendor/github.com/cheggaaa/pb/runecount.go delete mode 100644 vendor/github.com/cheggaaa/pb/termios_bsd.go delete mode 100644 vendor/github.com/cheggaaa/pb/termios_sysv.go delete mode 100644 vendor/github.com/cheggaaa/pb/writer.go create mode 100644 vendor/github.com/go-openapi/swag/fileutils/fs.go create mode 100644 vendor/github.com/go-openapi/swag/fileutils/mapfs.go create mode 100644 vendor/github.com/go-openapi/swag/fileutils/opaque.go create mode 100644 vendor/github.com/go-openapi/swag/fileutils/overlay.go create mode 100644 vendor/github.com/go-openapi/swag/revive.toml delete mode 100644 vendor/github.com/muesli/reflow/ansi/ansi.go delete mode 100644 vendor/github.com/muesli/reflow/ansi/buffer.go delete mode 100644 vendor/github.com/muesli/reflow/ansi/writer.go delete mode 100644 vendor/github.com/muesli/reflow/truncate/truncate.go delete mode 100644 vendor/github.com/muesli/reflow/wordwrap/wordwrap.go diff --git a/.github/workflows/build-go.yaml b/.github/workflows/build-go.yaml index b50bb3bf7..38fb2ea3c 100644 --- a/.github/workflows/build-go.yaml +++ b/.github/workflows/build-go.yaml @@ -28,7 +28,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: '1.26' + go-version: '1.27' - name: Run 'make check' run: make check diff --git a/.github/workflows/build-on-hosted-runner.yaml b/.github/workflows/build-on-hosted-runner.yaml index b499e25d6..2b18255b1 100644 --- a/.github/workflows/build-on-hosted-runner.yaml +++ b/.github/workflows/build-on-hosted-runner.yaml @@ -27,7 +27,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.26" + go-version: "1.27" - name: Test run: go test -v ./... diff --git a/Makefile b/Makefile index cb484f175..0fe9b92d0 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ TKN_IMG ?= quay.io/redhat-developer/mapt:v${VERSION}-tkn # renovate: datasource=github-releases depName=cirruslabs/cirrus-cli CIRRUS_CLI ?= v1.0.0 # renovate: datasource=github-releases depName=actions/runner -GITHUB_RUNNER ?= 2.336.0 +GITHUB_RUNNER ?= 2.337.0 # renovate: datasource=gitlab-releases depName=gitlab-org/gitlab-runner GITLAB_RUNNER ?= 19.3.0 # renovate: datasource=github-releases depName=open-telemetry/opentelemetry-collector-releases diff --git a/go.mod b/go.mod index dd2d6f27a..e5f5b6036 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/redhat-developer/mapt go 1.26.0 replace ( - github.com/charmbracelet/bubbles/v2 => charm.land/bubbles/v2 v2.1.1 + github.com/charmbracelet/bubbles/v2 => charm.land/bubbles/v2 v2.2.1 github.com/charmbracelet/bubbletea/v2 => charm.land/bubbletea/v2 v2.0.8 github.com/charmbracelet/lipgloss/v2 => charm.land/lipgloss/v2 v2.0.6 ) @@ -13,7 +13,7 @@ require ( github.com/mapt-oss/pulumi-ibmcloud/sdk v0.0.12 github.com/pulumi/pulumi-command/sdk v1.2.1 github.com/pulumi/pulumi-random/sdk/v4 v4.21.1 - github.com/pulumi/pulumi/sdk/v3 v3.258.0 + github.com/pulumi/pulumi/sdk/v3 v3.259.0 github.com/sirupsen/logrus v1.10.1 github.com/spf13/cobra v1.10.2 ) @@ -28,13 +28,13 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.8.0 github.com/IBM/go-sdk-core/v5 v5.23.2 github.com/aws/amazon-ec2-instance-selector/v3 v3.1.3 - github.com/aws/aws-sdk-go-v2 v1.43.6 + github.com/aws/aws-sdk-go-v2 v1.43.8 github.com/aws/aws-sdk-go-v2/config v1.32.37 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.321.3 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.323.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.107.2 github.com/aws/aws-sdk-go-v2/service/sts v1.45.6 - github.com/pulumi/pulumi-aws-native/sdk v1.75.0 - github.com/pulumi/pulumi-aws/sdk/v7 v7.42.0 + github.com/pulumi/pulumi-aws-native/sdk v1.76.0 + github.com/pulumi/pulumi-aws/sdk/v7 v7.43.0 github.com/pulumi/pulumi-awsx/sdk/v3 v3.8.0 github.com/pulumi/pulumi-azure-native-sdk/authorization/v3 v3.26.0 github.com/pulumi/pulumi-azure-native-sdk/compute/v3 v3.26.0 @@ -46,7 +46,7 @@ require ( github.com/pulumi/pulumi-github/sdk/v6 v6.15.0 github.com/pulumi/pulumi-gitlab/sdk/v9 v9.11.1 github.com/pulumi/pulumi-tls/sdk/v5 v5.5.1 - golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 + golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa k8s.io/apimachinery v0.36.3 k8s.io/client-go v0.36.3 ) @@ -59,25 +59,30 @@ require ( ) require ( + charm.land/bubbles/v2 v2.1.0 // indirect + charm.land/bubbletea/v2 v2.0.5 // indirect + charm.land/lipgloss/v2 v2.0.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/charmbracelet/bubbles v1.0.0 // indirect github.com/charmbracelet/bubbletea v1.3.10 // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260413211237-bd52878bcec2 // indirect + github.com/charmbracelet/x/termios v0.1.1 // indirect + github.com/charmbracelet/x/windows v0.2.2 // indirect github.com/danieljoos/wincred v1.2.3 // indirect github.com/ebitengine/purego v0.10.2 // indirect - github.com/evertras/bubble-table v0.17.1 // indirect + github.com/evertras/bubble-table v0.22.3 // indirect github.com/gabriel-vasile/mimetype v1.4.15 // indirect github.com/go-git/gcfg/v2 v2.0.2 // indirect github.com/go-git/go-billy/v6 v6.0.0-alpha.2 // indirect github.com/go-git/go-git/v6 v6.0.0-alpha.5 // indirect - github.com/go-openapi/swag/pools v0.28.0 // indirect + github.com/go-openapi/swag/pools v0.29.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/google/go-tpm v0.9.8 // indirect github.com/leodido/go-urn v1.5.0 // indirect - github.com/muesli/reflow v0.3.0 // indirect github.com/pgavlin/fx v0.1.6 // indirect github.com/pulumi/pulumi-azure-native-sdk/v3 v3.26.0 // indirect github.com/pulumi/pulumi-docker/sdk/v4 v4.5.8 // indirect @@ -85,9 +90,9 @@ require ( github.com/stretchr/objx v0.5.3 // indirect github.com/zalando/go-keyring v0.2.8 // indirect go.opentelemetry.io/contrib/bridges/otelslog v0.20.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0 // indirect - go.opentelemetry.io/otel/log v0.21.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.21.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.22.0 // indirect + go.opentelemetry.io/otel/log v0.22.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.22.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect ) @@ -112,21 +117,21 @@ require ( dario.cat/mergo v1.0.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.2.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0 // indirect github.com/BurntSushi/toml v1.6.0 // indirect github.com/IBM/platform-services-go-sdk v0.103.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.36 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.37 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.37 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.37 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.17 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.37 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.39 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.39 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.39 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.5.6 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.33.6 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.6 // indirect - github.com/aws/smithy-go v1.27.8 // indirect + github.com/aws/smithy-go v1.28.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect // github.com/charmbracelet/bubbles v1.0.0 // indirect @@ -134,7 +139,7 @@ require ( github.com/charmbracelet/colorprofile v0.4.3 // indirect github.com/charmbracelet/x/ansi v0.11.8 // indirect github.com/charmbracelet/x/cellbuf v0.0.15 // indirect - github.com/charmbracelet/x/exp/golden v0.0.0-20260816001655-68d539dca504 // indirect + github.com/charmbracelet/x/exp/golden v0.0.0-20260823001701-96af6d2cb5f6 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect @@ -153,17 +158,17 @@ require ( github.com/go-openapi/runtime/server-middleware v0.33.0 // indirect github.com/go-openapi/spec v0.22.9 // indirect github.com/go-openapi/strfmt v0.27.0 // indirect - github.com/go-openapi/swag v0.28.0 // indirect - github.com/go-openapi/swag/cmdutils v0.28.0 // indirect - github.com/go-openapi/swag/conv v0.28.0 // indirect - github.com/go-openapi/swag/fileutils v0.28.0 // indirect - github.com/go-openapi/swag/jsonutils v0.28.0 // indirect - github.com/go-openapi/swag/loading v0.28.0 // indirect - github.com/go-openapi/swag/mangling v0.28.0 // indirect - github.com/go-openapi/swag/netutils v0.28.0 // indirect - github.com/go-openapi/swag/stringutils v0.28.0 // indirect - github.com/go-openapi/swag/typeutils v0.28.0 // indirect - github.com/go-openapi/swag/yamlutils v0.28.0 // indirect + github.com/go-openapi/swag v0.29.1 // indirect + github.com/go-openapi/swag/cmdutils v0.29.1 // indirect + github.com/go-openapi/swag/conv v0.29.1 // indirect + github.com/go-openapi/swag/fileutils v0.29.1 // indirect + github.com/go-openapi/swag/jsonutils v0.29.1 // indirect + github.com/go-openapi/swag/loading v0.29.1 // indirect + github.com/go-openapi/swag/mangling v0.29.1 // indirect + github.com/go-openapi/swag/netutils v0.29.1 // indirect + github.com/go-openapi/swag/stringutils v0.29.1 // indirect + github.com/go-openapi/swag/typeutils v0.29.1 // indirect + github.com/go-openapi/swag/yamlutils v0.29.1 // indirect github.com/go-openapi/validate v0.26.3 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect @@ -204,23 +209,23 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/collector/featuregate v1.65.0 // indirect go.opentelemetry.io/collector/pdata v1.65.0 // indirect - go.opentelemetry.io/otel v1.45.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect - go.opentelemetry.io/otel/metric v1.45.0 // indirect - go.opentelemetry.io/otel/sdk v1.45.0 // indirect - go.opentelemetry.io/otel/trace v1.45.0 // indirect + go.opentelemetry.io/otel v1.46.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0 // indirect + go.opentelemetry.io/otel/metric v1.46.0 // indirect + go.opentelemetry.io/otel/sdk v1.46.0 // indirect + go.opentelemetry.io/otel/trace v1.46.0 // indirect go.opentelemetry.io/proto/otlp v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/time v0.15.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260818201246-1b0934165a6f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260818201246-1b0934165a6f // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.140.0 // indirect - k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect + k8s.io/kube-openapi v0.0.0-20260821135717-be32def86098 // indirect k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect @@ -230,13 +235,12 @@ require ( require ( github.com/IBM-Cloud/power-go-client v1.16.2 - github.com/IBM/vpc-go-sdk v0.89.0 + github.com/IBM/vpc-go-sdk v0.90.0 github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/aws/aws-sdk-go-v2/service/ecs v1.90.2 github.com/aws/aws-sdk-go-v2/service/iam v1.59.1 github.com/blang/semver v3.5.1+incompatible // indirect - github.com/cheggaaa/pb v1.0.29 // indirect github.com/djherbis/times v1.6.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect @@ -269,7 +273,7 @@ require ( golang.org/x/sys v0.47.0 // indirect golang.org/x/term v0.45.0 // indirect golang.org/x/text v0.41.0 // indirect - google.golang.org/grpc v1.83.0 // indirect + google.golang.org/grpc v1.83.1 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect lukechampine.com/frand v1.5.1 // indirect diff --git a/go.sum b/go.sum index 249dd8a7e..eb0f33ca9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g= +charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY= +charm.land/bubbletea/v2 v2.0.5 h1:TQlLFqxo39AAHSVuOhJ5D3nH7O9Nk8JGinsfWQ4y1U4= +charm.land/bubbletea/v2 v2.0.5/go.mod h1:dvbsYZD+MHkdIZl+Z67D212hEvB+GII2tfH8f9SnoDw= +charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU= +charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.0 h1:4gRPBpN1f6xt88yi4WR26m7XaD9OlWtVT6bWPdGUIok= @@ -32,8 +38,8 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.8.0 h1:irsmOWwkp0KCTTNS5 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.8.0/go.mod h1:GWcBkQj3MqN7ozHKLaCCAuNLiXoIGv2RtanfAwSjY/Y= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI= -github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0 h1:MDT4FxAPve5FnYn6vOL1r7RCRDG+l9cI7a5LlCuHsqA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= @@ -44,8 +50,8 @@ github.com/IBM/go-sdk-core/v5 v5.23.2 h1:wY+XtK9xnnaqwdOWE2m+1sn9sxfYrNbm9fcrB4u github.com/IBM/go-sdk-core/v5 v5.23.2/go.mod h1:NOPMhrdMNec7QBzY50gI0G7K1Y4O8lBrjjjO6C5eJ4A= github.com/IBM/platform-services-go-sdk v0.103.0 h1:qQK9DYWD2kR201uwKaXxT2F/WY3uj2YFcTxD6wrldI0= github.com/IBM/platform-services-go-sdk v0.103.0/go.mod h1:1isdll9kcFRZT9o3McwdvfrQafL4/K61uFbKzueDkEg= -github.com/IBM/vpc-go-sdk v0.89.0 h1:IYCeW3kmY9XHoY4hmxJuyxNXIOXpY0iXs3LMDEWucrI= -github.com/IBM/vpc-go-sdk v0.89.0/go.mod h1:y014nUi5rUn3sOs48MgW4tDtpQ1tmWGtAPMfAqTfwxE= +github.com/IBM/vpc-go-sdk v0.90.0 h1:YE6IgADlmjyx7Na6M41737rSFEZDNzhzX7kZvQUYUeE= +github.com/IBM/vpc-go-sdk v0.90.0/go.mod h1:y014nUi5rUn3sOs48MgW4tDtpQ1tmWGtAPMfAqTfwxE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= @@ -66,8 +72,8 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/amazon-ec2-instance-selector/v3 v3.1.3 h1:13qtG4reL2+2UiCm2U7gM9QJthDO8hPPzvn7hTjHask= github.com/aws/amazon-ec2-instance-selector/v3 v3.1.3/go.mod h1:wdlMRtz9G4IO6H1yZPsqfGBxR8E6B/bdxHlGkls4kGQ= -github.com/aws/aws-sdk-go-v2 v1.43.6 h1:RrmFcqCBxkJuf7g1axVo5krB4jM/AO8r5e5oujrgdoQ= -github.com/aws/aws-sdk-go-v2 v1.43.6/go.mod h1:tXpPM+v0D1lndmga+HqqLDIzUFJlEeR21aspVklHF00= +github.com/aws/aws-sdk-go-v2 v1.43.8 h1:fpnrxwuwsoGIgjvgLeDU3y9w7YaHBxyF6AF3vQL8duw= +github.com/aws/aws-sdk-go-v2 v1.43.8/go.mod h1:j7gYSq8dL95QejkFXxvQNESH4I9WGHFI6iO+vhqEi5Q= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.18 h1:LAfOuhAH331fmOjTQpAaOlH+Ftn7RzSDJ2VFwjdMMy4= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.18/go.mod h1:4e5xhuXHx1e4U9EthvbPP1r/DIMp5c2823OL8karzcM= github.com/aws/aws-sdk-go-v2/config v1.32.37 h1:Ljl7LOJB6ym0liuEl0+TZ3d7f5I8MEZN1Cj9PINlj/g= @@ -76,24 +82,24 @@ github.com/aws/aws-sdk-go-v2/credentials v1.19.36 h1:84s5xMme6ENYEdKG8rsbSFFg/8+ github.com/aws/aws-sdk-go-v2/credentials v1.19.36/go.mod h1:c46BLdagDLIswjgt+GeQOslXgeS0E6wCacs5yZbxPGk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.37 h1:b5tb+CZItBkydC7r3hTNdSO3pszG1R2EtnA+7TePQPk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.37/go.mod h1:ZQ+6SU9X0oz6+7MUCSswv9Mjci4eaqZr21HI2RVy/yA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.37 h1:lznzIOvvbqjfe8UAaciCRJgBgJsxuTROKlhZuXQWfv8= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.37/go.mod h1:otfkzyfQeMMLZAqX59GSXTL3o22BR/l6HFaRzzbWSqA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.37 h1:zCEORWo0eU0gDjG+IyApE/2B+ZGG1m+GU7B263XV8ds= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.37/go.mod h1:i6c0PEl3TNOWxRbQ++KQcVenPWS/GoQeiklKhNuqzJ8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.39 h1:YrEI22hVQcqMpq934ZoPQyJjGNzX4CGdrSDCjBD59sI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.39/go.mod h1:N8qOX83LkaCeizvrfiNjwkBOXkxHt6a74CiZn8qz9F8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.39 h1:Vo7UZzBjB6zS6feEOuBlpEgaj8iBTdiNlye+7w9ooGo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.39/go.mod h1:JgxtAO/77e95Rs9WMWUzz99hT182gqdAh7/DHuEMA/k= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.38 h1:A3UAuCmx7LyUcrixBTzKJYYIUZ2yTvn6ZhT8PB+7APk= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.38/go.mod h1:1PDUYG9Z+JrbbsobsAZHjWOm9QBT/djiK3QbykTL5Z4= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.321.3 h1:D/jnJv0FOeJKpRguRNC4tptuJ7y1yYYk/dKVTPmHQJs= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.321.3/go.mod h1:0YYJ+4BAgeIkRucGTesOdWnVnxhodrwWo6+lJ6Wmndg= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.323.0 h1:29nSFt0PmBrj030/5rcMJ9GldUaZ90cB+WpSI45UrA8= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.323.0/go.mod h1:MS7XSsj8ivUJf102MfU1XbJicqGEDF1ex88FfKsxckk= github.com/aws/aws-sdk-go-v2/service/ecs v1.90.2 h1:qVT/ixJEmfC2SAv4FdkpTFRLt7remszYCY/DuguobWg= github.com/aws/aws-sdk-go-v2/service/ecs v1.90.2/go.mod h1:bZR2sTaOf5t+iLUB756XNv2HJhMFav1GLUbD8XNu4Dk= github.com/aws/aws-sdk-go-v2/service/iam v1.59.1 h1:Dr7wQQgyc9YVkIR6AWIOSWuOFZ6A0K2jFL6Ld6uJQ1E= github.com/aws/aws-sdk-go-v2/service/iam v1.59.1/go.mod h1:WmY9HZODfCg9inthJD2PctQbn7uEMMGnSv5MZ2BIjcM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.17 h1:OvYZOB3qA6zvfdRFiRFRzVSiElMYrz3GdntkXZxlp1o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.17/go.mod h1:JgR/2Ew50ACfIWau1oeMRX59tMtC0kM+PYQGEaT04cY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.18 h1:+fiwOxNdE8bOK3SoVTln8hwP+OCyArbi2/InIr/A9AU= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.18/go.mod h1:aua4m7EZSvQra/96b8zJxWHwtHxuXQ8bx4DiM92V044= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.30 h1:5437eMoOwqqQpZn2XJy74mlDCuPYL81texMT3mXqgtU= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.30/go.mod h1:xfu2m3dOpvW8lj98wQYa8V9ku/Rta59hsbireGzhh3A= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.37 h1:a3D4AjrOrTrP8+d9ILBthqrElf0z1JNol09Xvnwcys8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.37/go.mod h1:ky0gTu+ukvUTuUKFIpp6Wid4oninrkCyvbFkVs0kpHM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.39 h1:inoUrqz4Lfpw1XwpUvQnBiAJ2tUzn3opZ0gduNLxo+8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.39/go.mod h1:Yx+RrmAF+XGZTccwhQ3o4K5V8qkZBsTAcq148Y8g57k= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.38 h1:gX8B8y3Ho30B1LPxefDKMi/HZqWEb47U9ogs3DtSG0M= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.38/go.mod h1:l5WblZlcmGPe4/O7JY2HO25Z+xqTBvyfTyFbRMf8gYw= github.com/aws/aws-sdk-go-v2/service/pricing v1.44.6 h1:BIQuRIKq/3YyV4UapmgYCiWXf4Sja62oAM/sOnwhAmg= @@ -108,8 +114,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.6 h1:49BBtY68A+KJCQ3a2F3eUe6R github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.6/go.mod h1:ptG2hbs7QltE1GcQY0MpS4bfrc51KCnBXUr7OT1EEfE= github.com/aws/aws-sdk-go-v2/service/sts v1.45.6 h1:JvExZWabChDM0qJAirQYGfOYo0ndT3edXj+fqSPNjkE= github.com/aws/aws-sdk-go-v2/service/sts v1.45.6/go.mod h1:XZcaQkV2cItp6yEkrwljyaPOf22RuX7T43jxap/FOmM= -github.com/aws/smithy-go v1.27.8 h1:FR0dxZfIlV7Z8eh2iHfIofdunw382XsDV3Mxt9nUvRY= -github.com/aws/smithy-go v1.27.8/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/smithy-go v1.28.0 h1:hwfPnN99NWCkjNmfzTixXFopyjBLUe9KTdRcRGxwZd4= +github.com/aws/smithy-go v1.28.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= @@ -132,16 +138,20 @@ github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/ultraviolet v0.0.0-20260413211237-bd52878bcec2 h1:mRAlb/WARLaCnCwAEBa8Zfk965GrYc414MhJamV4anw= +github.com/charmbracelet/ultraviolet v0.0.0-20260413211237-bd52878bcec2/go.mod h1:bAAz7dh/FTYfC+oiHavL4mX1tOIBZ0ZwYjSi3qE6ivM= github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ= github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0= github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= -github.com/charmbracelet/x/exp/golden v0.0.0-20260816001655-68d539dca504 h1:6OnDxYzAjEAzuWmF1AFnqy3AoFOQBcnTCLfE9LZjc5Y= -github.com/charmbracelet/x/exp/golden v0.0.0-20260816001655-68d539dca504/go.mod h1:6fMpcW6iwN/kX+xJ52eqVWsDiBTe0UJD24JLoHFe+P0= +github.com/charmbracelet/x/exp/golden v0.0.0-20260823001701-96af6d2cb5f6 h1:PiWBtjCI6AGfSsy3X+PfGtR9hEl2C9xCOU6Yxr49mAU= +github.com/charmbracelet/x/exp/golden v0.0.0-20260823001701-96af6d2cb5f6/go.mod h1:6fMpcW6iwN/kX+xJ52eqVWsDiBTe0UJD24JLoHFe+P0= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= -github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= -github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= +github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= +github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= +github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= @@ -167,9 +177,8 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/evertras/bubble-table v0.17.1 h1:HJwq3iQrZulXDE93ZcqJNiUVQCBbN4IJ2CkB/IxO3kk= -github.com/evertras/bubble-table v0.17.1/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/evertras/bubble-table v0.22.3 h1:fPt9L5issLtbN/lzEBf6JEK+ygv9ajVUihDY+760dxI= +github.com/evertras/bubble-table v0.22.3/go.mod h1:f3xHDRcXh6fcMsbTRsqOIrrFQZdyQBBNSofGanmOAOM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -222,36 +231,36 @@ github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+ github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0= github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM= github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc= -github.com/go-openapi/swag v0.28.0 h1:xkgbOSKj6DZziNpyqRRAOt3GJGtgjgsd2RoyT30VWuw= -github.com/go-openapi/swag v0.28.0/go.mod h1:4qYnT3Cqr1p1VknOdPo70evN4rgQnAg6jwApHyxSGIg= -github.com/go-openapi/swag/cmdutils v0.28.0 h1:7TOeNtkYru1SG8Y34tDh9WBbLsMqGnptuxWiHREPZ4Q= -github.com/go-openapi/swag/cmdutils v0.28.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8= -github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= -github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU= -github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= -github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4= -github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= -github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU= -github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k= -github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM= -github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= -github.com/go-openapi/swag/netutils v0.28.0 h1:YXN6TALEi2pzts8/8GNm6T61HTAZsieukGZidap989k= -github.com/go-openapi/swag/netutils v0.28.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= -github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU= -github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= -github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU= -github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= -github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY= -github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= -github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY= -github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs= -github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= -github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= -github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= -github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/swag v0.29.1 h1:C6EeWzUwQtcWEhE9eqBdUubGXxhWY4PlzHMLD7kLaiQ= +github.com/go-openapi/swag v0.29.1/go.mod h1:BzxEXKiPlSXRsRTv1KSBF/BpGKHxA/YciCnr4tv9bvA= +github.com/go-openapi/swag/cmdutils v0.29.1 h1:3DorPGfUdE80BogKY22EzoHBcHMrkVomZMoV7kS4ANY= +github.com/go-openapi/swag/cmdutils v0.29.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.29.1 h1:AC4Eh/5c/eUDOUCzzsRC9ghmFgOSBHeRMGIngY0ZUGA= +github.com/go-openapi/swag/conv v0.29.1/go.mod h1:S1X7/ZrBEZOC0Wc8AGxjbcGS92l3WEjA7aPtpl+RaqM= +github.com/go-openapi/swag/fileutils v0.29.1 h1:ZcPzMceVhU1WPbK6N1G6sNQKdd1CWJlf3cA08UHuoM0= +github.com/go-openapi/swag/fileutils v0.29.1/go.mod h1:/wofKYckbtRl2p3+EwQsosie5CT1B38+dQ+PS579BzI= +github.com/go-openapi/swag/jsonutils v0.29.1 h1:AFCxs0eQZ24/QyfhVHM2t49rMz7Vv3XCsZQI6yrNy+c= +github.com/go-openapi/swag/jsonutils v0.29.1/go.mod h1:u3+sCfJpttDpcmS5kpm0yxL6GK0eWgODsx8Yw8fcqNM= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.1 h1:BiiXE31Bx9SfpsMmOQj5KYpUhTZBpLVriVhJDuLuY2o= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.1/go.mod h1:julgTUKZ9/D0j6O7GKajmRs+812FWxQg/mMpGunWSjg= +github.com/go-openapi/swag/loading v0.29.1 h1:FCv5fG8UhTdDJa2R7w+5O9Ekpcbw7tt0nFWvmDKGBjc= +github.com/go-openapi/swag/loading v0.29.1/go.mod h1:N0ESuem4p2oedKal8EJhciqnJ9Q9Wmt83L1CRB3Fouw= +github.com/go-openapi/swag/mangling v0.29.1 h1:lHALtvYCdxVnRl4GrHmFPwfBTZYIObqdGNSKyu/8D6I= +github.com/go-openapi/swag/mangling v0.29.1/go.mod h1:SAop9pB7PUjQ/CGCNf/JmCKTRK+GDO+RqE9UHqC/N6s= +github.com/go-openapi/swag/netutils v0.29.1 h1:IjIvdEP5duKcghFqJEPSUraRnkKYHoM65kTluTu+Jb4= +github.com/go-openapi/swag/netutils v0.29.1/go.mod h1:DUde7x4Bx00k5jYl2AdRpNAO0m7atUvD2x6X+bWkbno= +github.com/go-openapi/swag/pools v0.29.1 h1:NRogYxdEW9SjRM4mkAOji9iefO4MRXq3p/ZJcoQbUKg= +github.com/go-openapi/swag/pools v0.29.1/go.mod h1:leDcaghjkRAhCuCRv9NfJU5f0mjoU3cT/XZObhMk3pc= +github.com/go-openapi/swag/stringutils v0.29.1 h1:1ykunK7iJQk1uOO7+oUH1ukbsK85fFCOiCFMOVSY+F0= +github.com/go-openapi/swag/stringutils v0.29.1/go.mod h1:7fSqZ+z8Qc0tOfAAK0jVa5qFGrnIlRi6n7NeGGrr1vc= +github.com/go-openapi/swag/typeutils v0.29.1 h1:Nzv9nhnlLCRBPQqfOX+7lB6Guju370or8StT+lIOf6M= +github.com/go-openapi/swag/typeutils v0.29.1/go.mod h1:hxpgDZJVBkBsi/d3MIUosafoFdE5exaQRmVp0zwu3YE= +github.com/go-openapi/swag/yamlutils v0.29.1 h1:69w3tsBajm7MR/fejLy7HD/3J68Ys1SeeZMEzZ3w2sk= +github.com/go-openapi/swag/yamlutils v0.29.1/go.mod h1:rgsp3vT/QdWzKwn43CigDwjOGIenPyTZMKnxEM8jZOA= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.1 h1:Jm+/ze2rMtbD98yen92AhATGLGREDYXG56Xr4gMjEtE= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.1/go.mod h1:YDPnwCRDu38/oJBVMBVXOUDiJ9cIeBHWvfImHaXqnv4= +github.com/go-openapi/testify/v2 v2.6.1 h1:6CNJhTjMzgaeaH8WhshcsZNPIvRemiOcFpU7seO/y7Q= +github.com/go-openapi/testify/v2 v2.6.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-openapi/validate v0.26.3 h1:OkfZgLvLDnGP2hrRGD+42WBiPWWkoHomTJ+IVI+KaDc= github.com/go-openapi/validate v0.26.3/go.mod h1:7DOOa4raU6NRe7A8VQSKbm3VcuUIioREYHFt+er9Sk8= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -337,17 +346,12 @@ github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBql github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mapt-oss/pulumi-ibmcloud/sdk v0.0.12 h1:F+Sr/Q2nZoEeb7epVFmtfkSIeYm59HO3uMJd4qLi6jQ= github.com/mapt-oss/pulumi-ibmcloud/sdk v0.0.12/go.mod h1:U30V8lnPz4X/KybXyAOlz2vHXYcN4ZY/BwgE1XDPt2g= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.28 h1:rPyg2ybwEKPebvpzVWe1gKBkH8EQFkxO4Y0hjBeLaBU= github.com/mattn/go-runewidth v0.0.28/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -366,8 +370,6 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -409,10 +411,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 h1:vkHw5I/plNdTr435cARxCW6q9gc0S/Yxz7Mkd38pOb0= github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231/go.mod h1:murToZ2N9hNJzewjHBgfFdXhZKjY3z5cYC1VXk+lbFE= -github.com/pulumi/pulumi-aws-native/sdk v1.75.0 h1:pbox0Wj5Fuzk7xD5u61Wiq2gAQtT+GKCZokuQ9VRQC4= -github.com/pulumi/pulumi-aws-native/sdk v1.75.0/go.mod h1:2hxwGXsFXUWHCNGXNy67wSWpQHJuquE25kpbnQ/LCA0= -github.com/pulumi/pulumi-aws/sdk/v7 v7.42.0 h1:BOVzggIfWBIQGzSFWzDW339YSxKp4VSw2ePpwnnu3ZU= -github.com/pulumi/pulumi-aws/sdk/v7 v7.42.0/go.mod h1:wImO2X5EeAVjuNtyJF/W/N96Q73tEO9t1Ne9Uqa50Ps= +github.com/pulumi/pulumi-aws-native/sdk v1.76.0 h1:i999q7ktm/V2lgGtdDM57gEGmGnJ67whV4MgQ0O/Qto= +github.com/pulumi/pulumi-aws-native/sdk v1.76.0/go.mod h1:2hxwGXsFXUWHCNGXNy67wSWpQHJuquE25kpbnQ/LCA0= +github.com/pulumi/pulumi-aws/sdk/v7 v7.43.0 h1:Z5+wr3Po7dlgIH1EX8JdYpTSuwHuq9lQl611kgyk8Ow= +github.com/pulumi/pulumi-aws/sdk/v7 v7.43.0/go.mod h1:wImO2X5EeAVjuNtyJF/W/N96Q73tEO9t1Ne9Uqa50Ps= github.com/pulumi/pulumi-awsx/sdk/v3 v3.8.0 h1:7C6VZV3yBxxn39wsFDtA0D78Ayg9tDmnl80mxfed7EA= github.com/pulumi/pulumi-awsx/sdk/v3 v3.8.0/go.mod h1:5IRij9mHJMZBjecVckf+xYMRMlyxBP4+OH0FpX3wUy0= github.com/pulumi/pulumi-azure-native-sdk/authorization/v3 v3.26.0 h1:jGR5mCiKwMvLYLqvJbEWLSyQ/JXEdD6zq01BgT/ANf4= @@ -447,10 +449,8 @@ github.com/pulumi/pulumi-random/sdk/v4 v4.21.1 h1:pqvBaBMwFPP3DV8BYh/f45V4A62kFq github.com/pulumi/pulumi-random/sdk/v4 v4.21.1/go.mod h1:4Q2jFqgCimgOQxvWntZSnV6u8+JhCkPHewloJQfLoeQ= github.com/pulumi/pulumi-tls/sdk/v5 v5.5.1 h1:pL01s6xK/qc7tW/TYeAJX9bYpkd88RkMOG6no/85yaQ= github.com/pulumi/pulumi-tls/sdk/v5 v5.5.1/go.mod h1:xE3qVFi5Nl9h5OAM6iHTQZzOCgbX50tE5oAej+mEZDk= -github.com/pulumi/pulumi/sdk/v3 v3.258.0 h1:k6EOdMnR7e9SR75t3y7/p9AOxo2SHgNFAPAaimHImmc= -github.com/pulumi/pulumi/sdk/v3 v3.258.0/go.mod h1:pyYSaOHxk1R0bYiSzpcbYzeN3MBtlyckup9tvImBjjo= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/pulumi/pulumi/sdk/v3 v3.259.0 h1:mJP9tXF9oxcfwidBgteXkUUchIz2L59SbGS5byZBo1w= +github.com/pulumi/pulumi/sdk/v3 v3.259.0/go.mod h1:DPK83ZiPYy55hY0MoaFAbl95Yee/dCGc6cQAqI7/ctw= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= @@ -485,8 +485,8 @@ github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+Q github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= -github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= @@ -515,28 +515,28 @@ go.opentelemetry.io/collector/pdata v1.65.0 h1:6bQ3sIrEzOdapetxYFjdCns90kKXg1qCo go.opentelemetry.io/collector/pdata v1.65.0/go.mod h1:r5vRY0p7nZcEif06twUW09Sf6vaNsyPzij+EpwI/xeI= go.opentelemetry.io/contrib/bridges/otelslog v0.20.0 h1:oEl2Pw/i4OQwhAuda2pAHFAcOMivA+Xa+iTccBfab/g= go.opentelemetry.io/contrib/bridges/otelslog v0.20.0/go.mod h1:yMSQaiiq5dpfrSJCYLBcqFeJkFFI67seT4ngvx6jfVo= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0 h1:WseeVYf5dJZTsyPiyW5L14k5qsSibqXAMTSiFEDiWr0= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0/go.mod h1:SiLZnQS6Qk2eCpvr2CH/XMAOa64TWGXxEZJZCpD2Lmc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 h1:fG5MCxGz8+2VtrN/WgqSpJFctVz24gpxj8CxkKmc8Ww= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0/go.mod h1:BmAYTn+3ysbRe+IU2msxmf5Rx3g6DHvex+tWI3LdhYI= -go.opentelemetry.io/otel/log v0.21.0 h1:SLsVDGmtyBrdw8/a2Z0bOIxou/+bN4z56GebH7T0LvA= -go.opentelemetry.io/otel/log v0.21.0/go.mod h1:iReetQrZL9Wyg84cCkOoCmqDHS5RCFfyxC7J+r8fn8g= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/log v0.21.0 h1:QsE7XSR0ktQdKmRKGnR+f1ObGF32WG+7MER/P9KgmYc= -go.opentelemetry.io/otel/sdk/log v0.21.0/go.mod h1:m9mApjCoD2/1QuKCAptjv+BrG9WKOvQLVdNx+iBldTo= -go.opentelemetry.io/otel/sdk/log/logtest v0.21.0 h1:X+JBBgKlswCGYsmgL0CnoUUtlE//VB345c84jYAYkdQ= -go.opentelemetry.io/otel/sdk/log/logtest v0.21.0/go.mod h1:HD1575K8e6sIFBBDd5tZB3t9DlMytWXq9FuR+Y4rfjE= -go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= -go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= +go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc= +go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.22.0 h1:Bu39F5tzJct+f2IZbB8989fwyTps3c8e7EsUQsz+vs8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.22.0/go.mod h1:dJUwod88EsFgYCqrDHaSPzhiY9pBUpt0d85/qSfua7k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 h1:OFnwLJr+pF3iHrlGSzbxyuo6/6HyBlnlN1CWEJmBVcw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0/go.mod h1:716wFneO0ov19A2beH5hjfh9AK5z/VWNAtDijp1Y0/g= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0 h1:w53CDeOA/Kurp7yRsegSr6pbbr759dOvJ+yNmWM6Hxs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0/go.mod h1:BOmGMCbAtvcJiSJ+hLuhgPLdDbimnraSl8irz3iY8sY= +go.opentelemetry.io/otel/log v0.22.0 h1:5DBNnfvaJ6CVdkJ+Jle8Tzs50aSSv49TXGj9XRsEYw0= +go.opentelemetry.io/otel/log v0.22.0/go.mod h1:gzOt/R67vF2GniAqWu8Qv0SXy89f71muHcrkz76PCdc= +go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8= +go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o= +go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI= +go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM= +go.opentelemetry.io/otel/sdk/log v0.22.0 h1:PRL+s6P63XT4E/bheEflopPUpVxuvANqZwtt89yhoGk= +go.opentelemetry.io/otel/sdk/log v0.22.0/go.mod h1:JNp0sBELrjCTcu5W3GzABVypeU6vDJjBS+X0JISuz+g= +go.opentelemetry.io/otel/sdk/log/logtest v0.22.0 h1:infPnfNrhCNgOUZRs3gWUg8vhoBUHihq02gwK05gzlg= +go.opentelemetry.io/otel/sdk/log/logtest v0.22.0/go.mod h1:gkQZA3z15Bv3KU9vigBTi8dFechSozRP7v94X4VZv+s= +go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE= +go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4= +go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c= +go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI= go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= go.opentelemetry.io/proto/slim/otlp v1.11.0 h1:zB37f+f99+y6UIZR4h7UpwbXd5kFNyip35U7GaJ/Jik= @@ -561,8 +561,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= -golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 h1:YXnL44eJ77R+ji4/ooy8UsXIhz+lbi2Qgdlc8iRN0gY= -golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297/go.mod h1:Mkmymgv+uMpSQ/XxJ/7GpdrdYoqm3u72jEbpCLiJmNk= +golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa h1:QSyA8ishJCyT21kER9KwNt0b7BM3iRK4x9QXhjN5Fdk= +golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -584,9 +584,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -618,12 +616,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260818201246-1b0934165a6f h1:zRL8hmUGXSGBJ2D+r85c0S3xhrg6BtfQ3V6KKbR/I9M= -google.golang.org/genproto/googleapis/api v0.0.0-20260818201246-1b0934165a6f/go.mod h1:q/3oV3jAi5vwelxsVAprMBC8BcM2zmNe+IjRGd+9/ks= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260818201246-1b0934165a6f h1:kMQMi+2r0XRQ/Ad2/tgd+5S7JYSBGYO4pwkLTE8F2y0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260818201246-1b0934165a6f/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ= -google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 h1:izFU9hz7aeLI/Mi1J0991ae+xcwRLr7hTqWnB/9aIIU= +google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5/go.mod h1:3LhxRw4YYkf+ylAfgaY9JlVLFKhokkCV8duhLLe7+t0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 h1:1VUiZAXyC+zmiFYi+WLtBzr68Cj8wOofHjjrA/kkizc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= +google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -649,8 +647,8 @@ k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg= k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= -k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= +k8s.io/kube-openapi v0.0.0-20260821135717-be32def86098 h1:z5+pcu1jTyKK5mNTe2/+x+U6Uuv9jRVOJQLaBJJMpeI= +k8s.io/kube-openapi v0.0.0-20260821135717-be32def86098/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE= k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM= lukechampine.com/frand v1.5.1 h1:fg0eRtdmGFIxhP5zQJzM1lFDbD6CUfu/f+7WgAZd5/w= diff --git a/oci/Containerfile b/oci/Containerfile index 5398260aa..1a6b449b5 100644 --- a/oci/Containerfile +++ b/oci/Containerfile @@ -1,4 +1,4 @@ -FROM registry.access.redhat.com/ubi9/go-toolset@sha256:71e89a1a51ab32cc30634d89ee4dc8ea40ad9991057fa1eae3b1af32bc7db73f as builder +FROM registry.access.redhat.com/ubi9/go-toolset@sha256:f221165cd493d89c13ce384e8e58914571ea353f47a1443698d981494842ff83 as builder ARG TARGETARCH USER root SHELL ["/bin/bash", "-euo", "pipefail", "-c"] @@ -6,7 +6,7 @@ WORKDIR /workspace COPY . . # renovate: datasource=github-releases depName=pulumi/pulumi -ENV PULUMI_VERSION 3.258.0 +ENV PULUMI_VERSION 3.259.0 ENV PULUMI_BASE_URL="https://github.com/pulumi/pulumi/releases/download/v${PULUMI_VERSION}/pulumi-v${PULUMI_VERSION}" ENV PULUMI_URL="${PULUMI_BASE_URL}-linux-x64.tar.gz" @@ -20,7 +20,7 @@ RUN unset VERSION \ # Pulumi plugins — installed in build stage, copied into runtime # renovate: datasource=github-releases depName=pulumi/pulumi-aws -ARG PULUMI_AWS_VERSION=v7.42.0 +ARG PULUMI_AWS_VERSION=v7.43.0 # renovate: datasource=github-releases depName=pulumi/pulumi-awsx ARG PULUMI_AWSX_VERSION=v3.8.0 # renovate: datasource=github-releases depName=pulumi/pulumi-azure-native @@ -32,11 +32,11 @@ ARG PULUMI_TLS_VERSION=v5.5.1 # renovate: datasource=github-releases depName=pulumi/pulumi-random ARG PULUMI_RANDOM_VERSION=v4.21.1 # renovate: datasource=github-releases depName=pulumi/pulumi-aws-native -ARG PULUMI_AWS_NATIVE_VERSION=v1.75.0 +ARG PULUMI_AWS_NATIVE_VERSION=v1.76.0 # renovate: datasource=github-releases depName=pulumi/pulumi-github ARG PULUMI_GITHUB_VERSION=v6.15.0 # renovate: datasource=github-releases depName=pulumi/pulumi-gitlab -ARG PULUMI_GITLAB_VERSION=v10.1.1 +ARG PULUMI_GITLAB_VERSION=v10.2.0 # renovate: datasource=github-releases depName=mapt-oss/pulumi-ibmcloud ARG PULUMI_IBMCLOUD_VERSION=v0.0.12 ENV IBMCLOUD_PLUGIN_URL https://github.com/mapt-oss/pulumi-ibmcloud/releases/download/${PULUMI_IBMCLOUD_VERSION}/pulumi-resource-ibmcloud-${PULUMI_IBMCLOUD_VERSION}-linux-${TARGETARCH}.tar.gz @@ -59,7 +59,7 @@ RUN mkdir -p ${PULUMI_HOME} \ && pulumi plugin install resource gitlab ${PULUMI_GITLAB_VERSION} # Stage 2: Red Hat Hardened minimal runtime (glibc + coreutils, no toolchain) -FROM registry.access.redhat.com/hi/core-runtime@sha256:1b171b70ec4cc99471bf4b70d3e338d9c703325a9c3dad6b9f69839d907db474 +FROM registry.access.redhat.com/hi/core-runtime@sha256:c435c1e85e7036af150e1f74cd81f34cf05be7aaaab2de8f66f73ac15879283d USER 0 ARG TARGETARCH LABEL org.opencontainers.image.authors="Redhat Developer" diff --git a/tools/go.mod b/tools/go.mod index 0d0da84a1..28b20f988 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -3,20 +3,21 @@ module github.com/redhat-developer/mapt/tools go 1.26.0 replace ( - github.com/charmbracelet/bubbles/v2 => charm.land/bubbles/v2 v2.1.1 + github.com/charmbracelet/bubbles/v2 => charm.land/bubbles/v2 v2.2.1 github.com/charmbracelet/bubbletea/v2 => charm.land/bubbletea/v2 v2.0.8 github.com/charmbracelet/lipgloss/v2 => charm.land/lipgloss/v2 v2.0.6 ) -require github.com/golangci/golangci-lint/v2 v2.12.2 +require github.com/golangci/golangci-lint/v2 v2.13.1 require ( + dev.gaijin.team/go/exhaustruct/v5 v5.0.3 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect - github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect + github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792 // indirect ) require ( - github.com/bombsimon/wsl/v5 v5.8.0 // indirect + github.com/bombsimon/wsl/v5 v5.9.0 // indirect github.com/charmbracelet/ultraviolet v0.0.0-20260812204455-68fa937c71be // indirect // github.com/denis-tingaikin/go-header v1.0.1 // indirect github.com/ryancurrah/gomodguard/v2 v2.1.3 // indirect @@ -50,7 +51,7 @@ require ( github.com/godoc-lint/godoc-lint v0.11.2 // indirect github.com/golangci/asciicheck v0.5.0 // indirect github.com/gostaticanalysis/nilerr v0.1.2 // indirect - github.com/nunnatsa/ginkgolinter v0.23.1 // indirect + github.com/nunnatsa/ginkgolinter v0.24.0 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect ) @@ -156,7 +157,7 @@ require ( github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-runewidth v0.0.28 // indirect - github.com/mgechev/revive v1.15.0 // indirect + github.com/mgechev/revive v1.16.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -182,7 +183,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect - github.com/securego/gosec/v2 v2.28.0 // indirect + github.com/securego/gosec/v2 v2.29.0 // indirect github.com/sirupsen/logrus v1.10.1 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sonatard/noctx v0.5.1 // indirect @@ -195,7 +196,7 @@ require ( github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect github.com/stretchr/objx v0.5.3 // indirect - github.com/stretchr/testify v1.12.0 // indirect + github.com/stretchr/testify v1.12.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tetafro/godot v1.5.6 // indirect github.com/timakin/bodyclose v0.0.0-20260723120731-857993a2939c // indirect @@ -217,7 +218,7 @@ require ( go.augendre.info/fatcontext v0.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect - golang.org/x/exp/typeparams v0.0.0-20260813180055-c1d0aacb2297 // indirect + golang.org/x/exp/typeparams v0.0.0-20260824195058-e88cd73687aa // indirect golang.org/x/mod v0.40.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect @@ -225,7 +226,7 @@ require ( golang.org/x/tools v0.49.0 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.7.0 // indirect + honnef.co/go/tools v0.8.1 // indirect mvdan.cc/gofumpt v0.11.0 // indirect - mvdan.cc/unparam v0.0.0-20260818115549-3f964bcb5673 // indirect + mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 // indirect ) diff --git a/tools/go.sum b/tools/go.sum index e332ad2ac..ae9fc4a8b 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -10,6 +10,8 @@ codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh codeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8= dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= +dev.gaijin.team/go/exhaustruct/v5 v5.0.3 h1:yOeA7DNjlT8y4yfmN6nWWYYggA13N523YAj9/TXbuTM= +dev.gaijin.team/go/exhaustruct/v5 v5.0.3/go.mod h1:KwtBsX8nHHH1YxhxkpiBq6bfsmw5WnazWpNvJPHgY9Y= dev.gaijin.team/go/golib v0.8.1 h1:JYju4x9BSo+QD/AYeHULVDcvEhiFg8wOi6pT0IaZF5E= dev.gaijin.team/go/golib v0.8.1/go.mod h1:c5fu7t1RSGMxSQgcUYO1sODbzsYnOCXJLmHeNG1Eb+0= github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= @@ -68,8 +70,8 @@ github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= -github.com/bombsimon/wsl/v5 v5.8.0 h1:JTkyfs4yl8SPejrCF2GdABXE+mO1WvM7iUYzRWlsxDs= -github.com/bombsimon/wsl/v5 v5.8.0/go.mod h1:AbOLsulgkqP4ZnitHf9gwPtCOGlrzkk0jb0uNxRSY0o= +github.com/bombsimon/wsl/v5 v5.9.0 h1:WCrgZ7RQnZO5oEwbVTlYgBdU3wL294kR1BSWV8vTfsU= +github.com/bombsimon/wsl/v5 v5.9.0/go.mod h1:kjo4HiAV5FDkHC8/uzJq9mBffEEd6WT/nvN7DoMovDM= github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE= github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg= @@ -139,8 +141,8 @@ github.com/ghostiam/protogetter v0.3.21 h1:EeWTGvL/Eyosp653hiWb6Byx4b69iJC4/E+za github.com/ghostiam/protogetter v0.3.21/go.mod h1:iAKSpyoHwYzay+OpjoWgwzRtPFthEfuUvmlomTThck0= github.com/go-critic/go-critic v0.14.4 h1:dSX4C3pWSeuMVxvQh6yG8U0ReSf3YOmKi4nwX5q7n/8= github.com/go-critic/go-critic v0.14.4/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474= github.com/go-quicktest/qt v1.102.0/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= @@ -180,10 +182,10 @@ github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 h1:CbTB8KpqnViI6lIXx github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U= github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss= -github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= -github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= -github.com/golangci/golangci-lint/v2 v2.12.2 h1:7+d1uY0bq1MU2UV3R5pW5Q7QWdcoq4naMRXM+gsJKrs= -github.com/golangci/golangci-lint/v2 v2.12.2/go.mod h1:opqHHuIcTG2R+4akzWMd4o1BnD9/1LcjICWOujr91U8= +github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792 h1:WL8YKrt3UbOBqSRU7GpP5BTtQTMWtVtj+mfPijgZeIg= +github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792/go.mod h1:te5hX0dW4C5r6YbXs+6ysNr8Q5UTmdIqGbb+mlFiYmA= +github.com/golangci/golangci-lint/v2 v2.13.1 h1:RuM4OcluM4xFQcGuRE6R7jA33pqxK/W1EsBxpugdZjg= +github.com/golangci/golangci-lint/v2 v2.13.1/go.mod h1:HwX7mDzqHbcSxlhrTygjX1GJbAfQ3sJAqOx41qQlhDE= github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0= github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10= github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg= @@ -203,8 +205,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0 h1:du0WGc8xSKq/++e0cglxhS/mXVqsR7+c7jLEi5Vqduw= -github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs= github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= @@ -290,8 +292,8 @@ github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsRe github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-runewidth v0.0.28 h1:rPyg2ybwEKPebvpzVWe1gKBkH8EQFkxO4Y0hjBeLaBU= github.com/mattn/go-runewidth v0.0.28/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= -github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= -github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= +github.com/mgechev/revive v1.16.0 h1:9OZVO8xOPUwIYynplHXv7liMllPQ/BTNZLp07sM/Im0= +github.com/mgechev/revive v1.16.0/go.mod h1:nT854jMmvQsS/qqxEaMPM4pJSUpxhcPoHM788ZxDvdQ= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= @@ -306,10 +308,10 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.23.1 h1:1JOE+iDubBmH2YvP485w9AHBI8osHFBdVeMucRRvkDM= -github.com/nunnatsa/ginkgolinter v0.23.1/go.mod h1:jh1sHyjC5TzL24rFUJvxtXV0iD2/9S7C/8N4aQd/lag= -github.com/onsi/ginkgo/v2 v2.32.0 h1:Hw7s2pVrQo/8Yz5N77qdnpHaoc+c6cC9WIV1Jce+J6E= -github.com/onsi/ginkgo/v2 v2.32.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/nunnatsa/ginkgolinter v0.24.0 h1:Mp0EagluLFP98JatP6nqp/gGEoljNG97uf9AcxcBVy8= +github.com/nunnatsa/ginkgolinter v0.24.0/go.mod h1:2ZMRuzX6+3XXyY6UZOwb6n+MCocVGbkIsDBC4vuWz5c= +github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw= +github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I= github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= @@ -363,8 +365,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= -github.com/securego/gosec/v2 v2.28.0 h1:ZsSdiDb0AtTpLFVol5z91gbMei9ZiLEPG/pZjZujp7c= -github.com/securego/gosec/v2 v2.28.0/go.mod h1:lb4/9AHe+lJy/kjWmWRWWsEipvbwGKuxf+tY1Pmjdnk= +github.com/securego/gosec/v2 v2.29.0 h1:pF2HSLcnY5voqpxQumEe0O5YAAbvF4Syu4qeIewpLyc= +github.com/securego/gosec/v2 v2.29.0/go.mod h1:47C7ej+L2bC1NOxteU2aCGnCi4cZaoKzRt5y9plIvuY= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.10.1 h1:xi4336Zh11WpU14fXR6I67V3yaTPQYwRx2WEtHbRg4Q= @@ -396,8 +398,8 @@ github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= -github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= @@ -468,8 +470,8 @@ golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/y golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20260813180055-c1d0aacb2297 h1:5MlQaTIH1l83SH0C4Xd5VtW0muW6qnokTvh0uybX1Lw= -golang.org/x/exp/typeparams v0.0.0-20260813180055-c1d0aacb2297/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo= +golang.org/x/exp/typeparams v0.0.0-20260824195058-e88cd73687aa h1:0crASk1XE9p9RP0n9EbRutyWZoOkzTSuwTp3mqwppb0= +golang.org/x/exp/typeparams v0.0.0-20260824195058-e88cd73687aa/go.mod h1:qkMnvgP8C7+xkci04txL2SJFc/0PGqnkf6gs5JUC43E= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -551,9 +553,9 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= -honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= +honnef.co/go/tools v0.8.1 h1:+JKf3xJ1ni4CwrhVg4/pqsfPGP6vNAXcKbMXJodYx3w= +honnef.co/go/tools v0.8.1/go.mod h1:XA+OnlRA9EDh/ukGvXMNSZNKGwFQJ+5dER0ioUkOxks= mvdan.cc/gofumpt v0.11.0 h1:0H01XB95PnN2QgCSR9ELdZyTlJqNZ7181B0BTMh5VZc= mvdan.cc/gofumpt v0.11.0/go.mod h1:BeT5wCsOJt6J9zT2MZIOGszjUHzFkn1/l9g6xAzqsXo= -mvdan.cc/unparam v0.0.0-20260818115549-3f964bcb5673 h1:dEE6li4OPIE54oojY2qaayFS1fSp17G14si0gXRxl0U= -mvdan.cc/unparam v0.0.0-20260818115549-3f964bcb5673/go.mod h1:62roFV3D3nYOWIXv3PfGO4UYEKAotz2WgLywT87ONd8= +mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 h1:Re1NRyLpiAt9kB+ImaaoapwWiQXrKwER4tY8fLOkDew= +mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8/go.mod h1:MrS/+zJ1M2xvGXhKktiHbNQeQPyg3Qel+KkzT+f7/i4= diff --git a/vendor/github.com/muesli/reflow/LICENSE b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/LICENSE similarity index 96% rename from vendor/github.com/muesli/reflow/LICENSE rename to tools/vendor/dev.gaijin.team/go/exhaustruct/v5/LICENSE index 8532c45c9..6698196c5 100644 --- a/vendor/github.com/muesli/reflow/LICENSE +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Christian Muehlhaeuser +Copyright (c) 2022 Gaijin Entertainment Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/analyzer.go b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/analyzer.go new file mode 100644 index 000000000..ef1bc9c59 --- /dev/null +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/analyzer.go @@ -0,0 +1,112 @@ +package analyzer + +import ( + "flag" + "sync" + + "dev.gaijin.team/go/golib/e" + "dev.gaijin.team/go/golib/fields" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + + "dev.gaijin.team/go/exhaustruct/v5/internal/astutil" + "dev.gaijin.team/go/exhaustruct/v5/internal/directive" + "dev.gaijin.team/go/exhaustruct/v5/internal/pattern" + "dev.gaijin.team/go/exhaustruct/v5/internal/structure" +) + +// NewAnalyzer returns an analyzer configured exclusively through command-line +// flags, intended for CLI drivers (singlechecker, go vet -vettool). The +// processor is built lazily on the first run, after the driver has parsed the +// flags. +func NewAnalyzer() *analysis.Analyzer { + config := &Config{} + + lazyProcessor := sync.OnceValues(func() (*structure.Processor, error) { + return newProcessor(config) + }) + + a := newBaseAnalyzer(func(pass *analysis.Pass) (any, error) { + processor, err := lazyProcessor() + if err != nil { + return nil, err + } + + run(pass, config, processor) + + return nil, nil //nolint:nilnil + }) + + a.Flags.Init("", flag.PanicOnError) + config.bindToFlagSet(&a.Flags) + + return a +} + +// NewAnalyzerWithConfig returns an analyzer configured programmatically, +// intended for library consumers such as golangci-lint. The configuration is +// copied and validated immediately; it exposes no flags, and later mutations +// of the passed Config have no effect. +func NewAnalyzerWithConfig(config Config) (*analysis.Analyzer, error) { + processor, err := newProcessor(&config) + if err != nil { + return nil, err + } + + return newBaseAnalyzer(func(pass *analysis.Pass) (any, error) { + run(pass, &config, processor) + + return nil, nil //nolint:nilnil + }), nil +} + +func newBaseAnalyzer(run func(*analysis.Pass) (any, error)) *analysis.Analyzer { + return &analysis.Analyzer{ //nolint:exhaustruct + Name: "exhaustruct", + Doc: "Checks if all structure fields are initialized", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + } +} + +func newProcessor(config *Config) (*structure.Processor, error) { + enforce, err := pattern.NewList(config.EnforcePatterns...) + if err != nil { + return nil, e.NewFrom("compile enforce patterns", err, fields.F("flag", "enforce-rx")) + } + + ignore, err := pattern.NewList(config.IgnorePatterns...) + if err != nil { + return nil, e.NewFrom("compile ignore patterns", err, fields.F("flag", "ignore-rx")) + } + + optional, err := pattern.NewList(config.OptionalPatterns...) + if err != nil { + return nil, e.NewFrom("compile optional patterns", err, fields.F("flag", "optional-rx")) + } + + allowEmpty, err := pattern.NewList(config.AllowEmptyPatterns...) + if err != nil { + return nil, e.NewFrom("compile allow-empty patterns", err, fields.F("flag", "allow-empty-rx")) + } + + fp := astutil.NewFileParser() + + return structure.NewProcessor( + directive.NewScanner(fp), + structure.NewOriginScanner(fp), + structure.WithEnforce(enforce), + structure.WithIgnore(ignore), + structure.WithOptional(optional), + structure.WithAllowEmpty(allowEmpty), + ), nil +} + +func run(pass *analysis.Pass, config *Config, processor *structure.Processor) { + for _, diag := range processor.Directives().ProcessFiles(pass.Fset, pass.Files...) { + pass.Report(diag) + } + + newMissingFieldsVisitor(pass, config, processor).run() + runTagMigration(pass) +} diff --git a/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/config.go b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/config.go new file mode 100644 index 000000000..82101bc3c --- /dev/null +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/config.go @@ -0,0 +1,134 @@ +package analyzer + +import ( + "flag" + "strings" + + "dev.gaijin.team/go/exhaustruct/v5/internal/pattern" +) + +type Config struct { + // EnforcePatterns is a list of regular expressions to match type names that + // should be checked. Anonymous structs can be matched by '' alias. + // + // Each regular expression must match the full type name, including package path. + // For example, to match type `net/http.Cookie` regular expression should be + // `.*/http\.Cookie`, but not `http\.Cookie`. + EnforcePatterns Patterns `exhaustruct:"optional"` + + // IgnorePatterns is a list of regular expressions to match type names that + // should be skipped from checking. Anonymous structs can be matched by + // '' alias. + // + // Has precedence over EnforcePatterns. + // + // Each regular expression must match the full type name, including package path. + // For example, to match type `net/http.Cookie` regular expression should be + // `.*/http\.Cookie`, but not `http\.Cookie`. + IgnorePatterns Patterns `exhaustruct:"optional"` + + // OptionalPatterns is a list of regular expressions to match type names where + // all fields are treated as optional. Anonymous structs can be matched by + // '' alias. + // + // Each regular expression must match the full type name, including package path. + // For example, to match type `net/http.Cookie` regular expression should be + // `.*/http\.Cookie`, but not `http\.Cookie`. + OptionalPatterns Patterns `exhaustruct:"optional"` + + // AllowEmpty allows empty structures, effectively excluding them from the check. + AllowEmpty bool `exhaustruct:"optional"` + + // AllowEmptyPatterns is a list of regular expressions to match type names that + // should be allowed to be empty. Anonymous structs can be matched by + // '' alias. + // + // Each regular expression must match the full type name, including package path. + // For example, to match type `net/http.Cookie` regular expression should be + // `.*/http\.Cookie`, but not `http\.Cookie`. + AllowEmptyPatterns Patterns `exhaustruct:"optional"` + + // AllowEmptyReturns allows empty structures in return statements. + AllowEmptyReturns bool `exhaustruct:"optional"` + + // AllowEmptyDeclarations allows empty structures in variable declarations. + AllowEmptyDeclarations bool `exhaustruct:"optional"` + + // ReportFullTypePath enables full package path in error messages instead of + // short package name. This helps when configuring include/exclude patterns, + // as import aliases can make short names ambiguous. + ReportFullTypePath bool `exhaustruct:"optional"` + + // ExplicitMode enables opt-in checking. When true, only types marked with + // //exhaustruct:enforce directive or matching enforce-rx patterns are checked. + ExplicitMode bool `exhaustruct:"optional"` +} + +// bindToFlagSet binds the config fields to the provided flag set. +func (c *Config) bindToFlagSet(fs *flag.FlagSet) { + fs.BoolVar(&c.ExplicitMode, "explicit", c.ExplicitMode, + "Enable explicit mode: only check types marked with //exhaustruct:enforce "+ + "directive or matching -enforce-rx patterns") + + fs.Var(&c.EnforcePatterns, "enforce-rx", + "Regular expression to match type names that should be checked. "+ + "Anonymous structs can be matched by '' alias. "+ + "Each regex must match the full type name including package path. "+ + "Example: `.*/http\\.Cookie`. Can be used multiple times.") + + fs.Var(&c.IgnorePatterns, "ignore-rx", + "Regular expression to skip type names from checking, has precedence over -enforce-rx. "+ + "Anonymous structs can be matched by '' alias. "+ + "Each regex must match the full type name including package path. "+ + "Example: `.*/http\\.Cookie`. Can be used multiple times.") + + fs.Var(&c.OptionalPatterns, "optional-rx", + "Regular expression to match type names where all fields are optional. "+ + "Anonymous structs can be matched by '' alias. "+ + "Each regex must match the full type name including package path. "+ + "Example: `.*/http\\.Cookie`. Can be used multiple times.") + + fs.BoolVar(&c.AllowEmpty, "allow-empty", c.AllowEmpty, + "Allow empty structures, effectively excluding them from the check") + + fs.Var(&c.AllowEmptyPatterns, "allow-empty-rx", + "Regular expression to match type names that should be allowed to be empty. "+ + "Anonymous structs can be matched by '' alias. "+ + "Each regex must match the full type name including package path. "+ + "Example: `.*/http\\.Cookie`. Can be used multiple times.") + + fs.BoolVar(&c.AllowEmptyReturns, "allow-empty-returns", c.AllowEmptyReturns, + "Allow empty structures in return statements") + + fs.BoolVar(&c.AllowEmptyDeclarations, "allow-empty-declarations", c.AllowEmptyDeclarations, + "Allow empty structures in variable declarations") + + fs.BoolVar(&c.ReportFullTypePath, "report-full-type-path", c.ReportFullTypePath, + "Report full package path in error messages (e.g., 'net/http.Cookie' instead of 'http.Cookie'). "+ + "Useful for identifying types when configuring enforce/ignore patterns.") +} + +// Patterns is a list of regular expression patterns. It implements +// flag.Value, validating each value as a regular expression at +// flag-parse time so invalid patterns fail early. +type Patterns []string + +// String returns the patterns joined with commas (flag.Value interface). +func (p *Patterns) String() string { + if p == nil { + return "" + } + + return strings.Join(*p, ",") +} + +// Set validates and appends a pattern (flag.Value interface). +func (p *Patterns) Set(value string) error { + if _, err := pattern.NewList(value); err != nil { + return err //nolint:wrapcheck + } + + *p = append(*p, value) + + return nil +} diff --git a/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/missing-fields-visitor.go b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/missing-fields-visitor.go new file mode 100644 index 000000000..558d80927 --- /dev/null +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/missing-fields-visitor.go @@ -0,0 +1,381 @@ +package analyzer + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "slices" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" + + "dev.gaijin.team/go/exhaustruct/v5/internal/directive" + "dev.gaijin.team/go/exhaustruct/v5/internal/structure" +) + +// missingFieldsVisitor checks struct literals for missing field initializations. +type missingFieldsVisitor struct { + pass *analysis.Pass + config *Config + processor *structure.Processor +} + +func newMissingFieldsVisitor( + pass *analysis.Pass, + config *Config, + processor *structure.Processor, +) *missingFieldsVisitor { + return &missingFieldsVisitor{ + pass: pass, + config: config, + processor: processor, + } +} + +func (v *missingFieldsVisitor) run() { + insp := v.pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert + + insp.WithStack([]ast.Node{(*ast.CompositeLit)(nil)}, v.visit) +} + +func (v *missingFieldsVisitor) visit(n ast.Node, push bool, stack []ast.Node) bool { + if !push { + return true + } + + lit, ok := n.(*ast.CompositeLit) + if !ok { + return true + } + + lv := literalVisitor{missingFieldsVisitor: v, lit: lit, stack: stack} + lv.process() + + return true +} + +// literalVisitor carries context for processing a single composite literal. +type literalVisitor struct { + *missingFieldsVisitor + + lit *ast.CompositeLit + stack []ast.Node +} + +// literal holds resolved info for a struct literal being checked. +type literal struct { + strct *structure.Struct + ignored bool + enforced bool +} + +// shouldCheck implements checking decision priority. +func (l literal) shouldCheck(explicitMode bool) bool { + if l.ignored { + return false + } + + if l.enforced { + return true + } + + if l.strct.IsIgnored() { + return false + } + + if l.strct.IsEnforced() { + return true + } + + return !explicitMode +} + +func (lv literalVisitor) process() { + lit, ok := lv.resolveLiteral() + if !ok { + return + } + + if len(lv.lit.Elts) == 0 && lv.checkEmptyAllowed(lit.strct) { + return + } + + if pos, msg := lv.checkLiteral(lit); pos != nil { + lv.pass.Reportf(*pos, "%s", msg) + } +} + +// resolveLiteral extracts struct type information from the composite literal, +// retrieves cached metadata, and looks up directives. +func (lv literalVisitor) resolveLiteral() (lit literal, ok bool) { + typeName, strct, pos := lv.resolveLiteralType() + if strct == nil { + return literal{}, false //nolint:exhaustruct + } + + s, diags := lv.processor.ResolveStruct( + lv.pass.Fset, typeName, strct, pos, lv.pass.Pkg, + ) + + for _, diag := range diags { + lv.pass.Report(diag) + } + + if s == nil { + return literal{}, false //nolint:exhaustruct + } + + litPos := lv.pass.Fset.Position(lv.lit.Pos()) + dirs, dirDiags := lv.processor.Directives().Lookup(lv.pass.Fset, litPos) + + for _, d := range dirDiags { + lv.pass.Report(d) + } + + return literal{ + strct: s, + ignored: dirs.Contains(directive.Ignore), + enforced: dirs.Contains(directive.Enforce), + }, true +} + +// resolveLiteralType resolves the composite literal's type and definition position. +func (lv literalVisitor) resolveLiteralType() (name *types.TypeName, strct *types.Struct, pos token.Pos) { + typ := lv.pass.TypesInfo.TypeOf(lv.lit) + + if ptr, ok := typ.(*types.Pointer); ok { + typ = ptr.Elem() + } + + switch t := typ.(type) { + case *types.Alias: + name = t.Obj() + case *types.Named: + name = t.Obj() + } + + typ = types.Unalias(typ) + + switch t := typ.(type) { + case *types.Named: + var ok bool + + if strct, ok = t.Underlying().(*types.Struct); !ok { + return nil, nil, token.NoPos + } + + pos = name.Pos() + + return name, strct, pos + + case *types.Struct: + pos = lv.findAnonymousStructPos() + + return name, t, pos + + default: + return nil, nil, token.NoPos + } +} + +// findAnonymousStructPos finds the position of the struct keyword for anonymous structs. +func (lv literalVisitor) findAnonymousStructPos() token.Pos { + if lv.lit.Type != nil { + if st, ok := lv.lit.Type.(*ast.StructType); ok { + return st.Struct + } + + return token.NoPos + } + + for i := len(lv.stack) - 2; i >= 0; i-- { //nolint:mnd + switch parent := lv.stack[i].(type) { + case *ast.KeyValueExpr: + continue + + case *ast.CompositeLit: + return structPosFromType(parent.Type) + + default: + return token.NoPos + } + } + + return token.NoPos +} + +func structPosFromType(typ ast.Expr) token.Pos { + if typ == nil { + return token.NoPos + } + + switch t := typ.(type) { + case *ast.ArrayType: + return structPosFromExpr(t.Elt) + + case *ast.MapType: + return structPosFromExpr(t.Value) + } + + return token.NoPos +} + +func structPosFromExpr(expr ast.Expr) token.Pos { + if star, ok := expr.(*ast.StarExpr); ok { + expr = star.X + } + + if st, ok := expr.(*ast.StructType); ok { + return st.Struct + } + + return token.NoPos +} + +func (lv literalVisitor) checkEmptyAllowed(s *structure.Struct) bool { + if lv.config.AllowEmpty { + return true + } + + if s.AllowEmptyDecl { + return true + } + + if ret, ok := lv.getParentReturnStmt(); ok { + if lv.config.AllowEmptyReturns { + return true + } + + if lv.isErrorReturnStatement(ret) { + return true + } + } + + if lv.isChildOfVariableDeclaration() && lv.config.AllowEmptyDeclarations { + return true + } + + return false +} + +func (lv literalVisitor) checkLiteral(lit literal) (*token.Pos, string) { + if !lit.shouldCheck(lv.config.ExplicitMode) { + return nil, "" + } + + strct := lit.strct + + missingFields := strct.SkippedFields(lv.lit, lv.pass.Pkg.Path()) + + if len(missingFields) == 0 { + return nil, "" + } + + pos := lv.lit.Pos() + + displayName := strct.PackageName + "." + strct.Name + if lv.config.ReportFullTypePath { + displayName = strct.FullPath + } + + if len(missingFields) == 1 { + return &pos, fmt.Sprintf("%s is missing field %s", displayName, structure.FormatFieldNames(missingFields)) + } + + return &pos, fmt.Sprintf("%s is missing fields %s", displayName, structure.FormatFieldNames(missingFields)) +} + +func (lv literalVisitor) isChildOfVariableDeclaration() bool { + if len(lv.stack) < 2 { //nolint:mnd + return false + } + + for i := len(lv.stack) - 1; i > 0; i-- { + parent := lv.stack[i-1] + + switch p := parent.(type) { + case *ast.AssignStmt: + if p.Tok == token.DEFINE { + return true + } + + case *ast.ValueSpec: + return true + + case *ast.UnaryExpr: + if p.Op == token.AND { + continue + } + + return false + + default: + return false + } + } + + return false +} + +func (lv literalVisitor) getParentReturnStmt() (*ast.ReturnStmt, bool) { + if len(lv.stack) < 2 { //nolint:mnd + return nil, false + } + + for i := len(lv.stack) - 1; i > 0; i-- { + parent := lv.stack[i-1] + + switch p := parent.(type) { + case *ast.ReturnStmt: + return p, true + + case *ast.UnaryExpr: + if p.Op == token.AND { + continue + } + + return nil, false + + default: + return nil, false + } + } + + return nil, false +} + +//nolint:forcetypeassert,gochecknoglobals +var builtinErrorInterface = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) + +func (lv literalVisitor) isErrorReturnStatement(n *ast.ReturnStmt) bool { + if len(n.Results) == 0 { + return false + } + + for _, ri := range slices.Backward(n.Results) { + if ri == lv.lit { + continue + } + + switch ri := ri.(type) { + case *ast.Ident: + if ri.Name == "nil" { + continue + } + + case *ast.UnaryExpr: + if ri.X == lv.lit { + continue + } + } + + resultType := lv.pass.TypesInfo.TypeOf(ri) + if resultType != nil && types.Implements(resultType, builtinErrorInterface) { + return true + } + } + + return false +} diff --git a/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/tag-migration-visitor.go b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/tag-migration-visitor.go new file mode 100644 index 000000000..7e75b6680 --- /dev/null +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/analyzer/tag-migration-visitor.go @@ -0,0 +1,111 @@ +package analyzer + +import ( + "go/ast" + "reflect" + "regexp" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +// runTagMigration scans struct definitions for deprecated exhaustruct tags +// and emits migration diagnostics with suggested fixes, using inspector to +// traverse StructType nodes efficiently. +func runTagMigration(pass *analysis.Pass) { + insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert + + insp.Preorder([]ast.Node{new(ast.StructType)}, func(n ast.Node) { + visitStructType(pass, n) + }) +} + +func visitStructType(pass *analysis.Pass, n ast.Node) { + st, ok := n.(*ast.StructType) + if !ok { + return + } + + if st.Fields == nil { + return + } + + for _, field := range st.Fields.List { + if field.Tag == nil { + continue + } + + value, ok := parseExhaustructTag(field.Tag.Value) + if !ok { + continue + } + + pass.Report(buildTagDiagnostic(field, value)) + } +} + +const exhaustructTagKey = "exhaustruct" + +// parseExhaustructTag extracts value from `exhaustruct:"value"` tag. +// Returns ("", false) if tag not present. +func parseExhaustructTag(tagLiteral string) (string, bool) { + if len(tagLiteral) < 2 { //nolint:mnd + return "", false + } + + // Strip backticks + inner := tagLiteral[1 : len(tagLiteral)-1] + + return reflect.StructTag(inner).Lookup(exhaustructTagKey) +} + +func buildTagDiagnostic(field *ast.Field, tagValue string) analysis.Diagnostic { + return analysis.Diagnostic{ + Pos: field.Tag.Pos(), + Message: `struct tag "exhaustruct" is not supported anymore, use comment directives`, + SuggestedFixes: []analysis.SuggestedFix{buildTagFix(field, tagValue)}, + } +} + +func buildTagFix(field *ast.Field, tagValue string) analysis.SuggestedFix { + tag := field.Tag + newTag := removeExhaustructFromTag(tag.Value) + + if tagValue == "optional" { + if newTag != "" { + newTag += " " + } + + newTag += "//exhaustruct:optional" + } + + // Calculate start position (include leading space if removing entirely) + startPos := tag.Pos() + if newTag == "" { + startPos = field.Type.End() + } + + return analysis.SuggestedFix{ + Message: "fix", + TextEdits: []analysis.TextEdit{{ + Pos: startPos, + End: tag.End(), + NewText: []byte(newTag), + }}, + } +} + +var exhaustructTagPattern = regexp.MustCompile(`\s*exhaustruct:"[^"]*"`) + +func removeExhaustructFromTag(tagLiteral string) string { + tagLiteral = tagLiteral[1 : len(tagLiteral)-1] + tagLiteral = strings.TrimSpace(exhaustructTagPattern.ReplaceAllString(tagLiteral, "")) + + if tagLiteral == "" { + return "" + } + + return "`" + tagLiteral + "`" +} diff --git a/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/astutil/file-parser.go b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/astutil/file-parser.go new file mode 100644 index 000000000..dc437fb29 --- /dev/null +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/astutil/file-parser.go @@ -0,0 +1,213 @@ +// Package astutil provides AST file parsing utilities for the analyzer. +package astutil + +import ( + "go/ast" + "go/build" + "go/parser" + "go/token" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + + "dev.gaijin.team/go/golib/e" + "dev.gaijin.team/go/golib/fields" + "golang.org/x/tools/go/analysis" +) + +type ParseCallback func(fset *token.FileSet, file *ast.File) []analysis.Diagnostic + +// FileParser orchestrates AST parsing by triggering registered callbacks. +// Each file is processed only once. +// +// Safe for concurrent use. +type FileParser struct { + mu sync.RWMutex `exhaustruct:"optional"` + parsed map[string]bool + callbacks []ParseCallback `exhaustruct:"optional"` + parseFlags parser.Mode `exhaustruct:"optional"` + hits atomic.Uint64 `exhaustruct:"optional"` + misses atomic.Uint64 `exhaustruct:"optional"` +} + +type Option func(*FileParser) + +// WithParseFlags sets parser flags for file parsing. +// Default: parser.ParseComments | parser.SkipObjectResolution. +func WithParseFlags(flags parser.Mode) Option { + return func(p *FileParser) { p.parseFlags = flags } +} + +const parsedCachePrealloc = 64 + +func NewFileParser(opts ...Option) *FileParser { + p := &FileParser{ + parsed: make(map[string]bool, parsedCachePrealloc), + parseFlags: parser.ParseComments | parser.SkipObjectResolution, + } + + for _, opt := range opts { + opt(p) + } + + return p +} + +func (p *FileParser) OnFileParsed(cb ParseCallback) { + p.callbacks = append(p.callbacks, cb) +} + +// ProcessFiles triggers all callbacks for each provided AST file. +// Already-parsed files are skipped. +func (p *FileParser) ProcessFiles(fset *token.FileSet, files ...*ast.File) []analysis.Diagnostic { + var allDiags []analysis.Diagnostic + + for _, file := range files { + filename := fset.Position(file.Pos()).Filename + + p.mu.RLock() + + alreadyParsed := p.parsed[filename] + p.mu.RUnlock() + + if alreadyParsed { + p.hits.Add(1) + + continue + } + + p.mu.Lock() + + if p.parsed[filename] { + p.mu.Unlock() + p.hits.Add(1) + + continue + } + + p.misses.Add(1) + + for _, cb := range p.callbacks { + allDiags = append(allDiags, cb(fset, file)...) + } + + p.parsed[filename] = true + p.mu.Unlock() + } + + return allDiags +} + +// ProcessFilename parses a file from disk and triggers all callbacks. +// Returns nil if already processed or if the file belongs to the Go +// distribution. +func (p *FileParser) ProcessFilename(fset *token.FileSet, filename string) []analysis.Diagnostic { + if isGoRootFile(filename) { + return nil + } + + p.mu.RLock() + + alreadyParsed := p.parsed[filename] + p.mu.RUnlock() + + if alreadyParsed { + p.hits.Add(1) + + return nil + } + + p.mu.Lock() + defer p.mu.Unlock() + + if p.parsed[filename] { + p.hits.Add(1) + + return nil + } + + p.misses.Add(1) + + file, err := p.parse(fset, filename) + if err != nil { + p.parsed[filename] = true + + return []analysis.Diagnostic{{ + Pos: token.NoPos, + Message: err.Error(), + }} + } + + var allDiags []analysis.Diagnostic + + for _, cb := range p.callbacks { + allDiags = append(allDiags, cb(fset, file)...) + } + + p.parsed[filename] = true + + return allDiags +} + +func (p *FileParser) Stats() (hits, misses, size uint64) { + p.mu.RLock() + + size = uint64(len(p.parsed)) + + p.mu.RUnlock() + + return p.hits.Load(), p.misses.Load(), size +} + +// goRootPlaceholder is the literal prefix the compiler records instead of the +// real GOROOT for files of the Go distribution, see cmd/internal/objabi.AbsFile. +// Positions loaded from export data carry it as-is, so such paths can never be +// opened. +const goRootPlaceholder = "$GOROOT" + +// isGoRootFile reports whether filename belongs to the Go distribution. Its +// sources carry no exhaustruct directives, so parsing them yields nothing and +// their definitions are treated as directive-free. +func isGoRootFile(filename string) bool { + return hasPathPrefix(filename, goRootPlaceholder) || + hasPathPrefix(filename, build.Default.GOROOT) +} + +// hasPathPrefix reports whether path starts with prefix at a path element +// boundary. Separators are normalized, since the compiler records slashes on +// every platform while GOROOT uses the native separator. An empty prefix, as +// an unset or root GOROOT normalizes to, matches nothing rather than +// everything. +func hasPathPrefix(path, prefix string) bool { + prefix = strings.TrimSuffix(filepath.ToSlash(prefix), "/") + if prefix == "" { + return false + } + + path = filepath.ToSlash(path) + + if !strings.HasPrefix(path, prefix) { + return false + } + + rest := path[len(prefix):] + + return rest == "" || rest[0] == '/' +} + +func (p *FileParser) parse(fset *token.FileSet, filename string) (*ast.File, error) { + //nolint:gosec // filename is derived from source code, not user input + content, err := os.ReadFile(filename) + if err != nil { + return nil, e.NewFrom("read file", err, fields.F("filename", filename)) + } + + file, err := parser.ParseFile(fset, filename, content, p.parseFlags) + if err != nil { + return nil, e.NewFrom("parse file", err, fields.F("filename", filename)) + } + + return file, nil +} diff --git a/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/cache/cache.go b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/cache/cache.go new file mode 100644 index 000000000..c480c6b3b --- /dev/null +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/cache/cache.go @@ -0,0 +1,100 @@ +// Package cache provides a generic thread-safe cache with hit/miss tracking. +package cache + +import ( + "sync" + "sync/atomic" +) + +type Cache[K comparable, V any] struct { + entries map[K]V + mu sync.RWMutex `exhaustruct:"optional"` + hits atomic.Uint64 `exhaustruct:"optional"` + misses atomic.Uint64 `exhaustruct:"optional"` +} + +func New[K comparable, V any](initialCapacity int) *Cache[K, V] { + return &Cache[K, V]{ + entries: make(map[K]V, initialCapacity), + } +} + +func (c *Cache[K, V]) Get(key K) (v V, ok bool) { + c.mu.RLock() + + v, ok = c.entries[key] + + c.mu.RUnlock() + + if ok { + c.hits.Add(1) + } + + return v, ok +} + +// Peek returns the value without updating hit/miss counters. Use it when the +// caller has already recorded the miss that triggered the fill and a follow-up +// read for the just-written entry must not inflate the hit rate. +func (c *Cache[K, V]) Peek(key K) (v V, ok bool) { + c.mu.RLock() + + v, ok = c.entries[key] + + c.mu.RUnlock() + + return v, ok +} + +// Set stores value and increments miss counter (caller computed the value). +func (c *Cache[K, V]) Set(key K, value V) { + c.mu.Lock() + + c.entries[key] = value + c.misses.Add(1) + + c.mu.Unlock() +} + +// GetOrSet uses double-check locking to avoid computing values that are +// cached between the initial read check and acquiring the write lock. +func (c *Cache[K, V]) GetOrSet(key K, compute func() V) V { + c.mu.RLock() + + if v, ok := c.entries[key]; ok { + c.mu.RUnlock() + c.hits.Add(1) + + return v + } + + c.mu.RUnlock() + + c.mu.Lock() + defer c.mu.Unlock() + + // Double-check after acquiring write lock. + if v, ok := c.entries[key]; ok { + c.hits.Add(1) + + return v + } + + c.misses.Add(1) + + v := compute() + + c.entries[key] = v + + return v +} + +func (c *Cache[K, V]) Stats() (hits, misses, size uint64) { + c.mu.RLock() + + size = uint64(len(c.entries)) + + c.mu.RUnlock() + + return c.hits.Load(), c.misses.Load(), size +} diff --git a/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/directive/directive.go b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/directive/directive.go new file mode 100644 index 000000000..aa7f8d904 --- /dev/null +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/directive/directive.go @@ -0,0 +1,95 @@ +package directive + +import ( + "slices" + "strings" + + "dev.gaijin.team/go/golib/e" +) + +var ( + ErrEmptyDirective = e.New("empty directive") + ErrUnknownDirective = e.New("unknown directive") + ErrDuplicateDirectives = e.New("duplicate directives") +) + +type Directive string + +const ( + // Ignore skips checking for a specific struct literal. + Ignore Directive = "ignore" + // Enforce forces check even if type is excluded. + Enforce Directive = "enforce" + // Optional marks a field as optional. + Optional Directive = "optional" +) + +func (d Directive) IsValid() bool { + switch d { + case Ignore, Enforce, Optional: + return true + + default: + return false + } +} + +// Directives represents a collection of directives for a single line. +// Multiple directives can be specified comma-separated: //exhaustruct:enforce,optional. +type Directives []Directive + +func (ds Directives) Contains(d Directive) bool { + return slices.Contains(ds, d) +} + +// directivePrefix is the exact prefix for exhaustruct directives. +// Format: //exhaustruct: [optional comment]. +const directivePrefix = "//exhaustruct:" + +func Parse(text string) (found bool, result Directives, errs []error) { + text, found = strings.CutPrefix(text, directivePrefix) + if !found { + return false, nil, nil + } + + if idx := strings.IndexAny(text, " \t\n"); idx > 0 { + text = text[:idx] + } + + if text == "" { + return true, nil, []error{ErrEmptyDirective} + } + + parts := strings.Split(text, ",") + + result = make(Directives, 0, len(parts)) + + var hasDups bool + + for _, part := range parts { + d := Directive(part) + + if !d.IsValid() { + errs = append(errs, ErrUnknownDirective.WithField("directive", d)) + continue + } + + // giving the resulting size, linear search would be most efficient + if slices.Contains(result, d) { + hasDups = true + continue + } + + result = append(result, d) + } + + if hasDups { + errs = append(errs, ErrDuplicateDirectives) + } + + if len(result) == 0 { + result = nil + } + + return true, result, errs +} diff --git a/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/directive/scanner.go b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/directive/scanner.go new file mode 100644 index 000000000..0de61ad59 --- /dev/null +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/directive/scanner.go @@ -0,0 +1,189 @@ +package directive + +import ( + "go/ast" + "go/token" + + "golang.org/x/tools/go/analysis" + + "dev.gaijin.team/go/exhaustruct/v5/internal/astutil" + "dev.gaijin.team/go/exhaustruct/v5/internal/cache" +) + +// Scanner provides thread-safe caching and lookup of file directives. +type Scanner struct { + parser *astutil.FileParser + cache *cache.Cache[string, fileDirectives] +} + +const cachePreallocSize = 64 + +// NewScanner creates a new directive scanner that registers a callback +// with the file parser to extract directives from parsed files. +func NewScanner(parser *astutil.FileParser) *Scanner { + s := &Scanner{ + parser: parser, + cache: cache.New[string, fileDirectives](cachePreallocSize), + } + + parser.OnFileParsed(s.onFileParsed) + + return s +} + +func (s *Scanner) onFileParsed(fset *token.FileSet, file *ast.File) []analysis.Diagnostic { + filename := fset.Position(file.Pos()).Filename + + fd, diags := s.parseFileDirectives(fset, file) + + s.cache.Set(filename, fd) + + return diags +} + +// ProcessFiles pre-populates the cache by delegating to FileParser.ProcessFiles. +// Returns diagnostics from directive parsing. +func (s *Scanner) ProcessFiles(fset *token.FileSet, files ...*ast.File) []analysis.Diagnostic { + return s.parser.ProcessFiles(fset, files...) +} + +// Lookup returns the directives at the given source position. +// If the file is not in cache, triggers FileParser.ProcessFilename to parse it. +func (s *Scanner) Lookup(fset *token.FileSet, pos token.Position) (Directives, []analysis.Diagnostic) { + if pos.Filename == "" { + return nil, nil + } + + if fd, ok := s.cache.Get(pos.Filename); ok { + return fd[pos.Line], nil + } + + // Cache miss - parse file (triggers onFileParsed callback, which stores + // the result via cache.Set and increments the miss counter). + diags := s.parser.ProcessFilename(fset, pos.Filename) + + // Peek avoids counting this self-induced read as a hit — the miss was + // already recorded by Set above. + if fd, ok := s.cache.Peek(pos.Filename); ok { + return fd[pos.Line], diags + } + + // Still not in cache means parsing failed. + return nil, diags +} + +func (s *Scanner) Stats() (hits, misses, size uint64) { + return s.cache.Stats() +} + +// fileDirectives holds directives found in a single file, indexed by line number. +type fileDirectives map[int]Directives + +// parseFileDirectives parses an AST file and extracts all exhaustruct directives. +// Returns diagnostics in case file parsing errors, directive parsing errors, or +// conflicting directives for the same target line. +func (*Scanner) parseFileDirectives(fset *token.FileSet, file *ast.File) (fileDirectives, []analysis.Diagnostic) { + directives, diagnostics := parseCommentDirectives(fset, file.Comments) + + if len(directives) == 0 { + return nil, diagnostics + } + + ast.Inspect(file, func(n ast.Node) bool { + switch n.(type) { + case nil, *ast.Comment, *ast.CommentGroup: + return false + } + + line := fset.Position(n.Pos()).Line + if d, ok := directives[line]; ok { + d.targetLine = line + directives[line] = d + } + + return true + }) + + result := make(fileDirectives, len(directives)) + + for line, d := range directives { + _, exists := result[d.targetLine] + if !exists { + result[d.targetLine] = d.directives + continue + } + + pos := d.pos + + // directives from block comments win over inline comments + if line != d.targetLine { + result[d.targetLine] = d.directives + pos = directives[d.targetLine].pos + } + + diagnostics = append(diagnostics, analysis.Diagnostic{ + Pos: pos, + Message: "directive ignored, conflicting directive already exists for the same target line", + }) + } + + return result, diagnostics +} + +type parsedDirective struct { + pos token.Pos + targetLine int + directives Directives +} + +func parseCommentDirectives( + fset *token.FileSet, + comments []*ast.CommentGroup, +) (map[int]parsedDirective, []analysis.Diagnostic) { + var ( + directives = make(map[int]parsedDirective) + diagnostics []analysis.Diagnostic + ) + + for _, cg := range comments { + hasDirective := false + + for _, comment := range cg.List { + found, parsed, errs := Parse(comment.Text) + if !found { + continue + } + + pos := comment.Pos() + + for _, err := range errs { + diagnostics = append(diagnostics, analysis.Diagnostic{ + Pos: pos, + Message: err.Error(), + }) + } + + if len(parsed) == 0 { + continue + } + + if hasDirective { + diagnostics = append(diagnostics, analysis.Diagnostic{ + Pos: pos, + Message: "multiple exhaustruct directives in a single comment group, ignoring", + }) + + continue + } + + hasDirective = true + directives[fset.Position(pos).Line] = parsedDirective{ + pos: pos, + targetLine: fset.Position(cg.End()).Line + 1, + directives: parsed, + } + } + } + + return directives, diagnostics +} diff --git a/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/pattern/list.go b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/pattern/list.go new file mode 100644 index 000000000..c715839fa --- /dev/null +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/pattern/list.go @@ -0,0 +1,63 @@ +package pattern + +import ( + "regexp" + + "dev.gaijin.team/go/golib/e" + "dev.gaijin.team/go/golib/fields" +) + +// List is a collection of compiled regular expressions. +type List []*regexp.Regexp + +// NewList compiles patterns into a List. +// Returns error if any pattern is empty or invalid. +func NewList(patterns ...string) (List, error) { + if len(patterns) == 0 { + return nil, nil + } + + list := make(List, 0, len(patterns)) + + for _, pattern := range patterns { + re, err := compilePattern(pattern) + if err != nil { + return nil, err + } + + list = append(list, re) + } + + return list, nil +} + +// MatchFullString returns true if any regex matches the entire string. +// Pattern "test" matches "test" but not "testing" or "contest". +func (l List) MatchFullString(target string) bool { + if len(l) == 0 { + return false + } + + for i := range len(l) { + // A match spanning [0, len(target)) covers every byte of target, so the + // matched substring is target itself — no need to allocate it for comparison. + if loc := l[i].FindStringIndex(target); loc != nil && loc[0] == 0 && loc[1] == len(target) { + return true + } + } + + return false +} + +func compilePattern(pattern string) (*regexp.Regexp, error) { + if pattern == "" { + return nil, e.New("empty regular expression is not allowed") + } + + re, err := regexp.Compile(pattern) + if err != nil { + return nil, e.NewFrom("compile regular expression", err, fields.F("pattern", pattern)) + } + + return re, nil +} diff --git a/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/structure/origin-scanner.go b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/structure/origin-scanner.go new file mode 100644 index 000000000..4f8c774db --- /dev/null +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/structure/origin-scanner.go @@ -0,0 +1,121 @@ +package structure + +import ( + "go/ast" + "go/token" + + "golang.org/x/tools/go/analysis" + + "dev.gaijin.team/go/exhaustruct/v5/internal/astutil" + "dev.gaijin.team/go/exhaustruct/v5/internal/cache" +) + +type TypeOrigin uint8 + +const ( + OriginUnknown TypeOrigin = iota + OriginStruct + OriginAlias + OriginDerived +) + +type fileOrigins map[string]TypeOrigin + +// OriginScanner extracts type origin information from AST files. +// Registers with FileParser to process files as they are parsed. +// Thread-safe. +type OriginScanner struct { + parser *astutil.FileParser + cache *cache.Cache[string, fileOrigins] +} + +const originCachePrealloc = 64 + +func NewOriginScanner(parser *astutil.FileParser) *OriginScanner { + o := &OriginScanner{ + parser: parser, + cache: cache.New[string, fileOrigins](originCachePrealloc), + } + + parser.OnFileParsed(o.onFileParsed) + + return o +} + +func (o *OriginScanner) onFileParsed( + fset *token.FileSet, + file *ast.File, +) []analysis.Diagnostic { + filename := fset.Position(file.Pos()).Filename + + origins := extractTypeOrigins(file) + + o.cache.Set(filename, origins) + + return nil +} + +// Lookup returns the type origin for a named type in the given file. +// Triggers on-demand parsing if file is not cached. +func (o *OriginScanner) Lookup( + fset *token.FileSet, + filename string, + typeName string, +) TypeOrigin { + if filename == "" || typeName == "" { + return OriginUnknown + } + + if origins, ok := o.cache.Get(filename); ok { + return origins[typeName] + } + + // Cache miss - parse file (onFileParsed stores the result via cache.Set + // and records the miss). Peek avoids inflating the hit rate by re-reading + // the entry we just wrote. + o.parser.ProcessFilename(fset, filename) + + if origins, ok := o.cache.Peek(filename); ok { + return origins[typeName] + } + + return OriginUnknown +} + +func (o *OriginScanner) Stats() (hits, misses, size uint64) { + return o.cache.Stats() +} + +func extractTypeOrigins(file *ast.File) fileOrigins { + origins := make(fileOrigins) + + for _, decl := range file.Decls { + gd, ok := decl.(*ast.GenDecl) + if !ok || gd.Tok != token.TYPE { + continue + } + + for _, spec := range gd.Specs { + ts, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + + origins[ts.Name.Name] = classifyTypeSpec(ts) + } + } + + return origins +} + +func classifyTypeSpec(ts *ast.TypeSpec) TypeOrigin { + if ts.Assign != token.NoPos { + return OriginAlias + } + + if _, isStruct := ts.Type.(*ast.StructType); isStruct { + return OriginStruct + } + + return OriginDerived +} diff --git a/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/structure/processor.go b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/structure/processor.go new file mode 100644 index 000000000..1c63030e2 --- /dev/null +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/structure/processor.go @@ -0,0 +1,254 @@ +package structure + +import ( + "go/token" + "go/types" + + "golang.org/x/tools/go/analysis" + + "dev.gaijin.team/go/exhaustruct/v5/internal/cache" + "dev.gaijin.team/go/exhaustruct/v5/internal/directive" + "dev.gaijin.team/go/exhaustruct/v5/internal/pattern" +) + +type Processor struct { + directives *directive.Scanner + origins *OriginScanner + fieldsCache *cache.Cache[*types.Struct, structFields] + structCache *cache.Cache[token.Position, *Struct] + + enforce pattern.List `exhaustruct:"optional"` + ignore pattern.List `exhaustruct:"optional"` + optional pattern.List `exhaustruct:"optional"` + allowEmpty pattern.List `exhaustruct:"optional"` +} + +type Option func(*Processor) + +func WithEnforce(patterns pattern.List) Option { + return func(p *Processor) { p.enforce = patterns } +} + +func WithIgnore(patterns pattern.List) Option { + return func(p *Processor) { p.ignore = patterns } +} + +func WithOptional(patterns pattern.List) Option { + return func(p *Processor) { p.optional = patterns } +} + +func WithAllowEmpty(patterns pattern.List) Option { + return func(p *Processor) { p.allowEmpty = patterns } +} + +const cachePreallocSize = 64 + +func NewProcessor(directives *directive.Scanner, origins *OriginScanner, opts ...Option) *Processor { + p := &Processor{ + directives: directives, + origins: origins, + fieldsCache: cache.New[*types.Struct, structFields](cachePreallocSize), + structCache: cache.New[token.Position, *Struct](cachePreallocSize), + } + + for _, opt := range opts { + opt(p) + } + + return p +} + +// Directives returns the directive scanner the processor was constructed +// with, shared so callers can resolve use-site directives against the same +// cache. +func (p *Processor) Directives() *directive.Scanner { + return p.directives +} + +// ResolveStruct returns Struct metadata for the given type. +// Type resolution (pointers, aliases) is done by the caller. +// +// Parameters: +// - typeName: the type's TypeName, or nil for anonymous structs +// - strct: the underlying struct type (required) +// - pos: position of type definition (from analyzer's AST inspection) +// - callerPkg: package context, used for anonymous struct path +func (p *Processor) ResolveStruct( + fset *token.FileSet, + typeName *types.TypeName, + strct *types.Struct, + pos token.Pos, + callerPkg *types.Package, +) (*Struct, []analysis.Diagnostic) { + if strct == nil { + return nil, nil + } + + position := fset.Position(pos) + + // Check cache before allocating + if position.IsValid() { + if cached, ok := p.structCache.Get(position); ok { + return cached, nil + } + } + + s := p.buildStruct(typeName, position, callerPkg) + + diags := p.populateFields(fset, s, strct) + p.resolveStructOrigin(fset, s) + + diags = append(diags, p.resolveStructDirectives(fset, s)...) + p.matchStructPatterns(s) + + if s.Position.IsValid() { + p.structCache.Set(s.Position, s) + } + + return s, diags +} + +// buildStruct creates Struct metadata from type info. +func (*Processor) buildStruct(typeName *types.TypeName, pos token.Position, callerPkg *types.Package) *Struct { + if typeName != nil { + pkg := typeName.Pkg() + + return &Struct{ + Name: typeName.Name(), + FullPath: pkg.Path() + "." + typeName.Name(), + PackageName: pkg.Name(), + Position: pos, + } + } + + // Anonymous struct + return &Struct{ + Name: AnonymousName, + FullPath: callerPkg.Path() + "." + AnonymousName, + PackageName: callerPkg.Name(), + Position: pos, + } +} + +func (p *Processor) getStructFields(fset *token.FileSet, strct *types.Struct) (structFields, []analysis.Diagnostic) { + if fields, ok := p.fieldsCache.Get(strct); ok { + return fields, nil + } + + fields, diags := p.resolveStructFields(fset, strct) + + p.fieldsCache.Set(strct, fields) + + return fields, diags +} + +func (p *Processor) resolveStructFields( + fset *token.FileSet, + strct *types.Struct, +) (structFields, []analysis.Diagnostic) { + result := structFields{ + packagePath: "", + fields: make([]fieldInfo, 0, strct.NumFields()), + } + + var diags []analysis.Diagnostic + + for f := range strct.Fields() { + if result.packagePath == "" && f.Pkg() != nil { + result.packagePath = f.Pkg().Path() + } + + field := fieldInfo{ + name: f.Name(), + exported: f.Exported(), + } + + if p.directives != nil { + fieldPos := fset.Position(f.Pos()) + dirs, d := p.directives.Lookup(fset, fieldPos) + + diags = append(diags, d...) + + field.enforced = dirs.Contains(directive.Enforce) + field.optional = dirs.Contains(directive.Optional) + } + + result.fields = append(result.fields, field) + } + + return result, diags +} + +func (p *Processor) populateFields(fset *token.FileSet, s *Struct, strct *types.Struct) []analysis.Diagnostic { + resolved, diags := p.getStructFields(fset, strct) + + // Fields are external when declared in a different package than the struct type. + // This happens for derived types and aliases from external packages. + // + // Rationale behind that filtering is that noone except package that has declared + // the struct can access unexported fields, therefore we can simply filter them + // out to save up on storage. Usage of derived type from the package of structure + // definition is simply impossible since it will cause import cycle - thus, such + // filtering is safe. + fieldsExternal := resolved.packagePath != s.PackagePath() + + s.Fields = Fields{ + PackagePath: resolved.packagePath, + Items: make([]Field, 0, len(resolved.fields)), + } + + for _, sf := range resolved.fields { + if fieldsExternal && !sf.exported { + continue + } + + fieldPath := s.FullPath + "#" + sf.name + + s.Fields.Items = append(s.Fields.Items, Field{ + Name: sf.name, + Exported: sf.exported, + Enforced: sf.enforced, + Optional: sf.optional, + PatternEnforced: p.enforce.MatchFullString(fieldPath), + PatternOptional: p.optional.MatchFullString(fieldPath), + }) + } + + return diags +} + +func (p *Processor) resolveStructOrigin(fset *token.FileSet, s *Struct) { + if !s.Position.IsValid() || s.Name == AnonymousName { + return + } + + origin := p.origins.Lookup(fset, s.Position.Filename, s.Name) + + s.IsAlias = origin == OriginAlias + s.IsDerived = origin == OriginDerived +} + +func (p *Processor) resolveStructDirectives(fset *token.FileSet, s *Struct) []analysis.Diagnostic { + if p.directives == nil || !s.Position.IsValid() { + return nil + } + + dirs, diags := p.directives.Lookup(fset, s.Position) + + s.Enforced = dirs.Contains(directive.Enforce) + s.Ignored = dirs.Contains(directive.Ignore) + s.Optional = dirs.Contains(directive.Optional) + + return diags +} + +func (p *Processor) matchStructPatterns(s *Struct) { + s.PatternEnforced = p.enforce.MatchFullString(s.FullPath) + s.PatternIgnored = p.ignore.MatchFullString(s.FullPath) + s.PatternOptional = p.optional.MatchFullString(s.FullPath) + s.AllowEmptyDecl = p.allowEmpty.MatchFullString(s.FullPath) +} + +func (p *Processor) Stats() (hits, misses, size uint64) { + return p.structCache.Stats() +} diff --git a/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/structure/struct.go b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/structure/struct.go new file mode 100644 index 000000000..c6cd49f2d --- /dev/null +++ b/tools/vendor/dev.gaijin.team/go/exhaustruct/v5/internal/structure/struct.go @@ -0,0 +1,224 @@ +package structure + +import ( + "go/ast" + "go/token" + "strings" +) + +const AnonymousName = "" + +// fieldInfo contains raw field data independent of type name. +type fieldInfo struct { + // name is the name of the field. + name string + // exported indicates if the field is exported. + exported bool `exhaustruct:"optional"` + // enforced indicates if the field is enforced via directive. + enforced bool `exhaustruct:"optional"` + // optional indicates if the field is optional via directive. + optional bool `exhaustruct:"optional"` +} + +// structFields contains field information for a struct, independent of type name. +type structFields struct { + // packagePath is the package path where fields are declared. + packagePath string + // fields is the list of fields in declaration order. + fields []fieldInfo +} + +type Struct struct { + Name string + FullPath string + PackageName string + + Position token.Position `exhaustruct:"optional"` + Fields Fields `exhaustruct:"optional"` + + Enforced bool `exhaustruct:"optional"` + Ignored bool `exhaustruct:"optional"` + Optional bool `exhaustruct:"optional"` + + PatternEnforced bool `exhaustruct:"optional"` + PatternIgnored bool `exhaustruct:"optional"` + PatternOptional bool `exhaustruct:"optional"` + + AllowEmptyDecl bool `exhaustruct:"optional"` + + // Detected via OriginScanner AST inspection before types.Unalias. + IsAlias bool `exhaustruct:"optional"` + IsDerived bool `exhaustruct:"optional"` +} + +// PackagePath returns the package path of the struct type. +func (s *Struct) PackagePath() string { + if idx := strings.LastIndex(s.FullPath, "."); idx >= 0 { + return s.FullPath[:idx] + } + + return s.FullPath +} + +// IsEnforced returns true if struct is enforced via directive or pattern. +func (s *Struct) IsEnforced() bool { + return s.Enforced || s.PatternEnforced +} + +// IsIgnored returns true if struct is ignored via directive or pattern. +func (s *Struct) IsIgnored() bool { + return s.Ignored || s.PatternIgnored +} + +// IsOptional returns true if struct is optional via directive or pattern. +func (s *Struct) IsOptional() bool { + return s.Optional || s.PatternOptional +} + +// SkippedFields returns missing required fields for a composite literal. +// callerPkgPath is used to determine if unexported fields are accessible. +// For positional literals: returns fields after the last provided element. +// For named literals: returns fields not present in the literal. +func (s *Struct) SkippedFields(lit *ast.CompositeLit, callerPkgPath string) []Field { + externalPkg := s.Fields.PackagePath != callerPkgPath + + if isNamedLiteral(lit) { + return s.skippedNamed(lit, externalPkg) + } + + return s.skippedPositional(len(lit.Elts), externalPkg) +} + +// isNamedLiteral checks if a composite literal uses named fields. It treats +// empty literals as not named, since positional literals checks are simpler. +func isNamedLiteral(lit *ast.CompositeLit) bool { + if len(lit.Elts) == 0 { + return false + } + + _, ok := lit.Elts[0].(*ast.KeyValueExpr) + + return ok +} + +func (s *Struct) skippedPositional(count int, externalPkg bool) []Field { + items := s.Fields.Items + + if count >= len(items) { + return nil + } + + remaining := items[count:] + missing := make([]Field, 0, len(remaining)) + + for _, f := range remaining { + if s.isFieldRequired(f, externalPkg) { + missing = append(missing, f) + } + } + + if len(missing) == 0 { + return nil + } + + return missing +} + +func (s *Struct) skippedNamed(lit *ast.CompositeLit, externalPkg bool) []Field { + present := make(map[string]bool, len(lit.Elts)) + + for _, elt := range lit.Elts { + if kv, ok := elt.(*ast.KeyValueExpr); ok { + if k, ok := kv.Key.(*ast.Ident); ok { + present[k.Name] = true + } + } + } + + missing := make([]Field, 0, len(s.Fields.Items)-len(present)) + + for _, f := range s.Fields.Items { + if !present[f.Name] && s.isFieldRequired(f, externalPkg) { + missing = append(missing, f) + } + } + + if len(missing) == 0 { + return nil + } + + return missing +} + +func (s *Struct) isFieldRequired(f Field, externalPkg bool) bool { + // explicit field directives win over everything + if f.Enforced { + return true + } + + if f.Optional { + return false + } + + // field-level patterns apply only when they specifically target the field — + // i.e., the pattern matches the field path but not the struct path. A broad + // pattern that also matches the struct is handled via s.IsEnforced/IsOptional + // and must not silently promote unrelated fields to required/optional. + if f.PatternEnforced && !s.PatternEnforced { + return true + } + + if f.PatternOptional && !s.PatternOptional { + return false + } + + // optionality can be inherited from the structure settings + if s.IsOptional() { + return false + } + + // unexported fields are only required for same-package usage + if externalPkg && !f.Exported { + return false + } + + return true +} + +type Field struct { + Name string + Exported bool `exhaustruct:"optional"` + Enforced bool `exhaustruct:"optional"` + Optional bool `exhaustruct:"optional"` + + PatternEnforced bool `exhaustruct:"optional"` + PatternOptional bool `exhaustruct:"optional"` +} + +// Fields is a collection of struct fields with shared package metadata. +// Items are in declaration order (required for positional literals). +type Fields struct { + PackagePath string + Items []Field +} + +func FormatFieldNames(fields []Field) string { + switch len(fields) { + case 0: + return "" + case 1: + return fields[0].Name + } + + var b strings.Builder + + b.Grow(len(fields)) + b.WriteString(fields[0].Name) + + for _, s := range fields[1:] { + b.WriteString(", ") + b.WriteString(s.Name) + } + + return b.String() +} diff --git a/tools/vendor/github.com/bombsimon/wsl/v5/.golangci.yml b/tools/vendor/github.com/bombsimon/wsl/v5/.golangci.yml index 9c008370e..7a55c0cde 100644 --- a/tools/vendor/github.com/bombsimon/wsl/v5/.golangci.yml +++ b/tools/vendor/github.com/bombsimon/wsl/v5/.golangci.yml @@ -14,10 +14,13 @@ linters: - dupword - err113 - exhaustruct + - exhaustruct_v5 - forbidigo - funlen - gocognit + - goconst - godot + - gomodguard - lll - mnd - nlreturn diff --git a/tools/vendor/github.com/bombsimon/wsl/v5/analyzer.go b/tools/vendor/github.com/bombsimon/wsl/v5/analyzer.go index 718ed4ba0..81a4bc4f1 100644 --- a/tools/vendor/github.com/bombsimon/wsl/v5/analyzer.go +++ b/tools/vendor/github.com/bombsimon/wsl/v5/analyzer.go @@ -12,7 +12,7 @@ import ( "golang.org/x/tools/go/analysis" ) -const version = "wsl version v5.8.0" +const version = "wsl version v5.9.0" func NewAnalyzer(config *Configuration) *analysis.Analyzer { wa := &wslAnalyzer{config: config} diff --git a/tools/vendor/github.com/bombsimon/wsl/v5/wsl.go b/tools/vendor/github.com/bombsimon/wsl/v5/wsl.go index 40098af98..d5581fc9a 100644 --- a/tools/vendor/github.com/bombsimon/wsl/v5/wsl.go +++ b/tools/vendor/github.com/bombsimon/wsl/v5/wsl.go @@ -175,8 +175,9 @@ func (w *WSL) checkCuddlingMaxAllowed( } previousNode := cursor.PreviousNode() + previousStmtNode := unlabeledStmt(previousNode) numStmtsAbove := w.numberOfStatementsAbove(cursor) - previousIdents := w.identsFromNode(previousNode, true) + previousIdents := w.identsFromNode(previousStmtNode, true) // If we don't have any statements above, we only care about potential error // cuddling (for if statements) so check that. @@ -185,7 +186,7 @@ func (w *WSL) checkCuddlingMaxAllowed( return } - if w.isLockOrUnlock(stmt, previousNode) { + if w.isLockOrUnlock(stmt, previousStmtNode) { return } @@ -195,7 +196,7 @@ func (w *WSL) checkCuddlingMaxAllowed( // We're cuddled but not with an assign, declare, increment/decrement and // we're not a statement with relaxed check. - if !isAssignDeclOrIncDec(previousNode) && !currRelaxesPrevType { + if !isAssignDeclOrIncDec(previousStmtNode) && !currRelaxesPrevType { w.addErrorInvalidTypeCuddle(cursor.Stmt().Pos(), cursor.checkType) return } @@ -1607,6 +1608,17 @@ func identsIntersect(a, b []*ast.Ident) bool { return false } +func unlabeledStmt(node ast.Node) ast.Node { + for { + labeled, ok := node.(*ast.LabeledStmt) + if !ok { + return node + } + + node = labeled.Stmt + } +} + func isTypeOrPredeclConst(obj types.Object) bool { switch o := obj.(type) { case *types.TypeName: diff --git a/tools/vendor/github.com/golangci/gofmt/Makefile b/tools/vendor/github.com/golangci/gofmt/Makefile new file mode 100644 index 000000000..8375bf400 --- /dev/null +++ b/tools/vendor/github.com/golangci/gofmt/Makefile @@ -0,0 +1,9 @@ +.PHONY: build + +default: test build + +build: + go build . + +test: + go test ./... diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt.go b/tools/vendor/github.com/golangci/gofmt/gofmt.go new file mode 100644 index 000000000..430a1b8d7 --- /dev/null +++ b/tools/vendor/github.com/golangci/gofmt/gofmt.go @@ -0,0 +1,73 @@ +package gofmt + +import ( + "fmt" + "go/ast" + "go/parser" + "go/printer" + "go/token" + + "github.com/golangci/gofmt/internal" +) + +type Options struct { + NeedSimplify bool + RewriteRules []RewriteRule +} + +type RewriteRule struct { + Pattern string + Replacement string +} + +// Source formats the code like gofmt. +// Empty string `rewrite` will be ignored. +// https://github.com/golang/go/blob/1b291b70dff51732415da5b68debe323704d8e8d/src/cmd/gofmt/gofmt.go#L236-L300 +// https://github.com/golang/go/blob/1b291b70dff51732415da5b68debe323704d8e8d/src/go/format/format.go#L101-L115 +func Source(filename string, src []byte, opts Options) ([]byte, error) { + fset := token.NewFileSet() + + file, sourceAdj, indentAdj, err := internal.Parse(fset, filename, src, false) + if err != nil { + return nil, err + } + + file, err = rewriteFileContent(fset, file, opts.RewriteRules) + if err != nil { + return nil, err + } + + ast.SortImports(fset, file) + + if opts.NeedSimplify { + internal.Simplify(file) + } + + return internal.Format(fset, file, sourceAdj, indentAdj, src, printer.Config{Mode: internal.PrinterMode, Tabwidth: internal.TabWidth}) +} + +func rewriteFileContent(fset *token.FileSet, file *ast.File, rewriteRules []RewriteRule) (*ast.File, error) { + for _, rewriteRule := range rewriteRules { + pattern, err := parseExpression(rewriteRule.Pattern, "pattern") + if err != nil { + return nil, err + } + + replacement, err := parseExpression(rewriteRule.Replacement, "replacement") + if err != nil { + return nil, err + } + + file = internal.RewriteFile(fset, pattern, replacement, file) + } + + return file, nil +} + +func parseExpression(s, what string) (ast.Expr, error) { + x, err := parser.ParseExpr(s) + if err != nil { + return nil, fmt.Errorf("parsing %s %q at %s\n", what, s, err) + } + return x, nil +} diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/golangci.go b/tools/vendor/github.com/golangci/gofmt/gofmt/golangci.go deleted file mode 100644 index a7f3ef6e7..000000000 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/golangci.go +++ /dev/null @@ -1,133 +0,0 @@ -package gofmt - -import ( - "bytes" - "fmt" - "go/ast" - "go/parser" - "go/printer" - "go/token" - "os" - "path/filepath" - "sync" - - "github.com/rogpeppe/go-internal/diff" -) - -type Options struct { - NeedSimplify bool - RewriteRules []RewriteRule -} - -var parserModeMu sync.RWMutex - -type RewriteRule struct { - Pattern string - Replacement string -} - -// Run runs gofmt. -// Deprecated: use [Source] instead. -func Run(filename string, needSimplify bool) ([]byte, error) { - return RunRewrite(filename, needSimplify, nil) -} - -// RunRewrite runs gofmt. -// Deprecated: use [Source] instead. -func RunRewrite(filename string, needSimplify bool, rewriteRules []RewriteRule) ([]byte, error) { - src, err := os.ReadFile(filename) - if err != nil { - return nil, err - } - - fset := token.NewFileSet() - - parserModeMu.Lock() - initParserMode() - parserModeMu.Unlock() - - file, sourceAdj, indentAdj, err := parse(fset, filename, src, false) - if err != nil { - return nil, err - } - - file, err = rewriteFileContent(fset, file, rewriteRules) - if err != nil { - return nil, err - } - - ast.SortImports(fset, file) - - if needSimplify { - simplify(file) - } - - res, err := format(fset, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth}) - if err != nil { - return nil, err - } - - if bytes.Equal(src, res) { - return nil, nil - } - - // formatting has changed - newName := filepath.ToSlash(filename) - oldName := newName + ".orig" - - return diff.Diff(oldName, src, newName, res), nil -} - -// Source formats the code like gofmt. -// Empty string `rewrite` will be ignored. -func Source(filename string, src []byte, opts Options) ([]byte, error) { - fset := token.NewFileSet() - - parserModeMu.Lock() - initParserMode() - parserModeMu.Unlock() - - file, sourceAdj, indentAdj, err := parse(fset, filename, src, false) - if err != nil { - return nil, err - } - - file, err = rewriteFileContent(fset, file, opts.RewriteRules) - if err != nil { - return nil, err - } - - ast.SortImports(fset, file) - - if opts.NeedSimplify { - simplify(file) - } - - return format(fset, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth}) -} - -func rewriteFileContent(fset *token.FileSet, file *ast.File, rewriteRules []RewriteRule) (*ast.File, error) { - for _, rewriteRule := range rewriteRules { - pattern, err := parseExpression(rewriteRule.Pattern, "pattern") - if err != nil { - return nil, err - } - - replacement, err := parseExpression(rewriteRule.Replacement, "replacement") - if err != nil { - return nil, err - } - - file = rewriteFile(fset, pattern, replacement, file) - } - - return file, nil -} - -func parseExpression(s, what string) (ast.Expr, error) { - x, err := parser.ParseExpr(s) - if err != nil { - return nil, fmt.Errorf("parsing %s %q at %s\n", what, s, err) - } - return x, nil -} diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/readme.md b/tools/vendor/github.com/golangci/gofmt/gofmt/readme.md deleted file mode 100644 index 907973116..000000000 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/readme.md +++ /dev/null @@ -1,15 +0,0 @@ -# Hard Fork of gofmt - -- https://github.com/golang/go/blob/master/src/cmd/gofmt/ -- https://github.com/golang/go/blob/master/src/internal/testenv -- https://github.com/golang/go/blob/master/src/internal/platform -- https://github.com/golang/go/blob/master/src/internal/diff -> replaced by `github.com/rogpeppe/go-internal/diff` -- https://github.com/golang/go/blob/master/src/internal/cfg - -## Updates - -- 2024-08-17: Sync with go1.22.6 -- 2023-02-28: Sync with go1.21.7 -- 2023-10-04: Sync with go1.20.8 -- 2023-10-04: Sync with go1.19.13 -- 2022-08-31: Sync with go1.18.5 diff --git a/tools/vendor/github.com/golangci/gofmt/internal/.gitattributes b/tools/vendor/github.com/golangci/gofmt/internal/.gitattributes new file mode 100644 index 000000000..19d80d28e --- /dev/null +++ b/tools/vendor/github.com/golangci/gofmt/internal/.gitattributes @@ -0,0 +1 @@ +/testdata/crlf.input text eol=crlf diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/LICENSE b/tools/vendor/github.com/golangci/gofmt/internal/LICENSE similarity index 100% rename from tools/vendor/github.com/golangci/gofmt/gofmt/LICENSE rename to tools/vendor/github.com/golangci/gofmt/internal/LICENSE diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/doc.go b/tools/vendor/github.com/golangci/gofmt/internal/doc.go similarity index 99% rename from tools/vendor/github.com/golangci/gofmt/gofmt/doc.go rename to tools/vendor/github.com/golangci/gofmt/internal/doc.go index d0a458021..6a25f1de1 100644 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/doc.go +++ b/tools/vendor/github.com/golangci/gofmt/internal/doc.go @@ -99,7 +99,7 @@ When invoked with -s gofmt will make the following source transformations where This may result in changes that are incompatible with earlier versions of Go. */ -package gofmt +package internal // BUG(rsc): The implementation of -r is a bit slow. // BUG(gri): If -w fails, the restored original file may not have some of the diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/gofmt.go b/tools/vendor/github.com/golangci/gofmt/internal/gofmt.go similarity index 89% rename from tools/vendor/github.com/golangci/gofmt/gofmt/gofmt.go rename to tools/vendor/github.com/golangci/gofmt/internal/gofmt.go index a4f252e86..1fd62b8f2 100644 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/gofmt.go +++ b/tools/vendor/github.com/golangci/gofmt/internal/gofmt.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gofmt +package internal import ( "bytes" @@ -10,7 +10,6 @@ import ( "flag" "fmt" "go/ast" - "go/parser" "go/printer" "go/scanner" "go/token" @@ -39,6 +38,9 @@ var ( // debugging cpuprofile = flag.String("gofmt.cpuprofile", "", "write cpu profile to this file") + + // errors + errFormattingDiffers = fmt.Errorf("formatting differs from gofmt's") ) // Keep these in sync with go/format/format.go. @@ -64,8 +66,8 @@ const ( var fdSem = make(chan bool, 200) var ( - rewrite func(*token.FileSet, *ast.File) *ast.File - parserMode parser.Mode + rewrite func(*token.FileSet, *ast.File) *ast.File + // parserMode parser.Mode // NOTE(golangci-lint): replaced with a constant ) func usage() { @@ -73,22 +75,23 @@ func usage() { flag.PrintDefaults() } +// NOTE(golangci-lint): replaced with a constant func initParserMode() { - parserMode = parser.ParseComments - if *allErrors { - parserMode |= parser.AllErrors - } - // It's only -r that makes use of go/ast's object resolution, - // so avoid the unnecessary work if the flag isn't used. - if *rewriteRule == "" { - parserMode |= parser.SkipObjectResolution - } + /* + parserMode = parser.ParseComments + if *allErrors { + parserMode |= parser.AllErrors + } + // It's only -r that makes use of go/ast's object resolution, + // so avoid the unnecessary work if the flag isn't used. + if *rewriteRule == "" { + parserMode |= parser.SkipObjectResolution + } + */ } -func isGoFile(f fs.DirEntry) bool { - // ignore non-Go files - name := f.Name() - return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && !f.IsDir() +func isGoFilename(name string) bool { + return !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") } // A sequencer performs concurrent tasks that may write output, but emits that @@ -218,8 +221,12 @@ func (r *reporter) Report(err error) { panic("Report with nil error") } st := r.getState() - scanner.PrintError(st.err, err) - st.exitCode = 2 + if err == errFormattingDiffers { + st.exitCode = 1 + } else { + scanner.PrintError(st.err, err) + st.exitCode = 2 + } } func (r *reporter) ExitCode() int { @@ -273,7 +280,7 @@ func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter) e } perm := info.Mode().Perm() - if err := writeFile(filename, src, res, perm, info.Size()); err != nil { + if err := writeFile(filename, src, res, perm); err != nil { return err } } @@ -281,6 +288,7 @@ func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter) e newName := filepath.ToSlash(filename) oldName := newName + ".orig" r.Write(diff.Diff(oldName, src, newName, res)) + return errFormattingDiffers } } @@ -406,34 +414,30 @@ func gofmtMain(s *sequencer) { } for _, arg := range args { - switch info, err := os.Stat(arg); { - case err != nil: - s.AddReport(err) - case !info.IsDir(): - // Non-directory arguments are always formatted. - arg := arg - s.Add(fileWeight(arg, info), func(r *reporter) error { - return processFile(arg, info, nil, r) - }) - default: - // Directories are walked, ignoring non-Go files. - err := filepath.WalkDir(arg, func(path string, f fs.DirEntry, err error) error { - if err != nil || !isGoFile(f) { - return err - } - info, err := f.Info() - if err != nil { - s.AddReport(err) - return nil - } - s.Add(fileWeight(path, info), func(r *reporter) error { - return processFile(path, info, nil, r) - }) - return nil - }) + // Walk each given argument as a directory tree. + // If the argument is not a directory, it's always formatted as a Go file. + // If the argument is a directory, we walk it, ignoring non-Go files. + if err := filepath.WalkDir(arg, func(path string, d fs.DirEntry, err error) error { + switch { + case err != nil: + return err + case d.IsDir(): + return nil // simply recurse into directories + case path == arg: + // non-directories given as explicit arguments are always formatted + case !isGoFilename(d.Name()): + return nil // skip walked non-Go files + } + info, err := d.Info() if err != nil { - s.AddReport(err) + return err } + s.Add(fileWeight(path, info), func(r *reporter) error { + return processFile(path, info, nil, r) + }) + return nil + }); err != nil { + s.AddReport(err) } } } @@ -458,7 +462,7 @@ func fileWeight(path string, info fs.FileInfo) int64 { } // writeFile updates a file with the new formatted data. -func writeFile(filename string, orig, formatted []byte, perm fs.FileMode, size int64) error { +func writeFile(filename string, orig, formatted []byte, perm fs.FileMode) error { // Make a temporary backup file before rewriting the original file. bakname, err := backupFile(filename, orig, perm) if err != nil { @@ -482,7 +486,7 @@ func writeFile(filename string, orig, formatted []byte, perm fs.FileMode, size i } n, err := fout.Write(formatted) - if err == nil && int64(n) < size { + if err == nil { err = fout.Truncate(int64(n)) } @@ -555,7 +559,7 @@ func backupFile(filename string, data []byte, perm fs.FileMode) (string, error) if err == nil { break } - if err != nil && !os.IsExist(err) { + if !os.IsExist(err) { return "", err } } diff --git a/tools/vendor/github.com/golangci/gofmt/internal/golangci.go b/tools/vendor/github.com/golangci/gofmt/internal/golangci.go new file mode 100644 index 000000000..b05e5aaf0 --- /dev/null +++ b/tools/vendor/github.com/golangci/gofmt/internal/golangci.go @@ -0,0 +1,51 @@ +package internal + +import ( + "go/ast" + "go/parser" + "go/printer" + "go/token" +) + +/* + The goal of this file is: + - to expose the unexported constants and the function. + - set the parserMode. +*/ + +const ( + TabWidth = tabWidth + PrinterMode = printerMode +) + +// https://github.com/golang/go/blob/1b291b70dff51732415da5b68debe323704d8e8d/src/cmd/gofmt/gofmt.go#L81-L91 +// https://github.com/golang/go/blob/1b291b70dff51732415da5b68debe323704d8e8d/src/go/format/format.go#L41 +const parserMode = parser.ParseComments | parser.SkipObjectResolution + +func Parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) ( + file *ast.File, + sourceAdj func(src []byte, indent int) []byte, + indentAdj int, + err error, +) { + return parse(fset, filename, src, fragmentOk) +} + +func Format( + fset *token.FileSet, + file *ast.File, + sourceAdj func(src []byte, indent int) []byte, + indentAdj int, + src []byte, + cfg printer.Config, +) ([]byte, error) { + return format(fset, file, sourceAdj, indentAdj, src, cfg) +} + +func Simplify(f *ast.File) { + simplify(f) +} + +func RewriteFile(fileSet *token.FileSet, pattern, replace ast.Expr, p *ast.File) *ast.File { + return rewriteFile(fileSet, pattern, replace, p) +} diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/internal.go b/tools/vendor/github.com/golangci/gofmt/internal/internal.go similarity index 96% rename from tools/vendor/github.com/golangci/gofmt/gofmt/internal.go rename to tools/vendor/github.com/golangci/gofmt/internal/internal.go index 231a25091..ef60a1432 100644 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/internal.go +++ b/tools/vendor/github.com/golangci/gofmt/internal/internal.go @@ -7,7 +7,7 @@ // one without the other. Determine if we can factor out functionality // in a public API. See also #11844 for context. -package gofmt +package internal import ( "bytes" @@ -26,13 +26,6 @@ func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) ( indentAdj int, err error, ) { - - // START - Change related to usage inside golangci-lint - parserModeMu.Lock() - parserMode := parserMode - parserModeMu.Unlock() - // END - Change related to usage inside golangci-lint - // Try as whole source file. file, err = parser.ParseFile(fset, filename, src, parserMode) // If there's no error, return. If the error is that the source file didn't begin with a diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/rewrite.go b/tools/vendor/github.com/golangci/gofmt/internal/rewrite.go similarity index 96% rename from tools/vendor/github.com/golangci/gofmt/gofmt/rewrite.go rename to tools/vendor/github.com/golangci/gofmt/internal/rewrite.go index c95d44f61..0a399986b 100644 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/rewrite.go +++ b/tools/vendor/github.com/golangci/gofmt/internal/rewrite.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gofmt +package internal import ( "fmt" @@ -105,11 +105,11 @@ var ( objectPtrNil = reflect.ValueOf((*ast.Object)(nil)) scopePtrNil = reflect.ValueOf((*ast.Scope)(nil)) - identType = reflect.TypeOf((*ast.Ident)(nil)) - objectPtrType = reflect.TypeOf((*ast.Object)(nil)) - positionType = reflect.TypeOf(token.NoPos) - callExprType = reflect.TypeOf((*ast.CallExpr)(nil)) - scopePtrType = reflect.TypeOf((*ast.Scope)(nil)) + identType = reflect.TypeFor[*ast.Ident]() + objectPtrType = reflect.TypeFor[*ast.Object]() + positionType = reflect.TypeFor[token.Pos]() + callExprType = reflect.TypeFor[*ast.CallExpr]() + scopePtrType = reflect.TypeFor[*ast.Scope]() ) // apply replaces each AST field x in val with f(x), returning val. diff --git a/tools/vendor/github.com/golangci/gofmt/gofmt/simplify.go b/tools/vendor/github.com/golangci/gofmt/internal/simplify.go similarity index 99% rename from tools/vendor/github.com/golangci/gofmt/gofmt/simplify.go rename to tools/vendor/github.com/golangci/gofmt/internal/simplify.go index 3b34d562b..d57e8cef0 100644 --- a/tools/vendor/github.com/golangci/gofmt/gofmt/simplify.go +++ b/tools/vendor/github.com/golangci/gofmt/internal/simplify.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gofmt +package internal import ( "go/ast" diff --git a/tools/vendor/github.com/golangci/gofmt/readme.md b/tools/vendor/github.com/golangci/gofmt/readme.md new file mode 100644 index 000000000..6e1b0036d --- /dev/null +++ b/tools/vendor/github.com/golangci/gofmt/readme.md @@ -0,0 +1,46 @@ +# Hard Fork of gofmt + +## Updates + +- 2026-08-20: Sync with go1.27.0 +- 2025-12-14: Sync with go1.26.0-pre-rc1 + - except (because it uses go1.26 specific elements): + - `internal/testenv/testenv_unix.go` + - `internal/platform/zosarch.go` +- 2025-07-04: Sync with go1.24.4 +- 2025-04-14: Sync with go1.23.8 +- 2024-08-17: Sync with go1.22.6 +- 2023-02-28: Sync with go1.21.7 +- 2023-10-04: Sync with go1.20.8 +- 2023-10-04: Sync with go1.19.13 +- 2022-08-31: Sync with go1.18.5 + +## Notes + +### Packages + +- https://github.com/golang/go/blob/master/src/cmd/gofmt/ +- https://github.com/golang/go/blob/master/src/internal/cfg +- https://github.com/golang/go/blob/master/src/internal/goarch +- https://github.com/golang/go/blob/master/src/internal/testenv +- https://github.com/golang/go/blob/master/src/internal/platform +- https://github.com/golang/go/blob/master/src/internal/diff -> replaced by `github.com/rogpeppe/go-internal/diff` + +### Details + +`go/src/cmd/gofmt/internal.go` and `go/src/go/format/internal.go` are identical. +The `parserMode` is a global variable for `gofmt` and a constant for `go/format`. + +The constants (`tabWidth`, `printerMode`, `printerNormalizeNumbers`) are duplicated inside: +- [`go/src/cmd/gofmt/gofmt.go`](https://github.com/golang/go/blob/1b291b70dff51732415da5b68debe323704d8e8d/src/cmd/gofmt/gofmt.go#L49-L59) +- [`go/src/go/format/format.go`](https://github.com/golang/go/blob/1b291b70dff51732415da5b68debe323704d8e8d/src/go/format/format.go#L27-L37) + +Theoretically, only the following files are required: +- `gofmt.go` (only the constants (`tabWidth`, `printerMode`, `printerNormalizeNumbers`)) +- `internal.go` +- `LICENSE` +- `rewrite.go` +- `simplify.go` + +But it's easier to synchronize everything to follow changes. +But the isolation of `internal` packages from Go can be complex, so maybe, at some point, we will reduce the number of files and so remove the `internal/internal` directory (and the test files). diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/cache/cache.go b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/cache/cache.go index f299afe7c..cf20b175d 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/cache/cache.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/cache/cache.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "maps" - "path/filepath" "runtime" "slices" "strings" @@ -173,11 +172,6 @@ func (c *Cache) computePkgHash(pkg *packages.Package) (hashResults, error) { return nil, fmt.Errorf("failed to calculate file %s hash: %w", f, fErr) } - // This is the current module (the project to analyze). - if pkg.Module != nil && pkg.Module.Version == "" { - f = pkg.Module.Path + strings.TrimPrefix(filepath.ToSlash(f), filepath.ToSlash(pkg.Module.Dir)) - } - fmt.Fprintf(key, "file %s %x\n", f, h) } @@ -296,7 +290,7 @@ func SetSalt(b *bytes.Buffer) { cache.SetSalt(b.Bytes()) } -func DefaultDir() string { - cacheDir, _ := cache.DefaultDir() - return cacheDir +func DefaultDir() (string, error) { + cacheDir, _, err := cache.DefaultDir() + return cacheDir, err } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/base/readme.md b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/base/readme.md index 93afe9f28..49ba0c699 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/base/readme.md +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/base/readme.md @@ -6,5 +6,7 @@ Only the function `IsETXTBSY` is extracted. ## History +- https://github.com/golangci/golangci-lint/pull/6642 + - sync go1.26.4 (no change) - https://github.com/golangci/golangci-lint/pull/5576 - sync go1.24.1 diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/cache.go b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/cache.go index c514613dc..1ac3f0feb 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/cache.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/cache.go @@ -303,6 +303,10 @@ func GetBytes(c Cache, id ActionID) ([]byte, Entry, error) { // GetMmap looks up the action ID in the cache and returns // the corresponding output bytes. // GetMmap should only be used for data that can be expected to fit in memory. +// The boolean result indicates whether the file was opened. +// If it is true, the caller should avoid attempting +// to write to the file on Windows, because Windows locks +// the open file, and writes to it will fail. func GetMmap(c Cache, id ActionID) ([]byte, Entry, bool, error) { entry, err := c.Get(id) if err != nil { @@ -388,13 +392,42 @@ func (c *DiskCache) Trim() error { // trim time is too far in the future, attempt the trim anyway. It's possible that // the cache was full when the corruption happened. Attempting a trim on // an empty cache is cheap, so there wouldn't be a big performance hit in that case. - if data, err := lockedfile.Read(filepath.Join(c.dir, "trim.txt")); err == nil { + skipTrim := func(data []byte) bool { if t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64); err == nil { lastTrim := time.Unix(t, 0) if d := now.Sub(lastTrim); d < trimInterval && d > -mtimeInterval { - return nil + return true } } + return false + } + // Check to see if we need a trim. Do this check separately from the lockedfile.Transform + // so that we can skip getting an exclusive lock in the common case. + if data, err := lockedfile.Read(filepath.Join(c.dir, "trim.txt")); err == nil { + if skipTrim(data) { + return nil + } + } + + errFileChanged := errors.New("file changed") + + // Write the new timestamp before we start trimming to reduce the chance that multiple invocations + // try to trim at the same time, causing contention in CI (#76314). + err := lockedfile.Transform(filepath.Join(c.dir, "trim.txt"), func(data []byte) ([]byte, error) { + if skipTrim(data) { + // The timestamp in the file no longer meets the criteria for us to + // do a trim. It must have been updated by another go command invocation + // since we last read it. Skip the trim. + return nil, errFileChanged + } + return fmt.Appendf(nil, "%d", now.Unix()), nil + }) + if errors.Is(err, errors.ErrUnsupported) { + return err + } + if errors.Is(err, errFileChanged) { + // Skip the trim because we don't need it anymore. + return nil } // Trim each of the 256 subdirectories. @@ -406,14 +439,6 @@ func (c *DiskCache) Trim() error { c.trimSubdir(subdir, cutoff) } - // Ignore errors from here: if we don't write the complete timestamp, the - // cache will appear older than it is, and we'll trim it again next time. - var b bytes.Buffer - fmt.Fprintf(&b, "%d", now.Unix()) - if err := lockedfile.Write(filepath.Join(c.dir, "trim.txt"), &b, 0o666); err != nil { - return err - } - return nil } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/default.go b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/default.go index cf38ab3d7..a6cacaf18 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/default.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/default.go @@ -29,7 +29,10 @@ const cacheREADME = `This directory holds cached build artifacts from golangci-l // initDefaultCache does the work of finding the default cache // the first time Default is called. func initDefaultCache() Cache { - dir, _ := DefaultDir() + dir, _, err := DefaultDir() + if err != nil { + base.Fatalf("build cache is required, but could not be located: %v", err) + } if dir == "off" { if defaultDirErr != nil { base.Fatalf("build cache is required, but could not be located: %v", defaultDirErr) @@ -66,16 +69,27 @@ var ( // DefaultDir returns the effective GOLANGCI_LINT_CACHE setting. // It returns "off" if the cache is disabled, // and reports whether the effective value differs from GOLANGCI_LINT_CACHE. -func DefaultDir() (string, bool) { +func DefaultDir() (string, bool, error) { // Save the result of the first call to DefaultDir for later use in // initDefaultCache. cmd/go/main.go explicitly sets GOLANGCI_LINT_CACHE so that // subprocesses will inherit it, but that means initDefaultCache can't // otherwise distinguish between an explicit "off" and a UserCacheDir error. defaultDirOnce.Do(func() { - defaultDir = os.Getenv(envGolangciLintCache) - if defaultDir != "" { - defaultDirChanged = true + // Compute default location. + dir, err := os.UserCacheDir() + if err != nil { + defaultDir = "off" + defaultDirErr = fmt.Errorf("%s is not defined and %v", envGolangciLintCache, err) + } else { + defaultDir = filepath.Join(dir, "golangci-lint") + } + + newDir := os.Getenv(envGolangciLintCache) + if newDir != "" { + defaultDirErr = nil + defaultDirChanged = newDir != defaultDir + defaultDir = newDir if filepath.IsAbs(defaultDir) || defaultDir == "off" { return } @@ -83,17 +97,7 @@ func DefaultDir() (string, bool) { defaultDirErr = fmt.Errorf("%s is not an absolute path", envGolangciLintCache) return } - - // Compute default location. - dir, err := os.UserCacheDir() - if err != nil { - defaultDir = "off" - defaultDirChanged = true - defaultDirErr = fmt.Errorf("%s is not defined and %w", envGolangciLintCache, err) - return - } - defaultDir = filepath.Join(dir, "golangci-lint") }) - return defaultDir, defaultDirChanged + return defaultDir, defaultDirChanged, defaultDirErr } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/hash.go b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/hash.go index 6a53dd886..3fa2422f5 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/hash.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/hash.go @@ -43,6 +43,9 @@ func stripExperiment(version string) string { if i := strings.Index(version, " X:"); i >= 0 { return version[:i] } + if i := strings.Index(version, "-X:"); i >= 0 { + return version[:i] + } return version } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/readme.md b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/readme.md index 66341fd4b..6d33419f0 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/readme.md +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cache/readme.md @@ -3,7 +3,7 @@ Extracted from `go/src/cmd/go/internal/cache/`. The main modifications are: -- The errors management +- The error management - Some methods return error. - Some errors are returned instead of being ignored. - The name of the env vars: @@ -12,6 +12,8 @@ The main modifications are: ## History +- https://github.com/golangci/golangci-lint/pull/6642 + - sync go1.26.4 - https://github.com/golangci/golangci-lint/pull/5576 - sync go1.24.1 - https://github.com/golangci/golangci-lint/pull/5100 @@ -30,7 +32,7 @@ The main modifications are: ## Previous History -Based on the initial PR/commit the based in a mix between go1.12 and go1.13: +Based on the initial PR/commit this was based in a mix between go1.12 and go1.13: - cache.go (go1.13) - cache_test.go (go1.12?) - default.go (go1.12?) diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cacheprog/cacheprog.go b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cacheprog/cacheprog.go index a2796592d..9379636e5 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cacheprog/cacheprog.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cacheprog/cacheprog.go @@ -76,9 +76,6 @@ type Request struct { ActionID []byte `json:",omitempty"` // or nil if not used // OutputID is stored with the body for "put" requests. - // - // Prior to Go 1.24, when GOCACHEPROG was still an experiment, this was - // accidentally named ObjectID. It was renamed to OutputID in Go 1.24. OutputID []byte `json:",omitempty"` // or nil if not used // Body is the body for "put" requests. It's sent after the JSON object @@ -91,14 +88,6 @@ type Request struct { // BodySize is the number of bytes of Body. If zero, the body isn't written. BodySize int64 `json:",omitempty"` - - // ObjectID is the accidental spelling of OutputID that was used prior to Go - // 1.24. - // - // Deprecated: use OutputID. This field is only populated temporarily for - // backwards compatibility with Go 1.23 and earlier when - // GOEXPERIMENT=gocacheprog is set. It will be removed in Go 1.25. - ObjectID []byte `json:",omitempty"` } // Response is the JSON response from the child process to the go command. @@ -125,7 +114,7 @@ type Response struct { // For "get" requests. Miss bool `json:",omitempty"` // cache miss - OutputID []byte `json:",omitempty"` // the ObjectID stored with the body + OutputID []byte `json:",omitempty"` // the OutputID stored with the body Size int64 `json:",omitempty"` // body size in bytes Time *time.Time `json:",omitempty"` // when the object was put in the cache (optional; used for cache expiration) diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cacheprog/readme.md b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cacheprog/readme.md index 1b08c8480..defb1adae 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cacheprog/readme.md +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/cacheprog/readme.md @@ -5,5 +5,7 @@ This is just a copy of the Go code without any changes. ## History +- https://github.com/golangci/golangci-lint/pull/6642 + - sync go1.26.4 - https://github.com/golangci/golangci-lint/pull/5576 - sync go1.24.1 diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/mmap/mmap.go b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/mmap/mmap.go index fd374df82..cd7ea80f2 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/mmap/mmap.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/mmap/mmap.go @@ -22,11 +22,30 @@ type Data struct { } // Mmap maps the given file into memory. +// The boolean result indicates whether the file was opened. +// If it is true, the caller should avoid attempting +// to write to the file on Windows, because Windows locks +// the open file, and writes to it will fail. func Mmap(file string) (Data, bool, error) { f, err := os.Open(file) if err != nil { return Data{}, false, err } data, err := mmapFile(f) + + // Closing the file causes it not to count against this process's + // limit on open files; however, the mapping still counts against + // the system-wide limit, which is typically higher. Examples: + // + // macOS process (sysctl kern.maxfilesperproc): 61440 + // macOS system (sysctl kern.maxfiles): 122880 + // linux process (ulimit -n) 1048576 + // linux system (/proc/sys/fs/file-max) 100000 + if cerr := f.Close(); cerr != nil && err == nil { + return data, true, cerr + } + + // The file is still considered to be in use on Windows after + // it's closed because of the mapping. return data, true, err } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/mmap/readme.md b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/mmap/readme.md index 5cbfdeefe..764c2444a 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/mmap/readme.md +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/mmap/readme.md @@ -5,6 +5,8 @@ This is just a copy of the Go code without any changes. ## History +- https://github.com/golangci/golangci-lint/pull/6642 + - sync go1.26.4 - https://github.com/golangci/golangci-lint/pull/5576 - sync go1.24.1 - https://github.com/golangci/golangci-lint/pull/5100 diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/quoted/readme.md b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/quoted/readme.md index 97868185c..45467e928 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/quoted/readme.md +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/go/quoted/readme.md @@ -5,6 +5,8 @@ This is just a copy of the Go code without any changes. ## History +- https://github.com/golangci/golangci-lint/pull/6642 + - sync go1.26.4 (no change) - https://github.com/golangci/golangci-lint/pull/5576 - sync go1.24.1 (no change) - https://github.com/golangci/golangci-lint/pull/5100 diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/lcs/old.go b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/lcs/old.go index 4c346706a..d6265c8c7 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/lcs/old.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/lcs/old.go @@ -16,10 +16,6 @@ type Diff struct { ReplStart, ReplEnd int // offset of replacement text in B } -// DiffStrings returns the differences between two strings. -// It does not respect rune boundaries. -func DiffStrings(a, b string) []Diff { return diff(stringSeqs{a, b}) } - // DiffBytes returns the differences between two byte sequences. // It does not respect rune boundaries. func DiffBytes(a, b []byte) []Diff { return diff(bytesSeqs{a, b}) } @@ -27,9 +23,13 @@ func DiffBytes(a, b []byte) []Diff { return diff(bytesSeqs{a, b}) } // DiffRunes returns the differences between two rune sequences. func DiffRunes(a, b []rune) []Diff { return diff(runesSeqs{a, b}) } +// DiffLines returns the differences between two string sequences. +func DiffLines(a, b []string) []Diff { return diff(linesSeqs{a, b}) } + +// A limit on how deeply the LCS algorithm should search. The value is just a guess. +var maxDiffs = 100 + func diff(seqs sequences) []Diff { - // A limit on how deeply the LCS algorithm should search. The value is just a guess. - const maxDiffs = 100 diff, _ := compute(seqs, twosided, maxDiffs/2) return diff } @@ -378,10 +378,7 @@ func (e *editGraph) twoDone(df, db int) (int, bool) { return 0, false // diagonals cannot overlap } kmin := max(-df, -db+e.delta) - kmax := db + e.delta - if df < kmax { - kmax = df - } + kmax := min(df, db+e.delta) for k := kmin; k <= kmax; k += 2 { x := e.vf.get(df, k) u := e.vb.get(db, k-e.delta) diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/lcs/sequence.go b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/lcs/sequence.go index 2d72d2630..429e8c619 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/lcs/sequence.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/lcs/sequence.go @@ -13,63 +13,44 @@ type sequences interface { commonSuffixLen(ai, aj, bi, bj int) int // len(commonSuffix(A[ai:aj], B[bi:bj])) } -type stringSeqs struct{ a, b string } - -func (s stringSeqs) lengths() (int, int) { return len(s.a), len(s.b) } -func (s stringSeqs) commonPrefixLen(ai, aj, bi, bj int) int { - return commonPrefixLenString(s.a[ai:aj], s.b[bi:bj]) -} -func (s stringSeqs) commonSuffixLen(ai, aj, bi, bj int) int { - return commonSuffixLenString(s.a[ai:aj], s.b[bi:bj]) -} - // The explicit capacity in s[i:j:j] leads to more efficient code. type bytesSeqs struct{ a, b []byte } func (s bytesSeqs) lengths() (int, int) { return len(s.a), len(s.b) } func (s bytesSeqs) commonPrefixLen(ai, aj, bi, bj int) int { - return commonPrefixLenBytes(s.a[ai:aj:aj], s.b[bi:bj:bj]) + return commonPrefixLen(s.a[ai:aj:aj], s.b[bi:bj:bj]) } func (s bytesSeqs) commonSuffixLen(ai, aj, bi, bj int) int { - return commonSuffixLenBytes(s.a[ai:aj:aj], s.b[bi:bj:bj]) + return commonSuffixLen(s.a[ai:aj:aj], s.b[bi:bj:bj]) } type runesSeqs struct{ a, b []rune } func (s runesSeqs) lengths() (int, int) { return len(s.a), len(s.b) } func (s runesSeqs) commonPrefixLen(ai, aj, bi, bj int) int { - return commonPrefixLenRunes(s.a[ai:aj:aj], s.b[bi:bj:bj]) + return commonPrefixLen(s.a[ai:aj:aj], s.b[bi:bj:bj]) } func (s runesSeqs) commonSuffixLen(ai, aj, bi, bj int) int { - return commonSuffixLenRunes(s.a[ai:aj:aj], s.b[bi:bj:bj]) + return commonSuffixLen(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} + +type linesSeqs struct{ a, b []string } + +func (s linesSeqs) lengths() (int, int) { return len(s.a), len(s.b) } +func (s linesSeqs) commonPrefixLen(ai, aj, bi, bj int) int { + return commonPrefixLen(s.a[ai:aj], s.b[bi:bj]) +} +func (s linesSeqs) commonSuffixLen(ai, aj, bi, bj int) int { + return commonSuffixLen(s.a[ai:aj], s.b[bi:bj]) } // TODO(adonovan): optimize these functions using ideas from: // - https://go.dev/cl/408116 common.go // - https://go.dev/cl/421435 xor_generic.go -// TODO(adonovan): factor using generics when available, -// but measure performance impact. - -// commonPrefixLen* returns the length of the common prefix of a[ai:aj] and b[bi:bj]. -func commonPrefixLenBytes(a, b []byte) int { - n := min(len(a), len(b)) - i := 0 - for i < n && a[i] == b[i] { - i++ - } - return i -} -func commonPrefixLenRunes(a, b []rune) int { - n := min(len(a), len(b)) - i := 0 - for i < n && a[i] == b[i] { - i++ - } - return i -} -func commonPrefixLenString(a, b string) int { +// commonPrefixLen returns the length of the common prefix of a[ai:aj] and b[bi:bj]. +func commonPrefixLen[T comparable](a, b []T) int { n := min(len(a), len(b)) i := 0 for i < n && a[i] == b[i] { @@ -78,16 +59,8 @@ func commonPrefixLenString(a, b string) int { return i } -// commonSuffixLen* returns the length of the common suffix of a[ai:aj] and b[bi:bj]. -func commonSuffixLenBytes(a, b []byte) int { - n := min(len(a), len(b)) - i := 0 - for i < n && a[len(a)-1-i] == b[len(b)-1-i] { - i++ - } - return i -} -func commonSuffixLenRunes(a, b []rune) int { +// commonSuffixLen returns the length of the common suffix of a[ai:aj] and b[bi:bj]. +func commonSuffixLen[T comparable](a, b []T) int { n := min(len(a), len(b)) i := 0 for i < n && a[len(a)-1-i] == b[len(b)-1-i] { @@ -95,19 +68,3 @@ func commonSuffixLenRunes(a, b []rune) int { } return i } -func commonSuffixLenString(a, b string) int { - n := min(len(a), len(b)) - i := 0 - for i < n && a[len(a)-1-i] == b[len(b)-1-i] { - i++ - } - return i -} - -func min(x, y int) int { - if x < y { - return x - } else { - return y - } -} diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/ndiff.go b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/ndiff.go index 1c64d1ecd..5083a7ea7 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/ndiff.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/ndiff.go @@ -6,11 +6,30 @@ package diff import ( "bytes" + "strings" "unicode/utf8" "github.com/golangci/golangci-lint/v2/internal/x/tools/diff/lcs" ) +// Lines computes differences between two strings. All edits are at line boundaries. +func Lines(before, after string) []Edit { + beforeLines, bOffsets := splitLines(before) + afterLines, _ := splitLines(after) + diffs := lcs.DiffLines(beforeLines, afterLines) + + // Convert from LCS diffs to Edits + res := make([]Edit, len(diffs)) + for i, d := range diffs { + res[i] = Edit{ + Start: bOffsets[d.Start], + End: bOffsets[d.End], + New: strings.Join(afterLines[d.ReplStart:d.ReplEnd], ""), + } + } + return res +} + // Strings computes the differences between two strings. // The resulting edits respect rune boundaries. func Strings(before, after string) []Edit { diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/readme.md b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/readme.md index b28e41d9c..7262b45aa 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/readme.md +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/readme.md @@ -5,6 +5,8 @@ This is just a copy of the code without any changes. ## History +- https://github.com/golangci/golangci-lint/pull/6642 + - sync with https://github.com/golang/tools/blob/v0.48.0/internal/diff/ - https://github.com/golangci/golangci-lint/pull/6076 - sync with https://github.com/golang/tools/blob/v0.37.0/internal/diff/ - https://github.com/golangci/golangci-lint/pull/5576 diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/unified.go b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/unified.go index 9a786dbbe..8fc011e4d 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/unified.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/diff/unified.go @@ -7,6 +7,8 @@ package diff import ( "fmt" "log" + "regexp" + "strconv" "strings" ) @@ -18,7 +20,7 @@ const DefaultContextLines = 3 // The old and new labels are the names of the old and new files. // If the strings are equal, it returns the empty string. func Unified(oldLabel, newLabel, old, new string) string { - edits := Strings(old, new) + edits := Lines(old, new) unified, err := ToUnified(oldLabel, newLabel, old, edits, DefaultContextLines) if err != nil { // Can't happen: edits are consistent. @@ -114,7 +116,7 @@ func toUnified(fromName, toName string, content string, edits []Edit, contextLin if err != nil { return u, err } - lines := splitLines(content) + lines, _ := splitLines(content) var h *hunk last := 0 toLine := 0 @@ -156,7 +158,8 @@ func toUnified(fromName, toName string, content string, edits []Edit, contextLin last++ } if edit.New != "" { - for _, content := range splitLines(edit.New) { + v, _ := splitLines(edit.New) + for _, content := range v { h.lines = append(h.lines, line{kind: opInsert, content: content}) toLine++ } @@ -170,12 +173,24 @@ func toUnified(fromName, toName string, content string, edits []Edit, contextLin return u, nil } -func splitLines(text string) []string { - lines := strings.SplitAfter(text, "\n") - if lines[len(lines)-1] == "" { - lines = lines[:len(lines)-1] +// split into lines removing a final empty line, +// and also return the offsets of the line beginnings. +func splitLines(text string) ([]string, []int) { + var lines []string + offsets := []int{0} + start := 0 + for i, r := range text { + if r == '\n' { + lines = append(lines, text[start:i+1]) + start = i + 1 + offsets = append(offsets, start) + } + } + if start < len(text) { + lines = append(lines, text[start:]) + offsets = append(offsets, len(text)) } - return lines + return lines, offsets } func addEqualLines(h *hunk, lines []string, start, end int) int { @@ -249,3 +264,51 @@ func (u unified) String() string { } return b.String() } + +// ApplyUnified applies the unified diffs. +func ApplyUnified(udiffs, bef string) (string, error) { + before := strings.Split(bef, "\n") + unif := strings.Split(udiffs, "\n") + var got []string + left := 0 + // parse and apply the unified diffs + for _, l := range unif { + if len(l) == 0 { + continue // probably the last line (from Split) + } + switch l[0] { + case '@': // The @@ line + m := atregexp.FindStringSubmatch(l) + fromLine, err := strconv.Atoi(m[1]) + if err != nil { + return "", fmt.Errorf("missing line number in %q", l) + } + // before is a slice, so0-based; fromLine is 1-based + for ; left < fromLine-1; left++ { + got = append(got, before[left]) + } + case '+': // add this line + if strings.HasPrefix(l, "+++ ") { + continue + } + got = append(got, l[1:]) + case '-': // delete this line + if strings.HasPrefix(l, "--- ") { + continue + } + left++ + case ' ': + return "", fmt.Errorf("unexpected line %q", l) + default: + return "", fmt.Errorf("invalid unified diff: <<%s>>", udiffs) + } + } + // copy any remaining lines + for ; left < len(before); left++ { + got = append(got, before[left]) + } + return strings.Join(got, "\n"), nil +} + +// The first number in the @@ lines is the line number in the 'before' data +var atregexp = regexp.MustCompile(`@@ -(\d+).* @@`) diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/driverutil/readme.md b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/driverutil/readme.md index 8720fb6ff..1ee6259d3 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/driverutil/readme.md +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/internal/x/tools/driverutil/readme.md @@ -7,6 +7,9 @@ Previously, it was `analysisinternal` and `analysisflags` packages. ## History +- https://github.com/golangci/golangci-lint/pull/6642 (no changes) + - sync with https://github.com/golang/tools/blob/v0.48.0/internal/analysis/driverutil/readfile.go + - https://github.com/golangci/golangci-lint/pull/6434 - sync with https://github.com/golang/tools/blob/v0.43.0/internal/analysis/driverutil/readfile.go diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/jsonschema/golangci.jsonschema.json b/tools/vendor/github.com/golangci/golangci-lint/v2/jsonschema/golangci.jsonschema.json index 0ac9d577e..bb4f0398e 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/jsonschema/golangci.jsonschema.json +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/jsonschema/golangci.jsonschema.json @@ -716,7 +716,8 @@ "identical", "unused", "opaque", - "unexported" + "unexported", + "unusedmethod" ] }, "tagliatelle-cases": { @@ -739,15 +740,21 @@ "modernize-analyzers": { "enum": [ "any", - "fmtappendf", + "atomictypes", + "embedlit", + "errorsastype", "forvar", + "importcomment", "mapsloop", "minmax", "newexpr", "omitzero", "plusbuild", "rangeint", + "reflecttypeassert", "reflecttypefor", + "slicesbackward", + "slicesclip", "slicescontains", "slicessort", "stditerators", @@ -757,7 +764,7 @@ "stringsbuilder", "testingcontext", "unsafefuncs", - "waitgroup" + "waitgroupgo" ] }, "wsl-checks": { @@ -853,6 +860,7 @@ "errorlint", "exhaustive", "exhaustruct", + "exhaustruct_v5", "exptostd", "fatcontext", "forbidigo", @@ -989,6 +997,11 @@ "description": "Checks only comments, skip strings.", "type": "boolean", "default": false + }, + "skip-raw-strings": { + "description": "Skip raw string literals (backtick-delimited) from duplicate word checking.", + "type": "boolean", + "default": false } } }, @@ -1436,6 +1449,60 @@ } } }, + "exhaustructv5Settings": { + "type": "object", + "additionalProperties": false, + "properties": { + "enforce-patterns": { + "description": "List of regular expressions to match type names that should be checked.", + "type": "array", + "items": { + "type": "string" + } + }, + "ignore-patterns":{ + "description": "List of regular expressions to match type names that should be skipped from checking.", + "type": "array", + "items": { + "type": "string" + } + }, + "optional-patterns": { + "description": "List of regular expressions to match type names where all fields are treated as optional.", + "type": "array", + "items": { + "type": "string" + } + }, + "allow-empty":{ + "description": "Allows empty structures, effectively excluding them from the check.", + "type": "boolean", + "default": false + }, + "allow-empty-patterns": { + "description": "List of regular expressions to match type names that should be allowed to be empty.", + "type": "array", + "items": { + "type": "string" + } + }, + "allow-empty-returns": { + "description": "Allows empty structures in return statements.", + "type": "boolean", + "default": false + }, + "allow-empty-declarations": { + "description": "Allows empty structures in variable declarations.", + "type": "boolean", + "default": false + }, + "explicit-mode":{ + "description": "When true, only types marked with //exhaustruct:enforce directive or matching enforce-rx patterns are checked.", + "type": "boolean", + "default": false + } + } + }, "fatcontextSettings": { "type": "object", "additionalProperties": false, @@ -1444,6 +1511,16 @@ "description": "Check for potential fat contexts in struct pointers.", "type": "boolean", "default": false + }, + "check-loops": { + "description": "Disable detection of fat contexts in function literals.", + "type": "boolean", + "default": true + }, + "check-function-literals": { + "description": "Disable detection of fat contexts in function literals.", + "type": "boolean", + "default": true } } }, @@ -1702,6 +1779,19 @@ "type": "integer", "default": 3 }, + "exclude-types": { + "type": "array", + "items": { + "enum": [ + "Assignment", + "Binary", + "Case", + "Return", + "Call", + "CompositeLit" + ] + } + }, "ignore-calls": { "description": "Ignore when constant is not used as function argument", "type": "boolean", @@ -1749,6 +1839,10 @@ "items": { "type": "string" } + }, + "ignore-map-keys": { + "description": "Ignore string literals used as map keys", + "type": "boolean" } } }, @@ -2152,6 +2246,24 @@ "module-path": { "description": " Module path which contains the source code being formatted.", "type": "string" + }, + "extra": { + "type": "object", + "additionalProperties": false, + "properties": { + "group-params": { + "type": "boolean", + "default": false + }, + "clothe-returns": { + "type": "boolean", + "default": false + }, + "balance-calls": { + "type": "boolean", + "default": false + } + } } } }, @@ -2244,6 +2356,11 @@ "type": "string" } }, + "replace-allow-all": { + "description": "Allow all `replace` directives.", + "type": "boolean", + "default": false + }, "retract-allow-no-explanation": { "description": "Allow to not explain why the version has been retracted in the `retract` directives.", "type": "boolean", @@ -2611,6 +2728,18 @@ } } } + }, + "unusedmethod": { + "type": "object", + "additionalProperties": false, + "properties": { + "exclude": { + "type": "array", + "items": { + "type": "string" + } + } + } } } } @@ -3066,6 +3195,11 @@ "description": "Report named error if it is assigned inside defer.", "type": "boolean", "default": false + }, + "allow-unused-named-returns": { + "description": "Allow named returns in the signature but report them if referenced in the body or used by a naked return.", + "type": "boolean", + "default": false } } }, @@ -4984,6 +5118,9 @@ "exhaustruct": { "$ref": "#/definitions/settings/definitions/exhaustructSettings" }, + "exhaustruct_v5": { + "$ref": "#/definitions/settings/definitions/exhaustructv5Settings" + }, "fatcontext": { "$ref": "#/definitions/settings/definitions/fatcontextSettings" }, diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/jsonschema/golangci.next.jsonschema.json b/tools/vendor/github.com/golangci/golangci-lint/v2/jsonschema/golangci.next.jsonschema.json index 0ac9d577e..bb4f0398e 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/jsonschema/golangci.next.jsonschema.json +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/jsonschema/golangci.next.jsonschema.json @@ -716,7 +716,8 @@ "identical", "unused", "opaque", - "unexported" + "unexported", + "unusedmethod" ] }, "tagliatelle-cases": { @@ -739,15 +740,21 @@ "modernize-analyzers": { "enum": [ "any", - "fmtappendf", + "atomictypes", + "embedlit", + "errorsastype", "forvar", + "importcomment", "mapsloop", "minmax", "newexpr", "omitzero", "plusbuild", "rangeint", + "reflecttypeassert", "reflecttypefor", + "slicesbackward", + "slicesclip", "slicescontains", "slicessort", "stditerators", @@ -757,7 +764,7 @@ "stringsbuilder", "testingcontext", "unsafefuncs", - "waitgroup" + "waitgroupgo" ] }, "wsl-checks": { @@ -853,6 +860,7 @@ "errorlint", "exhaustive", "exhaustruct", + "exhaustruct_v5", "exptostd", "fatcontext", "forbidigo", @@ -989,6 +997,11 @@ "description": "Checks only comments, skip strings.", "type": "boolean", "default": false + }, + "skip-raw-strings": { + "description": "Skip raw string literals (backtick-delimited) from duplicate word checking.", + "type": "boolean", + "default": false } } }, @@ -1436,6 +1449,60 @@ } } }, + "exhaustructv5Settings": { + "type": "object", + "additionalProperties": false, + "properties": { + "enforce-patterns": { + "description": "List of regular expressions to match type names that should be checked.", + "type": "array", + "items": { + "type": "string" + } + }, + "ignore-patterns":{ + "description": "List of regular expressions to match type names that should be skipped from checking.", + "type": "array", + "items": { + "type": "string" + } + }, + "optional-patterns": { + "description": "List of regular expressions to match type names where all fields are treated as optional.", + "type": "array", + "items": { + "type": "string" + } + }, + "allow-empty":{ + "description": "Allows empty structures, effectively excluding them from the check.", + "type": "boolean", + "default": false + }, + "allow-empty-patterns": { + "description": "List of regular expressions to match type names that should be allowed to be empty.", + "type": "array", + "items": { + "type": "string" + } + }, + "allow-empty-returns": { + "description": "Allows empty structures in return statements.", + "type": "boolean", + "default": false + }, + "allow-empty-declarations": { + "description": "Allows empty structures in variable declarations.", + "type": "boolean", + "default": false + }, + "explicit-mode":{ + "description": "When true, only types marked with //exhaustruct:enforce directive or matching enforce-rx patterns are checked.", + "type": "boolean", + "default": false + } + } + }, "fatcontextSettings": { "type": "object", "additionalProperties": false, @@ -1444,6 +1511,16 @@ "description": "Check for potential fat contexts in struct pointers.", "type": "boolean", "default": false + }, + "check-loops": { + "description": "Disable detection of fat contexts in function literals.", + "type": "boolean", + "default": true + }, + "check-function-literals": { + "description": "Disable detection of fat contexts in function literals.", + "type": "boolean", + "default": true } } }, @@ -1702,6 +1779,19 @@ "type": "integer", "default": 3 }, + "exclude-types": { + "type": "array", + "items": { + "enum": [ + "Assignment", + "Binary", + "Case", + "Return", + "Call", + "CompositeLit" + ] + } + }, "ignore-calls": { "description": "Ignore when constant is not used as function argument", "type": "boolean", @@ -1749,6 +1839,10 @@ "items": { "type": "string" } + }, + "ignore-map-keys": { + "description": "Ignore string literals used as map keys", + "type": "boolean" } } }, @@ -2152,6 +2246,24 @@ "module-path": { "description": " Module path which contains the source code being formatted.", "type": "string" + }, + "extra": { + "type": "object", + "additionalProperties": false, + "properties": { + "group-params": { + "type": "boolean", + "default": false + }, + "clothe-returns": { + "type": "boolean", + "default": false + }, + "balance-calls": { + "type": "boolean", + "default": false + } + } } } }, @@ -2244,6 +2356,11 @@ "type": "string" } }, + "replace-allow-all": { + "description": "Allow all `replace` directives.", + "type": "boolean", + "default": false + }, "retract-allow-no-explanation": { "description": "Allow to not explain why the version has been retracted in the `retract` directives.", "type": "boolean", @@ -2611,6 +2728,18 @@ } } } + }, + "unusedmethod": { + "type": "object", + "additionalProperties": false, + "properties": { + "exclude": { + "type": "array", + "items": { + "type": "string" + } + } + } } } } @@ -3066,6 +3195,11 @@ "description": "Report named error if it is assigned inside defer.", "type": "boolean", "default": false + }, + "allow-unused-named-returns": { + "description": "Allow named returns in the signature but report them if referenced in the body or used by a naked return.", + "type": "boolean", + "default": false } } }, @@ -4984,6 +5118,9 @@ "exhaustruct": { "$ref": "#/definitions/settings/definitions/exhaustructSettings" }, + "exhaustruct_v5": { + "$ref": "#/definitions/settings/definitions/exhaustructv5Settings" + }, "fatcontext": { "$ref": "#/definitions/settings/definitions/fatcontextSettings" }, diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/jsonschema/golangci.v2.12.jsonschema.json b/tools/vendor/github.com/golangci/golangci-lint/v2/jsonschema/golangci.v2.12.jsonschema.json new file mode 100644 index 000000000..0ac9d577e --- /dev/null +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/jsonschema/golangci.v2.12.jsonschema.json @@ -0,0 +1,5438 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/golangci-lint.json", + "definitions": { + "gocritic-checks": { + "enum": [ + "appendAssign", + "appendCombine", + "argOrder", + "assignOp", + "badCall", + "badCond", + "badLock", + "badRegexp", + "badSorting", + "badSyncOnceFunc", + "boolExprSimplify", + "builtinShadow", + "builtinShadowDecl", + "captLocal", + "caseOrder", + "codegenComment", + "commentFormatting", + "commentedOutCode", + "commentedOutImport", + "defaultCaseOrder", + "deferInLoop", + "deferUnlambda", + "deprecatedComment", + "docStub", + "dupArg", + "dupBranchBody", + "dupCase", + "dupImport", + "dupOption", + "dupSubExpr", + "dynamicFmtString", + "elseif", + "emptyDecl", + "emptyFallthrough", + "emptyStringTest", + "equalFold", + "evalOrder", + "exitAfterDefer", + "exposedSyncMutex", + "externalErrorReassign", + "filepathJoin", + "flagDeref", + "flagName", + "hexLiteral", + "httpNoBody", + "hugeParam", + "ifElseChain", + "importShadow", + "indexAlloc", + "initClause", + "mapKey", + "methodExprCall", + "nestingReduce", + "newDeref", + "nilValReturn", + "octalLiteral", + "offBy1", + "paramTypeCombine", + "preferDecodeRune", + "preferFilepathJoin", + "preferFprint", + "preferStringWriter", + "preferWriteByte", + "ptrToRefParam", + "rangeAppendAll", + "rangeExprCopy", + "rangeValCopy", + "redundantSprint", + "regexpMust", + "regexpPattern", + "regexpSimplify", + "returnAfterHttpError", + "ruleguard", + "singleCaseSwitch", + "sliceClear", + "sloppyLen", + "sloppyReassign", + "sloppyTypeAssert", + "sortSlice", + "sprintfQuotedString", + "sqlQuery", + "stringConcatSimplify", + "stringXbytes", + "stringsCompare", + "switchTrue", + "syncMapLoadAndDelete", + "timeExprSimplify", + "todoCommentWithoutDetail", + "tooManyResultsChecker", + "truncateCmp", + "typeAssertChain", + "typeDefFirst", + "typeSwitchVar", + "typeUnparen", + "uncheckedInlineErr", + "underef", + "unlabelStmt", + "unlambda", + "unnamedResult", + "unnecessaryBlock", + "unnecessaryDefer", + "unslice", + "valSwap", + "weakCond", + "whyNoLint", + "wrapperFunc", + "yodaStyleExpr", + "zeroByteRepeat" + ] + }, + "gocritic-tags": { + "enum": [ + "diagnostic", + "style", + "performance", + "experimental", + "opinionated", + "security" + ] + }, + "staticcheck-checks": { + "enum": [ + "*", + "all", + "SA*", + "-SA*", + "SA1*", + "-SA1*", + "SA1000", + "-SA1000", + "SA1001", + "-SA1001", + "SA1002", + "-SA1002", + "SA1003", + "-SA1003", + "SA1004", + "-SA1004", + "SA1005", + "-SA1005", + "SA1006", + "-SA1006", + "SA1007", + "-SA1007", + "SA1008", + "-SA1008", + "SA1010", + "-SA1010", + "SA1011", + "-SA1011", + "SA1012", + "-SA1012", + "SA1013", + "-SA1013", + "SA1014", + "-SA1014", + "SA1015", + "-SA1015", + "SA1016", + "-SA1016", + "SA1017", + "-SA1017", + "SA1018", + "-SA1018", + "SA1019", + "-SA1019", + "SA1020", + "-SA1020", + "SA1021", + "-SA1021", + "SA1023", + "-SA1023", + "SA1024", + "-SA1024", + "SA1025", + "-SA1025", + "SA1026", + "-SA1026", + "SA1027", + "-SA1027", + "SA1028", + "-SA1028", + "SA1029", + "-SA1029", + "SA1030", + "-SA1030", + "SA1031", + "-SA1031", + "SA1032", + "-SA1032", + "SA2*", + "-SA2*", + "SA2000", + "-SA2000", + "SA2001", + "-SA2001", + "SA2002", + "-SA2002", + "SA2003", + "-SA2003", + "SA3*", + "-SA3*", + "SA3000", + "-SA3000", + "SA3001", + "-SA3001", + "SA4*", + "-SA4*", + "SA4000", + "-SA4000", + "SA4001", + "-SA4001", + "SA4003", + "-SA4003", + "SA4004", + "-SA4004", + "SA4005", + "-SA4005", + "SA4006", + "-SA4006", + "SA4008", + "-SA4008", + "SA4009", + "-SA4009", + "SA4010", + "-SA4010", + "SA4011", + "-SA4011", + "SA4012", + "-SA4012", + "SA4013", + "-SA4013", + "SA4014", + "-SA4014", + "SA4015", + "-SA4015", + "SA4016", + "-SA4016", + "SA4017", + "-SA4017", + "SA4018", + "-SA4018", + "SA4019", + "-SA4019", + "SA4020", + "-SA4020", + "SA4021", + "-SA4021", + "SA4022", + "-SA4022", + "SA4023", + "-SA4023", + "SA4024", + "-SA4024", + "SA4025", + "-SA4025", + "SA4026", + "-SA4026", + "SA4027", + "-SA4027", + "SA4028", + "-SA4028", + "SA4029", + "-SA4029", + "SA4030", + "-SA4030", + "SA4031", + "-SA4031", + "SA4032", + "-SA4032", + "SA5*", + "-SA5*", + "SA5000", + "-SA5000", + "SA5001", + "-SA5001", + "SA5002", + "-SA5002", + "SA5003", + "-SA5003", + "SA5004", + "-SA5004", + "SA5005", + "-SA5005", + "SA5007", + "-SA5007", + "SA5008", + "-SA5008", + "SA5009", + "-SA5009", + "SA5010", + "-SA5010", + "SA5011", + "-SA5011", + "SA5012", + "-SA5012", + "SA6*", + "-SA6*", + "SA6000", + "-SA6000", + "SA6001", + "-SA6001", + "SA6002", + "-SA6002", + "SA6003", + "-SA6003", + "SA6005", + "-SA6005", + "SA6006", + "-SA6006", + "SA9*", + "-SA9*", + "SA9001", + "-SA9001", + "SA9002", + "-SA9002", + "SA9003", + "-SA9003", + "SA9004", + "-SA9004", + "SA9005", + "-SA9005", + "SA9006", + "-SA9006", + "SA9007", + "-SA9007", + "SA9008", + "-SA9008", + "SA9009", + "-SA9009", + "ST*", + "-ST*", + "ST1*", + "-ST1*", + "ST1000", + "-ST1000", + "ST1001", + "-ST1001", + "ST1003", + "-ST1003", + "ST1005", + "-ST1005", + "ST1006", + "-ST1006", + "ST1008", + "-ST1008", + "ST1011", + "-ST1011", + "ST1012", + "-ST1012", + "ST1013", + "-ST1013", + "ST1015", + "-ST1015", + "ST1016", + "-ST1016", + "ST1017", + "-ST1017", + "ST1018", + "-ST1018", + "ST1019", + "-ST1019", + "ST1020", + "-ST1020", + "ST1021", + "-ST1021", + "ST1022", + "-ST1022", + "ST1023", + "-ST1023", + "S*", + "-S*", + "S1*", + "-S1*", + "S1000", + "-S1000", + "S1001", + "-S1001", + "S1002", + "-S1002", + "S1003", + "-S1003", + "S1004", + "-S1004", + "S1005", + "-S1005", + "S1006", + "-S1006", + "S1007", + "-S1007", + "S1008", + "-S1008", + "S1009", + "-S1009", + "S1010", + "-S1010", + "S1011", + "-S1011", + "S1012", + "-S1012", + "S1016", + "-S1016", + "S1017", + "-S1017", + "S1018", + "-S1018", + "S1019", + "-S1019", + "S1020", + "-S1020", + "S1021", + "-S1021", + "S1023", + "-S1023", + "S1024", + "-S1024", + "S1025", + "-S1025", + "S1028", + "-S1028", + "S1029", + "-S1029", + "S1030", + "-S1030", + "S1031", + "-S1031", + "S1032", + "-S1032", + "S1033", + "-S1033", + "S1034", + "-S1034", + "S1035", + "-S1035", + "S1036", + "-S1036", + "S1037", + "-S1037", + "S1038", + "-S1038", + "S1039", + "-S1039", + "S1040", + "-S1040", + "QF*", + "-QF*", + "QF1*", + "-QF1*", + "QF1001", + "-QF1001", + "QF1002", + "-QF1002", + "QF1003", + "-QF1003", + "QF1004", + "-QF1004", + "QF1005", + "-QF1005", + "QF1006", + "-QF1006", + "QF1007", + "-QF1007", + "QF1008", + "-QF1008", + "QF1009", + "-QF1009", + "QF1010", + "-QF1010", + "QF1011", + "-QF1011", + "QF1012", + "-QF1012" + ] + }, + "godoclint-rules": { + "enum": [ + "pkg-doc", + "single-pkg-doc", + "require-pkg-doc", + "start-with-name", + "require-doc", + "deprecated", + "max-len", + "no-unused-link", + "require-stdlib-doclink" + ] + }, + "gosec-rules": { + "enum": [ + "G101", + "G102", + "G103", + "G104", + "G106", + "G107", + "G108", + "G109", + "G110", + "G111", + "G112", + "G113", + "G114", + "G115", + "G116", + "G117", + "G118", + "G119", + "G120", + "G121", + "G122", + "G123", + "G124", + "G201", + "G202", + "G203", + "G204", + "G301", + "G302", + "G303", + "G304", + "G305", + "G306", + "G307", + "G401", + "G402", + "G403", + "G404", + "G405", + "G406", + "G408", + "G501", + "G502", + "G503", + "G504", + "G505", + "G506", + "G507", + "G601", + "G602", + "G701", + "G702", + "G703", + "G704", + "G705", + "G706", + "G707", + "G708", + "G709", + "G710" + ] + }, + "govet-analyzers": { + "enum": [ + "appends", + "asmdecl", + "assign", + "atomic", + "atomicalign", + "bools", + "buildtag", + "cgocall", + "composites", + "copylocks", + "deepequalerrors", + "defers", + "directive", + "errorsas", + "fieldalignment", + "findcall", + "framepointer", + "hostport", + "httpmux", + "httpresponse", + "ifaceassert", + "inline", + "loopclosure", + "lostcancel", + "nilfunc", + "nilness", + "printf", + "reflectvaluecompare", + "shadow", + "shift", + "sigchanyzer", + "slog", + "sortslice", + "stdmethods", + "stdversion", + "stringintconv", + "structtag", + "testinggoroutine", + "tests", + "timeformat", + "unmarshal", + "unreachable", + "unsafeptr", + "unusedresult", + "unusedwrite", + "waitgroup" + ] + }, + "revive-rules": { + "enum": [ + "add-constant", + "argument-limit", + "atomic", + "banned-characters", + "bare-return", + "blank-imports", + "bool-literal-in-expr", + "call-to-gc", + "cognitive-complexity", + "comment-spacings", + "comments-density", + "confusing-naming", + "confusing-results", + "constant-logical-expr", + "context-as-argument", + "context-keys-type", + "cyclomatic", + "datarace", + "deep-exit", + "defer", + "dot-imports", + "duplicated-imports", + "early-return", + "empty-block", + "empty-lines", + "enforce-map-style", + "enforce-repeated-arg-type-style", + "enforce-slice-style", + "enforce-switch-style", + "epoch-naming", + "error-naming", + "error-return", + "error-strings", + "errorf", + "exported", + "file-header", + "file-length-limit", + "filename-format", + "flag-parameter", + "forbidden-call-in-wg-go", + "function-length", + "function-result-limit", + "get-return", + "identical-branches", + "identical-ifelseif-branches", + "identical-ifelseif-conditions", + "identical-switch-branches", + "identical-switch-conditions", + "if-return", + "import-alias-naming", + "import-shadowing", + "imports-blocklist", + "increment-decrement", + "indent-error-flow", + "inefficient-map-lookup", + "line-length-limit", + "max-control-nesting", + "max-public-structs", + "modifies-parameter", + "modifies-value-receiver", + "nested-structs", + "optimize-operands-order", + "package-comments", + "package-naming", + "package-directory-mismatch", + "range-val-address", + "range-val-in-closure", + "range", + "receiver-naming", + "redefines-builtin-id", + "redundant-build-tag", + "redundant-import-alias", + "redundant-test-main-exit", + "string-format", + "string-of-int", + "struct-tag", + "superfluous-else", + "time-date", + "time-equal", + "time-naming", + "unchecked-type-assertion", + "unconditional-recursion", + "unexported-naming", + "unexported-return", + "unhandled-error", + "unnecessary-format", + "unnecessary-if", + "unnecessary-stmt", + "unreachable-code", + "unsecure-url-scheme", + "unused-parameter", + "unused-receiver", + "use-any", + "use-errors-new", + "use-fmt-print", + "use-slices-sort", + "use-waitgroup-go", + "useless-break", + "useless-fallthrough", + "var-declaration", + "var-naming", + "waitgroup-by-value" + ] + }, + "iface-analyzers": { + "enum": [ + "identical", + "unused", + "opaque", + "unexported" + ] + }, + "tagliatelle-cases": { + "enum": [ + "", + "camel", + "pascal", + "kebab", + "snake", + "goCamel", + "goPascal", + "goKebab", + "goSnake", + "upper", + "upperSnake", + "lower", + "header" + ] + }, + "modernize-analyzers": { + "enum": [ + "any", + "fmtappendf", + "forvar", + "mapsloop", + "minmax", + "newexpr", + "omitzero", + "plusbuild", + "rangeint", + "reflecttypefor", + "slicescontains", + "slicessort", + "stditerators", + "stringscut", + "stringscutprefix", + "stringsseq", + "stringsbuilder", + "testingcontext", + "unsafefuncs", + "waitgroup" + ] + }, + "wsl-checks": { + "enum": [ + "after-block", + "after-decl", + "after-defer", + "after-expr", + "after-go", + "append", + "assign-exclusive", + "assign-expr", + "assign", + "branch", + "cuddle-group", + "decl", + "defer", + "err", + "expr", + "for", + "go", + "if", + "inc-dec", + "label", + "leading-whitespace", + "range", + "return", + "select", + "send", + "switch", + "trailing-whitespace", + "type-switch" + ] + }, + "relative-path-modes": { + "enum": [ + "gomod", + "gitroot", + "cfg", + "wd" + ] + }, + "simple-format": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "$ref": "#/definitions/formats-path", + "default": "stdout" + } + } + }, + "formats-path" : { + "anyOf": [ + { + "enum": [ + "stdout", + "stderr" + ] + }, + { + "type": "string" + } + ] + }, + "linter-names": { + "$comment": "anyOf with enum is used to allow auto-completion of non-custom linters", + "description": "Usable linter names.", + "anyOf": [ + { + "enum": [ + "arangolint", + "asasalint", + "asciicheck", + "bidichk", + "bodyclose", + "canonicalheader", + "clickhouselint", + "containedctx", + "contextcheck", + "copyloopvar", + "cyclop", + "decorder", + "depguard", + "dogsled", + "dupl", + "dupword", + "durationcheck", + "embeddedstructfieldcheck", + "errcheck", + "errchkjson", + "errname", + "errorlint", + "exhaustive", + "exhaustruct", + "exptostd", + "fatcontext", + "forbidigo", + "forcetypeassert", + "funcorder", + "funlen", + "ginkgolinter", + "gocheckcompilerdirectives", + "gochecknoglobals", + "gochecknoinits", + "gochecksumtype", + "gocognit", + "goconst", + "gocritic", + "gocyclo", + "godoclint", + "godot", + "godox", + "err113", + "goheader", + "gomoddirectives", + "gomodguard", + "gomodguard_v2", + "goprintffuncname", + "gosec", + "gosimple", + "gosmopolitan", + "govet", + "grouper", + "iface", + "importas", + "inamedparam", + "ineffassign", + "interfacebloat", + "intrange", + "iotamixing", + "ireturn", + "lll", + "loggercheck", + "maintidx", + "makezero", + "mirror", + "misspell", + "mnd", + "modernize", + "musttag", + "nakedret", + "nestif", + "nilerr", + "nilnesserr", + "nilnil", + "nlreturn", + "noctx", + "noinlineerr", + "nolintlint", + "nonamedreturns", + "nosprintfhostport", + "paralleltest", + "perfsprint", + "prealloc", + "predeclared", + "promlinter", + "protogetter", + "reassign", + "recvcheck", + "revive", + "rowserrcheck", + "sloglint", + "sqlclosecheck", + "staticcheck", + "stylecheck", + "tagalign", + "tagliatelle", + "testableexamples", + "testifylint", + "testpackage", + "thelper", + "tparallel", + "unconvert", + "unparam", + "unused", + "usestdlibvars", + "usetesting", + "varnamelen", + "wastedassign", + "whitespace", + "wrapcheck", + "wsl", + "wsl_v5", + "zerologlint" + ] + }, + { + "type": "string" + } + ] + }, + "formatter-names": { + "description": "Usable formatter names.", + "enum": [ + "gci", + "gofmt", + "gofumpt", + "goimports", + "golines", + "swaggo" + ] + }, + "settings": { + "definitions": { + "dupwordSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "keywords": { + "description": "Keywords for detecting duplicate words. If this list is not empty, only the words defined in this list will be detected.", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "examples": ["the", "and", "a"] + } + }, + "ignore": { + "description": "Keywords used to ignore detection.", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "examples": ["0C0C"] + } + }, + "comments-only": { + "description": "Checks only comments, skip strings.", + "type": "boolean", + "default": false + } + } + }, + "asasalintSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "exclude": { + "description": "To specify a set of function names to exclude.", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "examples": ["\\.Wrapf"] + } + }, + "use-builtin-exclusions": { + "description": "To enable/disable the asasalint builtin exclusions of function names.", + "type": "boolean", + "default": true + } + } + }, + "bidichkSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "left-to-right-embedding": { + "description": "Disallow: LEFT-TO-RIGHT-EMBEDDING", + "type": "boolean", + "default": false + }, + "right-to-left-embedding": { + "description": "Disallow: RIGHT-TO-LEFT-EMBEDDING", + "type": "boolean", + "default": false + }, + "pop-directional-formatting": { + "description": "Disallow: POP-DIRECTIONAL-FORMATTING", + "type": "boolean", + "default": false + }, + "left-to-right-override": { + "description": "Disallow: LEFT-TO-RIGHT-OVERRIDE", + "type": "boolean", + "default": false + }, + "right-to-left-override": { + "description": "Disallow: RIGHT-TO-LEFT-OVERRIDE", + "type": "boolean", + "default": false + }, + "left-to-right-isolate": { + "description": "Disallow: LEFT-TO-RIGHT-ISOLATE", + "type": "boolean", + "default": false + }, + "right-to-left-isolate": { + "description": "Disallow: RIGHT-TO-LEFT-ISOLATE", + "type": "boolean", + "default": false + }, + "first-strong-isolate": { + "description": "Disallow: FIRST-STRONG-ISOLATE", + "type": "boolean", + "default": false + }, + "pop-directional-isolate": { + "description": "Disallow: POP-DIRECTIONAL-ISOLATE", + "type": "boolean", + "default": false + } + } + }, + "bodycloseSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "check-consumption": { + "description": "Check that the response body is consumed.", + "type": "boolean", + "default": false + } + } + }, + "cyclopSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "max-complexity": { + "description": "Max complexity the function can have", + "type": "integer", + "default": 10, + "minimum": 0 + }, + "package-average": { + "description": "Max average complexity in package", + "type": "number", + "default": 0, + "minimum": 0 + } + } + }, + "decorderSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "dec-order": { + "type": "array", + "default": [["type", "const", "var", "func"]], + "items": { + "enum": ["type", "const", "var", "func"] + } + }, + "ignore-underscore-vars": { + "description": "Underscore vars (vars with \"_\" as the name) will be ignored at all checks", + "default": true, + "type": "boolean" + }, + "disable-dec-order-check": { + "description": "Order of declarations is not checked", + "default": true, + "type": "boolean" + }, + "disable-init-func-first-check": { + "description": "Allow init func to be anywhere in file", + "default": true, + "type": "boolean" + }, + "disable-dec-num-check": { + "description": "Multiple global type, const and var declarations are allowed", + "default": true, + "type": "boolean" + }, + "disable-type-dec-num-check": { + "description": "Type declarations will be ignored for dec num check", + "default": true, + "type": "boolean" + }, + "disable-const-dec-num-check": { + "description": "Const declarations will be ignored for dec num check", + "default": true, + "type": "boolean" + }, + "disable-var-dec-num-check": { + "description": "Var declarations will be ignored for dec num check", + "default": true, + "type": "boolean" + } + } + }, + "depguardSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "rules": { + "description": "Rules to apply.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^[^.]+$": { + "description": "Name of a rule.", + "type": "object", + "additionalProperties": false, + "properties": { + "list-mode": { + "description": "Used to determine the package matching priority.", + "enum": ["original", "strict", "lax"], + "default": "original" + }, + "files": { + "description": "List of file globs that will match this list of settings to compare against.", + "additionalProperties": false, + "type": "array", + "items": { + "type": "string" + } + }, + "allow": { + "description": "List of allowed packages.", + "additionalProperties": false, + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Packages that are not allowed where the value is a suggestion.", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "desc": { + "description": "Description", + "type": "string" + }, + "pkg": { + "description": "Package", + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "dogsledSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "max-blank-identifiers": { + "description": "Check assignments with too many blank identifiers.", + "type": "integer", + "default": 2, + "minimum": 0 + } + } + }, + "duplSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "threshold": { + "description": "Tokens count to trigger issue.", + "type": "integer", + "default": 150, + "minimum": 0 + } + } + }, + "embeddedstructfieldcheckSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "empty-line": { + "description": "Checks that there is an empty space between the embedded fields and regular fields.", + "type": "boolean", + "default": false + }, + "forbid-mutex": { + "description": "Checks that sync.Mutex and sync.RWMutex are not used as embedded fields.", + "type": "boolean", + "default": false + } + } + }, + "errcheckSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "check-type-assertions": { + "description": "Report about not checking errors in type assertions, i.e.: `a := b.(MyStruct)`", + "type": "boolean", + "default": false + }, + "check-blank": { + "description": "Report about assignment of errors to blank identifier", + "type": "boolean", + "default": false + }, + "exclude-functions": { + "description": "List of functions to exclude from checking, where each entry is a single function to exclude", + "type": "array", + "examples": ["io/ioutil.ReadFile", "io.Copy(*bytes.Buffer)"], + "items": { + "type": "string" + } + }, + "disable-default-exclusions": { + "description": "To disable the errcheck built-in exclude list", + "type": "boolean", + "default": false + }, + "verbose": { + "description": "Display function signature instead of selector", + "type": "boolean", + "default": false + } + } + }, + "errchkjsonSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "check-error-free-encoding": { + "type": "boolean", + "default": false + }, + "report-no-exported": { + "description": "Issue on struct that doesn't have exported fields.", + "type": "boolean", + "default": false + } + } + }, + "errorlintSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "errorf": { + "description": "Check whether fmt.Errorf uses the %w verb for formatting errors", + "type": "boolean", + "default": true + }, + "errorf-multi": { + "description": "Permit more than 1 %w verb, valid per Go 1.20", + "type": "boolean", + "default": true + }, + "asserts": { + "description": "Check for plain type assertions and type switches.", + "type": "boolean", + "default": true + }, + "comparison": { + "description": "Check for plain error comparisons", + "type": "boolean", + "default": true + }, + "allowed-errors": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "err": { + "type": "string" + }, + "fun": { + "type": "string" + } + } + } + }, + "allowed-errors-wildcard": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "err": { + "type": "string" + }, + "fun": { + "type": "string" + } + } + } + } + } + }, + "exhaustiveSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "check": { + "description": "Program elements to check for exhaustiveness.", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "examples": ["switch", "map"] + } + }, + "explicit-exhaustive-switch": { + "description": "Only run exhaustive check on switches with \"//exhaustive:enforce\" comment.", + "type": "boolean", + "default": false + }, + "explicit-exhaustive-map": { + "description": "Only run exhaustive check on map literals with \"//exhaustive:enforce\" comment.", + "type": "boolean", + "default": false + }, + "default-case-required": { + "description": "Switch statement requires default case even if exhaustive.", + "type": "boolean", + "default": false + }, + "default-signifies-exhaustive": { + "description": "Presence of `default` case in switch statements satisfies exhaustiveness, even if all enum members are not listed.", + "type": "boolean", + "default": false + }, + "ignore-enum-members": { + "description": "Enum members matching `regex` do not have to be listed in switch statements to satisfy exhaustiveness", + "type": "string" + }, + "ignore-enum-types": { + "description": "Enum types matching the supplied regex do not have to be listed in switch statements to satisfy exhaustiveness.", + "type": "string" + }, + "package-scope-only": { + "description": "Consider enums only in package scopes, not in inner scopes.", + "type": "boolean", + "default": false + } + } + }, + "exhaustructSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "include": { + "description": "List of regular expressions to match struct packages and names.", + "type": "array", + "examples": [".*\\.Test"], + "items": { + "type": "string" + } + }, + "exclude": { + "description": "List of regular expressions to exclude struct packages and names from check.", + "type": "array", + "examples": ["cobra\\.Command$"], + "items": { + "type": "string" + } + }, + "allow-empty": { + "description": "Allows empty structures, effectively excluding them from the check.", + "type": "boolean", + "default": false + }, + "allow-empty-rx": { + "description": "List of regular expressions to match type names that should be allowed to be empty.", + "type": "array", + "items": { + "type": "string" + } + }, + "allow-empty-returns": { + "description": "Allows empty structures in return statements.", + "type": "boolean", + "default": false + }, + "allow-empty-declarations": { + "description": "Allows empty structures in variable declarations.", + "type": "boolean", + "default": false + } + } + }, + "fatcontextSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "check-struct-pointers": { + "description": "Check for potential fat contexts in struct pointers.", + "type": "boolean", + "default": false + } + } + }, + "forbidigoSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "exclude-godoc-examples": { + "description": "Exclude code in godoc examples.", + "type": "boolean", + "default": true + }, + "analyze-types": { + "description": "Instead of matching the literal source code, use type information to replace expressions with strings that contain the package name and (for methods and fields) the type name.", + "type": "boolean", + "default": true + }, + "forbid": { + "description": "List of identifiers to forbid (written using `regexp`)", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "pattern": { + "description": "Pattern", + "type": "string" + }, + "pkg": { + "description": "Package", + "type": "string" + }, + "msg": { + "description": "Message", + "type": "string" + } + } + } + } + } + }, + "funcorderSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "constructor": { + "description": "Checks that constructors are placed after the structure declaration.", + "type": "boolean", + "default": true + }, + "struct-method": { + "description": "Checks if the exported methods of a structure are placed before the non-exported ones.", + "type": "boolean", + "default": true + }, + "alphabetical": { + "description": "Checks if the constructors and/or structure methods are sorted alphabetically.", + "type": "boolean", + "default": false + }, + "function": { + "description": "Checks that exported functions are placed before unexported functions.", + "type": "boolean", + "default": false + } + } + }, + "funlenSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "lines": { + "description": "Limit lines number per function.", + "type": "integer", + "default": 60 + }, + "statements": { + "description": "Limit statements number per function.", + "type": "integer", + "default": 40 + }, + "ignore-comments": { + "description": "Ignore comments when counting lines.", + "type": "boolean", + "default": true + } + } + }, + "gciSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "sections": { + "description": "Section configuration to compare against.", + "type": "array", + "items": { + "anyOf": [ + { + "enum": [ + "standard", + "default", + "blank", + "dot", + "alias", + "localmodule" + ] + }, + { + "type": "string" + } + ] + }, + "default": ["standard", "default"] + }, + "no-inline-comments": { + "description": "Checks that no inline Comments are present.", + "type": "boolean", + "default": false + }, + "no-prefix-comments": { + "description": "Checks that no prefix Comments(comment lines above an import) are present.", + "type": "boolean", + "default": false + }, + "custom-order": { + "description": "Enable custom order of sections.", + "type": "boolean", + "default": false + }, + "no-lex-order": { + "description": "Drops lexical ordering for custom sections.", + "type": "boolean", + "default": false + } + } + }, + "ginkgolinterSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "suppress-len-assertion": { + "description": "Suppress the wrong length assertion warning.", + "type": "boolean", + "default": false + }, + "suppress-nil-assertion": { + "description": "Suppress the wrong nil assertion warning.", + "type": "boolean", + "default": false + }, + "suppress-err-assertion": { + "description": "Suppress the wrong error assertion warning.", + "type": "boolean", + "default": false + }, + "suppress-compare-assertion": { + "description": "Suppress the wrong comparison assertion warning.", + "type": "boolean", + "default": false + }, + "suppress-async-assertion": { + "description": "Suppress the function all in async assertion warning.", + "type": "boolean", + "default": false + }, + "suppress-type-compare-assertion": { + "description": "Suppress warning for comparing values from different types, like int32 and uint32.", + "type": "boolean", + "default": false + }, + "forbid-focus-container": { + "description": "Trigger warning for ginkgo focus containers like FDescribe, FContext, FWhen or FIt.", + "type": "boolean", + "default": false + }, + "allow-havelen-zero": { + "description": "Don't trigger warnings for HaveLen(0).", + "type": "boolean", + "default": false + }, + "force-expect-to": { + "description": "Force using `Expect` with `To`, `ToNot` or `NotTo`", + "type": "boolean", + "default": false + }, + "validate-async-intervals": { + "description": "Best effort validation of async intervals (timeout and polling).", + "type": "boolean", + "default": false + }, + "forbid-spec-pollution": { + "description": "Trigger a warning for variable assignments in ginkgo containers like `Describe`, `Context` and `When`, instead of in `BeforeEach()`.", + "type": "boolean", + "default": false + }, + "force-succeed": { + "description": "Force using the Succeed matcher for error functions, and the HaveOccurred matcher for non-function error values.", + "type": "boolean", + "default": false + }, + "force-assertion-description": { + "description": "Force adding assertion descriptions to gomega matchers.", + "type": "boolean", + "default": false + }, + "force-tonot": { + "description": "Force using `ToNot`, `ShouldNot` instead of `To(Not())`.", + "type": "boolean", + "default": false + } + } + }, + "gochecksumtypeSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "default-signifies-exhaustive": { + "description": "Presence of `default` case in switch statements satisfies exhaustiveness, if all members are not listed.", + "type": "boolean", + "default": true + }, + "include-shared-interfaces": { + "description": "Include shared interfaces in the exhaustiviness check.", + "type": "boolean", + "default": false + } + } + }, + "gocognitSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "min-complexity": { + "description": "Minimal code complexity to report (we recommend 10-20).", + "type": "integer", + "default": 30 + } + } + }, + "goconstSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "match-constant": { + "description": "Look for existing constants matching the values", + "type": "boolean", + "default": true + }, + "min-len": { + "description": "Minimum length of string constant.", + "type": "integer", + "default": 3 + }, + "min-occurrences": { + "description": "Minimum occurrences count to trigger.", + "type": "integer", + "default": 3 + }, + "ignore-calls": { + "description": "Ignore when constant is not used as function argument", + "type": "boolean", + "default": true + }, + "ignore-string-values": { + "description": "Exclude strings matching the given regular expression", + "type": "array", + "items": { + "type": "string" + } + }, + "numbers": { + "description": "Search also for duplicated numbers.", + "type": "boolean", + "default": false + }, + "min": { + "description": "Minimum value, only works with `numbers`", + "type": "integer", + "default": 3 + }, + "max": { + "description": "Maximum value, only works with `numbers`", + "type": "integer", + "default": 3 + }, + "find-duplicates": { + "description": "Detects constants with identical values", + "type": "boolean", + "default": false + }, + "eval-const-expressions": { + "description": "Evaluates of constant expressions like Prefix + \"suffix\"", + "type": "boolean", + "default": false + }, + "ignore-tests": { + "description": "Ignore strings from test files", + "type": "boolean", + "default": false + }, + "ignore-functions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "gocriticSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled-checks": { + "description": "Which checks should be enabled. By default, a list of stable checks is used. To see it, run `GL_DEBUG=gocritic golangci-lint run`.", + "type": "array", + "items": { + "$ref": "#/definitions/gocritic-checks" + } + }, + "disabled-checks": { + "description": "Which checks should be disabled.", + "type": "array", + "items": { + "$ref": "#/definitions/gocritic-checks" + }, + "default": [] + }, + "enabled-tags": { + "description": "Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.", + "type": "array", + "items": { + "$ref": "#/definitions/gocritic-tags" + } + }, + "disabled-tags": { + "description": "Disable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.", + "type": "array", + "items": { + "$ref": "#/definitions/gocritic-tags" + } + }, + "settings": { + "description": "Settings passed to gocritic. Properties must be valid and enabled check names.", + "type": "object", + "additionalProperties": false, + "properties": { + "captLocal": { + "type": "object", + "additionalProperties": false, + "properties": { + "paramsOnly" : { + "type": "boolean", + "default": true + } + } + }, + "commentedOutCode": { + "type": "object", + "additionalProperties": false, + "properties": { + "minLength" : { + "type": "number", + "default": 15 + } + } + }, + "elseif": { + "type": "object", + "additionalProperties": false, + "properties": { + "skipBalanced" : { + "type": "boolean", + "default": true + } + } + }, + "hugeParam": { + "type": "object", + "additionalProperties": false, + "properties": { + "sizeThreshold" : { + "type": "number", + "default": 80 + } + } + }, + "ifElseChain": { + "type": "object", + "additionalProperties": false, + "properties": { + "minThreshold" : { + "type": "number", + "default": 2 + } + } + }, + "nestingReduce": { + "type": "object", + "additionalProperties": false, + "properties": { + "bodyWidth" : { + "type": "number", + "default": 5 + } + } + }, + "rangeExprCopy": { + "type": "object", + "additionalProperties": false, + "properties": { + "sizeThreshold" : { + "type": "number", + "default": 512 + }, + "skipTestFuncs" : { + "type": "boolean", + "default": true + } + } + }, + "rangeValCopy": { + "type": "object", + "additionalProperties": false, + "properties": { + "sizeThreshold" : { + "type": "number", + "default": 128 + }, + "skipTestFuncs" : { + "type": "boolean", + "default": true + } + } + }, + "ruleguard": { + "type": "object", + "additionalProperties": false, + "properties": { + "debug" : { + "type": "string" + }, + "enable" : { + "type": "string" + }, + "disable" : { + "type": "string" + }, + "failOn" : { + "type": "string" + }, + "rules" : { + "type": "string" + } + } + }, + "tooManyResultsChecker": { + "type": "object", + "additionalProperties": false, + "properties": { + "maxResults" : { + "type": "number", + "default": 5 + } + } + }, + "truncateCmp": { + "type": "object", + "additionalProperties": false, + "properties": { + "skipArchDependent" : { + "type": "boolean", + "default": true + } + } + }, + "underef": { + "type": "object", + "additionalProperties": false, + "properties": { + "skipRecvDeref" : { + "type": "boolean", + "default": true + } + } + }, + "unnamedResult": { + "type": "object", + "additionalProperties": false, + "properties": { + "checkExported" : { + "type": "boolean", + "default": false + } + } + } + } + }, + "disable-all": { + "type": "boolean", + "default": false + }, + "enable-all": { + "type": "boolean", + "default": false + } + } + }, + "gocycloSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "min-complexity": { + "description": "Minimum code complexity to report (we recommend 10-20).", + "type": "integer", + "default": 30 + } + } + }, + "godoclintSettings": { + "type": "object", + "properties": { + "default": { + "type": "string", + "enum": ["all", "basic", "none"], + "default": "basic", + "description": "Default set of rules to enable." + }, + "enable": { + "description": "List of rules to enable in addition to the default set.", + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/godoclint-rules" + } + }, + "disable": { + "description": "List of rules to disable.", + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/godoclint-rules" + } + }, + "options": { + "type": "object", + "description": "A map for setting individual rule options.", + "properties": { + "max-len": { + "type": "object", + "properties": { + "length": { + "type": "integer", + "description": "Maximum line length for godocs, not including the `//`, `/*` or `*/` tokens.", + "default": 77 + } + } + }, + "require-doc": { + "type": "object", + "properties": { + "ignore-exported": { + "type": "boolean", + "description": "Ignore exported (public) symbols when applying the `require-doc` rule.", + "default": false + }, + "ignore-unexported": { + "type": "boolean", + "description": "Ignore unexported (private) symbols when applying the `require-doc` rule.", + "default": true + } + } + }, + "start-with-name": { + "type": "object", + "properties": { + "include-unexported": { + "type": "boolean", + "description": "Include unexported symbols when applying the `start-with-name` rule.", + "default": false + } + } + } + } + } + } + }, + "godotSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "scope": { + "description": "Comments to be checked.", + "enum": ["declarations", "toplevel", "all", "noinline"], + "default": "declarations" + }, + "exclude": { + "description": "List of regexps for excluding particular comment lines from check.", + "type": "array", + "items": { + "type": "string" + } + }, + "period": { + "description": "Check that each sentence ends with a period.", + "type": "boolean", + "default": true + }, + "capital": { + "description": "Check that each sentence starts with a capital letter.", + "type": "boolean", + "default": false + }, + "check-all": { + "description": "DEPRECATED: Check all top-level comments, not only declarations.", + "type": "boolean", + "default": false + } + } + }, + "godoxSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "keywords": { + "description": "Report any comments starting with one of these keywords. This is useful for TODO or FIXME comments that might be left in the code accidentally and should be resolved before merging.", + "type": "array", + "items": { + "type": "string" + }, + "default": ["TODO", "BUG", "FIXME"] + } + } + }, + "gofmtSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "simplify": { + "description": "Simplify code.", + "type": "boolean", + "default": true + }, + "rewrite-rules": { + "description": "Apply the rewrite rules to the source before reformatting.", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "pattern": { + "type": "string" + }, + "replacement": { + "type": "string" + } + } + } + } + } + }, + "golinesSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "max-len": { + "type": "integer", + "default": 100 + }, + "tab-len": { + "type": "integer", + "default": 4 + }, + "shorten-comments": { + "type": "boolean", + "default": false + }, + "reformat-tags": { + "type": "boolean", + "default": true + }, + "chain-split-dots": { + "type": "boolean", + "default": true + } + } + }, + "interfacebloatSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "max": { + "description": "The maximum number of methods allowed for an interface.", + "type": "integer" + } + } + }, + "gofumptSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "extra-rules": { + "description": "Choose whether or not to use the extra rules that are disabled by default.", + "type": "boolean", + "default": false + }, + "module-path": { + "description": " Module path which contains the source code being formatted.", + "type": "string" + } + } + }, + "goheaderSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "values": { + "type": "object", + "additionalProperties": false, + "properties": { + "const": { + "description": "Constants to use in the template.", + "type": "object", + "patternProperties": { + "^.+$": { + "description": "Value for the constant.", + "type": "string" + } + }, + "additionalProperties": false, + "examples": [ + { + "YEAR": "2030", + "COMPANY": "MY FUTURISTIC COMPANY" + } + ] + }, + "regexp": { + "description": "Regular expressions to use in your template.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^.+$": { + "type": "string" + } + }, + "examples": [ + { + "AUTHOR": ".*@mycompany\\.com" + } + ] + } + } + }, + "template": { + "description": "Template to put on top of every file.", + "type": "string", + "examples": [ + "{{ MY COMPANY }}\nSPDX-License-Identifier: Apache-2.0\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at:\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License." + ] + }, + "template-path": { + "description": "Path to the file containing the template source.", + "type": "string", + "examples": ["my_header_template.txt"] + } + }, + "oneOf": [ + { "required": ["template"] }, + { "required": ["template-path"] } + ] + }, + "goimportsSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "local-prefixes": { + "description": "Put imports beginning with prefix after 3rd-party packages. It is a list of prefixes.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "gomoddirectivesSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "replace-local": { + "description": "Allow local `replace` directives.", + "type": "boolean", + "default": true + }, + "replace-allow-list": { + "description": "List of allowed `replace` directives.", + "type": "array", + "items": { + "type": "string" + } + }, + "retract-allow-no-explanation": { + "description": "Allow to not explain why the version has been retracted in the `retract` directives.", + "type": "boolean", + "default": false + }, + "exclude-forbidden": { + "description": "Forbid the use of the `exclude` directives.", + "type": "boolean", + "default": false + }, + "ignore-forbidden": { + "description": "Forbid the use of the `ignore` directives. (>= go1.25)", + "type": "boolean", + "default": false + }, + "toolchain-forbidden": { + "description": "Forbid the use of the `toolchain` directive.", + "type": "boolean", + "default": false + }, + "toolchain-pattern": { + "description": "Defines a pattern to validate `toolchain` directive.", + "type": "string" + }, + "tool-forbidden": { + "description": "Forbid the use of the `tool` directives.", + "type": "boolean", + "default": false + }, + "go-debug-forbidden": { + "description": "Forbid the use of the `godebug` directive.", + "type": "boolean", + "default": false + }, + "go-version-pattern": { + "description": "Defines a pattern to validate `go` minimum version directive.", + "type": "string", + "default": "" + }, + "check-module-path": { + "description": "Check the validity of the module path.", + "type": "boolean", + "default": false + } + } + }, + "gomodguardv2Settings": { + "type": "object", + "additionalProperties": false, + "properties": { + "local-replace-directives": { + "type": "boolean" + }, + "allowed": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["module"], + "properties": { + "module": { + "type": "string" + }, + "version": { + "type": "string" + }, + "match-type": { + "enum": ["", "exact", "prefix", "regex"], + "default": "exact" + } + } + } + }, + "blocked": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["module"], + "properties": { + "module": { + "type": "string" + }, + "version": { + "type": "string" + }, + "match-type": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "recommendations": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "gomodguardSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "allowed": { + "type": "object", + "additionalProperties": false, + "properties": { + "modules": { + "description": "List of allowed modules.", + "type": "array", + "items": { + "type": "string", + "examples": ["gopkg.in/yaml.v2"] + } + }, + "domains": { + "description": "List of allowed module domains.", + "type": "array", + "items": { + "type": "string", + "examples": ["golang.org"] + } + } + } + }, + "blocked": { + "type": "object", + "additionalProperties": false, + "properties": { + "modules": { + "description": "List of blocked modules.", + "type": "array", + "items": { + "type": "object", + "patternProperties": { + "^.+$": { + "type": "object", + "additionalProperties": false, + "properties": { + "recommendations": { + "description": "Recommended modules that should be used instead.", + "type": "array", + "items": { + "type": "string" + } + }, + "reason": { + "description": "Reason why the recommended module should be used.", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + }, + "versions": { + "description": "List of blocked module version constraints.", + "type": "array", + "items": { + "type": "object", + "patternProperties": { + "^.*$": { + "type": "object", + "additionalProperties": false, + "properties": { + "version": { + "description": "Version constraint.", + "type": "string" + }, + "reason": { + "description": "Reason why the version constraint exists.", + "type": "string" + } + }, + "required": ["reason"] + } + } + } + }, + "local-replace-directives": { + "description": "Raise lint issues if loading local path with replace directive", + "type": "boolean", + "default": true + } + } + } + } + }, + "gosecSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "includes": { + "type": "array", + "description": "To select a subset of rules to run", + "examples": [["G401"]], + "items": { + "$ref": "#/definitions/gosec-rules" + } + }, + "excludes": { + "type": "array", + "description": "To specify a set of rules to explicitly exclude", + "examples": [["G401"]], + "items": { + "$ref": "#/definitions/gosec-rules" + } + }, + "severity": { + "description": "Filter out the issues with a lower severity than the given value", + "type": "string", + "enum": ["low", "medium", "high"], + "default": "low" + }, + "confidence": { + "description": "Filter out the issues with a lower confidence than the given value", + "type": "string", + "enum": ["low", "medium", "high"], + "default": "low" + }, + "config": { + "description": "To specify the configuration of rules", + "type": "object" + }, + "concurrency": { + "description": "Concurrency value", + "type": "integer" + } + } + }, + "gosmopolitanSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "allow-time-local": { + "description": "Allow and ignore `time.Local` usages.", + "type": "boolean", + "default": false + }, + "escape-hatches": { + "description": "List of fully qualified names in the `full/pkg/path.name` form, to act as \"i18n escape hatches\".", + "type": "array", + "items": { + "type": "string" + } + }, + "watch-for-scripts": { + "description": "List of Unicode scripts to watch for any usage in string literals.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "govetSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "settings": { + "description": "Settings per analyzer. Map of analyzer name to specific settings.\nRun `go tool vet help` to find out more.", + "type": "object", + "propertyNames": { + "$ref": "#/definitions/govet-analyzers" + }, + "patternProperties": { + "^.*$": { + "description": "Run `go tool vet help ` to see all settings.", + "type": "object" + } + } + }, + "enable": { + "description": "Enable analyzers by name.", + "type": "array", + "items": { + "$ref": "#/definitions/govet-analyzers" + } + }, + "disable": { + "description": "Disable analyzers by name.", + "type": "array", + "items": { + "$ref": "#/definitions/govet-analyzers" + } + }, + "enable-all": { + "description": "Enable all analyzers.", + "type": "boolean", + "default": false + }, + "disable-all": { + "description": "Disable all analyzers.", + "type": "boolean", + "default": false + } + } + }, + "grouperSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "const-require-single-const": { + "type": "boolean", + "default": false + }, + "const-require-grouping": { + "type": "boolean", + "default": false + }, + "import-require-single-import": { + "type": "boolean", + "default": false + }, + "import-require-grouping": { + "type": "boolean", + "default": false + }, + "type-require-single-type": { + "type": "boolean", + "default": false + }, + "type-require-grouping": { + "type": "boolean", + "default": false + }, + "var-require-single-var": { + "type": "boolean", + "default": false + }, + "var-require-grouping": { + "type": "boolean", + "default": false + } + } + }, + "ifaceSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "enable": { + "description": "Enable analyzers by name.", + "type": "array", + "items": { + "$ref": "#/definitions/iface-analyzers" + } + }, + "settings": { + "type": "object", + "additionalProperties": false, + "properties": { + "unused": { + "type": "object", + "additionalProperties": false, + "properties": { + "exclude": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "importasSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "no-unaliased": { + "description": "Do not allow unaliased imports of aliased packages.", + "type": "boolean", + "default": false + }, + "no-extra-aliases": { + "description": "Do not allow non-required aliases.", + "type": "boolean", + "default": false + }, + "alias": { + "description": "List of aliases", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "pkg": { + "description": "Package path e.g. knative.dev/serving/pkg/apis/autoscaling/v1alpha1", + "type": "string" + }, + "alias": { + "description": "Package alias e.g. autoscalingv1alpha1", + "type": "string" + } + }, + "required": ["pkg", "alias"] + } + } + } + }, + "inamedparamSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "skip-single-param": { + "description": "Skips check for interface methods with only a single parameter.", + "type": "boolean", + "default": false + } + } + }, + "ineffassignSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "check-escaping-errors": { + "description": "Check escaping variables of type error, may cause false positives.", + "type": "boolean", + "default": false + } + } + }, + "iotamixingSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "report-individual": { + "description": "Whether to report individual consts rather than just the const block.", + "type": "boolean", + "default": false + } + } + }, + "ireturnSettings": { + "type": "object", + "additionalProperties": false, + "description": "Use either `reject` or `allow` properties for interfaces matching.", + "properties": { + "allow": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": ["anon", "error", "empty", "stdlib"] + } + ] + } + }, + "reject": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": ["anon", "error", "empty", "stdlib"] + } + ] + } + } + }, + "anyOf": [ + { + "not": { + "properties": { + "allow": { + "const": "reject" + } + } + }, + "required": ["allow"] + }, + { + "required": ["reject"] + } + ] + }, + "lllSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "tab-width": { + "description": "Width of \"\\t\" in spaces.", + "type": "integer", + "minimum": 0, + "default": 1 + }, + "line-length": { + "description": "Maximum allowed line length, lines longer will be reported.", + "type": "integer", + "minimum": 1, + "default": 120 + } + } + }, + "maintidxSettings": { + "description": "Maintainability index https://docs.microsoft.com/en-us/visualstudio/code-quality/code-metrics-maintainability-index-range-and-meaning?view=vs-2022", + "type": "object", + "additionalProperties": false, + "properties": { + "under": { + "description": "Minimum accatpable maintainability index level (see https://docs.microsoft.com/en-us/visualstudio/code-quality/code-metrics-maintainability-index-range-and-meaning?view=vs-2022)", + "type": "number", + "default": 20 + } + } + }, + "makezeroSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "always": { + "description": "Allow only slices initialized with a length of zero.", + "type": "boolean", + "default": false + } + } + }, + "loggercheckSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "kitlog": { + "description": "Allow check for the github.com/go-kit/log library.", + "type": "boolean", + "default": true + }, + "klog": { + "description": "Allow check for the k8s.io/klog/v2 library.", + "type": "boolean", + "default": true + }, + "logr": { + "description": "Allow check for the github.com/go-logr/logr library.", + "type": "boolean", + "default": true + }, + "slog": { + "description": "Allow check for the log/slog library.", + "type": "boolean", + "default": true + }, + "zap": { + "description": "Allow check for the \"sugar logger\" from go.uber.org/zap library.", + "type": "boolean", + "default": true + }, + "require-string-key": { + "description": "Require all logging keys to be inlined constant strings.", + "type": "boolean", + "default": false + }, + "no-printf-like": { + "description": "Require printf-like format specifier (%s, %d for example) not present.", + "type": "boolean", + "default": false + }, + "rules": { + "description": "List of custom rules to check against, where each rule is a single logger pattern, useful for wrapped loggers.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "misspellSettings": { + "description": "Correct spellings using locale preferences for US or UK. Default is to use a neutral variety of English.", + "type": "object", + "additionalProperties": false, + "properties": { + "locale": { + "enum": ["US", "UK"] + }, + "ignore-rules": { + "description": "List of rules to ignore.", + "type": "array", + "items": { + "type": "string" + } + }, + "mode": { + "description": "Mode of the analysis.", + "enum": ["restricted", "", "default"], + "default": "" + }, + "extra-words": { + "description": "Extra word corrections.", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "correction": { + "type": "string" + }, + "typo": { + "type": "string" + } + } + } + } + } + }, + "musttagSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "functions": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "arg-pos": { + "type": "integer" + } + } + } + } + } + }, + "nakedretSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "max-func-lines": { + "description": "Report if a function has more lines of code than this value and it has naked returns.", + "type": "integer", + "minimum": 0, + "default": 30 + } + } + }, + "nestifSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "min-complexity": { + "description": "Minimum complexity of \"if\" statements to report.", + "type": "integer", + "default": 5 + } + } + }, + "nilnilSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "only-two": { + "type": "boolean", + "description": "To check functions with only two return values.", + "default": true + }, + "detect-opposite": { + "type": "boolean", + "description": "In addition, detect opposite situation (simultaneous return of non-nil error and valid value).", + "default": false + }, + "checked-types": { + "type": "array", + "description": "List of return types to check.", + "items": { + "enum": ["chan", "func", "iface", "map", "ptr", "uintptr", "unsafeptr"] + }, + "default": ["chan", "func", "iface", "map", "ptr", "uintptr", "unsafeptr"] + } + } + }, + "nlreturnSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "block-size": { + "description": "set block size that is still ok", + "type": "number", + "default": 0, + "minimum": 0 + } + } + }, + "mndSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "ignored-files": { + "description": "List of file patterns to exclude from analysis.", + "examples": [["magic1_.*.go"]], + "type": "array", + "items": { + "type": "string" + } + }, + "ignored-functions": { + "description": "Comma-separated list of function patterns to exclude from the analysis.", + "examples": [["math.*", "http.StatusText", "make"]], + "type": "array", + "items": { + "type": "string" + } + }, + "ignored-numbers": { + "description": "List of numbers to exclude from analysis.", + "examples": [["1000", "1234_567_890", "3.14159264"]], + "type": "array", + "items": { + "type": "string" + } + }, + "checks": { + "description": "The list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description.", + "type": "array", + "items": { + "enum": [ + "argument", + "case", + "condition", + "operation", + "return", + "assign" + ] + } + } + } + }, + "modernizeSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "disable": { + "description": "List of analyzers to disable.", + "type": "array", + "items": { + "$ref": "#/definitions/modernize-analyzers" + } + } + } + }, + "nolintlintSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "allow-unused": { + "description": "Enable to ensure that nolint directives are all used.", + "type": "boolean", + "default": true + }, + "allow-no-explanation": { + "description": "Exclude these linters from requiring an explanation.", + "type": "array", + "items": { + "$ref": "#/definitions/linter-names" + }, + "default": [] + }, + "require-explanation": { + "description": "Enable to require an explanation of nonzero length after each nolint directive.", + "type": "boolean", + "default": false + }, + "require-specific": { + "description": "Enable to require nolint directives to mention the specific linter being suppressed.", + "type": "boolean", + "default": false + } + } + }, + "reassignSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "patterns": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "recvcheckSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "disable-builtin": { + "description": "Disables the built-in method exclusions.", + "type": "boolean", + "default": true + }, + "exclusions": { + "description": "User-defined method exclusions.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "nonamedreturnsSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "report-error-in-defer": { + "description": "Report named error if it is assigned inside defer.", + "type": "boolean", + "default": false + } + } + }, + "paralleltestSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "ignore-missing": { + "description": "Ignore missing calls to `t.Parallel()` and only report incorrect uses of it.", + "type": "boolean", + "default": false + }, + "ignore-missing-subtests": { + "description": "Ignore missing calls to `t.Parallel()` in subtests. Top-level tests are still required to have `t.Parallel`, but subtests are allowed to skip it.", + "type": "boolean", + "default": false + }, + "check-cleanup": { + "description": "Check that defer is not used with t.Parallel (use t.Cleanup instead).", + "type": "boolean", + "default": false + } + } + }, + "perfsprintSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "integer-format": { + "description": "Enable/disable optimization of integer formatting.", + "type": "boolean", + "default": true + }, + "int-conversion": { + "description": "Optimizes even if it requires an int or uint type cast.", + "type": "boolean", + "default": true + }, + "error-format": { + "description": "Enable/disable optimization of error formatting.", + "type": "boolean", + "default": true + }, + "err-error": { + "description": "Optimizes into `err.Error()` even if it is only equivalent for non-nil errors.", + "type": "boolean", + "default": false + }, + "errorf": { + "description": "Optimizes `fmt.Errorf`.", + "type": "boolean", + "default": true + }, + "string-format": { + "description": "Enable/disable optimization of string formatting.", + "type": "boolean", + "default": true + }, + "sprintf1": { + "description": "Optimizes `fmt.Sprintf` with only one argument.", + "type": "boolean", + "default": true + }, + "strconcat": { + "description": "Optimizes into strings concatenation.", + "type": "boolean", + "default": true + }, + "bool-format": { + "description": "Enable/disable optimization of bool formatting.", + "type": "boolean", + "default": true + }, + "hex-format": { + "description": "Enable/disable optimization of hex formatting.", + "type": "boolean", + "default": true + }, + "concat-loop": { + "description": "Enable/disable optimization of concat loop.", + "type": "boolean", + "default": true + }, + "loop-other-ops": { + "description": "Optimization of `concat-loop` even with other operations.", + "type": "boolean", + "default": false + } + } + }, + "preallocSettings": { + "description": "We do not recommend using this linter before doing performance profiling.\nFor most programs usage of `prealloc` will be premature optimization.", + "type": "object", + "additionalProperties": false, + "properties": { + "simple": { + "description": "Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.", + "type": "boolean", + "default": true + }, + "range-loops": { + "description": "Report preallocation suggestions on range loops.", + "type": "boolean", + "default": true + }, + "for-loops": { + "description": "Report preallocation suggestions on for loops.", + "type": "boolean", + "default": false + } + } + }, + "predeclaredSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "ignore": { + "description": "List of predeclared identifiers to not report on.", + "type": "array", + "items": { + "type": "string" + } + }, + "qualified-name": { + "description": "Include method names and field names in checks.", + "type": "boolean", + "default": false + } + } + }, + "promlinterSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "strict": {}, + "disabled-linters": { + "type": "array", + "items": { + "enum": [ + "Help", + "MetricUnits", + "Counter", + "HistogramSummaryReserved", + "MetricTypeInName", + "ReservedChars", + "CamelCase", + "UnitAbbreviations" + ] + } + } + } + }, + "protogetterSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "skip-generated-by": { + "type": "array", + "items": { + "type": "string", + "examples": ["protoc-gen-go-my-own-generator"] + } + }, + "skip-files": { + "type": "array", + "items": { + "type": "string", + "examples": ["*.pb.go"] + } + }, + "skip-any-generated": { + "description": "Skip any generated files from the checking.", + "type": "boolean", + "default": false + }, + "replace-first-arg-in-append": { + "description": "Skip first argument of append function.", + "type": "boolean", + "default": false + } + } + }, + "reviveSettings": { + "type": "object", + "additionalProperties": false, + "examples": [ + { + "ignore-generated-header": true, + "severity": "warning", + "rules": [ + { + "name": "indent-error-flow", + "severity": "warning" + }, + { + "name": "add-constant", + "severity": "warning", + "arguments": [ + { + "maxLitCount": "3", + "allowStrs": "\"\"", + "allowInts": "0,1,2", + "allowFloats": "0.0,0.,1.0,1.,2.0,2." + } + ] + } + ] + } + ], + "properties": { + "max-open-files": { + "type": "integer" + }, + "confidence": { + "type": "number" + }, + "severity": { + "type": "string", + "enum": ["warning", "error"] + }, + "enable-all-rules": { + "type": "boolean", + "default": false + }, + "enable-default-rules": { + "type": "boolean", + "default": false + }, + "directives": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "enum": ["specify-disable-reason"] + }, + "severity": { + "type": "string", + "enum": ["warning", "error"] + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + }, + "arguments": { + "type": "array" + } + } + } + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name"], + "properties": { + "name": { + "$ref": "#/definitions/revive-rules", + "title": "The rule name" + }, + "disabled": { + "type": "boolean" + }, + "severity": { + "type": "string", + "enum": ["warning", "error"] + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + } + }, + "arguments": { + "type": "array" + } + } + } + } + } + }, + "rowserrcheckSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "packages": { + "type": "array", + "items": { + "description": "", + "type": "string", + "examples": ["github.com/jmoiron/sqlx"] + } + } + } + }, + "sloglintSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "no-global": { + "description": "Report the use of global loggers.", + "enum": ["", "all", "default"], + "default": "" + }, + "context": { + "description": "Report the use of functions without a context.Context.", + "enum": ["", "all", "scope"], + "default": "" + }, + "static-msg": { + "description": "Report dynamic log messages, such as those that are built with fmt.Sprintf.", + "type": "boolean", + "default": false + }, + "msg-style": { + "description": "Report log messages that do not match a particular style.", + "enum": ["", "lowercased", "capitalized"], + "default": "" + }, + "no-mixed-args": { + "description": "Report the use of both key-value pairs and attributes within a single function call.", + "type": "boolean", + "default": true + }, + "kv-only": { + "description": "Report any use of attributes as function call arguments.", + "type": "boolean", + "default": false + }, + "attr-only": { + "description": "Report any use of key-value pairs as function call arguments.", + "type": "boolean", + "default": false + }, + "args-on-sep-lines": { + "description": "Report two or more arguments on the same line.", + "type": "boolean", + "default": false + }, + "no-raw-keys": { + "description": "Report the use of string literals as log keys.", + "type": "boolean", + "default": false + }, + "allowed-keys": { + "description": "Report the use of log keys that are not explicitly allowed.", + "type": "array", + "items": { + "type": "string" + } + }, + "forbidden-keys": { + "description": "Report the use of forbidden log keys.", + "type": "array", + "items": { + "type": "string" + } + }, + "key-naming-case": { + "description": "Report log keys that do not match a particular naming case.", + "enum": ["snake", "kebab", "camel", "pascal"] + }, + "custom-funcs": { + "description": "Analyze custom functions in addition to the standard log/slog functions.", + "type": "array", + "items": { + "$ref": "#/definitions/settings/definitions/sloglintCustomFunc" + } + } + } + }, + "sloglintCustomFunc": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "description": "The full name of the function, including the package. If the function is a method, the receiver type must be wrapped in parentheses.", + "type": "string" + }, + "msg-pos": { + "description": "The position of the \"msg string\" argument in the function signature, starting from 0. If there is no message in the function, a negative value must be passed.", + "type": "integer" + }, + "args-pos": { + "description": "The position of the \"args ...any\" argument in the function signature, starting from 0. If there are no arguments in the function, a negative value must be passed.", + "type": "integer" + } + } + }, + "spancheckSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "checks": { + "description": "Checks to enable.", + "type": "array", + "items": { + "enum": ["end", "record-error", "set-status"] + } + }, + "ignore-check-signatures": { + "description": "A list of regexes for function signatures that silence `record-error` and `set-status` reports if found in the call path to a returned error.", + "type": "array", + "items": { + "type": "string" + } + }, + "extra-start-span-signatures": { + "description": "A list of regexes for additional function signatures that create spans.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "staticcheckSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "checks": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/staticcheck-checks" + }, + { + "type": "string" + } + ] + } + }, + "dot-import-whitelist": { + "description": "By default, ST1001 forbids all uses of dot imports in non-test packages. This setting allows setting a whitelist of import paths that can be dot-imported anywhere.", + "type": "array", + "items": { + "type": "string" + } + }, + "http-status-code-whitelist": { + "description": "ST1013 recommends using constants from the net/http package instead of hard-coding numeric HTTP status codes. This setting specifies a list of numeric status codes that this check does not complain about.", + "default": ["200", "400", "404", "500"], + "type": "array", + "items": { + "enum": [ + "100", + "101", + "102", + "103", + "200", + "201", + "202", + "203", + "204", + "205", + "206", + "207", + "208", + "226", + "300", + "301", + "302", + "303", + "304", + "305", + "306", + "307", + "308", + "400", + "401", + "402", + "403", + "404", + "405", + "406", + "407", + "408", + "409", + "410", + "411", + "412", + "413", + "414", + "415", + "416", + "417", + "418", + "421", + "422", + "423", + "424", + "425", + "426", + "428", + "429", + "431", + "451", + "500", + "501", + "502", + "503", + "504", + "505", + "506", + "507", + "508", + "510", + "511" + ] + } + }, + "initialisms": { + "description": "ST1003 check, among other things, for the correct capitalization of initialisms. The set of known initialisms can be configured with this option.", + "type": "array", + "items": { + "type": "string", + "default": [ + "ACL", + "API", + "ASCII", + "CPU", + "CSS", + "DNS", + "EOF", + "GUID", + "HTML", + "HTTP", + "HTTPS", + "ID", + "IP", + "JSON", + "QPS", + "RAM", + "RPC", + "SLA", + "SMTP", + "SQL", + "SSH", + "TCP", + "TLS", + "TTL", + "UDP", + "UI", + "GID", + "UID", + "UUID", + "URI", + "URL", + "UTF8", + "VM", + "XML", + "XMPP", + "XSRF", + "XSS", + "SIP", + "RTP", + "AMQP", + "DB", + "TS" + ] + } + } + } + }, + "tagalignSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "align": { + "description": "Align and sort can be used together or separately.", + "type": "boolean", + "default": true + }, + "sort": { + "description": "Whether enable tags sort.", + "type": "boolean", + "default": true + }, + "order": { + "description": "Specify the order of tags, the other tags will be sorted by name.", + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "examples": [ + [ + "json", + "yaml", + "yml", + "toml", + "mapstructure", + "binding", + "validate" + ] + ] + }, + "strict": { + "description": "Whether enable strict style.", + "type": "boolean", + "default": false + } + } + }, + "tagliatelleSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "case": { + "type": "object", + "additionalProperties": false, + "properties": { + "use-field-name": { + "description": "Use the struct field name to check the name of the struct tag.", + "type": "boolean", + "default": false + }, + "ignored-fields": { + "description": "The field names to ignore.", + "type": "array", + "items": { + "type": "string", + "examples": ["example"] + } + }, + "rules": { + "type": "object", + "patternProperties": { + "^.+$": { + "$ref": "#/definitions/tagliatelle-cases" + } + } + }, + "extended-rules": { + "description": "Defines the association between tag name and case.", + "type": "object", + "patternProperties": { + "^.+$": { + "type": "object", + "additionalProperties": false, + "required": ["case"], + "properties": { + "case": { + "$ref": "#/definitions/tagliatelle-cases" + }, + "extra-initialisms": { + "type": "boolean", + "default": false + }, + "initialism-overrides": { + "type": "object", + "patternProperties": { + "^.+$": { + "type": "boolean", + "default": false + } + } + } + } + } + } + }, + "overrides": { + "description": "Overrides the default/root configuration.", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["pkg"], + "properties": { + "pkg": { + "description": "A package path.", + "type": "string" + }, + "use-field-name": { + "description": "Use the struct field name to check the name of the struct tag.", + "type": "boolean", + "default": false + }, + "ignored-fields": { + "description": "The field names to ignore.", + "type": "array", + "items": { + "type": "string", + "examples": ["example"] + } + }, + "ignore": { + "description": "Ignore the package (takes precedence over all other configurations).", + "type": "boolean", + "default": false + }, + "rules": { + "type": "object", + "patternProperties": { + "^.+$": { + "$ref": "#/definitions/tagliatelle-cases" + } + } + }, + "extended-rules": { + "description": "Defines the association between tag name and case.", + "type": "object", + "patternProperties": { + "^.+$": { + "type": "object", + "additionalProperties": false, + "required": ["case"], + "properties": { + "case": { + "$ref": "#/definitions/tagliatelle-cases" + }, + "extra-initialisms": { + "type": "boolean", + "default": false + }, + "initialism-overrides": { + "type": "object", + "patternProperties": { + "^.+$": { + "type": "boolean", + "default": false + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "testifylintSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "enable-all": { + "description": "Enable all checkers.", + "type": "boolean", + "default": false + }, + "disable-all": { + "description": "Disable all checkers.", + "type": "boolean", + "default": false + }, + "enable": { + "description": "Enable specific checkers.", + "type": "array", + "items": { + "enum": [ + "blank-import", + "bool-compare", + "compares", + "contains", + "empty", + "encoded-compare", + "equal-values", + "error-is-as", + "error-nil", + "expected-actual", + "float-compare", + "formatter", + "go-require", + "len", + "negative-positive", + "nil-compare", + "regexp", + "require-error", + "suite-broken-parallel", + "suite-dont-use-pkg", + "suite-extra-assert-call", + "suite-method-signature", + "suite-subtest-run", + "suite-thelper", + "useless-assert" + ] + }, + "default": [ + "blank-import", + "bool-compare", + "compares", + "contains", + "empty", + "encoded-compare", + "equal-values", + "error-is-as", + "error-nil", + "expected-actual", + "float-compare", + "formatter", + "go-require", + "len", + "negative-positive", + "nil-compare", + "regexp", + "require-error", + "suite-broken-parallel", + "suite-dont-use-pkg", + "suite-extra-assert-call", + "suite-method-signature", + "suite-subtest-run", + "useless-assert" + ] + }, + "disable": { + "description": "Disable specific checkers.", + "type": "array", + "items": { + "enum": [ + "blank-import", + "bool-compare", + "compares", + "contains", + "empty", + "encoded-compare", + "equal-values", + "error-is-as", + "error-nil", + "expected-actual", + "float-compare", + "formatter", + "go-require", + "len", + "negative-positive", + "nil-compare", + "regexp", + "require-error", + "suite-broken-parallel", + "suite-dont-use-pkg", + "suite-extra-assert-call", + "suite-method-signature", + "suite-subtest-run", + "suite-thelper", + "useless-assert" + ], + "default": [ + "suite-thelper" + ] + } + }, + "bool-compare": { + "type": "object", + "additionalProperties": false, + "properties": { + "ignore-custom-types": { + "description": "To ignore user defined types (over builtin bool).", + "type": "boolean", + "default": false + } + } + }, + "expected-actual": { + "type": "object", + "additionalProperties": false, + "properties": { + "pattern": { + "description": "Regexp for expected variable name.", + "type": "string", + "default": "(^(exp(ected)?|want(ed)?)([A-Z]\\w*)?$)|(^(\\w*[a-z])?(Exp(ected)?|Want(ed)?)$)" + } + } + }, + "formatter": { + "type": "object", + "additionalProperties": false, + "properties": { + "check-format-string": { + "description": "To enable go vet's printf checks.", + "type": "boolean", + "default": true + }, + "require-f-funcs": { + "description": "To require f-assertions (e.g. assert.Equalf) if format string is used, even if there are no variable-length variables.", + "type": "boolean", + "default": false + }, + "require-string-msg": { + "description": "To require that the first element of msgAndArgs (msg) has a string type.", + "type": "boolean", + "default": true + } + } + }, + "go-require": { + "type": "object", + "additionalProperties": false, + "properties": { + "ignore-http-handlers": { + "description": "To ignore HTTP handlers (like http.HandlerFunc).", + "type": "boolean", + "default": false + } + } + }, + "require-error": { + "type": "object", + "additionalProperties": false, + "properties": { + "fn-pattern": { + "description": "Regexp for assertions to analyze. If defined, then only matched error assertions will be reported.", + "type": "string", + "default": "" + } + } + }, + "suite-extra-assert-call": { + "type": "object", + "additionalProperties": false, + "properties": { + "mode": { + "description": "To require or remove extra Assert() call?", + "type": "string", + "enum": ["remove", "require"], + "default": "remove" + } + } + } + } + }, + "testpackageSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "skip-regexp": { + "description": "Files with names matching this regular expression are skipped.", + "type": "string", + "examples": ["(export|internal)_test\\.go"] + }, + "allow-packages": { + "description": "List of packages that don't end with _test that tests are allowed to be in.", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "examples": ["example"] + } + } + } + }, + "thelperSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "test": { + "type": "object", + "additionalProperties": false, + "properties": { + "begin": { + "description": "Check if `t.Helper()` begins helper function.", + "default": true, + "type": "boolean" + }, + "first": { + "description": "Check if *testing.T is first param of helper function.", + "default": true, + "type": "boolean" + }, + "name": { + "description": "Check if *testing.T param has t name.", + "default": true, + "type": "boolean" + } + } + }, + "benchmark": { + "type": "object", + "additionalProperties": false, + "properties": { + "begin": { + "description": "Check if `b.Helper()` begins helper function.", + "default": true, + "type": "boolean" + }, + "first": { + "description": "Check if *testing.B is first param of helper function.", + "default": true, + "type": "boolean" + }, + "name": { + "description": "Check if *testing.B param has b name.", + "default": true, + "type": "boolean" + } + } + }, + "tb": { + "type": "object", + "additionalProperties": false, + "properties": { + "begin": { + "description": "Check if `tb.Helper()` begins helper function.", + "default": true, + "type": "boolean" + }, + "first": { + "description": "Check if *testing.TB is first param of helper function.", + "default": true, + "type": "boolean" + }, + "name": { + "description": "Check if *testing.TB param has tb name.", + "default": true, + "type": "boolean" + } + } + }, + "fuzz": { + "type": "object", + "additionalProperties": false, + "properties": { + "begin": { + "description": "Check if `f.Helper()` begins helper function.", + "default": true, + "type": "boolean" + }, + "first": { + "description": "Check if *testing.F is first param of helper function.", + "default": true, + "type": "boolean" + }, + "name": { + "description": "Check if *testing.F param has f name.", + "default": true, + "type": "boolean" + } + } + } + } + }, + "usestdlibvarsSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "http-method": { + "description": "Suggest the use of http.MethodXX.", + "type": "boolean", + "default": true + }, + "http-status-code": { + "description": "Suggest the use of http.StatusXX.", + "type": "boolean", + "default": true + }, + "time-weekday": { + "description": "Suggest the use of time.Weekday.String().", + "type": "boolean", + "default": false + }, + "time-month": { + "description": "Suggest the use of time.Month.String().", + "type": "boolean", + "default": false + }, + "time-layout": { + "description": "Suggest the use of time.Layout.", + "type": "boolean", + "default": false + }, + "time-date-month": { + "description": "Suggest the use of time.Month in time.Date.", + "type": "boolean", + "default": false + }, + "crypto-hash": { + "description": "Suggest the use of crypto.Hash.String().", + "type": "boolean", + "default": false + }, + "default-rpc-path": { + "description": "Suggest the use of rpc.DefaultXXPath.", + "type": "boolean", + "default": false + }, + "sql-isolation-level": { + "description": "Suggest the use of sql.LevelXX.String().", + "type": "boolean", + "default": false + }, + "tls-signature-scheme": { + "description": "Suggest the use of tls.SignatureScheme.String().", + "type": "boolean", + "default": false + }, + "constant-kind": { + "description": "Suggest the use of constant.Kind.String().", + "type": "boolean", + "default": false + } + } + }, + "usetestingSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "context-background": { + "type": "boolean", + "default": false + }, + "context-todo": { + "type": "boolean", + "default": false + }, + "os-chdir": { + "type": "boolean", + "default": true + }, + "os-mkdir-temp": { + "type": "boolean", + "default": true + }, + "os-setenv": { + "type": "boolean", + "default": true + }, + "os-create-temp": { + "type": "boolean", + "default": true + }, + "os-temp-dir": { + "type": "boolean", + "default": false + } + } + }, + "unconvertSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "fast-math": { + "type": "boolean", + "default": false + }, + "safe": { + "type": "boolean", + "default": false + } + } + }, + "unparamSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "check-exported": { + "description": "Inspect exported functions. Set to true if no external program/library imports your code.\n\nWARNING: if you enable this setting, unparam will report a lot of false-positives in text editors:\nif it's called for subdir of a project it can't find external interfaces. All text editor integrations\nwith golangci-lint call it on a directory with the changed file.", + "type": "boolean", + "default": false + } + } + }, + "unqueryvetSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "check-sql-builders": { + "description": "Enable SQL builder checking.", + "type": "boolean", + "default": true + }, + "check-aliased-wildcard": { + "description": "Enable aliased wildcard detection like SELECT t.*.", + "type": "boolean", + "default": true + }, + "check-string-concat": { + "description": "Enable string concatenation analysis.", + "type": "boolean", + "default": true + }, + "check-format-strings": { + "description": "Enable format string analysis like fmt.Sprintf.", + "type": "boolean", + "default": true + }, + "check-string-builder": { + "description": "Enable strings.Builder analysis.", + "type": "boolean", + "default": true + }, + "check-subqueries": { + "description": "Enable subquery analysis.", + "type": "boolean", + "default": true + }, + "check-n1": { + "type": "boolean", + "default": false + }, + "check-sql-injection": { + "type": "boolean", + "default": false + }, + "check-tx-leaks": { + "type": "boolean", + "default": false + }, + "allowed-patterns": { + "description": "Regex patterns for acceptable SELECT * usage.", + "type": "array", + "items": { + "type": "string" + } + }, + "allow": { + "description": "Allow is a list of SQL patterns to allow (whitelist).", + "type": "array", + "items": { + "type": "string" + } + }, + "ignored-functions": { + "description": "Functions to ignore.", + "type": "array", + "items": { + "type": "string" + } + }, + "sql-builders": { + "type": "object", + "additionalProperties": false, + "properties": { + "squirrel": { + "type": "boolean", + "default": true + }, + "gorm": { + "type": "boolean", + "default": true + }, + "sqlx": { + "type": "boolean", + "default": true + }, + "ent": { + "type": "boolean", + "default": true + }, + "pgx": { + "type": "boolean", + "default": true + }, + "bun": { + "type": "boolean", + "default": true + }, + "sqlboiler": { + "type": "boolean", + "default": true + }, + "jet": { + "type": "boolean", + "default": true + } + } + }, + "custom-rules": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "pattern": { + "type": "string" + }, + "patterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "when": { + "type": "string" + }, + "message": { + "type": "string" + }, + "action": { + "type": "string" + } + } + } + } + } + }, + "unusedSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "field-writes-are-uses": { + "description": "", + "type": "boolean", + "default": true + }, + "post-statements-are-reads": { + "description": "", + "type": "boolean", + "default": false + }, + "exported-fields-are-used": { + "description": "", + "type": "boolean", + "default": true + }, + "parameters-are-used": { + "description": "", + "type": "boolean", + "default": true + }, + "local-variables-are-used": { + "description": "", + "type": "boolean", + "default": true + }, + "generated-is-used": { + "description": "", + "type": "boolean", + "default": true + } + } + }, + "varnamelenSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "max-distance": { + "description": "Variables used in at most this N-many lines will be ignored.", + "type": "integer", + "default": 5 + }, + "min-name-length": { + "description": "The minimum length of a variable's name that is considered `long`.", + "type": "integer", + "default": 3 + }, + "check-receiver": { + "description": "Check method receiver names.", + "default": false, + "type": "boolean" + }, + "check-return": { + "description": "Check named return values.", + "default": false, + "type": "boolean" + }, + "check-type-param": { + "description": "Check type parameters.", + "default": false, + "type": "boolean" + }, + "ignore-type-assert-ok": { + "description": "Ignore `ok` variables that hold the bool return value of a type assertion", + "default": false, + "type": "boolean" + }, + "ignore-map-index-ok": { + "description": "Ignore `ok` variables that hold the bool return value of a map index.", + "default": false, + "type": "boolean" + }, + "ignore-chan-recv-ok": { + "description": "Ignore `ok` variables that hold the bool return value of a channel receive.", + "default": false, + "type": "boolean" + }, + "ignore-names": { + "description": "Optional list of variable names that should be ignored completely.", + "default": [[]], + "type": "array", + "items": { + "type": "string" + } + }, + "ignore-decls": { + "description": "Optional list of variable declarations that should be ignored completely.", + "type": "array", + "items": { + "type": "string" + }, + "examples": [ + ["c echo.Context", "t testing.T", "f *foo.Bar", "const C"] + ] + } + } + }, + "whitespaceSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "multi-if": { + "description": "Enforces newlines (or comments) after every multi-line if statement", + "type": "boolean", + "default": false + }, + "multi-func": { + "description": "Enforces newlines (or comments) after every multi-line function signature", + "type": "boolean", + "default": false + } + } + }, + "wrapcheckSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "extra-ignore-sigs": { + "description": "An array of strings specifying additional substrings of signatures to ignore.", + "default": [ + ".CustomError(", + ".SpecificWrap(" + ], + "type": "array", + "items": { + "type": "string" + } + }, + "ignore-sigs": { + "description": "An array of strings which specify substrings of signatures to ignore.", + "default": [ + ".Errorf(", + "errors.New(", + "errors.Unwrap(", + ".Wrap(", + ".Wrapf(", + ".WithMessage(", + ".WithMessagef(", + ".WithStack(" + ], + "type": "array", + "items": { + "type": "string" + } + }, + "ignore-sig-regexps": { + "description": "An array of strings which specify regular expressions of signatures to ignore.", + "default": [""], + "type": "array", + "items": { + "type": "string" + } + }, + "ignore-package-globs": { + "description": "An array of glob patterns which, if any match the package of the function returning the error, will skip wrapcheck analysis for this error.", + "default": [""], + "type": "array", + "items": { + "type": "string" + } + }, + "ignore-interface-regexps": { + "description": "An array of glob patterns which, if matched to an underlying interface name, will ignore unwrapped errors returned from a function whose call is defined on the given interface.", + "default": [""], + "type": "array", + "items": { + "type": "string" + } + }, + "report-internal-errors": { + "description": "Determines whether wrapcheck should report errors returned from inside the package.", + "type": "boolean", + "default": false + } + } + }, + "wslSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "allow-assign-and-anything": { + "description": "Controls if you may cuddle assignments and anything without needing an empty line between them.", + "type": "boolean", + "default": false + }, + "allow-assign-and-call": { + "description": "Allow calls and assignments to be cuddled as long as the lines have any matching variables, fields or types.", + "type": "boolean", + "default": true + }, + "allow-cuddle-declarations": { + "description": "Allow declarations (var) to be cuddled.", + "type": "boolean", + "default": false + }, + "allow-cuddle-with-calls": { + "description": "A list of call idents that everything can be cuddled with.", + "type": "array", + "items": { + "type": "string" + } + }, + "allow-cuddle-with-rhs": { + "description": "AllowCuddleWithRHS is a list of right hand side variables that is allowed to be cuddled with anything.", + "type": "array", + "items": { + "type": "string" + } + }, + "allow-cuddle-used-in-block": { + "description": "Allow cuddling with any block as long as the variable is used somewhere in the block", + "type": "boolean", + "default": false + }, + "allow-multiline-assign": { + "description": "Allow multiline assignments to be cuddled.", + "type": "boolean", + "default": true + }, + "allow-separated-leading-comment": { + "description": "Allow leading comments to be separated with empty lines.", + "type": "boolean", + "default": false + }, + "allow-trailing-comment": { + "description": "Allow trailing comments in ending of blocks.", + "type": "boolean", + "default": false + }, + "error-variable-names": { + "description": "When force-err-cuddling is enabled this is a list of names used for error variables to check for in the conditional.", + "type": "array", + "items": { + "type": "string" + } + }, + "force-case-trailing-whitespace": { + "description": "Force newlines in end of case at this limit (0 = never).", + "type": "integer", + "minimum": 0, + "default": 0 + }, + "force-err-cuddling": { + "description": "Causes an error when an If statement that checks an error variable doesn't cuddle with the assignment of that variable.", + "type": "boolean", + "default": false + }, + "force-short-decl-cuddling": { + "description": "Causes an error if a short declaration (:=) cuddles with anything other than another short declaration.", + "type": "boolean", + "default": false + }, + "strict-append": { + "description": "If true, append is only allowed to be cuddled if appending value is matching variables, fields or types on line above.", + "type": "boolean", + "default": true + } + } + }, + "wslSettingsV5": { + "type": "object", + "additionalProperties": false, + "properties": { + "allow-first-in-block": { + "type": "boolean", + "default": true + }, + "allow-whole-block": { + "type": "boolean", + "default": false + }, + "branch-max-lines": { + "type": "integer", + "default": 2 + }, + "case-max-lines": { + "type": "integer", + "default": 0 + }, + "cuddle-max-statements": { + "type": "integer", + "default": 1 + }, + "default": { + "enum": ["all", "none", "default", ""], + "default": "default" + }, + "enable": { + "type": "array", + "items": { + "$ref": "#/definitions/wsl-checks" + } + }, + "disable": { + "type": "array", + "items": { + "$ref": "#/definitions/wsl-checks" + } + } + } + }, + "copyloopvarSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "check-alias": { + "type": "boolean", + "default": false + } + } + }, + "customSettings": { + "description": "The custom section can be used to define linter plugins to be loaded at runtime. See README of golangci-lint for more information.\nEach custom linter should have a unique name.", + "type": "object", + "patternProperties": { + "^.*$": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "description": "The plugin type.", + "enum": ["module", "goplugin"], + "default": "goplugin" + }, + "path": { + "description": "The path to the plugin *.so. Can be absolute or local.", + "type": "string", + "examples": ["/path/to/example.so"] + }, + "description": { + "description": "The description of the linter, for documentation purposes only.", + "type": "string" + }, + "original-url": { + "description": "Intended to point to the repo location of the linter, for documentation purposes only.", + "type": "string" + }, + "settings": { + "description": "Plugins settings/configuration. Only work with plugin based on `linterdb.PluginConstructor`.", + "type": "object" + } + }, + "oneOf": [ + { + "properties": { + "type": {"enum": ["module"] } + }, + "required": ["type"] + }, + { + "required": ["path"] + } + ] + } + } + } + } + } + }, + "type": "object", + "additionalProperties": false, + "required": ["version"], + "properties": { + "version": { + "type": "string", + "default": "2" + }, + "run": { + "description": "Options for analysis running,", + "type": "object", + "additionalProperties": false, + "properties": { + "concurrency": { + "description": "Number of concurrent runners. Defaults to the number of available CPU cores.", + "type": "integer", + "minimum": 0, + "examples": [4] + }, + "timeout": { + "description": "Timeout for the analysis.", + "type": "string", + "pattern": "^((\\d+h)?(\\d+m)?(\\d+(?:\\.\\d)?s)?|0)$", + "default": "1m", + "examples": ["30s", "5m", "5m30s"] + }, + "issues-exit-code": { + "description": "Exit code when at least one issue was found.", + "type": "integer", + "default": 1 + }, + "tests": { + "description": "Enable inclusion of test files.", + "type": "boolean", + "default": true + }, + "build-tags": { + "description": "List of build tags to pass to all linters.", + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "examples": [["mytag"]] + }, + "modules-download-mode": { + "description": "Option to pass to \"go list -mod={option}\".\nSee \"go help modules\" for more information.", + "enum": ["mod", "readonly", "vendor"] + }, + "enable-build-vcs": { + "type": "boolean", + "default": false + }, + "allow-parallel-runners": { + "description": "Allow multiple parallel golangci-lint instances running. If disabled, golangci-lint acquires file lock on start.", + "type": "boolean", + "default": false + }, + "allow-serial-runners": { + "description": "Allow multiple golangci-lint instances running, but serialize them around a lock.", + "type": "boolean", + "default": false + }, + "go": { + "description": "Targeted Go version.", + "type": "string", + "default": "1.17" + }, + "relative-path-mode": { + "description": "The mode used to evaluate relative paths.", + "type": "string", + "$ref": "#/definitions/relative-path-modes", + "default": "wd" + } + } + }, + "output": { + "description": "Output configuration options.", + "type": "object", + "additionalProperties": false, + "properties": { + "formats": { + "description": "Output formats to use.", + "type": "object", + "additionalProperties": false, + "properties": { + "text": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "$ref": "#/definitions/formats-path", + "default": "stdout" + }, + "print-linter-name": { + "type": "boolean", + "default": true + }, + "print-issued-lines": { + "type": "boolean", + "default": true + }, + "colors": { + "type": "boolean", + "default": true + } + } + }, + "json": { + "$ref": "#/definitions/simple-format" + }, + "tab": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "$ref": "#/definitions/formats-path", + "default": "stdout" + }, + "print-linter-name": { + "type": "boolean", + "default": true + }, + "colors": { + "type": "boolean", + "default": true + } + } + }, + "html": { + "$ref": "#/definitions/simple-format" + }, + "checkstyle": { + "$ref": "#/definitions/simple-format" + }, + "code-climate": { + "$ref": "#/definitions/simple-format" + }, + "junit-xml": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "$ref": "#/definitions/formats-path", + "default": "stdout" + }, + "extended": { + "type": "boolean", + "default": true + } + } + }, + "teamcity": { + "$ref": "#/definitions/simple-format" + }, + "sarif": { + "$ref": "#/definitions/simple-format" + } + } + }, + "path-mode": { + "type": "string", + "default": "", + "examples": ["abs"] + }, + "path-prefix": { + "description": "Add a prefix to the output file references.", + "type": "string", + "default": "" + }, + "show-stats": { + "description": "Show statistics per linter.", + "type": "boolean", + "default": true + }, + "sort-order": { + "type": "array", + "items": { + "enum": ["linter", "severity", "file"] + } + } + } + }, + "linters": { + "type": "object", + "additionalProperties": false, + "properties": { + "default": { + "enum": [ + "standard", + "all", + "none", + "fast" + ] + }, + "enable": { + "description": "List of enabled linters.", + "type": "array", + "items": { + "$ref": "#/definitions/linter-names" + } + }, + "disable": { + "description": "List of disabled linters.", + "type": "array", + "items": { + "$ref": "#/definitions/linter-names" + } + }, + "settings": { + "description": "All available settings of specific linters.", + "type": "object", + "additionalProperties": false, + "properties": { + "dupword": { + "$ref": "#/definitions/settings/definitions/dupwordSettings" + }, + "asasalint": { + "$ref": "#/definitions/settings/definitions/asasalintSettings" + }, + "bidichk": { + "$ref": "#/definitions/settings/definitions/bidichkSettings" + }, + "bodyclose": { + "$ref": "#/definitions/settings/definitions/bodycloseSettings" + }, + "cyclop": { + "$ref": "#/definitions/settings/definitions/cyclopSettings" + }, + "decorder": { + "$ref": "#/definitions/settings/definitions/decorderSettings" + }, + "depguard":{ + "$ref": "#/definitions/settings/definitions/depguardSettings" + }, + "dogsled": { + "$ref": "#/definitions/settings/definitions/dogsledSettings" + }, + "dupl": { + "$ref": "#/definitions/settings/definitions/duplSettings" + }, + "embeddedstructfieldcheck": { + "$ref": "#/definitions/settings/definitions/embeddedstructfieldcheckSettings" + }, + "errcheck": { + "$ref": "#/definitions/settings/definitions/errcheckSettings" + }, + "errchkjson": { + "$ref": "#/definitions/settings/definitions/errchkjsonSettings" + }, + "errorlint": { + "$ref": "#/definitions/settings/definitions/errorlintSettings" + }, + "exhaustive": { + "$ref": "#/definitions/settings/definitions/exhaustiveSettings" + }, + "exhaustruct": { + "$ref": "#/definitions/settings/definitions/exhaustructSettings" + }, + "fatcontext": { + "$ref": "#/definitions/settings/definitions/fatcontextSettings" + }, + "forbidigo": { + "$ref": "#/definitions/settings/definitions/forbidigoSettings" + }, + "funcorder": { + "$ref": "#/definitions/settings/definitions/funcorderSettings" + }, + "funlen": { + "$ref": "#/definitions/settings/definitions/funlenSettings" + }, + "ginkgolinter": { + "$ref": "#/definitions/settings/definitions/ginkgolinterSettings" + }, + "gochecksumtype": { + "$ref": "#/definitions/settings/definitions/gochecksumtypeSettings" + }, + "gocognit": { + "$ref": "#/definitions/settings/definitions/gocognitSettings" + }, + "goconst": { + "$ref": "#/definitions/settings/definitions/goconstSettings" + }, + "gocritic": { + "$ref": "#/definitions/settings/definitions/gocriticSettings" + }, + "gocyclo": { + "$ref": "#/definitions/settings/definitions/gocycloSettings" + }, + "godoclint": { + "$ref": "#/definitions/settings/definitions/godoclintSettings" + }, + "godot": { + "$ref": "#/definitions/settings/definitions/godotSettings" + }, + "godox": { + "$ref": "#/definitions/settings/definitions/godoxSettings" + }, + "interfacebloat":{ + "$ref": "#/definitions/settings/definitions/interfacebloatSettings" + }, + "goheader": { + "$ref": "#/definitions/settings/definitions/goheaderSettings" + }, + "gomoddirectives": { + "$ref": "#/definitions/settings/definitions/gomoddirectivesSettings" + }, + "gomodguard": { + "$ref": "#/definitions/settings/definitions/gomodguardSettings" + }, + "gomodguard_v2": { + "$ref": "#/definitions/settings/definitions/gomodguardv2Settings" + }, + "gosec": { + "$ref": "#/definitions/settings/definitions/gosecSettings" + }, + "gosmopolitan": { + "$ref": "#/definitions/settings/definitions/gosmopolitanSettings" + }, + "govet": { + "$ref": "#/definitions/settings/definitions/govetSettings" + }, + "grouper": { + "$ref": "#/definitions/settings/definitions/grouperSettings" + }, + "iface": { + "$ref": "#/definitions/settings/definitions/ifaceSettings" + }, + "importas": { + "$ref": "#/definitions/settings/definitions/importasSettings" + }, + "inamedparam": { + "$ref": "#/definitions/settings/definitions/inamedparamSettings" + }, + "ineffassign": { + "$ref": "#/definitions/settings/definitions/ineffassignSettings" + }, + "iotamixing": { + "$ref": "#/definitions/settings/definitions/iotamixingSettings" + }, + "ireturn": { + "$ref": "#/definitions/settings/definitions/ireturnSettings" + }, + "lll": { + "$ref": "#/definitions/settings/definitions/lllSettings" + }, + "maintidx": { + "$ref": "#/definitions/settings/definitions/maintidxSettings" + }, + "makezero":{ + "$ref": "#/definitions/settings/definitions/makezeroSettings" + }, + "loggercheck": { + "$ref": "#/definitions/settings/definitions/loggercheckSettings" + }, + "misspell": { + "$ref": "#/definitions/settings/definitions/misspellSettings" + }, + "musttag": { + "$ref": "#/definitions/settings/definitions/musttagSettings" + }, + "nakedret": { + "$ref": "#/definitions/settings/definitions/nakedretSettings" + }, + "nestif": { + "$ref": "#/definitions/settings/definitions/nestifSettings" + }, + "nilnil": { + "$ref": "#/definitions/settings/definitions/nilnilSettings" + }, + "nlreturn": { + "$ref": "#/definitions/settings/definitions/nlreturnSettings" + }, + "mnd": { + "$ref": "#/definitions/settings/definitions/mndSettings" + }, + "modernize": { + "$ref": "#/definitions/settings/definitions/modernizeSettings" + }, + "nolintlint":{ + "$ref": "#/definitions/settings/definitions/nolintlintSettings" + }, + "reassign": { + "$ref": "#/definitions/settings/definitions/reassignSettings" + }, + "recvcheck": { + "$ref": "#/definitions/settings/definitions/recvcheckSettings" + }, + "nonamedreturns": { + "$ref": "#/definitions/settings/definitions/nonamedreturnsSettings" + }, + "paralleltest": { + "$ref": "#/definitions/settings/definitions/paralleltestSettings" + }, + "perfsprint": { + "$ref": "#/definitions/settings/definitions/perfsprintSettings" + }, + "prealloc": { + "$ref": "#/definitions/settings/definitions/preallocSettings" + }, + "predeclared": { + "$ref": "#/definitions/settings/definitions/predeclaredSettings" + }, + "promlinter": { + "$ref": "#/definitions/settings/definitions/promlinterSettings" + }, + "protogetter": { + "$ref": "#/definitions/settings/definitions/protogetterSettings" + }, + "revive": { + "$ref": "#/definitions/settings/definitions/reviveSettings" + }, + "rowserrcheck": { + "$ref": "#/definitions/settings/definitions/rowserrcheckSettings" + }, + "sloglint": { + "$ref": "#/definitions/settings/definitions/sloglintSettings" + }, + "spancheck": { + "$ref": "#/definitions/settings/definitions/spancheckSettings" + }, + "staticcheck":{ + "$ref": "#/definitions/settings/definitions/staticcheckSettings" + }, + "tagalign": { + "$ref": "#/definitions/settings/definitions/tagalignSettings" + }, + "tagliatelle": { + "$ref": "#/definitions/settings/definitions/tagliatelleSettings" + }, + "testifylint": { + "$ref": "#/definitions/settings/definitions/testifylintSettings" + }, + "testpackage": { + "$ref": "#/definitions/settings/definitions/testpackageSettings" + }, + "thelper": { + "$ref": "#/definitions/settings/definitions/thelperSettings" + }, + "usestdlibvars": { + "$ref": "#/definitions/settings/definitions/usestdlibvarsSettings" + }, + "usetesting": { + "$ref": "#/definitions/settings/definitions/usetestingSettings" + }, + "unconvert": { + "$ref": "#/definitions/settings/definitions/unconvertSettings" + }, + "unparam": { + "$ref": "#/definitions/settings/definitions/unparamSettings" + }, + "unqueryvet": { + "$ref": "#/definitions/settings/definitions/unqueryvetSettings" + }, + "unused": { + "$ref": "#/definitions/settings/definitions/unusedSettings" + }, + "varnamelen": { + "$ref": "#/definitions/settings/definitions/varnamelenSettings" + }, + "whitespace": { + "$ref": "#/definitions/settings/definitions/whitespaceSettings" + }, + "wrapcheck": { + "$ref": "#/definitions/settings/definitions/wrapcheckSettings" + }, + "wsl": { + "$ref": "#/definitions/settings/definitions/wslSettings" + }, + "wsl_v5": { + "$ref": "#/definitions/settings/definitions/wslSettingsV5" + }, + "copyloopvar": { + "$ref": "#/definitions/settings/definitions/copyloopvarSettings" + }, + "custom":{ + "$ref": "#/definitions/settings/definitions/customSettings" + } + } + }, + "exclusions":{ + "type": "object", + "additionalProperties": false, + "properties": { + "generated": { + "enum": ["strict", "lax", "disable"], + "default": "strict" + }, + "warn-unused": { + "type": "boolean", + "default": false + }, + "presets": { + "type": "array", + "items": { + "enum": [ + "comments", + "std-error-handling", + "common-false-positives", + "legacy" + ] + } + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "path-except": { + "type": "string" + }, + "linters": { + "type": "array", + "items": { + "$ref": "#/definitions/linter-names" + } + }, + "text": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "anyOf": [ + { "required": ["path"] }, + { "required": ["path-except"] }, + { "required": ["linters"] }, + { "required": ["text"] }, + { "required": ["source"] } + ] + } + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "paths-except": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "formatters": { + "type": "object", + "additionalProperties": false, + "properties": { + "enable": { + "description": "List of enabled formatters.", + "type": "array", + "items": { + "$ref": "#/definitions/formatter-names" + } + }, + "settings": { + "type": "object", + "additionalProperties": false, + "properties": { + "gci": { + "$ref": "#/definitions/settings/definitions/gciSettings" + }, + "gofmt": { + "$ref": "#/definitions/settings/definitions/gofmtSettings" + }, + "gofumpt": { + "$ref": "#/definitions/settings/definitions/gofumptSettings" + }, + "goimports": { + "$ref": "#/definitions/settings/definitions/goimportsSettings" + }, + "golines": { + "$ref": "#/definitions/settings/definitions/golinesSettings" + } + } + }, + "exclusions": { + "type": "object", + "additionalProperties": false, + "properties": { + "generated": { + "enum": ["strict", "lax", "disable"], + "default": "strict" + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "warn-unused": { + "type": "boolean", + "default": false + } + } + } + } + }, + "issues": { + "type": "object", + "additionalProperties": false, + "properties": { + "max-issues-per-linter": { + "description": "Maximum issues count per one linter. Set to 0 to disable.", + "type": "integer", + "default": 50, + "minimum": 0 + }, + "max-same-issues": { + "description": "Maximum count of issues with the same text. Set to 0 to disable.", + "type": "integer", + "default": 3, + "minimum": 0 + }, + "new": { + "description": "Show only new issues: if there are unstaged changes or untracked files, only those changes are analyzed, else only changes in HEAD~ are analyzed.", + "type": "boolean", + "default": false + }, + "new-from-merge-base": { + "description": "Show only new issues created after the best common ancestor (merge-base against HEAD).", + "type": "string" + }, + "new-from-rev": { + "description": "Show only new issues created after this git revision.", + "type": "string" + }, + "new-from-patch": { + "description": "Show only new issues created in git patch with this file path.", + "type": "string", + "examples": ["path/to/patch/file"] + }, + "fix": { + "description": "Apply the fixes detected by the linters and formatters (if it's supported by the linter).", + "type": "boolean", + "default": false + }, + "uniq-by-line": { + "description": "Make issues output unique by line.", + "type": "boolean", + "default": true + }, + "whole-files": { + "description": "Show issues in any part of update files (requires new-from-rev or new-from-patch).", + "type": "boolean", + "default": false + } + } + }, + "severity": { + "type": "object", + "additionalProperties": false, + "properties": { + "default": { + "description": "Set the default severity for issues. If severity rules are defined and the issues do not match or no severity is provided to the rule this will be the default severity applied. Severities should match the supported severity names of the selected out format.", + "type": "string", + "default": "" + }, + "rules": { + "description": "When a list of severity rules are provided, severity information will be added to lint issues. Severity rules have the same filtering capability as exclude rules except you are allowed to specify one matcher per severity rule.\nOnly affects out formats that support setting severity information.", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "severity": { + "type": "string" + }, + "path": { + "type": "string" + }, + "path-except": { + "type": "string" + }, + "linters": { + "type": "array", + "items": { + "$ref": "#/definitions/linter-names" + } + }, + "text": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "required": ["severity"], + "anyOf": [ + { "required": ["path"] }, + { "required": ["path-except"] }, + { "required": ["linters"] }, + { "required": ["text"] }, + { "required": ["source"] } + ] + }, + "default": [] + } + }, + "required": ["default"] + } + } +} diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/cache.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/cache.go index 9772841c9..094fe6303 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/cache.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/cache.go @@ -41,7 +41,7 @@ func newCacheCommand() *cacheCommand { Short: "Show cache status", Args: cobra.NoArgs, ValidArgsFunction: cobra.NoFileCompletions, - Run: c.executeStatus, + RunE: c.executeStatus, }, ) @@ -51,7 +51,10 @@ func newCacheCommand() *cacheCommand { } func (*cacheCommand) executeClean(_ *cobra.Command, _ []string) error { - cacheDir := cache.DefaultDir() + cacheDir, err := cache.DefaultDir() + if err != nil { + return err + } if err := os.RemoveAll(cacheDir); err != nil { return fmt.Errorf("failed to remove dir %s: %w", cacheDir, err) @@ -60,8 +63,11 @@ func (*cacheCommand) executeClean(_ *cobra.Command, _ []string) error { return nil } -func (*cacheCommand) executeStatus(_ *cobra.Command, _ []string) { - cacheDir := cache.DefaultDir() +func (*cacheCommand) executeStatus(_ *cobra.Command, _ []string) error { + cacheDir, err := cache.DefaultDir() + if err != nil { + return err + } _, _ = fmt.Fprintf(logutils.StdOut, "Dir: %s\n", cacheDir) @@ -69,6 +75,8 @@ func (*cacheCommand) executeStatus(_ *cobra.Command, _ []string) { if err == nil { _, _ = fmt.Fprintf(logutils.StdOut, "Size: %s\n", fsutils.PrettifyBytesCount(cacheSizeBytes)) } + + return nil } func dirSizeBytes(path string) (int64, error) { diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate.go index 69b6c7c42..d44804f51 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate.go @@ -1,14 +1,13 @@ package migrate import ( - "github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/ptr" "github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/versionone" "github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/versiontwo" ) func ToConfig(old *versionone.Config) *versiontwo.Config { return &versiontwo.Config{ - Version: ptr.Pointer("2"), + Version: new("2"), Linters: toLinters(old), Formatters: toFormatters(old), Issues: toIssues(old), diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_linters.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_linters.go index c070119b6..9284da376 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_linters.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_linters.go @@ -22,9 +22,9 @@ func toLinters(old *versionone.Config) versiontwo.Linters { func getDefaultName(old versionone.Linters) *string { switch { case ptr.Deref(old.DisableAll): - return ptr.Pointer("none") + return new("none") case ptr.Deref(old.EnableAll): - return ptr.Pointer("all") + return new("all") default: return nil // standard is the default } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_linters_exclusions.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_linters_exclusions.go index aac4d381a..18c3176a2 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_linters_exclusions.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_linters_exclusions.go @@ -21,7 +21,7 @@ func toExclusions(old *versionone.Config) versiontwo.LinterExclusions { func toExclusionGenerated(excludeGenerated *string) *string { if excludeGenerated == nil || ptr.Deref(excludeGenerated) == "" { - return ptr.Pointer("lax") + return new("lax") } if ptr.Deref(excludeGenerated) == "strict" { @@ -83,8 +83,8 @@ func toExclusionRules(old *versionone.Config) []versiontwo.ExcludeRule { for _, pattern := range old.Issues.ExcludePatterns { results = append(results, versiontwo.ExcludeRule{ BaseRule: versiontwo.BaseRule{ - Path: ptr.Pointer(`(.+)\.go$`), - Text: addPrefix(old.Issues, ptr.Pointer(pattern)), + Path: new(`(.+)\.go$`), + Text: addPrefix(old.Issues, new(pattern)), }, }) } @@ -102,7 +102,7 @@ func addPrefix(old versionone.Issues, s *string) *string { prefix = "(?i)" } - return ptr.Pointer(prefix + ptr.Deref(s)) + return new(prefix + ptr.Deref(s)) } func linterTestExclusions(old versionone.LintersSettings) []versiontwo.ExcludeRule { @@ -128,7 +128,7 @@ func linterTestExclusions(old versionone.LintersSettings) []versiontwo.ExcludeRu return []versiontwo.ExcludeRule{{ BaseRule: versiontwo.BaseRule{ Linters: excludedTestLinters, - Path: ptr.Pointer(`(.+)_test\.go`), + Path: new(`(.+)_test\.go`), }, }} } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_linters_settings.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_linters_settings.go index 4656842d6..b831143d2 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_linters_settings.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_linters_settings.go @@ -268,7 +268,7 @@ func toForbidigoSettings(old versionone.ForbidigoSettings) versiontwo.ForbidigoS } settings.Forbid = append(settings.Forbid, versiontwo.ForbidigoPattern{ - Pattern: ptr.Pointer(string(buffer)), + Pattern: new(string(buffer)), }) continue diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_output.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_output.go index e76f8e447..b41aa3bcb 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_output.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/migrate_output.go @@ -21,52 +21,52 @@ func toOutput(old *versionone.Config) versiontwo.Output { formats.Text.PrintLinterName = old.Output.PrintLinterName formats.Text.PrintIssuedLine = old.Output.PrintIssuedLine formats.Text.Colors = nil // color is true by default (flags). - formats.Text.Path = ptr.Pointer(defaultFormatPath(ptr.Deref(format.Path))) + formats.Text.Path = new(defaultFormatPath(ptr.Deref(format.Path))) case "line-number": formats.Text.PrintLinterName = old.Output.PrintLinterName formats.Text.PrintIssuedLine = old.Output.PrintIssuedLine - formats.Text.Colors = ptr.Pointer(false) - formats.Text.Path = ptr.Pointer(defaultFormatPath(ptr.Deref(format.Path))) + formats.Text.Colors = new(false) + formats.Text.Path = new(defaultFormatPath(ptr.Deref(format.Path))) case "json": - formats.JSON.Path = ptr.Pointer(defaultFormatPath(ptr.Deref(format.Path))) + formats.JSON.Path = new(defaultFormatPath(ptr.Deref(format.Path))) case "colored-tab": formats.Tab.PrintLinterName = old.Output.PrintLinterName formats.Tab.Colors = nil // Colors is true by default (flags). - formats.Tab.Path = ptr.Pointer(defaultFormatPath(ptr.Deref(format.Path))) + formats.Tab.Path = new(defaultFormatPath(ptr.Deref(format.Path))) case "tab": formats.Tab.PrintLinterName = old.Output.PrintLinterName - formats.Tab.Colors = ptr.Pointer(false) - formats.Tab.Path = ptr.Pointer(defaultFormatPath(ptr.Deref(format.Path))) + formats.Tab.Colors = new(false) + formats.Tab.Path = new(defaultFormatPath(ptr.Deref(format.Path))) case "html": - formats.HTML.Path = ptr.Pointer(defaultFormatPath(ptr.Deref(format.Path))) + formats.HTML.Path = new(defaultFormatPath(ptr.Deref(format.Path))) case "checkstyle": - formats.Checkstyle.Path = ptr.Pointer(defaultFormatPath(ptr.Deref(format.Path))) + formats.Checkstyle.Path = new(defaultFormatPath(ptr.Deref(format.Path))) case "code-climate": - formats.CodeClimate.Path = ptr.Pointer(defaultFormatPath(ptr.Deref(format.Path))) + formats.CodeClimate.Path = new(defaultFormatPath(ptr.Deref(format.Path))) case "junit-xml": formats.JUnitXML.Extended = nil // Extended is false by default. - formats.JUnitXML.Path = ptr.Pointer(defaultFormatPath(ptr.Deref(format.Path))) + formats.JUnitXML.Path = new(defaultFormatPath(ptr.Deref(format.Path))) case "junit-xml-extended": - formats.JUnitXML.Extended = ptr.Pointer(true) - formats.JUnitXML.Path = ptr.Pointer(defaultFormatPath(ptr.Deref(format.Path))) + formats.JUnitXML.Extended = new(true) + formats.JUnitXML.Path = new(defaultFormatPath(ptr.Deref(format.Path))) case "github-actions": // Ignored case "teamcity": - formats.TeamCity.Path = ptr.Pointer(defaultFormatPath(ptr.Deref(format.Path))) + formats.TeamCity.Path = new(defaultFormatPath(ptr.Deref(format.Path))) case "sarif": - formats.Sarif.Path = ptr.Pointer(defaultFormatPath(ptr.Deref(format.Path))) + formats.Sarif.Path = new(defaultFormatPath(ptr.Deref(format.Path))) } } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/ptr/ptr.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/ptr/ptr.go index b0c7974e0..eadb6d4fd 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/ptr/ptr.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/ptr/ptr.go @@ -8,5 +8,3 @@ func Deref[T any](v *T) T { return *v } - -func Pointer[T any](v T) *T { return &v } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/versionone/linters_settings.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/versionone/linters_settings.go index 3c641541c..f057a4c37 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/versionone/linters_settings.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/versionone/linters_settings.go @@ -246,7 +246,7 @@ type ForbidigoPattern struct { func (p *ForbidigoPattern) UnmarshalText(text []byte) error { // Validation happens when instantiating forbidigo. - p.patternString = ptr.Pointer(string(text)) + p.patternString = new(string(text)) return nil } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/versionone/output.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/versionone/output.go index a3d86fc1d..b3e29cafd 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/versionone/output.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/versionone/output.go @@ -2,8 +2,6 @@ package versionone import ( "strings" - - "github.com/golangci/golangci-lint/v2/pkg/commands/internal/migrate/ptr" ) type Output struct { @@ -28,8 +26,8 @@ func (p *OutputFormats) UnmarshalText(text []byte) error { format, path, _ := strings.Cut(item, ":") *p = append(*p, OutputFormat{ - Path: ptr.Pointer(path), - Format: ptr.Pointer(format), + Path: new(path), + Format: new(format), }) } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/run.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/run.go index 84c470152..a7efa264a 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/run.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/commands/run.go @@ -250,8 +250,7 @@ func (c *runCommand) execute(_ *cobra.Command, _ []string) { if err := c.runAndPrint(ctx); err != nil { c.log.Errorf("Running error: %s", err) if c.exitCode == exitcodes.Success { - var exitErr *exitcodes.ExitError - if errors.As(err, &exitErr) { + if exitErr, ok := errors.AsType[*exitcodes.ExitError](err); ok { c.exitCode = exitErr.Code } else { c.exitCode = exitcodes.Failure @@ -713,8 +712,7 @@ func computeGoModSalt() (string, error) { return "", fmt.Errorf("failed to read go.mod: %w", err) } - // NOTE: the variable `goModPath` is not used here to ensure getting the same hash, independently of the location, for the same content. - sum, err := dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) { + sum, err := dirhash.Hash1([]string{goModPath}, func(string) (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(data)), nil }) if err != nil { diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/config/base_loader.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/config/base_loader.go index 9ad332ac2..1ebfc9af8 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/config/base_loader.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/config/base_loader.go @@ -155,8 +155,7 @@ func (l *BaseLoader) getConfigSearchPaths() []string { func (l *BaseLoader) parseConfig() error { if err := l.viper.ReadInConfig(); err != nil { - var configFileNotFoundError viper.ConfigFileNotFoundError - if errors.As(err, &configFileNotFoundError) { + if _, ok := errors.AsType[viper.ConfigFileNotFoundError](err); ok { // Load configuration from flags only. err = l.viper.Unmarshal(l.cfg, customDecoderHook()) if err != nil { diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/config/formatters_settings.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/config/formatters_settings.go index d99354ba3..bb7ad18f5 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/config/formatters_settings.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/config/formatters_settings.go @@ -42,12 +42,21 @@ type GoFmtRewriteRule struct { } type GoFumptSettings struct { - ModulePath string `mapstructure:"module-path"` - ExtraRules bool `mapstructure:"extra-rules"` + ModulePath string `mapstructure:"module-path"` + Extra GoFumptExtra `mapstructure:"extra"` + + // Deprecated: use Extra instead. + ExtraRules bool `mapstructure:"extra-rules"` LangVersion string `mapstructure:"-"` } +type GoFumptExtra struct { + GroupParams bool `mapstructure:"group-params"` + ClotheReturns bool `mapstructure:"clothe-returns"` + BalanceCalls bool `mapstructure:"balance-calls"` +} + type GoImportsSettings struct { LocalPrefixes []string `mapstructure:"local-prefixes"` } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/config/linters_settings.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/config/linters_settings.go index 7f001da34..e85611973 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/config/linters_settings.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/config/linters_settings.go @@ -41,6 +41,11 @@ var defaultLintersSettings = LintersSettings{ ExplicitExhaustiveMap: false, ExplicitExhaustiveSwitch: false, }, + Fatcontext: FatcontextSettings{ + CheckStructPointers: false, + CheckLoops: true, + CheckFunctionLiterals: true, + }, Forbidigo: ForbidigoSettings{ ExcludeGodocExamples: true, }, @@ -63,6 +68,7 @@ var defaultLintersSettings = LintersSettings{ MinOccurrencesCount: 3, NumberMin: 3, NumberMax: 3, + ExcludeTypes: []string{"Call"}, IgnoreCalls: true, }, Gocritic: GoCriticSettings{ @@ -259,6 +265,7 @@ type LintersSettings struct { ErrorLint ErrorLintSettings `mapstructure:"errorlint"` Exhaustive ExhaustiveSettings `mapstructure:"exhaustive"` Exhaustruct ExhaustructSettings `mapstructure:"exhaustruct"` + Exhaustructv5 ExhaustructV5Settings `mapstructure:"exhaustruct_v5"` Fatcontext FatcontextSettings `mapstructure:"fatcontext"` Forbidigo ForbidigoSettings `mapstructure:"forbidigo"` FuncOrder FuncOrderSettings `mapstructure:"funcorder"` @@ -414,9 +421,10 @@ type DuplSettings struct { } type DupWordSettings struct { - Keywords []string `mapstructure:"keywords"` - Ignore []string `mapstructure:"ignore"` - CommentsOnly bool `mapstructure:"comments-only"` + Keywords []string `mapstructure:"keywords"` + Ignore []string `mapstructure:"ignore"` + CommentsOnly bool `mapstructure:"comments-only"` + SkipRawStrings bool `mapstructure:"skip-raw-strings"` } type EmbeddedStructFieldCheckSettings struct { @@ -462,6 +470,7 @@ type ExhaustiveSettings struct { DefaultCaseRequired bool `mapstructure:"default-case-required"` } +// Deprecated: use ExhaustructV5Settings instead. type ExhaustructSettings struct { Include []string `mapstructure:"include"` Exclude []string `mapstructure:"exclude"` @@ -471,8 +480,21 @@ type ExhaustructSettings struct { AllowEmptyDeclarations bool `mapstructure:"allow-empty-declarations"` } +type ExhaustructV5Settings struct { + EnforcePatterns []string `mapstructure:"enforce-patterns"` + IgnorePatterns []string `mapstructure:"ignore-patterns"` + OptionalPatterns []string `mapstructure:"optional-patterns"` + AllowEmpty bool `mapstructure:"allow-empty"` + AllowEmptyPatterns []string `mapstructure:"allow-empty-patterns"` + AllowEmptyReturns bool `mapstructure:"allow-empty-returns"` + AllowEmptyDeclarations bool `mapstructure:"allow-empty-declarations"` + ExplicitMode bool `mapstructure:"explicit-mode"` +} + type FatcontextSettings struct { - CheckStructPointers bool `mapstructure:"check-struct-pointers"` + CheckStructPointers bool `mapstructure:"check-struct-pointers"` + CheckLoops bool `mapstructure:"check-loops"` + CheckFunctionLiterals bool `mapstructure:"check-function-literals"` } type ForbidigoSettings struct { @@ -491,6 +513,7 @@ type FuncOrderSettings struct { Constructor bool `mapstructure:"constructor,omitempty"` StructMethod bool `mapstructure:"struct-method,omitempty"` Alphabetical bool `mapstructure:"alphabetical,omitempty"` + Function bool `mapstructure:"function,omitempty"` } type FunlenSettings struct { @@ -533,15 +556,21 @@ type GoConstSettings struct { ParseNumbers bool `mapstructure:"numbers"` NumberMin int `mapstructure:"min"` NumberMax int `mapstructure:"max"` - IgnoreCalls bool `mapstructure:"ignore-calls"` + ExcludeTypes []string `mapstructure:"exclude-types"` FindDuplicates bool `mapstructure:"find-duplicates"` EvalConstExpressions bool `mapstructure:"eval-const-expressions"` IgnoreFunctions []string `mapstructure:"ignore-functions"` + IgnoreMapKeys bool `mapstructure:"ignore-map-keys"` // This option cannot be managed with `linters.exclusions.rules`. // Because the linter counts occurrences across all files in the package. IgnoreTests bool `mapstructure:"ignore-tests"` + // NOTE(ldez): `ignore-calls` was here to have the same options as goconst as CLI. + // + // Deprecated: use ExcludeTypes instead. + IgnoreCalls bool `mapstructure:"ignore-calls"` + // Deprecated: use IgnoreStringValues instead. IgnoreStrings string `mapstructure:"ignore-strings"` } @@ -601,7 +630,9 @@ type GoHeaderSettings struct { type GoModDirectivesSettings struct { ReplaceAllowList []string `mapstructure:"replace-allow-list"` ReplaceLocal bool `mapstructure:"replace-local"` + ReplaceAllowAll bool `mapstructure:"replace-allow-all"` ExcludeForbidden bool `mapstructure:"exclude-forbidden"` + IgnoreForbidden bool `mapstructure:"ignore-forbidden"` RetractAllowNoExplanation bool `mapstructure:"retract-allow-no-explanation"` ToolchainForbidden bool `mapstructure:"toolchain-forbidden"` ToolchainPattern string `mapstructure:"toolchain-pattern"` @@ -827,7 +858,8 @@ type NoLintLintSettings struct { } type NoNamedReturnsSettings struct { - ReportErrorInDefer bool `mapstructure:"report-error-in-defer"` + ReportErrorInDefer bool `mapstructure:"report-error-in-defer"` + AllowUnusedNamedReturns bool `mapstructure:"allow-unused-named-returns"` } type ParallelTestSettings struct { diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goanalysis/runner_action_cache.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goanalysis/runner_action_cache.go index 1fafbca57..8fcacf6ec 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goanalysis/runner_action_cache.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goanalysis/runner_action_cache.go @@ -30,35 +30,74 @@ func (act *action) loadCachedFacts() bool { return true // no need to load facts } - return act.loadPersistedFacts() + if !act.loadPersistedFacts() { + return false + } + + // The cache only stores the facts a package produces about its own objects. + // The facts a package re-exports from its dependencies (see exportedFrom) are not persisted, + // to avoid duplicating them in every cache entry (which grows quadratically with the import graph). + // Instead, we rebuild them in memory here by inheriting from the dependencies, + // exactly like analyze() does for packages analyzed from source. + // This is safe because every dependency is fully analyzed (from cache or source) before this package is loaded, + // so their facts are already available. + act.inheritFactsFromDeps() + + return true }() + act.loadCachedFactsDone = true act.loadCachedFactsOk = res + return res } +// inheritFactsFromDeps rebuilds, in memory, +// the facts re-exported from this action's dependencies (vertical edges: same analyzer, different package), +// mirroring the inheritance performed by analyze() for packages analyzed from source. +func (act *action) inheritFactsFromDeps() { + for _, dep := range act.Deps { + if dep.Package == act.Package || dep.Analyzer != act.Analyzer { + continue + } + + inheritFacts(act, dep) + } +} + func (act *action) persistFactsToCache() error { analyzer := act.Analyzer + if len(analyzer.FactTypes) == 0 { return nil } - // Merge new facts into the package and persist them. + // Persist only the facts this package produces about its own objects. + // + // Facts about objects from other packages (inherited through this package's export data) are intentionally NOT persisted: + // doing so duplicates them in every cache entry along the import graph and makes the cache grow quadratically. + // When a package is restored from the cache, + // those re-exported facts are rebuilt in memory by inheriting from its dependencies (see inheritFactsFromDeps). + var facts []Fact + for key, fact := range act.packageFacts { if key.pkg != act.Package.Types { - // The fact is from inherited facts from another package + // The fact is inherited from another package. continue } + facts = append(facts, Fact{ Path: "", Fact: fact, }) } + for key, fact := range act.objectFacts { obj := key.obj + if obj.Pkg() != act.Package.Types { - // The fact is from inherited facts from another package + // The fact is inherited from another package. continue } @@ -89,6 +128,7 @@ func (act *action) loadPersistedFacts() bool { } factsCacheDebugf("No cached facts for package %q and analyzer %s", act.Package.Name, act.Analyzer.Name) + return false } @@ -100,6 +140,7 @@ func (act *action) loadPersistedFacts() bool { act.packageFacts[key] = f.Fact continue } + obj, err := objectpath.Object(act.Package.Types, objectpath.Path(f.Path)) if err != nil { // Be lenient about these errors. diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goanalysis/runner_loadingpackage.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goanalysis/runner_loadingpackage.go index e01d3eaa2..f918be638 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goanalysis/runner_loadingpackage.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goanalysis/runner_loadingpackage.go @@ -531,8 +531,8 @@ func sizeOfReflectValueTreeBytes(rv reflect.Value, visitedPtrs map[uintptr]struc return sizeOfReflectValueTreeBytes(rv.Elem(), visitedPtrs) case reflect.Struct: ret := 0 - for i := range rv.NumField() { - ret += sizeOfReflectValueTreeBytes(rv.Field(i), visitedPtrs) + for _, field := range rv.Fields() { + ret += sizeOfReflectValueTreeBytes(field, visitedPtrs) } return ret case reflect.Slice, reflect.Array, reflect.Chan: diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goformatters/gci/internal/section/standard_list.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goformatters/gci/internal/section/standard_list.go index a7787409e..120acb13e 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goformatters/gci/internal/section/standard_list.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goformatters/gci/internal/section/standard_list.go @@ -1,6 +1,6 @@ package section -// Code generated based on go1.26.0 X:boringcrypto,arenas,jsonv2,runtimesecret. DO NOT EDIT. +// Code generated based on go1.27.0 Thu May 28 17:10:42 2026 -0700 X:boringcrypto,arenas,runtimesecret. DO NOT EDIT. var standardPackages = map[string]struct{}{ "archive/tar": {}, @@ -33,6 +33,7 @@ var standardPackages = map[string]struct{}{ "crypto/hmac": {}, "crypto/hpke": {}, "crypto/md5": {}, + "crypto/mldsa": {}, "crypto/mlkem": {}, "crypto/mlkem/mlkemtest": {}, "crypto/pbkdf2": {}, @@ -185,5 +186,6 @@ var standardPackages = map[string]struct{}{ "unicode/utf8": {}, "unique": {}, "unsafe": {}, + "uuid": {}, "weak": {}, } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goformatters/gofmt/gofmt.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goformatters/gofmt/gofmt.go index 4d5ff8632..38660a595 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goformatters/gofmt/gofmt.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goformatters/gofmt/gofmt.go @@ -1,7 +1,7 @@ package gofmt import ( - "github.com/golangci/gofmt/gofmt" + "github.com/golangci/gofmt" "github.com/golangci/golangci-lint/v2/pkg/config" ) diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goformatters/gofumpt/gofumpt.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goformatters/gofumpt/gofumpt.go index d8f5f2f73..1e3f71002 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goformatters/gofumpt/gofumpt.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/goformatters/gofumpt/gofumpt.go @@ -6,6 +6,7 @@ import ( gofumpt "mvdan.cc/gofumpt/format" "github.com/golangci/golangci-lint/v2/pkg/config" + "github.com/golangci/golangci-lint/v2/pkg/goformatters/internal" ) const Name = "gofumpt" @@ -18,10 +19,19 @@ func New(settings *config.GoFumptSettings, goVersion string) *Formatter { var options gofumpt.Options if settings != nil { + if settings.ExtraRules { + internal.FormatterLogger.Warnf("gofumpt: `extra-rules` is deprecated, please use `extra.group-params` instead.") + } + options = gofumpt.Options{ LangVersion: getLangVersion(goVersion), ModulePath: settings.ModulePath, ExtraRules: settings.ExtraRules, + Extra: gofumpt.Extra{ + GroupParams: settings.Extra.GroupParams, + ClotheReturns: settings.Extra.ClotheReturns, + BalanceCalls: settings.Extra.BalanceCalls, + }, } } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/dupword/dupword.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/dupword/dupword.go index 0308534e3..08ae18689 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/dupword/dupword.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/dupword/dupword.go @@ -14,9 +14,10 @@ func New(settings *config.DupWordSettings) *goanalysis.Linter { if settings != nil { cfg = map[string]any{ - "keyword": strings.Join(settings.Keywords, ","), - "ignore": strings.Join(settings.Ignore, ","), - "comments-only": settings.CommentsOnly, + "keyword": strings.Join(settings.Keywords, ","), + "ignore": strings.Join(settings.Ignore, ","), + "comments-only": settings.CommentsOnly, + "skip-raw-strings": settings.SkipRawStrings, } } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/exhaustruct/exhaustruct_v5.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/exhaustruct/exhaustruct_v5.go new file mode 100644 index 000000000..eabfe1ae4 --- /dev/null +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/exhaustruct/exhaustruct_v5.go @@ -0,0 +1,34 @@ +package exhaustruct + +import ( + exhaustruct "dev.gaijin.team/go/exhaustruct/v5/analyzer" + + "github.com/golangci/golangci-lint/v2/pkg/config" + "github.com/golangci/golangci-lint/v2/pkg/goanalysis" + "github.com/golangci/golangci-lint/v2/pkg/golinters/internal" +) + +func NewV5(settings *config.ExhaustructV5Settings) *goanalysis.Linter { + cfg := exhaustruct.Config{} + + if settings != nil { + cfg.EnforcePatterns = settings.EnforcePatterns + cfg.IgnorePatterns = settings.IgnorePatterns + cfg.OptionalPatterns = settings.OptionalPatterns + cfg.AllowEmpty = settings.AllowEmpty + cfg.AllowEmptyPatterns = settings.AllowEmptyPatterns + cfg.AllowEmptyReturns = settings.AllowEmptyReturns + cfg.AllowEmptyDeclarations = settings.AllowEmptyDeclarations + cfg.ExplicitMode = settings.ExplicitMode + } + + analyzer, err := exhaustruct.NewAnalyzerWithConfig(cfg) + if err != nil { + internal.LinterLogger.Fatalf("exhaustruct configuration: %v", err) + } + + return goanalysis. + NewLinterFromAnalyzer(analyzer). + WithVersion(5). //nolint:mnd // It's the linter version. + WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/fatcontext/fatcontext.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/fatcontext/fatcontext.go index 58933c660..656fd4230 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/fatcontext/fatcontext.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/fatcontext/fatcontext.go @@ -12,7 +12,9 @@ func New(settings *config.FatcontextSettings) *goanalysis.Linter { if settings != nil { cfg = map[string]any{ - analyzer.FlagCheckStructPointers: settings.CheckStructPointers, + analyzer.FlagCheckStructPointers: settings.CheckStructPointers, + analyzer.FlagCheckLoops: settings.CheckLoops, + analyzer.FlagCheckFunctionLiterals: settings.CheckFunctionLiterals, } } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/funcorder/funcorder.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/funcorder/funcorder.go index 06400753e..aa9188963 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/funcorder/funcorder.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/funcorder/funcorder.go @@ -15,6 +15,7 @@ func New(settings *config.FuncOrderSettings) *goanalysis.Linter { analyzer.ConstructorCheckName: settings.Constructor, analyzer.StructMethodCheckName: settings.StructMethod, analyzer.AlphabeticalCheckName: settings.Alphabetical, + analyzer.FunctionCheckName: settings.Function, } } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/goconst/goconst.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/goconst/goconst.go index be2312247..4320c44c9 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/goconst/goconst.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/goconst/goconst.go @@ -2,6 +2,7 @@ package goconst import ( "fmt" + "strings" "sync" goconstAPI "github.com/jgautheron/goconst" @@ -48,7 +49,7 @@ func New(settings *config.GoConstSettings) *goanalysis.Linter { } func runGoconst(pass *analysis.Pass, settings *config.GoConstSettings) ([]*goanalysis.Issue, error) { - cfg := goconstAPI.Config{ + cfg := &goconstAPI.Config{ IgnoreStrings: settings.IgnoreStringValues, IgnoreTests: settings.IgnoreTests, MatchWithConstants: settings.MatchWithConstants, @@ -61,13 +62,27 @@ func runGoconst(pass *analysis.Pass, settings *config.GoConstSettings) ([]*goana FindDuplicates: settings.FindDuplicates, EvalConstExpressions: settings.EvalConstExpressions, IgnoreFunctions: settings.IgnoreFunctions, + IgnoreMapKeys: settings.IgnoreMapKeys, } - if settings.IgnoreCalls { - cfg.ExcludeTypes[goconstAPI.Call] = true + // There is no deprecation log for `IgnoreCalls` because the default is true. + // As the default of `ExcludeTypes` contains `call`, + // setting `IgnoreCalls` to false is the only way for a user to explicitly use the value `call`. + // TODO(ldez): `IgnoreCalls` must be removed if the next major version. + if !settings.IgnoreCalls && len(settings.ExcludeTypes) == 1 && strings.EqualFold(settings.ExcludeTypes[0], "call") { + settings.ExcludeTypes = nil } - lintIssues, err := goconstAPI.Run(pass.Files, pass.Fset, pass.TypesInfo, &cfg) + for _, k := range settings.ExcludeTypes { + typ, err := toType(k) + if err != nil { + return nil, err + } + + cfg.ExcludeTypes[typ] = true + } + + lintIssues, err := goconstAPI.Run(pass.Files, pass.Fset, pass.TypesInfo, cfg) if err != nil { return nil, err } @@ -110,3 +125,28 @@ func runGoconst(pass *analysis.Pass, settings *config.GoConstSettings) ([]*goana return res, nil } + +func toType(v string) (goconstAPI.Type, error) { + switch strings.ToLower(v) { + case "assignment": + return goconstAPI.Assignment, nil + + case "binary": + return goconstAPI.Binary, nil + + case "case": + return goconstAPI.Case, nil + + case "return": + return goconstAPI.Return, nil + + case "call": + return goconstAPI.Call, nil + + case "compositelit": + return goconstAPI.CompositeLit, nil + + default: + return 0, fmt.Errorf("unknown type %s", v) + } +} diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/godoclint/godoclint.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/godoclint/godoclint.go index 0d0171251..26c98adbc 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/godoclint/godoclint.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/godoclint/godoclint.go @@ -46,18 +46,18 @@ func New(settings *config.GodoclintSettings) *goanalysis.Linter { Disable: settings.Disable, Options: &glconfig.PlainRuleOptions{ MaxLenLength: settings.Options.MaxLen.Length, - MaxLenIncludeTests: pointer(true), + MaxLenIncludeTests: new(true), MaxLenIgnorePatterns: []string{`^\+kubebuilder:`}, - PkgDocIncludeTests: pointer(false), - SinglePkgDocIncludeTests: pointer(true), - RequirePkgDocIncludeTests: pointer(false), - RequireDocIncludeTests: pointer(true), + PkgDocIncludeTests: new(false), + SinglePkgDocIncludeTests: new(true), + RequirePkgDocIncludeTests: new(false), + RequireDocIncludeTests: new(true), RequireDocIgnoreExported: settings.Options.RequireDoc.IgnoreExported, RequireDocIgnoreUnexported: settings.Options.RequireDoc.IgnoreUnexported, - StartWithNameIncludeTests: pointer(false), + StartWithNameIncludeTests: new(false), StartWithNameIncludeUnexported: settings.Options.StartWithName.IncludeUnexported, - RequireStdlibDoclinkIncludeTests: pointer(true), - NoUnusedLinkIncludeTests: pointer(true), + RequireStdlibDoclinkIncludeTests: new(true), + NoUnusedLinkIncludeTests: new(true), }, } @@ -107,8 +107,6 @@ func checkSettings(settings *config.GodoclintSettings) error { return nil } -func pointer[T any](v T) *T { return &v } - func deref[T any](v *T) T { if v == nil { var zero T diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/gofumpt/gofumpt.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/gofumpt/gofumpt.go index 1ee7c833a..69f617997 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/gofumpt/gofumpt.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/gofumpt/gofumpt.go @@ -13,7 +13,7 @@ func New(settings *config.GoFumptSettings) *goanalysis.Linter { NewLinterFromAnalyzer( goformatters.NewAnalyzer( internal.LinterLogger.Child(gofumptbase.Name), - "Check if code and import statements are formatted, with additional rules.", + "Enforce a stricter format than gofmt, while being backwards compatible.", gofumptbase.New(settings, settings.LangVersion), ), ). diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/gomoddirectives/gomoddirectives.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/gomoddirectives/gomoddirectives.go index 817b5498e..71c42c4ea 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/gomoddirectives/gomoddirectives.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/gomoddirectives/gomoddirectives.go @@ -24,8 +24,10 @@ func New(settings *config.GoModDirectivesSettings) *goanalysis.Linter { if settings != nil { opts.ReplaceAllowLocal = settings.ReplaceLocal opts.ReplaceAllowList = settings.ReplaceAllowList + opts.ReplaceAllowAll = settings.ReplaceAllowAll opts.RetractAllowNoExplanation = settings.RetractAllowNoExplanation opts.ExcludeForbidden = settings.ExcludeForbidden + opts.IgnoreForbidden = settings.IgnoreForbidden opts.ToolchainForbidden = settings.ToolchainForbidden opts.ToolForbidden = settings.ToolForbidden opts.GoDebugForbidden = settings.GoDebugForbidden diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/iface/iface.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/iface/iface.go index 0a4a38abc..03330fdac 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/iface/iface.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/iface/iface.go @@ -7,6 +7,7 @@ import ( "github.com/uudashr/iface/opaque" "github.com/uudashr/iface/unexported" "github.com/uudashr/iface/unused" + "github.com/uudashr/iface/unusedmethod" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/v2/pkg/config" @@ -29,10 +30,11 @@ func New(settings *config.IfaceSettings) *goanalysis.Linter { func analyzersFromSettings(settings *config.IfaceSettings) []*analysis.Analyzer { allAnalyzers := map[string]*analysis.Analyzer{ - "identical": identical.Analyzer, - "unused": unused.Analyzer, - "opaque": opaque.Analyzer, - "unexported": unexported.Analyzer, + "identical": identical.Analyzer, + "unused": unused.Analyzer, + "opaque": opaque.Analyzer, + "unexported": unexported.Analyzer, + "unusedmethod": unusedmethod.Analyzer, } if settings == nil || len(settings.Enable) == 0 { diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/modernize/modernize.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/modernize/modernize.go index 97825c07e..6878c01e2 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/modernize/modernize.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/modernize/modernize.go @@ -5,6 +5,7 @@ import ( "github.com/golangci/golangci-lint/v2/pkg/config" "github.com/golangci/golangci-lint/v2/pkg/goanalysis" + "github.com/golangci/golangci-lint/v2/pkg/golinters/internal" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/modernize" @@ -16,6 +17,15 @@ func New(settings *config.ModernizeSettings) *goanalysis.Linter { if settings == nil { analyzers = modernize.Suite } else { + for _, name := range settings.Disable { + switch name { + case "fmtappendf": + internal.LinterLogger.Warnf("%s has been removed from the modernize suite", name) + case "waitgroup": + internal.LinterLogger.Warnf("%s has been renamed to 'waitgroupgo'", name) + } + } + for _, analyzer := range modernize.Suite { if slices.Contains(settings.Disable, analyzer.Name) { continue diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/nonamedreturns/nonamedreturns.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/nonamedreturns/nonamedreturns.go index 4149ef818..0dd71e28b 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/nonamedreturns/nonamedreturns.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/golinters/nonamedreturns/nonamedreturns.go @@ -12,7 +12,8 @@ func New(settings *config.NoNamedReturnsSettings) *goanalysis.Linter { if settings != nil { cfg = map[string]any{ - analyzer.FlagReportErrorInDefer: settings.ReportErrorInDefer, + analyzer.FlagReportErrorInDefer: settings.ReportErrorInDefer, + analyzer.FlagAllowUnusedNamedReturns: settings.AllowUnusedNamedReturns, } } diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/lint/lintersdb/builder_linter.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/lint/lintersdb/builder_linter.go index 2e9515b27..d2b6ac047 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/lint/lintersdb/builder_linter.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/lint/lintersdb/builder_linter.go @@ -257,6 +257,13 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { linter.NewConfig(exhaustruct.New(&cfg.Linters.Settings.Exhaustruct)). WithSince("v1.46.0"). + DeprecatedWarning("new major version.", "v2.13.0", + linter.Replacement("exhaustruct_v5", nil, &cfg.Linters.Settings.Exhaustruct)). + WithLoadForGoAnalysis(). + WithURL("https://github.com/GaijinEntertainment/go-exhaustruct"), + + linter.NewConfig(exhaustruct.NewV5(&cfg.Linters.Settings.Exhaustructv5)). + WithSince("v2.13.0"). WithLoadForGoAnalysis(). WithURL("https://github.com/GaijinEntertainment/go-exhaustruct"), @@ -373,7 +380,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { linter.NewConfig(golines.New(&cfg.Linters.Settings.GoLines)). WithSince("v2.0.0"). WithAutoFix(). - WithURL("https://github.com/segmentio/golines"), + WithURL("https://github.com/golangci/golines"), linter.NewConfig(goheader.New(&cfg.Linters.Settings.Goheader, placeholderReplacer)). WithSince("v1.28.0"). @@ -391,6 +398,7 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { linter.NewConfig(modernize.New(&cfg.Linters.Settings.Modernize)). WithSince("v2.6.0"). + WithAutoFix(). WithLoadForGoAnalysis(). WithURL("https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize"), diff --git a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/lint/lintersdb/builder_plugin_go.go b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/lint/lintersdb/builder_plugin_go.go index 005ca6169..61105da4c 100644 --- a/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/lint/lintersdb/builder_plugin_go.go +++ b/tools/vendor/github.com/golangci/golangci-lint/v2/pkg/lint/lintersdb/builder_plugin_go.go @@ -92,7 +92,9 @@ func (b *PluginGoBuilder) getAnalyzerPlugin(cfg *config.Config, path string, set path = filepath.Join(basePath, path) } + //nolint:staticcheck,nolintlint // Ignore because the implementation on Windows returns nil plug, err := plugin.Open(path) + //nolint:staticcheck,nolintlint // Ignore because the implementation on Windows returns nil if err != nil { return nil, err } @@ -106,7 +108,9 @@ func (b *PluginGoBuilder) getAnalyzerPlugin(cfg *config.Config, path string, set } func (b *PluginGoBuilder) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, error) { + //nolint:staticcheck,nolintlint // Ignore because the implementation on Windows returns nil symbol, err := plug.Lookup("New") + //nolint:staticcheck,nolintlint // Ignore because the implementation on Windows returns nil if err != nil { analyzers, errP := b.lookupAnalyzerPlugin(plug) if errP != nil { @@ -126,7 +130,9 @@ func (b *PluginGoBuilder) lookupPlugin(plug *plugin.Plugin, settings any) ([]*an } func (b *PluginGoBuilder) lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyzer, error) { + //nolint:staticcheck,nolintlint // Ignore because the implementation on Windows returns nil symbol, err := plug.Lookup("AnalyzerPlugin") + //nolint:staticcheck,nolintlint // Ignore because the implementation on Windows returns nil if err != nil { return nil, err } diff --git a/tools/vendor/github.com/mgechev/revive/config/config.go b/tools/vendor/github.com/mgechev/revive/config/config.go index d01f409c5..62b88408d 100644 --- a/tools/vendor/github.com/mgechev/revive/config/config.go +++ b/tools/vendor/github.com/mgechev/revive/config/config.go @@ -5,10 +5,14 @@ import ( "errors" "fmt" "os" + "reflect" + "slices" + "strings" "github.com/BurntSushi/toml" "github.com/mgechev/revive/formatter" + internalconfig "github.com/mgechev/revive/internal/config" "github.com/mgechev/revive/lint" "github.com/mgechev/revive/rule" ) @@ -119,8 +123,41 @@ var allRules = append([]lint.Rule{ &rule.EpochNamingRule{}, &rule.UseSlicesSort{}, &rule.PackageNamingRule{}, + &rule.MultilineIfInitRule{}, + &rule.MarshalReceiverRule{}, }, defaultRules...) +// AllRules returns a copy of the list of all rules registered in revive. +func AllRules() []lint.Rule { + return slices.Clone(allRules) +} + +// DefaultRules returns a copy of the list of rules that are enabled by default. +func DefaultRules() []lint.Rule { + return slices.Clone(defaultRules) +} + +// EnabledRules returns the rules that are enabled in the given configuration. +func EnabledRules(config *lint.Config) []lint.Rule { + if config == nil { + return nil + } + rulesByName := make(map[string]lint.Rule, len(allRules)) + for _, r := range allRules { + rulesByName[r.Name()] = r + } + var rules []lint.Rule + for name, c := range config.Rules { + if c.Disabled { + continue + } + if r, ok := rulesByName[actualRuleName(name)]; ok { + rules = append(rules, r) + } + } + return rules +} + // allFormatters is a list of all available formatters to output the linting results. // Keep the list sorted and in sync with available formatters in README.md. var allFormatters = []lint.Formatter{ @@ -190,10 +227,31 @@ func actualRuleName(name string) string { } func parseConfig(data []byte, config *lint.Config) error { - err := toml.Unmarshal(data, config) + // Decode the top-level keys as primitives first so each option can be matched to its config field + // regardless of the spelling used in the file (camelCase, kebab-case or lowercase). + primitives := map[string]toml.Primitive{} + md, err := toml.Decode(string(data), &primitives) if err != nil { return fmt.Errorf("cannot parse the config file: %w", err) } + + fields := configFieldsByNormalizedName(config) + seen := make(map[string]string, len(primitives)) + for key, primitive := range primitives { + normalized := internalconfig.NormalizeOption(key) + field, ok := fields[normalized] + if !ok { + continue // ignore unknown options, as toml.Unmarshal does + } + if other, dup := seen[normalized]; dup { + return fmt.Errorf("cannot parse the config file: options %q and %q refer to the same option", other, key) + } + seen[normalized] = key + if err := md.PrimitiveDecode(primitive, field.Addr().Interface()); err != nil { + return fmt.Errorf("cannot parse the config file: %w", err) + } + } + for k, r := range config.Rules { err := r.Initialize() if err != nil { @@ -205,14 +263,33 @@ func parseConfig(data []byte, config *lint.Config) error { return nil } +// configFieldsByNormalizedName maps the normalized name of each config option to the corresponding struct field, +// so an option can be looked up regardless of the casing or hyphenation used in the config file. +func configFieldsByNormalizedName(config *lint.Config) map[string]reflect.Value { + v := reflect.ValueOf(config).Elem() + t := v.Type() + fields := make(map[string]reflect.Value, t.NumField()) + for i := range t.NumField() { + tag := t.Field(i).Tag.Get("toml") + if tag == "" { + continue + } + name, _, _ := strings.Cut(tag, ",") + fields[internalconfig.NormalizeOption(name)] = v.Field(i) + } + return fields +} + func validateConfig(config *lint.Config) error { if config.EnableAllRules && config.EnableDefaultRules { - return errors.New("config options enableAllRules and enableDefaultRules cannot be combined") + return errors.New("config options enable-all-rules and enable-default-rules cannot be combined") } return nil } -func normalizeConfig(config *lint.Config) { +// Normalize fills in default rule entries (according to the EnableAllRules / EnableDefaultRules options) +// and propagates the configured severity to rules and directives that don't define their own. +func Normalize(config *lint.Config) { if len(config.Rules) == 0 { config.Rules = map[string]lint.RuleConfig{} } @@ -249,14 +326,15 @@ func normalizeConfig(config *lint.Config) { } } -const defaultConfidence = 0.8 +// DefaultConfidence is the default confidence level for revive's linter. +const DefaultConfidence = 0.8 // GetConfig yields the configuration. func GetConfig(configPath string) (*lint.Config, error) { config := &lint.Config{} switch { case configPath != "": - config.Confidence = defaultConfidence + config.Confidence = DefaultConfidence data, err := os.ReadFile(configPath) //nolint:gosec // ignore G304: potential file inclusion via variable if err != nil { return nil, errors.New("cannot read the config file") @@ -267,14 +345,14 @@ func GetConfig(configPath string) (*lint.Config, error) { } default: // no configuration provided - config = defaultConfig() + config = Default() } if err := validateConfig(config); err != nil { return nil, err } - normalizeConfig(config) + Normalize(config) return config, nil } @@ -291,9 +369,10 @@ func GetFormatter(formatterName string) (lint.Formatter, error) { return f, nil } -func defaultConfig() *lint.Config { +// Default returns the default linter configuration, used when no configuration is provided. +func Default() *lint.Config { defaultConfig := lint.Config{ - Confidence: defaultConfidence, + Confidence: DefaultConfidence, Severity: lint.SeverityWarning, Rules: map[string]lint.RuleConfig{}, } diff --git a/tools/vendor/github.com/mgechev/revive/formatter/checkstyle.go b/tools/vendor/github.com/mgechev/revive/formatter/checkstyle.go index 1df1f5573..331b047c3 100644 --- a/tools/vendor/github.com/mgechev/revive/formatter/checkstyle.go +++ b/tools/vendor/github.com/mgechev/revive/formatter/checkstyle.go @@ -32,15 +32,12 @@ type issue struct { func (*Checkstyle) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { issues := map[string][]issue{} for failure := range failures { - buf := new(bytes.Buffer) - xml.Escape(buf, []byte(failure.Failure)) - what := buf.String() iss := issue{ Line: failure.Position.Start.Line, Col: failure.Position.Start.Column, - What: what, + What: failure.Failure, Confidence: failure.Confidence, - Severity: severity(config, failure), + Severity: failure.SeverityFor(&config), RuleName: failure.RuleName, } fn := failure.Filename() @@ -50,7 +47,7 @@ func (*Checkstyle) Format(failures <-chan lint.Failure, config lint.Config) (str issues[fn] = append(issues[fn], iss) } - t, err := plain.New("revive").Parse(checkstyleTemplate) + t, err := plain.New("revive").Funcs(plain.FuncMap{"escape": xmlEscape}).Parse(checkstyleTemplate) if err != nil { return "", err } @@ -65,12 +62,22 @@ func (*Checkstyle) Format(failures <-chan lint.Failure, config lint.Config) (str return buf.String(), nil } +// xmlEscape escapes s so that it can be safely interpolated +// into the XML attributes of the checkstyle template. +// It is registered as the "escape" function of the template. +func xmlEscape(s string) string { + buf := new(bytes.Buffer) + xml.Escape(buf, []byte(s)) + return buf.String() +} + const checkstyleTemplate = ` {{- range $k, $v := . }} - + {{- range $i, $issue := $v }} - + {{- end }} {{- end }} diff --git a/tools/vendor/github.com/mgechev/revive/formatter/friendly.go b/tools/vendor/github.com/mgechev/revive/formatter/friendly.go index cb1afcb3d..c170e1dc8 100644 --- a/tools/vendor/github.com/mgechev/revive/formatter/friendly.go +++ b/tools/vendor/github.com/mgechev/revive/formatter/friendly.go @@ -35,7 +35,7 @@ func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (str warningEmoji := color.YellowString("⚠") errorEmoji := color.RedString("✘") for failure := range failures { - sev := severity(config, failure) + sev := failure.SeverityFor(&config) firstCol := warningEmoji if sev == lint.SeverityError { firstCol = errorEmoji diff --git a/tools/vendor/github.com/mgechev/revive/formatter/json.go b/tools/vendor/github.com/mgechev/revive/formatter/json.go index 46a61980c..c3feea615 100644 --- a/tools/vendor/github.com/mgechev/revive/formatter/json.go +++ b/tools/vendor/github.com/mgechev/revive/formatter/json.go @@ -29,7 +29,7 @@ func (*JSON) Format(failures <-chan lint.Failure, config lint.Config) (string, e var slice []jsonObject for failure := range failures { obj := jsonObject{} - obj.Severity = severity(config, failure) + obj.Severity = failure.SeverityFor(&config) obj.Failure = failure slice = append(slice, obj) } diff --git a/tools/vendor/github.com/mgechev/revive/formatter/ndjson.go b/tools/vendor/github.com/mgechev/revive/formatter/ndjson.go index f80b5bcbc..8ca416545 100644 --- a/tools/vendor/github.com/mgechev/revive/formatter/ndjson.go +++ b/tools/vendor/github.com/mgechev/revive/formatter/ndjson.go @@ -24,7 +24,7 @@ func (*NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string, enc := json.NewEncoder(&buf) for failure := range failures { obj := jsonObject{} - obj.Severity = severity(config, failure) + obj.Severity = failure.SeverityFor(&config) obj.Failure = failure err := enc.Encode(obj) if err != nil { diff --git a/tools/vendor/github.com/mgechev/revive/formatter/severity.go b/tools/vendor/github.com/mgechev/revive/formatter/severity.go deleted file mode 100644 index a43bf3192..000000000 --- a/tools/vendor/github.com/mgechev/revive/formatter/severity.go +++ /dev/null @@ -1,13 +0,0 @@ -package formatter - -import "github.com/mgechev/revive/lint" - -func severity(config lint.Config, failure lint.Failure) lint.Severity { - if config, ok := config.Rules[failure.RuleName]; ok && config.Severity == lint.SeverityError { - return lint.SeverityError - } - if config, ok := config.Directives[failure.RuleName]; ok && config.Severity == lint.SeverityError { - return lint.SeverityError - } - return lint.SeverityWarning -} diff --git a/tools/vendor/github.com/mgechev/revive/formatter/stylish.go b/tools/vendor/github.com/mgechev/revive/formatter/stylish.go index 100a7927b..54ad98020 100644 --- a/tools/vendor/github.com/mgechev/revive/formatter/stylish.go +++ b/tools/vendor/github.com/mgechev/revive/formatter/stylish.go @@ -42,7 +42,7 @@ func (*Stylish) Format(failures <-chan lint.Failure, config lint.Config) (string for f := range failures { total++ - currentType := severity(config, f) + currentType := f.SeverityFor(&config) if currentType == lint.SeverityError { totalErrors++ } diff --git a/tools/vendor/github.com/mgechev/revive/internal/astutils/ast_utils.go b/tools/vendor/github.com/mgechev/revive/internal/astutils/ast_utils.go index b0fbc5a0d..5dc5d24b0 100644 --- a/tools/vendor/github.com/mgechev/revive/internal/astutils/ast_utils.go +++ b/tools/vendor/github.com/mgechev/revive/internal/astutils/ast_utils.go @@ -9,6 +9,7 @@ import ( "go/ast" "go/printer" "go/token" + "go/types" "regexp" "slices" ) @@ -123,6 +124,22 @@ func IsPkgDotName(expr ast.Expr, pkg, name string) bool { return ok && IsIdent(sel.X, pkg) && IsIdent(sel.Sel, name) } +// IsPointerToPkgDotType returns true if typ is a pointer to the named type pkgPath.typeName. +func IsPointerToPkgDotType(typ types.Type, pkgPath, typeName string) bool { + ptrType, ok := typ.(*types.Pointer) + if !ok { + return false + } + + namedType, ok := ptrType.Elem().(*types.Named) + if !ok { + return false + } + + obj := namedType.Obj() + return obj != nil && obj.Pkg() != nil && obj.Pkg().Path() == pkgPath && obj.Name() == typeName +} + // PickNodes yields a list of nodes by picking them from a sub-ast with root node n. // Nodes are selected by applying the selector function. func PickNodes(n ast.Node, selector func(n ast.Node) bool) []ast.Node { diff --git a/tools/vendor/github.com/mgechev/revive/internal/astutils/exit_funcs.go b/tools/vendor/github.com/mgechev/revive/internal/astutils/exit_funcs.go new file mode 100644 index 000000000..1a20be839 --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/internal/astutils/exit_funcs.go @@ -0,0 +1,46 @@ +package astutils + +import "go/ast" + +// exitFuncChecker is a function type that checks whether a function call is an exit function. +type exitFuncChecker func(args []ast.Expr) bool + +var alwaysTrue exitFuncChecker = func([]ast.Expr) bool { return true } + +// exitFunctions is a map of std packages and functions that are considered as exit functions. +var exitFunctions = map[string]map[string]exitFuncChecker{ + "os": {"Exit": alwaysTrue}, + "syscall": {"Exit": alwaysTrue}, + "log": { + "Fatal": alwaysTrue, + "Fatalf": alwaysTrue, + "Fatalln": alwaysTrue, + "Panic": alwaysTrue, + "Panicf": alwaysTrue, + "Panicln": alwaysTrue, + }, + "flag": { + "Parse": func([]ast.Expr) bool { return true }, + "NewFlagSet": func(args []ast.Expr) bool { + if len(args) != 2 { + return false + } + return IsPkgDotName(args[1], "flag", "ExitOnError") + }, + }, +} + +// IsCallToExitFunction checks if the function call is a call to an exit function. +func IsCallToExitFunction(pkgName, functionName string, callArgs []ast.Expr) bool { + m, ok := exitFunctions[pkgName] + if !ok { + return false + } + + check, ok := m[functionName] + if !ok { + return false + } + + return check(callArgs) +} diff --git a/tools/vendor/github.com/mgechev/revive/internal/config/config.go b/tools/vendor/github.com/mgechev/revive/internal/config/config.go new file mode 100644 index 000000000..07e504ad0 --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/internal/config/config.go @@ -0,0 +1,12 @@ +// Package config provides helpers for handling revive configuration and rule option names. +package config + +import "strings" + +// NormalizeOption returns an option name lowercased and without hyphens, so that the camelCase, kebab-case, +// and lowercase spellings of an option all map to the same value. +// +// Example: NormalizeOption("allowTypesBefore"), NormalizeOption("allow-types-before") -> "allowtypesbefore". +func NormalizeOption(name string) string { + return strings.ToLower(strings.ReplaceAll(name, "-", "")) +} diff --git a/tools/vendor/github.com/mgechev/revive/lint/config.go b/tools/vendor/github.com/mgechev/revive/lint/config.go index 533ce054c..bf3d348a1 100644 --- a/tools/vendor/github.com/mgechev/revive/lint/config.go +++ b/tools/vendor/github.com/mgechev/revive/lint/config.go @@ -56,17 +56,17 @@ type DirectivesConfig = map[string]DirectiveConfig // Config defines the config of the linter. type Config struct { - IgnoreGeneratedHeader bool `toml:"ignoreGeneratedHeader"` + IgnoreGeneratedHeader bool `toml:"ignore-generated-header"` Confidence float64 `toml:"confidence"` Severity Severity `toml:"severity"` - EnableAllRules bool `toml:"enableAllRules"` - EnableDefaultRules bool `toml:"enableDefaultRules"` + EnableAllRules bool `toml:"enable-all-rules"` + EnableDefaultRules bool `toml:"enable-default-rules"` Rules RulesConfig `toml:"rule"` - ErrorCode int `toml:"errorCode"` - WarningCode int `toml:"warningCode"` + ErrorCode int `toml:"error-code"` + WarningCode int `toml:"warning-code"` Directives DirectivesConfig `toml:"directive"` Exclude []string `toml:"exclude"` // If set, overrides the go language version specified in go.mod of // packages being linted, and assumes this specific language version. - GoVersion *goversion.Version `toml:"goVersion"` + GoVersion *goversion.Version `toml:"go-version"` } diff --git a/tools/vendor/github.com/mgechev/revive/lint/failure.go b/tools/vendor/github.com/mgechev/revive/lint/failure.go index 01ed09115..7a0d0726a 100644 --- a/tools/vendor/github.com/mgechev/revive/lint/failure.go +++ b/tools/vendor/github.com/mgechev/revive/lint/failure.go @@ -98,6 +98,21 @@ func (f *Failure) IsInternal() bool { return f.Category == failureCategoryInternal } +// SeverityFor returns the effective severity of the failure under the given configuration. +// A failure is an error if its rule or directive is configured with [SeverityError]; otherwise it is a warning. +func (f *Failure) SeverityFor(config *Config) Severity { + if config == nil { + return SeverityWarning + } + if c, ok := config.Rules[f.RuleName]; ok && c.Severity == SeverityError { + return SeverityError + } + if c, ok := config.Directives[f.RuleName]; ok && c.Severity == SeverityError { + return SeverityError + } + return SeverityWarning +} + // NewInternalFailure yields an internal failure with the given message as failure message. func NewInternalFailure(message string) Failure { return Failure{ diff --git a/tools/vendor/github.com/mgechev/revive/lint/file.go b/tools/vendor/github.com/mgechev/revive/lint/file.go index 15b7aa850..785e65b8c 100644 --- a/tools/vendor/github.com/mgechev/revive/lint/file.go +++ b/tools/vendor/github.com/mgechev/revive/lint/file.go @@ -2,12 +2,12 @@ package lint import ( "bytes" - "errors" "go/ast" "go/parser" "go/printer" "go/token" "go/types" + "log/slog" "math" "regexp" "strings" @@ -19,6 +19,7 @@ type File struct { Pkg *Package content []byte AST *ast.File + logger *slog.Logger } // IsTest returns if the file contains tests. @@ -57,6 +58,7 @@ func NewFile(name string, content []byte, pkg *Package) (*File, error) { content: content, Pkg: pkg, AST: f, + logger: slog.New(slog.DiscardHandler), }, nil } @@ -112,21 +114,33 @@ func (f *File) isMain() bool { return f.AST.Name.Name == "main" } -const directiveSpecifyDisableReason = "specify-disable-reason" +const ( + directiveSpecifyDisableReason = "specify-disable-reason" + directiveSpecifyDisableRule = "specify-disable-rule" +) func (f *File) lint(rules []Rule, config Config, failures chan Failure) error { rulesConfig := config.Rules _, mustSpecifyDisableReason := config.Directives[directiveSpecifyDisableReason] - disabledIntervals := f.disabledIntervals(rules, mustSpecifyDisableReason, failures) + _, mustSpecifyDisableRules := config.Directives[directiveSpecifyDisableRule] + disabledIntervals := f.disabledIntervals(rules, mustSpecifyDisableReason, mustSpecifyDisableRules, failures) for _, currentRule := range rules { ruleConfig := rulesConfig[currentRule.Name()] if ruleConfig.MustExclude(f.Name) { continue } currentFailures := currentRule.Apply(f, ruleConfig.Arguments) - for idx, failure := range currentFailures { + filtered := currentFailures[:0] + for _, failure := range currentFailures { + // Log and skip internal failures: they signal a rule could not run on this file, + // but other rules can still produce useful reports. if failure.IsInternal() { - return errors.New(failure.Failure) + f.logger.Warn("rule skipped due to internal failure", + "rule", currentRule.Name(), + "file", f.Name, + "failure", failure.Failure, + ) + continue } if failure.RuleName == "" { @@ -135,9 +149,9 @@ func (f *File) lint(rules []Rule, config Config, failures chan Failure) error { if failure.Node != nil { failure.Position = ToFailurePosition(failure.Node.Pos(), failure.Node.End(), f) } - currentFailures[idx] = failure + filtered = append(filtered, failure) } - currentFailures = f.filterFailures(currentFailures, disabledIntervals) + currentFailures = f.filterFailures(filtered, disabledIntervals) for _, failure := range currentFailures { if failure.Confidence >= config.Confidence { failures <- failure @@ -163,7 +177,7 @@ const ( var directiveRegexp = regexp.MustCompile(`^//[\s]*revive:(enable|disable)(?:-(line|next-line))?(?::([^\s]+))?[\s]*(?: (.+))?$`) -func (f *File) disabledIntervals(rules []Rule, mustSpecifyDisableReason bool, failures chan Failure) disabledIntervalsMap { +func (f *File) disabledIntervals(rules []Rule, mustSpecifyDisableReason, mustSpecifyDisableRules bool, failures chan Failure) disabledIntervalsMap { enabledDisabledRulesMap := map[string][]enableDisableConfig{} getEnabledDisabledIntervals := func() disabledIntervalsMap { @@ -255,6 +269,18 @@ func (f *File) disabledIntervals(rules []Rule, mustSpecifyDisableReason bool, fa continue // skip this linter disabling directive } + mustCheckDisablingRules := mustSpecifyDisableRules && match[directivePos] == "disable" + if mustCheckDisablingRules && len(ruleNames) == 0 { + failures <- Failure{ + Confidence: 1, + RuleName: directiveSpecifyDisableRule, + Failure: "rule name for lint disabling not found", + Position: ToFailurePosition(c.Pos(), c.End(), f), + Node: c, + } + continue // skip this linter disabling directive + } + // TODO: optimize if len(ruleNames) == 0 { for _, rule := range rules { diff --git a/tools/vendor/github.com/mgechev/revive/lint/linter.go b/tools/vendor/github.com/mgechev/revive/lint/linter.go index 46ae3f9c1..e7bd87c45 100644 --- a/tools/vendor/github.com/mgechev/revive/lint/linter.go +++ b/tools/vendor/github.com/mgechev/revive/lint/linter.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" "go/token" + "log/slog" "os" "path/filepath" "regexp" @@ -23,6 +24,7 @@ type ReadFile func(path string) (result []byte, err error) type Linter struct { reader ReadFile fileReadTokens chan struct{} + logger *slog.Logger } // New creates a new Linter. @@ -34,6 +36,14 @@ func New(reader ReadFile, maxOpenFiles int) Linter { return Linter{ reader: reader, fileReadTokens: fileReadTokens, + logger: slog.New(slog.DiscardHandler), + } +} + +// SetLogger sets the logger for the linter if the provided logger is not nil. +func (l *Linter) SetLogger(logger *slog.Logger) { + if logger != nil { + l.logger = logger } } @@ -146,6 +156,7 @@ func (l *Linter) lintPackage(filenames []string, gover *goversion.Version, ruleS addInvalidFileFailure(filename, err.Error(), failures) continue } + file.logger = l.logger pkg.files[filename] = file } diff --git a/tools/vendor/github.com/mgechev/revive/lint/package.go b/tools/vendor/github.com/mgechev/revive/lint/package.go index eaaf64134..9ddefa209 100644 --- a/tools/vendor/github.com/mgechev/revive/lint/package.go +++ b/tools/vendor/github.com/mgechev/revive/lint/package.go @@ -34,8 +34,12 @@ var ( trueValue = 1 falseValue = 2 + // Go111 is a constant representing the Go version 1.11. + Go111 = goversion.Must(goversion.NewVersion("1.11")) // Go115 is a constant representing the Go version 1.15. Go115 = goversion.Must(goversion.NewVersion("1.15")) + // Go118 is a constant representing the Go version 1.18. + Go118 = goversion.Must(goversion.NewVersion("1.18")) // Go121 is a constant representing the Go version 1.21. Go121 = goversion.Must(goversion.NewVersion("1.21")) // Go122 is a constant representing the Go version 1.22. @@ -44,6 +48,8 @@ var ( Go124 = goversion.Must(goversion.NewVersion("1.24")) // Go125 is a constant representing the Go version 1.25. Go125 = goversion.Must(goversion.NewVersion("1.25")) + // Go126 is a constant representing the Go version 1.26. + Go126 = goversion.Must(goversion.NewVersion("1.26")) ) // Files return package's files. @@ -223,6 +229,14 @@ func (p *Package) IsAtLeastGoVersion(v *goversion.Version) bool { return p.goVersion.GreaterThanOrEqual(v) } +// GoVersion returns the Go version for this package. +func (p *Package) GoVersion() *goversion.Version { + p.mu.RLock() + defer p.mu.RUnlock() + + return p.goVersion +} + func getSortableMethodFlagForFunction(fn *ast.FuncDecl) sortableMethodsFlags { switch { case astutils.FuncSignatureIs(fn, "Len", []string{}, []string{"int"}): diff --git a/tools/vendor/github.com/mgechev/revive/rule/add_constant.go b/tools/vendor/github.com/mgechev/revive/rule/add_constant.go index d0770a104..ef9add737 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/add_constant.go +++ b/tools/vendor/github.com/mgechev/revive/rule/add_constant.go @@ -166,6 +166,10 @@ func (w *lintAddConstantRule) checkStrLit(n *ast.BasicLit) { mustCheck := count > ignoreMarker if mustCheck { w.strLits[n.Value] = count + 1 + val, _ := strconv.Unquote(n.Value) + if val == "" { + return + } if w.strLits[n.Value] > w.strLitLimit { w.onFailure(lint.Failure{ Confidence: 1, diff --git a/tools/vendor/github.com/mgechev/revive/rule/comment_spacings.go b/tools/vendor/github.com/mgechev/revive/rule/comment_spacings.go index d28bce04f..7ddfd25ce 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/comment_spacings.go +++ b/tools/vendor/github.com/mgechev/revive/rule/comment_spacings.go @@ -7,8 +7,13 @@ import ( "github.com/mgechev/revive/lint" ) +// defaultAllowList is a set of comment prefixes that are allowed to not have a space after the comment delimiter. +var defaultAllowList = []string{ + "//#nosec", +} + // CommentSpacingsRule checks whether there is a space between -// the comment symbol // and the start of the comment text. +// the comment symbol `//` and the start of the comment text. type CommentSpacingsRule struct { allowList []string } @@ -17,7 +22,7 @@ type CommentSpacingsRule struct { // // Configuration implements the [lint.ConfigurableRule] interface. func (r *CommentSpacingsRule) Configure(arguments lint.Arguments) error { - r.allowList = []string{} + r.allowList = defaultAllowList for _, arg := range arguments { allow, ok := arg.(string) // Alt. non panicking version if !ok { diff --git a/tools/vendor/github.com/mgechev/revive/rule/deep_exit.go b/tools/vendor/github.com/mgechev/revive/rule/deep_exit.go index c1042a6aa..f0044c359 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/deep_exit.go +++ b/tools/vendor/github.com/mgechev/revive/rule/deep_exit.go @@ -65,7 +65,7 @@ func (w *lintDeepExit) Visit(node ast.Node) ast.Visitor { pkg := id.Name fn := fc.Sel.Name - if isCallToExitFunction(pkg, fn, ce.Args) { + if astutils.IsCallToExitFunction(pkg, fn, ce.Args) { msg := fmt.Sprintf("calls to %s.%s only in main() or init() functions", pkg, fn) if pkg == "flag" && fn == "NewFlagSet" && diff --git a/tools/vendor/github.com/mgechev/revive/rule/defer.go b/tools/vendor/github.com/mgechev/revive/rule/defer.go index a85336846..79f3e8101 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/defer.go +++ b/tools/vendor/github.com/mgechev/revive/rule/defer.go @@ -5,16 +5,17 @@ import ( "go/ast" "github.com/mgechev/revive/internal/astutils" + "github.com/mgechev/revive/internal/config" "github.com/mgechev/revive/lint" ) var ( - deferOptionLoop = normalizeRuleOption("loop") - deferOptionCallChain = normalizeRuleOption("callChain") - deferOptionMethodCall = normalizeRuleOption("methodCall") - deferOptionReturn = normalizeRuleOption("return") - deferOptionRecover = normalizeRuleOption("recover") - deferOptionImmediateRecover = normalizeRuleOption("immediateRecover") + deferOptionLoop = config.NormalizeOption("loop") + deferOptionCallChain = config.NormalizeOption("callChain") + deferOptionMethodCall = config.NormalizeOption("methodCall") + deferOptionReturn = config.NormalizeOption("return") + deferOptionRecover = config.NormalizeOption("recover") + deferOptionImmediateRecover = config.NormalizeOption("immediateRecover") ) // DeferRule lints gotchas in defer statements. @@ -77,7 +78,7 @@ func (*DeferRule) allowFromArgs(args lint.Arguments) (map[string]bool, error) { if !ok { return nil, fmt.Errorf("invalid argument '%v' for 'defer' rule. Expecting string, got %T", subcase, subcase) } - allow[normalizeRuleOption(sc)] = true + allow[config.NormalizeOption(sc)] = true } return allow, nil diff --git a/tools/vendor/github.com/mgechev/revive/rule/empty_block.go b/tools/vendor/github.com/mgechev/revive/rule/empty_block.go index 210692c94..64b616d27 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/empty_block.go +++ b/tools/vendor/github.com/mgechev/revive/rule/empty_block.go @@ -52,6 +52,13 @@ func (w lintEmptyBlock) Visit(node ast.Node) ast.Visitor { } case *ast.RangeStmt: if len(n.Body.List) == 0 { + if n.Key == nil && n.Value == nil { + // Conservatively skip bare for-range loops; this pattern is commonly + // used for channel draining and we avoid false positives. + w.ignore[n.Body] = true + return w + } + w.onFailure(lint.Failure{ Confidence: 0.9, Node: n, diff --git a/tools/vendor/github.com/mgechev/revive/rule/exported.go b/tools/vendor/github.com/mgechev/revive/rule/exported.go index 9d8ace8e5..177db1b31 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/exported.go +++ b/tools/vendor/github.com/mgechev/revive/rule/exported.go @@ -370,7 +370,7 @@ func (gds exportedGoDocStatus) confidence() float64 { } func (gds exportedGoDocStatus) correctionHint(firstCommentLine string) string { - firstWord := strings.Split(firstCommentLine, " ")[0] + firstWord, _, _ := strings.Cut(firstCommentLine, " ") switch gds { case exportedGoDocStatusCaseMismatch: return ` by using its correct casing, not "` + firstWord + ` ..."` diff --git a/tools/vendor/github.com/mgechev/revive/rule/line_length_limit.go b/tools/vendor/github.com/mgechev/revive/rule/line_length_limit.go index f2c9a1467..f2f914e70 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/line_length_limit.go +++ b/tools/vendor/github.com/mgechev/revive/rule/line_length_limit.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "go/token" + "regexp" "strings" "unicode/utf8" @@ -14,7 +15,8 @@ import ( // LineLengthLimitRule lints the number of characters in a line. type LineLengthLimitRule struct { - max int + max int + excludes []*regexp.Regexp } const defaultLineLengthLimit = 80 @@ -23,27 +25,93 @@ const defaultLineLengthLimit = 80 // // Configuration implements the [lint.ConfigurableRule] interface. func (r *LineLengthLimitRule) Configure(arguments lint.Arguments) error { + r.max = defaultLineLengthLimit + r.excludes = nil if len(arguments) < 1 { - r.max = defaultLineLengthLimit return nil } - maxLength, ok := arguments[0].(int64) // Alt. non panicking version - if !ok || maxLength < 0 { + switch arg := arguments[0].(type) { + case int64: + // backward compatibility: if the first argument is an integer, it is treated as the maximum line length. + return r.setMax(arg) + case map[string]any: + return r.configureFromMap(arg) + default: + return fmt.Errorf(`invalid argument to the "line-length-limit" rule: expecting an integer or an options map, got %T`, arguments[0]) + } +} + +func (r *LineLengthLimitRule) setMax(value int64) error { + if value < 0 { return errors.New(`invalid value passed as argument number to the "line-length-limit" rule`) } - r.max = int(maxLength) + r.max = int(value) return nil } +func (r *LineLengthLimitRule) configureFromMap(options map[string]any) error { + for k, v := range options { + switch { + case isRuleOption(k, "max"): + maxLength, ok := v.(int64) + if !ok { + return fmt.Errorf(`invalid value for the "max" option of the "line-length-limit" rule: expecting an integer, got %T`, v) + } + + if err := r.setMax(maxLength); err != nil { + return err + } + case isRuleOption(k, "excludes"): + excludes, err := parseLineLengthExcludes(v) + if err != nil { + return err + } + + r.excludes = excludes + } + } + + return nil +} + +func parseLineLengthExcludes(value any) ([]*regexp.Regexp, error) { + list, ok := value.([]any) + if !ok { + return nil, fmt.Errorf(`invalid value for the "excludes" option of the "line-length-limit" rule: expecting a slice of strings, got %T`, value) + } + + excludes := make([]*regexp.Regexp, 0, len(list)) + for _, v := range list { + pattern, ok := v.(string) + if !ok { + return nil, fmt.Errorf(`invalid value in the "excludes" option of the "line-length-limit" rule: expecting a string, got %T`, v) + } + + if pattern == "" { + return nil, errors.New(`invalid value in the "excludes" option of the "line-length-limit" rule: regular expression must not be empty`) + } + + exp, err := regexp.Compile(pattern) + if err != nil { + return nil, fmt.Errorf(`invalid value in the "excludes" option of the "line-length-limit" rule: regexp %q does not compile: %w`, pattern, err) + } + + excludes = append(excludes, exp) + } + + return excludes, nil +} + // Apply applies the rule to given file. func (r *LineLengthLimitRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { var failures []lint.Failure checker := lintLineLengthNum{ - max: r.max, - file: file, + max: r.max, + excludes: r.excludes, + file: file, onFailure: func(failure lint.Failure) { failures = append(failures, failure) }, @@ -61,6 +129,7 @@ func (*LineLengthLimitRule) Name() string { type lintLineLengthNum struct { max int + excludes []*regexp.Regexp file *lint.File onFailure func(lint.Failure) } @@ -72,6 +141,11 @@ func (r lintLineLengthNum) check() { s := bufio.NewScanner(f) for s.Scan() { t := s.Text() + if r.isExcluded(t) { + l++ + continue + } + t = strings.ReplaceAll(t, "\t", spaces) c := utf8.RuneCountInString(t) if c > r.max { @@ -97,3 +171,14 @@ func (r lintLineLengthNum) check() { l++ } } + +// isExcluded reports whether the raw line matches any of the configured exclude patterns. +func (r lintLineLengthNum) isExcluded(line string) bool { + for _, exclude := range r.excludes { + if exclude.MatchString(line) { + return true + } + } + + return false +} diff --git a/tools/vendor/github.com/mgechev/revive/rule/marshal_receiver.go b/tools/vendor/github.com/mgechev/revive/rule/marshal_receiver.go new file mode 100644 index 000000000..ea3978f25 --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/rule/marshal_receiver.go @@ -0,0 +1,76 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/internal/typeparams" + "github.com/mgechev/revive/lint" +) + +// MarshalReceiverRule lints marshal/unmarshal methods with incorrect receiver types. +type MarshalReceiverRule struct{} + +// Name returns the rule name. +func (*MarshalReceiverRule) Name() string { + return "marshal-receiver" +} + +// Apply applies the rule to given file. +func (*MarshalReceiverRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + for _, decl := range file.AST.Decls { + fn, ok := decl.(*ast.FuncDecl) + if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { + continue + } + + name := fn.Name.Name + qualifiedName := typeparams.ReceiverType(fn) + "." + name + isMarshal := isMarshalMethod(name) + isUnmarshal := !isMarshal && isUnmarshalMethod(name) + if !isMarshal && !isUnmarshal { + continue + } + + recv := fn.Recv.List[0] + _, isPtr := recv.Type.(*ast.StarExpr) + + var msg string + switch { + case isMarshal && isPtr: + msg = " method should use a value receiver, not a pointer receiver" + case isUnmarshal && !isPtr: + msg = " method should use a pointer receiver, not a value receiver" + default: + continue // nothing to say about the method declaration + } + + failures = append(failures, lint.Failure{ + Node: decl, + Confidence: 1, + Category: lint.FailureCategoryBadPractice, + Failure: qualifiedName + msg, + }) + } + + return failures +} + +func isMarshalMethod(name string) bool { + switch name { + case "MarshalJSON", "MarshalText", "MarshalYAML": + return true + default: + return false + } +} + +func isUnmarshalMethod(name string) bool { + switch name { + case "UnmarshalJSON", "UnmarshalText", "UnmarshalYAML": + return true + default: + return false + } +} diff --git a/tools/vendor/github.com/mgechev/revive/rule/multiline_if_init.go b/tools/vendor/github.com/mgechev/revive/rule/multiline_if_init.go new file mode 100644 index 000000000..f57c89628 --- /dev/null +++ b/tools/vendor/github.com/mgechev/revive/rule/multiline_if_init.go @@ -0,0 +1,59 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// MultilineIfInitRule flags if statements whose init clause spans multiple lines. +// A multi-line init defeats the purpose of the if-init idiom, which exists for tight one-liners. +// When it wraps, the reader has to visually parse a struct literal or call chain to +// find where the init ends and the condition begins. +type MultilineIfInitRule struct{} + +// Apply applies the rule to given file. +func (*MultilineIfInitRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + ast.Walk(lintMultilineIfInit{ + file: file, + onFailure: onFailure, + }, file.AST) + return failures +} + +// Name returns the rule name. +func (*MultilineIfInitRule) Name() string { + return "multiline-if-init" +} + +type lintMultilineIfInit struct { + file *lint.File + onFailure func(lint.Failure) +} + +func (w lintMultilineIfInit) Visit(n ast.Node) ast.Visitor { + ifStmt, ok := n.(*ast.IfStmt) + if !ok || ifStmt.Init == nil { + return w + } + + initStart := w.file.ToPosition(ifStmt.Init.Pos()) + initEnd := w.file.ToPosition(ifStmt.Init.End()) + + if initEnd.Line-initStart.Line > 0 { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: ifStmt, + Category: lint.FailureCategoryStyle, + Failure: "if-init statement should not span multiple lines", + }) + } + + return w +} diff --git a/tools/vendor/github.com/mgechev/revive/rule/package_comments.go b/tools/vendor/github.com/mgechev/revive/rule/package_comments.go index 74af62679..6849d04d7 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/package_comments.go +++ b/tools/vendor/github.com/mgechev/revive/rule/package_comments.go @@ -118,16 +118,16 @@ func (l *lintPackageComments) Visit(_ ast.Node) ast.Visitor { lastCG = cg } if lastCG != nil && strings.HasPrefix(lastCG.Text(), prefix) { - endPos := l.file.ToPosition(lastCG.End()) + endLine := commentGroupEndLine(l.file, lastCG) pkgPos := l.file.ToPosition(l.fileAst.Package) - if endPos.Line+1 < pkgPos.Line { + if endLine+1 < pkgPos.Line { // There isn't a great place to anchor this error; // the start of the blank lines between the doc and the package statement // is at least pointing at the location of the problem. pos := token.Position{ - Filename: endPos.Filename, + Filename: pkgPos.Filename, // Offset not set; it is non-trivial, and doesn't appear to be needed. - Line: endPos.Line + 1, + Line: endLine + 1, Column: 1, } l.onFailure(lint.Failure{ @@ -163,6 +163,16 @@ func (l *lintPackageComments) Visit(_ ast.Node) ast.Visitor { return nil } +// commentGroupEndLine returns the line of the last character of the given comment group. +// +// It does not rely on [ast.CommentGroup.End] because that position is derived from the +// comment text, from which the scanner strips carriage returns. On CRLF sources End() +// therefore points before the actual end of a block comment (see https://go.dev/issue/41197). +func commentGroupEndLine(file *lint.File, cg *ast.CommentGroup) int { + lastComment := cg.List[len(cg.List)-1] + return file.ToPosition(lastComment.Slash).Line + strings.Count(lastComment.Text, "\n") +} + func isEmptyDoc(commentGroup *ast.CommentGroup) bool { return commentGroup == nil || commentGroup.Text() == "" } diff --git a/tools/vendor/github.com/mgechev/revive/rule/redundant_build_tag.go b/tools/vendor/github.com/mgechev/revive/rule/redundant_build_tag.go index d195ce6e4..9163c5758 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/redundant_build_tag.go +++ b/tools/vendor/github.com/mgechev/revive/rule/redundant_build_tag.go @@ -1,6 +1,8 @@ package rule import ( + "fmt" + "go/version" "strings" "github.com/mgechev/revive/lint" @@ -9,24 +11,44 @@ import ( // RedundantBuildTagRule lints the presence of redundant build tags. type RedundantBuildTagRule struct{} -// Apply triggers if an old build tag `// +build` is found after a new one `//go:build`. -// `//go:build` comments are automatically added by gofmt when Go 1.17+ is used. +// Apply triggers on two kinds of redundant build tags: +// - an old `// +build` tag found after a new `//go:build` tag, since `//go:build` +// comments are automatically added by gofmt when Go 1.17+ is used; +// - a `//go:build go1.X` version constraint that is already guaranteed by the +// go version in go.mod (Go 1.21+, where the go directive is a hard requirement). +// // See https://pkg.go.dev/cmd/go#hdr-Build_constraints func (*RedundantBuildTagRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { for _, group := range file.AST.Comments { hasGoBuild := false for _, comment := range group.List { - if strings.HasPrefix(comment.Text, "//go:build ") { + if ver, ok := strings.CutPrefix(comment.Text, "//go:build "); ok { hasGoBuild = true + + // goVersion is the module's language version, e.g. "go1.21". + goVersion := version.Lang("go" + file.Pkg.GoVersion().String()) + // Starting with Go 1.21 the go version in go.mod is a hard requirement, + // so a `//go:build go1.X` constraint it already satisfies is redundant. + // Skip this check for Go 1.20 and earlier. + if file.Pkg.IsAtLeastGoVersion(lint.Go121) && version.IsValid(ver) && version.Compare(goVersion, ver) >= 0 { + return []lint.Failure{{ + Category: lint.FailureCategoryStyle, + Confidence: 1, + Node: comment, + Failure: fmt.Sprintf("The build tag %q is redundant for Go %s and can be removed", comment.Text, strings.TrimPrefix(goVersion, "go")), + }} + } + continue } - if hasGoBuild && strings.HasPrefix(comment.Text, "// +build ") { + const oldGoBuildPrefix = "// +build" + if hasGoBuild && strings.HasPrefix(comment.Text, oldGoBuildPrefix) { return []lint.Failure{{ Category: lint.FailureCategoryStyle, Confidence: 1, Node: comment, - Failure: `The build tag "// +build" is redundant since Go 1.17 and can be removed`, + Failure: fmt.Sprintf("The build tag %q is redundant since Go 1.17 and can be removed", oldGoBuildPrefix), }} } } diff --git a/tools/vendor/github.com/mgechev/revive/rule/redundant_test_main_exit.go b/tools/vendor/github.com/mgechev/revive/rule/redundant_test_main_exit.go index 04e643e5f..029cdca27 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/redundant_test_main_exit.go +++ b/tools/vendor/github.com/mgechev/revive/rule/redundant_test_main_exit.go @@ -4,6 +4,7 @@ import ( "fmt" "go/ast" + "github.com/mgechev/revive/internal/astutils" "github.com/mgechev/revive/lint" ) @@ -71,7 +72,7 @@ func (w *lintRedundantTestMainExit) Visit(node ast.Node) ast.Visitor { } fn := fc.Sel.Name - if isCallToExitFunction(pkg, fn, ce.Args) { + if astutils.IsCallToExitFunction(pkg, fn, ce.Args) { w.onFailure(lint.Failure{ Confidence: 1, Node: ce, diff --git a/tools/vendor/github.com/mgechev/revive/rule/struct_tag.go b/tools/vendor/github.com/mgechev/revive/rule/struct_tag.go index c245f51fc..f6e816e11 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/struct_tag.go +++ b/tools/vendor/github.com/mgechev/revive/rule/struct_tag.go @@ -569,7 +569,7 @@ func checkProtobufOptions(checkCtx *checkContext, options []string) (message str seenOptions := map[string]bool{} hasName := false for _, opt := range options { - opt := strings.Split(opt, "=")[0] + opt, _, _ := strings.Cut(opt, "=") if number, err := strconv.Atoi(opt); err == nil { _, alreadySeen := checkCtx.usedTagNbr[number] diff --git a/tools/vendor/github.com/mgechev/revive/rule/unconditional_recursion.go b/tools/vendor/github.com/mgechev/revive/rule/unconditional_recursion.go index 738a9e71d..64e348b76 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/unconditional_recursion.go +++ b/tools/vendor/github.com/mgechev/revive/rule/unconditional_recursion.go @@ -193,7 +193,7 @@ func (*lintUnconditionalRecursionRule) hasControlExit(node ast.Node) bool { functionName := se.Sel.Name pkgName := id.Name - return isCallToExitFunction(pkgName, functionName, n.Args) + return astutils.IsCallToExitFunction(pkgName, functionName, n.Args) } return false diff --git a/tools/vendor/github.com/mgechev/revive/rule/unhandled_error.go b/tools/vendor/github.com/mgechev/revive/rule/unhandled_error.go index 9dd8e7f22..e5b7db576 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/unhandled_error.go +++ b/tools/vendor/github.com/mgechev/revive/rule/unhandled_error.go @@ -112,6 +112,10 @@ func (w *lintUnhandledErrors) addFailure(n *ast.CallExpr) { return } + if w.isSafeFprintfToBuffer(n) { + return + } + w.onFailure(lint.Failure{ Category: lint.FailureCategoryBadPractice, Confidence: 1, @@ -173,3 +177,21 @@ func (w *lintUnhandledErrors) getFunc(call *ast.CallExpr) (*types.Func, bool) { return fn, true } + +func (w *lintUnhandledErrors) isSafeFprintfToBuffer(call *ast.CallExpr) bool { + if len(call.Args) == 0 { + return false + } + + fn, ok := w.getFunc(call) + if !ok || fn.FullName() != "fmt.Fprintf" { + return false + } + + argType := w.pkg.TypeOf(call.Args[0]) + if argType == nil { + return false + } + + return astutils.IsPointerToPkgDotType(argType, "bytes", "Buffer") || astutils.IsPointerToPkgDotType(argType, "strings", "Builder") +} diff --git a/tools/vendor/github.com/mgechev/revive/rule/use_any.go b/tools/vendor/github.com/mgechev/revive/rule/use_any.go index 0ebb4d628..a88579019 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/use_any.go +++ b/tools/vendor/github.com/mgechev/revive/rule/use_any.go @@ -11,6 +11,10 @@ type UseAnyRule struct{} // Apply applies the rule to given file. func (*UseAnyRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + if !file.Pkg.IsAtLeastGoVersion(lint.Go118) { + return nil // nothing to do, the alias any was added in version 1.18 + } + var failures []lint.Failure walker := lintUseAny{ diff --git a/tools/vendor/github.com/mgechev/revive/rule/use_errors_new.go b/tools/vendor/github.com/mgechev/revive/rule/use_errors_new.go index 5473bc7e7..97ff1419e 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/use_errors_new.go +++ b/tools/vendor/github.com/mgechev/revive/rule/use_errors_new.go @@ -12,6 +12,11 @@ type UseErrorsNewRule struct{} // Apply applies the rule to given file. func (*UseErrorsNewRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + if file.Pkg.IsAtLeastGoVersion(lint.Go126) { + // For unformatted strings in Go 1.26, fmt.Errorf matches the behavior of errors.New, so we can skip the analysis. + return nil + } + var failures []lint.Failure walker := lintFmtErrorf{ diff --git a/tools/vendor/github.com/mgechev/revive/rule/use_fmt_print.go b/tools/vendor/github.com/mgechev/revive/rule/use_fmt_print.go index 3da85ff7f..dd36adc2e 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/use_fmt_print.go +++ b/tools/vendor/github.com/mgechev/revive/rule/use_fmt_print.go @@ -47,7 +47,7 @@ func (w lintUseFmtPrint) Visit(node ast.Node) ast.Visitor { return w // nothing to do, the node is not a call } - id, ok := (ce.Fun).(*ast.Ident) + id, ok := ce.Fun.(*ast.Ident) if !ok { return nil } diff --git a/tools/vendor/github.com/mgechev/revive/rule/use_waitgroup_go.go b/tools/vendor/github.com/mgechev/revive/rule/use_waitgroup_go.go index 6f04fd65c..47afc5381 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/use_waitgroup_go.go +++ b/tools/vendor/github.com/mgechev/revive/rule/use_waitgroup_go.go @@ -98,15 +98,9 @@ func (w *lintUseWaitGroupGo) analyzeBlock(b *ast.BlockStmt) { // we will iterate from the (i+1)-th statement up to the last statement of block.List for i++; i < len(stmts); i++ { stmt := stmts[i] - // looking for a go statement - goStmt, ok := stmt.(*ast.GoStmt) - if !ok { - continue // not a go statement - } - - // here we found a the go statement - // now let's check is the go statement is applied to a function literal that contains a wg.Done - if !w.hasCallToWgDone(goStmt) { + // looking for a go statement (directly or inside a for/range loop) + goStmt := w.findGoStmtWithWgDone(stmt) + if goStmt == nil { continue } @@ -122,6 +116,38 @@ func (w *lintUseWaitGroupGo) analyzeBlock(b *ast.BlockStmt) { } } +// findGoStmtWithWgDone returns the first go statement with wg.Done found in stmt, +// searching directly and one level deep inside for/range loop bodies. +// Returns nil if no such go statement is found. +func (w *lintUseWaitGroupGo) findGoStmtWithWgDone(stmt ast.Stmt) *ast.GoStmt { + switch s := stmt.(type) { + case *ast.GoStmt: + if w.hasCallToWgDone(s) { + return s + } + case *ast.ForStmt: + return w.seekGoStmtWithWgDoneInBlock(s.Body) + case *ast.RangeStmt: + return w.seekGoStmtWithWgDoneInBlock(s.Body) + } + return nil +} + +// seekGoStmtWithWgDoneInBlock returns the first go statement with wg.Done +// found as a direct child of block, or nil if none is found. +func (w *lintUseWaitGroupGo) seekGoStmtWithWgDoneInBlock(block *ast.BlockStmt) *ast.GoStmt { + if block == nil { + return nil + } + for _, s := range block.List { + goStmt, ok := s.(*ast.GoStmt) + if ok && w.hasCallToWgDone(goStmt) { + return goStmt + } + } + return nil +} + // hasCallToWgDone returns true if the given go statement // calls to a function literal containing a call to wg.Done, false otherwise. func (*lintUseWaitGroupGo) hasCallToWgDone(goStmt *ast.GoStmt) bool { diff --git a/tools/vendor/github.com/mgechev/revive/rule/utils.go b/tools/vendor/github.com/mgechev/revive/rule/utils.go index 8d653227d..f90b1c5ca 100644 --- a/tools/vendor/github.com/mgechev/revive/rule/utils.go +++ b/tools/vendor/github.com/mgechev/revive/rule/utils.go @@ -1,43 +1,14 @@ package rule import ( - "go/ast" "go/token" "regexp" "strings" - "github.com/mgechev/revive/internal/astutils" + "github.com/mgechev/revive/internal/config" "github.com/mgechev/revive/lint" ) -// exitFuncChecker is a function type that checks whether a function call is an exit function. -type exitFuncChecker func(args []ast.Expr) bool - -var alwaysTrue exitFuncChecker = func([]ast.Expr) bool { return true } - -// exitFunctions is a map of std packages and functions that are considered as exit functions. -var exitFunctions = map[string]map[string]exitFuncChecker{ - "os": {"Exit": alwaysTrue}, - "syscall": {"Exit": alwaysTrue}, - "log": { - "Fatal": alwaysTrue, - "Fatalf": alwaysTrue, - "Fatalln": alwaysTrue, - "Panic": alwaysTrue, - "Panicf": alwaysTrue, - "Panicln": alwaysTrue, - }, - "flag": { - "Parse": func([]ast.Expr) bool { return true }, - "NewFlagSet": func(args []ast.Expr) bool { - if len(args) != 2 { - return false - } - return astutils.IsPkgDotName(args[1], "flag", "ExitOnError") - }, - }, -} - func srcLine(src []byte, p token.Position) string { // Run to end of line in both directions if not at line start/end. lo, hi := p.Offset, p.Offset+1 @@ -52,14 +23,7 @@ func srcLine(src []byte, p token.Position) string { // isRuleOption returns true if arg and name are the same after normalization. func isRuleOption(arg, name string) bool { - return normalizeRuleOption(arg) == normalizeRuleOption(name) -} - -// normalizeRuleOption returns an option name from the argument. It is lowercased and without hyphens. -// -// Example: normalizeRuleOption("allowTypesBefore"), normalizeRuleOption("allow-types-before") -> "allowtypesbefore". -func normalizeRuleOption(arg string) string { - return strings.ToLower(strings.ReplaceAll(arg, "-", "")) + return config.NormalizeOption(arg) == config.NormalizeOption(name) } var normalizePathReplacer = strings.NewReplacer("-", "", "_", "", ".", "") @@ -92,21 +56,6 @@ func isDirectiveComment(line string) bool { return directiveCommentRE.MatchString(line) } -// isCallToExitFunction checks if the function call is a call to an exit function. -func isCallToExitFunction(pkgName, functionName string, callArgs []ast.Expr) bool { - m, ok := exitFunctions[pkgName] - if !ok { - return false - } - - check, ok := m[functionName] - if !ok { - return false - } - - return check(callArgs) -} - // newInternalFailureError returns a slice of Failure with a single internal failure in it. func newInternalFailureError(e error) []lint.Failure { return []lint.Failure{lint.NewInternalFailure(e.Error())} diff --git a/tools/vendor/github.com/nunnatsa/ginkgolinter/config/config.go b/tools/vendor/github.com/nunnatsa/ginkgolinter/config/config.go index 1fee023cd..e62158ede 100644 --- a/tools/vendor/github.com/nunnatsa/ginkgolinter/config/config.go +++ b/tools/vendor/github.com/nunnatsa/ginkgolinter/config/config.go @@ -63,8 +63,8 @@ func (s *Config) UpdateFromComment(commentGroup []*ast.CommentGroup) { } for _, cmnt := range cmntList.List { - commentLines := strings.Split(cmnt.Text, "\n") - for _, comment := range commentLines { + commentLines := strings.SplitSeq(cmnt.Text, "\n") + for comment := range commentLines { comment = strings.TrimPrefix(comment, "//") comment = strings.TrimPrefix(comment, "/*") comment = strings.TrimSuffix(comment, "*/") diff --git a/tools/vendor/github.com/securego/gosec/v2/action.yml b/tools/vendor/github.com/securego/gosec/v2/action.yml index 62848c718..ed3651020 100644 --- a/tools/vendor/github.com/securego/gosec/v2/action.yml +++ b/tools/vendor/github.com/securego/gosec/v2/action.yml @@ -10,7 +10,7 @@ inputs: runs: using: "docker" - image: "docker://ghcr.io/securego/gosec@sha256:d0859f242774504d57b17442b2dc77d4ffd2f4d30fbdf92c7eb612b2a394f477" # 2.27.1 + image: "docker://ghcr.io/securego/gosec@sha256:4342ad119a7c69f3f4e4ce78d81ba183dc774a70a7a4c6eeb15fe9e511f214f0" # 2.28.0 args: - ${{ inputs.args }} diff --git a/tools/vendor/github.com/securego/gosec/v2/analyzers/pathtraversal.go b/tools/vendor/github.com/securego/gosec/v2/analyzers/pathtraversal.go index ba9b72e81..1077b423f 100644 --- a/tools/vendor/github.com/securego/gosec/v2/analyzers/pathtraversal.go +++ b/tools/vendor/github.com/securego/gosec/v2/analyzers/pathtraversal.go @@ -66,22 +66,25 @@ func PathTraversal() taint.Config { {Package: "net/http", Method: "ServeFileFS", CheckArgs: []int{3}}, }, Sanitizers: []taint.Sanitizer{ - // filepath.Clean normalizes and removes traversal components - {Package: "path/filepath", Method: "Clean"}, - // filepath.Abs calls Clean internally (per Go docs) - {Package: "path/filepath", Method: "Abs"}, // filepath.Base extracts just the filename, removing directory traversal {Package: "path/filepath", Method: "Base"}, // filepath.Rel computes a relative path safely {Package: "path/filepath", Method: "Rel"}, - // url.PathEscape escapes path components - {Package: "net/url", Method: "PathEscape"}, - // path.Base and path.Clean provide identical traversal-stripping - // semantics as their filepath counterparts (the only difference is - // separator handling, which is irrelevant for security). + // path.Base provides identical traversal-stripping semantics as its + // filepath counterpart (the only difference is separator handling, + // which is irrelevant for security). {Package: "path", Method: "Base"}, - {Package: "path", Method: "Clean"}, + + // NOTE: Clean/filepath.Clean, filepath.Abs and url.PathEscape are + // deliberately NOT sanitizers. Clean only lexically normalizes a + // path: it evaluates ".." elements instead of rejecting them, so + // Clean("/prefix/../../etc/passwd") yields "/etc/passwd" — the + // traversal succeeds. filepath.Abs calls Clean internally and + // inherits the same behavior, and url.PathUnescape reverses + // url.PathEscape, restoring any ".." that was escaped. Confining a + // path to a directory requires os.Root or an explicit prefix check, + // not Clean. See https://github.com/securego/gosec/issues/1721. // Integer conversions eliminate path traversal vectors entirely — // the result can never contain "/" or ".." characters. diff --git a/tools/vendor/github.com/securego/gosec/v2/analyzers/util.go b/tools/vendor/github.com/securego/gosec/v2/analyzers/util.go index 3d3464653..93e48dc1d 100644 --- a/tools/vendor/github.com/securego/gosec/v2/analyzers/util.go +++ b/tools/vendor/github.com/securego/gosec/v2/analyzers/util.go @@ -223,11 +223,11 @@ func newIssue(analyzerID string, desc string, fileSet *token.FileSet, func issueCodeSnippet(fileSet *token.FileSet, pos token.Pos) string { file := fileSet.File(pos) - start := (int64)(file.Line(pos)) + start := int64(file.Line(pos)) if start-issue.SnippetOffset > 0 { start = start - issue.SnippetOffset } - end := (int64)(file.Line(pos)) + end := int64(file.Line(pos)) end = end + issue.SnippetOffset var code string diff --git a/tools/vendor/github.com/securego/gosec/v2/config.go b/tools/vendor/github.com/securego/gosec/v2/config.go index 954daabaa..743822b24 100644 --- a/tools/vendor/github.com/securego/gosec/v2/config.go +++ b/tools/vendor/github.com/securego/gosec/v2/config.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "strings" ) const ( @@ -45,8 +46,10 @@ const ( ) // NoSecTag returns the tag used to disable gosec for a line of code. +// A single leading '#' is tolerated, so that both "dontanalyze" and +// "#dontanalyze" produce the same "#dontanalyze" tag. func NoSecTag(tag string) string { - return fmt.Sprintf("%s%s", "#", tag) + return fmt.Sprintf("%s%s", "#", strings.TrimPrefix(tag, "#")) } // Config is used to provide configuration and customization to each of the rules. diff --git a/tools/vendor/github.com/securego/gosec/v2/issue/issue.go b/tools/vendor/github.com/securego/gosec/v2/issue/issue.go index f2cc986d2..74d00fb32 100644 --- a/tools/vendor/github.com/securego/gosec/v2/issue/issue.go +++ b/tools/vendor/github.com/securego/gosec/v2/issue/issue.go @@ -209,7 +209,7 @@ func CodeSnippet(file *os.File, start int64, end int64) (string, error) { } func codeSnippetStartLine(node ast.Node, fobj *token.File) int64 { - s := (int64)(fobj.Line(node.Pos())) + s := int64(fobj.Line(node.Pos())) if s-SnippetOffset > 0 { return s - SnippetOffset } @@ -217,7 +217,7 @@ func codeSnippetStartLine(node ast.Node, fobj *token.File) int64 { } func codeSnippetEndLine(node ast.Node, fobj *token.File) int64 { - e := (int64)(fobj.Line(node.End())) + e := int64(fobj.Line(node.End())) return e + SnippetOffset } diff --git a/tools/vendor/github.com/securego/gosec/v2/resolve.go b/tools/vendor/github.com/securego/gosec/v2/resolve.go index 18f9ad9c1..72b995f40 100644 --- a/tools/vendor/github.com/securego/gosec/v2/resolve.go +++ b/tools/vendor/github.com/securego/gosec/v2/resolve.go @@ -14,7 +14,11 @@ package gosec -import "go/ast" +import ( + "go/ast" + "go/token" + "go/types" +) func resolveIdent(n *ast.Ident, c *Context) bool { if n.Obj == nil || n.Obj.Kind != ast.Var { @@ -66,11 +70,154 @@ func resolveBinExpr(n *ast.BinaryExpr, c *Context) bool { return (TryResolve(n.X, c) && TryResolve(n.Y, c)) } -func resolveCallExpr(_ *ast.CallExpr, _ *Context) bool { +func resolveCallExpr(node *ast.CallExpr, c *Context) bool { + // A strings.Builder / bytes.Buffer .String() call resolves to a constant when + // every value written to the receiver is itself a constant. This keeps rules + // such as G202 consistent with their lenient handling of constant string + // concatenation (e.g. building a value with +=) and avoids false positives + // when a builder is used purely to assemble a constant string. + if obj, ok := builderStringReceiver(node, c); ok { + return builderWritesAreConst(obj, node, c) + } // TODO(tkelsey): next step, full function resolution return false } +// builderStringReceiver returns the receiver's object when node is a call to +// String() on a strings.Builder or bytes.Buffer value. +func builderStringReceiver(node *ast.CallExpr, c *Context) (types.Object, bool) { + sel, ok := node.Fun.(*ast.SelectorExpr) + if !ok || sel.Sel.Name != "String" || len(node.Args) != 0 { + return nil, false + } + ident, ok := sel.X.(*ast.Ident) + if !ok || !isStringBuilderType(c.Info.TypeOf(ident)) { + return nil, false + } + obj := c.Info.ObjectOf(ident) + if obj == nil { + return nil, false + } + // Only reason about local builders; a package-level one may be written to + // in files we do not inspect here. + if c.Pkg != nil && obj.Parent() == c.Pkg.Scope() { + return nil, false + } + return obj, true +} + +// isStringBuilderType reports whether t is strings.Builder or bytes.Buffer +// (or a pointer to either). +func isStringBuilderType(t types.Type) bool { + if ptr, ok := t.(*types.Pointer); ok { + t = ptr.Elem() + } + named, ok := t.(*types.Named) + if !ok { + return false + } + obj := named.Obj() + if obj == nil || obj.Pkg() == nil { + return false + } + switch obj.Pkg().Path() + "." + obj.Name() { + case "strings.Builder", "bytes.Buffer": + return true + } + return false +} + +// builderWritesAreConst reports whether every value written to the builder +// referenced by obj is a constant. It is conservative: any usage of the builder +// that cannot be reasoned about (e.g. its address escaping to a function) makes +// it return false. +func builderWritesAreConst(obj types.Object, strCall *ast.CallExpr, c *Context) bool { + file := ContainingFile(strCall, c) + if file == nil { + return false + } + + allRefs := map[*ast.Ident]bool{} + accounted := map[*ast.Ident]bool{} + safe := true + + ast.Inspect(file, func(n ast.Node) bool { + switch node := n.(type) { + case *ast.Ident: + if c.Info.ObjectOf(node) == obj { + allRefs[node] = true + } + case *ast.ValueSpec: // var b strings.Builder + for _, name := range node.Names { + if c.Info.ObjectOf(name) == obj { + accounted[name] = true + } + } + case *ast.AssignStmt: + if node.Tok != token.DEFINE { + return true + } + for i, lhs := range node.Lhs { + id, ok := lhs.(*ast.Ident) + if !ok || c.Info.ObjectOf(id) != obj { + continue + } + accounted[id] = true + // Only an empty composite literal (strings.Builder{}) is a + // known-empty starting point; anything else is opaque. + if len(node.Rhs) != len(node.Lhs) || !isEmptyCompositeLit(node.Rhs[i]) { + safe = false + } + } + case *ast.CallExpr: // b.WriteString(...), b.String(), ... + sel, ok := node.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + recv, ok := sel.X.(*ast.Ident) + if !ok || c.Info.ObjectOf(recv) != obj { + return true + } + accounted[recv] = true + switch sel.Sel.Name { + case "WriteString", "WriteByte", "WriteRune", "Write": + for _, arg := range node.Args { + if !TryResolve(arg, c) { + safe = false + } + } + case "String", "Len", "Cap", "Reset", "Grow": + // read-only or adds no content + default: + safe = false + } + } + return true + }) + + if !safe { + return false + } + // Any reference we could not account for (e.g. &b passed to a function) + // means the builder's contents are unknown. + for id := range allRefs { + if !accounted[id] { + return false + } + } + return true +} + +// isEmptyCompositeLit reports whether e is an empty composite literal, optionally +// address-taken (e.g. strings.Builder{} or &strings.Builder{}). +func isEmptyCompositeLit(e ast.Expr) bool { + if u, ok := e.(*ast.UnaryExpr); ok && u.Op == token.AND { + e = u.X + } + cl, ok := e.(*ast.CompositeLit) + return ok && len(cl.Elts) == 0 +} + // TryResolve will attempt, given a subtree starting at some AST node, to resolve // all values contained within to a known constant. It is used to check for any // unknown values in compound expressions. diff --git a/tools/vendor/github.com/securego/gosec/v2/rules/rsa.go b/tools/vendor/github.com/securego/gosec/v2/rules/rsa.go index ca4f138f6..50bd336bc 100644 --- a/tools/vendor/github.com/securego/gosec/v2/rules/rsa.go +++ b/tools/vendor/github.com/securego/gosec/v2/rules/rsa.go @@ -30,7 +30,7 @@ type weakKeyStrength struct { // Match overrides the base to check the bits argument of rsa.GenerateKey func (w *weakKeyStrength) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { if callExpr := w.calls.ContainsPkgCallExpr(n, c, false); callExpr != nil { - if bits, err := gosec.GetInt(callExpr.Args[1]); err == nil && bits < (int64)(w.bits) { + if bits, err := gosec.GetInt(callExpr.Args[1]); err == nil && bits < int64(w.bits) { return c.NewIssue(n, w.ID(), w.What, w.Severity, w.Confidence), nil } } diff --git a/tools/vendor/github.com/securego/gosec/v2/taint/analyzer.go b/tools/vendor/github.com/securego/gosec/v2/taint/analyzer.go index 63640177b..50d65875d 100644 --- a/tools/vendor/github.com/securego/gosec/v2/taint/analyzer.go +++ b/tools/vendor/github.com/securego/gosec/v2/taint/analyzer.go @@ -128,11 +128,11 @@ func newIssue(analyzerID string, desc string, fileSet *token.FileSet, func issueCodeSnippet(fileSet *token.FileSet, pos token.Pos) string { file := fileSet.File(pos) - start := (int64)(file.Line(pos)) + start := int64(file.Line(pos)) if start-issue.SnippetOffset > 0 { start = start - issue.SnippetOffset } - end := (int64)(file.Line(pos)) + end := int64(file.Line(pos)) end = end + issue.SnippetOffset var code string diff --git a/tools/vendor/github.com/stretchr/testify/assert/assertions.go b/tools/vendor/github.com/stretchr/testify/assert/assertions.go index 1419e4776..166f63726 100644 --- a/tools/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/tools/vendor/github.com/stretchr/testify/assert/assertions.go @@ -17,7 +17,7 @@ import ( "unicode" "unicode/utf8" - // Wrapper around gopkg.in/yaml.v3 + // Wrapper around go.yaml.in/yaml/v3 "github.com/stretchr/testify/assert/yaml" "github.com/stretchr/testify/internal/difflib" "github.com/stretchr/testify/internal/spew" diff --git a/tools/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go b/tools/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go index 5a74c4f4d..956227ca2 100644 --- a/tools/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go +++ b/tools/vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go @@ -7,7 +7,7 @@ // go test -tags testify_yaml_custom // // This implementation can be used at build time to replace the default implementation -// to avoid linking with [gopkg.in/yaml.v3]. +// to avoid linking with [go.yaml.in/yaml/v3]. // // In your test package: // diff --git a/tools/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go b/tools/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go index 0bae80e34..dd89ac03a 100644 --- a/tools/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go +++ b/tools/vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go @@ -6,7 +6,7 @@ // indirection with an alternative implementation of this package that uses // another implementation of YAML deserialization. This allows to not either not // use YAML deserialization at all, or to use another implementation than -// [gopkg.in/yaml.v3] (for example for license compatibility reasons, see [PR #1120]). +// [go.yaml.in/yaml/v3] (for example for license compatibility reasons, see [PR #1120]). // // Alternative implementations are selected using build tags: // @@ -28,9 +28,9 @@ // [PR #1120]: https://github.com/stretchr/testify/pull/1120 package yaml -import goyaml "gopkg.in/yaml.v3" +import goyaml "go.yaml.in/yaml/v3" -// Unmarshal is just a wrapper of [gopkg.in/yaml.v3.Unmarshal]. +// Unmarshal is just a wrapper of [go.yaml.in/yaml/v3.Unmarshal]. func Unmarshal(in []byte, out interface{}) error { return goyaml.Unmarshal(in, out) } diff --git a/tools/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go b/tools/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go index 8041803fd..a51d27925 100644 --- a/tools/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go +++ b/tools/vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go @@ -3,7 +3,7 @@ // Package yaml is an implementation of YAML functions that always fail. // // This implementation can be used at build time to replace the default implementation -// to avoid linking with [gopkg.in/yaml.v3]: +// to avoid linking with [go.yaml.in/yaml/v3]: // // go test -tags testify_yaml_fail package yaml diff --git a/tools/vendor/github.com/uudashr/iface/unusedmethod/doc.go b/tools/vendor/github.com/uudashr/iface/unusedmethod/doc.go new file mode 100644 index 000000000..cef63a7f7 --- /dev/null +++ b/tools/vendor/github.com/uudashr/iface/unusedmethod/doc.go @@ -0,0 +1,3 @@ +// Package unusedmethod defines an Analyzer that detects interface methods which +// are never used anywhere in the same package where they are defined. +package unusedmethod diff --git a/tools/vendor/github.com/uudashr/iface/unusedmethod/unusedmethod.go b/tools/vendor/github.com/uudashr/iface/unusedmethod/unusedmethod.go new file mode 100644 index 000000000..2f671465c --- /dev/null +++ b/tools/vendor/github.com/uudashr/iface/unusedmethod/unusedmethod.go @@ -0,0 +1,226 @@ +package unusedmethod + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "os" + "slices" + "strings" + + "github.com/uudashr/iface/internal/directive" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var Analyzer = newAnalyzer() + +func newAnalyzer() *analysis.Analyzer { + r := runner{} + + analyzer := &analysis.Analyzer{ + Name: "unusedmethod", + Doc: "Detects interface methods that are never used anywhere in the same package where they are defined.", + URL: "https://pkg.go.dev/github.com/uudashr/iface/unusedmethod", + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: r.run, + } + + analyzer.Flags.BoolVar(&r.debug, "nerd", false, "enable nerd mode") + analyzer.Flags.StringVar(&r.exclude, "exclude", "", "comma-separated list of packages to exclude from the check") + + return analyzer +} + +type methodEntry struct { + ifaceName string + field *ast.Field +} + +type runner struct { + debug bool + exclude string +} + +func (r *runner) run(pass *analysis.Pass) (any, error) { + var excludes []string + + if r.exclude != "" { + for _, pkg := range strings.Split(r.exclude, ",") { + if p := strings.TrimSpace(pkg); p != "" { + excludes = append(excludes, p) + } + } + } + + if slices.Contains(excludes, pass.Pkg.Path()) { + return nil, nil + } + + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + unusedMethods := make(map[*types.Func]methodEntry) + + nodeFilter := []ast.Node{ + (*ast.GenDecl)(nil), + } + + inspect.Preorder(nodeFilter, func(n ast.Node) { + decl, ok := n.(*ast.GenDecl) + if !ok { + return + } + + if r.debug { + fmt.Fprintf(os.Stderr, "GenDecl: %v specs=%d\n", decl.Tok, len(decl.Specs)) + } + + if decl.Tok != token.TYPE { + return + } + + if directive.ShouldIgnore(decl.Doc, pass.Analyzer.Name) { + return + } + + for i, spec := range decl.Specs { + r.debugf(" spec[%d]: %v %T\n", i, spec, spec) + + ts, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + + ifaceType, ok := ts.Type.(*ast.InterfaceType) + if !ok { + continue + } + + r.debugln(" -> Interface type declaration:", ts.Name.Name) + + if directive.ShouldIgnore(ts.Doc, pass.Analyzer.Name) { + continue + } + + for j, field := range ifaceType.Methods.List { + switch ft := field.Type.(type) { + case *ast.FuncType: + name := field.Names[0] + obj := pass.TypesInfo.Defs[name] + + if r.debug { + fmt.Fprintf(os.Stderr, " [%d] Field: func %s %T\n", j, name.Name, ft) + fmt.Fprintf(os.Stderr, " obj: %v %T %p\n", obj, obj, obj) + } + + if directive.ShouldIgnore(field.Doc, pass.Analyzer.Name) { + continue + } + + if directive.ShouldIgnore(field.Comment, pass.Analyzer.Name) { + continue + } + + if fn, ok := obj.(*types.Func); ok { + unusedMethods[fn] = methodEntry{ + ifaceName: ts.Name.Name, + field: field, + } + } + default: + r.debugf(" [%d] Field: unknown %v %T\n", j, ft, ft) + } + } + } + }) + + // Method is always SelectorExpr + nodeFilter = []ast.Node{ + (*ast.SelectorExpr)(nil), + } + + r.debugln("Method usage") + inspect.Preorder(nodeFilter, func(n ast.Node) { + r.debugf(" n %v %T\n", n, n) + + selExp, ok := n.(*ast.SelectorExpr) + if !ok { + return + } + + r.debugf(" selExp sel: %v %T, x: %v %T\n", selExp.Sel, selExp.Sel, selExp.X, selExp.X) + + sel, ok := pass.TypesInfo.Selections[selExp] + if !ok { + return + } + + if r.debug { + fmt.Fprintf(os.Stderr, " sel -> %v %T, kind: %v %T, obj: %v %T\n", sel, sel, sel.Kind(), sel.Kind(), sel.Obj(), sel.Obj()) + } + + fn, ok := sel.Obj().(*types.Func) + if !ok { + return + } + + delete(unusedMethods, fn) + }) + + if r.debug { + fmt.Fprintf(os.Stderr, "Unused methods %d\n", len(unusedMethods)) + } + + for fn, entry := range unusedMethods { + if r.debug { + fmt.Fprintf(os.Stderr, " %v %T %s\n", fn, fn, fn.Name()) + } + + msg := fmt.Sprintf("method '%s()' is declared on interface '%s' but not used within the package", fn.Name(), entry.ifaceName) + + field := entry.field + + pos := field.Pos() + if doc := field.Doc; doc != nil { + pos = doc.Pos() + } + + end := field.End() + if comment := field.Comment; comment != nil { + end = comment.End() + } + + pass.Report(analysis.Diagnostic{ + Pos: field.Pos(), + Message: msg, + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Remove the unused method", + TextEdits: []analysis.TextEdit{ + { + Pos: pos, + End: end, + NewText: []byte{}, + }, + }, + }, + }, + }) + } + + return nil, nil +} + +func (r *runner) debugln(a ...any) { + if r.debug { + fmt.Fprintln(os.Stderr, a...) + } +} + +func (r *runner) debugf(format string, a ...any) { + if r.debug { + fmt.Fprintf(os.Stderr, format, a...) + } +} diff --git a/tools/vendor/honnef.co/go/tools/analysis/code/code.go b/tools/vendor/honnef.co/go/tools/analysis/code/code.go index 9e4791921..e87a1d1df 100644 --- a/tools/vendor/honnef.co/go/tools/analysis/code/code.go +++ b/tools/vendor/honnef.co/go/tools/analysis/code/code.go @@ -16,7 +16,6 @@ import ( "honnef.co/go/tools/analysis/facts/generated" "honnef.co/go/tools/analysis/facts/purity" "honnef.co/go/tools/analysis/facts/tokenfile" - "honnef.co/go/tools/go/ast/astutil" "honnef.co/go/tools/go/types/typeutil" "honnef.co/go/tools/knowledge" "honnef.co/go/tools/pattern" @@ -92,15 +91,18 @@ func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string { info := pass.TypesInfo sel := info.Selections[expr] if sel == nil { - if x, ok := expr.X.(*ast.Ident); ok { + switch x := expr.X.(type) { + case *ast.Ident: pkg, ok := info.ObjectOf(x).(*types.PkgName) if !ok { - // This shouldn't happen - return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name) + return fmt.Sprintf("(%s).%s", info.TypeOf(x), expr.Sel.Name) } return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name) + case *ast.SelectorExpr: + return fmt.Sprintf("(%s).%s", SelectorName(pass, x), expr.Sel.Name) + default: + panic(fmt.Sprintf("unsupported selector: %v", expr)) } - panic(fmt.Sprintf("unsupported selector: %v", expr)) } if v, ok := sel.Obj().(*types.Var); ok && v.IsField() { return fmt.Sprintf("(%s).%s", typeutil.DereferenceR(sel.Recv()), sel.Obj().Name()) @@ -169,7 +171,7 @@ func CallName(pass *analysis.Pass, call *ast.CallExpr) string { // See the comment in typeutil.FuncName for why this doesn't require special handling // of aliases. - fun := astutil.Unparen(call.Fun) + fun := ast.Unparen(call.Fun) // Instantiating a function cannot return another generic function, so doing this once is enough switch idx := fun.(type) { diff --git a/tools/vendor/honnef.co/go/tools/analysis/code/visit.go b/tools/vendor/honnef.co/go/tools/analysis/code/visit.go index 83b585d90..18271e919 100644 --- a/tools/vendor/honnef.co/go/tools/analysis/code/visit.go +++ b/tools/vendor/honnef.co/go/tools/analysis/code/visit.go @@ -9,8 +9,8 @@ import ( "iter" "slices" - typeindexanalyzer "honnef.co/go/tools/internal/analysisinternal/typeindex" - "honnef.co/go/tools/internal/typesinternal/typeindex" + typeindexanalyzer "honnef.co/go/tools/internal/xtools-internal/analysis/typeindex" + "honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex" "honnef.co/go/tools/pattern" "golang.org/x/tools/go/analysis" diff --git a/tools/vendor/honnef.co/go/tools/analysis/dfa/dense/flow.go b/tools/vendor/honnef.co/go/tools/analysis/dfa/dense/flow.go new file mode 100644 index 000000000..efa573c5d --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/analysis/dfa/dense/flow.go @@ -0,0 +1,60 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package flow implements a monotone flow analysis framework. +package dense + +import ( + "cmp" + "slices" + + "honnef.co/go/tools/internal/xtools-internal/graph" +) + +const debug = false + +// Analysis is the result of a monotone analysis. Fact is the type of elements +// in the analysis semilattice, and represents the outcome of the analysis at +// every node and edge. +type Analysis[Fact any, NodeID comparable] struct { + nodeMap *graph.Index[NodeID] + ins []Fact // By NodeID + edges []edgeFact[Fact] // Sorted by (from, to) +} + +// In returns the analysis fact on entry to nid. This is the merge of the facts +// on all incoming edges. +func (a *Analysis[Fact, NodeID]) In(nid NodeID) Fact { + return a.ins[a.nodeMap.Index(nid)] +} + +// Edge returns the analysis fact propagated on edge from ==> to. +func (a *Analysis[Fact, NodeID]) Edge(from, to NodeID) Fact { + i, found := slices.BinarySearchFunc(a.edges, a.edge(from, to), edgeFact[Fact].compare) + if !found { + panic("no such edge") + } + return a.edges[i].fact +} + +func (a *Analysis[Fact, NodeID]) edge(from, to NodeID) edge { + fromNum, toNum := a.nodeMap.Index(from), a.nodeMap.Index(to) + return edge{fromNum, toNum} +} + +type edge struct { + from, to int +} + +func (e edge) compare(f edge) int { + if v := cmp.Compare(e.from, f.from); v != 0 { + return v + } + return cmp.Compare(e.to, f.to) +} + +type edgeFact[Fact any] struct { + edge + fact Fact +} diff --git a/tools/vendor/honnef.co/go/tools/analysis/dfa/dense/forward.go b/tools/vendor/honnef.co/go/tools/analysis/dfa/dense/forward.go new file mode 100644 index 000000000..7837f6409 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/analysis/dfa/dense/forward.go @@ -0,0 +1,271 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dense + +import ( + "container/heap" + "log" + "slices" + + "honnef.co/go/tools/analysis/dfa" + "honnef.co/go/tools/internal/xtools-internal/graph" +) + +// Forward performs a forward monotone analysis over a control flow graph. +// +// The entry map provides initial state for entry blocks (blocks with zero +// predecessors). For each edge, it calls transfer(fact, edge), where fact is +// the analysis state on entry to edge.Pred. The transfer function must return +// the outgoing analysis state of the edge (which may be fact, if the edge has +// no effect on the analysis state). +func Forward[L dfa.Semilattice[Fact], Fact any, NodeID comparable](g graph.Graph[NodeID], entry map[NodeID]Fact, transfer func(from, to NodeID, fact Fact) Fact) *Analysis[Fact, NodeID] { + cg, nodeMap := graph.Compact(g) + + nNodes := cg.NumNodes() + fb := &fwdBuilder[L, Fact, NodeID]{ + cfg: cg, + nodeMap: nodeMap, + transfer: transfer, + blocks: make([]blockInfo[Fact], nNodes), + } + fb.queue.init(cg) + + // Initialize each node. + totalEdges := 0 + for ni := range nNodes { + b := &fb.blocks[ni] + + // Construct back-edges. + // + // I experimented with making Graph support iterating over in-edges, but + // in practice that just meant each Graph implementation had a copy of + // this logic. So instead we keep Graph as simple as possible and + // compute the auxiliary data in the algorithm. One drawback of this is + // that, for the [Transpose] graph, this information is redundant with + // the underlying graph. We could potentially special-case that. + outs := 0 + for succID := range cg.Out(ni) { + succ := &fb.blocks[succID] + succ.preds = append(succ.preds, blockEdge{ni, outs}) + outs++ + totalEdges++ + } + + // Initialize in & out states. + fact, ok := entry[nodeMap.Value(ni)] + if !ok { + fact = fb.l.Ident() + } + b.in = fact + b.out = slices.Repeat([]Fact{fb.l.Ident()}, outs) + + // Enqueue block. + // + // It's tempting to enqueue only the entry blocks, but this is wrong. + // The entry map may be empty if there are no interesting entry states, + // but the transfer function may still introduce interesting states + // anywhere. + b.dirty = true + fb.queue.enqueue(ni) + } + + // Propagate over blocks. + fb.propagate() + + // Collect the final analysis results. + a := Analysis[Fact, NodeID]{ + nodeMap: nodeMap, + ins: make([]Fact, nNodes), + edges: make([]edgeFact[Fact], 0, totalEdges), + } + for pred := range nNodes { + a.ins[pred] = fb.blocks[pred].in + i := 0 + for succ := range cg.Out(pred) { + edge := edge{pred, succ} + a.edges = append(a.edges, edgeFact[Fact]{edge, fb.blocks[pred].out[i]}) + i++ + } + } + slices.SortFunc(a.edges, func(a, b edgeFact[Fact]) int { return a.edge.compare(b.edge) }) + return &a +} + +// fwdBuilder is the state used during [Forward] analysis. +type fwdBuilder[L dfa.Semilattice[Fact], Fact any, NodeID comparable] struct { + l L // Lattice + + cfg graph.Graph[int] // Control flow graph (compact) + nodeMap *graph.Index[NodeID] // Map from cfg to original NodeIDs + + // transfer is the edge transfer function. + transfer func(from, to NodeID, fact Fact) Fact + + blocks []blockInfo[Fact] + + queue nodeHeap +} + +type blockInfo[Fact any] struct { + dirty bool // The in fact has never been propagated. + + preds []blockEdge + + in Fact + out []Fact // Corresponds to i'th out edge +} + +type blockEdge struct { + node int + i int // Out edge index +} + +// nodeHeap implements a heap of NodeIDs, ordered topologically. +// +// We use this ordering so forward analysis converges more quickly. +type nodeHeap struct { + heap []int // Remaining nodes in the current sweep + deferred []int // Nodes of next sweep + inQueue []int64 // Bitmap over node IDs + prio []int // NodeID -> priority + currentPrio int // Priority of last dequeued node, or -1 +} + +func (h *nodeHeap) init(g graph.Graph[int]) { + nNodes := g.NumNodes() + *h = nodeHeap{ + inQueue: make([]int64, (nNodes+63)/64), + prio: make([]int, nNodes), + currentPrio: -1, + } + for p, nid := range graph.ReversePostorder(g) { + h.prio[nid] = p + } +} + +func (h *nodeHeap) enqueue(nid int) { + if h.inQueue[nid/64]&(1<<(nid%64)) != 0 { + return + } + h.inQueue[nid/64] |= 1 << (nid % 64) + + if h.currentPrio >= 0 && h.prio[nid] <= h.currentPrio { + // This is a retreating edge, self-edge, or other update to a node + // already passed in this sweep. Coalesce it into the next sweep. + h.deferred = append(h.deferred, nid) + } else { + heap.Push(h, nid) + } +} + +func (h *nodeHeap) dequeue() int { + if len(h.heap) == 0 { + // Start the next RPO sweep. + h.heap, h.deferred = h.deferred, h.heap[:0] + h.currentPrio = -1 + heap.Init(h) + } + + nid := h.heap[0] + heap.Pop(h) + h.inQueue[nid/64] &^= 1 << (nid % 64) + h.currentPrio = h.prio[nid] + return nid +} + +func (h *nodeHeap) pending() bool { return len(h.heap) != 0 || len(h.deferred) != 0 } +func (h nodeHeap) Len() int { return len(h.heap) } +func (h nodeHeap) Less(i, j int) bool { return h.prio[h.heap[i]] < h.prio[h.heap[j]] } +func (h nodeHeap) Swap(i, j int) { h.heap[i], h.heap[j] = h.heap[j], h.heap[i] } +func (h *nodeHeap) Push(x any) { h.heap = append(h.heap, x.(int)) } +func (h *nodeHeap) Pop() any { + n := len(h.heap) + x := h.heap[n-1] + h.heap = h.heap[:n-1] + return x +} + +func (fb *fwdBuilder[L, Fact, NodeID]) merge(a, b Fact) Fact { + if fb.l.Equals(a, b) { + return a + } + return fb.l.Merge(a, b) +} + +func (fb *fwdBuilder[L, Fact, NodeID]) propagate() { + for fb.queue.pending() { + bi := fb.queue.dequeue() + block := &fb.blocks[bi] + + // Merge predecessor facts to compute updated "in" fact. + var in Fact + first := true + for _, edge := range block.preds { + pred := &fb.blocks[edge.node] + var edgeFact Fact + if pred.dirty { + // We haven't visited this predecessor yet, so it doesn't have + // meaningful out facts. + edgeFact = fb.l.Ident() + } else { + edgeFact = pred.out[edge.i] + } + if first { + if debug { + log.Printf("propagate to node %d", bi) + } + in = edgeFact + first = false + } else { + in = fb.merge(in, edgeFact) + } + if debug { + log.Printf(" from node %d: %v", edge.node, edgeFact) + } + } + if first { + // No predecessors. + if debug { + log.Printf("node %d gets initial state", bi) + } + in = block.in + } + + if !block.dirty && fb.l.Equals(in, block.in) { + // No change to block input, which means the transfer function + // results also won't change from the last time we ran it. + if debug { + log.Printf(" initial state unchanged: %v", in) + } + continue + } + if debug { + log.Printf(" new initial state: %v", in) + } + block.in = in + + // Apply transfer function. + predID := fb.nodeMap.Value(bi) + i := 0 + for succNum := range fb.cfg.Out(bi) { + edgeFact := fb.transfer(predID, fb.nodeMap.Value(succNum), in) + if block.dirty || !fb.l.Equals(block.out[i], edgeFact) { + // Out fact changed, so recompute the target block. + if debug { + log.Printf(" to node %d: %v", succNum, edgeFact) + } + block.out[i] = edgeFact + fb.queue.enqueue(succNum) + } else { + if debug { + log.Printf(" to node %d: no change", succNum) + } + } + i++ + } + + block.dirty = false + } +} diff --git a/tools/vendor/honnef.co/go/tools/analysis/dfa/dot.go b/tools/vendor/honnef.co/go/tools/analysis/dfa/dot.go new file mode 100644 index 000000000..1895aa846 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/analysis/dfa/dot.go @@ -0,0 +1,48 @@ +package dfa + +import ( + "fmt" + "strings" +) + +// Dot returns a directed graph in [Graphviz] format that represents the finite +// join-semilattice ⟨S, ≤⟩. Vertices represent elements in S and edges +// represent the ≤ relation between elements. We map from ⟨S, ∨⟩ to ⟨S, ≤⟩ by +// computing x ∨ y for all elements in [S]², where x ≤ y iff x ∨ y == y. +// +// The resulting graph can be filtered through [tred] to compute the transitive +// reduction of the graph, the visualisation of which corresponds to the Hasse +// diagram of the semilattice. +// +// [Graphviz]: https://graphviz.org/ +// [tred]: https://graphviz.org/docs/cli/tred/ +func Dot[L Semilattice[Elem], Elem any](states []Elem) string { + var sb strings.Builder + sb.WriteString("digraph{\n") + sb.WriteString("rankdir=\"BT\"\n") + + for i, v := range states { + if vs, ok := any(v).(fmt.Stringer); ok { + fmt.Fprintf(&sb, "n%d [label=%q]\n", i, vs) + } else { + fmt.Fprintf(&sb, "n%d [label=%q]\n", i, fmt.Sprintf("%v", v)) + } + } + + var l L + + for dx, x := range states { + for dy, y := range states { + if dx == dy { + continue + } + + if l.Equals(l.Merge(x, y), y) { + fmt.Fprintf(&sb, "n%d -> n%d\n", dx, dy) + } + } + } + + sb.WriteString("}") + return sb.String() +} diff --git a/tools/vendor/honnef.co/go/tools/analysis/dfa/lattice.go b/tools/vendor/honnef.co/go/tools/analysis/dfa/lattice.go new file mode 100644 index 000000000..672875b6b --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/analysis/dfa/lattice.go @@ -0,0 +1,147 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dfa + +import ( + "fmt" + "maps" + "slices" +) + +// A Semilattice describes a bounded semilattice over Elem. +// That is, a partial order over values of type Elem, with a binary +// Merge operator and an identity element. +// +// This is typically implemented by a stateless type, and acts as a factory for +// lattice elements. +type Semilattice[Elem any] interface { + // Ident returns the identity element of this lattice, that is the unit of + // the Merge operation. + Ident() Elem + + // Equals returns whether a and b are the same element. + Equals(a, b Elem) bool + + // Merge combines two lattice values, such as the two possible values of a + // variable at the end of an if/else statement. + // + // Merge must satisfy the following identities, where we use ∧ for Merge, = + // for Equals, and 𝟏 for Ident: + // + // - Associativity: x ∧ (y ∧ z) = (x ∧ y) ∧ z + // - Commutativity: x ∧ y = y ∧ x + // - Idempotency: x ∧ x = x + // - Identity: x ∧ 𝟏 = x + Merge(a, b Elem) Elem +} + +// A MapLattice implements [Semilattice][map[Key]Elem]. The values in the map +// are themselves defined by [Semilattice] L. +// +// Any elements missing from the map are implicitly L's identity element, and +// L's identity element never appears as a value in the map. +// +// For densely numbered keys, consider using [DenseMapLattice] instead. +type MapLattice[Key comparable, Elem any, L Semilattice[Elem]] struct { + l L +} + +func (m MapLattice[Key, Elem, L]) Ident() map[Key]Elem { + return nil +} + +func (m MapLattice[Key, Elem, L]) Equals(a, b map[Key]Elem) bool { + return maps.EqualFunc(a, b, m.l.Equals) +} + +func (m MapLattice[Key, Elem, L]) Merge(a, b map[Key]Elem) map[Key]Elem { + if len(a) == 0 { + return b + } else if len(b) == 0 { + return a + } + + // We need to consider the union of keys in a and b. + out := make(map[Key]Elem) + id := m.l.Ident() + for k, av := range a { + bv, ok := b[k] + if !ok { + // Because Merge(x, Ident()) == x, we can skip calling L.Merge. + out[k] = av + continue + } + + w := m.l.Merge(av, bv) + if m.l.Equals(w, id) { + // In a semilattice, Merge(x, y) = Ident is only possible when x == + // Ident and y == Ident. + panic(fmt.Sprintf( + "%T is not a semilattice: Merge(%v, %v) returned Ident for non-Ident arguments", + m.l, av, bv)) + } + out[k] = w + } + // We considered keys that are only in a, and in both a and b. Now we just + // need to handle keys that are only in b. + for k, v2 := range b { + if _, ok := a[k]; !ok { + out[k] = v2 + } + } + + return out +} + +// A DenseMapLattice implements [Semilattice][[]Elem]. It is like a [MapLattice] +// that is indexed by integers. The values in the map are themselves defined by +// [Semilattice] L. +// +// Unlike [MapLattice], L's identity element may appear as a value in the map, +// to allow for gaps in the numbering of keys when the identity element is +// Elem's zero value. +type DenseMapLattice[Elem any, L Semilattice[Elem]] struct { + l L +} + +func (s DenseMapLattice[Elem, L]) Ident() []Elem { + return nil +} + +func (s DenseMapLattice[Elem, L]) Equals(a, b []Elem) bool { + nmin := min(len(a), len(b)) + ident := s.l.Ident() + + // Check that up to nmin, all elements in a and b match. If one of a or b + // is longer, then its tail nmin:nmax must only contain identity elements. + return slices.EqualFunc(a[:nmin], b[:nmin], s.l.Equals) && + !slices.ContainsFunc(a[nmin:], func(e Elem) bool { + return !s.l.Equals(e, ident) + }) && + !slices.ContainsFunc(b[nmin:], func(e Elem) bool { + return !s.l.Equals(e, ident) + }) +} + +func (s DenseMapLattice[Elem, L]) Merge(a, b []Elem) []Elem { + if len(a) == 0 { + return b + } else if len(b) == 0 { + return a + } + out := make([]Elem, max(len(a), len(b))) + for k := range max(len(a), len(b)) { + av := s.l.Ident() + bv := s.l.Ident() + if k < len(a) { + av = a[k] + } + if k < len(b) { + bv = b[k] + } + out[k] = s.l.Merge(av, bv) + } + return out +} diff --git a/tools/vendor/honnef.co/go/tools/analysis/facts/nilness/nilness.go b/tools/vendor/honnef.co/go/tools/analysis/facts/nilness/nilness.go index a7c91a7aa..14198f816 100644 --- a/tools/vendor/honnef.co/go/tools/analysis/facts/nilness/nilness.go +++ b/tools/vendor/honnef.co/go/tools/analysis/facts/nilness/nilness.go @@ -2,203 +2,479 @@ package nilness import ( "fmt" + "go/constant" "go/token" "go/types" "reflect" + "slices" + "strings" + "honnef.co/go/tools/analysis/dfa" + "honnef.co/go/tools/analysis/dfa/dense" "honnef.co/go/tools/go/ir" "honnef.co/go/tools/go/types/typeutil" "honnef.co/go/tools/internal/passes/buildir" + "golang.org/x/exp/typeparams" "golang.org/x/tools/go/analysis" ) -// neverReturnsNilFact denotes that a function's return value will never -// be nil (typed or untyped). The analysis errs on the side of false -// negatives. -type neverReturnsNilFact struct { - Rets []neverNilness +// TODO(dh): The analysis is currently entirely forward, which means that for +// +// x := s[:0] +// y := s[:1] +// z := s[:0] +// +// x will have MaybeNil at every program point and s will have MaybeNil before +// execution of y, even though executing y without panicing tells us that s has +// been non-nil for all 3 instructions. + +type nilnessFact struct { + Rets []ValueNilness } -func (*neverReturnsNilFact) AFact() {} -func (fact *neverReturnsNilFact) String() string { - return fmt.Sprintf("never returns nil: %v", fact.Rets) +func (*nilnessFact) AFact() {} +func (fact *nilnessFact) String() string { + return fmt.Sprintf("nilness: %v", fact.Rets) +} + +type ValueNilness struct { + // Undefined for non-interface values. + // For interface values, whether the stored value may be nil. + // Even when Outer == MaybeNil, Inner may still offer precise information + // for the cases when Outer is dynamically not nil. For example, {NeverNil, + // MaybeNil} states that the interface value might be nil, but if it isn't, + // it will definitely contain a non-nil value. + Inner Nilness + // For non-interface values, whether the value may be nil. + // For interface values, whether the interface value may be nil. + Outer Nilness } type Result struct { - m map[*types.Func][]neverNilness + m map[*types.Func][]ValueNilness } var Analysis = &analysis.Analyzer{ Name: "nilness", - Doc: "Annotates return values that will never be nil (typed or untyped)", + Doc: "Annotates return values with their nilness", Run: run, Requires: []*analysis.Analyzer{buildir.Analyzer}, - FactTypes: []analysis.Fact{(*neverReturnsNilFact)(nil)}, + FactTypes: []analysis.Fact{(*nilnessFact)(nil)}, ResultType: reflect.TypeFor[*Result](), } -// MayReturnNil reports whether the ret's return value of fn might be -// a typed or untyped nil value. The value of ret is zero-based. When -// globalOnly is true, the only possible nil values are global -// variables. -// -// The analysis has false positives: MayReturnNil can incorrectly -// report true, but never incorrectly reports false. -func (r *Result) MayReturnNil(fn *types.Func, ret int) (yes bool, globalOnly bool) { - if !typeutil.IsPointerLike(fn.Type().(*types.Signature).Results().At(ret).Type()) { - return false, false +// Nilness returns nilness information for return value ret of fn. +func (r *Result) Nilness(fn *types.Func, ret int) ValueNilness { + typ := fn.Type().(*types.Signature).Results().At(ret).Type() + if !typeutil.MaybePointerLike(typ) { + return ValueNilness{Outer: NeverNil} } if len(r.m[fn]) == 0 { - return true, false + return ValueNilness{Inner: MaybeNil, Outer: MaybeNil} } - v := r.m[fn][ret] - return v != neverNil, v == onlyGlobal + return normalize(r.m[fn][ret], typ) +} + +func normalize(v ValueNilness, typ types.Type) ValueNilness { + if v.Inner == 0 || !types.IsInterface(typ) { + v.Inner = MaybeNil + } + if v.Outer == 0 { + v.Outer = MaybeNil + } + return v } func run(pass *analysis.Pass) (any, error) { seen := map[*ir.Function]struct{}{} out := &Result{ - m: map[*types.Func][]neverNilness{}, + m: map[*types.Func][]ValueNilness{}, } + + // TODO(dh): instead of recursion and giving up on mutual recursion, we + // should compute the DFA over the call graph, at least until we have + // proper function summaries. for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { impl(pass, fn, seen) } for _, fact := range pass.AllObjectFacts() { - out.m[fact.Object.(*types.Func)] = fact.Fact.(*neverReturnsNilFact).Rets + out.m[fact.Object.(*types.Func)] = fact.Fact.(*nilnessFact).Rets } return out, nil } -type neverNilness uint8 +type Nilness uint8 const ( - neverNil neverNilness = 1 - onlyGlobal neverNilness = 2 - nilly neverNilness = 3 + // The value is never nil. + NeverNil Nilness = iota + 1 + // The value is always nil. + AlwaysNil + // The value might be nil, but only because of the value of a global + // variable. + MaybeNilGlobal + // The value might be nil. + MaybeNil ) -func (n neverNilness) String() string { +func (n Nilness) String() string { switch n { - case neverNil: - return "never" - case onlyGlobal: - return "global" - case nilly: - return "nil" + case 0: + return "NoNilness" + case NeverNil: + return "NeverNil" + case AlwaysNil: + return "AlwaysNil" + case MaybeNilGlobal: + return "MaybeNilGlobal" + case MaybeNil: + return "MaybeNil" default: - return "BUG" + return "InvalidNilness" + } +} + +type state struct { + cloned bool + m []ValueNilness + n numbering +} + +func (s *state) get(v ir.Value) ValueNilness { + if !typeutil.MaybePointerLike(v.Type()) { + // All non-pointer-like types are always {_ NeverNil}. + return ValueNilness{Outer: NeverNil} + } + num := s.n.number(v) + if num < len(s.m) { + return s.m[num] + } + + switch v.(type) { + case *ir.Parameter: + return ValueNilness{Inner: MaybeNil, Outer: MaybeNil} + case *ir.Builtin: + return ValueNilness{Outer: NeverNil} + case *ir.FreeVar: + return ValueNilness{Inner: MaybeNil, Outer: MaybeNil} + case *ir.Function: + return ValueNilness{Outer: NeverNil} + case *ir.Global: + // Globals are addresses, not the values stored in them. The addresses + // cannot be nil. + return ValueNilness{Outer: NeverNil} + } + + return lattice{}.Ident() +} + +func (s *state) set(key ir.Value, value ValueNilness) { + if !typeutil.MaybePointerLike(key.Type()) { + // No point in recording state for non-pointer-like types. They're + // always {_ NeverNil}. + return + } + + if value == (lattice{}.Ident()) { + // No point in storing the default value. + return + } + num := s.n.number(key) + if !s.cloned { + if num < len(s.m) && s.m[num] == value { + // Don't clone if the value already matches. + return + } + s.cloned = true + s.m = slices.Clone(s.m) + } + if num >= len(s.m) { + s.m = append(s.m, make([]ValueNilness, num-len(s.m)+1)...) + } + s.m[num] = value +} + +func (s *state) setInner(key ir.Value, value Nilness) { + if !typeutil.MaybePointerLike(key.Type()) { + return + } + if value == (lattice{}.Ident().Inner) { + return + } + num := s.n.number(key) + if !s.cloned { + if num < len(s.m) && s.m[num].Inner == value { + // Don't clone if the value already matches. + return + } + s.cloned = true + s.m = slices.Clone(s.m) + } + if num >= len(s.m) { + dflt := s.get(key) + s.m = append(s.m, make([]ValueNilness, num-len(s.m)+1)...) + s.m[num] = dflt + } + v := s.m[num] + v.Inner = value + s.m[num] = v +} + +func (s *state) setOuter(key ir.Value, value Nilness) { + if !typeutil.MaybePointerLike(key.Type()) { + return + } + if value == (lattice{}).Ident().Outer { + return + } + num := s.n.number(key) + if !s.cloned { + if num < len(s.m) && s.m[num].Outer == value { + // Don't clone if the value already matches. + return + } + s.cloned = true + s.m = slices.Clone(s.m) + } + if num >= len(s.m) { + dflt := s.get(key) + s.m = append(s.m, make([]ValueNilness, num-len(s.m)+1)...) + s.m[num] = dflt } + v := s.m[num] + v.Outer = value + s.m[num] = v } -func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) []neverNilness { +func defaultNilnessForSignature(pass *analysis.Pass, typ *types.Signature) []ValueNilness { + n := typ.Results().Len() + if n == 0 { + return nil + } + out := make([]ValueNilness, n) + for i := range n { + out[i] = defaultNilness(pass, typ.Results().At(i).Type()) + } + return out +} + +func defaultNilness(pass *analysis.Pass, typ types.Type) ValueNilness { + if typeutil.MaybePointerLike(typ) { + // IsPointerLike handles type parameters with type sets, too. + return ValueNilness{MaybeNil, MaybeNil} + } else { + return ValueNilness{NeverNil, NeverNil} + } +} + +func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) []ValueNilness { + goto start +bailout: + return defaultNilnessForSignature(pass, fn.Signature) + +start: + if fn.Signature.Results().Len() == 0 { + return nil + } if fn.Object() == nil { // TODO(dh): support closures - return nil + goto bailout } - if fact := new(neverReturnsNilFact); pass.ImportObjectFact(fn.Object(), fact) { + if fact := new(nilnessFact); pass.ImportObjectFact(fn.Object(), fact) { return fact.Rets } if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg { - return nil + goto bailout } if fn.Blocks == nil { - return nil + goto bailout } if _, ok := seenFns[fn]; ok { // break recursion - return nil + goto bailout } seenFns[fn] = struct{}{} - seen := map[ir.Value]struct{}{} - - var mightReturnNil func(v ir.Value) neverNilness - mightReturnNil = func(v ir.Value) neverNilness { - if _, ok := seen[v]; ok { - // break cycle - return nilly - } - if !typeutil.IsPointerLike(v.Type()) { - return neverNil + anyPointers := false + for ret := range fn.Signature.Results().Variables() { + if typeutil.MaybePointerLike(ret.Type()) { + anyPointers = true + break } - seen[v] = struct{}{} - switch v := v.(type) { - case *ir.MakeInterface: - return mightReturnNil(v.X) - case *ir.Convert: - return mightReturnNil(v.X) - case *ir.SliceToArrayPointer: - if typeutil.CoreType(v.Type()).(*types.Pointer).Elem().Underlying().(*types.Array).Len() == 0 { - return mightReturnNil(v.X) - } else { - // converting a slice to an array pointer of length > 0 panics if the slice is nil - return neverNil - } - case *ir.Slice: - return mightReturnNil(v.X) - case *ir.Phi: - ret := neverNil - for _, e := range v.Edges { - if n := mightReturnNil(e); n > ret { - ret = n - } + } + + if !anyPointers { + goto bailout + } + + n := numbering{} + + processBlock := func(from, to *ir.BasicBlock, s state) state { + handleReturnValue := func(v ir.Value, call *ir.Call, idx int) { + typ := call.Common().Signature().Results().At(idx).Type() + if !typeutil.MaybePointerLike(typ) { + s.setOuter(v, NeverNil) + return } - return ret - case *ir.Extract: - switch d := v.Tuple.(type) { - case *ir.Call: - if callee := d.Call.StaticCallee(); callee != nil { - ret := impl(pass, callee, seenFns) - if len(ret) == 0 { - return nilly + + if callee, ok := call.Call.Value.(*ir.Builtin); ok { + switch callee.Name() { + case "append": + // TODO(dh): if we knew that the varargs had non-zero + // length, we'd know that the resulting slice is non-nil. + switch an := s.get(call.Call.Args[0]).Outer; an { + case MaybeNil, MaybeNilGlobal, NeverNil: + s.setOuter(v, an) + case AlwaysNil: + s.setOuter(v, MaybeNil) } - return ret[v.Index] - } else { - return nilly - } - case *ir.TypeAssert, *ir.Next, *ir.Select, *ir.MapLookup, *ir.TypeSwitch, *ir.Recv, *ir.Sigma: - // we don't need to look at the Extract's index - // because we've already checked its type. - return nilly - default: - panic(fmt.Sprintf("internal error: unhandled type %T", d)) - } - case *ir.Call: - if callee := v.Call.StaticCallee(); callee != nil { - ret := impl(pass, callee, seenFns) - if len(ret) == 0 { - return nilly + case "UnsafeSlice": + // If len is negative, or if ptr is nil and len is not + // zero, unsafe.Slice panics. This implies that a non-nil + // pointer cannot become nil, and vice versa. + s.set(v, s.get(call.Call.Args[0])) + case "UnsafeStringData": + // TODO(dh): if we had string length information we could + // return better information. + s.setOuter(v, MaybeNil) + case "UnsafeSliceData": + // When the slice is non-nil but has zero capacity, the + // returned pointer is still non-nil, so we don't have to + // worry about that. + s.set(v, s.get(call.Call.Args[0])) + case "UnsafeAdd": + // TODO(dh): a positive addend can never result in a nil pointer. + + // Pointer arithmetic can turn nil pointers into non-nil + // ones and vice versa. + s.setOuter(v, MaybeNil) + case "ssa:deferstack": + s.setOuter(v, NeverNil) + case "ssa:wrapnilchk": + s.setOuter(v, NeverNil) + case "recover": + s.setOuter(v, MaybeNil) + default: + panic(fmt.Sprintf("internal error: unhandled builtin %s", callee.Name())) } - return ret[0] - } else { - return nilly + return } - case *ir.BinOp, *ir.UnOp, *ir.Alloc, *ir.FieldAddr, *ir.IndexAddr, *ir.Global, *ir.MakeSlice, *ir.MakeClosure, *ir.Function, *ir.MakeMap, *ir.MakeChan: - return neverNil - case *ir.Sigma: - iff, ok := v.From.Control().(*ir.If) - if !ok { - return nilly + + callee := call.Common().StaticCallee() + if callee == nil { + // We don't know which function is being called. + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + return } - binop, ok := iff.Cond.(*ir.BinOp) - if !ok { - return nilly + calleeNilness := impl(pass, callee, seenFns) + if len(calleeNilness) > idx { + s.set(v, normalize(calleeNilness[idx], typ)) + } else { + s.set(v, ValueNilness{MaybeNil, MaybeNil}) } - isNil := func(v ir.Value) bool { - k, ok := v.(*ir.Const) - if !ok { + } + + for _, instr := range from.Instrs { + // It is tempting to return early when instr is an ir.Value that + // doesn't have pointer type. However, instructions like ir.Load + // tell us something about the value being operated on. + + switch v := instr.(type) { + case *ir.Convert: + s.set(v, s.get(v.X)) + case *ir.SliceToArrayPointer: + // Go does not currently allow (*T)(s) where T is a type + // parameter with a type set consisting of array types, but it + // does allow (T)(s) where T is a type parameter with a type + // set consisting of pointers to array types. + + allNonZero := typeutil.All(v.Type(), func(term *types.Term) bool { + ptr := term.Type().Underlying().(*types.Pointer).Elem() + return typeutil.All(ptr, func(innerTerm *types.Term) bool { + return innerTerm.Type().Underlying().(*types.Array).Len() != 0 + }) + }) + + if allNonZero { + // converting a slice to an array pointer of length > 0 + // panics if the slice is nil + s.setOuter(v, NeverNil) + s.setOuter(v.X, NeverNil) + } else { + s.set(v, s.get(v.X)) + } + case *ir.SliceToArray: + // Pretty much the same logic as SliceToArrayPointer, minus the + // pointer. + + allNonZero := typeutil.All(v.Type(), func(term *types.Term) bool { + return term.Type().Underlying().(*types.Array).Len() != 0 + }) + + if allNonZero { + // converting a slice to an array of length > 0 + // panics if the slice is nil + s.setOuter(v.X, NeverNil) + } + case *ir.Slice: + if typeutil.All(v.X.Type(), typeutil.IsType[*types.Array]) { + // Slicing arrays never results in a nil slice. + s.setOuter(v, NeverNil) + continue + } + + // checkBound returns true if one of the bounds (low, high, + // capacity) has non-zero value. + checkBound := func(v ir.Value) bool { + if v == nil { + return false + } + // TODO(dh): this is where integration with constant + // propagation and value range analysis would be useful. + if k, ok := v.(*ir.Const); ok { + kv, ok := constant.Int64Val(k.Value) + return !ok || kv != 0 + } return false } - return k.Value == nil - } - if binop.X == v.X && isNil(binop.Y) || binop.Y == v.X && isNil(binop.X) { + if checkBound(v.Low) || checkBound(v.High) || checkBound(v.Max) { + // One of the indices is non-zero, which means slicing can + // only succeed if the slicee is not nil. + s.setOuter(v, NeverNil) + s.setOuter(v.X, NeverNil) + } else { + // The new slice is as nilly as the slicee. + s.set(v, s.get(v.X)) + } + + case *ir.If: + cond := v.Cond + binop, ok := cond.(*ir.BinOp) + if !ok { + continue + } + isNil := func(v ir.Value) bool { + k, ok := v.(*ir.Const) + if !ok { + return false + } + return k.Value == nil + } + var target ir.Value + if isNil(binop.X) { + target = binop.Y + } else if isNil(binop.Y) { + target = binop.X + } else { + continue + } op := binop.Op - if v.From.Succs[0] != v.Block() { + if to != from.Succs[0] { // we're in the false branch, negate op switch op { case token.EQL: @@ -211,45 +487,344 @@ func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{ } switch op { case token.EQL: - return nilly + s.set(target, ValueNilness{AlwaysNil, AlwaysNil}) case token.NEQ: - return neverNil + s.setOuter(target, NeverNil) default: panic(fmt.Sprintf("internal error: unhandled token %v", op)) } + + // TODO(dh): also handle comparison of two non-nil values. The + // true branch of neverNil == nilly makes the nilly value neverNil. + case *ir.ChangeType: + s.set(v, s.get(v.X)) + case *ir.MultiConvert: + s.set(v, s.get(v.X)) + case *ir.Load: + if _, ok := v.X.(*ir.Global); ok { + s.setOuter(v, MaybeNilGlobal) + } else { + s.setOuter(v, MaybeNil) + } + s.setOuter(v.X, NeverNil) + case *ir.FieldAddr: + s.setOuter(v.X, NeverNil) + s.setOuter(v, NeverNil) + case *ir.IndexAddr: + s.setOuter(v.X, NeverNil) + s.setOuter(v, NeverNil) + case *ir.Alloc, *ir.MakeMap, *ir.MakeSlice, *ir.MakeClosure, *ir.MakeChan: + s.setOuter(v.(ir.Value), NeverNil) + case *ir.MapUpdate: + s.setOuter(v.Map, NeverNil) + case *ir.Store: + s.setOuter(v.Addr, NeverNil) + case ir.CallInstruction: + // go/defer/calling a nil function fatals/panics + if !v.Common().IsInvoke() { + s.setOuter(v.Common().Value, NeverNil) + } + _, ok := v.(*ir.Call) + if !ok { + // Defer and Go don't produce values + continue + } + if v.Common().Signature().Results().Len() != 1 { + // If the called function doesn't return any values then we + // don't care about it. If it has more than one return + // value, they'll be handled by Extract. + continue + } + + handleReturnValue(v.(ir.Value), v.(*ir.Call), 0) + case *ir.Send: + s.setOuter(v.Chan, NeverNil) + case *ir.Recv: + s.setOuter(v.Chan, NeverNil) + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + case *ir.MakeInterface: + s.set(v, ValueNilness{ + Inner: s.get(v.X).Outer, + Outer: NeverNil, + }) + case *ir.ChangeInterface: + s.set(v, s.get(v.X)) + case *ir.TypeAssert: + if !v.CommaOk { + // The interface value cannot have been nil, or the type + // assertion would have panicked. + s.setOuter(v.X, NeverNil) + + if types.IsInterface(v.Type()) && !typeparams.IsTypeParam(v.Type()) { + // Type asserting to another interface doesn't succeed + // if the assertee was nil. It also results in a new + // interface value. + s.setOuter(v, NeverNil) + s.setInner(v, s.get(v.X).Inner) + } else { + // We've extracted the interface value's inner value. + s.setOuter(v, s.get(v.X).Inner) + } + } else { + // In a comma-ok type assertion, the return type is a + // tuple. There'll be Extract instructions getting the + // individual values, to which we'll attach the nilness + // info. + } + case *ir.TypeSwitch: + // Handled in Extract + case *ir.MapLookup: + if s.get(v.X).Outer == AlwaysNil { + s.set(v, ValueNilness{AlwaysNil, AlwaysNil}) + } else { + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + } + case *ir.Field: + s.set(v.X, ValueNilness{NeverNil, NeverNil}) + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + case *ir.Index: + s.set(v.X, ValueNilness{NeverNil, NeverNil}) + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + + case *ir.Extract: + switch tuple := v.Tuple.(type) { + case *ir.TypeAssert: + // When we get here, the type assertion used the comma-ok + // form, and we don't yet know anything about the result of + // the type assertion. + if v.Index == 0 { + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + } + + // TODO(dh): We should set v's nilness in the true and + // false branches of checks on the ok value. However, ok can be + // used in arbitrary ways, and we're also not set up to handle + // relational facts (ok being true or false affects the value + // of the other Extract). + + case *ir.Call: + handleReturnValue(v, tuple, v.Index) + + case *ir.TypeSwitch: + if v.Index == 0 { + // Index 0 is an integer and not interesting. + continue + } + idx := v.Index - 1 + if idx >= len(tuple.Conds) { + // Default branch + + // If there is an untyped nil case, then being in the + // default branch tells us that the interface value + // isn't nil. + hasNil := slices.ContainsFunc(tuple.Conds, func(typ types.Type) bool { + if typ, ok := typ.(*types.Basic); ok && typ.Kind() == types.UntypedNil { + return true + } + return false + }) + if hasNil { + s.setOuter(tuple.Tag, NeverNil) + } else { + s.setOuter(tuple.Tag, MaybeNil) + } + s.setOuter(v, s.get(tuple.Tag).Inner) + } else { + // There is no Extract for the 'untyped nil' case, + // which means that executing any Extract from a type + // switch implies that the switched-over value wasn't a + // nil interface value. + s.setOuter(tuple.Tag, NeverNil) + typ := tuple.Conds[idx] + if types.IsInterface(typ) && !typeparams.IsTypeParam(typ) { + // Succesfully type asserting to an interface type + // always produces a non-nil interface value. + s.setInner(v, s.get(tuple.Tag).Inner) + s.setOuter(v, NeverNil) + } else { + s.setOuter(v, s.get(tuple.Tag).Inner) + } + } + default: + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + } + case *ir.Select: + if v.Blocking && len(v.States) == 1 { + // If the select doesn't have a default branch and only has + // one state, that state's channel cannot have been nil if + // we finished execution the select. + s.setOuter(v.States[0].Chan, NeverNil) + } + case *ir.Jump, *ir.BlankStore, *ir.Phi, + *ir.Panic, *ir.Return, *ir.RunDefers, *ir.Unreachable, *ir.ConstantSwitch, + *ir.UnOp, *ir.BinOp, *ir.CompositeValue, *ir.Range, *ir.Next: + default: + posn := pass.Fset.PositionFor(v.Pos(), false) + panic(fmt.Sprintf("internal error: unhandled type %T at %s", v, posn)) } - return nilly - case *ir.ChangeType: - return mightReturnNil(v.X) - case *ir.MultiConvert: - return mightReturnNil(v.X) - case *ir.Load: - if _, ok := v.X.(*ir.Global); ok { - return onlyGlobal + } + return s + } + + processPhis := func(b *ir.BasicBlock, i int, s state) state { + for _, instr := range b.Instrs { + if instr, ok := instr.(*ir.Phi); ok { + s.set(instr, s.get(instr.Edges[i])) + } else { + break } - return nilly - case *ir.AggregateConst: - return neverNil - case *ir.TypeAssert, *ir.ChangeInterface, *ir.Field, *ir.Const, *ir.GenericConst, *ir.Index, *ir.MapLookup, *ir.Parameter, *ir.Recv, *ir.TypeSwitch: - return nilly - default: - panic(fmt.Sprintf("internal error: unhandled type %T", v)) } + return s } - ret := fn.Exit.Control().(*ir.Return) - out := make([]neverNilness, len(ret.Results)) - export := false - for i, v := range ret.Results { - // OPT(dh): couldn't we check the result type's pointer-likeness early, and skip - // processing the return value altogether? - v := mightReturnNil(v) - out[i] = v - if v != nilly && typeutil.IsPointerLike(fn.Signature.Results().At(i).Type()) { - export = true + + // Populate default state for non-instruction values we encounter. We + // cannot defer this logic to state.get because control flow merges use + // simple merges of lattice values and won't know about value-specific + // defaults. + entrys := state{cloned: true, n: n} + for _, param := range fn.Params { + if typeutil.MaybePointerLike(param.Type()) { + entrys.set(param, ValueNilness{Inner: MaybeNil, Outer: MaybeNil}) + } else { + // We never track nilness for value types, so they don't have to be + // present in the entry state, either. } } - if export { - pass.ExportObjectFact(fn.Object(), &neverReturnsNilFact{out}) + if strings.HasPrefix(fn.Synthetic, "bound method wrapper") { + // This is a bound method and the bound receiver might be nil + for _, fvar := range fn.FreeVars { + entrys.set(fvar, ValueNilness{Outer: MaybeNil}) + } + } else { + // This is a closure, and closed over variables are allocs, which + // cannot be nil. + for _, fvar := range fn.FreeVars { + entrys.set(fvar, ValueNilness{Outer: NeverNil}) + } } - return out + var ops []*ir.Value + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + ops = instr.Operands(ops[:0]) + for _, pop := range ops { + if op, ok := (*pop).(*ir.Const); ok && typeutil.MaybePointerLike(op.Type()) { + // The only constant pointer-like is nil. + entrys.set(op, ValueNilness{Inner: AlwaysNil, Outer: AlwaysNil}) + } + } + } + } + res := dense.Forward[dfa.DenseMapLattice[ValueNilness, lattice]]( + fn, + map[int][]ValueNilness{0: entrys.m}, + func(fromID, toID int, in []ValueNilness) []ValueNilness { + from := fn.Blocks[fromID] + to := fn.Blocks[toID] + s := state{n: n, m: in} + s = processBlock(from, to, s) + i := slices.Index(to.Preds, from) + s = processPhis(to, i, s) + return s.m + }, + ) + + retNilness := make([]ValueNilness, fn.Signature.Results().Len()) + for b := range fn.Returns() { + ret := b.Control().(*ir.Return) + s := state{n: n, m: res.In(b.Index)} + s = processBlock(b, nil, s) + for i, res := range ret.Results { + retNilness[i] = lattice{}.Merge(retNilness[i], s.get(res)) + } + } + + interesting := false + for i := range retNilness { + typ := fn.Signature.Results().At(i).Type() + if !typeutil.MaybePointerLike(typ) { + retNilness[i] = ValueNilness{NeverNil, NeverNil} + continue + } + retNilness[i] = normalize(retNilness[i], typ) + if retNilness[i] != (ValueNilness{MaybeNil, MaybeNil}) { + interesting = true + } + } + + if interesting { + pass.ExportObjectFact(fn.Object(), &nilnessFact{retNilness}) + } + + return retNilness +} + +type lattice struct{} + +var _ dfa.Semilattice[ValueNilness] = lattice{} + +// Equals implements [dfa.Semilattice]. +func (l lattice) Equals(a, b ValueNilness) bool { + return a == b +} + +// Ident implements [dfa.Semilattice]. +func (l lattice) Ident() ValueNilness { + return ValueNilness{} +} + +var latticeMerge = [5][5]Nilness{ + 0: { + 0: 0, + NeverNil: NeverNil, + AlwaysNil: AlwaysNil, + MaybeNilGlobal: MaybeNilGlobal, + MaybeNil: MaybeNil, + }, + NeverNil: { + 0: NeverNil, + NeverNil: NeverNil, + AlwaysNil: MaybeNil, + MaybeNilGlobal: MaybeNilGlobal, + MaybeNil: MaybeNil, + }, + AlwaysNil: { + 0: AlwaysNil, + NeverNil: MaybeNil, + AlwaysNil: AlwaysNil, + MaybeNilGlobal: MaybeNil, + MaybeNil: MaybeNil, + }, + MaybeNilGlobal: { + 0: MaybeNilGlobal, + NeverNil: MaybeNilGlobal, + AlwaysNil: MaybeNil, + MaybeNilGlobal: MaybeNilGlobal, + MaybeNil: MaybeNil, + }, + MaybeNil: { + 0: MaybeNil, + NeverNil: MaybeNil, + AlwaysNil: MaybeNil, + MaybeNilGlobal: MaybeNil, + MaybeNil: MaybeNil, + }, +} + +// Merge implements [dfa.Semilattice]. +func (l lattice) Merge(a, b ValueNilness) ValueNilness { + return ValueNilness{ + Inner: latticeMerge[a.Inner][b.Inner], + Outer: latticeMerge[a.Outer][b.Outer], + } +} + +type numbering map[ir.Value]int + +func (n numbering) number(v ir.Value) int { + i, ok := n[v] + if !ok { + i = len(n) + n[v] = i + } + return i } diff --git a/tools/vendor/honnef.co/go/tools/analysis/facts/typedness/typedness.go b/tools/vendor/honnef.co/go/tools/analysis/facts/typedness/typedness.go deleted file mode 100644 index fd21dd228..000000000 --- a/tools/vendor/honnef.co/go/tools/analysis/facts/typedness/typedness.go +++ /dev/null @@ -1,253 +0,0 @@ -package typedness - -import ( - "fmt" - "go/token" - "go/types" - "reflect" - - "honnef.co/go/tools/go/ir" - "honnef.co/go/tools/go/ir/irutil" - "honnef.co/go/tools/internal/passes/buildir" - - "golang.org/x/exp/typeparams" - "golang.org/x/tools/go/analysis" -) - -// alwaysTypedFact denotes that a function's return value will never -// be untyped nil. The analysis errs on the side of false negatives. -type alwaysTypedFact struct { - Rets uint8 -} - -func (*alwaysTypedFact) AFact() {} -func (fact *alwaysTypedFact) String() string { - return fmt.Sprintf("always typed: %08b", fact.Rets) -} - -type Result struct { - m map[*types.Func]uint8 -} - -var Analysis = &analysis.Analyzer{ - Name: "typedness", - Doc: "Annotates return values that are always typed values", - Run: run, - Requires: []*analysis.Analyzer{buildir.Analyzer}, - FactTypes: []analysis.Fact{(*alwaysTypedFact)(nil)}, - ResultType: reflect.TypeFor[*Result](), -} - -// MustReturnTyped reports whether the ret's return value of fn must -// be a typed value, i.e. an interface value containing a concrete -// type or trivially a concrete type. The value of ret is zero-based. -// -// The analysis has false negatives: MustReturnTyped may incorrectly -// report false, but never incorrectly reports true. -func (r *Result) MustReturnTyped(fn *types.Func, ret int) bool { - if _, ok := fn.Type().(*types.Signature).Results().At(ret).Type().Underlying().(*types.Interface); !ok { - return true - } - return (r.m[fn] & (1 << ret)) != 0 -} - -func run(pass *analysis.Pass) (any, error) { - seen := map[*ir.Function]struct{}{} - out := &Result{ - m: map[*types.Func]uint8{}, - } - for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { - impl(pass, fn, seen) - } - - for _, fact := range pass.AllObjectFacts() { - out.m[fact.Object.(*types.Func)] = fact.Fact.(*alwaysTypedFact).Rets - } - - return out, nil -} - -func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) (out uint8) { - if fn.Signature.Results().Len() > 8 { - return 0 - } - if fn.Object() == nil { - // TODO(dh): support closures - return 0 - } - if fact := new(alwaysTypedFact); pass.ImportObjectFact(fn.Object(), fact) { - return fact.Rets - } - if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg { - return 0 - } - if fn.Blocks == nil { - return 0 - } - if irutil.IsStub(fn) { - return 0 - } - if _, ok := seenFns[fn]; ok { - // break recursion - return 0 - } - - seenFns[fn] = struct{}{} - defer func() { - for i := 0; i < fn.Signature.Results().Len(); i++ { - if _, ok := fn.Signature.Results().At(i).Type().Underlying().(*types.Interface); !ok { - // we don't need facts to know that non-interface - // types can't be untyped nil. zeroing out those bits - // may result in all bits being zero, in which case we - // don't have to save any fact. - out &= ^(1 << i) - } - } - if out > 0 { - pass.ExportObjectFact(fn.Object(), &alwaysTypedFact{out}) - } - }() - - isUntypedNil := func(v ir.Value) bool { - k, ok := v.(*ir.Const) - if !ok { - return false - } - if _, ok := k.Type().Underlying().(*types.Interface); !ok { - return false - } - return k.Value == nil - } - - var do func(v ir.Value, seen map[ir.Value]struct{}) bool - do = func(v ir.Value, seen map[ir.Value]struct{}) bool { - if _, ok := seen[v]; ok { - // break cycle - return false - } - seen[v] = struct{}{} - switch v := v.(type) { - case *ir.Const: - // can't be a typed nil, because then we'd be returning the - // result of MakeInterface. - return false - case *ir.ChangeInterface: - return do(v.X, seen) - case *ir.Extract: - call, ok := v.Tuple.(*ir.Call) - if !ok { - // We only care about extracts of function results. For - // everything else (e.g. channel receives and map - // lookups), we can either not deduce any information, or - // will see a MakeInterface. - return false - } - if callee := call.Call.StaticCallee(); callee != nil { - return impl(pass, callee, seenFns)&(1<interface conversions, which - // don't tell us anything about the nilness. - return false - case *ir.MapLookup, *ir.Index, *ir.Recv, *ir.Parameter, *ir.Load, *ir.Field: - // All other instructions that tell us nothing about the - // typedness of interface values. - return false - default: - panic(fmt.Sprintf("internal error: unhandled type %T", v)) - } - } - - ret := fn.Exit.Control().(*ir.Return) - for i, v := range ret.Results { - typ := fn.Signature.Results().At(i).Type() - if _, ok := typ.Underlying().(*types.Interface); ok && !typeparams.IsTypeParam(typ) { - if do(v, map[ir.Value]struct{}{}) { - out |= 1 << i - } - } - } - return out -} diff --git a/tools/vendor/honnef.co/go/tools/analysis/report/report.go b/tools/vendor/honnef.co/go/tools/analysis/report/report.go index 82befcaef..39919a895 100644 --- a/tools/vendor/honnef.co/go/tools/analysis/report/report.go +++ b/tools/vendor/honnef.co/go/tools/analysis/report/report.go @@ -13,7 +13,6 @@ import ( "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/facts/generated" - "honnef.co/go/tools/go/ast/astutil" "golang.org/x/tools/go/analysis" ) @@ -122,7 +121,7 @@ func shortRange(node ast.Node) (pos, end token.Pos) { case *ast.FuncLit: return node.Pos(), node.Type.End() case *ast.GoStmt: - if _, ok := astutil.Unparen(node.Call.Fun).(*ast.FuncLit); ok { + if _, ok := ast.Unparen(node.Call.Fun).(*ast.FuncLit); ok { return node.Pos(), node.Go + token.Pos(len("go")) } else { return node.Pos(), node.End() diff --git a/tools/vendor/honnef.co/go/tools/go/ast/astutil/util.go b/tools/vendor/honnef.co/go/tools/go/ast/astutil/util.go index 342654409..c23968f88 100644 --- a/tools/vendor/honnef.co/go/tools/go/ast/astutil/util.go +++ b/tools/vendor/honnef.co/go/tools/go/ast/astutil/util.go @@ -69,17 +69,6 @@ func GroupSpecs(fset *token.FileSet, specs []ast.Spec) [][]ast.Spec { return groups } -// Unparen returns e with any enclosing parentheses stripped. -func Unparen(e ast.Expr) ast.Expr { - for { - p, ok := e.(*ast.ParenExpr) - if !ok { - return e - } - e = p.X - } -} - // CopyExpr creates a deep copy of an expression. // It doesn't support copying FuncLits and returns ok == false when encountering one. func CopyExpr(node ast.Expr) (ast.Expr, bool) { diff --git a/tools/vendor/honnef.co/go/tools/go/ir/UPSTREAM b/tools/vendor/honnef.co/go/tools/go/ir/UPSTREAM deleted file mode 100644 index 03f487ae8..000000000 --- a/tools/vendor/honnef.co/go/tools/go/ir/UPSTREAM +++ /dev/null @@ -1,9 +0,0 @@ -This package started as a copy of golang.org/x/tools/go/ssa, imported from an unknown commit in 2016. -It has since been heavily modified to match our own needs in an IR. -The changes are too many to list here, and it is best to consider this package independent of go/ssa. - -Upstream changes still get applied when they address bugs in portions of code we have inherited. - -The last upstream commit we've looked at was: -05409620da166985e94b711ad4103bee40406eee - diff --git a/tools/vendor/honnef.co/go/tools/go/ir/block.go b/tools/vendor/honnef.co/go/tools/go/ir/block.go new file mode 100644 index 000000000..44399c7ac --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/go/ir/block.go @@ -0,0 +1,133 @@ +// 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 ir + +import ( + "fmt" + "go/ast" +) + +// This file implements the BasicBlock type. + +// addEdge adds a control-flow graph edge from from to to. +func addEdge(from, to *BasicBlock) { + from.Succs = append(from.Succs, to) + to.Preds = append(to.Preds, from) +} + +// Control returns the last instruction in the block. +func (b *BasicBlock) Control() Instruction { + if len(b.Instrs) == 0 { + return nil + } + return b.Instrs[len(b.Instrs)-1] +} + +// Parent returns the function that contains block b. +func (b *BasicBlock) Parent() *Function { return b.parent } + +func (b *BasicBlock) Reaches(o *BasicBlock) bool { + return b.SCC.reachable.Bit(o.SCC.Index) != 0 +} + +// String returns a human-readable label of this block. +// It is not guaranteed unique within the function. +func (b *BasicBlock) String() string { + return fmt.Sprintf("%d", b.Index) +} + +// emit appends an instruction to the current basic block. +// If the instruction defines a Value, it is returned. +func (b *BasicBlock) emit(i Instruction, source ast.Node) Value { + i.setSource(source) + i.setBlock(b) + b.Instrs = append(b.Instrs, i) + v, _ := i.(Value) + return v +} + +// predIndex returns the i such that b.Preds[i] == c or panics if +// there is none. +func (b *BasicBlock) predIndex(c *BasicBlock) int { + for i, pred := range b.Preds { + if pred == c { + return i + } + } + panic(fmt.Sprintf("no edge %s -> %s", c, b)) +} + +// hasPhi returns true if b.Instrs contains φ-nodes. +func (b *BasicBlock) hasPhi() bool { + _, ok := b.Instrs[0].(*Phi) + return ok +} + +func (b *BasicBlock) Phis() []Instruction { + return b.phis() +} + +// phis returns the prefix of b.Instrs containing all the block's φ-nodes. +func (b *BasicBlock) phis() []Instruction { + for i, instr := range b.Instrs { + if _, ok := instr.(*Phi); !ok { + return b.Instrs[:i] + } + } + return nil // unreachable in well-formed blocks +} + +// replacePred replaces all occurrences of p in b's predecessor list with q. +// Ordinarily there should be at most one. +func (b *BasicBlock) replacePred(p, q *BasicBlock) { + for i, pred := range b.Preds { + if pred == p { + b.Preds[i] = q + } + } +} + +// replaceSucc replaces all occurrences of p in b's successor list with q. +// Ordinarily there should be at most one. +func (b *BasicBlock) replaceSucc(p, q *BasicBlock) { + for i, succ := range b.Succs { + if succ == p { + b.Succs[i] = q + } + } +} + +// removePred removes all occurrences of p in b's +// predecessor list and φ-nodes. +// Ordinarily there should be at most one. +func (b *BasicBlock) removePred(p *BasicBlock) { + phis := b.phis() + + // We must preserve edge order for φ-nodes. + j := 0 + for i, pred := range b.Preds { + if pred != p { + b.Preds[j] = b.Preds[i] + // Strike out φ-edge too. + for _, instr := range phis { + phi := instr.(*Phi) + phi.Edges[j] = phi.Edges[i] + } + j++ + } + } + // Nil out b.Preds[j:] and φ-edges[j:] to aid GC. + for i := j; i < len(b.Preds); i++ { + b.Preds[i] = nil + for _, instr := range phis { + instr.(*Phi).Edges[i] = nil + } + } + b.Preds = b.Preds[:j] + for _, instr := range phis { + phi := instr.(*Phi) + phi.Edges = phi.Edges[:j] + } +} diff --git a/tools/vendor/honnef.co/go/tools/go/ir/blockopt.go b/tools/vendor/honnef.co/go/tools/go/ir/blockopt.go index 537886117..de6ac9a7c 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/blockopt.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/blockopt.go @@ -38,12 +38,9 @@ func deleteUnreachableBlocks(f *Function) { b.gaps = white } markReachable(f.Blocks[0]) - // In SSI form, we need the exit to be reachable for correct - // post-dominance information. In original form, however, we - // cannot unconditionally mark it reachable because we won't - // be adding fake edges, and this breaks the calculation of - // dominance information. - markReachable(f.Exit) + if f.Recover != nil { + markReachable(f.Recover) + } for i, b := range f.Blocks { if b.gaps == white { for _, c := range b.Succs { @@ -67,7 +64,7 @@ func jumpThreading(f *Function, b *BasicBlock) bool { if b.Index == 0 { return false // don't apply to entry block } - if b.Instrs == nil { + if len(b.Instrs) == 0 { return false } for _, pred := range b.Preds { @@ -120,9 +117,6 @@ func fuseBlocks(f *Function, a *BasicBlock) bool { if len(a.Succs) != 1 { return false } - if a.Succs[0] == f.Exit { - return false - } b := a.Succs[0] if len(b.Preds) != 1 { return false @@ -165,11 +159,6 @@ func fuseBlocks(f *Function, a *BasicBlock) bool { // completed function: dead block elimination, block fusion, jump // threading. func optimizeBlocks(f *Function) { - if debugBlockOpt { - f.WriteTo(os.Stderr) - mustSanityCheck(f, nil) - } - deleteUnreachableBlocks(f) // Loop until no further progress. diff --git a/tools/vendor/honnef.co/go/tools/go/ir/builder.go b/tools/vendor/honnef.co/go/tools/go/ir/builder.go index 17a6c8917..d2918304d 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/builder.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/builder.go @@ -4,25 +4,73 @@ package ir -// This file implements the BUILD phase of IR construction. +// This file defines the builder, which builds SSA-form IR for function bodies. // -// IR construction has two phases, CREATE and BUILD. In the CREATE phase -// (create.go), all packages are constructed and type-checked and -// definitions of all package members are created, method-sets are -// computed, and wrapper methods are synthesized. -// ir.Packages are created in arbitrary order. +// SSA construction has two phases, "create" and "build". First, one +// or more packages are created in any order by a sequence of calls to +// CreatePackage, either from syntax or from mere type information. +// Each created package has a complete set of Members (const, var, +// type, func) that can be accessed through methods like +// Program.FuncValue. // -// In the BUILD phase (builder.go), the builder traverses the AST of -// each Go source function and generates IR instructions for the -// function body. Initializer expressions for package-level variables -// are emitted to the package's init() function in the order specified -// by go/types.Info.InitOrder, then code for each function in the -// package is generated in lexical order. +// It is not necessary to call CreatePackage for all dependencies of +// each syntax package, only for its direct imports. (In future +// perhaps even this restriction may be lifted.) // -// The builder's and Program's indices (maps) are populated and -// mutated during the CREATE phase, but during the BUILD phase they -// remain constant. The sole exception is Prog.methodSets and its -// related maps, which are protected by a dedicated mutex. +// Second, packages created from syntax are built, by one or more +// calls to Package.Build, which may be concurrent; or by a call to +// Program.Build, which builds all packages in parallel. Building +// traverses the type-annotated syntax tree of each function body and +// creates SSA-form IR, a control-flow graph of instructions, +// populating fields such as Function.Body, .Params, and others. +// +// Building may create additional methods, including: +// - wrapper methods (e.g. for embedding, or implicit &recv) +// - bound method closures (e.g. for use(recv.f)) +// - thunks (e.g. for use(I.f) or use(T.f)) +// - generic instances (e.g. to produce f[int] from f[any]). +// As these methods are created, they are added to the build queue, +// and then processed in turn, until a fixed point is reached, +// Since these methods might belong to packages that were not +// created (by a call to CreatePackage), their Pkg field is unset. +// +// Instances of generic functions may be either instantiated (f[int] +// is a copy of f[T] with substitutions) or wrapped (f[int] delegates +// to f[T]), depending on the availability of generic syntax and the +// InstantiateGenerics mode flag. +// +// Each package has an initializer function named "init" that calls +// the initializer functions of each direct import, computes and +// assigns the initial value of each global variable, and calls each +// source-level function named "init". (These generate SSA functions +// named "init#1", "init#2", etc.) +// +// Runtime types +// +// Each MakeInterface operation is a conversion from a non-interface +// type to an interface type. The semantics of this operation requires +// a runtime type descriptor, which is the type portion of an +// interface, and the value abstracted by reflect.Type. +// +// The program accumulates all non-parameterized types that are +// encountered as MakeInterface operands, along with all types that +// may be derived from them using reflection. This set is available as +// Program.RuntimeTypes, and the methods of these types may be +// reachable via interface calls or reflection even if they are never +// referenced from the SSA IR. (In practice, algorithms such as RTA +// that compute reachability from package main perform their own +// tracking of runtime types at a finer grain, so this feature is not +// very useful.) +// +// Function literals +// +// Anonymous functions must be built as soon as they are encountered, +// as it may affect locals of the enclosing function, but they are not +// marked 'built' until the end of the outermost enclosing function. +// (Among other things, this causes them to be logged in top-down order.) +// +// The Function.build fields determines the algorithm for building the +// function body. It is cleared to mark that building is complete. import ( "fmt" @@ -30,11 +78,14 @@ import ( "go/constant" "go/token" "go/types" - "go/version" "os" + "runtime" + "slices" + "sync" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/xtools-internal/versions" "golang.org/x/exp/typeparams" ) @@ -45,6 +96,8 @@ var ( // Type constants. tBool = types.Typ[types.Bool] + tByte = types.Typ[types.Byte] + tRune = types.Universe.Lookup("rune").Type() // prints as "rune" (Typ[Rune] is same as Int32) tInt = types.Typ[types.Int] tInvalid = types.Typ[types.Invalid] tString = types.Typ[types.String] @@ -52,39 +105,66 @@ var ( tEface = types.NewInterfaceType(nil, nil).Complete() tDeferStack = types.NewPointer(typeutil.NewDeferStack()) + vOne = intConst(1, nil) + vTrue = NewConst(constant.MakeBool(true), tBool, nil) + vNoReturn = NewConst(constant.MakeString("noreturn"), tString, nil) + + jReady = intConst(0, nil) // range-over-func jump is READY + jBusy = intConst(-1, nil) // range-over-func jump is BUSY + jDone = intConst(-2, nil) // range-over-func jump is DONE + jDroppedPanic = stringConst("iterator call did not preserve panic", nil) + jLateYield = stringConst("yield function called after range loop exit", nil) + vDeferStack = &Builtin{ name: "ssa:deferstack", sig: types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(anonVar(tDeferStack)), false), } ) -// range-over-func jump is READY -func jReady() *Const { - c := intConst(0, nil) - c.comment = "rangefunc.exit.ready" - return c -} +// builder holds state associated with the package currently being built. +// Its methods contain all the logic for AST-to-IR conversion. +// +// All Functions belong to the same Program. +// +// builders are not thread-safe. +type builder struct { + fns []*Function // Functions that have finished their CREATE phases. + + finished int // finished is the length of the prefix of fns containing built functions. -// range-over-func jump is BUSY -func jBusy() *Const { - c := intConst(-1, nil) - c.comment = "rangefunc.exit.busy" - return c + // The task of building shared functions within the builder. + // Shared functions are ones the builder may either create or lookup. + // These may be built by other builders in parallel. + // The task is done when the builder has finished iterating, and it + // waits for all shared functions to finish building. + // nil implies there are no hared functions to wait on. + buildshared *task } -// range-over-func jump is DONE -func jDone() *Const { - c := intConst(-2, nil) - c.comment = "rangefunc.exit.done" - return c +// shared is done when the builder has built all of the +// enqueued functions to a fixed-point. +func (b *builder) shared() *task { + if b.buildshared == nil { // lazily-initialize + b.buildshared = &task{done: make(chan unit)} + } + return b.buildshared } -// builder holds state associated with the package currently being built. -// Its methods contain all the logic for AST-to-IR conversion. -type builder struct { - printFunc string +// enqueue fn to be built by the builder. +func (b *builder) enqueue(fn *Function) { + b.fns = append(b.fns, fn) +} - blocksets [5]BlockSet +// waitForSharedFunction indicates that the builder should wait until +// the potentially shared function fn has finished building. +// +// This should include any functions that may be built by other +// builders. +func (b *builder) waitForSharedFunction(fn *Function) { + if fn.buildshared != nil { // maybe need to wait? + s := b.shared() + s.addEdge(fn.buildshared) + } } // cond emits to fn code to evaluate boolean condition e and jump @@ -136,17 +216,17 @@ func (b *builder) logicalBinop(fn *Function, e *ast.BinaryExpr) Value { // T(e) = T(e.X) = T(e.Y) after untyped constants have been // eliminated. // TODO(adonovan): not true; MyBool==MyBool yields UntypedBool. - t := fn.Pkg.typeOf(e) + t := fn.typeOf(e) var short Value // value of the short-circuit path switch e.Op { case token.LAND: b.cond(fn, e.X, rhs, done) - short = emitConst(fn, NewConst(constant.MakeBool(false), t, e)) + short = NewConst(constant.MakeBool(false), t, e) case token.LOR: b.cond(fn, e.X, done, rhs) - short = emitConst(fn, NewConst(constant.MakeBool(true), t, e)) + short = NewConst(constant.MakeBool(true), t, e) } // Is rhs unreachable? @@ -189,7 +269,7 @@ func (b *builder) logicalBinop(fn *Function, e *ast.BinaryExpr) Value { // assignment or return statement, and "value,ok" uses of // TypeAssertExpr, IndexExpr (when X is a map), and Recv. func (b *builder) exprN(fn *Function, e ast.Expr) Value { - typ := fn.Pkg.typeOf(e).(*types.Tuple) + typ := fn.typeOf(e).(*types.Tuple) switch e := e.(type) { case *ast.ParenExpr: return b.exprN(fn, e.X) @@ -204,7 +284,7 @@ func (b *builder) exprN(fn *Function, e ast.Expr) Value { return emitCall(fn, &c, e) case *ast.IndexExpr: - mapt := typeutil.CoreType(fn.Pkg.typeOf(e.X)).Underlying().(*types.Map) + mapt := typeutil.CoreType(fn.typeOf(e.X)).(*types.Map) // ,ok must be a map. lookup := &MapLookup{ X: b.expr(fn, e.X), Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key(), e), @@ -230,17 +310,10 @@ func (b *builder) exprN(fn *Function, e ast.Expr) Value { // the caller should treat this like an ordinary library function // call. func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ types.Type, source ast.Node) Value { + typ = fn.typ(typ) switch obj.Name() { case "make": - styp := typ.Underlying() - if _, ok := typ.Underlying().(*types.Interface); ok { - // This must be a type parameter with a core type. - // Set styp to the core type and generate instructions based on it. - assert(typeparams.IsTypeParam(typ)) - styp = typeutil.CoreType(typ) - assert(styp != nil) - } - switch styp.(type) { + switch ct := typeutil.CoreType(typ).(type) { case *types.Slice: n := b.expr(fn, args[1]) m := n @@ -250,7 +323,7 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ if m, ok := m.(*Const); ok { // treat make([]T, n, m) as new([m]T)[:n] cap := m.Int64() - at := types.NewArray(styp.Underlying().(*types.Slice).Elem(), cap) + at := types.NewArray(ct.Elem(), cap) v := &Slice{ X: emitNew(fn, at, source, "makeslice"), High: n, @@ -275,7 +348,7 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ return fn.emit(v, source) case *types.Chan: - var sz Value = emitConst(fn, intConst(0, source)) + var sz Value = intConst(0, source) if len(args) == 2 { sz = b.expr(fn, args[1]) } @@ -289,7 +362,8 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ case "new": alloc := emitNew(fn, deref(typ), source, "new") - if !fn.Pkg.info.Types[args[0]].IsType() { + if !fn.info.Types[args[0]].IsType() { + // new(expr), requires go1.26 v := b.expr(fn, args[0]) emitStore(fn, alloc, v, source) } @@ -305,10 +379,10 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ // // Technically this shouldn't apply to type parameters because their length/capacity is never constant. We still // choose to treat them as constant so that users of the IR get the practically constant length for free. - t := typeutil.CoreType(deref(fn.Pkg.typeOf(args[0]))) + t := typeutil.CoreType(deref(fn.typeOf(args[0]))) if at, ok := t.(*types.Array); ok { b.expr(fn, args[0]) // for effects only - return emitConst(fn, intConst(at.Len(), args[0])) + return intConst(at.Len(), args[0]) } // Otherwise treat as normal. @@ -316,9 +390,8 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ fn.emit(&Panic{ X: emitConv(fn, b.expr(fn, args[0]), tEface, source), }, source) - addEdge(fn.currentBlock, fn.Exit) fn.currentBlock = fn.newBasicBlock("unreachable") - return emitConst(fn, NewConst(constant.MakeBool(true), tBool, nil)) // any non-nil Value will do + return vTrue // any non-nil Value will do } return nil // treat all others as a regular function call } @@ -345,21 +418,23 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ // - &x, including when implicit in method call or composite literals. // - a[:] iff a is an array (not *array) // - references to variables in lexically enclosing functions. -func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) (RET lvalue) { +func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { switch e := e.(type) { case *ast.Ident: if isBlankIdent(e) { return blank{} } - obj := fn.Pkg.objectOf(e) - v := fn.Prog.packageLevelValue(obj) // var (address) - if v == nil { - v = fn.lookup(obj.(*types.Var), escaping) + obj := fn.objectOf(e).(*types.Var) + var v Value + if g := fn.Prog.packageLevelMember(obj); g != nil { + v = g.(*Global) // var (address) + } else { + v = fn.lookup(obj, escaping) } return &address{addr: v, expr: e} case *ast.CompositeLit: - t := deref(fn.Pkg.typeOf(e)) + t := deref(fn.typeOf(e)) var v *Alloc if escaping { v = emitNew(fn, t, e, "complit") @@ -375,19 +450,19 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) (RET lvalue) { return b.addr(fn, e.X, escaping) case *ast.SelectorExpr: - sel, ok := fn.Pkg.info.Selections[e] - if !ok { + sel := fn.selection(e) + if sel == nil { // qualified identifier return b.addr(fn, e.Sel, escaping) } - if sel.Kind() != types.FieldVal { + if sel.kind != types.FieldVal { panic(sel) } wantAddr := true v := b.receiver(fn, e.X, wantAddr, escaping, sel, e) - index := sel.Index()[len(sel.Index())-1] - vut := typeutil.CoreType(deref(v.Type())).Underlying().(*types.Struct) - fld := vut.Field(index) + index := sel.index[len(sel.index)-1] + fld := fieldOf(deref(v.Type()), index) // v is an addr. + // Due to the two phases of resolving AssignStmt, a panic from x.f = p() // when x is nil is required to come after the side-effects of // evaluating x and p(). @@ -397,61 +472,35 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) (RET lvalue) { return &lazyAddress{addr: emit, t: fld.Type(), expr: e.Sel} case *ast.IndexExpr: + xt := fn.typeOf(e.X) + elem, mode := indexType(xt) var x Value var et types.Type - xt := fn.Pkg.typeOf(e.X) - - // Indexing doesn't need a core type, it only requires all types to be similar enough. For example, []int64 | - // [5]int64 can be indexed. The element types do have to match though. - - terms, err := typeparams.NormalTerms(xt) - if err != nil { - panic(fmt.Sprintf("unexpected error: %s", err)) - } - isArrayLike := func() (types.Type, bool) { - for _, term := range terms { - arr, ok := term.Type().Underlying().(*types.Array) - if ok { - return arr.Elem(), true - } - } - return nil, false - } - - isSliceLike := func() (types.Type, bool) { - for _, term := range terms { - switch t := term.Type().Underlying().(type) { - case *types.Slice: - return t.Elem(), true - case *types.Pointer: - return t.Elem().Underlying().(*types.Array).Elem(), true - } - } - return nil, false - } - - if elem, ok := isArrayLike(); ok { - // array + switch mode { + case ixArrVar: // array, array|slice, array|*array, or array|*array|slice. x = b.addr(fn, e.X, escaping).address(fn) et = types.NewPointer(elem) - } else if elem, ok := isSliceLike(); ok { - // slice or *array + case ixVar: // *array, slice, *array|slice x = b.expr(fn, e.X) et = types.NewPointer(elem) - } else if t, ok := typeutil.CoreType(xt).Underlying().(*types.Map); ok { + case ixMap: + mt := typeutil.CoreType(xt).(*types.Map) return &element{ m: b.expr(fn, e.X), - k: emitConv(fn, b.expr(fn, e.Index), t.Key(), e.Index), - t: t.Elem(), + k: emitConv(fn, b.expr(fn, e.Index), mt.Key(), e.Index), + t: mt.Elem(), } - } else { - panic("unexpected container type in IndexExpr: " + t.String()) + default: + panic("unexpected container type in IndexExpr: " + xt.String()) + } + index := b.expr(fn, e.Index) + if isUntyped(index.Type()) { + index = emitConv(fn, index, tInt, e.Index) } // Due to the two phases of resolving AssignStmt, a panic from x[i] = p() // when x is nil or i is out-of-bounds is required to come after the // side-effects of evaluating x, i and p(). - index := b.expr(fn, e.Index) emit := func(fn *Function) Value { v := &IndexAddr{ X: x, @@ -475,7 +524,7 @@ type store struct { source ast.Node // if debugRef is set no other fields will be set - debugRef *DebugRef + debugRef *debugRef } type storebuf struct{ stores []store } @@ -484,7 +533,7 @@ func (sb *storebuf) store(lhs lvalue, rhs Value, source ast.Node) { sb.stores = append(sb.stores, store{lhs, rhs, source, nil}) } -func (sb *storebuf) storeDebugRef(ref *DebugRef) { +func (sb *storebuf) storeDebugRef(ref *debugRef) { sb.stores = append(sb.stores, store{debugRef: ref}) } @@ -513,12 +562,12 @@ func (sb *storebuf) emit(fn *Function) { // literal that may reference parts of the LHS. func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb *storebuf, source ast.Node) { // Can we initialize it in place? - if e, ok := unparen(e).(*ast.CompositeLit); ok { + if e, ok := ast.Unparen(e).(*ast.CompositeLit); ok { // A CompositeLit never evaluates to a pointer, // so if the type of the location is a pointer, // an &-operation is implied. if _, ok := loc.(blank); !ok { // avoid calling blank.typ() - if isPointer(loc.typ()) { + if isPointerCore(loc.typ()) { // Example input that hits this code: // // type S1 struct{ X int } @@ -555,7 +604,7 @@ func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb * // Subtle: emit debug ref for aggregate types only; // slice and map are handled by store ops in compLit. - switch typeutil.CoreType(loc.typ()).Underlying().(type) { + switch typeutil.CoreType(loc.typ()).(type) { case *types.Struct, *types.Array: if sb != nil { // Make sure we don't emit DebugRefs before the store has actually occurred @@ -584,13 +633,13 @@ func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb * // expr lowers a single-result expression e to IR form, emitting code // to fn and returning the Value defined by the expression. func (b *builder) expr(fn *Function, e ast.Expr) Value { - e = unparen(e) + e = ast.Unparen(e) - tv := fn.Pkg.info.Types[e] + tv := fn.info.Types[e] // Is expression a constant? if tv.Value != nil { - return emitConst(fn, NewConst(tv.Value, tv.Type, e)) + return NewConst(tv.Value, fn.typ(tv.Type), e) } var v Value @@ -614,26 +663,35 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { panic("non-constant BasicLit") // unreachable case *ast.FuncLit: + /* function literal */ fn2 := &Function{ - name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)), - Signature: fn.Pkg.typeOf(e.Type).Underlying().(*types.Signature), - parent: fn, - Pkg: fn.Pkg, - Prog: fn.Prog, - functionBody: new(functionBody), - goversion: fn.goversion, // share the parent's goversion + name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)), + Signature: fn.typeOf(e.Type).(*types.Signature), + pos: e.Type.Func, + parent: fn, + anonIdx: int32(len(fn.AnonFuncs)), + Pkg: fn.Pkg, + Prog: fn.Prog, + syntax: e, + info: fn.info, + goversion: fn.goversion, + build: (*builder).buildFromSyntax, + topLevelOrigin: nil, // use anonIdx to lookup an anon instance's origin. + typeparams: fn.typeparams, // share the parent's type parameters. + typeargs: fn.typeargs, // share the parent's type arguments. + subst: fn.subst, // share the parent's type substitutions. } fn2.uniq = fn.uniq // start from parent's unique values - fn2.source = e fn.AnonFuncs = append(fn.AnonFuncs, fn2) - fn2.initHTML(b.printFunc) - b.buildFunction(fn2) + // Build anon immediately, as it may cause fn's locals to escape. + // (It is not marked 'built' until the end of the enclosing FuncDecl.) + fn2.build(b, fn2) fn.uniq = fn2.uniq // resume after anon's unique values if fn2.FreeVars == nil { return fn2 } v := &MakeClosure{Fn: fn2} - v.setType(tv.Type) + v.setType(fn.typ(tv.Type)) for _, fv := range fn2.FreeVars { v.Bindings = append(v.Bindings, fv.outer) fv.outer = nil @@ -641,19 +699,19 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { return fn.emit(v, e) case *ast.TypeAssertExpr: // single-result form only - return emitTypeAssert(fn, b.expr(fn, e.X), tv.Type, e) + return emitTypeAssert(fn, b.expr(fn, e.X), fn.typ(tv.Type), e) case *ast.CallExpr: - if fn.Pkg.info.Types[e.Fun].IsType() { + if fn.info.Types[e.Fun].IsType() { // Explicit type conversion, e.g. string(x) or big.Int(x) x := b.expr(fn, e.Args[0]) - y := emitConv(fn, x, tv.Type, e) + y := emitConv(fn, x, fn.typ(tv.Type), e) return y } // Call to "intrinsic" built-ins, e.g. new, make, panic. - if id, ok := unparen(e.Fun).(*ast.Ident); ok { - if obj, ok := fn.Pkg.info.Uses[id].(*types.Builtin); ok { - if v := b.builtin(fn, obj, e.Args, tv.Type, e); v != nil { + if id, ok := ast.Unparen(e.Fun).(*ast.Ident); ok { + if obj, ok := fn.info.Uses[id].(*types.Builtin); ok { + if v := b.builtin(fn, obj, e.Args, fn.typ(tv.Type), e); v != nil { return v } } @@ -661,14 +719,14 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { // Regular function call. var v Call b.setCall(fn, e, &v.Call) - v.setType(tv.Type) + v.setType(fn.typ(tv.Type)) return emitCall(fn, &v, e) case *ast.UnaryExpr: switch e.Op { case token.AND: // &X --- potentially escaping. addr := b.addr(fn, e.X, true) - if _, ok := unparen(e.X).(*ast.StarExpr); ok { + if _, ok := ast.Unparen(e.X).(*ast.StarExpr); ok { // &*p must panic if p is nil (https://golang.org/s/go12nil). // For simplicity, we'll just (suboptimally) rely // on the side effects of a load. @@ -683,10 +741,10 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { Op: e.Op, X: b.expr(fn, e.X), } - v.setType(tv.Type) + v.setType(fn.typ(tv.Type)) return fn.emit(v, e) case token.ARROW: - return emitRecv(fn, b.expr(fn, e.X), false, tv.Type, e) + return emitRecv(fn, b.expr(fn, e.X), false, fn.typ(tv.Type), e) default: panic(e.Op) } @@ -698,35 +756,34 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { case token.SHL, token.SHR: fallthrough case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT: - return emitArith(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), tv.Type, e) + return emitArith(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), fn.typ(tv.Type), e) case token.EQL, token.NEQ, token.GTR, token.LSS, token.LEQ, token.GEQ: cmp := emitCompare(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), e) // The type of x==y may be UntypedBool. - return emitConv(fn, cmp, types.Default(tv.Type), e) + return emitConv(fn, cmp, types.Default(fn.typ(tv.Type)), e) default: panic("illegal op in BinaryExpr: " + e.Op.String()) } case *ast.SliceExpr: + var low, high, max Value var x Value - if core := typeutil.CoreType(fn.Pkg.typeOf(e.X)); core != nil { - switch core.Underlying().(type) { - case *types.Array: - // Potentially escaping. - x = b.addr(fn, e.X, true).address(fn) - case *types.Basic, *types.Slice, *types.Pointer: // *array - x = b.expr(fn, e.X) - default: - panic("unreachable") - } - } else { - // We're indexing a string | []byte. Note that other combinations such as []byte | [4]byte are currently not - // allowed by the language. + xtyp := fn.typeOf(e.X) + switch typeutil.CoreType(xtyp).(type) { + case *types.Array: + // Potentially escaping. + x = b.addr(fn, e.X, true).address(fn) + case *types.Basic, *types.Slice, *types.Pointer: // *array x = b.expr(fn, e.X) + default: + // core type exception? + if isBytestring(xtyp) { + x = b.expr(fn, e.X) // bytestring is handled as string and []byte. + } else { + panic("unexpected sequence type in SliceExpr") + } } - - var low, high, max Value if e.Low != nil { low = b.expr(fn, e.Low) } @@ -742,72 +799,112 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { High: high, Max: max, } - v.setType(tv.Type) + v.setType(fn.typ(tv.Type)) return fn.emit(v, e) case *ast.Ident: - obj := fn.Pkg.info.Uses[e] + obj := fn.info.Uses[e] // Universal built-in or nil? switch obj := obj.(type) { case *types.Builtin: - return &Builtin{name: obj.Name(), sig: tv.Type.(*types.Signature)} + return &Builtin{name: obj.Name(), sig: fn.instanceType(e).(*types.Signature)} case *types.Nil: - return emitConst(fn, nilConst(tv.Type, e)) + return zeroConst(fn.instanceType(e), e) } + // Package-level func or var? - if v := fn.Prog.packageLevelValue(obj); v != nil { - if _, ok := obj.(*types.Var); ok { - return emitLoad(fn, v, e) // var (address) + // (obj must belong to same package or a direct import.) + if v := fn.Prog.packageLevelMember(obj); v != nil { + if g, ok := v.(*Global); ok { + return emitLoad(fn, g, e) // var (address) } - if instance, ok := fn.Pkg.info.Instances[e]; ok { - // Instantiated generic function - return makeInstance(fn.Prog, v.(*Function), instance.Type.(*types.Signature), instance.TypeArgs) + callee := v.(*Function) // (func) + if callee.typeparams.Len() > 0 { + targs := fn.subtargs(e) + callee = callee.instance(nil, targs, b) } - return v // (func) + return callee } // Local var. return emitLoad(fn, fn.lookup(obj.(*types.Var), false), e) // var (address) case *ast.SelectorExpr: - sel, ok := fn.Pkg.info.Selections[e] - if !ok { + sel := fn.selection(e) + if sel == nil { // builtin unsafe.{Add,Slice} - if obj, ok := fn.Pkg.info.Uses[e.Sel].(*types.Builtin); ok { - return &Builtin{name: "Unsafe" + obj.Name(), sig: tv.Type.(*types.Signature)} + if obj, ok := fn.info.Uses[e.Sel].(*types.Builtin); ok { + return &Builtin{name: "Unsafe" + obj.Name(), sig: fn.typ(tv.Type).(*types.Signature)} } // qualified identifier return b.expr(fn, e.Sel) } - switch sel.Kind() { + switch sel.kind { case types.MethodExpr: // (*T).f or T.f, the method f from the method-set of type T. // The result is a "thunk". - return emitConv(fn, makeThunk(fn.Prog, sel), tv.Type, e) + targs := fn.subtargs(e.Sel) + thunk := createThunk(fn.Prog, sel, targs) + b.enqueue(thunk) + return thunk case types.MethodVal: // e.f where e is an expression and f is a method. // The result is a "bound". - obj := sel.Obj().(*types.Func) - rt := recvType(obj) + m := sel.obj.(*types.Func) + rt := fn.typ(recvType(m)) wantAddr := isPointer(rt) escaping := true v := b.receiver(fn, e.X, wantAddr, escaping, sel, e) + if types.IsInterface(rt) { - // If v has interface type I, + // If v may be an interface type I (after instantiating), // we must emit a check that v is non-nil. - // We use: typeassert v.(I). - emitTypeAssert(fn, v, rt, e) + if recv, ok := types.Unalias(sel.recv).(*types.TypeParam); ok { + // Emit a nil check if any possible instantiation of the + // type parameter is an interface type. + if !typeSetIsEmpty(recv) { + // recv has a concrete term its typeset. + // So it cannot be instantiated as an interface. + // + // Example: + // func _[T interface{~int; Foo()}] () { + // var v T + // _ = v.Foo // <-- MethodVal + // } + } else { + // rt may be instantiated as an interface. + // Emit nil check: typeassert (any(v)).(any). + emitTypeAssert(fn, emitConv(fn, v, tEface, nil), tEface, nil) + } + } else { + // non-type param interface + // Emit nil check: typeassert v.(I). + emitTypeAssert(fn, v, rt, e.Sel) + } + } + + if rtargs := fn.subrtargs(m); len(rtargs) > 0 { + m = fn.Prog.canon.instantiateMethod(m, rtargs, fn.Prog.ctxt) } + + targs := fn.subtargs(e.Sel) + bound := createBound(fn.Prog, m, targs) + b.enqueue(bound) + + // The assignment may widen a type parameter to its + // interface bound (case #3 of go.dev/issue.78110). + v = emitConv(fn, v, bound.FreeVars[0].Type(), nil) + c := &MakeClosure{ - Fn: makeBound(fn.Prog, obj), + Fn: bound, Bindings: []Value{v}, } c.source = e.Sel - c.setType(tv.Type) - return fn.emit(c, e) + c.setType(bound.Signature) + return fn.emit(c, e.Sel) case types.FieldVal: - indices := sel.Index() + indices := sel.index last := len(indices) - 1 v := b.expr(fn, e.X) v = emitImplicitSelections(fn, v, indices[:last], e) @@ -817,69 +914,52 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { panic("unexpected expression-relative selector") - case *ast.IndexExpr: - // IndexExpr might either be an actual indexing operation, or an instantiation - xt := fn.Pkg.typeOf(e.X) - - terms, err := typeparams.NormalTerms(xt) - if err != nil { - panic(fmt.Sprintf("unexpected error: %s", err)) - } - isNonAddressableIndexable := func() (types.Type, bool) { - for _, term := range terms { - switch t := term.Type().Underlying().(type) { - case *types.Array: - return t.Elem(), true - case *types.Basic: - // a string - return types.Universe.Lookup("byte").Type(), true - } - } - return nil, false + case *ast.IndexListExpr: + // f[X, Y] must be a generic function + if !instance(fn.info, e.X) { + panic("unexpected expression-could not match index list to instantiation") } + return b.expr(fn, e.X) // Handle instantiation within the *Ident or *SelectorExpr cases. - isAddressableIndexable := func() (types.Type, bool) { - for _, term := range terms { - switch t := term.Type().Underlying().(type) { - case *types.Slice: - return t.Elem(), true - case *types.Pointer: - return t.Elem().Underlying().(*types.Array).Elem(), true - } - } - return nil, false - } + case *ast.IndexExpr: + if instance(fn.info, e.X) { + return b.expr(fn, e.X) // Handle instantiation within the *Ident or *SelectorExpr cases. + } + // not a generic instantiation. + xt := fn.typeOf(e.X) + switch et, mode := indexType(xt); mode { + case ixVar: + // Addressable slice/array; use IndexAddr and Load. + return b.addr(fn, e, false).load(fn, e) + + case ixArrVar, ixValue: + // An array in a register, a string or a combined type that contains + // either an [_]array (ixArrVar) or string (ixValue). - if elem, ok := isNonAddressableIndexable(); ok { - // At least one of the types is non-addressable + // Note: for ixArrVar and CoreType(xt)==nil can be IndexAddr and Load. + index := b.expr(fn, e.Index) + if isUntyped(index.Type()) { + index = emitConv(fn, index, tInt, e.Index) + } v := &Index{ X: b.expr(fn, e.X), - Index: b.expr(fn, e.Index), + Index: index, } - v.setType(elem) + v.setType(et) return fn.emit(v, e) - } else if _, ok := isAddressableIndexable(); ok { - // All types are addressable (otherwise the previous branch would've fired) - return b.addr(fn, e, false).load(fn, e) - } else if t, ok := typeutil.CoreType(xt).Underlying().(*types.Map); ok { - // Maps are not addressable. + + case ixMap: + ct := typeutil.CoreType(xt).(*types.Map) v := &MapLookup{ X: b.expr(fn, e.X), - Index: emitConv(fn, b.expr(fn, e.Index), t.Key(), e.Index), + Index: emitConv(fn, b.expr(fn, e.Index), ct.Key(), e.Index), } - v.setType(t.Elem()) + v.setType(ct.Elem()) return fn.emit(v, e) - } else if _, ok := xt.Underlying().(*types.Signature); ok { - // Instantiating a generic function - return b.expr(fn, e.X) - } else { - panic("unexpected container type in IndexExpr: " + t.String()) + default: + panic("unexpected container type in IndexExpr: " + xt.String()) } - case *ast.IndexListExpr: - // Instantiating a generic function - return b.expr(fn, e.X) - case *ast.CompositeLit, *ast.StarExpr: // Addressable types (lvalues) return b.addr(fn, e, false).load(fn, e) @@ -901,21 +981,24 @@ func (b *builder) stmtList(fn *Function, list []ast.Stmt) { // selections of sel. // // wantAddr requests that the result is an address. If -// !sel.Indirect(), this may require that e be built in addr() mode; it +// !sel.indirect, this may require that e be built in addr() mode; it // must thus be addressable. // // escaping is defined as per builder.addr(). -func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, sel *types.Selection, source ast.Node) Value { +func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, sel *selection, source ast.Node) Value { var v Value - if wantAddr && !sel.Indirect() && !isPointer(fn.Pkg.typeOf(e)) { + if wantAddr && !sel.indirect && !isPointerCore(fn.typeOf(e)) { v = b.addr(fn, e, escaping).address(fn) } else { v = b.expr(fn, e) } - last := len(sel.Index()) - 1 - v = emitImplicitSelections(fn, v, sel.Index()[:last], source) - if !wantAddr && isPointer(v.Type()) { + last := len(sel.index) - 1 + v = emitImplicitSelections(fn, v, sel.index[:last], source) + if types.IsInterface(v.Type()) { + // When v is an interface, sel.Kind()==MethodValue and v.f is invoked. + // So v is not loaded, even if v has a pointer core type. + } else if !wantAddr && isPointerCore(v.Type()) { v = emitLoad(fn, v, e) } return v @@ -925,33 +1008,37 @@ func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, se // (Func, Method, Recv, Args[0]) based on the kind of invocation // occurring in e. func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) { - // Is this a method call? - if selector, ok := unparen(e.Fun).(*ast.SelectorExpr); ok { - sel, ok := fn.Pkg.info.Selections[selector] - if ok && sel.Kind() == types.MethodVal { - obj := sel.Obj().(*types.Func) + // Is this a (possibly generic) method call? + m := ast.Unparen(e.Fun) + switch e := m.(type) { + case *ast.IndexExpr: + m = e.X + case *ast.IndexListExpr: + m = e.X + } + if selector, ok := m.(*ast.SelectorExpr); ok { + sel := fn.selection(selector) + if sel != nil && sel.kind == types.MethodVal { + obj := sel.obj.(*types.Func) recv := recvType(obj) + wantAddr := isPointer(recv) escaping := true v := b.receiver(fn, selector.X, wantAddr, escaping, sel, selector) if types.IsInterface(recv) { // Invoke-mode call. - - // Methods in interfaces cannot have their own type parameters, so we needn't do anything for type - // parameters. - c.Value = v + c.Value = v // possibly type param c.Method = obj } else { // "Call"-mode call. - - // declaredFunc takes care of creating wrappers for functions with type parameters. - c.Value = fn.Prog.declaredFunc(obj) + targs := fn.subtargs(selector.Sel) + c.Value = fn.Prog.objectMethod(obj, targs, b) c.Args = append(c.Args, v) } return } - // sel.Kind()==MethodExpr indicates T.f() or (*T).f(): + // sel.kind==MethodExpr indicates T.f() or (*T).f(): // a statically dispatched call to the method f in the // method-set of T or *T. T may be an interface. // @@ -981,9 +1068,8 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) { // - apply implicit field selections. // - use MethodVal logic to populate fields of c. } + // Evaluate the function operand in the usual way. - // - // Code in expr takes care of creating wrappers for functions with type parameters. c.Value = b.expr(fn, e.Fun) } @@ -1034,16 +1120,15 @@ func (b *builder) emitCallArgs(fn *Function, sig *types.Signature, e *ast.CallEx st := sig.Params().At(np).Type().(*types.Slice) vt := st.Elem() if len(varargs) == 0 { - args = append(args, emitConst(fn, nilConst(st, nil))) + args = append(args, zeroConst(st, nil)) } else { // Replace a suffix of args with a slice containing it. at := types.NewArray(vt, int64(len(varargs))) a := emitNew(fn, at, e, "varargs") - a.source = e for i, arg := range varargs { iaddr := &IndexAddr{ X: a, - Index: emitConst(fn, intConst(int64(i), nil)), + Index: intConst(int64(i), nil), } iaddr.setType(types.NewPointer(vt)) fn.emit(iaddr, e) @@ -1065,7 +1150,7 @@ func (b *builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) { b.setCallFunc(fn, e, c) // Then append the other actual parameters. - sig, _ := typeutil.CoreType(fn.Pkg.typeOf(e.Fun)).(*types.Signature) + sig, _ := typeutil.CoreType(fn.typeOf(e.Fun)).(*types.Signature) if sig == nil { panic(fmt.Sprintf("no signature for call of %s", e.Fun)) } @@ -1129,7 +1214,7 @@ func (b *builder) assignStmt(fn *Function, lhss, rhss []ast.Expr, isDef bool, so var lval lvalue = blank{} if !isBlankIdent(lhs) { if isDef { - if obj, ok := fn.Pkg.info.Defs[lhs.(*ast.Ident)].(*types.Var); ok { + if obj, ok := fn.info.Defs[lhs.(*ast.Ident)].(*types.Var); ok { emitLocalVar(fn, obj, lhs) isZero[i] = true } @@ -1190,56 +1275,121 @@ func (b *builder) arrayLen(fn *Function, elts []ast.Expr) int64 { // x := T{a: 1} // x = T{a: x.a} // -// all the reads must occur before all the writes. This is implicitly handled by the write buffering effected by -// compositeElement and explicitly by the storebuf for when we don't use CompositeValue. +// all the reads must occur before all the writes. Thus all stores to +// loc are emitted to the storebuf sb for later execution. // // A CompositeLit may have pointer type only in the recursive (nested) // case when the type name is implicit. e.g. in []*T{{}}, the inner // literal has type *T behaves like &T{}. // In that case, addr must hold a T, not a *T. func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero bool, sb *storebuf) { - typ := deref(fn.Pkg.typeOf(e)) + typ := deref(fn.typeOf(e)) // retain the named/alias/param type, if any switch t := typeutil.CoreType(typ).(type) { case *types.Struct: lvalue := &address{addr: addr, expr: e} if len(e.Elts) == 0 { if !isZero { - sb.store(lvalue, zeroValue(fn, deref(addr.Type()), e), e) + sb.store(lvalue, zeroConst(deref(addr.Type()), e), e) } } else { v := &CompositeValue{ Values: make([]Value, t.NumFields()), } - for i := 0; i < t.NumFields(); i++ { - v.Values[i] = emitConst(fn, zeroConst(t.Field(i).Type(), e)) + for i := range t.NumFields() { + v.Values[i] = zeroConst(t.Field(i).Type(), e) } v.setType(typ) + type chainElement struct { + children []chainElement + cv *CompositeValue + } + + chain := chainElement{ + cv: v, + } + for i, e := range e.Elts { - fieldIndex := i if kv, ok := e.(*ast.KeyValueExpr); ok { fname := kv.Key.(*ast.Ident).Name - for i, n := 0, t.NumFields(); i < n; i++ { - sf := t.Field(i) - if sf.Name() == fname { - fieldIndex = i - e = kv.Value - break + _, index, _ := types.LookupFieldOrMethod(t, true, fn.declaredPackage().Pkg, fname) + + var parent *chainElement + chain := &chain + chainCoreType := t + + // Ensure that the entire chain of embedded fields has + // corresponding CompositeValues + for _, idx := range index[:len(index)-1] { + if idx >= len(chain.children) { + n := make([]chainElement, idx+1) + copy(n, chain.children) + chain.children = n + } + + parent = chain + chain = &chain.children[idx] + treeType := chainCoreType.Field(idx).Type() + chainCoreType = typeutil.CoreType(treeType).(*types.Struct) + if chain.cv == nil { + ncv := &CompositeValue{ + Values: make([]Value, chainCoreType.NumFields()), + } + for i := range chainCoreType.NumFields() { + ncv.Values[i] = zeroConst(chainCoreType.Field(i).Type(), kv) + } + ncv.setType(treeType) + chain.cv = ncv + ce := &compositeElement{ + cv: parent.cv, + idx: idx, + t: ncv.Type(), + } + parent.cv.Bitmap.SetBit(&parent.cv.Bitmap, idx, 1) + parent.cv.NumSet++ + sb.store(ce, chain.cv, kv) } } + + ce := &compositeElement{ + cv: chain.cv, + idx: index[len(index)-1], + t: chainCoreType.Field(index[len(index)-1]).Type(), + expr: kv.Value, + } + // We use b.assign for its handling of implicit & (which, + // albeit not needed for structs now, may be needed in the + // future), but no store buffer because 1) it's not needed 2) + // implicit conversions have to be emitted before we emit the + // CompositeValue. + b.assign(fn, ce, kv.Value, isZero, nil, kv) + chain.cv.Bitmap.SetBit(&chain.cv.Bitmap, index[len(index)-1], 1) + chain.cv.NumSet++ + } else { + ce := &compositeElement{ + cv: v, + idx: i, + t: t.Field(i).Type(), + expr: e, + } + b.assign(fn, ce, e, isZero, nil, e) + v.Bitmap.SetBit(&v.Bitmap, i, 1) + v.NumSet++ + } + } + + var dfs func(t chainElement) + dfs = func(t chainElement) { + for _, tt := range t.children { + dfs(tt) } - ce := &compositeElement{ - cv: v, - idx: fieldIndex, - t: t.Field(fieldIndex).Type(), - expr: e, + // XXX better e + if t.cv != nil { + fn.emit(t.cv, e) } - b.assign(fn, ce, e, isZero, sb, e) - v.Bitmap.SetBit(&v.Bitmap, fieldIndex, 1) - v.NumSet++ } - fn.emit(v, e) + dfs(chain) sb.store(lvalue, v, e) } @@ -1258,7 +1408,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero var final Value if len(e.Elts) == 0 { if !isZero { - zc := emitConst(fn, zeroConst(at, e)) + zc := zeroConst(at, e) final = zc } } else { @@ -1267,7 +1417,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero v := &CompositeValue{ Values: make([]Value, at.Len()), } - zc := emitConst(fn, zeroConst(at.Elem(), e)) + zc := zeroConst(at.Elem(), e) for i := range v.Values { v.Values[i] = zc } @@ -1283,7 +1433,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero if idx != nil { idxval = idx.Int64() + 1 } - idx = emitConst(fn, intConst(idxval, e)).(*Const) + idx = intConst(idxval, e) } iaddr := &compositeElement{ @@ -1293,7 +1443,11 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero expr: e, } - b.assign(fn, iaddr, e, true, sb, e) + // We use b.assign for its handling of implicit &, but no + // store buffer because 1) it's not needed 2) implicit + // conversions have to be emitted before we emit the + // CompositeValue. + b.assign(fn, iaddr, e, true, nil, e) v.Bitmap.SetBit(&v.Bitmap, int(idx.Int64()), 1) v.NumSet++ } @@ -1304,7 +1458,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero // like []int{1<<62: 1}. if !isZero { // memclear - sb.store(&address{array, nil}, zeroValue(fn, deref(array.Type()), e), e) + sb.store(&address{array, nil}, zeroConst(deref(array.Type()), e), e) } var idx *Const @@ -1317,7 +1471,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero if idx != nil { idxval = idx.Int64() + 1 } - idx = emitConst(fn, intConst(idxval, e)).(*Const) + idx = intConst(idxval, e) } iaddr := &IndexAddr{ X: array, @@ -1346,7 +1500,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero } case *types.Map: - m := &MakeMap{Reserve: emitConst(fn, intConst(int64(len(e.Elts)), e))} + m := &MakeMap{Reserve: intConst(int64(len(e.Elts)), e)} m.setType(typ) fn.emit(m, e) for _, e := range e.Elts { @@ -1358,8 +1512,13 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero // map[*struct{}]bool{{}: true} // An &-operation may be implied: // map[*struct{}]bool{&struct{}{}: true} + wantAddr := false + if _, ok := ast.Unparen(e.Key).(*ast.CompositeLit); ok { + wantAddr = isPointerCore(t.Key()) + } + var key Value - if _, ok := unparen(e.Key).(*ast.CompositeLit); ok && isPointer(t.Key()) { + if wantAddr { // A CompositeLit never evaluates to a pointer, // so if the type of the location is a pointer, // an &-operation is implied. @@ -1385,7 +1544,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero sb.store(&address{addr: addr, expr: e}, m, e) default: - panic("unexpected CompositeLit type: " + t.String()) + panic("unexpected CompositeLit type: " + typ.String()) } } @@ -1398,7 +1557,7 @@ func (b *builder) switchStmt(fn *Function, s *ast.SwitchStmt, label *lblock) { for _, iclause := range s.Body.List { clause := iclause.(*ast.CaseClause) for _, cond := range clause.List { - if fn.Pkg.info.Types[unparen(cond)].Value == nil { + if fn.info.Types[ast.Unparen(cond)].Value == nil { dynamic = true break } @@ -1501,20 +1660,11 @@ func (b *builder) switchStmtDynamic(fn *Function, s *ast.SwitchStmt, label *lblo if s.Init != nil { b.stmt(fn, s.Init) } - kTrue := emitConst(fn, NewConst(constant.MakeBool(true), tBool, nil)) + var tag Value = vTrue - var tagv Value = kTrue - var tagSource ast.Node = s if s.Tag != nil { - tagv = b.expr(fn, s.Tag) - tagSource = s.Tag + tag = b.expr(fn, s.Tag) } - // lifting only considers loads and stores, but we want different - // sigma nodes for the different comparisons. use a temporary and - // load it in every branch. - tag := emitLocal(fn, tagv.Type(), tagSource, "switch.value") - tag.comment = "switch.tag" - emitStore(fn, tag, tagv, tagSource) done := fn.newBasicBlock("switch.done") if label != nil { @@ -1553,20 +1703,12 @@ func (b *builder) switchStmtDynamic(fn *Function, s *ast.SwitchStmt, label *lblo var nextCond *BasicBlock for _, cond := range cc.List { nextCond = fn.newBasicBlock("switch.next") - if tagv == kTrue { - // emit a proper if/else chain instead of a comparison - // of a value against true. - // - // NOTE(dh): adonovan had a todo saying "don't forget - // conversions though". As far as I can tell, there - // aren't any conversions that we need to take care of - // here. `case bool(a) && bool(b)` as well as `case - // bool(a && b)` are being taken care of by b.cond, - // and `case a` where a is not of type bool is - // invalid. + // For boolean switches, emit short-circuit control flow, + // just like an if/else-chain. + if tag == vTrue && !isNonTypeParamInterface(fn.info.Types[cond].Type) { b.cond(fn, cond, body, nextCond) } else { - cond := emitCompare(fn, token.EQL, emitLoad(fn, tag, cond), b.expr(fn, cond), cond) + cond := emitCompare(fn, token.EQL, tag, b.expr(fn, cond), cond) emitIf(fn, cond, body, nextCond, cond.Source()) } @@ -1601,6 +1743,8 @@ func (b *builder) switchStmtDynamic(fn *Function, s *ast.SwitchStmt, label *lblo fn.currentBlock = done } +// typeSwitchStmt emits to fn code for the type switch statement s, optionally +// labelled by label. func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lblock) { if s.Init != nil { b.stmt(fn, s.Init) @@ -1609,27 +1753,25 @@ func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lbl var tag Value switch e := s.Assign.(type) { case *ast.ExprStmt: // x.(type) - tag = b.expr(fn, unparen(e.X).(*ast.TypeAssertExpr).X) + tag = b.expr(fn, ast.Unparen(e.X).(*ast.TypeAssertExpr).X) case *ast.AssignStmt: // y := x.(type) - tag = b.expr(fn, unparen(e.Rhs[0]).(*ast.TypeAssertExpr).X) + tag = b.expr(fn, ast.Unparen(e.Rhs[0]).(*ast.TypeAssertExpr).X) default: panic("unreachable") } - tagPtr := emitLocal(fn, tag.Type(), tag.Source(), "") - emitStore(fn, tagPtr, tag, tag.Source()) // +1 in case there's no explicit default case heads := make([]*BasicBlock, 0, len(s.Body.List)+1) entry := fn.currentBlock - done := fn.newBasicBlock("done") + done := fn.newBasicBlock("typeswitch.done") if label != nil { label._break = done } // set up type switch and constant switch, populate their conditions tswtch := &TypeSwitch{ - Tag: emitLoad(fn, tagPtr, tag.Source()), + Tag: tag, Conds: make([]types.Type, 0, len(s.Body.List)+1), } cswtch := &ConstantSwitch{ @@ -1641,7 +1783,7 @@ func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lbl var default_ *ast.CaseClause for _, clause := range s.Body.List { cc := clause.(*ast.CaseClause) - if obj, ok := fn.Pkg.info.Implicits[cc].(*types.Var); ok { + if obj, ok := fn.info.Implicits[cc].(*types.Var); ok { emitLocalVar(fn, obj, cc) } if cc.List == nil { @@ -1649,12 +1791,12 @@ func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lbl default_ = cc } else { for _, expr := range cc.List { - tswtch.Conds = append(tswtch.Conds, fn.Pkg.typeOf(expr)) - cswtch.Conds = append(cswtch.Conds, emitConst(fn, intConst(int64(index), expr))) + tswtch.Conds = append(tswtch.Conds, fn.typeOf(expr)) + cswtch.Conds = append(cswtch.Conds, intConst(int64(index), expr)) index++ } if len(cc.List) == 1 { - rets = append(rets, fn.Pkg.typeOf(cc.List[0])) + rets = append(rets, fn.typeOf(cc.List[0])) } else { for range cc.List { rets = append(rets, tag.Type()) @@ -1675,10 +1817,7 @@ func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lbl // default branch fn.currentBlock = entry fn.emit(tswtch, s) - cswtch.Conds = append(cswtch.Conds, emitConst(fn, intConst(int64(-1), nil))) - // in theory we should add a local and stores/loads for tswtch, to - // generate sigma nodes in the branches. however, there isn't any - // useful information we could possibly attach to it. + cswtch.Conds = append(cswtch.Conds, intConst(int64(-1), nil)) cswtch.Tag = emitExtract(fn, tswtch, 0, s) fn.emit(cswtch, s) @@ -1696,7 +1835,7 @@ func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lbl heads = append(heads, head) fn.currentBlock = head - if obj, ok := fn.Pkg.info.Implicits[cc].(*types.Var); ok { + if obj, ok := fn.info.Implicits[cc].(*types.Var); ok { // In a switch y := x.(type), each case clause // implicitly declares a distinct object y. // In a single-type case, y has that type. @@ -1705,7 +1844,7 @@ func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lbl l := fn.vars[obj] if rets[index] == tUntypedNil { - emitStore(fn, l, emitConst(fn, nilConst(tswtch.Tag.Type(), nil)), s.Assign) + emitStore(fn, l, nilConst(tswtch.Tag.Type(), nil), s.Assign) } else { x := emitExtract(fn, tswtch, index+1, s.Assign) emitStore(fn, l, x, nil) @@ -1736,7 +1875,7 @@ func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lbl tail: fn.targets, _break: done, } - if obj, ok := fn.Pkg.info.Implicits[default_].(*types.Var); ok { + if obj, ok := fn.info.Implicits[default_].(*types.Var); ok { l := fn.vars[obj] x := emitExtract(fn, tswtch, index+1, s.Assign) emitStore(fn, l, x, s) @@ -1761,7 +1900,6 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) (no instr.setType(types.NewTuple(varIndex, varOk)) fn.emit(instr, s) fn.emit(new(Unreachable), s) - addEdge(fn.currentBlock, fn.Exit) return true } @@ -1806,7 +1944,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) (no Dir: types.SendOnly, Chan: ch, Send: emitConv(fn, b.expr(fn, comm.Value), - typeutil.CoreType(ch.Type()).Underlying().(*types.Chan).Elem(), comm), + typeutil.CoreType(fn.typ(ch.Type())).(*types.Chan).Elem(), comm), Pos: comm.Arrow, } if debugInfo { @@ -1814,7 +1952,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) (no } case *ast.AssignStmt: // x := <-ch - recv := unparen(comm.Rhs[0]).(*ast.UnaryExpr) + recv := ast.Unparen(comm.Rhs[0]).(*ast.UnaryExpr) st = &SelectState{ Dir: types.RecvOnly, Chan: b.expr(fn, recv.X), @@ -1825,7 +1963,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) (no } case *ast.ExprStmt: // <-ch - recv := unparen(comm.X).(*ast.UnaryExpr) + recv := ast.Unparen(comm.X).(*ast.UnaryExpr) st = &SelectState{ Dir: types.RecvOnly, Chan: b.expr(fn, recv.X), @@ -1849,7 +1987,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) (no vars = append(vars, varIndex, varOk) for _, st := range states { if st.Dir == types.RecvOnly { - tElem := typeutil.CoreType(st.Chan.Type()).Underlying().(*types.Chan).Elem() + tElem := typeutil.CoreType(fn.typ(st.Chan.Type())).(*types.Chan).Elem() vars = append(vars, anonVar(tElem)) } } @@ -1886,10 +2024,10 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) (no b.stmtList(fn, clause.Body) emitJump(fn, done, s) fn.targets = fn.targets.tail - swtch.Conds = append(swtch.Conds, emitConst(fn, intConst(-1, nil))) + swtch.Conds = append(swtch.Conds, intConst(-1, nil)) continue } - swtch.Conds = append(swtch.Conds, emitConst(fn, intConst(int64(state), nil))) + swtch.Conds = append(swtch.Conds, intConst(int64(state), nil)) body := fn.newBasicBlock("select.body") fn.currentBlock = body bodies = append(bodies, body) @@ -1947,23 +2085,23 @@ func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) { // Use forStmtGo122 instead if it applies. if s.Init != nil { if assign, ok := s.Init.(*ast.AssignStmt); ok && assign.Tok == token.DEFINE { - if version.Compare(fn.goversion, "go1.22") >= 0 { + if versions.AtLeast(fn.goversion, versions.Go1_22) { b.forStmtGo122(fn, s, label) return } } } - // ...init... - // jump loop + // ...init... + // jump loop // loop: - // if cond goto body else done + // if cond goto body else done // body: - // ...body... - // jump post - // post: (target of continue) - // ...post... - // jump loop + // ...body... + // jump post + // post: (target of continue) + // ...post... + // jump loop // done: (target of break) if s.Init != nil { b.stmt(fn, s.Init) @@ -2052,9 +2190,10 @@ func (b *builder) forStmtGo122(fn *Function, s *ast.ForStmt, label *lblock) { vars := make([]loopVar, len(init.Lhs)) for i, lhs := range init.Lhs { v := identVar(fn, lhs.(*ast.Ident)) + typ := fn.typ(v.Type()) fn.currentBlock = pre - outer := emitLocal(fn, v.Type(), lhs, v.Name()) + outer := emitLocal(fn, typ, lhs, v.Name()) fn.currentBlock = loop phi := &Phi{} @@ -2066,7 +2205,7 @@ func (b *builder) forStmtGo122(fn *Function, s *ast.ForStmt, label *lblock) { // If next is local, it reuses the address and zeroes the old value so // load before allocating next. load := emitLoad(fn, phi, init) - next := emitLocal(fn, v.Type(), lhs, v.Name()) + next := emitLocal(fn, typ, lhs, v.Name()) store := emitStore(fn, next, load, s) phi.Edges = []Value{outer, next} // pre edge is emitted before post edge. @@ -2079,7 +2218,7 @@ func (b *builder) forStmtGo122(fn *Function, s *ast.ForStmt, label *lblock) { fn.vars[v.obj] = v.outer } const isDef = false // assign to already-allocated outers - b.assignStmt(fn, init.Lhs, init.Rhs, isDef, s) + b.assignStmt(fn, init.Lhs, init.Rhs, isDef, init) if label != nil { label._break = done label._continue = post @@ -2163,8 +2302,8 @@ func (b *builder) forStmtGo122(fn *Function, s *ast.ForStmt, label *lblock) { // Remove instructions for phi, load, and store. // lift() will remove the unused i_next *Alloc. isDead := func(i Instruction) bool { return dead[i] } - loop.Instrs = removeInstrsIf(loop.Instrs, isDead) - post.Instrs = removeInstrsIf(post.Instrs, isDead) + loop.Instrs = slices.DeleteFunc(loop.Instrs, isDead) + post.Instrs = slices.DeleteFunc(post.Instrs, isDead) } } @@ -2174,46 +2313,40 @@ func (b *builder) forStmtGo122(fn *Function, s *ast.ForStmt, label *lblock) { // forPos is the position of the "for" token. func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, source ast.Node) (k, v Value, loop, done *BasicBlock) { // - // length = len(x) - // index = -1 - // loop: (target of continue) - // index++ - // if index < length goto body else done + // length = len(x) + // index = -1 + // loop: (target of continue) + // index++ + // if index < length goto body else done // body: - // k = index - // v = x[index] - // ...body... - // jump loop - // done: (target of break) - - // We store in an Alloc and load it on each iteration so that lifting produces the necessary σ nodes - xAlloc := newVariable(fn, x.Type(), source) - xAlloc.store(x) + // k = index + // v = x[index] + // ...body... + // jump loop + // done: (target of break) // Determine number of iterations. - // - // We store the length in an Alloc and load it on each iteration so that lifting produces the necessary σ nodes - length := newVariable(fn, tInt, source) - if arr, ok := typeutil.CoreType(deref(x.Type())).(*types.Array); ok { - // For array or *array, the number of iterations is known statically thanks to the type. We avoid a data - // dependence upon x, permitting later dead-code elimination if x is pure, static unrolling, etc. Ranging over a - // nil *array may have >0 iterations. We still generate code for x, in case it has effects. - // - // We use the core type of x, even though the length of type parameters isn't constant as per the language - // specification. Just because len(x) isn't constant doesn't mean we can't emit IR that takes advantage of a - // known length. - length.store(emitConst(fn, intConst(arr.Len(), nil))) + var length Value + dt := deref(x.Type()) + if arr, ok := typeutil.CoreType(dt).(*types.Array); ok { + // For array or *array, the number of iterations is + // known statically thanks to the type. We avoid a + // data dependence upon x, permitting later dead-code + // elimination if x is pure, static unrolling, etc. + // Ranging over a nil *array may have >0 iterations. + // We still generate code for x, in case it has effects. + length = intConst(arr.Len(), nil) } else { // length = len(x). var c Call c.Call.Value = makeLen(x.Type()) c.Call.Args = []Value{x} c.setType(tInt) - length.store(fn.emit(&c, source)) + length = fn.emit(&c, source) } index := emitLocal(fn, tInt, source, "rangeindex") - emitStore(fn, index, emitConst(fn, intConst(-1, nil)), source) + emitStore(fn, index, intConst(-1, nil), source) loop = fn.newBasicBlock("rangeindex.loop") emitJump(fn, loop, source) @@ -2222,20 +2355,19 @@ func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, source ast. incr := &BinOp{ Op: token.ADD, X: emitLoad(fn, index, source), - Y: emitConst(fn, intConst(1, nil)), + Y: vOne, } incr.setType(tInt) emitStore(fn, index, fn.emit(incr, source), source) body := fn.newBasicBlock("rangeindex.body") done = fn.newBasicBlock("rangeindex.done") - emitIf(fn, emitCompare(fn, token.LSS, incr, length.load(), source), body, done, source) + emitIf(fn, emitCompare(fn, token.LSS, incr, length, source), body, done, source) fn.currentBlock = body k = emitLoad(fn, index, source) if tv != nil { - x := xAlloc.load() - switch t := typeutil.CoreType(x.Type()).Underlying().(type) { + switch t := typeutil.CoreType(x.Type()).(type) { case *types.Array: instr := &Index{ X: x, @@ -2286,48 +2418,53 @@ func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, source ast // done: (target of break) // + var ak, av types.Type + isString := false + if m, ok := typeutil.CoreType(x.Type()).(*types.Map); ok { + ak, av = m.Key(), m.Elem() + } else { + isString = true + ak, av = tInt, tRune + } if tk == nil { - tk = tInvalid + ak = tInvalid } if tv == nil { - tv = tInvalid + av = tInvalid } rng := &Range{X: x} rng.setType(typeutil.NewIterator(types.NewTuple( varOk, - newVar("k", tk), - newVar("v", tv), + newVar("k", ak), + newVar("v", av), ))) - it := newVariable(fn, rng.typ, source) - it.store(fn.emit(rng, source)) + it := fn.emit(rng, source) loop = fn.newBasicBlock("rangeiter.loop") emitJump(fn, loop, source) fn.currentBlock = loop - // Go doesn't currently allow ranging over string|[]byte, so isString is decidable. - _, isString := typeutil.CoreType(x.Type()).Underlying().(*types.Basic) - - okvInstr := &Next{ - Iter: it.load(), + okv := &Next{ + Iter: it, IsString: isString, } - okvInstr.setType(rng.typ.(*typeutil.Iterator).Elem()) - fn.emit(okvInstr, source) - okv := newVariable(fn, okvInstr.Type(), source) - okv.store(okvInstr) + okv.setType(rng.typ.(*typeutil.Iterator).Elem()) + fn.emit(okv, source) body := fn.newBasicBlock("rangeiter.body") done = fn.newBasicBlock("rangeiter.done") - emitIf(fn, emitExtract(fn, okv.load(), 0, source), body, done, source) + emitIf(fn, emitExtract(fn, okv, 0, source), body, done, source) fn.currentBlock = body - if tk != tInvalid { - k = emitExtract(fn, okv.load(), 1, source) + // The assignment may widen a map or string + // key/value to a variable's interface type + // (cases #1 and #2 of go.dev/issue/78110). + if tk != nil { + k = emitConv(fn, emitExtract(fn, okv, 1, source), tk, source) } - if tv != tInvalid { - v = emitExtract(fn, okv.load(), 2, source) + if tv != nil { + v = emitConv(fn, emitExtract(fn, okv, 2, source), tv, source) } return } @@ -2353,16 +2490,14 @@ func (b *builder) rangeChan(fn *Function, x Value, tk types.Type, source ast.Nod emitJump(fn, loop, source) fn.currentBlock = loop - recv := emitRecv(fn, x, true, types.NewTuple(newVar("k", typeutil.CoreType(x.Type()).Underlying().(*types.Chan).Elem()), varOk), source) - retv := newVariable(fn, recv.Type(), source) - retv.store(recv) + retv := emitRecv(fn, x, true, types.NewTuple(newVar("k", typeutil.CoreType(x.Type()).(*types.Chan).Elem()), varOk), source) body := fn.newBasicBlock("rangechan.body") done = fn.newBasicBlock("rangechan.done") - emitIf(fn, emitExtract(fn, retv.load(), 1, source), body, done, source) + emitIf(fn, emitExtract(fn, retv, 1, source), body, done, source) fn.currentBlock = body if tk != nil { - k = emitExtract(fn, retv.load(), 0, source) + k = emitExtract(fn, retv, 0, source) } return } @@ -2393,7 +2528,7 @@ func (b *builder) rangeInt(fn *Function, x Value, tk types.Type, source ast.Node body := fn.newBasicBlock("rangeint.body") done = fn.newBasicBlock("rangeint.done") - emitIf(fn, emitCompare(fn, token.LSS, emitConst(fn, zeroConst(T, source)), x, source), body, done, source) + emitIf(fn, emitCompare(fn, token.LSS, zeroConst(T, source), x, source), body, done, source) loop = fn.newBasicBlock("rangeint.loop") fn.currentBlock = loop @@ -2401,7 +2536,7 @@ func (b *builder) rangeInt(fn *Function, x Value, tk types.Type, source ast.Node incr := &BinOp{ Op: token.ADD, X: emitLoad(fn, iter, source), - Y: emitConv(fn, emitConst(fn, intConst(1, source)), T, source), + Y: emitConv(fn, intConst(1, source), T, source), } incr.setType(T) emitStore(fn, iter, fn.emit(incr, source), source) @@ -2418,41 +2553,15 @@ func (b *builder) rangeInt(fn *Function, x Value, tk types.Type, source ast.Node return } -type variable struct { - alloc *Alloc - fn *Function - source ast.Node -} - -func newVariable(fn *Function, typ types.Type, source ast.Node) *variable { - alloc := &Alloc{} - alloc.setType(types.NewPointer(typ)) - fn.emit(alloc, source) - fn.Locals = append(fn.Locals, alloc) - return &variable{ - alloc: alloc, - fn: fn, - source: source, - } -} - -func (v *variable) store(sv Value) { - emitStore(v.fn, v.alloc, sv, v.source) -} - -func (v *variable) load() Value { - return emitLoad(v.fn, v.alloc, v.source) -} - // rangeStmt emits to fn code for the range statement s, optionally // labelled by label. func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock, source ast.Node) { var tk, tv types.Type if s.Key != nil && !isBlankIdent(s.Key) { - tk = fn.Pkg.typeOf(s.Key) + tk = fn.typeOf(s.Key) } if s.Value != nil && !isBlankIdent(s.Value) { - tv = fn.Pkg.typeOf(s.Value) + tv = fn.typeOf(s.Value) } // create locals for s.Key and s.Value @@ -2470,8 +2579,7 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock, sourc } } - afterGo122 := version.Compare(fn.goversion, "go1.22") >= 0 - + afterGo122 := versions.AtLeast(fn.goversion, versions.Go1_22) if s.Tok == token.DEFINE && !afterGo122 { // pre-go1.22: If iteration variables are defined (:=), this // occurs once outside the loop. @@ -2482,7 +2590,7 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock, sourc var k, v Value var loop, done *BasicBlock - switch rt := typeutil.CoreType(x.Type()).Underlying().(type) { + switch rt := typeutil.CoreType(x.Type()).(type) { case *types.Slice, *types.Array, *types.Pointer: // *array k, v, loop, done = b.rangeIndexed(fn, x, tv, source) @@ -2509,7 +2617,7 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock, sourc // for x := range f { ... } // into // f(func(x T) bool { ... }) - b.rangeFunc(fn, x, tk, tv, s, label) + b.rangeFunc(fn, x, s, label) return default: @@ -2555,7 +2663,7 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock, sourc // rangeFunc emits to fn code for the range-over-func rng.Body of the iterator // function x, optionally labelled by label. It creates a new anonymous function // yield for rng and builds the function. -func (b *builder) rangeFunc(fn *Function, x Value, tk, tv types.Type, rng *ast.RangeStmt, label *lblock) { +func (b *builder) rangeFunc(fn *Function, x Value, rng *ast.RangeStmt, label *lblock) { // Consider the SSA code for the outermost range-over-func in fn: // // func fn(...) (ret R) { @@ -2678,15 +2786,22 @@ func (b *builder) rangeFunc(fn *Function, x Value, tk, tv types.Type, rng *ast.R /* synthetic yield function for body of range-over-func loop */ y := &Function{ - name: fmt.Sprintf("%s$%d", fn.Name(), anonIdx+1), - Signature: ysig, - Synthetic: SyntheticRangeOverFuncYield, - parent: fn, - Pkg: fn.Pkg, - Prog: fn.Prog, - functionBody: new(functionBody), - } - y.source = rng + name: fmt.Sprintf("%s$%d", fn.Name(), anonIdx+1), + Signature: ysig, + Synthetic: "range-over-func yield", + pos: rng.Range, + parent: fn, + anonIdx: int32(len(fn.AnonFuncs)), + Pkg: fn.Pkg, + Prog: fn.Prog, + syntax: rng, + info: fn.info, + build: (*builder).buildYieldFunc, + topLevelOrigin: nil, + typeparams: fn.typeparams, + typeargs: fn.typeargs, + subst: fn.subst, + } y.goversion = fn.goversion y.jump = jump y.deferstack = fn.deferstack @@ -2704,7 +2819,7 @@ func (b *builder) rangeFunc(fn *Function, x Value, tk, tv types.Type, rng *ast.R // * create new exit nodes in exits. // (y is not marked 'built' until the end of the enclosing FuncDecl.) unresolved := len(fn.exits) - b.buildYieldFunc(y) + y.build(b, y) fn.uniq = y.uniq // resume after y's unique values // Emit the call of y. @@ -2755,28 +2870,25 @@ func (b *builder) buildYieldResume(fn *Function, jump *types.Var, exits []*exit, bodies[1] = fn.newBasicBlock("rangefunc.resume.ready") conds := make([]Value, 2, 2+len(exits)) - conds[0] = emitConst(fn, jBusy()) - conds[1] = emitConst(fn, jReady()) + conds[0] = jBusy + conds[1] = jReady fn.currentBlock = bodies[0] fn.emit( &Panic{ - X: emitConv(fn, emitConst(fn, stringConst("iterator call did not preserve panic", nil)), tEface, nil), + X: emitConv(fn, jDroppedPanic, tEface, nil), }, nil, ) - addEdge(fn.currentBlock, fn.Exit) fn.currentBlock = bodies[1] - storeVar(fn, jump, emitConst(fn, jDone()), nil) + storeVar(fn, jump, jDone, nil) emitJump(fn, done, nil) for _, e := range exits { body := fn.newBasicBlock(fmt.Sprintf("rangefunc.resume.exit.%d", e.id)) bodies = append(bodies, body) - id_ := intConst(e.id, nil) - id_.comment = fmt.Sprintf("rangefunc.exit.%d", e.id) - id := emitConst(fn, id_) + id := intConst(e.id, nil) conds = append(conds, id) fn.currentBlock = body @@ -2797,15 +2909,17 @@ func (b *builder) buildYieldResume(fn *Function, jump *types.Var, exits []*exit, // fn is a range-over-func function. storeVar(fn, fn.jump, id, e.source) - vFalse := emitConst(fn, NewConst(constant.MakeBool(false), tBool, e.source)) - emitReturn(fn, []Value{vFalse}, e.source) + vFalse := NewConst(constant.MakeBool(false), tBool, e.source) + fn.emit(&Return{Results: []Value{vFalse}}, e.source) case e.block == nil && e.label == nil: // return from fn? // case EXIT(id): { return ... } - - // The results have already been stored to variables in fn.results, so - // emitReturn doesn't have to do it again. - emitReturn(fn, nil, e.source) + fn.emit(new(RunDefers), e.source) + results := make([]Value, len(fn.results)) + for i, r := range fn.results { + results[i] = emitLoad(fn, r, e.source) + } + fn.emit(&Return{Results: results}, e.source) case e.block != nil: // case EXIT(id): goto block @@ -2874,7 +2988,7 @@ start: instr := &Send{ Chan: b.expr(fn, s.Chan), X: emitConv(fn, b.expr(fn, s.Value), - typeutil.CoreType(fn.Pkg.typeOf(s.Chan)).Underlying().(*types.Chan).Elem(), s), + typeutil.CoreType(fn.typeOf(s.Chan)).(*types.Chan).Elem(), s), } fn.emit(instr, s) @@ -2884,7 +2998,7 @@ start: op = token.SUB } loc := b.addr(fn, s.X, false) - b.assignOp(fn, loc, emitConst(fn, NewConst(constant.MakeInt64(1), loc.typ(), s)), op, s) + b.assignOp(fn, loc, NewConst(constant.MakeInt64(1), loc.typ(), s), op, s) case *ast.AssignStmt: switch s.Tok { @@ -2907,11 +3021,14 @@ start: // The "intrinsics" new/make/len/cap are forbidden here. // panic is treated like an ordinary function call. deferstack := emitLoad(fn, fn.lookup(fn.deferstack, false), s) - v := Defer{_DeferStack: deferstack} + v := Defer{DeferStack: deferstack} b.setCall(fn, s.Call, &v.Call) - fn.hasDefer = true fn.emit(&v, s) + // A deferred call can cause recovery from panic, + // and control resumes at the Recover block. + createRecoverBlock(fn.source) + case *ast.ReturnStmt: b.returnStmt(fn, s) @@ -2993,12 +3110,10 @@ func (b *builder) branchStmt(fn *Function, s *ast.BranchStmt) { } else { // break outside of fn. // fn must be a range-over-func e := blockExit(fn, block, s) - id_ := intConst(e.id, s) - id_.comment = fmt.Sprintf("rangefunc.exit.%d", e.id) - id := emitConst(fn, id_) + id := intConst(e.id, s) storeVar(fn, fn.jump, id, s) - vFalse := emitConst(fn, NewConst(constant.MakeBool(false), tBool, s)) - emitReturn(fn, []Value{vFalse}, s) + vFalse := NewConst(constant.MakeBool(false), tBool, s) + fn.emit(&Return{Results: []Value{vFalse}}, e.source) } fn.currentBlock = fn.newBasicBlock("unreachable") } @@ -3009,7 +3124,7 @@ func (b *builder) returnStmt(fn *Function, s *ast.ReturnStmt) { var results []Value - sig := fn.sourceFn.Signature // signature of the enclosing source function + sig := fn.source.Signature // signature of the enclosing source function // Convert return operands to result type. if len(s.Results) == 1 && sig.Results().Len() > 1 { @@ -3032,7 +3147,7 @@ func (b *builder) returnStmt(fn *Function, s *ast.ReturnStmt) { // Store the results. for i, r := range results { var result Value // fn.sourceFn.result[i] conceptually - if fn == fn.sourceFn { + if fn == fn.source { result = fn.results[i] } else { // lookup needed? result = fn.lookup(fn.returnVars[i], false) @@ -3045,86 +3160,107 @@ func (b *builder) returnStmt(fn *Function, s *ast.ReturnStmt) { // The return statement is syntactically within the loop, // but the generated code is in the 'switch jump {...}' after it. e := returnExit(fn, s) - id_ := intConst(e.id, e.source) - id_.comment = fmt.Sprintf("rangefunc.exit.%d", e.id) - id := emitConst(fn, id_) + id := intConst(e.id, e.source) storeVar(fn, fn.jump, id, e.source) - vFalse := emitConst(fn, NewConst(constant.MakeBool(false), tBool, e.source)) - emitReturn(fn, []Value{vFalse}, e.source) + vFalse := NewConst(constant.MakeBool(false), tBool, e.source) + fn.emit(&Return{Results: []Value{vFalse}}, e.source) + fn.currentBlock = fn.newBasicBlock("unreachable") return } - // The results have already been stored to variables in fn.results, so - // emitReturn doesn't have to do it again. - emitReturn(fn, nil, s) + // Run function calls deferred in this + // function when explicitly returning from it. + fn.emit(new(RunDefers), s) + // Reload (potentially) named result variables to form the result tuple. + results = results[:0] + for _, nr := range fn.results { + results = append(results, emitLoad(fn, nr, s)) + } + + fn.emit(&Return{Results: results}, s) + fn.currentBlock = fn.newBasicBlock("unreachable") } -func emitReturn(fn *Function, results []Value, source ast.Node) { - for i, r := range results { - emitStore(fn, fn.results[i], r, source) +// A buildFunc is a strategy for building the SSA body for a function. +type buildFunc = func(*builder, *Function) + +// iterate causes all created but unbuilt functions to be built. As +// this may create new methods, the process is iterated until it +// converges. +// +// Waits for any dependencies to finish building. +func (b *builder) iterate() { + for ; b.finished < len(b.fns); b.finished++ { + fn := b.fns[b.finished] + b.buildFunction(fn) } - emitJump(fn, fn.Exit, source) - fn.currentBlock = fn.newBasicBlock("unreachable") + b.buildshared.markDone() + b.buildshared.wait() } // buildFunction builds IR code for the body of function fn. Idempotent. func (b *builder) buildFunction(fn *Function) { - if fn.Blocks != nil { - return // building already started + if fn.build != nil { + assert(fn.parent == nil, "anonymous functions should not be built by buildFunction()") + + if fn.Prog.mode&LogSource != 0 { + defer logStack("build %s @ %s", fn, fn.Prog.Fset.Position(fn.pos))() + } + fn.build(b, fn) + fn.done() } +} - var recvField *ast.FieldList - var body *ast.BlockStmt - var functype *ast.FuncType - switch n := fn.source.(type) { - case nil: - return // not a Go source function. (Synthetic, or from object file.) - case *ast.FuncDecl: - functype = n.Type - recvField = n.Recv - body = n.Body - case *ast.FuncLit: - functype = n.Type - body = n.Body - default: - panic(n) +// buildParamsOnly builds fn.Params from fn.Signature, but does not build fn.Body. +func (b *builder) buildParamsOnly(fn *Function) { + // For external (C, asm) functions or functions loaded from + // export data, we must set fn.Params even though there is no + // body code to reference them. + if recv := fn.Signature.Recv(); recv != nil { + // TODO(dh): should we synthesize a node so we have position info? + fn.addParamVar(recv, nil) + } + params := fn.Signature.Params() + for i, n := 0, params.Len(); i < n; i++ { + // TODO(dh): should we synthesize a node so we have position info? + fn.addParamVar(params.At(i), nil) } - if body == nil { - // External function. - if fn.Params == nil { - // This condition ensures we add a non-empty - // params list once only, but we may attempt - // the degenerate empty case repeatedly. - // TODO(adonovan): opt: don't do that. + // clear out other function state (keep consistent with finishBody) + fn.subst = nil +} - // We set Function.Params even though there is no body - // code to reference them. This simplifies clients. - if recv := fn.Signature.Recv(); recv != nil { - // XXX synthesize an ast.Node - fn.addParamVar(recv, nil) - } - params := fn.Signature.Params() - for i, n := 0, params.Len(); i < n; i++ { - // XXX synthesize an ast.Node - fn.addParamVar(params.At(i), nil) - } +// buildFromSyntax builds fn.Body from fn.syntax, which must be non-nil. +func (b *builder) buildFromSyntax(fn *Function) { + var ( + recvField *ast.FieldList + body *ast.BlockStmt + functype *ast.FuncType + ) + switch syntax := fn.syntax.(type) { + case *ast.FuncDecl: + functype = syntax.Type + recvField = syntax.Recv + body = syntax.Body + if body == nil { + b.buildParamsOnly(fn) // no body (non-Go function) + return } - return - } - if fn.Prog.mode&LogSource != 0 { - defer logStack("build function %s @ %s", fn, fn.Prog.Fset.Position(fn.Pos()))() + case *ast.FuncLit: + functype = syntax.Type + body = syntax.Body + case nil: + panic("no syntax") + default: + panic(syntax) // unexpected syntax } - fn.blocksets = b.blocksets - fn.Blocks = make([]*BasicBlock, 0, avgBlocks) - fn.sourceFn = fn + fn.source = fn fn.startBody() fn.createSyntacticParams(recvField, functype) fn.createDeferStack() - fn.exitBlock() b.stmt(fn, body) - if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb.Preds != nil) { + if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb == fn.Recover || cb.Preds != nil) { // Control fell off the end of the function's body block. // // Block optimizations eliminate the current block, if @@ -3132,15 +3268,10 @@ func (b *builder) buildFunction(fn *Function) { // if this no-arg return is ill-typed for // fn.Signature.Results, this block must be // unreachable. The sanity checker checks this. - // fn.emit(new(RunDefers)) - // fn.emit(new(Return)) - emitJump(fn, fn.Exit, nil) + fn.emit(new(RunDefers), nil) + fn.emit(new(Return), nil) } - optimizeBlocks(fn) - buildFakeExits(fn) fn.finishBody() - b.blocksets = fn.blocksets - fn.functionBody = nil } // buildYieldFunc builds the body of the yield function created @@ -3150,7 +3281,7 @@ func (b *builder) buildYieldFunc(fn *Function) { // // In pseudo-Go this roughly builds: // func yield(_k tk, _v tv) bool { - // if jump != READY { panic("yield function called after range loop exit") } + // if jump != READY { panic("yield function called after range loop exit") } // jump = BUSY // k, v = _k, _v // assign the iterator variable (if needed) // ... // rng.Body @@ -3158,15 +3289,13 @@ func (b *builder) buildYieldFunc(fn *Function) { // jump = READY // return true // } - s := fn.source.(*ast.RangeStmt) - fn.sourceFn = fn.parent.sourceFn + s := fn.syntax.(*ast.RangeStmt) + fn.source = fn.parent.source fn.startBody() params := fn.Signature.Params() for v := range params.Variables() { fn.addParamVar(v, nil) } - fn.addResultVar(fn.Signature.Results().At(0), nil) - fn.exitBlock() // Initial targets ycont := fn.newBasicBlock("yield-continue") @@ -3191,9 +3320,9 @@ func (b *builder) buildYieldFunc(fn *Function) { // return true saved := fn.currentBlock fn.currentBlock = ycont - storeVar(fn, fn.jump, emitConst(fn, jReady()), s.Body) - vTrue := emitConst(fn, NewConst(constant.MakeBool(true), tBool, nil)) - emitReturn(fn, []Value{vTrue}, nil) + storeVar(fn, fn.jump, jReady, s.Body) + // A yield function's own deferstack is always empty, so rundefers is not needed. + fn.emit(&Return{Results: []Value{vTrue}}, nil) // Emit header: // @@ -3205,26 +3334,25 @@ func (b *builder) buildYieldFunc(fn *Function) { invalid := fn.newBasicBlock("yield-invalid") jumpVal := emitLoad(fn, fn.lookup(fn.jump, true), nil) - emitIf(fn, emitCompare(fn, token.EQL, jumpVal, emitConst(fn, jReady()), nil), yloop, invalid, nil) + emitIf(fn, emitCompare(fn, token.EQL, jumpVal, jReady, nil), yloop, invalid, nil) fn.currentBlock = invalid fn.emit( &Panic{ - X: emitConv(fn, emitConst(fn, stringConst("yield function called after range loop exit", nil)), tEface, nil), + X: emitConv(fn, jLateYield, tEface, nil), }, nil, ) - addEdge(fn.currentBlock, fn.Exit) fn.currentBlock = yloop - storeVar(fn, fn.jump, emitConst(fn, jBusy()), s.Body) + storeVar(fn, fn.jump, jBusy, s.Body) // Initialize k and v from params. var tk, tv types.Type if s.Key != nil && !isBlankIdent(s.Key) { - tk = fn.Pkg.typeOf(s.Key) // fn.parent.typeOf is identical + tk = fn.typeOf(s.Key) // fn.parent.typeOf is identical } if s.Value != nil && !isBlankIdent(s.Value) { - tv = fn.Pkg.typeOf(s.Value) + tv = fn.typeOf(s.Value) } if s.Tok == token.DEFINE { if tk != nil { @@ -3257,12 +3385,13 @@ func (b *builder) buildYieldFunc(fn *Function) { // Build the body of the range loop. b.stmt(fn, s.Body) - if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb.Preds != nil) { + if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb == fn.Recover || cb.Preds != nil) { // Control fell off the end of the function's body block. // Block optimizations eliminate the current block, if // unreachable. emitJump(fn, ycont, nil) } + // pop the stack for the yield function fn.targets = fn.targets.tail // Clean up exits and promote any unresolved exits to fn.parent. @@ -3280,12 +3409,10 @@ func (b *builder) buildYieldFunc(fn *Function) { // jump = id // return false fn.currentBlock = lb._goto - id_ := intConst(e.id, e.source) - id_.comment = fmt.Sprintf("rangefunc.exit.%d", e.id) - id := emitConst(fn, id_) + id := intConst(e.id, e.source) storeVar(fn, fn.jump, id, e.source) - vFalse := emitConst(fn, NewConst(constant.MakeBool(false), tBool, e.source)) - emitReturn(fn, []Value{vFalse}, e.source) + vFalse := NewConst(constant.MakeBool(false), tBool, e.source) + fn.emit(&Return{Results: []Value{vFalse}}, e.source) } if e.to != fn { // e needs to be handled by the parent too. @@ -3296,38 +3423,54 @@ func (b *builder) buildYieldFunc(fn *Function) { fn.finishBody() } -// buildFuncDecl builds IR code for the function or method declared -// by decl in package pkg. -func (b *builder) buildFuncDecl(pkg *Package, decl *ast.FuncDecl) { - id := decl.Name - fn := pkg.values[pkg.info.Defs[id]].(*Function) - if decl.Recv == nil && id.Name == "init" { - var v Call - v.Call.Value = fn - v.setType(types.NewTuple()) - pkg.init.emit(&v, decl) - } - fn.source = decl - b.buildFunction(fn) +// addMakeInterfaceType records non-interface type t as the type of +// the operand a MakeInterface operation, for [Program.RuntimeTypes]. +// +// Acquires prog.makeInterfaceTypesMu. +func addMakeInterfaceType(prog *Program, t types.Type) { + prog.makeInterfaceTypesMu.Lock() + defer prog.makeInterfaceTypesMu.Unlock() + if prog.makeInterfaceTypes == nil { + prog.makeInterfaceTypes = make(map[types.Type]unit) + } + prog.makeInterfaceTypes[t] = unit{} } // Build calls Package.Build for each package in prog. +// Building occurs in parallel unless the BuildSerially mode flag was set. // // Build is intended for whole-program analysis; a typical compiler // need only build a single package. // // Build is idempotent and thread-safe. func (prog *Program) Build() { + var wg sync.WaitGroup for _, p := range prog.packages { - p.Build() + if prog.mode&BuildSerially != 0 { + p.Build() + } else { + wg.Add(1) + cpuLimit <- unit{} // acquire a token + go func(p *Package) { + p.Build() + wg.Done() + <-cpuLimit // release a token + }(p) + } } + wg.Wait() } +// cpuLimit is a counting semaphore to limit CPU parallelism. +var cpuLimit = make(chan unit, runtime.GOMAXPROCS(0)) + // Build builds IR code for all functions and vars in package p. // -// Precondition: CreatePackage must have been called for all of p's -// direct imports (and hence its direct imports must have been -// error-free). +// CreatePackage must have been called for all of p's direct imports +// (and hence its direct imports must have been error-free). It is not +// necessary to call CreatePackage for indirect dependencies. +// Functions will be created for all necessary methods in those +// packages on demand. // // Build is idempotent and thread-safe. func (p *Package) Build() { p.buildOnce.Do(p.build) } @@ -3336,49 +3479,58 @@ func (p *Package) build() { if p.info == nil { return // synthetic package, e.g. "testmain" } - - // Ensure we have runtime type info for all exported members. - // TODO(adonovan): ideally belongs in memberFromObject, but - // that would require package creation in topological order. - for name, mem := range p.Members { - if ast.IsExported(name) { - p.Prog.needMethodsOf(mem.Type()) - } - } if p.Prog.mode&LogSource != 0 { defer logStack("build %s", p)() } - init := p.init + + b := builder{fns: p.created} + b.iterate() + + // We no longer need transient information: ASTs or go/types deductions. + p.info = nil + p.created = nil + p.files = nil + p.initVersion = nil + + if p.Prog.mode&SanityCheckFunctions != 0 { + sanityCheckPackage(p) + } +} + +// buildPackageInit builds fn.Body for the synthetic package initializer. +func (b *builder) buildPackageInit(init *Function) { + p := init.Pkg init.startBody() - init.exitBlock() var done *BasicBlock - // Make init() skip if package is already initialized. - initguard := p.Var("init$guard") - doinit := init.newBasicBlock("init.start") - done = init.Exit - emitIf(init, emitLoad(init, initguard, nil), done, doinit, nil) - init.currentBlock = doinit - emitStore(init, initguard, emitConst(init, NewConst(constant.MakeBool(true), tBool, nil)), nil) + if p.Prog.mode&BareInits == 0 { + // Make init() skip if package is already initialized. + initguard := p.Var("init$guard") + doinit := init.newBasicBlock("init.start") + done = init.newBasicBlock("init.done") + emitIf(init, emitLoad(init, initguard, nil), done, doinit, nil) + init.currentBlock = doinit + emitStore(init, initguard, vTrue, nil) - // Call the init() function of each package we import. - for _, pkg := range p.Pkg.Imports() { - prereq := p.Prog.packages[pkg] - if prereq == nil { - panic(fmt.Sprintf("Package(%q).Build(): unsatisfied import: Program.CreatePackage(%q) was not called", p.Pkg.Path(), pkg.Path())) + // Call the init() function of each package we import. + for _, pkg := range p.Pkg.Imports() { + prereq := p.Prog.packages[pkg] + if prereq == nil { + panic(fmt.Sprintf("Package(%q).Build(): unsatisfied import: Program.CreatePackage(%q) was not called", p.Pkg.Path(), pkg.Path())) + } + var v Call + v.Call.Value = prereq.init + v.setType(types.NewTuple()) + init.emit(&v, nil) } - var v Call - v.Call.Value = prereq.init - v.setType(types.NewTuple()) - init.emit(&v, nil) } - b := builder{ - printFunc: p.printFunc, + // Initialize package-level vars in correct order. + if len(p.info.InitOrder) > 0 && len(p.files) == 0 { + panic("no source files provided for package. cannot initialize globals") } - // Initialize package-level vars in correct order. for _, varinit := range p.info.InitOrder { if init.Prog.mode&LogSource != 0 { fmt.Fprintf(os.Stderr, "build global initializer %v @ %s\n", @@ -3411,48 +3563,33 @@ func (p *Package) build() { } } } - init.goversion = "" // The rest of the init function is synthetic. No syntax => no goversion. - // Build all package-level functions, init functions - // and methods, including unreachable/blank ones. - // We build them in source order, but it's not significant. + // The rest of the init function is synthetic: + // no syntax, info, goversion. + init.info = nil + init.goversion = "" + + // Call all of the declared init() functions in source order. for _, file := range p.files { for _, decl := range file.Decls { if decl, ok := decl.(*ast.FuncDecl); ok { - b.buildFuncDecl(p, decl) + id := decl.Name + if !isBlankIdent(id) && id.Name == "init" && decl.Recv == nil { + declaredInit := p.values[p.info.Defs[id]].(*Function) + var v Call + v.Call.Value = declaredInit + v.setType(types.NewTuple()) + p.init.emit(&v, nil) + } } } } // Finish up init(). - emitJump(init, done, nil) - init.finishBody() - - // We no longer need ASTs or go/types deductions. - p.info = nil - p.initVersion = nil - - if p.Prog.mode&SanityCheckFunctions != 0 { - sanityCheckPackage(p) + if p.Prog.mode&BareInits == 0 { + emitJump(init, done, nil) + init.currentBlock = done } -} - -// Like ObjectOf, but panics instead of returning nil. -// Only valid during p's create and build phases. -func (p *Package) objectOf(id *ast.Ident) types.Object { - if o := p.info.ObjectOf(id); o != nil { - return o - } - panic(fmt.Sprintf("no types.Object for ast.Ident %s @ %s", - id.Name, p.Prog.Fset.Position(id.Pos()))) -} - -// Like TypeOf, but panics instead of returning nil. -// Only valid during p's create and build phases. -func (p *Package) typeOf(e ast.Expr) types.Type { - if T := p.info.TypeOf(e); T != nil { - return T - } - panic(fmt.Sprintf("no type for %T @ %s", - e, p.Prog.Fset.Position(e.Pos()))) + init.emit(new(Return), nil) + init.finishBody() } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/const.go b/tools/vendor/honnef.co/go/tools/go/ir/const.go index 7d27ec577..399eb7a1f 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/const.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/const.go @@ -7,24 +7,58 @@ package ir // This file defines the Const SSA value type. import ( + "bytes" "fmt" "go/ast" "go/constant" "go/types" "strconv" - "strings" "golang.org/x/exp/typeparams" - "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/xtools-internal/typesinternal" ) +// soleTypeKind returns a BasicInfo for which constant.Value can +// represent all zero values for the types in the type set. +// +// types.IsBoolean for false is a representative. +// types.IsInteger for 0 +// types.IsString for "" +// 0 otherwise. +func soleTypeKind(typ types.Type) types.BasicInfo { + // State records the set of possible zero values (false, 0, ""). + // Candidates (perhaps all) are eliminated during the type-set + // iteration, which executes at least once. + state := types.IsBoolean | types.IsInteger | types.IsString + underIs(typ, func(ut types.Type) bool { + var c types.BasicInfo + if t, ok := ut.(*types.Basic); ok { + c = t.Info() + } + if c&types.IsNumeric != 0 { // int/float/complex + c = types.IsInteger + } + state = state & c + return state != 0 + }) + return state +} + // NewConst returns a new constant of the specified value and type. // val must be valid according to the specification of Const.Value. func NewConst(val constant.Value, typ types.Type, source ast.Node) *Const { + if val == nil { + switch soleTypeKind(typ) { + case types.IsBoolean: + val = constant.MakeBool(false) + case types.IsInteger: + val = constant.MakeInt64(0) + case types.IsString: + val = constant.MakeString("") + } + } c := &Const{ - register: register{ - typ: typ, - }, + typ: typ, Value: val, } c.setSource(source) @@ -50,95 +84,25 @@ func stringConst(s string, source ast.Node) *Const { // zeroConst returns a new "zero" constant of the specified type. func zeroConst(t types.Type, source ast.Node) Constant { - if _, ok := t.Underlying().(*types.Interface); ok && !typeparams.IsTypeParam(t) { - // Handle non-generic interface early to simplify following code. - return nilConst(t, source) - } - - tset := typeutil.NewTypeSet(t) - - switch typ := tset.CoreType().(type) { - case *types.Struct: - values := make([]Value, typ.NumFields()) - for i := 0; i < typ.NumFields(); i++ { - values[i] = zeroConst(typ.Field(i).Type(), source) - } - ac := &AggregateConst{ - register: register{typ: t}, - Values: values, - } - ac.setSource(source) - return ac - case *types.Tuple: - values := make([]Value, typ.Len()) - for i := 0; i < typ.Len(); i++ { - values[i] = zeroConst(typ.At(i).Type(), source) - } - ac := &AggregateConst{ - register: register{typ: t}, - Values: values, - } - ac.setSource(source) - return ac - } - - isNillable := func(term *types.Term) bool { - switch typ := term.Type().Underlying().(type) { - case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature, *typeutil.Iterator: - return true - case *types.Basic: - switch typ.Kind() { - case types.UnsafePointer, types.UntypedNil: - return true - default: - return false - } - default: - return false - } - } - - isInfo := func(info types.BasicInfo) func(*types.Term) bool { - return func(term *types.Term) bool { - basic, ok := term.Type().Underlying().(*types.Basic) - if !ok { - return false - } - return (basic.Info() & info) != 0 - } - } - - isArray := func(term *types.Term) bool { - _, ok := term.Type().Underlying().(*types.Array) - return ok - } - - switch { - case tset.All(isInfo(types.IsNumeric)): - return NewConst(constant.MakeInt64(0), t, source) - case tset.All(isInfo(types.IsString)): - return NewConst(constant.MakeString(""), t, source) - case tset.All(isInfo(types.IsBoolean)): - return NewConst(constant.MakeBool(false), t, source) - case tset.All(isNillable): - return nilConst(t, source) - case tset.All(isArray): - var k ArrayConst - k.setType(t) - k.setSource(source) - return &k - default: - var k GenericConst - k.setType(t) - k.setSource(source) - return &k - } + return NewConst(nil, t, source) } func (c *Const) RelString(from *types.Package) string { var p string if c.Value == nil { - p = "nil" + switch c.typ.(type) { + case *types.Array, *types.Struct: + p = "{}" + case *types.Alias: + switch c.typ.Underlying().(type) { + case *types.Array, *types.Struct: + p = "{}" + default: + p, _ = typesinternal.ZeroString(types.Unalias(c.typ), types.RelativeTo(from)) + } + default: + p, _ = typesinternal.ZeroString(c.typ, types.RelativeTo(from)) + } } else if c.Value.Kind() == constant.String { v := constant.StringVal(c.Value) const max = 20 @@ -150,58 +114,83 @@ func (c *Const) RelString(from *types.Package) string { } else { p = c.Value.String() } - return fmt.Sprintf("Const <%s> {%s}", relType(c.Type(), from), p) + return p + ":" + relType(c.Type(), from) +} + +func (c *Const) Name() string { + return c.RelString(nil) } func (c *Const) String() string { - if c.block == nil { - // Constants don't have a block till late in the compilation process. But we want to print consts during - // debugging. - return c.RelString(nil) - } - return c.RelString(c.Parent().pkg()) + return c.RelString(nil) } -func (v *ArrayConst) RelString(pkg *types.Package) string { - return fmt.Sprintf("ArrayConst <%s>", relType(v.Type(), pkg)) +func (c *Const) Type() types.Type { + return c.typ } -func (v *ArrayConst) String() string { - return v.RelString(v.Parent().pkg()) +func (c *Const) Referrers() *[]Instruction { + return nil } +func (c *Const) Parent() *Function { return nil } + func (v *AggregateConst) RelString(pkg *types.Package) string { - values := make([]string, len(v.Values)) - for i, v := range v.Values { - if v != nil { - values[i] = v.Name() - } else { - values[i] = "nil" + var b bytes.Buffer + fmt.Fprint(&b, "const {") + for i, vv := range v.Values { + if i > 0 { + fmt.Fprint(&b, ", ") } + fmt.Fprint(&b, relName(vv, v)) } - return fmt.Sprintf("AggregateConst <%s> (%s)", relType(v.Type(), pkg), strings.Join(values, ", ")) + fmt.Fprint(&b, "}") + return b.String() +} + +func (v *AggregateConst) Name() string { + return v.RelString(nil) } func (v *AggregateConst) String() string { - if v.block == nil { - return v.RelString(nil) - } - return v.RelString(v.Parent().pkg()) + return v.RelString(nil) } -func (v *GenericConst) RelString(pkg *types.Package) string { - return fmt.Sprintf("GenericConst <%s>", relType(v.Type(), pkg)) +func (v *AggregateConst) Type() types.Type { + return v.typ } -func (v *GenericConst) String() string { - return v.RelString(v.Parent().pkg()) +func (v *AggregateConst) Referrers() *[]Instruction { + return nil } +func (v *AggregateConst) Parent() *Function { return nil } + // IsNil returns true if this constant represents a typed or untyped nil value. func (c *Const) IsNil() bool { - return c.Value == nil + return c.Value == nil && nillable(c.typ) +} + +// nillable reports whether *new(T) == nil is legal for type T. +func nillable(t types.Type) bool { + if typeparams.IsTypeParam(t) { + return underIs(t, func(u types.Type) bool { + // empty type set (u==nil) => any underlying types => not nillable + return u != nil && nillable(u) + }) + } + switch t.Underlying().(type) { + case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature: + return true + case *types.Interface: + return true // basic interface. + default: + return false + } } +// TODO(adonovan): move everything below into honnef.co/go/tools/go/ir/interp. + // Int64 returns the numeric value of this constant truncated to fit // a signed 64-bit integer. func (c *Const) Int64() int64 { @@ -237,61 +226,16 @@ func (c *Const) Uint64() uint64 { // Float64 returns the numeric value of this constant truncated to fit // a float64. func (c *Const) Float64() float64 { - f, _ := constant.Float64Val(c.Value) + x := constant.ToFloat(c.Value) // (c.Value == nil) => x.Kind() == Unknown + f, _ := constant.Float64Val(x) return f } // Complex128 returns the complex value of this constant truncated to // fit a complex128. func (c *Const) Complex128() complex128 { - re, _ := constant.Float64Val(constant.Real(c.Value)) - im, _ := constant.Float64Val(constant.Imag(c.Value)) + x := constant.ToComplex(c.Value) // (c.Value == nil) => x.Kind() == Unknown + re, _ := constant.Float64Val(constant.Real(x)) + im, _ := constant.Float64Val(constant.Imag(x)) return complex(re, im) } - -func (c *Const) equal(o Constant) bool { - // TODO(dh): don't use == for types, this will miss identical pointer types, among others - oc, ok := o.(*Const) - if !ok { - return false - } - return c.typ == oc.typ && c.Value == oc.Value && c.source == oc.source -} - -func (c *AggregateConst) equal(o Constant) bool { - oc, ok := o.(*AggregateConst) - if !ok { - return false - } - // TODO(dh): don't use == for types, this will miss identical pointer types, among others - if c.typ != oc.typ { - return false - } - if c.source != oc.source { - return false - } - for i, v := range c.Values { - if !v.(Constant).equal(oc.Values[i].(Constant)) { - return false - } - } - return true -} - -func (c *ArrayConst) equal(o Constant) bool { - oc, ok := o.(*ArrayConst) - if !ok { - return false - } - // TODO(dh): don't use == for types, this will miss identical pointer types, among others - return c.typ == oc.typ && c.source == oc.source -} - -func (c *GenericConst) equal(o Constant) bool { - oc, ok := o.(*GenericConst) - if !ok { - return false - } - // TODO(dh): don't use == for types, this will miss identical pointer types, among others - return c.typ == oc.typ && c.source == oc.source -} diff --git a/tools/vendor/honnef.co/go/tools/go/ir/create.go b/tools/vendor/honnef.co/go/tools/go/ir/create.go index 046ffb758..f5656e22e 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/create.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/create.go @@ -12,35 +12,35 @@ import ( "go/ast" "go/token" "go/types" - "go/version" "os" "sync" - "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/xtools-internal/versions" ) -// measured on the standard library and rounded up to powers of two, -// on average there are 8 blocks and 16 instructions per block in a -// function. -const avgBlocks = 8 -const avgInstructionsPerBlock = 16 - // NewProgram returns a new IR Program. // // mode controls diagnostics and checking during IR construction. +// +// To construct an SSA program: +// +// - Call NewProgram to create an empty Program. +// - Call CreatePackage providing typed syntax for each package +// you want to build, and call it with types but not +// syntax for each of those package's direct dependencies. +// - Call [Package.Build] on each syntax package you wish to build, +// or [Program.Build] to build all of them. +// +// See the Example tests for simple examples. func NewProgram(fset *token.FileSet, mode BuilderMode) *Program { - prog := &Program{ + return &Program{ Fset: fset, imported: make(map[string]*Package), packages: make(map[*types.Package]*Package), mode: mode, + canon: newCanonizer(), + ctxt: types.NewContext(), } - - h := typeutil.MakeHasher() // protected by methodsMu, in effect - prog.methodSets.SetHasher(h) - prog.canon.SetHasher(h) - - return prog } // memberFromObject populates package pkg with a member for the @@ -71,7 +71,7 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion Value: NewConst(obj.Val(), obj.Type(), syntax), pkg: pkg, } - pkg.values[obj] = c.Value + pkg.values[obj] = c if name != "_" { pkg.Members[name] = c } @@ -83,6 +83,7 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion object: obj, typ: types.NewPointer(obj.Type()), // address } + g.source = syntax pkg.values[obj] = g if name != "_" { pkg.Members[name] = g @@ -94,30 +95,9 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion pkg.ninit++ name = fmt.Sprintf("init#%d", pkg.ninit) } - fn := &Function{ - name: name, - object: obj, - Signature: sig, - Pkg: pkg, - Prog: pkg.Prog, - goversion: goversion, - } - - fn.source = syntax - fn.initHTML(pkg.printFunc) - if syntax == nil { - fn.Synthetic = SyntheticLoadedFromExportData - } else { - // Note: we initialize fn.Blocks in - // (*builder).buildFunction and not here because Blocks - // being nil is used to indicate that building of the - // function hasn't started yet. - - fn.functionBody = &functionBody{ - scratchInstructions: make([]Instruction, avgBlocks*avgInstructionsPerBlock), - } - } - + fn := createFunction(pkg.Prog, obj, name, syntax, pkg.info, goversion) + fn.Pkg = pkg + pkg.created = append(pkg.created, fn) pkg.values[obj] = fn pkg.Functions = append(pkg.Functions, fn) if name != "_" && sig.Recv() == nil { @@ -129,6 +109,37 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion } } +// createFunction creates a function or method. It supports both +// CreatePackage (with or without syntax) and the on-demand creation +// of methods in non-created packages based on their types.Func. +func createFunction(prog *Program, obj *types.Func, name string, syntax ast.Node, info *types.Info, goversion string) *Function { + sig := obj.Type().(*types.Signature) + + /* declared function/method (from syntax or export data) */ + fn := &Function{ + name: name, + object: obj, + Signature: sig, + build: (*builder).buildFromSyntax, + info: info, + goversion: goversion, + pos: obj.Pos(), + syntax: syntax, + Pkg: nil, // may be set by caller + Prog: prog, + recvtypeparams: sig.RecvTypeParams(), + typeparams: sig.TypeParams(), + } + if syntax == nil { + fn.Synthetic = "from type information" + fn.build = (*builder).buildParamsOnly + } + if fn.hasTypeParams() { + fn.generic = new(generic) + } + return fn +} + // membersFromDecl populates package pkg with members for each // typechecker object (var, func, const or type) associated with the // specified decl. @@ -162,20 +173,11 @@ func membersFromDecl(pkg *Package, decl ast.Decl, goversion string) { case *ast.FuncDecl: id := decl.Name - obj, ok := pkg.info.Defs[id] - if !ok { - panic(fmt.Sprintf("couldn't find object for id %q at %s", - id.Name, pkg.Prog.Fset.PositionFor(id.Pos(), false))) - } - if obj == nil { - panic(fmt.Sprintf("found nil object for id %q at %s", - id.Name, pkg.Prog.Fset.PositionFor(id.Pos(), false))) - } - memberFromObject(pkg, obj, decl, goversion) + memberFromObject(pkg, pkg.info.Defs[id], decl, goversion) } } -// CreatePackage constructs and returns an IR Package from the +// CreatePackage creates and returns an IR Package from the // specified type-checked, error-free file ASTs, and populates its // Members mapping. // @@ -183,40 +185,43 @@ func membersFromDecl(pkg *Package, decl ast.Decl, goversion string) { // subsequent call to ImportedPackage(pkg.Path()). // // The real work of building IR form for each function is not done -// until a subsequent call to Package.Build(). +// until a subsequent call to Package.Build. func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package { + if pkg == nil { + panic("nil pkg") // otherwise pkg.Scope below returns types.Universe! + } p := &Package{ Prog: prog, Members: make(map[string]Member), - values: make(map[types.Object]Value), + values: make(map[types.Object]Member), Pkg: pkg, - // transient values (CREATE and BUILD phases) + syntax: info != nil, + // transient values (cleared after Package.Build) info: info, files: files, - printFunc: prog.PrintFunc, initVersion: make(map[ast.Expr]string), } - // Add init() function. + /* synthesized package initializer */ p.init = &Function{ - name: "init", - Signature: new(types.Signature), - Synthetic: SyntheticPackageInitializer, - Pkg: p, - Prog: prog, - functionBody: new(functionBody), - goversion: "", // See Package.build for details. + name: "init", + Signature: new(types.Signature), + Synthetic: "package initializer", + Pkg: p, + Prog: prog, + build: (*builder).buildPackageInit, + info: p.info, + goversion: "", // See Package.build for details. } - p.init.initHTML(prog.PrintFunc) p.Members[p.init.name] = p.init p.Functions = append(p.Functions, p.init) + p.created = append(p.created, p.init) - // CREATE phase. // Allocate all package members: vars, funcs, consts and types. if len(files) > 0 { // Go source package. for _, file := range files { - goversion := version.Lang(p.info.FileVersions[file]) + goversion := versions.Lang(versions.FileVersion(p.info, file)) for _, decl := range file.Decls { membersFromDecl(p, decl, goversion) } @@ -230,6 +235,7 @@ func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info * obj := scope.Lookup(name) memberFromObject(p, obj, nil, "") if obj, ok := obj.(*types.TypeName); ok { + // No Unalias: aliases should not duplicate methods. if named, ok := obj.Type().(*types.Named); ok { for i, n := 0, named.NumMethods(); i < n; i++ { memberFromObject(p, named.Method(i), nil, "") @@ -239,13 +245,15 @@ func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info * } } - // Add initializer guard variable. - initguard := &Global{ - Pkg: p, - name: "init$guard", - typ: types.NewPointer(tBool), + if prog.mode&BareInits == 0 { + // Add initializer guard variable. + initguard := &Global{ + Pkg: p, + name: "init$guard", + typ: types.NewPointer(tBool), + } + p.Members[initguard.Name()] = initguard } - p.Members[initguard.Name()] = initguard if prog.mode&GlobalDebug != 0 { p.SetDebugMode(true) @@ -268,8 +276,8 @@ func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info * // printMu serializes printing of Packages/Functions to stdout. var printMu sync.Mutex -// AllPackages returns a new slice containing all packages in the -// program prog in unspecified order. +// AllPackages returns a new slice containing all packages created by +// prog.CreatePackage in unspecified order. func (prog *Program) AllPackages() []*Package { pkgs := make([]*Package, 0, len(prog.packages)) for _, pkg := range prog.packages { @@ -291,10 +299,21 @@ func (prog *Program) AllPackages() []*Package { // false---yet this function remains very convenient. // Clients should use (*Program).Package instead where possible. // IR doesn't really need a string-keyed map of packages. +// +// Furthermore, the graph of packages may contain multiple variants +// (e.g. "p" vs "p as compiled for q.test"), and each has a different +// view of its dependencies. func (prog *Program) ImportedPackage(path string) *Package { return prog.imported[path] } +// SetNoReturn sets the predicate used when building the ir.Program +// prog that reports whether a given function cannot return. +// This may be used to prune spurious control flow edges +// after (e.g.) log.Fatal, improving the precision of analyses. +// +// A typical implementation is the [ctrlflow.CFGs.NoReturn] method from +// [golang.org/x/tools/go/analysis/passes/ctrlflow]. func (prog *Program) SetNoReturn(fn func(*types.Func) bool) { prog.noReturn = fn } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/doc.go b/tools/vendor/honnef.co/go/tools/go/ir/doc.go index e15ef60f4..13e47498a 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/doc.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/doc.go @@ -4,34 +4,29 @@ // Package ir defines a representation of the elements of Go programs // (packages, types, functions, variables and constants) using a -// static single-information (SSI) form intermediate representation +// static single-assignment (SSA) form intermediate representation // (IR) for the bodies of functions. // -// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE. -// -// For an introduction to SSA form, upon which SSI builds, see -// https://en.wikipedia.org/wiki/Static_single_assignment_form. +// For an introduction to SSA form, see +// http://en.wikipedia.org/wiki/Static_single_assignment_form. // This page provides a broader reading list: -// https://www.dcs.gla.ac.uk/~jsinger/ssa.html. -// -// For an introduction to SSI form, see The static single information -// form by C. Scott Ananian. +// http://www.dcs.gla.ac.uk/~jsinger/ssa.html. // // The level of abstraction of the IR form is intentionally close to // the source language to facilitate construction of source analysis // tools. It is not intended for machine code generation. // // The simplest way to create the IR of a package is -// to load typed syntax trees using golang.org/x/tools/go/packages, then -// invoke the irutil.Packages helper function. See ExampleLoadPackages -// and ExampleWholeProgram for examples. -// The resulting ir.Program contains all the packages and their +// to load typed syntax trees using [golang.org/x/tools/go/packages], then +// invoke the [honnef.co/go/tools/go/ir/irutil.Packages] helper function. +// (See the package-level Examples named LoadPackages and LoadWholeProgram.) +// The resulting [ir.Program] contains all the packages and their // members, but IR code is not created for function bodies until a -// subsequent call to (*Package).Build or (*Program).Build. +// subsequent call to [Package.Build] or [Program.Build]. // // The builder initially builds a naive IR form in which all local // variables are addresses of stack locations with explicit loads and -// stores. Registerization of eligible locals and φ-node insertion +// stores. Registerisation of eligible locals and φ-node insertion // using dominance and dataflow are then performed as a second pass // called "lifting" to improve the accuracy and performance of // subsequent analyses; this pass can be skipped by setting the @@ -39,16 +34,16 @@ // // The primary interfaces of this package are: // -// - Member: a named member of a Go package. -// - Value: an expression that yields a value. -// - Instruction: a statement that consumes values and performs computation. -// - Node: a Value or Instruction (emphasizing its membership in the IR value graph) +// - [Member]: a named member of a Go package. +// - [Value]: an expression that yields a value. +// - [Instruction]: a statement that consumes values and performs computation. +// - [Node]: a [Value] or [Instruction] (emphasizing its membership in the IR value graph) // -// A computation that yields a result implements both the Value and -// Instruction interfaces. The following table shows for each +// A computation that yields a result implements both the [Value] and +// [Instruction] interfaces. The following table shows for each // concrete type which of these interfaces it implements. // -// Value? Instruction? Member? +// Value? Instruction? Member? // *Alloc ✔ ✔ // *BinOp ✔ ✔ // *BlankStore ✔ @@ -59,14 +54,14 @@ // *Const ✔ ✔ // *Convert ✔ ✔ // *DebugRef ✔ -// *Defer ✔ ✔ +// *Defer ✔ // *Extract ✔ ✔ // *Field ✔ ✔ // *FieldAddr ✔ ✔ // *FreeVar ✔ // *Function ✔ ✔ (func) // *Global ✔ ✔ (var) -// *Go ✔ ✔ +// *Go ✔ // *If ✔ // *Index ✔ ✔ // *IndexAddr ✔ ✔ @@ -78,7 +73,7 @@ // *MakeMap ✔ ✔ // *MakeSlice ✔ ✔ // *MapLookup ✔ ✔ -// *MapUpdate ✔ ✔ +// *MapUpdate ✔ // *MultiConvert ✔ ✔ // *NamedConst ✔ (const) // *Next ✔ ✔ @@ -90,27 +85,26 @@ // *Return ✔ // *RunDefers ✔ // *Select ✔ ✔ -// *Send ✔ ✔ -// *Sigma ✔ ✔ +// *Send ✔ // *Slice ✔ ✔ // *SliceToArrayPointer ✔ ✔ // *SliceToArray ✔ ✔ -// *Store ✔ ✔ +// *Store ✔ // *StringLookup ✔ ✔ // *Type ✔ (type) // *TypeAssert ✔ ✔ // *UnOp ✔ ✔ // *Unreachable ✔ // -// Other key types in this package include: Program, Package, Function -// and BasicBlock. +// Other key types in this package include: [Program], [Package], [Function] +// and [BasicBlock]. // // The program representation constructed by this package is fully // resolved internally, i.e. it does not rely on the names of Values, // Packages, Functions, Types or BasicBlocks for the correct // interpretation of the program. Only the identities of objects and // the topology of the IR and type graphs are semantically -// significant. (There is one exception: Ids, used to identify field +// significant. (There is one exception: [types.Id] values, which identify field // and method names, contain strings.) Avoidance of name-based // operations simplifies the implementation of subsequent passes and // can make them very efficient. Many objects are nonetheless named @@ -118,14 +112,12 @@ // either accurate or unambiguous. The public API exposes a number of // name-based maps for client convenience. // -// The ir/irutil package provides various utilities that depend only -// on the public API of this package. -// -// TODO(adonovan): Consider the exceptional control-flow implications -// of defer and recover(). +// The [honnef.co/go/tools/go/ir/irutil] package provides various +// helper functions, for example to simplify loading a Go program into +// SSA form. // // TODO(adonovan): write a how-to document for all the various cases // of trying to determine corresponding elements across the four // domains of source locations, ast.Nodes, types.Objects, -// ir.Values/Instructions. -package ir +// ssa.Values/Instructions. +package ir // import "honnef.co/go/tools/go/ir" diff --git a/tools/vendor/honnef.co/go/tools/go/ir/dom.go b/tools/vendor/honnef.co/go/tools/go/ir/dom.go index f63a4c407..d733c9502 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/dom.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/dom.go @@ -23,12 +23,14 @@ import ( "io" "math/big" "os" + "slices" "sort" ) // Idom returns the block that immediately dominates b: // its parent in the dominator tree, if any. -// The entry node (b.Index==0) does not have a parent. +// Neither the entry node (b.Index==0) nor recover node +// (b==b.Parent().Recover()) have a parent. func (b *BasicBlock) Idom() *BasicBlock { return b.dom.idom } // Dominees returns the list of blocks that b immediately dominates: @@ -40,20 +42,25 @@ func (b *BasicBlock) Dominates(c *BasicBlock) bool { return b.dom.pre <= c.dom.pre && c.dom.post <= b.dom.post } -type byDomPreorder []*BasicBlock - -func (a byDomPreorder) Len() int { return len(a) } -func (a byDomPreorder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byDomPreorder) Less(i, j int) bool { return a[i].dom.pre < a[j].dom.pre } - -// DomPreorder returns a new slice containing the blocks of f in -// dominator tree preorder. +// DomPreorder returns a new slice containing the blocks of f +// in a preorder traversal of the dominator tree. func (f *Function) DomPreorder() []*BasicBlock { - n := len(f.Blocks) - order := make(byDomPreorder, n) - copy(order, f.Blocks) - sort.Sort(order) - return order + slice := slices.Clone(f.Blocks) + sort.Slice(slice, func(i, j int) bool { + return slice[i].dom.pre < slice[j].dom.pre + }) + return slice +} + +// DomPostorder returns a new slice containing the blocks of f +// in a postorder traversal of the dominator tree. +// (This is not the same as a postdominance order.) +func (f *Function) DomPostorder() []*BasicBlock { + slice := slices.Clone(f.Blocks) + sort.Slice(slice, func(i, j int) bool { + return slice[i].dom.post < slice[j].dom.post + }) + return slice } // domInfo contains a BasicBlock's dominance information. @@ -63,6 +70,48 @@ type domInfo struct { pre, post int32 // pre- and post-order numbering within domtree } +// ltState holds the working state for Lengauer-Tarjan algorithm +// (during which domInfo.pre is repurposed for CFG DFS preorder number). +type ltState struct { + // Each slice is indexed by b.Index. + sdom []*BasicBlock // b's semidominator + parent []*BasicBlock // b's parent in DFS traversal of CFG + ancestor []*BasicBlock // b's ancestor with least sdom +} + +// dfs implements the depth-first search part of the LT algorithm. +func (lt *ltState) dfs(v *BasicBlock, i int32, preorder []*BasicBlock) int32 { + preorder[i] = v + v.dom.pre = i // For now: DFS preorder of spanning tree of CFG + i++ + lt.sdom[v.Index] = v + lt.link(nil, v) + for _, w := range v.Succs { + if lt.sdom[w.Index] == nil { + lt.parent[w.Index] = v + i = lt.dfs(w, i, preorder) + } + } + return i +} + +// eval implements the EVAL part of the LT algorithm. +func (lt *ltState) eval(v *BasicBlock) *BasicBlock { + // TODO(adonovan): opt: do path compression per simple LT. + u := v + for ; lt.ancestor[v.Index] != nil; v = lt.ancestor[v.Index] { + if lt.sdom[v.Index].dom.pre < lt.sdom[u.Index].dom.pre { + u = v + } + } + return u +} + +// link implements the LINK part of the LT algorithm. +func (lt *ltState) link(v, w *BasicBlock) { + lt.ancestor[w.Index] = v +} + // buildDomTree computes the dominator tree of f using the LT algorithm. // Precondition: all blocks are reachable (e.g. optimizeBlocks has been run). func buildDomTree(fn *Function) { @@ -74,199 +123,90 @@ func buildDomTree(fn *Function) { b.dom = domInfo{} } - idoms := make([]*BasicBlock, len(fn.Blocks)) - - order := make([]*BasicBlock, 0, len(fn.Blocks)) - seen := fn.blockset(0) - var dfs func(b *BasicBlock) - dfs = func(b *BasicBlock) { - if !seen.Add(b) { - return - } - for _, succ := range b.Succs { - dfs(succ) - } - if fn.fakeExits.Has(b) { - dfs(fn.Exit) - } - order = append(order, b) - b.post = len(order) - 1 + n := len(fn.Blocks) + // Allocate space for 5 contiguous [n]*BasicBlock arrays: + // sdom, parent, ancestor, preorder, buckets. + space := make([]*BasicBlock, 5*n) + lt := ltState{ + sdom: space[0:n], + parent: space[n : 2*n], + ancestor: space[2*n : 3*n], } - dfs(fn.Blocks[0]) - for i := 0; i < len(order)/2; i++ { - o := len(order) - i - 1 - order[i], order[o] = order[o], order[i] + // Step 1. Number vertices by depth-first preorder. + preorder := space[3*n : 4*n] + root := fn.Blocks[0] + prenum := lt.dfs(root, 0, preorder) + recover := fn.Recover + if recover != nil { + lt.dfs(recover, prenum, preorder) } - idoms[fn.Blocks[0].Index] = fn.Blocks[0] - changed := true - for changed { - changed = false - // iterate over all nodes in reverse postorder, except for the - // entry node - for _, b := range order[1:] { - var newIdom *BasicBlock - do := func(p *BasicBlock) { - if idoms[p.Index] == nil { - return - } - if newIdom == nil { - newIdom = p - } else { - finger1 := p - finger2 := newIdom - for finger1 != finger2 { - for finger1.post < finger2.post { - finger1 = idoms[finger1.Index] - } - for finger2.post < finger1.post { - finger2 = idoms[finger2.Index] - } - } - newIdom = finger1 - } - } - for _, p := range b.Preds { - do(p) - } - if b == fn.Exit { - for _, p := range fn.Blocks { - if fn.fakeExits.Has(p) { - do(p) - } - } - } + buckets := space[4*n : 5*n] + copy(buckets, preorder) - if idoms[b.Index] != newIdom { - idoms[b.Index] = newIdom - changed = true + // In reverse preorder... + for i := int32(n) - 1; i > 0; i-- { + w := preorder[i] + + // Step 3. Implicitly define the immediate dominator of each node. + for v := buckets[i]; v != w; v = buckets[v.dom.pre] { + u := lt.eval(v) + if lt.sdom[u.Index].dom.pre < i { + v.dom.idom = u + } else { + v.dom.idom = w } } - } - for i, b := range idoms { - fn.Blocks[i].dom.idom = b - if b == nil { - // malformed CFG - continue - } - if i == b.Index { - continue + // Step 2. Compute the semidominators of all nodes. + lt.sdom[w.Index] = lt.parent[w.Index] + for _, v := range w.Preds { + u := lt.eval(v) + if lt.sdom[u.Index].dom.pre < lt.sdom[w.Index].dom.pre { + lt.sdom[w.Index] = lt.sdom[u.Index] + } } - b.dom.children = append(b.dom.children, fn.Blocks[i]) - } - - numberDomTree(fn.Blocks[0], 0, 0) - - // printDomTreeDot(os.Stderr, fn) // debugging - // printDomTreeText(os.Stderr, root, 0) // debugging - - if fn.Prog.mode&SanityCheckFunctions != 0 { - sanityCheckDomTree(fn) - } -} -// buildPostDomTree is like buildDomTree, but builds the post-dominator tree instead. -func buildPostDomTree(fn *Function) { - // The step numbers refer to the original LT paper; the - // reordering is due to Georgiadis. - - // Clear any previous domInfo. - for _, b := range fn.Blocks { - b.pdom = domInfo{} - } - - idoms := make([]*BasicBlock, len(fn.Blocks)) + lt.link(lt.parent[w.Index], w) - order := make([]*BasicBlock, 0, len(fn.Blocks)) - seen := fn.blockset(0) - var dfs func(b *BasicBlock) - dfs = func(b *BasicBlock) { - if !seen.Add(b) { - return - } - for _, pred := range b.Preds { - dfs(pred) - } - if b == fn.Exit { - for _, p := range fn.Blocks { - if fn.fakeExits.Has(p) { - dfs(p) - } - } + if lt.parent[w.Index] == lt.sdom[w.Index] { + w.dom.idom = lt.parent[w.Index] + } else { + buckets[i] = buckets[lt.sdom[w.Index].dom.pre] + buckets[lt.sdom[w.Index].dom.pre] = w } - order = append(order, b) - b.post = len(order) - 1 } - dfs(fn.Exit) - for i := 0; i < len(order)/2; i++ { - o := len(order) - i - 1 - order[i], order[o] = order[o], order[i] + // The final 'Step 3' is now outside the loop. + for v := buckets[0]; v != root; v = buckets[v.dom.pre] { + v.dom.idom = root } - idoms[fn.Exit.Index] = fn.Exit - changed := true - for changed { - changed = false - // iterate over all nodes in reverse postorder, except for the - // exit node - for _, b := range order[1:] { - var newIdom *BasicBlock - do := func(p *BasicBlock) { - if idoms[p.Index] == nil { - return - } - if newIdom == nil { - newIdom = p - } else { - finger1 := p - finger2 := newIdom - for finger1 != finger2 { - for finger1.post < finger2.post { - finger1 = idoms[finger1.Index] - } - for finger2.post < finger1.post { - finger2 = idoms[finger2.Index] - } - } - newIdom = finger1 - } - } - for _, p := range b.Succs { - do(p) - } - if fn.fakeExits.Has(b) { - do(fn.Exit) - } - - if idoms[b.Index] != newIdom { - idoms[b.Index] = newIdom - changed = true + // Step 4. Explicitly define the immediate dominator of each + // node, in preorder. + for _, w := range preorder[1:] { + if w == root || w == recover { + w.dom.idom = nil + } else { + if w.dom.idom != lt.sdom[w.Index] { + w.dom.idom = w.dom.idom.dom.idom } + // Calculate Children relation as inverse of Idom. + w.dom.idom.dom.children = append(w.dom.idom.dom.children, w) } } - for i, b := range idoms { - fn.Blocks[i].pdom.idom = b - if b == nil { - // malformed CFG - continue - } - if i == b.Index { - continue - } - b.pdom.children = append(b.pdom.children, fn.Blocks[i]) + pre, post := numberDomTree(root, 0, 0) + if recover != nil { + numberDomTree(recover, pre, post) } - numberPostDomTree(fn.Exit, 0, 0) - - // printPostDomTreeDot(os.Stderr, fn) // debugging - // printPostDomTreeText(os.Stderr, fn.Exit, 0) // debugging + // printDomTreeDot(os.Stderr, f) // debugging + // printDomTreeText(os.Stderr, root, 0) // debugging - if fn.Prog.mode&SanityCheckFunctions != 0 { // XXX - sanityCheckDomTree(fn) // XXX + if fn.Prog.mode&SanityCheckFunctions != 0 { + sanityCheckDomTree(fn) } } @@ -284,20 +224,6 @@ func numberDomTree(v *BasicBlock, pre, post int32) (int32, int32) { return pre, post } -// numberPostDomTree sets the pre- and post-order numbers of a depth-first -// traversal of the post-dominator tree rooted at v. These are used to -// answer post-dominance queries in constant time. -func numberPostDomTree(v *BasicBlock, pre, post int32) (int32, int32) { - v.pdom.pre = pre - pre++ - for _, child := range v.pdom.children { - pre, post = numberPostDomTree(child, pre, post) - } - v.pdom.post = post - post++ - return pre, post -} - // Testing utilities ---------------------------------------- // sanityCheckDomTree checks the correctness of the dominator tree @@ -318,8 +244,8 @@ func sanityCheckDomTree(f *Function) { all.Set(one).Lsh(&all, uint(n)).Sub(&all, one) // Initialization. - for i := range f.Blocks { - if i == 0 { + for i, b := range f.Blocks { + if i == 0 || b == f.Recover { // A root is dominated only by itself. D[i].SetBit(&D[0], 0, 1) } else { @@ -333,7 +259,7 @@ func sanityCheckDomTree(f *Function) { for changed := true; changed; { changed = false for i, b := range f.Blocks { - if i == 0 { + if i == 0 || b == f.Recover { continue } // Compute intersection across predecessors. @@ -342,13 +268,6 @@ func sanityCheckDomTree(f *Function) { for _, pred := range b.Preds { x.And(&x, &D[pred.Index]) } - if b == f.Exit { - for _, p := range f.Blocks { - if f.fakeExits.Has(p) { - x.And(&x, &D[p.Index]) - } - } - } x.SetBit(&x, i, 1) // a block always dominates itself. if D[i].Cmp(&x) != 0 { D[i].Set(&x) @@ -358,10 +277,14 @@ func sanityCheckDomTree(f *Function) { } // Check the entire relation. O(n^2). + // The Recover block (if any) must be treated specially so we skip it. ok := true for i := range n { for j := range n { b, c := f.Blocks[i], f.Blocks[j] + if c == f.Recover { + continue + } actual := b.Dominates(c) expected := D[j].Bit(i) == 1 if actual != expected { @@ -382,12 +305,11 @@ func sanityCheckDomTree(f *Function) { if !ok { panic("sanityCheckDomTree failed for " + f.String()) } - } // Printing functions ---------------------------------------- -// printDomTree prints the dominator tree as text, using indentation. +// printDomTreeText prints the dominator tree as text, using indentation. // //lint:ignore U1000 used during debugging func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) { @@ -418,49 +340,6 @@ func printDomTreeDot(buf io.Writer, f *Function) { for _, pred := range b.Preds { fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.pre, v.pre) } - - if f.fakeExits.Has(b) { - fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0,color=red];\n", b.dom.pre, f.Exit.dom.pre) - } - } - fmt.Fprintln(buf, "}") -} - -// printDomTree prints the dominator tree as text, using indentation. -// -//lint:ignore U1000 used during debugging -func printPostDomTreeText(buf io.Writer, v *BasicBlock, indent int) { - fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v) - for _, child := range v.pdom.children { - printPostDomTreeText(buf, child, indent+1) - } -} - -// printDomTreeDot prints the dominator tree of f in AT&T GraphViz -// (.dot) format. -// -//lint:ignore U1000 used during debugging -func printPostDomTreeDot(buf io.Writer, f *Function) { - fmt.Fprintln(buf, "//", f) - fmt.Fprintln(buf, "digraph pdomtree {") - for _, b := range f.Blocks { - v := b.pdom - fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post) - // TODO(adonovan): improve appearance of edges - // belonging to both dominator tree and CFG. - - // Dominator tree edge. - if b != f.Exit { - fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.pdom.pre, v.pre) - } - // CFG edges. - for _, pred := range b.Preds { - fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.pdom.pre, v.pre) - } - - if f.fakeExits.Has(b) { - fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0,color=red];\n", b.dom.pre, f.Exit.dom.pre) - } } fmt.Fprintln(buf, "}") } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/emit.go b/tools/vendor/honnef.co/go/tools/go/ir/emit.go index 4eecf24d5..bd9a5bc36 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/emit.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/emit.go @@ -13,8 +13,6 @@ import ( "go/token" "go/types" - "honnef.co/go/tools/go/types/typeutil" - "golang.org/x/exp/typeparams" ) @@ -49,7 +47,7 @@ func emitNew(f *Function, typ types.Type, source ast.Node, comment string) *Allo // emits an Alloc instruction for it. // // (Use this function or emitNew for synthetic variables; -// for source-level variables, use emitLocalVar.) +// for source-level variables in the same function, use emitLocalVar.) func emitLocal(f *Function, t types.Type, source ast.Node, comment string) *Alloc { local := emitAlloc(f, t, source, comment) f.Locals = append(f.Locals, local) @@ -58,8 +56,9 @@ func emitLocal(f *Function, t types.Type, source ast.Node, comment string) *Allo // emitLocalVar creates a local var for v and emits an Alloc instruction for it. // Subsequent calls to f.lookup(v) return it. +// It applies the appropriate generic instantiation to the type. func emitLocalVar(f *Function, v *types.Var, source ast.Node) *Alloc { - alloc := emitLocal(f, v.Type(), source, v.Name()) + alloc := emitLocal(f, f.typ(v.Type()), source, v.Name()) f.vars[v] = alloc return alloc } @@ -92,7 +91,7 @@ func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) { f.emit(ref, nil) } -func makeDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) *DebugRef { +func makeDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) *debugRef { if !f.debugInfo() { return nil // debugging not enabled } @@ -100,18 +99,18 @@ func makeDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) *DebugRef { panic("nil") } var obj types.Object - e = unparen(e) + e = ast.Unparen(e) if id, ok := e.(*ast.Ident); ok { if isBlankIdent(id) { return nil } - obj = f.Pkg.objectOf(id) + obj = f.objectOf(id) switch obj.(type) { case *types.Nil, *types.Const, *types.Builtin: return nil } } - return &DebugRef{ + return &debugRef{ X: v, Expr: e, IsAddr: isAddr, @@ -200,7 +199,7 @@ func emitCompare(f *Function, op token.Token, x, y Value, source ast.Node) Value // isValuePreserving returns true if a conversion from ut_src to // ut_dst is value-preserving, i.e. just a change of type. -// Precondition: neither argument is a named type. +// Precondition: neither argument is a named or alias type. func isValuePreserving(ut_src, ut_dst types.Type) bool { // Identical underlying types? if types.IdenticalIgnoreTags(ut_dst, ut_src) { @@ -254,7 +253,7 @@ func emitConv(f *Function, val Value, t_dst types.Type, source ast.Node) Value { // Untyped nil constant? Return interface-typed nil constant. if ut_src == tUntypedNil { - return emitConst(f, zeroConst(t_dst, source)) + return zeroConst(t_dst, source) } // Convert (non-nil) "untyped" literals to their default type. @@ -262,20 +261,19 @@ func emitConv(f *Function, val Value, t_dst types.Type, source ast.Node) Value { val = emitConv(f, val, types.Default(ut_src), source) } - f.Pkg.Prog.needMethodsOf(val.Type()) + // Record the types of operands to MakeInterface, if + // non-parameterized, as they are the set of runtime types. + t := val.Type() + if !f.Prog.isParameterized(t) { + addMakeInterfaceType(f.Prog, t) + } + mi := &MakeInterface{X: val} mi.setType(t_dst) return f.emit(mi, source) } - // In the common case, the typesets of src and dst are singletons - // and we emit an appropriate conversion. But if either contains - // a type parameter, the conversion may represent a cross product, - // in which case which we emit a MultiConvert. - tset_dst := typeutil.NewTypeSet(ut_dst) - tset_src := typeutil.NewTypeSet(ut_src) - - // conversionCase describes an instruction pattern that may be emitted to + // conversionCase describes an instruction pattern that maybe emitted to // model d <- s for d in dst_terms and s in src_terms. // Multiple conversions can match the same pattern. type conversionCase uint8 @@ -287,7 +285,8 @@ func emitConv(f *Function, val Value, t_dst types.Type, source ast.Node) Value { sliceTo0ArrayPtr convert ) - + // classify the conversion case of a source type us to a destination type ud. + // us and ud are underlying types (not *Named or *Alias) classify := func(s, d types.Type) conversionCase { // Just a change of type, but not value or representation? if isValuePreserving(s, d) { @@ -331,13 +330,14 @@ func emitConv(f *Function, val Value, t_dst types.Type, source ast.Node) Value { } var classifications conversionCase - for _, s := range tset_src.Terms { - us := s.Type().Underlying() - for _, d := range tset_dst.Terms { - ud := d.Type().Underlying() - classifications |= classify(us, ud) - } - } + underIs(ut_src, func(us types.Type) bool { + return underIs(ut_dst, func(ud types.Type) bool { + if us != nil && ud != nil { + classifications |= classify(us, ud) + } + return classifications != 0 + }) + }) if classifications == 0 { panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), t_dst)) } @@ -351,12 +351,12 @@ func emitConv(f *Function, val Value, t_dst types.Type, source ast.Node) Value { // constant of the destination type and // (initially) the same abstract value. // We don't truncate the value yet. - return emitConst(f, NewConst(c.Value, t_dst, source)) + return NewConst(c.Value, t_dst, source) } // Can we always convert from zero value without panicking? const mayPanic = sliceToArray | sliceToArrayPtr if c.Value == nil && classifications&mayPanic == 0 { - return emitConst(f, NewConst(nil, t_dst, source)) + return NewConst(nil, t_dst, source) } // We're converting from constant to non-constant type, @@ -380,26 +380,54 @@ func emitConv(f *Function, val Value, t_dst types.Type, source ast.Node) Value { return f.emit(p, source) case sliceTo0Array: // slice to zero-length arrays (constant) - return emitConst(f, zeroConst(t_dst, source)) + return zeroConst(t_dst, source) case convert: // representation-changing conversion c := &Convert{X: val} c.setType(t_dst) return f.emit(c, source) - default: // multiple conversion - c := &MultiConvert{X: val, from: tset_src, to: tset_dst} + default: // The conversion represents a cross product. + c := &MultiConvert{X: val, from: t_src, to: t_dst} c.setType(t_dst) return f.emit(c, source) } } +// emitTypeCoercion emits to f code to coerce the type of a +// Value v to exactly type typ, and returns the coerced value. +// +// Requires that coercing v.Typ() to typ is a value preserving change. +// +// Currently used only when v.Type() is a type instance of typ or vice versa. +// A type v is a type instance of a type t if there exists a +// type parameter substitution σ s.t. σ(v) == t. Example: +// +// σ(func(T) T) == func(int) int for σ == [T ↦ int] +// +// This happens in instantiation wrappers for conversion +// from an instantiation to a parameterized type (and vice versa) +// with σ substituting f.typeparams by f.typeargs. +func emitTypeCoercion(f *Function, v Value, typ types.Type, source ast.Node) Value { + if types.Identical(v.Type(), typ) { + return v // no coercion needed + } + // TODO(taking): for instances should we record which side is the instance? + c := &ChangeType{ + X: v, + } + c.setType(typ) + f.emit(c, source) + return c +} + // emitStore emits to f an instruction to store value val at location // addr, applying implicit conversions as required by assignability rules. func emitStore(f *Function, addr, val Value, source ast.Node) *Store { + typ := deref(addr.Type()) s := &Store{ Addr: addr, - Val: emitConv(f, val, deref(addr.Type()), source), + Val: emitConv(f, val, typ, source), } f.emit(s, source) return s @@ -473,7 +501,7 @@ func emitTailCall(f *Function, call *Call, source ast.Node) { } else { call.typ = tresults } - tuple := f.emit(call, source) + tuple := emitCall(f, call, source) var ret Return switch nr { case 0: @@ -491,13 +519,12 @@ func emitTailCall(f *Function, call *Call, source ast.Node) { } } - f.Exit = f.newBasicBlock("exit") - emitJump(f, f.Exit, source) - f.currentBlock = f.Exit f.emit(&ret, source) f.currentBlock = nil } +// emitCall emits a call instruction. If the callee is "no return", +// it also emits a panic to eliminate infeasible CFG edges. func emitCall(fn *Function, call *Call, source ast.Node) Value { res := fn.emit(call, source) @@ -508,9 +535,10 @@ func emitCall(fn *Function, call *Call, source ast.Node) Value { fn.Prog.noReturn(callee.object) { // Call doesn't return normally. Either it doesn't return at all // (infinitely blocked or exitting the process), or it unwinds the stack - // (panic, runtime.Goexit). In case it unwinds, jump to the exit block. - fn.emit(new(Jump), source) - addEdge(fn.currentBlock, fn.Exit) + // (panic, runtime.Goexit). Model this as a panic. + fn.emit(&Panic{ + X: emitConv(fn, vNoReturn, tEface, source), + }, source) fn.currentBlock = fn.newBasicBlock("unreachable") } @@ -529,9 +557,8 @@ func emitImplicitSelections(f *Function, v Value, indices []int, source ast.Node // We may have a generic type containing a pointer, or a pointer to a generic type containing a struct. A // pointer to a generic containing a pointer to a struct shouldn't be possible because the outer pointer gets // dereferenced implicitly before we get here. - fld := typeutil.CoreType(deref(v.Type())).Underlying().(*types.Struct).Field(index) - - if isPointer(v.Type()) { + if isPointerCore(v.Type()) { + fld := fieldOf(deref(v.Type()), index) instr := &FieldAddr{ X: v, Field: index, @@ -539,10 +566,11 @@ func emitImplicitSelections(f *Function, v Value, indices []int, source ast.Node instr.setType(types.NewPointer(fld.Type())) v = f.emit(instr, source) // Load the field's value iff indirectly embedded. - if isPointer(fld.Type()) { + if isPointerCore(fld.Type()) { v = emitLoad(f, v, source) } } else { + fld := fieldOf(v.Type(), index) instr := &Field{ X: v, Field: index, @@ -561,12 +589,8 @@ func emitImplicitSelections(f *Function, v Value, indices []int, source ast.Node // field's value. // Ident id is used for position and debug info. func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value { - // We may have a generic type containing a pointer, or a pointer to a generic type containing a struct. A - // pointer to a generic containing a pointer to a struct shouldn't be possible because the outer pointer gets - // dereferenced implicitly before we get here. - vut := typeutil.CoreType(deref(v.Type())).Underlying().(*types.Struct) - fld := vut.Field(index) - if isPointer(v.Type()) { + if isPointerCore(v.Type()) { + fld := fieldOf(deref(v.Type()), index) instr := &FieldAddr{ X: v, Field: index, @@ -579,6 +603,7 @@ func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast. v = emitLoad(f, v, id) } } else { + fld := fieldOf(v.Type(), index) instr := &Field{ X: v, Field: index, @@ -591,68 +616,30 @@ func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast. return v } -// zeroValue emits to f code to produce a zero value of type t, -// and returns it. -func zeroValue(f *Function, t types.Type, source ast.Node) Value { - return emitConst(f, zeroConst(t, source)) -} - -type constKey struct { - typ types.Type - value constant.Value - source ast.Node -} - -func emitConst(f *Function, c Constant) Constant { - if f.consts == nil { - f.consts = map[constKey]constValue{} +// createRecoverBlock emits to f a block of code to return after a +// recovered panic, and sets f.Recover to it. +// +// If f's result parameters are named, the code loads and returns +// their current values, otherwise it returns the zero values of their +// type. +// +// Idempotent. +func createRecoverBlock(f *Function) { + if f.Recover != nil { + return // already created } + saved := f.currentBlock - typ := c.Type() - var val constant.Value - switch c := c.(type) { - case *Const: - val = c.Value - case *ArrayConst, *GenericConst: - // These can only represent zero values, so all we need is the type - case *AggregateConst: - candidates, _ := f.aggregateConsts.At(c.typ) - for _, candidate := range candidates { - if c.equal(candidate) { - return candidate - } - } + f.Recover = f.newBasicBlock("recover") + f.currentBlock = f.Recover - for i := range c.Values { - c.Values[i] = emitConst(f, c.Values[i].(Constant)) - } + var results []Value + // Reload NRPs to form value tuple. + for _, nr := range f.results { + results = append(results, emitLoad(f, nr, nr.source)) + } - c.setBlock(f.Blocks[0]) - rands := c.Operands(nil) - updateOperandsReferrers(c, rands) - candidates = append(candidates, c) - f.aggregateConsts.Set(c.typ, candidates) - return c + f.emit(&Return{Results: results}, nil) - default: - panic(fmt.Sprintf("unexpected type %T", c)) - } - k := constKey{ - typ: typ, - value: val, - source: c.Source(), - } - dup, ok := f.consts[k] - if ok { - return dup.c - } else { - c.setBlock(f.Blocks[0]) - f.consts[k] = constValue{ - c: c, - idx: len(f.consts), - } - rands := c.Operands(nil) - updateOperandsReferrers(c, rands) - return c - } + f.currentBlock = saved } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/func.go b/tools/vendor/honnef.co/go/tools/go/ir/func.go index 923b04966..5b0c338eb 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/func.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/func.go @@ -4,163 +4,99 @@ package ir -// This file implements the Function and BasicBlock types. +// This file implements the Function type. import ( "bytes" "fmt" "go/ast" - "go/format" "go/token" "go/types" "io" + "iter" "os" - "sort" "strings" - - "honnef.co/go/tools/go/types/typeutil" ) -// addEdge adds a control-flow graph edge from from to to. -func addEdge(from, to *BasicBlock) { - from.Succs = append(from.Succs, to) - to.Preds = append(to.Preds, from) -} - -// Control returns the last instruction in the block. -func (b *BasicBlock) Control() Instruction { - if len(b.Instrs) == 0 { - return nil +// Like ObjectOf, but panics instead of returning nil. +// Only valid during f's create and build phases. +func (f *Function) objectOf(id *ast.Ident) types.Object { + if o := f.info.ObjectOf(id); o != nil { + return o } - return b.Instrs[len(b.Instrs)-1] -} - -// SigmaFor returns the sigma node for v coming from pred. -func (b *BasicBlock) SigmaFor(v Value, pred *BasicBlock) *Sigma { - for _, instr := range b.Instrs { - sigma, ok := instr.(*Sigma) - if !ok { - // no more sigmas - return nil - } - if sigma.From == pred && sigma.X == v { - return sigma - } - } - return nil -} - -// Parent returns the function that contains block b. -func (b *BasicBlock) Parent() *Function { return b.parent } - -// String returns a human-readable label of this block. -// It is not guaranteed unique within the function. -func (b *BasicBlock) String() string { - return fmt.Sprintf("%d", b.Index) -} - -// emit appends an instruction to the current basic block. -// If the instruction defines a Value, it is returned. -func (b *BasicBlock) emit(i Instruction, source ast.Node) Value { - i.setSource(source) - i.setBlock(b) - b.Instrs = append(b.Instrs, i) - v, _ := i.(Value) - return v + panic(fmt.Sprintf("no types.Object for ast.Ident %s @ %s", + id.Name, f.Prog.Fset.Position(id.Pos()))) } -// predIndex returns the i such that b.Preds[i] == c or panics if -// there is none. -func (b *BasicBlock) predIndex(c *BasicBlock) int { - for i, pred := range b.Preds { - if pred == c { - return i +// instrs returns an iterator that returns each reachable instruction of the SSA function. +func (f *Function) instrs() iter.Seq[Instruction] { + return func(yield func(i Instruction) bool) { + for _, block := range f.Blocks { + for _, instr := range block.Instrs { + if !yield(instr) { + return + } + } } } - panic(fmt.Sprintf("no edge %s -> %s", c, b)) } -// succIndex returns the i such that b.Succs[i] == c or -1 if there is none. -func (b *BasicBlock) succIndex(c *BasicBlock) int { - for i, succ := range b.Succs { - if succ == c { - return i - } +// Like TypeOf, but panics instead of returning nil. +// Only valid during f's create and build phases. +func (f *Function) typeOf(e ast.Expr) types.Type { + if T := f.info.TypeOf(e); T != nil { + return f.typ(T) } - return -1 + panic(fmt.Sprintf("no type for %T @ %s", e, f.Prog.Fset.Position(e.Pos()))) } -// hasPhi returns true if b.Instrs contains φ-nodes. -func (b *BasicBlock) hasPhi() bool { - _, ok := b.Instrs[0].(*Phi) - return ok +// typ is the locally instantiated type of T. +// If f is not an instantiation, then f.typ(T)==T. +func (f *Function) typ(T types.Type) types.Type { + return f.subst.typ(T) } -func (b *BasicBlock) Phis() []Instruction { - return b.phis() -} - -// phis returns the prefix of b.Instrs containing all the block's φ-nodes. -func (b *BasicBlock) phis() []Instruction { - for i, instr := range b.Instrs { - if _, ok := instr.(*Phi); !ok { - return b.Instrs[:i] - } +// If id is an Instance, returns info.Instances[id].Type. +// Otherwise returns f.typeOf(id). +func (f *Function) instanceType(id *ast.Ident) types.Type { + if t, ok := f.info.Instances[id]; ok { + return t.Type } - return nil // unreachable in well-formed blocks + return f.typeOf(id) } -// replacePred replaces all occurrences of p in b's predecessor list with q. -// Ordinarily there should be at most one. -func (b *BasicBlock) replacePred(p, q *BasicBlock) { - for i, pred := range b.Preds { - if pred == p { - b.Preds[i] = q - } - } -} - -// replaceSucc replaces all occurrences of p in b's successor list with q. -// Ordinarily there should be at most one. -func (b *BasicBlock) replaceSucc(p, q *BasicBlock) { - for i, succ := range b.Succs { - if succ == p { - b.Succs[i] = q - } +// selection returns a *selection corresponding to f.info.Selections[selector] +// with potential updates for type substitution. +func (f *Function) selection(selector *ast.SelectorExpr) *selection { + sel := f.info.Selections[selector] + if sel == nil { + return nil } -} -// removePred removes all occurrences of p in b's -// predecessor list and φ-nodes. -// Ordinarily there should be at most one. -func (b *BasicBlock) removePred(p *BasicBlock) { - phis := b.phis() + switch sel.Kind() { + case types.MethodExpr, types.MethodVal: + if recv := f.typ(sel.Recv()); recv != sel.Recv() { + // recv changed during type substitution. + pkg := f.declaredPackage().Pkg + obj, index, indirect := types.LookupFieldOrMethod(recv, true, pkg, sel.Obj().Name()) - // We must preserve edge order for φ-nodes. - j := 0 - for i, pred := range b.Preds { - if pred != p { - b.Preds[j] = b.Preds[i] - // Strike out φ-edge too. - for _, instr := range phis { - phi := instr.(*Phi) - phi.Edges[j] = phi.Edges[i] + // sig replaces sel.Type(). See (types.Selection).Typ() for details. + sig := obj.Type().(*types.Signature) + sig = changeRecv(sig, newVar(sig.Recv().Name(), recv)) + if sel.Kind() == types.MethodExpr { + sig = recvAsFirstArg(sig) + } + return &selection{ + kind: sel.Kind(), + recv: recv, + typ: sig, + obj: obj, + index: index, + indirect: indirect, } - j++ - } - } - // Nil out b.Preds[j:] and φ-edges[j:] to aid GC. - for i := j; i < len(b.Preds); i++ { - b.Preds[i] = nil - for _, instr := range phis { - instr.(*Phi).Edges[i] = nil } } - b.Preds = b.Preds[:j] - for _, instr := range phis { - phi := instr.(*Phi) - phi.Edges = phi.Edges[:j] - } + return toSelection(sel) } // Destinations associated with unlabelled for/switch/select stmts. @@ -190,7 +126,7 @@ type lblock struct { // // label should be a non-blank identifier (label.Name != "_"). func (f *Function) label(label *ast.Ident) *types.Label { - return f.Pkg.objectOf(label).(*types.Label) + return f.objectOf(label).(*types.Label) } // lblockOf returns the branch target associated with the @@ -274,24 +210,16 @@ func (f *Function) addResultVar(v *types.Var, source ast.Node) { f.returnVars = append(f.returnVars, v) } +// addParamVar adds a parameter to f.Params. func (f *Function) addParamVar(v *types.Var, source ast.Node) *Parameter { name := v.Name() if name == "" { name = fmt.Sprintf("arg%d", len(f.Params)) } - var b *BasicBlock - if len(f.Blocks) > 0 { - b = f.Blocks[0] - } - param := &Parameter{name: name} - param.setBlock(b) - param.setType(v.Type()) + param := &Parameter{name: name, typ: f.typ(v.Type()), parent: f} param.setSource(source) param.object = v f.Params = append(f.Params, param) - if b != nil { - f.Blocks[0].Instrs = append(f.Blocks[0].Instrs, param) - } return param } @@ -302,7 +230,6 @@ func (f *Function) addSpilledParam(obj *types.Var, source ast.Node) { param := f.addParamVar(obj, source) spill := emitLocalVar(f, obj, source) emitStore(f, spill, param, source) - // f.emit(&Store{Addr: spill, Val: param}) } // startBody initializes the function prior to generating IR code for its body. @@ -328,30 +255,12 @@ func (f *Function) blockset(i int) *BlockSet { return bs } -func (f *Function) exitBlock() { - old := f.currentBlock - - f.Exit = f.newBasicBlock("exit") - f.currentBlock = f.Exit - - results := make([]Value, len(f.results)) - // Run function calls deferred in this - // function when explicitly returning from it. - f.emit(new(RunDefers), nil) - for i, r := range f.results { - results[i] = emitLoad(f, r, nil) - } - - f.emit(&Return{Results: results}, nil) - f.currentBlock = old -} - // createSyntacticParams populates f.Params and generates code (spills // and named result locals) for all the parameters declared in the // syntax. In addition it populates the f.objects mapping. // // Preconditions: -// f.startBody() was called. +// f.startBody() was called. f.info != nil. // Postcondition: // len(f.Params) == len(f.Signature.Params) + (f.Signature.Recv() ? 1 : 0) func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.FuncType) { @@ -459,120 +368,18 @@ func buildReferrers(f *Function) { updateOperandsReferrers(instr, rands) } } - - for _, c := range f.consts { - rands = c.c.Operands(rands[:0]) - updateOperandsReferrers(c.c, rands) - } -} - -func (f *Function) emitConsts() { - defer func() { - f.consts = nil - f.aggregateConsts = typeutil.Map[[]*AggregateConst]{} - }() - - if len(f.Blocks) == 0 { - return - } - - // TODO(dh): our deduplication only works on booleans and - // integers. other constants are represented as pointers to - // things. - head := make([]constValue, 0, len(f.consts)) - for _, c := range f.consts { - if len(*c.c.Referrers()) == 0 { - // TODO(dh): killing a const may make other consts dead, too - killInstruction(c.c) - } else { - head = append(head, c) - } - } - sort.Slice(head, func(i, j int) bool { - return head[i].idx < head[j].idx - }) - entry := f.Blocks[0] - instrs := make([]Instruction, 0, len(entry.Instrs)+len(head)) - for _, c := range head { - instrs = append(instrs, c.c) - } - f.aggregateConsts.Iterate(func(key types.Type, value []*AggregateConst) { - for _, c := range value { - instrs = append(instrs, c) - } - }) - - instrs = append(instrs, entry.Instrs...) - entry.Instrs = instrs -} - -// buildFakeExits ensures that every block in the function is -// reachable in reverse from the Exit block. This is required to build -// a full post-dominator tree, and to ensure the exit block's -// inclusion in the dominator tree. -func buildFakeExits(fn *Function) { - // Find back-edges via forward DFS - fn.fakeExits = BlockSet{values: make([]bool, len(fn.Blocks))} - seen := fn.blockset(0) - backEdges := fn.blockset(1) - - var dfs func(b *BasicBlock) - dfs = func(b *BasicBlock) { - if !seen.Add(b) { - backEdges.Add(b) - return - } - for _, pred := range b.Succs { - dfs(pred) - } - } - dfs(fn.Blocks[0]) -buildLoop: - for { - seen := fn.blockset(2) - var dfs func(b *BasicBlock) - dfs = func(b *BasicBlock) { - if !seen.Add(b) { - return - } - for _, pred := range b.Preds { - dfs(pred) - } - if b == fn.Exit { - for _, b := range fn.Blocks { - if fn.fakeExits.Has(b) { - dfs(b) - } - } - } - } - dfs(fn.Exit) - - for _, b := range fn.Blocks { - if !seen.Has(b) && backEdges.Has(b) { - // Block b is not reachable from the exit block. Add a - // fake jump from b to exit, then try again. Note that we - // only add one fake edge at a time, as it may make - // multiple blocks reachable. - // - // We only consider those blocks that have back edges. - // Any unreachable block that doesn't have a back edge - // must flow into a loop, which by definition has a - // back edge. Thus, by looking for loops, we should - // need fewer fake edges overall. - fn.fakeExits.Add(b) - continue buildLoop - } - } - - break - } } -// finishBody() finalizes the function after IR code generation of its body. +// finishBody() finalizes the contents of the function after IR code generation of its body. +// +// The function is not done being built until done() is called. func (f *Function) finishBody() { f.currentBlock = nil f.lblocks = nil + f.returnVars = nil + f.jump = nil + f.source = nil + f.exits = nil // Remove from f.Locals any Allocs that escape to the heap. j := 0 @@ -589,10 +396,9 @@ func (f *Function) finishBody() { f.Locals = f.Locals[:j] optimizeBlocks(f) - buildFakeExits(f) buildReferrers(f) buildDomTree(f) - buildPostDomTree(f) + buildSCCs(f) if f.Prog.mode&NaiveForm == 0 { for lift(f) { @@ -603,39 +409,82 @@ func (f *Function) finishBody() { } } - // emit constants after lifting, because lifting may produce new constants, but before other variable splitting, - // because it expects constants to have been deduplicated. - f.emitConsts() - - if f.Prog.mode&SplitAfterNewInformation != 0 { - splitOnNewInformation(f.Blocks[0], &StackMap{}) + if f.debugInfo() { + // Turn debug references into a map + f.exprToValue = map[ast.Expr]struct { + v Value + isAddr bool + }{} + for _, b := range f.Blocks { + newInstrs := b.Instrs[:0] + for _, instr := range b.Instrs { + if instr, ok := instr.(*debugRef); ok { + if refs := instr.X.Referrers(); refs != nil { + *refs = removeInstr(*refs, instr) + } + if _, ok := f.exprToValue[instr.Expr]; !ok { + f.exprToValue[instr.Expr] = struct { + v Value + isAddr bool + }{instr.X, instr.IsAddr} + } + continue + } + newInstrs = append(newInstrs, instr) + } + b.Instrs = newInstrs + } } // clear remaining builder state f.results = nil // (used by lifting) f.deferstack = nil // (used by lifting) f.vars = nil // (used by lifting) - f.goversion = "" numberNodes(f) - defer f.wr.Close() - f.wr.WriteFunc("start", "start", f) + // clear out other function state (keep consistent with buildParamsOnly) + f.subst = nil - if f.Prog.mode&PrintFunctions != 0 { - printMu.Lock() - f.WriteTo(os.Stdout) - printMu.Unlock() - } + numberNodes(f) +} + +// done marks the building of f's SSA body complete, +// along with any nested functions, and optionally prints them. +func (f *Function) done() { + assert(f.parent == nil, "done called on an anonymous function") - if f.Prog.mode&SanityCheckFunctions != 0 { - mustSanityCheck(f, nil) + var visit func(*Function) + visit = func(f *Function) { + for _, anon := range f.AnonFuncs { + visit(anon) // anon is done building before f. + } + + f.uniq = 0 // done with uniq + f.build = nil // function is built + + if f.Prog.mode&PrintFunctions != 0 { + printMu.Lock() + f.WriteTo(os.Stdout) + printMu.Unlock() + } + + if f.Prog.mode&SanityCheckFunctions != 0 { + mustSanityCheck(f, nil) + } } + visit(f) } func isUselessPhi(phi *Phi) (Value, bool) { var v0 Value + if len(phi.Edges) == 0 { + panic("φ with no edges") + } for _, e := range phi.Edges { + if e == nil { + panic(fmt.Sprintf("φ has nil edge: %v", phi)) + } if e == phi { continue } @@ -653,13 +502,12 @@ func isUselessPhi(phi *Phi) (Value, bool) { return nil, false } } + if v0 == nil { + panic("trying to replace φ with nil") + } return v0, true } -func (f *Function) RemoveNilBlocks() { - f.removeNilBlocks() -} - // removeNilBlocks eliminates nils from f.Blocks and updates each // BasicBlock.Index. Use this after any pass that may delete blocks. func (f *Function) removeNilBlocks() { @@ -683,13 +531,14 @@ func (f *Function) removeNilBlocks() { // size of the instruction stream, and causes Functions to depend upon // the ASTs, potentially keeping them live in memory for longer. func (pkg *Package) SetDebugMode(debug bool) { - // TODO(adonovan): do we want ast.File granularity? pkg.debug = debug } // debugInfo reports whether debug info is wanted for this function. func (f *Function) debugInfo() bool { - return f.Pkg != nil && f.Pkg.debug + // debug info for instantiations follows the debug info of their origin. + p := f.declaredPackage() + return p != nil && p.debug } // lookup returns the address of the named variable identified by obj @@ -782,7 +631,7 @@ func (f *Function) RelString(from *types.Package) string { // Thunk? if f.method != nil { - return f.relMethod(from, f.method.Recv()) + return f.relMethod(from, f.method.recv) } // Bound? @@ -792,7 +641,7 @@ func (f *Function) RelString(from *types.Package) string { // Package-level function? // Prefix with package name for cross-package references only. - if p := f.pkg(); p != nil && p != from { + if p := f.relPkg(); p != nil && p != from { return fmt.Sprintf("%s.%s", p.Path(), f.name) } @@ -820,9 +669,25 @@ func writeSignature(buf *bytes.Buffer, from *types.Package, name string, sig *ty types.WriteSignature(buf, sig, types.RelativeTo(from)) } -func (f *Function) pkg() *types.Package { - if f.Pkg != nil { - return f.Pkg.Pkg +// declaredPackage returns the package fn is declared in or nil if the +// function is not declared in a package. +func (fn *Function) declaredPackage() *Package { + switch { + case fn.Pkg != nil: + return fn.Pkg // non-generic function (does that follow??) + case fn.topLevelOrigin != nil: + return fn.topLevelOrigin.Pkg // instance of a named generic function + case fn.parent != nil: + return fn.parent.declaredPackage() // instance of an anonymous [generic] function + default: + return nil // function is not declared in a package, e.g. a wrapper. + } +} + +// relPkg returns types.Package fn is printed in relationship to. +func (fn *Function) relPkg() *types.Package { + if p := fn.declaredPackage(); p != nil { + return p.Pkg } return nil } @@ -842,7 +707,7 @@ func WriteFunction(buf *bytes.Buffer, f *Function) { if f.Pkg != nil { fmt.Fprintf(buf, "# Package: %s\n", f.Pkg.Pkg.Path()) } - if syn := f.Synthetic; syn != 0 { + if syn := f.Synthetic; syn != "" { fmt.Fprintln(buf, "# Synthetic:", syn) } if pos := f.Pos(); pos.IsValid() { @@ -853,26 +718,32 @@ func WriteFunction(buf *bytes.Buffer, f *Function) { fmt.Fprintf(buf, "# Parent: %s\n", f.parent.Name()) } - from := f.pkg() + from := f.relPkg() + + // NB. column calculations are confused by non-ASCII + // characters + const punchcard = 80 // for old time's sake. + const padding = " " if f.FreeVars != nil { buf.WriteString("# Free variables:\n") for i, fv := range f.FreeVars { - fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, fv.Name(), relType(fv.Type(), from)) + fmt.Fprintf(buf, "# % 3d:%s%s %s\n", i, padding, fv.Name(), relType(fv.Type(), from)) } } if len(f.Locals) > 0 { buf.WriteString("# Locals:\n") for i, l := range f.Locals { - fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, l.Name(), relType(deref(l.Type()), from)) + fmt.Fprintf(buf, "# % 3d:%s%s %s\n", i, padding, l.Name(), relType(deref(l.Type()), from)) } } writeSignature(buf, from, f.Name(), f.Signature) buf.WriteString(":\n") if f.Blocks == nil { - buf.WriteString("\t(external)\n") + buf.WriteString(padding) + buf.WriteString("(external)\n") } for _, b := range f.Blocks { @@ -881,86 +752,65 @@ func WriteFunction(buf *bytes.Buffer, f *Function) { fmt.Fprintf(buf, ".nil:\n") continue } - fmt.Fprintf(buf, "b%d:", b.Index) - if len(b.Preds) > 0 { - fmt.Fprint(buf, " ←") - for _, pred := range b.Preds { - fmt.Fprintf(buf, " b%d", pred.Index) - } - } - if b.Comment != "" { - fmt.Fprintf(buf, " # %s", b.Comment) + n, _ := fmt.Fprintf(buf, "%d:", b.Index) + // (|predecessors|, |successors|, immediate dominator) + bmsg := fmt.Sprintf("%s P:%d S:%d", b.Comment, len(b.Preds), len(b.Succs)) + if b.Idom() != nil { + bmsg = fmt.Sprintf("%s idom:%d", bmsg, b.Idom().Index) } - buf.WriteByte('\n') + fmt.Fprintf(buf, "%*s%s\n", punchcard-1-n-len(bmsg), "", bmsg) if false { // CFG debugging - fmt.Fprintf(buf, "\t# CFG: %s --> %s --> %s\n", b.Preds, b, b.Succs) + fmt.Fprintf(buf, "%s# CFG: %s --> %s --> %s\n", padding, b.Preds, b, b.Succs) } - - buf2 := &bytes.Buffer{} for _, instr := range b.Instrs { - buf.WriteString("\t") + buf.WriteString(padding) switch v := instr.(type) { case Value: + l := punchcard - len(padding) // Left-align the instruction. if name := v.Name(); name != "" { - fmt.Fprintf(buf, "%s = ", name) + n, _ := fmt.Fprintf(buf, "%s = ", name) + l -= n + } + n, _ := buf.WriteString(instr.String()) + l -= n + // Right-align the type if there's space. + if t := v.Type(); t != nil { + buf.WriteByte(' ') + ts := relType(t, from) + l -= len(ts) + len(" ") // (spaces before and after type) + if l > 0 { + fmt.Fprintf(buf, "%*s", l, "") + } + buf.WriteString(ts) } - buf.WriteString(instr.String()) case nil: // Be robust against bad transforms. buf.WriteString("") default: buf.WriteString(instr.String()) } - if instr != nil && instr.Comment() != "" { - buf.WriteString(" # ") - buf.WriteString(instr.Comment()) - } - buf.WriteString("\n") - - if f.Prog.mode&PrintSource != 0 { - if s := instr.Source(); s != nil { - buf2.Reset() - format.Node(buf2, f.Prog.Fset, s) - for { - line, err := buf2.ReadString('\n') - if len(line) == 0 { - break - } - buf.WriteString("\t\t> ") - buf.WriteString(line) - if line[len(line)-1] != '\n' { - buf.WriteString("\n") - } - if err != nil { - break - } - } + // -mode=S: show line numbers + if f.Prog.mode&LogSource != 0 { + if pos := instr.Pos(); pos.IsValid() { + fmt.Fprintf(buf, " L%d", f.Prog.Fset.Position(pos).Line) } } + buf.WriteString("\n") } - buf.WriteString("\n") } + buf.WriteString("\n") } // newBasicBlock adds to f a new basic block and returns it. It does // not automatically become the current block for subsequent calls to emit. // comment is an optional string for more readable debugging output. func (f *Function) newBasicBlock(comment string) *BasicBlock { - var instrs []Instruction - if len(f.functionBody.scratchInstructions) > 0 { - instrs = f.functionBody.scratchInstructions[0:0:avgInstructionsPerBlock] - f.functionBody.scratchInstructions = f.functionBody.scratchInstructions[avgInstructionsPerBlock:] - } else { - instrs = make([]Instruction, 0, avgInstructionsPerBlock) - } - b := &BasicBlock{ Index: len(f.Blocks), Comment: comment, parent: f, - Instrs: instrs, } b.Succs = b.succs2[:0] f.Blocks = append(f.Blocks, b) @@ -981,7 +831,7 @@ func (f *Function) newBasicBlock(comment string) *BasicBlock { // "reflect" package, etc. // // TODO(adonovan): think harder about the API here. -func (prog *Program) NewFunction(name string, sig *types.Signature, provenance Synthetic) *Function { +func (prog *Program) NewFunction(name string, sig *types.Signature, provenance string) *Function { return &Function{Prog: prog, name: name, Signature: sig, Synthetic: provenance} } @@ -991,15 +841,6 @@ type extentNode [2]token.Pos func (n extentNode) Pos() token.Pos { return n[0] } func (n extentNode) End() token.Pos { return n[1] } -func (f *Function) initHTML(name string) { - if name == "" { - return - } - if rel := f.RelString(nil); rel == name { - f.wr = NewHTMLWriter("ir.html", rel, "") - } -} - func killInstruction(instr Instruction) { ops := instr.Operands(nil) for _, op := range ops { @@ -1009,9 +850,14 @@ func killInstruction(instr Instruction) { } } +// Syntax returns the function's syntax (*ast.Func{Decl,Lit}) +// if it was produced from syntax or an *ast.RangeStmt if +// it is a range-over-func yield function. +func (f *Function) Syntax() ast.Node { return f.syntax } + // identVar returns the variable defined by id. func identVar(fn *Function, id *ast.Ident) *types.Var { - return fn.Pkg.info.Defs[id].(*types.Var) + return fn.info.Defs[id].(*types.Var) } // unique returns a unique positive int within the source tree of f. @@ -1082,7 +928,7 @@ func returnExit(fn *Function, source ast.Node) *exit { e := &exit{ id: unique(fn), from: fn, - to: fn.sourceFn, + to: fn.source, source: source, } fn.exits = append(fn.exits, e) diff --git a/tools/vendor/honnef.co/go/tools/go/ir/html.go b/tools/vendor/honnef.co/go/tools/go/ir/html.go deleted file mode 100644 index 86b2a63de..000000000 --- a/tools/vendor/honnef.co/go/tools/go/ir/html.go +++ /dev/null @@ -1,1126 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Copyright 2019 Dominik Honnef. All rights reserved. - -package ir - -import ( - "bytes" - "fmt" - "go/types" - "html" - "io" - "log" - "os" - "os/exec" - "path/filepath" - "reflect" - "sort" - "strings" -) - -func live(f *Function) []bool { - max := 0 - var ops []*Value - - for _, b := range f.Blocks { - for _, instr := range b.Instrs { - if int(instr.ID()) > max { - max = int(instr.ID()) - } - } - } - - out := make([]bool, max+1) - var q []Node - for _, b := range f.Blocks { - for _, instr := range b.Instrs { - switch instr.(type) { - case *BlankStore, *Call, *ConstantSwitch, *Defer, *Go, *If, *Jump, *MapUpdate, *Next, *Panic, *Recv, *Return, *RunDefers, *Send, *Store, *Unreachable: - out[instr.ID()] = true - q = append(q, instr) - } - } - } - - for len(q) > 0 { - v := q[len(q)-1] - q = q[:len(q)-1] - for _, op := range v.Operands(ops) { - if *op == nil { - continue - } - if !out[(*op).ID()] { - out[(*op).ID()] = true - q = append(q, *op) - } - } - } - - return out -} - -type funcPrinter interface { - startBlock(b *BasicBlock, reachable bool) - endBlock(b *BasicBlock) - value(v Node, live bool) - startDepCycle() - endDepCycle() - named(n string, vals []Value) -} - -func namedValues(f *Function) map[types.Object][]Value { - names := map[types.Object][]Value{} - for _, b := range f.Blocks { - for _, instr := range b.Instrs { - if instr, ok := instr.(*DebugRef); ok { - if obj := instr.object; obj != nil { - names[obj] = append(names[obj], instr.X) - } - } - } - } - // XXX deduplicate values - return names -} - -func fprintFunc(p funcPrinter, f *Function) { - // XXX does our IR form preserve unreachable blocks? - // reachable, live := findlive(f) - - l := live(f) - for _, b := range f.Blocks { - // XXX - // p.startBlock(b, reachable[b.Index]) - p.startBlock(b, true) - - end := max(len(b.Instrs)-1, 0) - for _, v := range b.Instrs[:end] { - if _, ok := v.(*DebugRef); !ok { - p.value(v, l[v.ID()]) - } - } - p.endBlock(b) - } - - names := namedValues(f) - keys := make([]types.Object, 0, len(names)) - for key := range names { - keys = append(keys, key) - } - sort.Slice(keys, func(i, j int) bool { - return keys[i].Pos() < keys[j].Pos() - }) - for _, key := range keys { - p.named(key.Name(), names[key]) - } -} - -func opName(v Node) string { - switch v := v.(type) { - case *Call: - if v.Common().IsInvoke() { - return "Invoke" - } - return "Call" - case *Alloc: - if v.Heap { - return "HeapAlloc" - } - return "StackAlloc" - case *Select: - if v.Blocking { - return "SelectBlocking" - } - return "SelectNonBlocking" - default: - return reflect.ValueOf(v).Type().Elem().Name() - } -} - -type HTMLWriter struct { - w io.WriteCloser - path string - dot *dotWriter -} - -func NewHTMLWriter(path string, funcname, cfgMask string) *HTMLWriter { - out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) - if err != nil { - log.Fatalf("%v", err) - } - pwd, err := os.Getwd() - if err != nil { - log.Fatalf("%v", err) - } - html := HTMLWriter{w: out, path: filepath.Join(pwd, path)} - html.dot = newDotWriter() - html.start(funcname) - return &html -} - -func (w *HTMLWriter) start(name string) { - if w == nil { - return - } - w.WriteString("") - w.WriteString(` - - - - - -`) - w.WriteString("") - w.WriteString("

") - w.WriteString(html.EscapeString(name)) - w.WriteString("

") - w.WriteString(` -help -
- -

-Click on a value or block to toggle highlighting of that value/block -and its uses. (Values and blocks are highlighted by ID, and IDs of -dead items may be reused, so not all highlights necessarily correspond -to the clicked item.) -

- -

-Faded out values and blocks are dead code that has not been eliminated. -

- -

-Values printed in italics have a dependency cycle. -

- -

-CFG: Dashed edge is for unlikely branches. Blue color is for backward edges. -Edge with a dot means that this edge follows the order in which blocks were laid out. -

- -
-`) - w.WriteString("") - w.WriteString("") -} - -func (w *HTMLWriter) Close() { - if w == nil { - return - } - io.WriteString(w.w, "") - io.WriteString(w.w, "
") - io.WriteString(w.w, "") - io.WriteString(w.w, "") - w.w.Close() - fmt.Printf("dumped IR to %v\n", w.path) -} - -// WriteFunc writes f in a column headed by title. -// phase is used for collapsing columns and should be unique across the table. -func (w *HTMLWriter) WriteFunc(phase, title string, f *Function) { - if w == nil { - return - } - w.WriteColumn(phase, title, "", funcHTML(f, phase, w.dot)) -} - -// WriteColumn writes raw HTML in a column headed by title. -// It is intended for pre- and post-compilation log output. -func (w *HTMLWriter) WriteColumn(phase, title, class, html string) { - if w == nil { - return - } - id := strings.Replace(phase, " ", "-", -1) - // collapsed column - w.Printf("
%v
", id, phase) - - if class == "" { - w.Printf("", id) - } else { - w.Printf("", id, class) - } - w.WriteString("

" + title + "

") - w.WriteString(html) - w.WriteString("") -} - -func (w *HTMLWriter) Printf(msg string, v ...any) { - if _, err := fmt.Fprintf(w.w, msg, v...); err != nil { - log.Fatalf("%v", err) - } -} - -func (w *HTMLWriter) WriteString(s string) { - if _, err := io.WriteString(w.w, s); err != nil { - log.Fatalf("%v", err) - } -} - -func valueHTML(v Node) string { - if v == nil { - return "<nil>" - } - // TODO: Using the value ID as the class ignores the fact - // that value IDs get recycled and that some values - // are transmuted into other values. - class := fmt.Sprintf("t%d", v.ID()) - var label string - switch v := v.(type) { - case *Function: - label = v.RelString(nil) - case *Builtin: - label = v.Name() - default: - label = class - } - return fmt.Sprintf("%s", class, label) -} - -func valueLongHTML(v Node) string { - // TODO: Any intra-value formatting? - // I'm wary of adding too much visual noise, - // but a little bit might be valuable. - // We already have visual noise in the form of punctuation - // maybe we could replace some of that with formatting. - var s strings.Builder - s.WriteString(fmt.Sprintf("", v.ID())) - - linenumber := "(?)" - if v.Pos().IsValid() { - line := v.Parent().Prog.Fset.Position(v.Pos()).Line - linenumber = fmt.Sprintf("(%d)", line, line) - } - - s.WriteString(fmt.Sprintf("%s %s = %s", valueHTML(v), linenumber, opName(v))) - - if v, ok := v.(Value); ok { - s.WriteString(" <" + html.EscapeString(v.Type().String()) + ">") - } - - switch v := v.(type) { - case *Parameter: - s.WriteString(fmt.Sprintf(" {%s}", html.EscapeString(v.name))) - case *BinOp: - s.WriteString(fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String()))) - case *UnOp: - s.WriteString(fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String()))) - case *Extract: - name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name() - s.WriteString(fmt.Sprintf(" [%d] (%s)", v.Index, name)) - case *Field: - st := v.X.Type().Underlying().(*types.Struct) - // Be robust against a bad index. - name := "?" - if 0 <= v.Field && v.Field < st.NumFields() { - name = st.Field(v.Field).Name() - } - s.WriteString(fmt.Sprintf(" [%d] (%s)", v.Field, name)) - case *FieldAddr: - st := deref(v.X.Type()).Underlying().(*types.Struct) - // Be robust against a bad index. - name := "?" - if 0 <= v.Field && v.Field < st.NumFields() { - name = st.Field(v.Field).Name() - } - - s.WriteString(fmt.Sprintf(" [%d] (%s)", v.Field, name)) - case *Recv: - s.WriteString(fmt.Sprintf(" {%t}", v.CommaOk)) - case *Call: - if v.Common().IsInvoke() { - s.WriteString(fmt.Sprintf(" {%s}", html.EscapeString(v.Common().Method.FullName()))) - } - case *Const: - if v.Value == nil { - s.WriteString(" {<nil>}") - } else { - s.WriteString(fmt.Sprintf(" {%s}", html.EscapeString(v.Value.String()))) - } - case *Sigma: - s.WriteString(fmt.Sprintf(" [#%s]", v.From)) - } - for _, a := range v.Operands(nil) { - s.WriteString(fmt.Sprintf(" %s", valueHTML(*a))) - } - if v, ok := v.(Instruction); ok { - s.WriteString(fmt.Sprintf(" (%s)", v.Comment())) - } - - // OPT(dh): we're calling namedValues many times on the same function. - allNames := namedValues(v.Parent()) - var names []string - for name, values := range allNames { - for _, value := range values { - if v == value { - names = append(names, name.Name()) - break - } - } - } - if len(names) != 0 { - s.WriteString(" (" + strings.Join(names, ", ") + ")") - } - - s.WriteString("") - return s.String() -} - -func blockHTML(b *BasicBlock) string { - // TODO: Using the value ID as the class ignores the fact - // that value IDs get recycled and that some values - // are transmuted into other values. - s := html.EscapeString(b.String()) - return fmt.Sprintf("%s", s, s) -} - -func blockLongHTML(b *BasicBlock) string { - var kind string - var term Instruction - if len(b.Instrs) > 0 { - term = b.Control() - kind = opName(term) - } - // TODO: improve this for HTML? - var s strings.Builder - s.WriteString(fmt.Sprintf("%s", b.Index, kind)) - - if term != nil { - ops := term.Operands(nil) - if len(ops) > 0 { - var ss []string - for _, op := range ops { - ss = append(ss, valueHTML(*op)) - } - s.WriteString(" " + strings.Join(ss, ", ")) - } - } - if len(b.Succs) > 0 { - s.WriteString(" →") // right arrow - for _, c := range b.Succs { - s.WriteString(" " + blockHTML(c)) - } - } - return s.String() -} - -func funcHTML(f *Function, phase string, dot *dotWriter) string { - buf := new(bytes.Buffer) - if dot != nil { - dot.writeFuncSVG(buf, phase, f) - } - fmt.Fprint(buf, "") - p := htmlFuncPrinter{w: buf} - fprintFunc(p, f) - - // fprintFunc(&buf, f) // TODO: HTML, not text,
for line breaks, etc. - fmt.Fprint(buf, "
") - return buf.String() -} - -type htmlFuncPrinter struct { - w io.Writer -} - -func (p htmlFuncPrinter) startBlock(b *BasicBlock, reachable bool) { - var dead string - if !reachable { - dead = "dead-block" - } - fmt.Fprintf(p.w, "
    ", b, dead) - fmt.Fprintf(p.w, "
  • %s:", blockHTML(b)) - if len(b.Preds) > 0 { - io.WriteString(p.w, " ←") // left arrow - for _, pred := range b.Preds { - fmt.Fprintf(p.w, " %s", blockHTML(pred)) - } - } - if len(b.Instrs) > 0 { - io.WriteString(p.w, ``) - } - io.WriteString(p.w, "
  • ") - if len(b.Instrs) > 0 { // start list of values - io.WriteString(p.w, "
  • ") - io.WriteString(p.w, "
      ") - } -} - -func (p htmlFuncPrinter) endBlock(b *BasicBlock) { - if len(b.Instrs) > 0 { // end list of values - io.WriteString(p.w, "
    ") - io.WriteString(p.w, "
  • ") - } - io.WriteString(p.w, "
  • ") - fmt.Fprint(p.w, blockLongHTML(b)) - io.WriteString(p.w, "
  • ") - io.WriteString(p.w, "
") -} - -func (p htmlFuncPrinter) value(v Node, live bool) { - var dead string - if !live { - dead = "dead-value" - } - fmt.Fprintf(p.w, "
  • ", dead) - fmt.Fprint(p.w, valueLongHTML(v)) - io.WriteString(p.w, "
  • ") -} - -func (p htmlFuncPrinter) startDepCycle() { - fmt.Fprintln(p.w, "") -} - -func (p htmlFuncPrinter) endDepCycle() { - fmt.Fprintln(p.w, "") -} - -func (p htmlFuncPrinter) named(n string, vals []Value) { - fmt.Fprintf(p.w, "
  • name %s: ", n) - for _, val := range vals { - fmt.Fprintf(p.w, "%s ", valueHTML(val)) - } - fmt.Fprintf(p.w, "
  • ") -} - -type dotWriter struct { - path string - broken bool -} - -// newDotWriter returns non-nil value when mask is valid. -// dotWriter will generate SVGs only for the phases specified in the mask. -// mask can contain following patterns and combinations of them: -// * - all of them; -// x-y - x through y, inclusive; -// x,y - x and y, but not the passes between. -func newDotWriter() *dotWriter { - path, err := exec.LookPath("dot") - if err != nil { - fmt.Println(err) - return nil - } - return &dotWriter{path: path} -} - -func (d *dotWriter) writeFuncSVG(w io.Writer, phase string, f *Function) { - if d.broken { - return - } - cmd := exec.Command(d.path, "-Tsvg") - pipe, err := cmd.StdinPipe() - if err != nil { - d.broken = true - fmt.Println(err) - return - } - buf := new(bytes.Buffer) - cmd.Stdout = buf - bufErr := new(bytes.Buffer) - cmd.Stderr = bufErr - err = cmd.Start() - if err != nil { - d.broken = true - fmt.Println(err) - return - } - fmt.Fprint(pipe, `digraph "" { margin=0; size="4,40"; ranksep=.2; `) - id := strings.Replace(phase, " ", "-", -1) - fmt.Fprintf(pipe, `id="g_graph_%s";`, id) - fmt.Fprintf(pipe, `node [style=filled,fillcolor=white,fontsize=16,fontname="Menlo,Times,serif",margin="0.01,0.03"];`) - fmt.Fprintf(pipe, `edge [fontsize=16,fontname="Menlo,Times,serif"];`) - for _, b := range f.Blocks { - layout := "" - fmt.Fprintf(pipe, `%v [label="%v%s\n%v",id="graph_node_%v_%v"];`, b, b, layout, b.Control().String(), id, b) - } - indexOf := make([]int, len(f.Blocks)) - for i, b := range f.Blocks { - indexOf[b.Index] = i - } - - // XXX - /* - ponums := make([]int32, len(f.Blocks)) - _ = postorderWithNumbering(f, ponums) - isBackEdge := func(from, to int) bool { - return ponums[from] <= ponums[to] - } - */ - isBackEdge := func(from, to int) bool { return false } - - for _, b := range f.Blocks { - for i, s := range b.Succs { - style := "solid" - color := "black" - arrow := "vee" - if isBackEdge(b.Index, s.Index) { - color = "blue" - } - fmt.Fprintf(pipe, `%v -> %v [label=" %d ",style="%s",color="%s",arrowhead="%s"];`, b, s, i, style, color, arrow) - } - } - fmt.Fprint(pipe, "}") - pipe.Close() - err = cmd.Wait() - if err != nil { - d.broken = true - fmt.Printf("dot: %v\n%v\n", err, bufErr.String()) - return - } - - svgID := "svg_graph_" + id - fmt.Fprintf(w, `
    `, svgID, svgID) - // For now, an awful hack: edit the html as it passes through - // our fingers, finding ' 0 || len(targs) > 0 + if len(rtargs) > 0 { + // possibly generic method on generic type + obj = prog.canon.instantiateMethod(fn.object, rtargs, prog.ctxt) + } else { + // generic method on non-generic type + obj = fn.object // instantiation does not exist yet + } + if len(targs) > 0 { + // generic method + instSig, err := types.Instantiate(prog.ctxt, obj.Signature(), targs, false) + if err != nil { + panic(err) + } + instance, ok := instSig.(*types.Signature) + if !ok { + panic("Instantiate of a Signature returned a non-signature") + } + // Do not canonicalize generic methods, because the receiver is not + // part of a Signature's type identity. For example, + // (*G[int]).m[int] and (G[int]).n[int] may be identical types even + // though their receiver types differ. + sig = instance + } else { + // non-generic method on generic type + sig = obj.Signature() + } + } else { + // function, len(rtargs) == 0 && len(targs) > 0 + instSig, err := types.Instantiate(prog.ctxt, fn.Signature, targs, false) + if err != nil { + panic(err) + } + instance, ok := instSig.(*types.Signature) + if !ok { + panic("Instantiate of a Signature returned a non-signature") + } + obj = fn.object // instantiation does not exist yet + sig = prog.canon.Type(instance).(*types.Signature) + } + + // Choose strategy (instance or wrapper). + var ( + synthetic string + subst *subster + build buildFunc + ) + if prog.mode&InstantiateGenerics != 0 && !prog.isParameterized(slices.Concat(rtargs, targs)...) { + synthetic = fmt.Sprintf("instance of %s", fn.Name()) + if fn.syntax != nil { + subst = makeSubster(prog.ctxt, obj, fn.recvtypeparams, rtargs, fn.typeparams, targs) + build = (*builder).buildFromSyntax + } else { + build = (*builder).buildParamsOnly + } + } else { + synthetic = fmt.Sprintf("instantiation wrapper of %s", fn.Name()) + build = (*builder).buildInstantiationWrapper + } + + name := fn.Name() + if len(targs) > 0 { + name = fmt.Sprintf("%s%s", name, targstr(targs)) // may not be unique + } + + /* generic instance or instantiation wrapper */ + ret := &Function{ + name: name, + object: obj, + Signature: sig, + Synthetic: synthetic, + syntax: fn.syntax, // \ + info: fn.info, // } empty for non-created packages + goversion: fn.goversion, // / + build: build, + topLevelOrigin: fn, + pos: obj.Pos(), + Pkg: nil, + Prog: fn.Prog, + recvtypeparams: fn.recvtypeparams, // share with origin + recvtypeargs: rtargs, + typeparams: fn.typeparams, // share with origin + typeargs: targs, + subst: subst, + } + return ret +} + +// isParameterized reports whether any of the specified types contains +// a free type parameter. It is safe to call concurrently. +func (prog *Program) isParameterized(ts ...types.Type) bool { + prog.hasParamsMu.Lock() + defer prog.hasParamsMu.Unlock() + + // TODO(adonovan): profile. If this operation is expensive, + // handle the most common but shallow cases such as T, pkg.T, + // *T without consulting the cache under the lock. + + return slices.ContainsFunc(ts, prog.hasParams.Has) +} diff --git a/tools/vendor/honnef.co/go/tools/go/ir/irutil/load.go b/tools/vendor/honnef.co/go/tools/go/ir/irutil/load.go index bbf0e2719..49eaa7222 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/irutil/load.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/irutil/load.go @@ -13,16 +13,9 @@ import ( "honnef.co/go/tools/go/ir" - //lint:ignore SA1019 go/loader is deprecated, but works fine for our tests - "golang.org/x/tools/go/loader" "golang.org/x/tools/go/packages" ) -type Options struct { - // Which function, if any, to print in HTML form - PrintFunc string -} - // Packages creates an IR program for a set of packages. // // The packages must have been loaded from source syntax using the @@ -40,8 +33,26 @@ type Options struct { // packages with well-typed syntax trees. // // The mode parameter controls diagnostics and checking during IR construction. -func Packages(initial []*packages.Package, mode ir.BuilderMode, opts *Options) (*ir.Program, []*ir.Package) { - return doPackages(initial, mode, false, opts) +func Packages(initial []*packages.Package, mode ir.BuilderMode) (*ir.Program, []*ir.Package) { + // TODO(adonovan): opt: this calls CreatePackage far more than + // necessary: for all dependencies, not just the (non-initial) + // direct dependencies of the initial packages. + // + // But can it reasonably be changed without breaking the + // spirit and/or letter of the law above? Clients may notice + // if we call CreatePackage less, as methods like + // Program.FuncValue will return nil. Or must we provide a new + // function (and perhaps deprecate this one)? Is it worth it? + // + // Tim King makes the interesting point that it would be + // possible to entirely alleviate the client from the burden + // of calling CreatePackage for non-syntax packages, if we + // were to treat vars and funcs lazily in the same way we now + // treat methods. (In essence, try to move away from the + // notion of ir.Packages, and make the Program answer + // all reasonable questions about any types.Object.) + + return doPackages(initial, mode, false) } // AllPackages creates an IR program for a set of packages plus all @@ -61,21 +72,17 @@ func Packages(initial []*packages.Package, mode ir.BuilderMode, opts *Options) ( // well-typed syntax trees. // // The mode parameter controls diagnostics and checking during IR construction. -func AllPackages(initial []*packages.Package, mode ir.BuilderMode, opts *Options) (*ir.Program, []*ir.Package) { - return doPackages(initial, mode, true, opts) +func AllPackages(initial []*packages.Package, mode ir.BuilderMode) (*ir.Program, []*ir.Package) { + return doPackages(initial, mode, true) } -func doPackages(initial []*packages.Package, mode ir.BuilderMode, deps bool, opts *Options) (*ir.Program, []*ir.Package) { - +func doPackages(initial []*packages.Package, mode ir.BuilderMode, deps bool) (*ir.Program, []*ir.Package) { var fset *token.FileSet if len(initial) > 0 { fset = initial[0].Fset } prog := ir.NewProgram(fset, mode) - if opts != nil { - prog.PrintFunc = opts.PrintFunc - } isInitial := make(map[*packages.Package]bool, len(initial)) for _, p := range initial { @@ -86,10 +93,12 @@ func doPackages(initial []*packages.Package, mode ir.BuilderMode, deps bool, opt packages.Visit(initial, nil, func(p *packages.Package) { if p.Types != nil && !p.IllTyped { var files []*ast.File + var info *types.Info if deps || isInitial[p] { files = p.Syntax + info = p.TypesInfo } - irmap[p] = prog.CreatePackage(p.Types, files, p.TypesInfo, true) + irmap[p] = prog.CreatePackage(p.Types, files, info, true) } }) @@ -100,30 +109,8 @@ func doPackages(initial []*packages.Package, mode ir.BuilderMode, deps bool, opt return prog, irpkgs } -// CreateProgram returns a new program in IR form, given a program -// loaded from source. An IR package is created for each transitively -// error-free package of lprog. -// -// Code for bodies of functions is not built until Build is called -// on the result. -// -// The mode parameter controls diagnostics and checking during IR construction. -// -// Deprecated: use golang.org/x/tools/go/packages and the Packages -// function instead; see ir.ExampleLoadPackages. -func CreateProgram(lprog *loader.Program, mode ir.BuilderMode) *ir.Program { - prog := ir.NewProgram(lprog.Fset, mode) - - for _, info := range lprog.AllPackages { - if info.TransitivelyErrorFree { - prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable) - } - } - - return prog -} - -// BuildPackage builds an IR program with IR for a single package. +// BuildPackage builds an IR program with IR intermediate +// representation (IR) for all functions of a single package. // // It populates pkg by type-checking the specified file ASTs. All // dependencies are loaded using the importer specified by tc, which @@ -176,6 +163,25 @@ func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, fil } createAll(pkg.Imports()) + // TODO(adonovan): we could replace createAll with just: + // + // // Create IR packages for all imports. + // for _, p := range pkg.Imports() { + // prog.CreatePackage(p, nil, nil, true) + // } + // + // (with minor changes to changes to ../builder_test.go as + // shown in CL 511715 PS 10.) But this would strictly violate + // the letter of the doc comment above, which says "all + // dependencies created". + // + // Tim makes the good point with some extra work we could + // remove the need for any CreatePackage calls except the + // ones with syntax (i.e. primary packages). Of course + // You wouldn't have ir.Packages and Members for as + // many things but no-one really uses that anyway. + // I wish I had done this from the outset. + // Create and build the primary package. irpkg := prog.CreatePackage(pkg, files, info, false) irpkg.Build() diff --git a/tools/vendor/honnef.co/go/tools/go/ir/irutil/stub.go b/tools/vendor/honnef.co/go/tools/go/ir/irutil/stub.go index 4311c7dbe..a686ede63 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/irutil/stub.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/irutil/stub.go @@ -6,22 +6,87 @@ import ( // IsStub reports whether a function is a stub. A function is // considered a stub if it has no instructions or if all it does is -// return a constant value. +// return a fixed value (either an actual constant or a new allocation). func IsStub(fn *ir.Function) bool { + if fn.Source() == nil { + // External functions have to be assumed to not be stubs. + return false + } + for _, b := range fn.Blocks { for _, instr := range b.Instrs { switch instr.(type) { - case *ir.Const: - // const naturally has no side-effects case *ir.Panic: // panic is a stub if it only uses constants case *ir.Return: // return is a stub if it only uses constants - case *ir.DebugRef: case *ir.Jump: // if there are no disallowed instructions, then we're // only jumping to the exit block (or possibly // somewhere else that's stubby?) + case *ir.MakeInterface: + // this can only be wrapping Const and HeapAlloc, which is fine. + case *ir.Alloc: + // allocating is fine as long as all we do with the allocation + // is return it (possibly in an interface value) + default: + // all other instructions are assumed to do actual work + return false + } + } + } + return true +} + +// IsTrivial reports whether a function is trivial. A function is +// considered trivial if it is a stub, only returns a global variable, or calls +// another trivial function. +func IsTrivial(fn *ir.Function) bool { + return isTrivial(fn, nil) +} + +func isTrivial(fn *ir.Function, seen map[*ir.Function]struct{}) bool { + if fn.Source() == nil { + // External functions have to be assumed to be nontrivial. + return false + } + + if _, ok := seen[fn]; ok { + // Mutual recursion is not trivial + return false + } + + // We delay adding to seen until a call, to avoid creating garbage in the + // common case. + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + switch instr := instr.(type) { + case *ir.Panic: + // panic is a stub if it only uses constants + case *ir.Return: + // return is a stub if it only uses constants + case *ir.Jump: + // if there are no disallowed instructions, then we're + // only jumping to the exit block (or possibly + // somewhere else that's stubby?) + case *ir.MakeInterface: + // this can only be wrapping allowed instructions + case *ir.Alloc: + // allocating is fine as long as all we do with the allocation + // is return it (possibly in an interface value) + case *ir.Load: + if _, ok := instr.X.(*ir.Global); !ok { + return false + } + case *ir.Call: + if seen == nil { + seen = make(map[*ir.Function]struct{}) + } + seen[fn] = struct{}{} + callee := instr.Call.StaticCallee() + if callee == nil || !isTrivial(callee, seen) { + return false + } default: // all other instructions are assumed to do actual work return false diff --git a/tools/vendor/honnef.co/go/tools/go/ir/irutil/switch.go b/tools/vendor/honnef.co/go/tools/go/ir/irutil/switch.go index afe899d86..cf7c54e4b 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/irutil/switch.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/irutil/switch.go @@ -129,20 +129,10 @@ func Switches(fn *ir.Function) []Switch { return switches } -func isSameX(x1 ir.Value, x2 ir.Value) bool { - if x1 == x2 { - return true - } - if x2, ok := x2.(*ir.Sigma); ok { - return isSameX(x1, x2.X) - } - return false -} - func valueSwitch(sw *Switch, k *ir.Const, seen map[*ir.BasicBlock]bool) { b := sw.Start x := sw.X - for isSameX(sw.X, x) { + for sw.X == x { if seen[b] { break } @@ -159,13 +149,13 @@ func valueSwitch(sw *Switch, k *ir.Const, seen map[*ir.BasicBlock]bool) { switch instr.(type) { case *ir.If, *ir.BinOp: n++ - case *ir.Sigma, *ir.Phi, *ir.DebugRef: + case *ir.Phi: default: n += 1000 } } if n != 2 { - // Block b contains not just 'if x == k' and σ/ϕ nodes, + // Block b contains not just 'if x == k' and ϕ nodes, // so it may have side effects that // make it unsafe to elide. break @@ -183,7 +173,7 @@ func valueSwitch(sw *Switch, k *ir.Const, seen map[*ir.BasicBlock]bool) { func typeSwitch(sw *Switch, y ir.Value, T types.Type, seen map[*ir.BasicBlock]bool) { b := sw.Start x := sw.X - for isSameX(sw.X, x) { + for sw.X == x { if seen[b] { break } @@ -201,7 +191,7 @@ func typeSwitch(sw *Switch, y ir.Value, T types.Type, seen map[*ir.BasicBlock]bo switch instr.(type) { case *ir.TypeAssert, *ir.Extract, *ir.If: n++ - case *ir.Sigma, *ir.Phi: + case *ir.Phi: default: n += 1000 } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/irutil/util.go b/tools/vendor/honnef.co/go/tools/go/ir/irutil/util.go index 9fed28bb8..c8b980b36 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/irutil/util.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/irutil/util.go @@ -47,42 +47,31 @@ func Walk(b *ir.BasicBlock, fn func(*ir.BasicBlock) bool) { func Vararg(x *ir.Slice) ([]ir.Value, bool) { var out []ir.Value - alloc, ok := ir.Unwrap(x.X).(*ir.Alloc) + alloc, ok := x.X.(*ir.Alloc) if !ok { return nil, false } - var checkAlloc func(alloc ir.Value) bool - checkAlloc = func(alloc ir.Value) bool { - for _, ref := range *alloc.Referrers() { - if ref == x { - continue - } - if ref.Block() != x.Block() { - return false + for _, ref := range *alloc.Referrers() { + if ref == x { + continue + } + if ref.Block() != x.Block() { + return nil, false + } + switch ref := ref.(type) { + case *ir.IndexAddr: + idx := ref + if len(*idx.Referrers()) != 1 { + return nil, false } - switch ref := ref.(type) { - case *ir.IndexAddr: - idx := ref - if len(*idx.Referrers()) != 1 { - return false - } - store, ok := (*idx.Referrers())[0].(*ir.Store) - if !ok { - return false - } - out = append(out, store.Val) - case *ir.Copy: - if !checkAlloc(ref) { - return false - } - default: - return false + store, ok := (*idx.Referrers())[0].(*ir.Store) + if !ok { + return nil, false } + out = append(out, store.Val) + default: + return nil, false } - return true - } - if !checkAlloc(alloc) { - return nil, false } return out, true } @@ -111,16 +100,6 @@ func IsCallToAny(call *ir.CallCommon, names ...string) bool { return slices.Contains(names, q) } -func FilterDebug(instr []ir.Instruction) []ir.Instruction { - var out []ir.Instruction - for _, ins := range instr { - if _, ok := ins.(*ir.DebugRef); !ok { - out = append(out, ins) - } - } - return out -} - func IsExample(fn *ir.Function) bool { if !strings.HasPrefix(fn.Name(), "Example") { return false @@ -132,7 +111,7 @@ func IsExample(fn *ir.Function) bool { return strings.HasSuffix(f.Name(), "_test.go") } -// Flatten recursively returns the underlying value of an ir.Sigma or +// Flatten recursively returns the underlying value of an // ir.Phi node. If all edges in an ir.Phi node are the same (after // flattening), the flattened edge will get returned. If flattening is // not possible, nil is returned. @@ -151,8 +130,6 @@ func Flatten(v ir.Value) ir.Value { seen[v] = struct{}{} switch v := v.(type) { - case *ir.Sigma: - dfs(v.X) case *ir.Phi: for _, e := range v.Edges { dfs(e) diff --git a/tools/vendor/honnef.co/go/tools/go/ir/irutil/visit.go b/tools/vendor/honnef.co/go/tools/go/ir/irutil/visit.go index f2135dca4..ef438a9a7 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/irutil/visit.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/irutil/visit.go @@ -2,9 +2,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package irutil +package irutil // import "honnef.co/go/tools/go/ir/irutil" -import "honnef.co/go/tools/go/ir" +import ( + "go/ast" + "go/types" + + _ "unsafe" // for linkname hack + + "honnef.co/go/tools/go/ir" +) // This file defines utilities for visiting the IR of // a Program. @@ -18,50 +25,122 @@ import "honnef.co/go/tools/go/ir" // synthetic wrappers. // // Precondition: all packages are built. +// +// TODO(adonovan): this function is underspecified. It doesn't +// actually work like a linker, which computes reachability from main +// using something like go/callgraph/cha (without materializing the +// call graph). In fact, it treats all public functions and all +// methods of public non-parameterized types as roots, even though +// they may be unreachable--but only in packages created from syntax. +// +// I think we should deprecate AllFunctions function in favor of two +// clearly defined ones: +// +// 1. The first would efficiently compute CHA reachability from a set +// of main packages, making it suitable for a whole-program +// analysis context with InstantiateGenerics, in conjunction with +// Program.Build. +// +// 2. The second would return only the set of functions corresponding +// to source Func{Decl,Lit} syntax, like SrcFunctions in +// go/analysis/passes/buildssa; this is suitable for +// package-at-a-time (or handful of packages) context. +// ir.Package could easily expose it as a field. +// +// We could add them unexported for now and use them via the linkname hack. func AllFunctions(prog *ir.Program) map[*ir.Function]bool { - visit := visitor{ - prog: prog, - seen: make(map[*ir.Function]bool), + seen := make(map[*ir.Function]bool) + + var function func(fn *ir.Function) + function = func(fn *ir.Function) { + if !seen[fn] { + seen[fn] = true + var buf [10]*ir.Value // avoid alloc in common case + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + for _, op := range instr.Operands(buf[:0]) { + if fn, ok := (*op).(*ir.Function); ok { + function(fn) + } + } + } + } + } } - visit.program() - return visit.seen -} -type visitor struct { - prog *ir.Program - seen map[*ir.Function]bool -} + // TODO(adonovan): opt: provide a way to share a builder + // across a sequence of MethodValue calls. -func (visit *visitor) program() { - for _, pkg := range visit.prog.AllPackages() { - for _, mem := range pkg.Members { - if fn, ok := mem.(*ir.Function); ok { - visit.function(fn) + methodsOf := func(T types.Type) { + if !types.IsInterface(T) { + mset := prog.MethodSets.MethodSet(T) + for sel := range mset.Methods() { + // Skip generic methods. + if sel.Obj().(*types.Func).Signature().TypeParams() == nil { + function(prog.MethodValue(sel)) + } } } } - for _, T := range visit.prog.RuntimeTypes() { - mset := visit.prog.MethodSets.MethodSet(T) - for i, n := 0, mset.Len(); i < n; i++ { - visit.function(visit.prog.MethodValue(mset.At(i))) + + // Historically, Program.RuntimeTypes used to include the type + // of any exported member of a package loaded from syntax that + // has a non-parameterized type, plus all types + // reachable from that type using reflection, even though + // these runtime types may not be required for them. + // + // Rather than break existing programs that rely on + // AllFunctions visiting extra methods that are unreferenced + // by IR and unreachable via reflection, we moved the logic + // here, unprincipled though it is. + // (See doc comment for better ideas.) + // + // Nonetheless, after the move, we no longer visit every + // method of any type recursively reachable from T, only the + // methods of T and *T themselves, and we only apply this to + // named types T, and not to the type of every exported + // package member. + exportedTypeHack := func(t *ir.Type) { + if isSyntactic(t.Package()) && + ast.IsExported(t.Name()) && + !types.IsInterface(t.Type()) { + // Consider only named types. + // (Ignore aliases and unsafe.Pointer.) + if named, ok := t.Type().(*types.Named); ok { + // Skip generic types. + if named.TypeParams() == nil { + methodsOf(named) // T + methodsOf(types.NewPointer(named)) // *T + } + } } } -} -func (visit *visitor) function(fn *ir.Function) { - if !visit.seen[fn] { - visit.seen[fn] = true - var buf [10]*ir.Value // avoid alloc in common case - for _, b := range fn.Blocks { - for _, instr := range b.Instrs { - for _, op := range instr.Operands(buf[:0]) { - if fn, ok := (*op).(*ir.Function); ok { - visit.function(fn) - } - } + for _, pkg := range prog.AllPackages() { + for _, mem := range pkg.Members { + switch mem := mem.(type) { + case *ir.Function: + // Visit all package-level declared functions. + // + // (This may include generic functions, which is + // inconsistent with the treatment of methods: + // we skip both generic methods, + // and methods of generic types.) + function(mem) + + case *ir.Type: + exportedTypeHack(mem) } } } + + // Visit all methods of types for which runtime types were + // materialized, as they are reachable through reflection. + for _, T := range prog.RuntimeTypes() { + methodsOf(T) + } + + return seen } // MainPackages returns the subset of the specified packages @@ -76,3 +155,12 @@ func MainPackages(pkgs []*ir.Package) []*ir.Package { } return mains } + +// TODO(adonovan): propose a principled API for this. One possibility +// is a new field, Package.SrcFunctions []*Function, which would +// contain the list of SrcFunctions described in point 2 of the +// AllFunctions doc comment, or nil if the package is not from syntax. +// But perhaps overloading nil vs empty slice is too subtle. +// +//go:linkname isSyntactic honnef.co/go/tools/go/ir.isSyntactic +func isSyntactic(pkg *ir.Package) bool diff --git a/tools/vendor/honnef.co/go/tools/go/ir/lift.go b/tools/vendor/honnef.co/go/tools/go/ir/lift.go index c2da8774a..61324b0c9 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/lift.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/lift.go @@ -7,7 +7,7 @@ package ir // This file defines the lifting pass which tries to "lift" Alloc // cells (new/local variables) into SSA registers, replacing loads // with the dominating stored value, eliminating loads and stores, and -// inserting φ- and σ-nodes as needed. +// inserting φ-nodes as needed. // Cited papers and resources: // @@ -43,7 +43,6 @@ package ir // Also see many other "TODO: opt" suggestions in the code. import ( - "encoding/binary" "fmt" "os" "slices" @@ -79,16 +78,8 @@ func (df domFrontier) add(u, v *BasicBlock) { // the DF -> IDF step. func (df domFrontier) build(fn *Function) { for _, b := range fn.Blocks { - preds := b.Preds[0:len(b.Preds):len(b.Preds)] - if b == fn.Exit { - for i, v := range fn.fakeExits.values { - if v { - preds = append(preds, fn.Blocks[i]) - } - } - } - if len(preds) >= 2 { - for _, p := range preds { + if len(b.Preds) >= 2 { + for _, p := range b.Preds { runner := p for runner != b.dom.idom { df.add(runner, b) @@ -105,42 +96,27 @@ func buildDomFrontier(fn *Function) domFrontier { return df } -type postDomFrontier BlockMap[[]*BasicBlock] - -func (rdf postDomFrontier) add(u, v *BasicBlock) { - rdf[u.Index] = append(rdf[u.Index], v) -} - -func (rdf postDomFrontier) build(fn *Function) { - for _, b := range fn.Blocks { - succs := b.Succs[0:len(b.Succs):len(b.Succs)] - if fn.fakeExits.Has(b) { - succs = append(succs, fn.Exit) +func removeInstr(refs []Instruction, instr Instruction) []Instruction { + s := refs + i := -1 + for j := range s { + if instr == s[j] { + i = j + break } - if len(succs) >= 2 { - for _, s := range succs { - runner := s - for runner != b.pdom.idom { - rdf.add(runner, b) - runner = runner.pdom.idom - } - } + } + if i == -1 { + return s + } + // Don't start copying elements until we find one to delete. + for j := i + 1; j < len(s); j++ { + if v := s[j]; v != instr { + s[i] = v + i++ } } -} - -func buildPostDomFrontier(fn *Function) postDomFrontier { - rdf := make(postDomFrontier, len(fn.Blocks)) - rdf.build(fn) - return rdf -} - -func removeInstr(refs []Instruction, instr Instruction) []Instruction { - return removeInstrsIf(refs, func(i Instruction) bool { return i == instr }) -} - -func removeInstrsIf(refs []Instruction, p func(Instruction) bool) []Instruction { - return slices.DeleteFunc(refs, p) + clear(s[i:]) // zero/nil out the obsolete elements, for GC + return s[:i] } func clearInstrs(instrs []Instruction) { @@ -163,8 +139,8 @@ func numberNodesPerBlock(f *Function) { } // lift replaces local and new Allocs accessed only with -// load/store by IR registers, inserting φ- and σ-nodes where necessary. -// The result is a program in pruned SSI form. +// load/store by IR registers, inserting φ-nodes where necessary. +// The result is a program in pruned SSA form. // // Preconditions: // - fn has no dead blocks (blockopt has run). @@ -190,10 +166,7 @@ func lift(fn *Function) bool { // // But we will start with the simplest correct code. var df domFrontier - var rdf postDomFrontier - var closure *closure var newPhis BlockMap[[]newPhi] - var newSigmas BlockMap[[]newSigma] // During this pass we will replace some BasicBlock.Instrs // (allocs, loads and stores) with nil, keeping a count in @@ -221,6 +194,19 @@ func lift(fn *Function) bool { // Number nodes, for liftable numberNodesPerBlock(fn) + heads := make(BlockMap[int], len(fn.Blocks)) + for i := range heads { + heads[i] = -1 + } + for _, b := range fn.Blocks { + for i, instr := range b.Instrs { + if _, ok := instr.(*Phi); !ok { + heads[b.Index] = i + break + } + } + } + for _, b := range fn.Blocks { b.gaps = 0 b.rundefers = 0 @@ -228,19 +214,14 @@ func lift(fn *Function) bool { for _, instr := range b.Instrs { switch instr := instr.(type) { case *Alloc: - if !liftable(instr, instructions) { + if !liftable(instr, instructions, heads) { instr.index = -1 continue } if numAllocs == 0 { df = buildDomFrontier(fn) - rdf = buildPostDomFrontier(fn) - if len(fn.Blocks) > 2 { - closure = transitiveClosure(fn) - } newPhis = make(BlockMap[[]newPhi], len(fn.Blocks)) - newSigmas = make(BlockMap[[]newSigma], len(fn.Blocks)) if debugLifting { title := false @@ -261,11 +242,11 @@ func lift(fn *Function) bool { usesDefer = true if eliminateDeferStack { // Clear _DeferStack and remove references to loads - if instr._DeferStack != nil { - if refs := instr._DeferStack.Referrers(); refs != nil { + if instr.DeferStack != nil { + if refs := instr.DeferStack.Referrers(); refs != nil { *refs = removeInstr(*refs, instr) } - instr._DeferStack = nil + instr.DeferStack = nil } } case *RunDefers: @@ -303,7 +284,7 @@ func lift(fn *Function) bool { for _, b := range fn.Blocks { for _, instr := range b.Instrs { if instr, ok := instr.(*Alloc); ok && instr.index >= 0 { - liftAlloc(closure, df, rdf, instr, newPhis, newSigmas) + liftAlloc(df, instr, newPhis) } } } @@ -316,12 +297,12 @@ func lift(fn *Function) bool { renaming := make([]Value, numAllocs) // Renaming. - rename(fn.Blocks[0], renaming, newPhis, newSigmas) + rename(fn.Blocks[0], renaming, newPhis) - simplifyPhisAndSigmas(newPhis, newSigmas) + simplifyPhis(newPhis) - // Eliminate dead φ- and σ-nodes. - markLiveNodes(fn.Blocks, newPhis, newSigmas) + // Eliminate dead φ-nodes. + markLiveNodes(fn.Blocks, newPhis) // Eliminate ssa:deferstack() call. if eliminateDeferStack { @@ -342,23 +323,6 @@ func lift(fn *Function) bool { if numAllocs > 0 { nps := newPhis[b.Index] head = make([]Instruction, 0, len(nps)) - for _, pred := range b.Preds { - nss := newSigmas[pred.Index] - idx := pred.succIndex(b) - for _, newSigma := range nss { - if sigma := newSigma.sigmas[idx]; sigma != nil && sigma.live { - head = append(head, sigma) - - // we didn't populate referrers before, as most - // sigma nodes will be killed - if refs := sigma.X.Referrers(); refs != nil { - *refs = append(*refs, sigma) - } - } else if sigma != nil { - sigma.block = nil - } - } - } for _, np := range nps { if np.phi.live { head = append(head, np.phi) @@ -388,7 +352,7 @@ func lift(fn *Function) bool { // that seems to only be the case ~1% of the time, which // doesn't seem worth the extra branch. - // Remove dead instructions, add phis and sigmas + // Remove dead instructions, add phis ns := len(b.Instrs) + j - b.gaps - rundefersToKill if ns <= cap(b.Instrs) { // b.Instrs has enough capacity to store all instructions @@ -458,21 +422,17 @@ func lift(fn *Function) bool { func hasDirectReferrer(instr Instruction) bool { for _, instr := range *instr.Referrers() { - switch instr.(type) { - case *Phi, *Sigma: - // ignore - default: + if _, ok := instr.(*Phi); !ok { return true } } return false } -func markLiveNodes(blocks []*BasicBlock, newPhis BlockMap[[]newPhi], newSigmas BlockMap[[]newSigma]) { - // Phis and sigmas may become dead due to optimization passes. We may also insert more nodes than strictly - // necessary, e.g. sigma nodes for constants, which will never be used. +func markLiveNodes(blocks []*BasicBlock, newPhis BlockMap[[]newPhi]) { + // Phis may become dead due to optimization passes. - // Phi and sigma nodes are considered live if a non-phi, non-sigma + // Phi nodes are considered live if a non-phi // node uses them. Once we find a node that is live, we mark all // of its operands as used, too. for _, npList := range newPhis { @@ -483,15 +443,6 @@ func markLiveNodes(blocks []*BasicBlock, newPhis BlockMap[[]newPhi], newSigmas B } } } - for _, npList := range newSigmas { - for _, np := range npList { - for _, sigma := range np.sigmas { - if sigma != nil && !sigma.live && hasDirectReferrer(sigma) { - markLiveSigma(sigma) - } - } - } - } // Existing φ-nodes due to && and || operators // are all considered live (see Go issue 19622). for _, b := range blocks { @@ -504,48 +455,18 @@ func markLiveNodes(blocks []*BasicBlock, newPhis BlockMap[[]newPhi], newSigmas B func markLivePhi(phi *Phi) { phi.live = true for _, rand := range phi.Edges { - switch rand := rand.(type) { - case *Phi: + if rand, ok := rand.(*Phi); ok { if !rand.live { markLivePhi(rand) } - case *Sigma: - if !rand.live { - markLiveSigma(rand) - } } } } -func markLiveSigma(sigma *Sigma) { - sigma.live = true - switch rand := sigma.X.(type) { - case *Phi: - if !rand.live { - markLivePhi(rand) - } - case *Sigma: - if !rand.live { - markLiveSigma(rand) - } - } -} - -// simplifyPhisAndSigmas removes duplicate phi and sigma nodes, -// and replaces trivial phis with non-phi alternatives. Phi +// simplifyPhis replaces trivial phis with non-phi alternatives. Phi // nodes where all edges are identical, or consist of only the phi // itself and one other value, may be replaced with the value. -func simplifyPhisAndSigmas(newPhis BlockMap[[]newPhi], newSigmas BlockMap[[]newSigma]) { - // temporary numbering of values used in phis so that we can build map keys - var id ID - for _, npList := range newPhis { - for _, np := range npList { - for _, edge := range np.phi.Edges { - edge.setID(id) - id++ - } - } - } +func simplifyPhis(newPhis BlockMap[[]newPhi]) { // find all phis that are trivial and can be replaced with a // non-phi value. run until we reach a fixpoint, because replacing // a phi may make other phis trivial. @@ -554,7 +475,7 @@ func simplifyPhisAndSigmas(newPhis BlockMap[[]newPhi], newSigmas BlockMap[[]newS for _, npList := range newPhis { for _, np := range npList { if np.phi.live { - // we're reusing 'live' to mean 'dead' in the context of simplifyPhisAndSigmas + // we're reusing 'live' to mean 'dead' in the context of simplifyPhis continue } if r, ok := isUselessPhi(np.phi); ok { @@ -567,84 +488,6 @@ func simplifyPhisAndSigmas(newPhis BlockMap[[]newPhi], newSigmas BlockMap[[]newS } } } - - // Replace duplicate sigma nodes with a single node. These nodes exist when multiple allocs get replaced with the - // same dominating store. - for _, sigmaList := range newSigmas { - primarySigmas := map[struct { - succ int - v Value - }]*Sigma{} - for _, sigmas := range sigmaList { - for succ, sigma := range sigmas.sigmas { - if sigma == nil { - continue - } - if sigma.live { - // we're reusing 'live' to mean 'dead' in the context of simplifyPhisAndSigmas - continue - } - key := struct { - succ int - v Value - }{succ, sigma.X} - if alt, ok := primarySigmas[key]; ok { - replaceAll(sigma, alt) - sigma.live = true - changed = true - } else { - primarySigmas[key] = sigma - } - } - } - } - - // Replace duplicate phi nodes with a single node. As far as we know, these duplicate nodes only ever exist - // because of the previous sigma deduplication. - keyb := make([]byte, 0, 4*8) - for _, npList := range newPhis { - primaryPhis := map[string]*Phi{} - for _, np := range npList { - if np.phi.live { - continue - } - if n := len(np.phi.Edges) * 8; cap(keyb) >= n { - keyb = keyb[:n] - } else { - keyb = make([]byte, n, n*2) - } - for i, e := range np.phi.Edges { - binary.LittleEndian.PutUint64(keyb[i*8:i*8+8], uint64(e.ID())) - } - if alt, ok := primaryPhis[string(keyb)]; ok { - replaceAll(np.phi, alt) - np.phi.live = true - changed = true - } else { - primaryPhis[string(keyb)] = np.phi - } - } - } - - } - - for _, npList := range newPhis { - for _, np := range npList { - np.phi.live = false - for _, edge := range np.phi.Edges { - edge.setID(0) - } - } - } - - for _, sigmaList := range newSigmas { - for _, sigmas := range sigmaList { - for _, sigma := range sigmas.sigmas { - if sigma != nil { - sigma.live = false - } - } - } } } @@ -724,109 +567,6 @@ func (s *BlockSet) Take() int { return -1 } -type closure struct { - span []uint32 - reachables BlockMap[interval] -} - -type interval uint32 - -const ( - flagMask = 1 << 31 - numBits = 20 - lengthBits = 32 - numBits - 1 - lengthMask = (1<>numBits - } else { - // large interval - i++ - start = uint32(inv & numMask) - end = uint32(r[i]) - } - if idx >= start && idx <= end { - return true - } - } - return false -} - -func (c closure) reachable(id int) []interval { - return c.reachables[c.span[id]:c.span[id+1]] -} - -func (c closure) walk(current *BasicBlock, b *BasicBlock, visited []bool) { - // TODO(dh): the 'current' argument seems to be unused - // TODO(dh): there's no reason for this to be a method - visited[b.Index] = true - for _, succ := range b.Succs { - if visited[succ.Index] { - continue - } - visited[succ.Index] = true - c.walk(current, succ, visited) - } -} - -func transitiveClosure(fn *Function) *closure { - reachable := make(BlockMap[bool], len(fn.Blocks)) - c := &closure{} - c.span = make([]uint32, len(fn.Blocks)+1) - - addInterval := func(start, end uint32) { - if l := end - start; l <= 1<= 0 { // store of zero to Alloc cell - // Replace dominated loads by the zero value. - renaming[instr.index] = nil - if debugLifting { - fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr) - } - // Delete the Alloc. - u.Instrs[i] = nil - u.gaps++ - } + worklist := []worklistEntry{{u_, renaming_}} + var freelist [][]Value - case *Store: - if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell - // Replace dominated loads by the stored value. - renaming[alloc.index] = instr.Val - if debugLifting { - fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n", - instr, instr.Val.Name()) - } - if refs := instr.Addr.Referrers(); refs != nil { - *refs = removeInstr(*refs, instr) - } - if refs := instr.Val.Referrers(); refs != nil { - *refs = removeInstr(*refs, instr) - } - // Delete the Store. - u.Instrs[i] = nil - u.gaps++ - } + for len(worklist) > 0 { + entry := worklist[len(worklist)-1] + worklist = worklist[:len(worklist)-1] + u, renaming := entry.u, entry.renaming - case *Load: - if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell - // In theory, we wouldn't be able to replace loads directly, because a loaded value could be used in - // different branches, in which case it should be replaced with different sigma nodes. But we can't - // simply defer replacement, either, because then later stores might incorrectly affect this load. - // - // To avoid doing renaming on _all_ values (instead of just loads and stores like we're doing), we make - // sure during code generation that each load is only used in one block. For example, in constant switch - // statements, where the tag is only evaluated once, we store it in a temporary and load it for each - // comparison, so that we have individual loads to replace. - // - // Because we only rename stores and loads, the end result will not contain sigma nodes for all - // constants. Some constants may be used directly, e.g. in comparisons such as 'x == 5'. We may still - // end up inserting dead sigma nodes in branches, but these will never get used in renaming and will be - // cleaned up when we remove dead phis and sigmas. - newval := renamed(u.Parent(), renaming, alloc) - if debugLifting { - fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n", - instr.Name(), instr, newval) - } - replaceAll(instr, newval) - u.Instrs[i] = nil - u.gaps++ - } + // Each φ-node becomes the new name for its associated Alloc. + for _, np := range newPhis[u.Index] { + phi := np.phi + alloc := np.alloc + renaming[alloc.index] = phi + } - case *DebugRef: - if x, ok := instr.X.(*Alloc); ok && x.index >= 0 { - if instr.IsAddr { - instr.X = renamed(u.Parent(), renaming, x) - instr.IsAddr = false + // Rename loads and stores of allocs. + for i, instr := range u.Instrs { + switch instr := instr.(type) { + case *Alloc: + if instr.index >= 0 { // store of zero to Alloc cell + // Replace dominated loads by the zero value. + renaming[instr.index] = nil + if debugLifting { + fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr) + } + // Delete the Alloc. + u.Instrs[i] = nil + u.gaps++ + } - // Add DebugRef to instr.X's referrers. - if refs := instr.X.Referrers(); refs != nil { - *refs = append(*refs, instr) + case *Store: + if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell + // Replace dominated loads by the stored value. + renaming[alloc.index] = instr.Val + if debugLifting { + fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n", + instr, instr.Val.Name()) } - } else { - // A source expression denotes the address - // of an Alloc that was optimized away. - instr.X = nil + if refs := instr.Addr.Referrers(); refs != nil { + *refs = removeInstr(*refs, instr) + } + if refs := instr.Val.Referrers(); refs != nil { + *refs = removeInstr(*refs, instr) + } + // Delete the Store. + u.Instrs[i] = nil + u.gaps++ + } - // Delete the DebugRef. + case *Load: + if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell + newval := renamed(u.Parent(), renaming, alloc) + if debugLifting { + fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n", + instr.Name(), instr, newval) + } + // Replace all references to the loaded value by the dominating + // stored value. + replaceAll(instr, newval) u.Instrs[i] = nil u.gaps++ } + + case *debugRef: + if x, ok := instr.X.(*Alloc); ok && x.index >= 0 { + if instr.IsAddr { + instr.X = renamed(u.Parent(), renaming, x) + instr.IsAddr = false + + // Add DebugRef to instr.X's referrers. + if refs := instr.X.Referrers(); refs != nil { + *refs = append(*refs, instr) + } + } else { + // A source expression denotes the address + // of an Alloc that was optimized away. + instr.X = nil + + // Delete the DebugRef. + u.Instrs[i] = nil + u.gaps++ + } + } } } - } - // update all outgoing sigma nodes with the dominating store - for _, sigmas := range newSigmas[u.Index] { - for _, sigma := range sigmas.sigmas { - if sigma == nil { + // For each φ-node in a CFG successor, rename the edge. + for _, v := range u.Succs { + phis := newPhis[v.Index] + if len(phis) == 0 { continue } - sigma.X = renamed(u.Parent(), renaming, sigmas.alloc) - } - } - - // For each φ-node in a CFG successor, rename the edge. - for succi, v := range u.Succs { - phis := newPhis[v.Index] - if len(phis) == 0 { - continue - } - i := v.predIndex(u) - for _, np := range phis { - phi := np.phi - alloc := np.alloc - // if there's a sigma node, use it, else use the dominating value - var newval Value - for _, sigmas := range newSigmas[u.Index] { - if sigmas.alloc == alloc && sigmas.sigmas[succi] != nil { - newval = sigmas.sigmas[succi] - break + i := v.predIndex(u) + for _, np := range phis { + phi := np.phi + alloc := np.alloc + newval := renamed(u.Parent(), renaming, alloc) + if debugLifting { + fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n", + phi.Name(), u, v, i, alloc.Name(), newval.Name()) + } + phi.Edges[i] = newval + if prefs := newval.Referrers(); prefs != nil { + *prefs = append(*prefs, phi) } - } - if newval == nil { - newval = renamed(u.Parent(), renaming, alloc) - } - if debugLifting { - fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n", - phi.Name(), u, v, i, alloc.Name(), newval.Name()) - } - phi.Edges[i] = newval - if prefs := newval.Referrers(); prefs != nil { - *prefs = append(*prefs, phi) } } - } - // Continue depth-first recursion over domtree, pushing a - // fresh copy of the renaming map for each subtree. - r := make([]Value, len(renaming)) - for _, v := range u.dom.children { - copy(r, renaming) - - // on entry to a block, the incoming sigma nodes become the new values for their alloc - if idx := u.succIndex(v); idx != -1 { - for _, sigma := range newSigmas[u.Index] { - if sigma.sigmas[idx] != nil { - r[sigma.alloc.index] = sigma.sigmas[idx] - } + // Continue depth-first recursion over domtree, pushing a + // fresh copy of the renaming map for each subtree. + for _, v := range slices.Backward(u.dom.children) { + var r []Value + if len(freelist) == 0 { + r = make([]Value, len(renaming)) + } else { + r = freelist[len(freelist)-1] + freelist = freelist[:len(freelist)-1] } + copy(r, renaming) + worklist = append(worklist, worklistEntry{v, r}) } - rename(v, r, newPhis, newSigmas) + freelist = append(freelist, renaming) } - } func simplifyConstantCompositeValues(fn *Function) bool { @@ -1772,7 +1216,7 @@ func simplifyConstantCompositeValues(fn *Function) bool { } } if replaced { - replaceAll(cv, emitConst(fn, ac)) + replaceAll(cv, ac) killInstruction(cv) } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/lvalue.go b/tools/vendor/honnef.co/go/tools/go/ir/lvalue.go index 86eb4a5d1..502293c8e 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/lvalue.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/lvalue.go @@ -24,7 +24,7 @@ type lvalue interface { // An address is an lvalue represented by a true pointer. type address struct { - addr Value + addr Value // must have a pointer core type. expr ast.Expr // source syntax of the value (not address) [debug mode] } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/methods.go b/tools/vendor/honnef.co/go/tools/go/ir/methods.go index 082029da3..9ad8e4101 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/methods.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/methods.go @@ -10,235 +10,168 @@ import ( "fmt" "go/types" - "honnef.co/go/tools/analysis/lint" + "golang.org/x/tools/go/types/typeutil" + "honnef.co/go/tools/internal/xtools-internal/typesinternal" ) // MethodValue returns the Function implementing method sel, building -// wrapper methods on demand. It returns nil if sel denotes an -// abstract (interface) method. +// wrapper methods on demand. It returns nil if sel denotes an +// interface or generic method. // // Precondition: sel.Kind() == MethodVal. // // Thread-safe. // -// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) +// Acquires prog.methodsMu. func (prog *Program) MethodValue(sel *types.Selection) *Function { if sel.Kind() != types.MethodVal { panic(fmt.Sprintf("MethodValue(%s) kind != MethodVal", sel)) } T := sel.Recv() if types.IsInterface(T) { - return nil // abstract method + return nil // interface method or type parameter } + + if prog.isParameterized(T, sel.Type()) { + return nil // method on generic type or generic method + } + if prog.mode&LogSource != 0 { defer logStack("MethodValue %s %v", T, sel)() } - prog.methodsMu.Lock() - defer prog.methodsMu.Unlock() - - return prog.addMethod(prog.createMethodSet(T), sel) -} - -// LookupMethod returns the implementation of the method of type T -// identified by (pkg, name). It returns nil if the method exists but -// is abstract, and panics if T has no such method. -func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function { - sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name) - if sel == nil { - panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name))) - } - return prog.MethodValue(sel) -} + var b builder -// methodSet contains the (concrete) methods of a non-interface type. -type methodSet struct { - mapping map[string]*Function // populated lazily - complete bool // mapping contains all methods -} + m := func() *Function { + prog.methodsMu.Lock() + defer prog.methodsMu.Unlock() -// Precondition: !isInterface(T). -// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) -func (prog *Program) createMethodSet(T types.Type) *methodSet { - mset, ok := prog.methodSets.At(T) - if !ok { - mset = &methodSet{mapping: make(map[string]*Function)} - prog.methodSets.Set(T, mset) - } - return mset -} + // Get or create SSA method set. + mset, ok := prog.methodSets.At(T) + if !ok { + mset = &methodSet{mapping: make(map[string]*Function)} + prog.methodSets.Set(T, mset) + } -// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) -func (prog *Program) addMethod(mset *methodSet, sel *types.Selection) *Function { - if sel.Kind() == types.MethodExpr { - panic(sel) - } - id := sel.Obj().Id() - fn := mset.mapping[id] - if fn == nil { - obj := sel.Obj().(*types.Func) - - needsPromotion := len(sel.Index()) > 1 - needsIndirection := !isPointer(recvType(obj)) && isPointer(sel.Recv()) - if needsPromotion || needsIndirection { - fn = makeWrapper(prog, sel) + // Get or create SSA method. + id := sel.Obj().Id() + fn, ok := mset.mapping[id] + if !ok { + obj := sel.Obj().(*types.Func) + needsPromotion := len(sel.Index()) > 1 + needsIndirection := !isPointer(recvType(obj)) && isPointer(T) + if needsPromotion || needsIndirection { + fn = createWrapper(prog, toSelection(sel), nil) + fn.buildshared = b.shared() + b.enqueue(fn) + } else { + fn = prog.objectMethod(obj, nil, &b) + } + if fn.Signature.Recv() == nil { + panic(fn) + } + mset.mapping[id] = fn } else { - fn = prog.declaredFunc(obj) - } - if fn.Signature.Recv() == nil { - panic(fn) // missing receiver + b.waitForSharedFunction(fn) } - mset.mapping[id] = fn - } - return fn -} -// RuntimeTypes returns a new unordered slice containing all -// concrete types in the program for which a complete (non-empty) -// method set is required at run-time. -// -// Thread-safe. -// -// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) -func (prog *Program) RuntimeTypes() []types.Type { - prog.methodsMu.Lock() - defer prog.methodsMu.Unlock() + return fn + }() - var res []types.Type - prog.methodSets.Iterate(func(T types.Type, v *methodSet) { - if v.complete { - res = append(res, T) - } - }) - return res -} + b.iterate() -// declaredFunc returns the concrete function/method denoted by obj. -// Panic ensues if there is none. -func (prog *Program) declaredFunc(obj *types.Func) *Function { - if origin := obj.Origin(); origin != obj { - // Calling method on instantiated type, create a wrapper that calls the generic type's method - base := prog.packageLevelValue(origin) - return makeInstance(prog, base.(*Function), obj.Type().(*types.Signature), nil) - } else { - if v := prog.packageLevelValue(obj); v != nil { - return v.(*Function) - } - } - panic("no concrete method: " + obj.String()) + return m } -// needMethodsOf ensures that runtime type information (including the -// complete method set) is available for the specified type T and all -// its subcomponents. -// -// needMethodsOf must be called for at least every type that is an -// operand of some MakeInterface instruction, and for the type of -// every exported package member. -// -// Precondition: T is not a method signature (*Signature with Recv()!=nil). +// objectMethod returns the Function for a given method symbol. +// The symbol may be an instance of a generic function. It need not +// belong to an existing SSA package created by a call to +// prog.CreatePackage. // -// Thread-safe. (Called via emitConv from multiple builder goroutines.) +// objectMethod panics if the function is not a method. // -// TODO(adonovan): make this faster. It accounts for 20% of SSA build time. -// -// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) -func (prog *Program) needMethodsOf(T types.Type) { - prog.methodsMu.Lock() - prog.needMethods(T, false) - prog.methodsMu.Unlock() -} - -// Precondition: T is not a method signature (*Signature with Recv()!=nil). -// Recursive case: skip => don't create methods for T. -// -// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) -func (prog *Program) needMethods(T types.Type, skip bool) { - // Each package maintains its own set of types it has visited. - if prevSkip, ok := prog.runtimeTypes.At(T); ok { - // needMethods(T) was previously called - if !prevSkip || skip { - return // already seen, with same or false 'skip' value - } - } - prog.runtimeTypes.Set(T, skip) - - tmset := prog.MethodSets.MethodSet(T) - - if !skip && !types.IsInterface(T) && tmset.Len() > 0 { - // Create methods of T. - mset := prog.createMethodSet(T) - if !mset.complete { - mset.complete = true - n := tmset.Len() - for i := range n { - prog.addMethod(mset, tmset.At(i)) - } - } +// Acquires prog.objectMethodsMu. +func (prog *Program) objectMethod(obj *types.Func, targs []types.Type, b *builder) *Function { + sig := obj.Type().(*types.Signature) + if sig.Recv() == nil { + panic("not a method: " + obj.String()) } - // Recursion over signatures of each method. - for method := range tmset.Methods() { - sig := method.Type().(*types.Signature) - prog.needMethods(sig.Params(), false) - prog.needMethods(sig.Results(), false) + // Instantiation of generic? + if orig := obj.Origin(); orig != obj || len(targs) > 0 { + return prog.objectMethod(orig, nil, b).instance(receiverTypeArgs(obj), targs, b) } - switch t := T.(type) { - case *types.Basic: - // nop - - case *types.Interface, *types.TypeParam: - // nop---handled by recursion over method set. - - case *types.Pointer: - prog.needMethods(t.Elem(), false) - - case *types.Slice: - prog.needMethods(t.Elem(), false) - - case *types.Chan: - prog.needMethods(t.Elem(), false) + // Belongs to a created package? + if fn := prog.FuncValue(obj); fn != nil { + return fn + } - case *types.Map: - prog.needMethods(t.Key(), false) - prog.needMethods(t.Elem(), false) + // Consult/update cache of methods created from types.Func. + prog.objectMethodsMu.Lock() + defer prog.objectMethodsMu.Unlock() + fn, ok := prog.objectMethods[obj] + if !ok { + fn = createFunction(prog, obj, obj.Name(), nil, nil, "") + fn.Synthetic = "from type information (on demand)" + fn.buildshared = b.shared() + b.enqueue(fn) - case *types.Signature: - if t.Recv() != nil { - panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv())) - } - prog.needMethods(t.Params(), false) - prog.needMethods(t.Results(), false) - - case *types.Named: - // A pointer-to-named type can be derived from a named - // type via reflection. It may have methods too. - prog.needMethods(types.NewPointer(t), false) - - // Consider 'type T struct{S}' where S has methods. - // Reflection provides no way to get from T to struct{S}, - // only to S, so the method set of struct{S} is unwanted, - // so set 'skip' flag during recursion. - prog.needMethods(t.Underlying(), true) - - case *types.Array: - prog.needMethods(t.Elem(), false) - - case *types.Struct: - for i, n := 0, t.NumFields(); i < n; i++ { - prog.needMethods(t.Field(i).Type(), false) + if prog.objectMethods == nil { + prog.objectMethods = make(map[*types.Func]*Function) } + prog.objectMethods[obj] = fn + } else { + b.waitForSharedFunction(fn) + } + return fn +} - case *types.Tuple: - for i, n := 0, t.Len(); i < n; i++ { - prog.needMethods(t.At(i).Type(), false) - } +// LookupMethod returns the implementation of the method of type T +// identified by (pkg, name). It returns nil if the method exists but +// is an interface method or generic method, and panics if T has no such method. +func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function { + sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name) + if sel == nil { + panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name))) + } + return prog.MethodValue(sel) +} - case *types.Alias: - prog.needMethods(types.Unalias(t), false) +// methodSet contains the (concrete) methods of a concrete type (non-interface, non-parameterized). +type methodSet struct { + mapping map[string]*Function // populated lazily +} - default: - lint.ExhaustiveTypeSwitch(T) +// RuntimeTypes returns a new unordered slice containing all types in +// the program for which a runtime type is required. +// +// A runtime type is required for any non-parameterized, non-interface +// type that is converted to an interface, or for any type (including +// interface types) derivable from one through reflection. +// +// The methods of such types may be reachable through reflection or +// interface calls even if they are never called directly. +// +// Thread-safe. +// +// Acquires prog.makeInterfaceTypesMu. +func (prog *Program) RuntimeTypes() []types.Type { + prog.makeInterfaceTypesMu.Lock() + defer prog.makeInterfaceTypesMu.Unlock() + + // Compute the derived types on demand, since many SSA clients + // never call RuntimeTypes, and those that do typically call + // it once (often within irutil.AllFunctions, which will + // eventually not use it; see Go issue #69291.) This + // eliminates the need to eagerly compute all the element + // types during SSA building. + var runtimeTypes []types.Type + add := func(t types.Type) { runtimeTypes = append(runtimeTypes, t) } + var set typeutil.Map // for de-duping identical types + for t := range prog.makeInterfaceTypes { + typesinternal.ForEachElement(&set, &prog.MethodSets, t, add) } + + return runtimeTypes } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/mode.go b/tools/vendor/honnef.co/go/tools/go/ir/mode.go index 8a87a605c..8f55bce01 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/mode.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/mode.go @@ -20,26 +20,30 @@ import ( type BuilderMode uint const ( - PrintPackages BuilderMode = 1 << iota // Print package inventory to stdout - PrintFunctions // Print function IR code to stdout - PrintSource // Print source code when printing function IR - LogSource // Log source locations as IR builder progresses - SanityCheckFunctions // Perform sanity checking of function bodies - NaiveForm // Build naïve IR form: don't replace local loads/stores with registers - GlobalDebug // Enable debug info for all packages - SplitAfterNewInformation // Split live range after we learn something new about a value + PrintPackages BuilderMode = 1 << iota // Print package inventory to stdout + PrintFunctions // Print function IR code to stdout + PrintSource // Print source code when printing function IR + LogSource // Log source locations as IR builder progresses + SanityCheckFunctions // Perform sanity checking of function bodies + NaiveForm // Build naïve IR form: don't replace local loads/stores with registers + BuildSerially // Build packages serially, not in parallel. + GlobalDebug // Enable debug info for all packages + BareInits // Build init functions without guards or calls to dependent inits + InstantiateGenerics // Instantiate generics functions (monomorphize) while building ) const BuilderModeDoc = `Options controlling the IR builder. -The value is a sequence of zero or more of these symbols: +The value is a sequence of zero or more of these letters: C perform sanity [C]hecking of the IR form. D include [D]ebug info for every function. P print [P]ackage inventory. F print [F]unction IR code. A print [A]ST nodes responsible for IR instructions S log [S]ource locations as IR builder progresses. +L build distinct packages seria[L]ly instead of in parallel. N build [N]aive IR form: don't replace local loads/stores with registers. -I Split live range after a value is used as slice or array index +I build bare [I]nit functions: no init guards or calls to dependent inits. +G instantiate [G]eneric function bodies via monomorphization ` func (m BuilderMode) String() string { @@ -65,9 +69,15 @@ func (m BuilderMode) String() string { if m&NaiveForm != 0 { buf.WriteByte('N') } - if m&SplitAfterNewInformation != 0 { + if m&BuildSerially != 0 { + buf.WriteByte('L') + } + if m&BareInits != 0 { buf.WriteByte('I') } + if m&InstantiateGenerics != 0 { + buf.WriteByte('G') + } return buf.String() } @@ -85,13 +95,17 @@ func (m *BuilderMode) Set(s string) error { case 'A': mode |= PrintSource case 'S': - mode |= LogSource + mode |= LogSource | BuildSerially case 'C': mode |= SanityCheckFunctions case 'N': mode |= NaiveForm + case 'L': + mode |= BuildSerially case 'I': - mode |= SplitAfterNewInformation + mode |= BareInits + case 'G': + mode |= InstantiateGenerics default: return fmt.Errorf("unknown BuilderMode option: %q", c) } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/print.go b/tools/vendor/honnef.co/go/tools/go/ir/print.go index f2468763a..c71518ad5 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/print.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/print.go @@ -24,17 +24,19 @@ import ( // Functions (including methods) and Globals use RelString and // all types are displayed with relType, so that only cross-package // references are package-qualified. -func relName(v Value, i Instruction) string { +func relName(v Value, i Node) string { if v == nil { return "" } var from *types.Package if i != nil { - from = i.Parent().pkg() + from = i.Parent().relPkg() } switch v := v.(type) { case Member: // *Function or *Global return v.RelString(from) + case *Const: + return v.RelString(from) } return v.Name() } @@ -66,41 +68,37 @@ func relString(m Member, from *types.Package) string { // It never appears in disassembly, which uses Value.Name(). func (v *Parameter) String() string { - from := v.Parent().pkg() - return fmt.Sprintf("Parameter <%s> {%s}", relType(v.Type(), from), v.name) + from := v.Parent().relPkg() + return fmt.Sprintf("parameter %s : %s", v.Object().Name(), relType(v.Type(), from)) } func (v *FreeVar) String() string { - from := v.Parent().pkg() - return fmt.Sprintf("FreeVar <%s> %s", relType(v.Type(), from), v.Name()) + from := v.Parent().relPkg() + return fmt.Sprintf("freevar %s : %s", v.Name(), relType(v.Type(), from)) } func (v *Builtin) String() string { - return fmt.Sprintf("Builtin %s", v.Name()) + return fmt.Sprintf("builtin %s", v.Name()) } // Instruction.String() func (v *Alloc) String() string { - from := v.Parent().pkg() - storage := "Stack" + op := "local" if v.Heap { - storage = "Heap" + op = "new" } - return fmt.Sprintf("%sAlloc <%s>", storage, relType(v.Type(), from)) -} - -func (v *Sigma) String() string { - from := v.Parent().pkg() - s := fmt.Sprintf("Sigma <%s> [b%d] %s", relType(v.Type(), from), v.From.Index, v.X.Name()) - return s + from := v.Parent().relPkg() + return fmt.Sprintf("%s %s (%s)", op, relType(deref(v.Type()), from), v.Comment()) } func (v *Phi) String() string { var b bytes.Buffer - fmt.Fprintf(&b, "Phi <%s>", v.Type()) + b.WriteString("phi [") for i, edge := range v.Edges { - b.WriteString(" ") + if i > 0 { + b.WriteString(", ") + } // Be robust against malformed CFG. if v.block == nil { b.WriteString("??") @@ -110,39 +108,40 @@ func (v *Phi) String() string { if i < len(v.block.Preds) { block = v.block.Preds[i].Index } - fmt.Fprintf(&b, "%d:", block) + fmt.Fprintf(&b, "%d: ", block) edgeVal := "" // be robust if edge != nil { edgeVal = relName(edge, v) } b.WriteString(edgeVal) } + b.WriteString("]") + if v.Comment() != "" { + b.WriteString(" #") + b.WriteString(v.Comment()) + } return b.String() } func printCall(v *CallCommon, prefix string, instr Instruction) string { var b bytes.Buffer + b.WriteString(prefix) if !v.IsInvoke() { - if value, ok := instr.(Value); ok { - fmt.Fprintf(&b, "%s <%s> %s", prefix, relType(value.Type(), instr.Parent().pkg()), relName(v.Value, instr)) - } else { - fmt.Fprintf(&b, "%s %s", prefix, relName(v.Value, instr)) - } + b.WriteString(relName(v.Value, instr)) } else { - if value, ok := instr.(Value); ok { - fmt.Fprintf(&b, "%sInvoke <%s> %s.%s", prefix, relType(value.Type(), instr.Parent().pkg()), relName(v.Value, instr), v.Method.Name()) - } else { - fmt.Fprintf(&b, "%sInvoke %s.%s", prefix, relName(v.Value, instr), v.Method.Name()) - } + fmt.Fprintf(&b, "invoke %s.%s", relName(v.Value, instr), v.Method.Name()) } - for _, arg := range v.TypeArgs { - b.WriteString(" ") - b.WriteString(relType(arg, instr.Parent().pkg())) - } - for _, arg := range v.Args { - b.WriteString(" ") + b.WriteString("(") + for i, arg := range v.Args { + if i > 0 { + b.WriteString(", ") + } b.WriteString(relName(arg, instr)) } + if v.Signature().Variadic() { + b.WriteString("...") + } + b.WriteString(")") return b.String() } @@ -151,65 +150,97 @@ func (c *CallCommon) String() string { } func (v *Call) String() string { - return printCall(&v.Call, "Call", v) + return printCall(&v.Call, "", v) } func (v *BinOp) String() string { - return fmt.Sprintf("BinOp <%s> {%s} %s %s", relType(v.Type(), v.Parent().pkg()), v.Op.String(), relName(v.X, v), relName(v.Y, v)) + return fmt.Sprintf("%s %s %s", relName(v.X, v), v.Op.String(), relName(v.Y, v)) } func (v *UnOp) String() string { - return fmt.Sprintf("UnOp <%s> {%s} %s", relType(v.Type(), v.Parent().pkg()), v.Op.String(), relName(v.X, v)) + return fmt.Sprintf("%s%s", v.Op, relName(v.X, v)) } func (v *Load) String() string { - return fmt.Sprintf("Load <%s> %s", relType(v.Type(), v.Parent().pkg()), relName(v.X, v)) -} - -func (v *Copy) String() string { - return fmt.Sprintf("Copy <%s> %s", relType(v.Type(), v.Parent().pkg()), relName(v.X, v)) + return fmt.Sprintf("*%s", relName(v.X, v)) } func printConv(prefix string, v, x Value) string { - from := v.Parent().pkg() - return fmt.Sprintf("%s <%s> %s", + from := v.Parent().relPkg() + return fmt.Sprintf("%s %s <- %s (%s)", prefix, relType(v.Type(), from), + relType(x.Type(), from), relName(x, v.(Instruction))) } -func (v *ChangeType) String() string { return printConv("ChangeType", v, v.X) } -func (v *Convert) String() string { return printConv("Convert", v, v.X) } -func (v *ChangeInterface) String() string { return printConv("ChangeInterface", v, v.X) } -func (v *SliceToArrayPointer) String() string { return printConv("SliceToArrayPointer", v, v.X) } +func (v *ChangeType) String() string { return printConv("changetype", v, v.X) } +func (v *Convert) String() string { return printConv("convert", v, v.X) } +func (v *ChangeInterface) String() string { return printConv("change interface", v, v.X) } +func (v *SliceToArrayPointer) String() string { return printConv("slice to array pointer", v, v.X) } func (v *SliceToArray) String() string { return printConv("SliceToArray", v, v.X) } -func (v *MakeInterface) String() string { return printConv("MakeInterface", v, v.X) } +func (v *MakeInterface) String() string { return printConv("make", v, v.X) } + +func (v *MultiConvert) String() string { + from := v.Parent().relPkg() + + var b strings.Builder + b.WriteString(printConv("multiconvert", v, v.X)) + b.WriteString(" [") + for i, s := range termListOf(v.from) { + for j, d := range termListOf(v.to) { + if i != 0 || j != 0 { + b.WriteString(" | ") + } + fmt.Fprintf(&b, "%s <- %s", relTerm(d, from), relTerm(s, from)) + } + } + b.WriteString("]") + return b.String() +} func (v *MakeClosure) String() string { - from := v.Parent().pkg() var b bytes.Buffer - fmt.Fprintf(&b, "MakeClosure <%s> %s", relType(v.Type(), from), relName(v.Fn, v)) + fmt.Fprintf(&b, "make closure %s", relName(v.Fn, v)) if v.Bindings != nil { - for _, c := range v.Bindings { - b.WriteString(" ") + b.WriteString(" [") + for i, c := range v.Bindings { + if i > 0 { + b.WriteString(", ") + } b.WriteString(relName(c, v)) } + b.WriteString("]") } return b.String() } func (v *MakeSlice) String() string { - from := v.Parent().pkg() - return fmt.Sprintf("MakeSlice <%s> %s %s", + from := v.Parent().relPkg() + return fmt.Sprintf("make %s %s %s", relType(v.Type(), from), relName(v.Len, v), relName(v.Cap, v)) } func (v *Slice) String() string { - from := v.Parent().pkg() - return fmt.Sprintf("Slice <%s> %s %s %s %s", - relType(v.Type(), from), relName(v.X, v), relName(v.Low, v), relName(v.High, v), relName(v.Max, v)) + var b bytes.Buffer + b.WriteString("slice ") + b.WriteString(relName(v.X, v)) + b.WriteString("[") + if v.Low != nil { + b.WriteString(relName(v.Low, v)) + } + b.WriteString(":") + if v.High != nil { + b.WriteString(relName(v.High, v)) + } + if v.Max != nil { + b.WriteString(":") + b.WriteString(relName(v.Max, v)) + } + b.WriteString("]") + return b.String() } func (v *MakeMap) String() string { @@ -217,77 +248,69 @@ func (v *MakeMap) String() string { if v.Reserve != nil { res = relName(v.Reserve, v) } - from := v.Parent().pkg() - return fmt.Sprintf("MakeMap <%s> %s", relType(v.Type(), from), res) + from := v.Parent().relPkg() + return fmt.Sprintf("make %s %s", relType(v.Type(), from), res) } func (v *MakeChan) String() string { - from := v.Parent().pkg() - return fmt.Sprintf("MakeChan <%s> %s", relType(v.Type(), from), relName(v.Size, v)) + from := v.Parent().relPkg() + return fmt.Sprintf("make %s %s", relType(v.Type(), from), relName(v.Size, v)) } func (v *FieldAddr) String() string { - from := v.Parent().pkg() - // v.X.Type() might be a pointer to a type parameter whose core type is a pointer to a struct - st := deref(typeutil.CoreType(deref(v.X.Type()))).Underlying().(*types.Struct) // Be robust against a bad index. name := "?" - if 0 <= v.Field && v.Field < st.NumFields() { - name = st.Field(v.Field).Name() + if fld := fieldOf(deref(v.X.Type()), v.Field); fld != nil { + name = fld.Name() } - return fmt.Sprintf("FieldAddr <%s> [%d] (%s) %s", relType(v.Type(), from), v.Field, name, relName(v.X, v)) + return fmt.Sprintf("&%s.%s [#%d]", relName(v.X, v), name, v.Field) } func (v *Field) String() string { - st := typeutil.CoreType(v.X.Type()).Underlying().(*types.Struct) // Be robust against a bad index. name := "?" - if 0 <= v.Field && v.Field < st.NumFields() { - name = st.Field(v.Field).Name() + if fld := fieldOf(v.X.Type(), v.Field); fld != nil { + name = fld.Name() } - from := v.Parent().pkg() - return fmt.Sprintf("Field <%s> [%d] (%s) %s", relType(v.Type(), from), v.Field, name, relName(v.X, v)) + return fmt.Sprintf("%s.%s [#%d]", relName(v.X, v), name, v.Field) } func (v *IndexAddr) String() string { - from := v.Parent().pkg() - return fmt.Sprintf("IndexAddr <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v)) + return fmt.Sprintf("&%s[%s]", relName(v.X, v), relName(v.Index, v)) } func (v *Index) String() string { - from := v.Parent().pkg() - return fmt.Sprintf("Index <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v)) + return fmt.Sprintf("%s[%s]", relName(v.X, v), relName(v.Index, v)) } func (v *MapLookup) String() string { - from := v.Parent().pkg() - return fmt.Sprintf("MapLookup <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v)) + return fmt.Sprintf("%s[%s]%s", relName(v.X, v), relName(v.Index, v), commaOk(v.CommaOk)) } func (v *StringLookup) String() string { - from := v.Parent().pkg() - return fmt.Sprintf("StringLookup <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v)) + return fmt.Sprintf("%s[%s]", relName(v.X, v), relName(v.Index, v)) } func (v *Range) String() string { - from := v.Parent().pkg() - return fmt.Sprintf("Range <%s> %s", relType(v.Type(), from), relName(v.X, v)) + return "range " + relName(v.X, v) } func (v *Next) String() string { - from := v.Parent().pkg() - return fmt.Sprintf("Next <%s> %s", relType(v.Type(), from), relName(v.Iter, v)) + return "next " + relName(v.Iter, v) } func (v *TypeAssert) String() string { - from := v.Parent().pkg() - return fmt.Sprintf("TypeAssert <%s> %s", relType(v.Type(), from), relName(v.X, v)) + from := v.Parent().relPkg() + return fmt.Sprintf("typeassert%s %s.(%s)", commaOk(v.CommaOk), relName(v.X, v), relType(v.AssertedType, from)) } func (v *Extract) String() string { - from := v.Parent().pkg() name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name() - return fmt.Sprintf("Extract <%s> [%d] (%s) %s", relType(v.Type(), from), v.Index, name, relName(v.Tuple, v)) + if name == "" { + return fmt.Sprintf("extract %s #%d", relName(v.Tuple, v), v.Index) + } else { + return fmt.Sprintf("extract %s #%d (%s)", relName(v.Tuple, v), v.Index, name) + } } func (s *Jump) String() string { @@ -296,7 +319,7 @@ func (s *Jump) String() string { if s.block != nil && len(s.block.Succs) == 1 { block = s.block.Succs[0].Index } - str := fmt.Sprintf("Jump → b%d", block) + str := fmt.Sprintf("jump %d", block) if s.Comment() != "" { str = fmt.Sprintf("%s # %s", str, s.Comment()) } @@ -304,12 +327,7 @@ func (s *Jump) String() string { } func (s *Unreachable) String() string { - // Be robust against malformed CFG. - block := -1 - if s.block != nil && len(s.block.Succs) == 1 { - block = s.block.Succs[0].Index - } - return fmt.Sprintf("Unreachable → b%d", block) + return "Unreachable" } func (s *If) String() string { @@ -319,7 +337,7 @@ func (s *If) String() string { tblock = s.block.Succs[0].Index fblock = s.block.Succs[1].Index } - return fmt.Sprintf("If %s → b%d b%d", relName(s.Cond, s), tblock, fblock) + return fmt.Sprintf("if %s goto %d else %d", relName(s.Cond, s), tblock, fblock) } func (s *ConstantSwitch) String() string { @@ -337,79 +355,89 @@ func (s *ConstantSwitch) String() string { func (v *CompositeValue) String() string { var b bytes.Buffer - from := v.Parent().pkg() - fmt.Fprintf(&b, "CompositeValue <%s>", relType(v.Type(), from)) - if v.NumSet >= len(v.Values) { - // All values provided - fmt.Fprint(&b, " [all]") - } else if v.Bitmap.BitLen() == 0 { - // No values provided - fmt.Fprint(&b, " [none]") - } else { - // Some values provided - bits := fmt.Appendf(nil, "%0*b", len(v.Values), &v.Bitmap) - for i := 0; i < len(bits)/2; i++ { - o := len(bits) - 1 - i - bits[i], bits[o] = bits[o], bits[i] + fmt.Fprint(&b, "{") + first := true + + var printValue func(idx int, vv Value) + switch typ := typeutil.CoreType(v.typ).(type) { + case *types.Struct: + printValue = func(idx int, vv Value) { + fieldName := typ.Field(idx).Name() + fmt.Fprintf(&b, "%s: %s", fieldName, relName(vv, v)) } - fmt.Fprintf(&b, " [%s]", bits) + case *types.Array: + if v.NumSet >= len(v.Values) { + printValue = func(idx int, vv Value) { + fmt.Fprint(&b, relName(vv, v)) + } + } else { + // This is currently unreachable as we don't use CompositeValue for + // incomplete arrays. + printValue = func(idx int, vv Value) { + fmt.Fprintf(&b, "%d: %s", idx, relName(vv, v)) + } + } + default: + panic(fmt.Sprintf("internal error: unexpected type %T (%v)", typ, typ)) } - for _, vv := range v.Values { - fmt.Fprintf(&b, " %s", relName(vv, v)) + for i, vv := range v.Values { + if !first { + fmt.Fprint(&b, ", ") + } + first = false + printValue(i, vv) } + fmt.Fprint(&b, "}") return b.String() } func (s *TypeSwitch) String() string { - from := s.Parent().pkg() var b bytes.Buffer - fmt.Fprintf(&b, "TypeSwitch <%s> %s", relType(s.typ, from), relName(s.Tag, s)) + fmt.Fprintf(&b, "switch %s.(type)", relName(s.Tag, s)) for _, cond := range s.Conds { - fmt.Fprintf(&b, " %q", relType(cond, s.block.parent.pkg())) + fmt.Fprintf(&b, " %s", relType(cond, s.block.parent.relPkg())) } return b.String() } func (s *Go) String() string { - return printCall(&s.Call, "Go", s) + return printCall(&s.Call, "go ", s) } func (s *Panic) String() string { - // Be robust against malformed CFG. - block := -1 - if s.block != nil && len(s.block.Succs) == 1 { - block = s.block.Succs[0].Index - } - return fmt.Sprintf("Panic %s → b%d", relName(s.X, s), block) + return "panic " + relName(s.X, s) } func (s *Return) String() string { var b bytes.Buffer - b.WriteString("Return") - for _, r := range s.Results { - b.WriteString(" ") + b.WriteString("return") + for i, r := range s.Results { + if i == 0 { + b.WriteString(" ") + } else { + b.WriteString(", ") + } b.WriteString(relName(r, s)) } return b.String() } func (*RunDefers) String() string { - return "RunDefers" + return "rundefers" } func (s *Send) String() string { - return fmt.Sprintf("Send %s %s", relName(s.Chan, s), relName(s.X, s)) + return fmt.Sprintf("send %s <- %s", relName(s.Chan, s), relName(s.X, s)) } func (recv *Recv) String() string { - from := recv.Parent().pkg() - return fmt.Sprintf("Recv <%s> %s", relType(recv.Type(), from), relName(recv.Chan, recv)) + return fmt.Sprintf("<-%s", relName(recv.Chan, recv)) } func (s *Defer) String() string { - prefix := "Defer " - if s._DeferStack != nil { - prefix += "[" + relName(s._DeferStack, s) + "] " + prefix := "defer " + if s.DeferStack != nil { + prefix += "[" + relName(s.DeferStack, s) + "] " } c := printCall(&s.Call, prefix, s) return c @@ -432,26 +460,24 @@ func (s *Select) String() string { } non := "" if !s.Blocking { - non = "Non" + non = "non" } - from := s.Parent().pkg() - return fmt.Sprintf("Select%sBlocking <%s> [%s]", non, relType(s.Type(), from), b.String()) + return fmt.Sprintf("select %sblocking [%s]", non, b.String()) } func (s *Store) String() string { - return fmt.Sprintf("Store {%s} %s %s", - s.Val.Type(), relName(s.Addr, s), relName(s.Val, s)) + return fmt.Sprintf("*%s = %s", relName(s.Addr, s), relName(s.Val, s)) } func (s *BlankStore) String() string { - return fmt.Sprintf("BlankStore %s", relName(s.Val, s)) + return fmt.Sprintf("_ = %s", relName(s.Val, s)) } func (s *MapUpdate) String() string { - return fmt.Sprintf("MapUpdate %s %s %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s)) + return fmt.Sprintf("%s[%s] = %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s)) } -func (s *DebugRef) String() string { +func (s *debugRef) String() string { p := s.Parent().Prog.Fset.Position(s.Pos()) var descr any if s.object != nil { @@ -513,27 +539,16 @@ func WritePackage(buf *bytes.Buffer, p *Package) { case *Global: fmt.Fprintf(buf, " var %-*s %s\n", - maxname, name, relType(mem.Type().(*types.Pointer).Elem(), from)) + maxname, name, relType(deref(mem.Type()), from)) } } fmt.Fprintf(buf, "\n") } -func (v *MultiConvert) String() string { - from := v.Parent().Pkg.Pkg - - var b strings.Builder - b.WriteString(printConv("MultiConvert", v, v.X)) - b.WriteString(" [") - for i, s := range v.from.Terms { - for j, d := range v.to.Terms { - if i != 0 || j != 0 { - b.WriteString(" | ") - } - fmt.Fprintf(&b, "%s -> %s", relTerm(s, from), relTerm(d, from)) - } +func commaOk(x bool) string { + if x { + return ",ok" } - b.WriteString("]") - return b.String() + return "" } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/sanity.go b/tools/vendor/honnef.co/go/tools/go/ir/sanity.go index 5663b4cf2..ab379ed8f 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/sanity.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/sanity.go @@ -94,13 +94,6 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { switch instr := instr.(type) { case *If, *Jump, *Return, *Panic, *Unreachable, *ConstantSwitch: s.errorf("control flow instruction not at end of block") - case *Sigma: - if idx > 0 { - prev := s.block.Instrs[idx-1] - if _, ok := prev.(*Sigma); !ok { - s.errorf("Sigma instruction follows a non-Sigma: %T", prev) - } - } case *Phi: if idx == 0 { // It suffices to apply this check to just the first phi node. @@ -110,18 +103,20 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { } else { prev := s.block.Instrs[idx-1] switch prev.(type) { - case *Phi, *Sigma: + case *Phi: default: - s.errorf("Phi instruction follows a non-Phi, non-Sigma: %T", prev) + s.errorf("Phi instruction follows a non-Phi: %T", prev) } } if ne, np := len(instr.Edges), len(s.block.Preds); ne != np { s.errorf("phi node has %d edges but %d predecessors", ne, np) - } else { for i, e := range instr.Edges { if e == nil { s.errorf("phi node '%s' has no value for edge #%d from %s", instr.Comment(), i, s.block.Preds[i]) + } else if !types.Identical(instr.typ, e.Type()) { + s.errorf("phi node '%s' has a different type (%s) for edge #%d from %s (%s)", + instr.Comment(), instr.Type(), i, s.block.Preds[i], e.Type()) } } } @@ -133,19 +128,21 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { case *BinOp: case *Call: + if common := instr.Call; common.IsInvoke() { + if !types.IsInterface(common.Value.Type()) { + s.errorf("invoke on %s (%s) which is not an interface type (or type param)", common.Value, common.Value.Type()) + } + } case *ChangeInterface: case *ChangeType: case *SliceToArrayPointer: case *SliceToArray: case *Convert: - tsetInstrX := typeutil.NewTypeSet(instr.X.Type().Underlying()) - tsetInstr := typeutil.NewTypeSet(instr.Type().Underlying()) - ok1 := tsetInstr.Any(func(term *types.Term) bool { _, ok := term.Type().Underlying().(*types.Basic); return ok }) - ok2 := tsetInstrX.Any(func(term *types.Term) bool { _, ok := term.Type().Underlying().(*types.Basic); return ok }) - if !ok1 && !ok2 { - s.errorf("convert %s -> %s: at least one type set must contain basic type", instr.X.Type(), instr.Type()) + if from := instr.X.Type(); !isBasicConvTypes(from) { + if to := instr.Type(); !isBasicConvTypes(to) { + s.errorf("convert %s -> %s: at least one type must be basic (or all basic, []byte, or []rune)", from, to) + } } - case *MultiConvert: case *Defer: case *Extract: @@ -163,7 +160,13 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { if numFree != numBind { s.errorf("MakeClosure has %d Bindings for function %s with %d free vars", numBind, instr.Fn, numFree) - + } else { + for i, fv := range instr.Fn.(*Function).FreeVars { + if !types.Identical(instr.Bindings[i].Type(), fv.Type()) { + s.errorf("MakeClosure binding %d for %s has type %s, expected %s", + i, fv.Name(), instr.Bindings[i].Type(), fv.Type()) + } + } } if recv := instr.Type().(*types.Signature).Recv(); recv != nil { s.errorf("MakeClosure's type includes receiver %s", recv.Type()) @@ -174,22 +177,44 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { case *MakeSlice: case *MapUpdate: case *Next: + rng, ok := instr.Iter.(*Range) + if !ok { + s.errorf("Next: Iter is %T, not *Range", instr.Iter) + } + var ek, ev types.Type + switch xt := typeutil.CoreType(rng.X.Type()).(type) { + case *types.Basic: + if types.Default(xt) != tString { + s.errorf("Next: basic operand of Next.Iter (Range) is %s, want string or untyped string", xt) + } + ek, ev = tInt, tRune + case *types.Map: + ek, ev = xt.Key(), xt.Elem() + } + + res := instr.Type().(*types.Tuple) // (ok bool, k K, v V), but K or V may be invalid if unused + if !types.Identical(res.At(1).Type(), ek) && res.At(1).Type() != tInvalid { + s.errorf("Next: key type %s does not match map key type %s", res.At(1).Type(), ek) + } + if !types.Identical(res.At(2).Type(), ev) && res.At(2).Type() != tInvalid { + s.errorf("Next: value type %s does not match map value type %s", res.At(2).Type(), ev) + } + case *Range: case *RunDefers: case *Select: case *Send: case *Slice: case *Store: + if !types.Identical(instr.Val.Type(), typeutil.CoreType(instr.Addr.Type()).(*types.Pointer).Elem()) { + s.errorf("Store: value type %s does not match address type %s", + instr.Val.Type(), instr.Addr.Type()) + } case *TypeAssert: case *UnOp: - case *DebugRef: + case *debugRef: case *BlankStore: case *Load: - case *Parameter: - case *Const: - case *AggregateConst: - case *ArrayConst: - case *GenericConst: case *Recv: case *TypeSwitch: case *CompositeValue: @@ -209,6 +234,8 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { t := v.Type() if t == nil { s.errorf("no type: %s = %s", v.Name(), v) + } else if t == tDeferStack { + // not a proper type; ignore. } else if b, ok := t.Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 { if _, ok := v.(*Const); !ok { s.errorf("instruction has 'untyped' result: %s = %s : %s", v.Name(), v, t) @@ -255,18 +282,23 @@ func (s *sanity) checkFinalInstr(instr Instruction) { } case *Panic: - if nsuccs := len(s.block.Succs); nsuccs != 1 { - s.errorf("Panic-terminated block has %d successors; expected one", nsuccs) + if nsuccs := len(s.block.Succs); nsuccs != 0 { + s.errorf("Panic-terminated block has %d successors; expected none", nsuccs) return } case *Unreachable: - if nsuccs := len(s.block.Succs); nsuccs != 1 { - s.errorf("Unreachable-terminated block has %d successors; expected one", nsuccs) + if nsuccs := len(s.block.Succs); nsuccs != 0 { + s.errorf("Unreachable-terminated block has %d successors; expected none", nsuccs) return } case *ConstantSwitch: + if nsuccs := len(s.block.Succs); nsuccs != len(instr.Conds) { + s.errorf("ConstantSwitch-terminated block has %d successors for %d constants", + nsuccs, len(instr.Conds)) + return + } default: s.errorf("non-control flow instruction at end of block") @@ -284,8 +316,9 @@ func (s *sanity) checkBlock(b *BasicBlock, index int) { } // Check all blocks are reachable. - // (The entry block is always implicitly reachable, the exit block may be unreachable.) - if index > 1 && len(b.Preds) == 0 { + // (The entry block is always implicitly reachable, + // as is the Recover block, if any.) + if (index > 0 && b != b.parent.Recover) && len(b.Preds) == 0 { s.warnf("unreachable block") if b.Instrs == nil { // Since this block is about to be pruned, @@ -352,7 +385,7 @@ func (s *sanity) checkBlock(b *BasicBlock, index int) { // Check that "untyped" types only appear on constant operands. if _, ok := (*op).(*Const); !ok { - if basic, ok := types.Unalias((*op).Type()).(*types.Basic); ok { + if basic, ok := (*op).Type().Underlying().(*types.Basic); ok { if basic.Info()&types.IsUntyped != 0 { s.errorf("operand #%d of %s is untyped: %s", i, instr, basic) } @@ -413,40 +446,139 @@ func (s *sanity) checkReferrerList(v Value) { } } +func (s *sanity) checkFunctionParams() { + signature := s.fn.Signature + params := s.fn.Params + + // startSigParams is the start of signature.Params() within params. + startSigParams := 0 + if signature.Recv() != nil { + startSigParams = 1 + } + + if startSigParams+signature.Params().Len() != len(params) { + s.errorf("function has %d parameters in signature but has %d after building", + startSigParams+signature.Params().Len(), len(params)) + return + } + + for i, param := range params { + var sigType types.Type + si := i - startSigParams + if si < 0 { + sigType = signature.Recv().Type() + } else { + sigType = signature.Params().At(si).Type() + } + + if !types.Identical(sigType, param.Type()) { + s.errorf("expect type %s in signature but got type %s in param %d", sigType, param.Type(), i) + } + } +} + +// checkTransientFields checks whether all transient fields of Function are cleared. +func (s *sanity) checkTransientFields() { + fn := s.fn + if fn.build != nil { + s.errorf("function transient field 'build' is not nil") + } + if fn.currentBlock != nil { + s.errorf("function transient field 'currentBlock' is not nil") + } + if fn.vars != nil { + s.errorf("function transient field 'vars' is not nil") + } + if fn.results != nil { + s.errorf("function transient field 'results' is not nil") + } + if fn.returnVars != nil { + s.errorf("function transient field 'returnVars' is not nil") + } + if fn.targets != nil { + s.errorf("function transient field 'targets' is not nil") + } + if fn.lblocks != nil { + s.errorf("function transient field 'lblocks' is not nil") + } + if fn.subst != nil { + s.errorf("function transient field 'subst' is not nil") + } + if fn.jump != nil { + s.errorf("function transient field 'jump' is not nil") + } + if fn.deferstack != nil { + s.errorf("function transient field 'deferstack' is not nil") + } + if fn.source != nil { + s.errorf("function transient field 'source' is not nil") + } + if fn.exits != nil { + s.errorf("function transient field 'exits' is not nil") + } + if fn.uniq != 0 { + s.errorf("function transient field 'uniq' is not zero") + } +} + func (s *sanity) checkFunction(fn *Function) bool { - // TODO(adonovan): check Function invariants: - // - check params match signature - // - check transient fields are nil - // - warn if any fn.Locals do not appear among block instructions. s.fn = fn + s.checkFunctionParams() + s.checkTransientFields() + + // TODO(taking): Sanity check origin, typeparams, and typeargs. if fn.Prog == nil { s.errorf("nil Prog") } var buf bytes.Buffer - _ = fn.String() // must not crash - _ = fn.RelString(fn.pkg()) // must not crash - WriteFunction(&buf, fn) // must not crash + _ = fn.String() // must not crash + _ = fn.RelString(fn.relPkg()) // must not crash + WriteFunction(&buf, fn) // must not crash // All functions have a package, except delegates (which are // shared across packages, or duplicated as weak symbols in a // separate-compilation model), and error.Error. if fn.Pkg == nil { - switch fn.Synthetic { - case SyntheticWrapper, SyntheticBound, SyntheticThunk, SyntheticGeneric: - default: - if !strings.HasSuffix(fn.name, "Error") { - s.errorf("nil Pkg") - } + if strings.HasPrefix(fn.Synthetic, "from type information (on demand)") || + strings.HasPrefix(fn.Synthetic, "wrapper ") || + strings.HasPrefix(fn.Synthetic, "bound ") || + strings.HasPrefix(fn.Synthetic, "thunk ") || + strings.HasSuffix(fn.name, "Error") || + strings.HasPrefix(fn.Synthetic, "instance ") || + strings.HasPrefix(fn.Synthetic, "instantiation ") || + fn.parent != nil && fn.parent.hasTypeArgs() /* anon fun in instance */ { + // ok + } else { + s.errorf("nil Pkg") } } - if syn, src := fn.Synthetic == 0, fn.source != nil; src != syn { - if _, ok := fn.source.(*ast.RangeStmt); !ok || fn.Synthetic != SyntheticRangeOverFuncYield { - // Only range-over-func yield functions are synthetic and have syntax + if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn { + if fn.hasTypeArgs() && fn.Prog.mode&InstantiateGenerics != 0 { + // ok (instantiation with InstantiateGenerics on) + } else if fn.hasTypeArgs() && fn.topLevelOrigin != nil { + // ok (we always have the syntax set for instantiation) + } else if _, rng := fn.syntax.(*ast.RangeStmt); rng && fn.Synthetic == "range-over-func yield" { + // ok (range-func-yields are both synthetic and keep syntax) + } else { s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn) } } + + // Build the set of valid referrers. + s.instrs = make(map[Instruction]struct{}) + + // instrs are the instructions that are present in the function. + for instr := range fn.instrs() { + s.instrs[instr] = unit{} + } + + // Check all Locals allocations appear in the function instruction. for i, l := range fn.Locals { + if _, present := s.instrs[l]; !present { + s.warnf("function doesn't contain Local alloc %s", l.Name()) + } + if l.Parent() != fn { s.errorf("Local %s at index %d has wrong parent", l.Name(), i) } @@ -454,13 +586,6 @@ func (s *sanity) checkFunction(fn *Function) bool { s.errorf("Local %s at index %d has Heap flag set", l.Name(), i) } } - // Build the set of valid referrers. - s.instrs = make(map[Instruction]struct{}) - for _, b := range fn.Blocks { - for _, instr := range b.Instrs { - s.instrs[instr] = struct{}{} - } - } for i, p := range fn.Params { if p.Parent() != fn { s.errorf("Param %s at index %d has wrong parent", p.Name(), i) @@ -473,7 +598,6 @@ func (s *sanity) checkFunction(fn *Function) bool { } if !types.Identical(p.Type(), sig.Params().At(j).Type()) { s.errorf("Param %s at index %d has wrong type (%s, versus %s in Signature)", p.Name(), i, p.Type(), sig.Params().At(j).Type()) - } } @@ -498,12 +622,18 @@ func (s *sanity) checkFunction(fn *Function) bool { } s.checkBlock(b, i) } + if fn.Recover != nil && fn.Blocks[fn.Recover.Index] != fn.Recover { + s.errorf("Recover block is not in Blocks slice") + } s.block = nil for i, anon := range fn.AnonFuncs { if anon.Parent() != fn { s.errorf("AnonFuncs[%d]=%s but %s.Parent()=%s", i, anon, anon, anon.Parent()) } + if i != int(anon.anonIdx) { + s.errorf("AnonFuncs[%d]=%s but %s.anonIdx=%d", i, anon, anon, anon.anonIdx) + } } s.fn = nil return !s.insane @@ -516,6 +646,19 @@ func sanityCheckPackage(pkg *Package) { if pkg.Pkg == nil { panic(fmt.Sprintf("Package %s has no Object", pkg)) } + if pkg.info != nil { + panic(fmt.Sprintf("package %s field 'info' is not cleared", pkg)) + } + if pkg.files != nil { + panic(fmt.Sprintf("package %s field 'files' is not cleared", pkg)) + } + if pkg.created != nil { + panic(fmt.Sprintf("package %s field 'created' is not cleared", pkg)) + } + if pkg.initVersion != nil { + panic(fmt.Sprintf("package %s field 'initVersion' is not cleared", pkg)) + } + _ = pkg.String() // must not crash for name, mem := range pkg.Members { diff --git a/tools/vendor/honnef.co/go/tools/go/ir/scc.go b/tools/vendor/honnef.co/go/tools/go/ir/scc.go new file mode 100644 index 000000000..d7f740857 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/go/ir/scc.go @@ -0,0 +1,106 @@ +package ir + +import ( + "math" +) + +type sccState struct { + fn *Function + index int + lows BlockMap[int] + F []int + sccs []*SCC +} + +func (s *sccState) scc(v *BasicBlock) { + // This is Tarjan's algorithm, with some optimizations from [1] applied. + // + // [1] R. E. Tarjan and U. Zwick, “Finding strong components using depth-first search,” Apr. 11, 2022, arXiv: arXiv:2201.07197. doi: 10.48550/arXiv.2201.07197. + vi := v.Index + lows := s.lows + + s.index += 2 + lows[vi] = s.index + + for _, w := range v.Succs { + wi := w.Index + + if lows[wi] == 0 { + s.scc(w) + } + + if lows[wi] < lows[vi] { + lows[vi] = lows[wi] | 1 + } + } + + if lows[vi]&1 == 0 { + leaderLow := lows[vi] + + scc := &SCC{ + Index: len(s.sccs), + } + F := s.F + for lows[F[len(F)-1]] >= leaderLow { + x := F[len(F)-1] + F = F[:len(F)-1] + + lows[x] = math.MaxInt + scc.Blocks = append(scc.Blocks, s.fn.Blocks[x]) + s.fn.Blocks[x].SCC = scc + } + s.F = F + + lows[vi] = math.MaxInt + scc.Blocks = append(scc.Blocks, v) + v.SCC = scc + s.sccs = append(s.sccs, scc) + } else { + s.F = append(s.F, vi) + } +} + +// buildSCCs computes the strongly connected components of fn's control flow graph. +func buildSCCs(fn *Function) { + n := len(fn.Blocks) + + if n == 0 { + return + } + + state := sccState{ + fn: fn, + index: 0, + // The +1 makes space for the sentinel value stored in F. + lows: make(BlockMap[int], n+1), + F: []int{n}, + } + + // All blocks are reachable from the entry block. + state.scc(fn.Blocks[0]) + + seen := make([]bool, len(state.sccs)) + for _, scc := range state.sccs { + clear(seen) + for _, b := range scc.Blocks { + for _, succ := range b.Succs { + if succ.SCC != b.SCC && !seen[succ.SCC.Index] { + b.SCC.Succs = append(b.SCC.Succs, succ.SCC) + succ.SCC.Preds = append(succ.SCC.Preds, b.SCC) + seen[succ.SCC.Index] = true + } + } + } + } + + fn.SCCs = state.sccs + + // This iterates over the SCCs in reverse topological order, i.e., "bottom + // up". This allows us to build up reachability sets. + for _, scc := range fn.SCCs { + scc.reachable.SetBit(&scc.reachable, scc.Index, 1) + for _, succ := range scc.Succs { + scc.reachable.Or(&scc.reachable, &succ.reachable) + } + } +} diff --git a/tools/vendor/honnef.co/go/tools/go/ir/source.go b/tools/vendor/honnef.co/go/tools/go/ir/source.go index 5e3ac28b8..bd05db73e 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/source.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/source.go @@ -80,14 +80,23 @@ func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function } case *ast.FuncDecl: - // Declared function/method. - fn := findNamedFunc(pkg, decl.Pos()) - if fn == nil && decl.Recv == nil && decl.Name.Name == "init" { + if decl.Recv == nil && decl.Name.Name == "init" { + // Explicit init() function. + for _, b := range pkg.init.Blocks { + for _, instr := range b.Instrs { + if instr, ok := instr.(*Call); ok { + if callee, ok := instr.Call.Value.(*Function); ok && callee.Pkg == pkg && callee.Pos() == decl.Name.NamePos { + return callee + } + } + } + } // Hack: return non-nil when IR is not yet // built so that HasEnclosingFunction works. return pkg.init } - return fn + // Declared function/method. + return findNamedFunc(pkg, decl.Name.NamePos) } } return nil // not in any function @@ -96,9 +105,25 @@ func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function // findNamedFunc returns the named function whose FuncDecl.Ident is at // position pos. func findNamedFunc(pkg *Package, pos token.Pos) *Function { - for _, fn := range pkg.Functions { - if fn.Pos() == pos { - return fn + // Look at all package members and method sets of named types. + // Not very efficient. + for _, mem := range pkg.Members { + switch mem := mem.(type) { + case *Function: + if mem.Pos() == pos { + return mem + } + case *Type: + mset := pkg.Prog.MethodSets.MethodSet(types.NewPointer(mem.Type())) + for i, n := 0, mset.Len(); i < n; i++ { + // Don't call Program.Method: avoid creating wrappers. + obj := mset.At(i).Obj().(*types.Func) + if obj.Pos() == pos { + // obj from MethodSet may not be the origin type. + m := obj.Origin() + return pkg.values[m].(*Function) + } + } } } return nil @@ -127,19 +152,9 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function { // EnclosingFunction to locate the Function, then ValueForExpr to find // the ir.Value.) func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) { - if f.debugInfo() { // (opt) - e = unparen(e) - for _, b := range f.Blocks { - for _, instr := range b.Instrs { - if ref, ok := instr.(*DebugRef); ok { - if ref.Expr == e { - return ref.X, ref.IsAddr - } - } - } - } - } - return + e = ast.Unparen(e) + entry := f.exprToValue[e] + return entry.v, entry.isAddr } // --- Lookup functions for source-level named entities (types.Objects) --- @@ -151,25 +166,26 @@ func (prog *Program) Package(obj *types.Package) *Package { return prog.packages[obj] } -// packageLevelValue returns the package-level value corresponding to -// the specified named object, which may be a package-level const -// (*Const), var (*Global) or func (*Function) of some package in -// prog. It returns nil if the object is not found. -func (prog *Program) packageLevelValue(obj types.Object) Value { +// packageLevelMember returns the package-level member corresponding +// to the specified symbol, which may be a package-level const +// (*NamedConst), var (*Global) or func/method (*Function) of some +// package in prog. +// +// It returns nil if the object belongs to a package that has not been +// created by prog.CreatePackage. +func (prog *Program) packageLevelMember(obj types.Object) Member { if pkg, ok := prog.packages[obj.Pkg()]; ok { return pkg.values[obj] } return nil } -// FuncValue returns the concrete Function denoted by the source-level -// named function obj, or nil if obj denotes an interface method. -// -// TODO(adonovan): check the invariant that obj.Type() matches the -// result's Signature, both in the params/results and in the receiver. +// FuncValue returns the SSA function or (non-interface) method +// denoted by the specified func symbol. It returns nil if the symbol +// denotes an interface method, or belongs to a package that was not +// created by prog.CreatePackage. func (prog *Program) FuncValue(obj *types.Func) *Function { - obj = obj.Origin() - fn, _ := prog.packageLevelValue(obj).(*Function) + fn, _ := prog.packageLevelMember(obj).(*Function) return fn } @@ -184,8 +200,8 @@ func (prog *Program) ConstValue(obj *types.Const) *Const { return NewConst(obj.Val(), obj.Type(), nil) } // Package-level named constant? - if v := prog.packageLevelValue(obj); v != nil { - return v.(*Const) + if v := prog.packageLevelMember(obj); v != nil { + return v.(*NamedConst).Value } return NewConst(obj.Val(), obj.Type(), nil) } @@ -244,18 +260,12 @@ func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (val } // Other ident? - for _, b := range fn.Blocks { - for _, instr := range b.Instrs { - if dr, ok := instr.(*DebugRef); ok { - if dr.Pos() == id.Pos() { - return dr.X, dr.IsAddr - } - } - } + if v, ok := fn.exprToValue[id]; ok { + return v.v, v.isAddr } // Defining ident of package-level var? - if v := prog.packageLevelValue(obj); v != nil { + if v := prog.packageLevelMember(obj); v != nil { return v.(*Global), true } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/ssa.go b/tools/vendor/honnef.co/go/tools/go/ir/ssa.go index cd8ae0120..e9277360e 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/ssa.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/ssa.go @@ -5,7 +5,7 @@ package ir // This package defines a high-level intermediate representation for -// Go programs using static single-information (SSI) form. +// Go programs using static single-assignment (SSA) form. import ( "fmt" @@ -13,10 +13,16 @@ import ( "go/constant" "go/token" "go/types" + "iter" "math/big" + "reflect" + "slices" + "strings" "sync" + "unsafe" "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/xtools-internal/typeparams" ) const ( @@ -30,18 +36,31 @@ type ID int // A Program is a partial or complete Go program converted to IR form. type Program struct { Fset *token.FileSet // position information for the files of this Program - PrintFunc string // create ir.html for function specified in PrintFunc imported map[string]*Package // all importable Packages, keyed by import path - packages map[*types.Package]*Package // all loaded Packages, keyed by object + packages map[*types.Package]*Package // all created Packages mode BuilderMode // set of mode bits for IR construction MethodSets typeutil.MethodSetCache // cache of type-checker's method-sets - methodsMu sync.Mutex // guards the following maps: - methodSets typeutil.Map[*methodSet] // maps type to its concrete methodSet - runtimeTypes typeutil.Map[bool] // types for which rtypes are needed - canon typeutil.Map[types.Type] // type canonicalization map + canon *canonizer // type canonicalization map + ctxt *types.Context // cache for type checking instantiations - noReturn func(*types.Func) bool + methodsMu sync.Mutex + methodSets typeutil.Map[*methodSet] // maps type to its concrete *methodSet + + // memoization of whether a type refers to type parameters + hasParamsMu sync.Mutex + hasParams typeparams.Free + + // set of concrete types used as MakeInterface operands + makeInterfaceTypesMu sync.Mutex + makeInterfaceTypes map[types.Type]unit // (may contain redundant identical types) + + // objectMethods is a memoization of objectMethod + // to avoid creation of duplicate methods from type information. + objectMethodsMu sync.Mutex + objectMethods map[*types.Func]*Function + + noReturn func(*types.Func) bool // (optional) predicate that decides whether a given call cannot return } // A Package is a single analyzed Go package containing Members for @@ -53,14 +72,14 @@ type Program struct { // initializer) and "init#%d", the nth declared init function, // and unspecified other things too. type Package struct { - Prog *Program // the owning program - Pkg *types.Package // the corresponding go/types.Package - Members map[string]Member // all package members keyed by name (incl. init and init#%d) - Functions []*Function // all functions, excluding anonymous ones - values map[types.Object]Value // package members (incl. types and methods), keyed by object - init *Function // Func("init"); the package's init function - debug bool // include full debug info in this package - printFunc string // which function to print in HTML form + Prog *Program // the owning program + Pkg *types.Package // the corresponding go/types.Package + Members map[string]Member // all package members keyed by name (incl. init and init#%d) + Functions []*Function // all functions, excluding anonymous ones + values map[types.Object]Member // mapping of package objects to members (incl. methods). Contains *NamedConst, *Global, *Function (values but not types) + init *Function // Func("init"); the package's init function + debug bool // include full debug info in this package + syntax bool // package was loaded from syntax // The following fields are set transiently, then cleared // after building. @@ -68,6 +87,7 @@ type Package struct { ninit int32 // number of init functions info *types.Info // package type information files []*ast.File // package ASTs + created []*Function // members created as a result of building this package (includes declared functions, wrappers) initVersion map[ast.Expr]string // goversion to use for each global var init expr } @@ -106,8 +126,6 @@ type NamedConst struct { // A Value is an IR value that can be referenced by an instruction. type Value interface { - setID(ID) - // Name returns the name of this value, and determines how // this Value appears when used as an operand of an // Instruction. @@ -122,17 +140,6 @@ type Value interface { // and may not even be unique within a function. Name() string - // ID returns the ID of this value. IDs are unique within a single - // function and are densely numbered, but may contain gaps. - // Values and other Instructions share the same ID space. - // Globally, values are identified by their addresses. However, - // IDs exist to facilitate efficient storage of mappings between - // values and data when analysing functions. - // - // NB: IDs are allocated late in the IR construction process and - // are not available to early stages of said process. - ID() ID - // If this value is an Instruction, String returns its // disassembled form; otherwise it returns unspecified // human-readable information about the Value, such as its @@ -193,15 +200,15 @@ type Instruction interface { // String returns the disassembled form of this value. // // Examples of Instructions that are Values: - // "BinOp {+} t1 t2" (BinOp) - // "Call len t1" (Call) + // "x + y" (BinOp) + // "len([])" (Call) // Note that the name of the Value is not printed. // // Examples of Instructions that are not Values: - // "Return t1" (Return) - // "Store {int} t2 t1" (Store) + // "return x" (Return) + // "*y = x" (Store) // - // (The separation of Value.Name() from Value.String() is useful + // (The separation Value.Name() from Value.String() is useful // for some analyses which distinguish the operation from the // value it defines, e.g., 'y = local int' is both an allocation // of memory 'local int' and a definition of a pointer y.) @@ -270,10 +277,7 @@ type Instruction interface { // use the more specific and informative Value or Instruction // interfaces where appropriate. type Node interface { - setID(ID) - // Common methods: - ID() ID String() string Source() ast.Node Pos() token.Pos @@ -284,39 +288,6 @@ type Node interface { Referrers() *[]Instruction // nil for non-Values } -type Synthetic int - -const ( - SyntheticLoadedFromExportData Synthetic = iota + 1 - SyntheticPackageInitializer - SyntheticThunk - SyntheticWrapper - SyntheticBound - SyntheticGeneric - SyntheticRangeOverFuncYield -) - -func (syn Synthetic) String() string { - switch syn { - case SyntheticLoadedFromExportData: - return "loaded from export data" - case SyntheticPackageInitializer: - return "package initializer" - case SyntheticThunk: - return "thunk" - case SyntheticWrapper: - return "wrapper" - case SyntheticBound: - return "bound" - case SyntheticGeneric: - return "generic" - case SyntheticRangeOverFuncYield: - return "range-over-func yield" - default: - return fmt.Sprintf("Synthetic(%d)", syn) - } -} - // Function represents the parameters, results, and code of a function // or method. // @@ -331,6 +302,11 @@ func (syn Synthetic) String() string { // the disassembly. // To iterate over the blocks in dominance order, use DomPreorder(). // +// Recover is an optional second entry point to which control resumes +// after a recovered panic. The Recover block may contain only a return +// statement, preceded by a load of the function's named return +// parameters, if any. +// // A nested function (Parent()!=nil) that refers to one or more // lexically enclosing local variables ("free variables") has FreeVars. // Such functions cannot be called directly but require a @@ -346,7 +322,7 @@ func (syn Synthetic) String() string { // // Pos() returns the declaring ast.FuncLit.Type.Func or the position // of the ast.FuncDecl.Name, if the function was explicit in the -// source. Synthetic wrappers, for which Synthetic != "", may share +// source. Synthetic wrappers, for which Synthetic != "", may share // the same position as the function they wrap. // Syntax.Pos() always returns the position of the declaring "func" token. // @@ -354,6 +330,7 @@ func (syn Synthetic) String() string { // the loop body is transformed into a synthetic anonymous function // that is passed as the yield argument in a call to the iterator. // In that case, Function.Source() is the ast.RangeStmt. +// and Function.Syntax is the ast.RangeStmt. // // Synthetic functions, for which Synthetic != "", are functions // that do not appear in the source AST. These include: @@ -361,23 +338,47 @@ func (syn Synthetic) String() string { // - thunks, // - bound functions, // - empty functions built from loaded type information, -// - yield functions created from range-over-func loops, and -// - package init functions. +// - yield functions created from range-over-func loops, +// - package init functions, and +// - instantiations of generic functions. +// +// Synthetic wrapper functions may share the same position +// as the function they wrap. // // Type() returns the function's Signature. +// +// A generic function is a function or method that has uninstantiated type +// parameters (TypeParams() != nil). Consider a hypothetical generic +// method, (*Map[K,V]).Get. It may be instantiated with all +// non-parameterized types as (*Map[string,int]).Get or with +// parameterized types as (*Map[string,U]).Get, where U is a type parameter. +// In both instantiations, Origin() refers to the instantiated generic +// method, (*Map[K,V]).Get, TypeParams() refers to the parameters [K,V] of +// the generic method. TypeArgs() refers to [string,U] or [string,int], +// respectively, and is nil in the generic method. type Function struct { - node - name string - object *types.Func // symbol for declared function (nil for FuncLit or synthetic init) - method *types.Selection // info about provenance of synthetic methods + object *types.Func // symbol for declared function (nil for FuncLit or synthetic init) + method *selection // info about provenance of synthetic methods; thunk => non-nil Signature *types.Signature - generics instanceWrapperMap + pos token.Pos + syntax ast.Node + + // source information + Synthetic string // provenance of synthetic function; "" for true source functions + info *types.Info // type annotations (if syntax != nil) + goversion string // Go version of syntax (NB: init is special) + + parent *Function // enclosing function if anon; nil if global + Pkg *Package // enclosing package; nil for shared funcs (wrappers and error.Error) + Prog *Program // enclosing program + + exprToValue map[ast.Expr]struct { + v Value + isAddr bool + } - Synthetic Synthetic // provenance of synthetic function; 0 for true source functions - parent *Function // enclosing function if anon; nil if global - Pkg *Package // enclosing package; nil for shared funcs (wrappers and error.Error) - Prog *Program // enclosing program + buildshared *task // wait for a shared function to be done building (may be nil if <=1 builder ever needs to wait) // These fields are populated only when the function body is built: @@ -385,123 +386,37 @@ type Function struct { FreeVars []*FreeVar // free variables whose values must be supplied by closure Locals []*Alloc // frame-allocated variables of this function Blocks []*BasicBlock // basic blocks of the function; nil => external - Exit *BasicBlock // The function's exit block + Recover *BasicBlock // optional; control transfers here after recovered panic AnonFuncs []*Function // anonymous functions (from FuncLit, RangeStmt) directly beneath this one referrers []Instruction // referring instructions (iff Parent() != nil) - - goversion string // Go version of syntax (NB: init is special) - - // uniq is not stored in functionBody because we need it after function building finishes - uniq int64 // source of unique ints within the source tree while building - - *functionBody -} - -type instanceWrapperMap struct { - h typeutil.Hasher - entries map[uint32][]struct { - key *types.TypeList - val *Function - } - len int -} - -func typeListIdentical(l1, l2 *types.TypeList) bool { - if l1.Len() != l2.Len() { - return false - } - for i := 0; i < l1.Len(); i++ { - t1 := l1.At(i) - t2 := l2.At(i) - if !types.Identical(t1, t2) { - return false - } - } - return true -} - -func (m *instanceWrapperMap) At(key *types.TypeList) *Function { - if m.entries == nil { - m.entries = make(map[uint32][]struct { - key *types.TypeList - val *Function - }) - m.h = typeutil.MakeHasher() - } - - var hash uint32 - for t := range key.Types() { - hash += m.h.Hash(t) - } - - for _, e := range m.entries[hash] { - if typeListIdentical(e.key, key) { - return e.val - } - } - return nil -} - -func (m *instanceWrapperMap) Set(key *types.TypeList, val *Function) { - if m.entries == nil { - m.entries = make(map[uint32][]struct { - key *types.TypeList - val *Function - }) - m.h = typeutil.MakeHasher() - } - - var hash uint32 - for t := range key.Types() { - hash += m.h.Hash(t) - } - for i, e := range m.entries[hash] { - if typeListIdentical(e.key, key) { - m.entries[hash][i].val = val - return - } - } - m.entries[hash] = append(m.entries[hash], struct { - key *types.TypeList - val *Function - }{key, val}) - m.len++ -} - -func (m *instanceWrapperMap) Len() int { - return m.len -} - -type constValue struct { - c Constant - idx int -} - -type functionBody struct { - // The following fields are set transiently during building, - // then cleared. - currentBlock *BasicBlock // where to emit code - vars map[*types.Var]Value // addresses of local variables - results []*Alloc // result allocations of the current function - returnVars []*types.Var // variables for a return statement. Either results or for range-over-func a parent's results - targets *targets // linked stack of branch targets - lblocks map[*types.Label]*lblock // labelled blocks - jump *types.Var // synthetic variable for the yield state (non-nil => range-over-func) - deferstack *types.Var // synthetic variable holding enclosing ssa:deferstack() - sourceFn *Function // nearest enclosing source function - exits []*exit // exits of the function that need to be resolved - - consts map[constKey]constValue - aggregateConsts typeutil.Map[[]*AggregateConst] - - wr *HTMLWriter - fakeExits BlockSet - blocksets [5]BlockSet - hasDefer bool - - // a contiguous block of instructions that will be used by blocks, - // to avoid making multiple allocations. - scratchInstructions []Instruction + anonIdx int32 // position of a nested function in parent's AnonFuncs. fn.Parent()!=nil => fn.Parent().AnonFunc[fn.anonIdx] == fn. + + SCCs []*SCC // SCC DAG in reverse topological order + + recvtypeparams *types.TypeParamList // receiver type parameters of this function. recvtypeparams.Len() > 0 => method on generic or instance of generic type + recvtypeargs []types.Type // type arguments that instantiated recvtypeparams. len(recvtypeargs) > 0 => method on instance of generic type + typeparams *types.TypeParamList // type parameters of this function. typeparams.Len() > 0 => generic or instance of generic function or method + typeargs []types.Type // type arguments that instantiated typeparams. len(typeargs) > 0 => instance of generic function or method + topLevelOrigin *Function // the origin function if this is an instance of a source function. nil if Parent()!=nil. + generic *generic // instances of this function, if generic + + // The following fields are cleared after building. + build buildFunc // algorithm to build function body (nil => built) + currentBlock *BasicBlock // where to emit code + vars map[*types.Var]Value // addresses of local variables + results []*Alloc // result allocations of the current function + returnVars []*types.Var // variables for a return statement. Either results or for range-over-func a parent's results + targets *targets // linked stack of branch targets + lblocks map[*types.Label]*lblock // labelled blocks + subst *subster // type parameter substitutions (if non-nil) + jump *types.Var // synthetic variable for the yield state (non-nil => range-over-func) + deferstack *types.Var // synthetic variable holding enclosing ssa:deferstack() + source *Function // nearest enclosing source function + exits []*exit // exits of the function that need to be resolved + uniq int64 // source of unique ints within the source tree while building + liftableBlockMap BlockMap[liftableBlockDesc] + + blocksets [4]BlockSet } // BasicBlock represents an IR basic block. @@ -529,12 +444,18 @@ type BasicBlock struct { parent *Function // parent function Instrs []Instruction // instructions in order Preds, Succs []*BasicBlock // predecessors and successors + SCC *SCC // strongly connected component succs2 [2]*BasicBlock // initial space for Succs dom domInfo // dominator tree info - pdom domInfo // post-dominator tree info - post int - gaps int // number of nil Instrs (transient) - rundefers int // number of rundefers (transient) + gaps int // number of nil Instrs (transient) + rundefers int // number of rundefers (transient) +} + +type SCC struct { + Index int + Blocks []*BasicBlock + Preds, Succs []*SCC + reachable big.Int } // Pure values ---------------------------------------- @@ -568,42 +489,46 @@ type FreeVar struct { // A Parameter represents an input parameter of a function. type Parameter struct { - register + node - name string - object *types.Var // non-nil + name string + object *types.Var // non-nil + typ types.Type + parent *Function + referrers []Instruction } -// A Const represents the value of a constant expression. -// -// The underlying type of a constant may be any boolean, numeric, or -// string type. In addition, a Const may represent the nil value of -// any reference type---interface, map, channel, pointer, slice, or -// function---but not "untyped nil". -// -// All source-level constant expressions are represented by a Const -// of the same type and value. +// A Const represents a value known at build time. // -// Value holds the exact value of the constant, independent of its -// Type(), using the same representation as package go/constant uses for -// constants, or nil for a typed nil value. +// Consts include true constants of boolean, numeric, and string types, as +// defined by the Go spec; these are represented by a non-nil Value field. // -// Pos() returns token.NoPos. +// Consts also include the "zero" value of any type, of which the nil values +// of various pointer-like types are a special case; these are represented +// by a nil Value field. // // Example printed form: // -// Const {42} -// Const {"test"} -// Const {(3 + 4i)} +// 42:int +// "hello":untyped string +// 3+4i:MyComplex +// nil:*int +// nil:[]string +// [3]int{}:[3]int +// struct{x string}{}:struct{x string} +// 0:interface{int|int64} +// nil:interface{bool|int} // no go/constant representation type Const struct { - register + node + typ types.Type Value constant.Value } type AggregateConst struct { - register + node + typ types.Type Values []Value } @@ -613,33 +538,20 @@ type CompositeValue struct { // Bitmap records which elements were explicitly provided. For example, [4]byte{2: x} would have a bitmap of 0010. Bitmap big.Int // The number of bits set in Bitmap + // TODO(dh): remove NumSet. we can just query the bitmap. NumSet int // Dense list of values in the composite literal. Omitted elements are filled in with zero values. Values []Value } -// TODO add the element's zero constant to ArrayConst -type ArrayConst struct { - register -} - -type GenericConst struct { - register -} - type Constant interface { - Instruction Value aConstant() RelString(*types.Package) string - equal(Constant) bool - setType(types.Type) } func (*Const) aConstant() {} func (*AggregateConst) aConstant() {} -func (*ArrayConst) aConstant() {} -func (*GenericConst) aConstant() {} // A Global is a named Value holding the address of a package-level // variable. @@ -712,60 +624,17 @@ type Builtin struct { // // Example printed form: // -// t1 = StackAlloc <*int> -// t2 = HeapAlloc <*int> (new) +// t0 = local int +// t1 = new int type Alloc struct { register Heap bool index int // dense numbering; for lifting } -var _ Instruction = (*Sigma)(nil) -var _ Value = (*Sigma)(nil) - -// The Sigma instruction represents an SSI σ-node, which splits values -// at branches in the control flow. -// -// Conceptually, σ-nodes exist at the end of blocks that branch and -// constitute parallel assignments to one value per destination block. -// However, such a representation would be awkward to work with, so -// instead we place σ-nodes at the beginning of branch targets. The -// From field denotes to which incoming edge the node applies. -// -// Within a block, all σ-nodes must appear before all non-σ nodes. -// -// Example printed form: -// -// t2 = Sigma [#0] t1 (x) -type Sigma struct { - register - From *BasicBlock - X Value - - live bool // used during lifting -} - -type CopyInfo uint64 - -const ( - CopyInfoUnspecified CopyInfo = 0 - CopyInfoNotNil CopyInfo = 1 << iota - CopyInfoNotZeroLength - CopyInfoNotNegative - CopyInfoSingleConcreteType - CopyInfoClosed -) - -type Copy struct { - register - X Value - Why Instruction - Info CopyInfo -} - // The Phi instruction represents an SSA φ-node, which combines values // that differ across incoming control-flow edges and yields a new -// value. Within a block, all φ-nodes must appear before all non-φ, non-σ +// value. Within a block, all φ-nodes must appear before all non-φ // nodes. // // Pos() returns the position of the && or || for short-circuit @@ -774,7 +643,7 @@ type Copy struct { // // Example printed form: // -// t3 = Phi 2:t1 4:t2 (x) +// t2 = phi [0: t0, 1: t1] type Phi struct { register Edges []Value // Edges[i] is value for Block().Preds[i] @@ -794,9 +663,9 @@ type Phi struct { // // Example printed form: // -// t3 = Call <()> println t1 t2 -// t4 = Call <()> foo$1 -// t6 = Invoke t5.String +// t2 = println(t0, t1) +// t4 = t3() +// t7 = invoke t5.Println(...t6) type Call struct { register Call CallCommon @@ -808,7 +677,7 @@ type Call struct { // // Example printed form: // -// t3 = BinOp {+} t2 t1 +// t1 = t0 + 1:int type BinOp struct { register // One of: @@ -826,7 +695,7 @@ type BinOp struct { // // Example printed form: // -// t2 = UnOp {^} t1 +// t0 = ^x type UnOp struct { register Op token.Token // One of: NOT SUB XOR ! - ^ @@ -856,15 +725,23 @@ type Load struct { // - between (possibly named) pointers to identical base types. // - from a bidirectional channel to a read- or write-channel, // optionally adding/removing a name. +// - between a type (t) and an instance of the type (tσ), i.e. +// Type() == σ(X.Type()) (or X.Type()== σ(Type())) where +// σ is the type substitution of Parent().TypeParams by +// Parent().TypeArgs. // // This operation cannot fail dynamically. // +// Type changes may to be to or from a type parameter (or both). All +// types in the type set of X.Type() have a value-preserving type +// change to all types in the type set of Type(). +// // Pos() returns the ast.CallExpr.Lparen, if the instruction arose // from an explicit conversion in the source. // // Example printed form: // -// t2 = ChangeType <*T> t1 +// t1 = changetype *int <- IntPtr (t0) type ChangeType struct { register X Value @@ -884,6 +761,10 @@ type ChangeType struct { // // A conversion may imply a type name change also. // +// Conversions may to be to or from a type parameter. All types in +// the type set of X.Type() can be converted to all types in the type +// set of Type(). +// // This operation cannot fail dynamically. // // Conversions of untyped string/number/bool constants to a specific @@ -894,7 +775,7 @@ type ChangeType struct { // // Example printed form: // -// t2 = Convert <[]byte> t1 +// t1 = convert []byte <- string (t0) type Convert struct { register X Value @@ -915,9 +796,8 @@ type Convert struct { // t1 = multiconvert D <- S (t0) [*[2]rune <- []rune | string <- []rune] type MultiConvert struct { register - X Value - from typeutil.TypeSet - to typeutil.TypeSet + X Value + from, to types.Type } // ChangeInterface constructs a value of one interface type from a @@ -931,7 +811,7 @@ type MultiConvert struct { // // Example printed form: // -// t2 = ChangeInterface t1 +// t1 = change interface interface{} <- I (t0) type ChangeInterface struct { register X Value @@ -943,9 +823,17 @@ type ChangeInterface struct { // Pos() returns the ast.CallExpr.Lparen, if the instruction arose // from an explicit conversion in the source. // +// Conversion may to be to or from a type parameter. All types in +// the type set of X.Type() must be a slice types that can be converted to +// all types in the type set of Type() which must all be pointer to array +// types. +// +// This operation can fail dynamically if the length of the slice is less +// than the length of the array. +// // Example printed form: // -// t2 = SliceToArrayPointer <*[4]byte> t1 +// t1 = slice to array pointer *[4]byte <- []byte (t0) type SliceToArrayPointer struct { register X Value @@ -980,7 +868,8 @@ type SliceToArray struct { // // Example printed form: // -// t2 = MakeInterface t1 +// t1 = make interface{} <- int (42:int) +// t1 = make Stringer <- t0 type MakeInterface struct { register X Value @@ -996,8 +885,8 @@ type MakeInterface struct { // // Example printed form: // -// t1 = MakeClosure foo$1 t1 t2 -// t5 = MakeClosure (T).foo$bound t4 +// t0 = make closure anon@1.2 [x y z] +// t1 = make closure bound$(main.I).add [i] type MakeClosure struct { register Fn Value // always a *Function @@ -1014,8 +903,8 @@ type MakeClosure struct { // // Example printed form: // -// t1 = MakeMap -// t2 = MakeMap t1 +// t1 = make map[string]int t0 +// t1 = make StringIntMap t0 type MakeMap struct { register Reserve Value // initial space reservation; nil => default @@ -1031,8 +920,8 @@ type MakeMap struct { // // Example printed form: // -// t3 = MakeChan t1 -// t4 = MakeChan t2 +// t1 = make chan int t0 +// t1 = make IntChan t0 type MakeChan struct { register Size Value // int; size of buffer; zero => synchronous. @@ -1053,8 +942,8 @@ type MakeChan struct { // // Example printed form: // -// t3 = MakeSlice <[]string> t1 t2 -// t4 = MakeSlice t1 t2 +// t2 = make []string t1 t0 +// t2 = make StringSlice t1 t0 type MakeSlice struct { register Len Value @@ -1076,7 +965,7 @@ type MakeSlice struct { // // Example printed form: // -// t4 = Slice <[]int> t3 t2 t1 +// t2 = slice t0[t1:] type Slice struct { register X Value // slice, string, or *array @@ -1094,15 +983,18 @@ type Slice struct { // Type() returns a (possibly named) *types.Pointer. // // Pos() returns the position of the ast.SelectorExpr.Sel for the -// field, if explicit in the source. +// field, if explicit in the source. For implicit selections, returns +// the position of the inducing explicit selection. If produced for a +// struct literal S{f: e}, it returns the position of the colon; for +// S{e} it returns the start of expression e. // // Example printed form: // -// t2 = FieldAddr <*int> [0] (X) t1 +// t1 = &t0.name [#1] type FieldAddr struct { register X Value // *struct - Field int // field is X.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Struct).Field(Field) + Field int // index into CoreType(CoreType(X.Type()).(*types.Pointer).Elem()).(*types.Struct).Fields } // The Field instruction yields the Field of struct X. @@ -1112,22 +1004,23 @@ type FieldAddr struct { // package-local identifiers and permit compact representations. // // Pos() returns the position of the ast.SelectorExpr.Sel for the -// field, if explicit in the source. +// field, if explicit in the source. For implicit selections, returns +// the position of the inducing explicit selection. // // Example printed form: // -// t2 = FieldAddr [0] (X) t1 +// t1 = t0.name [#1] type Field struct { register X Value // struct - Field int // index into X.Type().(*types.Struct).Fields + Field int // index into CoreType(X.Type()).(*types.Struct).Fields } // The IndexAddr instruction yields the address of the element at // index Index of collection X. Index is an integer expression. // -// The elements of maps and strings are not addressable; use StringLookup, MapLookup or -// MapUpdate instead. +// The elements of maps and strings are not addressable; use Lookup (map), +// Index (string), or MapUpdate instead. // // Dynamically, this instruction panics if X evaluates to a nil *array // pointer. @@ -1139,28 +1032,31 @@ type Field struct { // // Example printed form: // -// t3 = IndexAddr <*int> t2 t1 +// t2 = &t0[t1] type IndexAddr struct { register - X Value // slice or *array, + X Value // *array, slice or type parameter with types array, *array, or slice. Index Value // numeric index } -// The Index instruction yields element Index of array X. +// The Index instruction yields element Index of collection X, an array, +// string or type parameter containing an array, a string, a pointer to an, +// array or a slice. // // Pos() returns the ast.IndexExpr.Lbrack for the index operation, if // explicit in the source. // // Example printed form: // -// t3 = Index t2 t1 +// t2 = t0[t1] type Index struct { register - X Value // array + X Value // array, string or type parameter with types array, *array, slice, or string. Index Value // integer index } // The MapLookup instruction yields element Index of collection X, a map. +// Index is the appropriate key type. // // If CommaOk, the result is a 2-tuple of the value above and a // boolean indicating the result of a map membership test for the key. @@ -1170,8 +1066,8 @@ type Index struct { // // Example printed form: // -// t4 = MapLookup t3 t1 -// t6 = MapLookup <(string, bool)> t3 t2 +// t2 = t0[t1] +// t5 = t3[t4],ok type MapLookup struct { register X Value // map @@ -1238,8 +1134,8 @@ type SelectState struct { // // Example printed form: // -// t6 = SelectNonBlocking <(index int, ok bool, int)> [<-t4, t5<-t1] -// t11 = SelectBlocking <(index int, ok bool)> [] +// t3 = select nonblocking [<-t0, t1<-t2] +// t4 = select blocking [] type Select struct { register States []*SelectState @@ -1257,7 +1153,7 @@ type Select struct { // // Example printed form: // -// t2 = Range t1 +// t0 = range "hello":string type Range struct { register X Value // string or map @@ -1280,8 +1176,7 @@ type Range struct { // // Example printed form: // -// t5 = Next <(ok bool, k int, v rune)> t2 -// t5 = Next <(ok bool, k invalid type, v invalid type)> t2 +// t1 = next t0 type Next struct { register Iter Value @@ -1299,6 +1194,9 @@ type Next struct { // is AssertedType's zero value. The components of the pair must be // accessed using the Extract instruction. // +// If Underlying: tests whether interface value X has the underlying +// type AssertedType. +// // If AssertedType is a concrete type, TypeAssert checks whether the // dynamic type in interface X is equal to it, and if so, the result // of the conversion is a copy of the value in the interface. @@ -1313,16 +1211,17 @@ type Next struct { // Type() reflects the actual type of the result, possibly a // 2-types.Tuple; AssertedType is the asserted type. // -// Pos() returns the ast.CallExpr.Lparen if the instruction arose from -// an explicit T(e) conversion; the ast.TypeAssertExpr.Lparen if the -// instruction arose from an explicit e.(T) operation; or the -// ast.CaseClause.Case if the instruction arose from a case of a -// type-switch statement. +// Depending on the TypeAssert's purpose, Pos may return: +// - the ast.CallExpr.Lparen of an explicit T(e) conversion; +// - the ast.TypeAssertExpr.Lparen of an explicit e.(T) operation; +// - the ast.CaseClause.Case of a case of a type-switch statement; +// - the Ident(m).NamePos of an interface method value i.m +// (for which TypeAssert may be used to effect the nil check). // // Example printed form: // -// t2 = TypeAssert t1 -// t4 = TypeAssert <(value fmt.Stringer, ok bool)> t1 +// t1 = typeassert t0.(int) +// t3 = typeassert,ok t2.(T) type TypeAssert struct { register X Value @@ -1338,7 +1237,7 @@ type TypeAssert struct { // // Example printed form: // -// t7 = Extract [1] (ok) t4 +// t1 = extract t0 #1 type Extract struct { register Tuple Value @@ -1356,7 +1255,7 @@ type Extract struct { // // Example printed form: // -// Jump → b1 +// jump done type Jump struct { anInstruction } @@ -1365,15 +1264,12 @@ type Jump struct { // continue after the preceding function call because it terminates // the process. // -// The instruction acts as a control instruction, jumping to the exit -// block. However, this jump will never execute. -// // An Unreachable instruction must be the last instruction of its -// containing BasicBlock. +// containing BasicBlock, which must have no successors. // // Example printed form: // -// Unreachable → b1 +// Unreachable type Unreachable struct { anInstruction } @@ -1389,7 +1285,7 @@ type Unreachable struct { // // Example printed form: // -// If t2 → b1 b2 +// if t0 goto done else body type If struct { anInstruction Cond Value @@ -1429,8 +1325,8 @@ type TypeSwitch struct { // // Example printed form: // -// Return -// Return t1 t2 +// return +// return t0, t1 type Return struct { anInstruction Results []Value @@ -1447,7 +1343,7 @@ type Return struct { // // Example printed form: // -// RunDefers +// rundefers type RunDefers struct { anInstruction } @@ -1455,7 +1351,7 @@ type RunDefers struct { // The Panic instruction initiates a panic with value X. // // A Panic instruction must be the last instruction of its containing -// BasicBlock, which must have one successor, the exit block. +// BasicBlock, which must have no successors. // // NB: 'go panic(x)' and 'defer panic(x)' do not use this instruction; // they are treated as calls to a built-in function. @@ -1465,7 +1361,7 @@ type RunDefers struct { // // Example printed form: // -// Panic t1 +// panic t0 type Panic struct { anInstruction X Value // an interface{} @@ -1480,9 +1376,9 @@ type Panic struct { // // Example printed form: // -// Go println t1 -// Go t3 -// GoInvoke t4.Bar t2 +// go println(t0, t1) +// go t3() +// go invoke t5.Println(...t6) type Go struct { anInstruction Call CallCommon @@ -1491,10 +1387,10 @@ type Go struct { // The Defer instruction pushes the specified call onto a stack of // functions to be called by a RunDefers instruction or by a panic. // -// If _DeferStack != nil, it indicates the defer list that the defer is +// If DeferStack != nil, it indicates the defer list that the defer is // added to. Defer list values come from the Builtin function // ssa:deferstack. Calls to ssa:deferstack() produces the defer stack -// of the current function frame. _DeferStack allows for deferring into an +// of the current function frame. DeferStack allows for deferring into an // alternative function stack than the current function. // // See CallCommon for generic function call documentation. @@ -1503,15 +1399,13 @@ type Go struct { // // Example printed form: // -// Defer println t1 -// Defer t3 -// DeferInvoke t4.Bar t2 +// defer println(t0, t1) +// defer t3() +// defer invoke t5.Println(...t6) type Defer struct { anInstruction - Call CallCommon - _DeferStack Value // stack (from ssa:deferstack() intrinsic) onto which this function is pushed - - // TODO: Exporting _DeferStack and possibly making _DeferStack != nil awaits proposal https://github.com/golang/go/issues/66601. + Call CallCommon + DeferStack Value // stack of deferred functions (from ssa:deferstack() intrinsic) onto which this function is pushed } // The Send instruction sends X on channel Chan. @@ -1520,7 +1414,7 @@ type Defer struct { // // Example printed form: // -// Send t2 t1 +// send t0 <- t1 type Send struct { anInstruction Chan, X Value @@ -1556,7 +1450,7 @@ type Recv struct { // // Example printed form: // -// Store {int} t2 t1 +// *x = y type Store struct { anInstruction Addr Value @@ -1586,7 +1480,7 @@ type BlankStore struct { // // Example printed form: // -// MapUpdate t3 t1 t2 +// t0[t1] = t2 type MapUpdate struct { anInstruction Map Value @@ -1594,17 +1488,22 @@ type MapUpdate struct { Value Value } -// A DebugRef instruction maps a source-level expression Expr to the +// A debugRef instruction maps a source-level expression Expr to the // IR value X that represents the value (!IsAddr) or address (IsAddr) // of that expression. // -// DebugRef is a pseudo-instruction: it has no dynamic effect. +// debugRef is a pseudo-instruction: it has no dynamic effect. // // Pos() returns Expr.Pos(), the start position of the source-level // expression. This is not the same as the "designated" token as // documented at Value.Pos(). e.g. CallExpr.Pos() does not return the // position of the ("designated") Lparen token. // +// If Expr is an *ast.Ident denoting a var or func, Object() returns +// the object; though this information can be obtained from the type +// checker, including it here greatly facilitates debugging. +// For non-Ident expressions, Object() returns nil. +// // DebugRefs are generated only for functions built with debugging // enabled; see Package.SetDebugMode() and the GlobalDebug builder // mode flag. @@ -1622,7 +1521,7 @@ type MapUpdate struct { // ; *ast.CallExpr @ 102:9 is t5 // ; var x float64 @ 109:72 is x // ; address of *ast.CompositeLit @ 216:10 is t0 -type DebugRef struct { +type debugRef struct { anInstruction Expr ast.Expr // the referring expression (never *ast.ParenExpr) object types.Object // the identity of the source var/func @@ -1647,12 +1546,8 @@ type register struct { type node struct { source ast.Node - id ID } -func (n *node) setID(id ID) { n.id = id } -func (n node) ID() ID { return n.id } - func (n *node) setSource(source ast.Node) { n.source = source } func (n *node) Source() ast.Node { return n.source } @@ -1667,10 +1562,19 @@ func (n *node) Pos() token.Pos { // It provides the implementations of the Block and setBlock methods. type anInstruction struct { node + id ID block *BasicBlock // the basic block of this instruction comment string } +func (instr *anInstruction) setID(id ID) { + instr.id = id +} + +func (instr anInstruction) ID() ID { + return instr.id +} + func (instr anInstruction) Comment() string { return instr.comment } @@ -1706,16 +1610,17 @@ func (instr anInstruction) Comment() string { // // Example printed form: // -// t3 = Call <()> println t1 t2 -// Go t3 -// Defer t3 +// t2 = println(t0, t1) +// go t3() +// defer t5(...t6) // // 2. "invoke" mode: when Method is non-nil (IsInvoke), a CallCommon // represents a dynamically dispatched call to an interface method. // In this mode, Value is the interface value and Method is the -// interface's abstract method. Note: an abstract method may be -// shared by multiple interfaces due to embedding; Value.Type() -// provides the specific interface used for this call. +// interface's abstract method. The interface value may be a type +// parameter. Note: an interface method may be shared by multiple +// interfaces due to embedding; Value.Type() provides the specific +// interface used for this call. // // Value is implicitly supplied to the concrete method implementation // as the receiver parameter; in other words, Args[0] holds not the @@ -1723,18 +1628,18 @@ func (instr anInstruction) Comment() string { // // Example printed form: // -// t6 = Invoke t5.String -// GoInvoke t4.Bar t2 -// DeferInvoke t4.Bar t2 +// t1 = invoke t0.String() +// go invoke t3.Run(t2) +// defer invoke t4.Handle(...t5) // // For all calls to variadic functions (Signature().Variadic()), // the last element of Args is a slice. type CallCommon struct { - Value Value // receiver (invoke mode) or func value (call mode) - Method *types.Func // abstract method (invoke mode) - Args []Value // actual parameters (in static method call, includes receiver) - TypeArgs []types.Type - Results Value + Value Value // receiver (invoke mode) or func value (call mode) + Method *types.Func // interface method (invoke mode) + Args []Value // actual parameters (in static method call, includes receiver) + + source ast.Node } // IsInvoke returns true if this call has "invoke" (not "call") mode. @@ -1742,6 +1647,13 @@ func (c *CallCommon) IsInvoke() bool { return c.Method != nil } +func (c *CallCommon) Pos() token.Pos { + if c.source != nil { + return c.source.Pos() + } + return token.NoPos +} + // Signature returns the signature of the called function. // // For an "invoke"-mode call, the signature of the interface method is @@ -1801,6 +1713,10 @@ func (s *Call) Common() *CallCommon { return &s.Call } func (s *Defer) Common() *CallCommon { return &s.Call } func (s *Go) Common() *CallCommon { return &s.Call } +func (s *Call) setSource(source ast.Node) { s.source = source; s.Call.source = source } +func (s *Defer) setSource(source ast.Node) { s.source = source; s.Call.source = source } +func (s *Go) setSource(source ast.Node) { s.source = source; s.Call.source = source } + func (s *Call) Value() *Call { return s } func (s *Defer) Value() *Call { return nil } func (s *Go) Value() *Call { return nil } @@ -1846,7 +1762,187 @@ func (v *Function) Referrers() *[]Instruction { return nil } -func (v *Parameter) Object() types.Object { return v.object } +// Returns returns an iterator over all blocks containing return statements. +func (v *Function) Returns() iter.Seq[*BasicBlock] { + return func(yield func(*BasicBlock) bool) { + for _, b := range v.Blocks { + if _, ok := b.Control().(*Return); ok { + if !yield(b) { + return + } + } + } + } +} + +// Exits returns an iterator over all blocks that have no successors. +func (v *Function) Exits() iter.Seq[*BasicBlock] { + return func(yield func(*BasicBlock) bool) { + for _, b := range v.Blocks { + if len(b.Succs) == 0 { + if !yield(b) { + return + } + } + } + } +} + +// Nodes returns an iterator over the basic block indices. +func (v *Function) Nodes() iter.Seq[int] { + return func(yield func(int) bool) { + for i := range v.Blocks { + if !yield(i) { + return + } + } + } +} + +// NumNodes returns the number of basic blocks in the graph. +func (v *Function) NumNodes() int { return len(v.Blocks) } + +// Out returns an iterator over the successor block indices of a given node. +func (v *Function) Out(node int) iter.Seq[int] { + return func(yield func(int) bool) { + for _, succ := range v.Blocks[node].Succs { + if !yield(succ.Index) { + return + } + } + } +} + +func (v *Parameter) Type() types.Type { return v.typ } +func (v *Parameter) Name() string { return v.name } +func (v *Parameter) Object() types.Object { return v.object } +func (v *Parameter) Referrers() *[]Instruction { return &v.referrers } +func (v *Parameter) Pos() token.Pos { return v.object.Pos() } +func (v *Parameter) Parent() *Function { return v.parent } + +// TypeParams are the function's type parameters if generic or the +// type parameters that were instantiated if fn is an instantiation. +// +// Specifically, the resulting list behaves like: +// +// func f // [] +// func f[P] // [P] +// func (T) m // [] +// func (T) m[P] // [P] +// func (T[P]) m // [P] +// func (T[P]) m[Q] // [P (index=0), Q (index=0)] +// +// Note that receiver type parameters precede other type parameters. +// Also, type parameters may have the same index if they come from +// different source type parameter lists. +func (fn *Function) TypeParams() *types.TypeParamList { + return consTypeParamLists(fn.recvtypeparams, fn.typeparams) +} + +func consTypeParamLists(l, r *types.TypeParamList) *types.TypeParamList { + if l.Len() == 0 { + return r + } + if r.Len() == 0 { + return l + } + + tpars := make([]*types.TypeParam, l.Len()+r.Len()) + for i := range l.Len() { + tpars[i] = l.At(i) + } + for i := range r.Len() { + tpars[i+l.Len()] = r.At(i) + } + // This logic unsafely assumes (and asserts) that the layout of the + // TypeParamList is identical to that of a slice of TypeParams. This + // is a hack while we work on getting a constructor for TypeParamList + // approved (see go.dev/issue/79603). + t := reflect.TypeFor[types.TypeParamList]() + if t.NumField() != 1 { + panic("TypeParamList has unexpected fields") + } + if f := t.Field(0); f.Offset != 0 || f.Type != reflect.TypeFor[[]*types.TypeParam]() { + panic("TypeParamList field is not []*TypeParam") + } + return (*types.TypeParamList)(unsafe.Pointer(&tpars)) +} + +// TypeArgs are the types that TypeParams() were instantiated by to create fn +// from fn.Origin(). +// +// Specifically, the resulting slice behaves like: +// +// f // [] +// f[int] // [int] +// T.m // [] +// T.m[int] // [int] +// T[int].m // [int] +// T[int].m[uint] // [int, uint] +// +// Note that receiver type arguments precede other type arguments. +func (fn *Function) TypeArgs() []types.Type { + return slices.Concat(fn.recvtypeargs, fn.typeargs) +} + +// Origin returns the generic function from which fn was instantiated, +// or nil if fn is not an instantiation. +func (fn *Function) Origin() *Function { + if fn.parent != nil && fn.parent.hasTypeArgs() { + // Nested functions are BUILT at a different time than their instances. + // Build declared package if not yet BUILT. This is not an expected use + // case, but is simple and robust. + fn.declaredPackage().Build() + } + return origin(fn) +} + +// hasTypeParams returns whether fn has any type parameters +func (fn *Function) hasTypeParams() bool { + return fn.recvtypeparams.Len()+fn.typeparams.Len() > 0 +} + +// hasTypeArgs returns whether fn has any type arguments +func (fn *Function) hasTypeArgs() bool { + return len(fn.recvtypeargs)+len(fn.typeargs) > 0 +} + +// subrtargs returns fn's receiver type parameters substituted with receiver type arguments +func (fn *Function) subrtargs(m *types.Func) []types.Type { + return fn.subst.types(receiverTypeArgs(m)) +} + +// subtargs returns fn's type parameters substituted with (possibly implied) type arguments +func (fn *Function) subtargs(id *ast.Ident) []types.Type { + return fn.subst.types(instanceArgs(fn.info, id)) +} + +// targstr returns a comma-separated string of the types in targs +func targstr(targs []types.Type) string { + var sb strings.Builder + if len(targs) > 0 { + sb.WriteString("[") + for i := range targs { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(targs[i].String()) + } + sb.WriteString("]") + } + return sb.String() +} + +// origin is the function that fn is an instantiation of. Returns nil if fn is +// not an instantiation. +// +// Precondition: fn and the origin function are done building. +func origin(fn *Function) *Function { + if fn.parent != nil && fn.parent.hasTypeArgs() { + return origin(fn.parent).AnonFuncs[fn.anonIdx] + } + return fn.topLevelOrigin +} func (v *Alloc) Type() types.Type { return v.typ } func (v *Alloc) Referrers() *[]Instruction { return &v.referrers } @@ -1879,6 +1975,11 @@ func (c *NamedConst) Object() types.Object { return c.object } func (c *NamedConst) Package() *Package { return c.pkg } func (c *NamedConst) RelString(from *types.Package) string { return relString(c, from) } +func (v *Function) Pos() token.Pos { return v.pos } +func (v *Function) Source() ast.Node { return v.syntax } + +func (d *debugRef) Object() types.Object { return d.object } + // Func returns the package-level function of the specified name, // or nil if not found. func (p *Package) Func(name string) (f *Function) { @@ -1907,7 +2008,7 @@ func (p *Package) Type(name string) (t *Type) { return } -func (s *DebugRef) Pos() token.Pos { return s.Expr.Pos() } +func (s *debugRef) Pos() token.Pos { return s.Expr.Pos() } // Operands. @@ -1936,7 +2037,7 @@ func (s *Call) Operands(rands []*Value) []*Value { } func (s *Defer) Operands(rands []*Value) []*Value { - return append(s.Call.Operands(rands), &s._DeferStack) + return append(s.Call.Operands(rands), &s.DeferStack) } func (v *ChangeInterface) Operands(rands []*Value) []*Value { @@ -1963,11 +2064,7 @@ func (v *SliceToArray) Operands(rands []*Value) []*Value { return append(rands, &v.X) } -func (s *DebugRef) Operands(rands []*Value) []*Value { - return append(rands, &s.X) -} - -func (s *Copy) Operands(rands []*Value) []*Value { +func (s *debugRef) Operands(rands []*Value) []*Value { return append(rands, &s.X) } @@ -2060,10 +2157,6 @@ func (s *Panic) Operands(rands []*Value) []*Value { return append(rands, &s.X) } -func (v *Sigma) Operands(rands []*Value) []*Value { - return append(rands, &v.X) -} - func (v *Phi) Operands(rands []*Value) []*Value { for i := range v.Edges { rands = append(rands, &v.Edges[i]) @@ -2140,11 +2233,9 @@ func (v *CompositeValue) Operands(rands []*Value) []*Value { } // Non-Instruction Values: -func (v *Builtin) Operands(rands []*Value) []*Value { return rands } -func (v *FreeVar) Operands(rands []*Value) []*Value { return rands } -func (v *Const) Operands(rands []*Value) []*Value { return rands } -func (v *ArrayConst) Operands(rands []*Value) []*Value { return rands } -func (v *GenericConst) Operands(rands []*Value) []*Value { return rands } -func (v *Function) Operands(rands []*Value) []*Value { return rands } -func (v *Global) Operands(rands []*Value) []*Value { return rands } -func (v *Parameter) Operands(rands []*Value) []*Value { return rands } +func (v *Builtin) Operands(rands []*Value) []*Value { return rands } +func (v *FreeVar) Operands(rands []*Value) []*Value { return rands } +func (v *Const) Operands(rands []*Value) []*Value { return rands } +func (v *Function) Operands(rands []*Value) []*Value { return rands } +func (v *Global) Operands(rands []*Value) []*Value { return rands } +func (v *Parameter) Operands(rands []*Value) []*Value { return rands } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/subst.go b/tools/vendor/honnef.co/go/tools/go/ir/subst.go new file mode 100644 index 000000000..07b636a40 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/go/ir/subst.go @@ -0,0 +1,577 @@ +// 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 ir + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/xtools-internal/aliases" +) + +// subster defines a type substitution operation of a set of type parameters +// to type parameter free replacement types. Substitution is done within +// the context of a package-level function instantiation. *Named types +// declared in the function are unique to the instantiation. +// +// For example, given a parameterized function F +// +// func F[S, T any]() any { +// type X struct{ s S; next *X } +// var p *X +// return p +// } +// +// calling the instantiation F[string, int]() returns an interface +// value (*X[string,int], nil) where the underlying value of +// X[string,int] is a struct{s string; next *X[string,int]}. +// +// A nil *subster is a valid, empty substitution map. It always acts as +// the identity function. This allows for treating parameterized and +// non-parameterized functions identically while compiling to ssa. +// +// Not concurrency-safe. +// +// Note: Some may find it helpful to think through some of the most +// complex substitution cases using lambda calculus inspired notation. +// subst.typ() solves evaluating a type expression E +// within the body of a function Fn[m] with the type parameters m +// once we have applied the type arguments N. +// We can succinctly write this as a function application: +// +// ((λm. E) N) +// +// go/types does not provide this interface directly. +// So what subster provides is a type substitution operation +// +// E[m:=N] +type subster struct { + replacements map[*types.TypeParam]types.Type // values should contain no type params + cache map[types.Type]types.Type // cache of subst results + origin *types.Func // types.Objects declared within this origin function are unique within this context + ctxt *types.Context // speeds up repeated instantiations + uniqueness typeutil.Map[types.Type] // determines the uniqueness of the instantiations within the function + // TODO(taking): consider adding Pos +} + +// Returns a subster that replaces rtparams[i] with rtargs[i] and tparams[i] with targs[i]. +// Uses ctxt as a cache. rtargs and targs should not contain any types in rtparams or tparams. +// fn is the generic function for which we are substituting. +func makeSubster(ctxt *types.Context, fn *types.Func, rtparams *types.TypeParamList, rtargs []types.Type, tparams *types.TypeParamList, targs []types.Type) *subster { + got := len(rtargs) + len(targs) + want := rtparams.Len() + tparams.Len() + if got != want { + panic(fmt.Sprintf("makeSubster argument count must match: got %d; want %d", got, want)) + } + + subst := &subster{ + replacements: make(map[*types.TypeParam]types.Type, want), + cache: make(map[types.Type]types.Type), + origin: fn.Origin(), + ctxt: ctxt, + } + for i := 0; i < rtparams.Len(); i++ { + subst.replacements[rtparams.At(i)] = rtargs[i] + } + for i := 0; i < tparams.Len(); i++ { + subst.replacements[tparams.At(i)] = targs[i] + } + return subst +} + +// typ returns the type of t with the type parameter tparams[i] substituted +// for the type targs[i] where subst was created using tparams and targs. +func (subst *subster) typ(t types.Type) (res types.Type) { + if subst == nil { + return t // A nil subst is type preserving. + } + if r, ok := subst.cache[t]; ok { + return r + } + defer func() { + subst.cache[t] = res + }() + + switch t := t.(type) { + case *types.TypeParam: + if r := subst.replacements[t]; r != nil { + return r + } + return t + + case *types.Basic: + return t + + case *types.Array: + if r := subst.typ(t.Elem()); r != t.Elem() { + return types.NewArray(r, t.Len()) + } + return t + + case *types.Slice: + if r := subst.typ(t.Elem()); r != t.Elem() { + return types.NewSlice(r) + } + return t + + case *types.Pointer: + if r := subst.typ(t.Elem()); r != t.Elem() { + return types.NewPointer(r) + } + return t + + case *types.Tuple: + return subst.tuple(t) + + case *types.Struct: + return subst.struct_(t) + + case *types.Map: + key := subst.typ(t.Key()) + elem := subst.typ(t.Elem()) + if key != t.Key() || elem != t.Elem() { + return types.NewMap(key, elem) + } + return t + + case *types.Chan: + if elem := subst.typ(t.Elem()); elem != t.Elem() { + return types.NewChan(t.Dir(), elem) + } + return t + + case *types.Signature: + return subst.signature(t) + + case *types.Union: + return subst.union(t) + + case *types.Interface: + return subst.interface_(t) + + case *types.Alias: + return subst.alias(t) + + case *types.Named: + return subst.named(t) + + case *typeutil.Iterator: + elem := subst.typ(t.Elem()) + if elem != t.Elem() { + return typeutil.NewIterator(elem) + } + return t + + case *typeutil.DeferStack: + return t + + default: + panic(fmt.Sprintf("internal error: unhandled type %T", t)) + } +} + +// types returns the result of {subst.typ(ts[i])}. +func (subst *subster) types(ts []types.Type) []types.Type { + res := make([]types.Type, len(ts)) + for i := range ts { + res[i] = subst.typ(ts[i]) + } + return res +} + +func (subst *subster) tuple(t *types.Tuple) *types.Tuple { + if t != nil { + if vars := subst.varlist(t); vars != nil { + return types.NewTuple(vars...) + } + } + return t +} + +type varlist interface { + At(i int) *types.Var + Len() int +} + +// fieldlist is an adapter for structs for the varlist interface. +type fieldlist struct { + str *types.Struct +} + +func (fl fieldlist) At(i int) *types.Var { return fl.str.Field(i) } +func (fl fieldlist) Len() int { return fl.str.NumFields() } + +func (subst *subster) struct_(t *types.Struct) *types.Struct { + if t != nil { + if fields := subst.varlist(fieldlist{t}); fields != nil { + tags := make([]string, t.NumFields()) + for i, n := 0, t.NumFields(); i < n; i++ { + tags[i] = t.Tag(i) + } + return types.NewStruct(fields, tags) + } + } + return t +} + +// varlist returns subst(in[i]) or return nils if subst(v[i]) == v[i] for all i. +func (subst *subster) varlist(in varlist) []*types.Var { + var out []*types.Var // nil => no updates + for i, n := 0, in.Len(); i < n; i++ { + v := in.At(i) + w := subst.var_(v) + if v != w && out == nil { + out = make([]*types.Var, n) + for j := 0; j < i; j++ { + out[j] = in.At(j) + } + } + if out != nil { + out[i] = w + } + } + return out +} + +func (subst *subster) var_(v *types.Var) *types.Var { + if v != nil { + if typ := subst.typ(v.Type()); typ != v.Type() { + if v.IsField() { + return types.NewField(v.Pos(), v.Pkg(), v.Name(), typ, v.Embedded()) + } + return types.NewParam(v.Pos(), v.Pkg(), v.Name(), typ) + } + } + return v +} + +func (subst *subster) union(u *types.Union) *types.Union { + var out []*types.Term // nil => no updates + + for i, n := 0, u.Len(); i < n; i++ { + t := u.Term(i) + r := subst.typ(t.Type()) + if r != t.Type() && out == nil { + out = make([]*types.Term, n) + for j := 0; j < i; j++ { + out[j] = u.Term(j) + } + } + if out != nil { + out[i] = types.NewTerm(t.Tilde(), r) + } + } + + if out != nil { + return types.NewUnion(out) + } + return u +} + +func (subst *subster) interface_(iface *types.Interface) *types.Interface { + if iface == nil { + return nil + } + + // methods for the interface. Initially nil if there is no known change needed. + // Signatures for the method where recv is nil. NewInterfaceType fills in the receivers. + var methods []*types.Func + initMethods := func(n int) { // copy first n explicit methods + methods = make([]*types.Func, iface.NumExplicitMethods()) + for i := range n { + f := iface.ExplicitMethod(i) + norecv := changeRecv(f.Type().(*types.Signature), nil) + methods[i] = types.NewFunc(f.Pos(), f.Pkg(), f.Name(), norecv) + } + } + for i := 0; i < iface.NumExplicitMethods(); i++ { + f := iface.ExplicitMethod(i) + // On interfaces, we need to cycle break on anonymous interface types + // being in a cycle with their signatures being in cycles with their receivers + // that do not go through a Named. + norecv := changeRecv(f.Type().(*types.Signature), nil) + sig := subst.typ(norecv) + if sig != norecv && methods == nil { + initMethods(i) + } + if methods != nil { + methods[i] = types.NewFunc(f.Pos(), f.Pkg(), f.Name(), sig.(*types.Signature)) + } + } + + var embeds []types.Type + initEmbeds := func(n int) { // copy first n embedded types + embeds = make([]types.Type, iface.NumEmbeddeds()) + for i := range n { + embeds[i] = iface.EmbeddedType(i) + } + } + for i := 0; i < iface.NumEmbeddeds(); i++ { + e := iface.EmbeddedType(i) + r := subst.typ(e) + if e != r && embeds == nil { + initEmbeds(i) + } + if embeds != nil { + embeds[i] = r + } + } + + if methods == nil && embeds == nil { + return iface + } + if methods == nil { + initMethods(iface.NumExplicitMethods()) + } + if embeds == nil { + initEmbeds(iface.NumEmbeddeds()) + } + return types.NewInterfaceType(methods, embeds).Complete() +} + +func (subst *subster) alias(t *types.Alias) types.Type { + // See subster.named. This follows the same strategy. + tparams := t.TypeParams() + targs := t.TypeArgs() + tname := t.Obj() + torigin := t.Origin() + + if !declaredWithin(tname, subst.origin) { + // t is declared outside of the function origin. So t is a package level type alias. + if targs.Len() == 0 { + // No type arguments so no instantiation needed. + return t + } + + // Instantiate with the substituted type arguments. + newTArgs := subst.typelist(targs) + return subst.instantiate(torigin, newTArgs) + } + + if targs.Len() == 0 { + // t is declared within the function origin and has no type arguments. + // + // Example: This corresponds to A or B in F, but not A[int]: + // + // func F[T any]() { + // type A[S any] = struct{t T, s S} + // type B = T + // var x A[int] + // ... + // } + // + // This is somewhat different than *Named as *Alias cannot be created recursively. + + // Copy and substitute type params. + var newTParams []*types.TypeParam + for cur := range tparams.TypeParams() { + cobj := cur.Obj() + cname := types.NewTypeName(cobj.Pos(), cobj.Pkg(), cobj.Name(), nil) + ntp := types.NewTypeParam(cname, nil) + subst.cache[cur] = ntp // See the comment "Note: Subtle" in subster.named. + newTParams = append(newTParams, ntp) + } + + // Substitute rhs. + rhs := subst.typ(t.Rhs()) + + // Create the fresh alias. + obj := aliases.New(tname.Pos(), tname.Pkg(), tname.Name(), rhs, newTParams) + + // Substitute into all of the constraints after they are created. + for i, ntp := range newTParams { + bound := tparams.At(i).Constraint() + ntp.SetConstraint(subst.typ(bound)) + } + return obj.Type() + } + + // t is declared within the function origin and has type arguments. + // + // Example: This corresponds to A[int] in F. Cases A and B are handled above. + // func F[T any]() { + // type A[S any] = struct{t T, s S} + // type B = T + // var x A[int] + // ... + // } + subOrigin := subst.typ(torigin) + subTArgs := subst.typelist(targs) + return subst.instantiate(subOrigin, subTArgs) +} + +func (subst *subster) named(t *types.Named) types.Type { + // A Named type is a user defined type. + // Ignoring generics, Named types are canonical: they are identical if + // and only if they have the same defining symbol. + // Generics complicate things, both if the type definition itself is + // parameterized, and if the type is defined within the scope of a + // parameterized function. In this case, two named types are identical if + // and only if their identifying symbols are identical, and all type + // arguments bindings in scope of the named type definition (including the + // type parameters of the definition itself) are equivalent. + // + // Notably: + // 1. For type definition type T[P1 any] struct{}, T[A] and T[B] are identical + // only if A and B are identical. + // 2. Inside the generic func Fn[m any]() any { type T struct{}; return T{} }, + // the result of Fn[A] and Fn[B] have identical type if and only if A and + // B are identical. + // 3. Both 1 and 2 could apply, such as in + // func F[m any]() any { type T[x any] struct{}; return T{} } + // + // A subster replaces type parameters within a function scope, and therefore must + // also replace free type parameters in the definitions of local types. + // + // Note: There are some detailed notes sprinkled throughout that borrow from + // lambda calculus notation. These contain some over simplifying math. + // + // LC: One way to think about subster is that it is a way of evaluating + // ((λm. E) N) as E[m:=N]. + // Each Named type t has an object *TypeName within a scope S that binds an + // underlying type expression U. U can refer to symbols within S (+ S's ancestors). + // Let x = t.TypeParams() and A = t.TypeArgs(). + // Each Named type t is then either: + // U where len(x) == 0 && len(A) == 0 + // λx. U where len(x) != 0 && len(A) == 0 + // ((λx. U) A) where len(x) == len(A) + // In each case, we will evaluate t[m:=N]. + tparams := t.TypeParams() // x + targs := t.TypeArgs() // A + + if !declaredWithin(t.Obj(), subst.origin) { + // t is declared outside of Fn[m]. + // + // In this case, we can skip substituting t.Underlying(). + // The underlying type cannot refer to the type parameters. + // + // LC: Let free(E) be the set of free type parameters in an expression E. + // Then whenever m ∉ free(E), then E = E[m:=N]. + // t ∉ Scope(fn) so therefore m ∉ free(U) and m ∩ x = ∅. + if targs.Len() == 0 { + // t has no type arguments. So it does not need to be instantiated. + // + // This is the normal case in real Go code, where t is not parameterized, + // declared at some package scope, and m is a TypeParam from a parameterized + // function F[m] or method. + // + // LC: m ∉ free(A) lets us conclude m ∉ free(t). So t=t[m:=N]. + return t + } + + // t is declared outside of Fn[m] and has type arguments. + // The type arguments may contain type parameters m so + // substitute the type arguments, and instantiate the substituted + // type arguments. + // + // LC: Evaluate this as ((λx. U) A') where A' = A[m := N]. + newTArgs := subst.typelist(targs) + return subst.instantiate(t.Origin(), newTArgs) + } + + // t is declared within Fn[m]. + + if targs.Len() == 0 { // no type arguments? + assert(t == t.Origin(), "local parameterized type abstraction must be an origin type") + + // t has no type arguments. + // The underlying type of t may contain the function's type parameters, + // replace these, and create a new type. + // + // Subtle: We short circuit substitution and use a newly created type in + // subst, i.e. cache[t]=fresh, to preemptively replace t with fresh + // in recursive types during traversal. This both breaks infinite cycles + // and allows for constructing types with the replacement applied in + // subst.typ(U). + // + // A new copy of the Named and Typename (and constraints) per function + // instantiation matches the semantics of Go, which treats all function + // instantiations F[N] as having distinct local types. + // + // LC: x.Len()=0 can be thought of as a special case of λx. U. + // LC: Evaluate (λx. U)[m:=N] as (λx'. U') where U'=U[x:=x',m:=N]. + tname := t.Obj() + obj := types.NewTypeName(tname.Pos(), tname.Pkg(), tname.Name(), nil) + fresh := types.NewNamed(obj, nil, nil) + var newTParams []*types.TypeParam + for cur := range tparams.TypeParams() { + cobj := cur.Obj() + cname := types.NewTypeName(cobj.Pos(), cobj.Pkg(), cobj.Name(), nil) + ntp := types.NewTypeParam(cname, nil) + subst.cache[cur] = ntp + newTParams = append(newTParams, ntp) + } + fresh.SetTypeParams(newTParams) + subst.cache[t] = fresh + subst.cache[fresh] = fresh + fresh.SetUnderlying(subst.typ(t.Underlying())) + // Substitute into all of the constraints after they are created. + for i, ntp := range newTParams { + bound := tparams.At(i).Constraint() + ntp.SetConstraint(subst.typ(bound)) + } + return fresh + } + + // t is defined within Fn[m] and t has type arguments (an instantiation). + // We reduce this to the two cases above: + // (1) substitute the function's type parameters into t.Origin(). + // (2) substitute t's type arguments A and instantiate the updated t.Origin() with these. + // + // LC: Evaluate ((λx. U) A)[m:=N] as (t' A') where t' = (λx. U)[m:=N] and A'=A [m:=N] + subOrigin := subst.typ(t.Origin()) + subTArgs := subst.typelist(targs) + return subst.instantiate(subOrigin, subTArgs) +} + +func (subst *subster) instantiate(orig types.Type, targs []types.Type) types.Type { + i, err := types.Instantiate(subst.ctxt, orig, targs, false) + assert(err == nil, "failed to Instantiate named (Named or Alias) type") + if c, _ := subst.uniqueness.At(i); c != nil { + return c + } + subst.uniqueness.Set(i, i) + return i +} + +func (subst *subster) typelist(l *types.TypeList) []types.Type { + res := make([]types.Type, l.Len()) + for i := 0; i < l.Len(); i++ { + res[i] = subst.typ(l.At(i)) + } + return res +} + +func (subst *subster) signature(t *types.Signature) types.Type { + tparams := t.TypeParams() + + // We are choosing not to support tparams.Len() > 0 until a need has been observed in practice. + // + // There are some known usages for types.Types coming from types.{Eval,CheckExpr}. + // To support tparams.Len() > 0, we just need to do the following [pseudocode]: + // targs := {subst.replacements[tparams[i]]]}; Instantiate(ctxt, t, targs, false) + + assert(tparams.Len() == 0, "Substituting types.Signatures with generic functions are currently unsupported.") + + // Either: + // (1)non-generic function. + // no type params to substitute + // (2)generic method and recv needs to be substituted. + + // Receivers can be either: + // named + // pointer to named + // interface + // nil + // interface is the problematic case. We need to cycle break there! + recv := subst.var_(t.Recv()) + params := subst.tuple(t.Params()) + results := subst.tuple(t.Results()) + if recv != t.Recv() || params != t.Params() || results != t.Results() { + return types.NewSignatureType(recv, nil, nil, params, results, t.Variadic()) + } + return t +} diff --git a/tools/vendor/honnef.co/go/tools/go/ir/task.go b/tools/vendor/honnef.co/go/tools/go/ir/task.go new file mode 100644 index 000000000..7a44404b3 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/go/ir/task.go @@ -0,0 +1,103 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import ( + "sync/atomic" +) + +// Each task has two states: it is initially "active", +// and transitions to "done". +// +// tasks form a directed graph. An edge from x to y (with y in x.edges) +// indicates that the task x waits on the task y to be done. +// Cycles are permitted. +// +// Calling x.wait() blocks the calling goroutine until task x, +// and all the tasks transitively reachable from x are done. +// +// The nil *task is always considered done. +type task struct { + done chan unit // close when the task is done. + edges map[*task]unit // set of predecessors of this task. + transitive atomic.Bool // true once it is known all predecessors are done. +} + +func (x *task) isTransitivelyDone() bool { return x == nil || x.transitive.Load() } + +// addEdge creates an edge from x to y, indicating that +// x.wait() will not return before y is done. +// All calls to x.addEdge(...) should happen before x.markDone(). +func (x *task) addEdge(y *task) { + if x == y || y.isTransitivelyDone() { + return // no work remaining + } + + // heuristic done check + select { + case <-x.done: + panic("cannot add an edge to a done task") + default: + } + + if x.edges == nil { + x.edges = make(map[*task]unit) + } + x.edges[y] = unit{} +} + +// markDone changes the task's state to markDone. +func (x *task) markDone() { + if x != nil { + close(x.done) + } +} + +// wait blocks until x and all the tasks it can reach through edges are done. +func (x *task) wait() { + if x.isTransitivelyDone() { + return // already known to be done. Skip allocations. + } + + // Use BFS to wait on u.done to be closed, for all u transitively + // reachable from x via edges. + // + // This work can be repeated by multiple workers doing wait(). + // + // Note: Tarjan's SCC algorithm is able to mark SCCs as transitively done + // as soon as the SCC has been visited. This is theoretically faster, but is + // a more complex algorithm. Until we have evidence, we need the more complex + // algorithm, the simpler algorithm BFS is implemented. + // + // In Go 1.23, ssa/TestStdlib reaches <=3 *tasks per wait() in most schedules + // On some schedules, there is a cycle building net/http and internal/trace/testtrace + // due to slices functions. + work := []*task{x} + enqueued := map[*task]unit{x: {}} + for i := 0; i < len(work); i++ { + u := work[i] + if u.isTransitivelyDone() { // already transitively done + work[i] = nil + continue + } + <-u.done // wait for u to be marked done. + + for v := range u.edges { + if _, ok := enqueued[v]; !ok { + enqueued[v] = unit{} + work = append(work, v) + } + } + } + + // work is transitively closed over dependencies. + // u in work is done (or transitively done and skipped). + // u is transitively done. + for _, u := range work { + if u != nil { + x.transitive.Store(true) + } + } +} diff --git a/tools/vendor/honnef.co/go/tools/go/ir/typeset.go b/tools/vendor/honnef.co/go/tools/go/ir/typeset.go new file mode 100644 index 000000000..6154e4f17 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/go/ir/typeset.go @@ -0,0 +1,175 @@ +// 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 ir + +import ( + "go/types" + + "honnef.co/go/tools/go/types/typeutil" +) + +// Utilities for dealing with type sets. + +const debug = false + +// typeset is an iterator over the (type/underlying type) pairs of the +// specific type terms of the type set implied by t. +// If t is a type parameter, the implied type set is the type set of t's constraint. +// In that case, if there are no specific terms, typeset calls yield with (nil, nil). +// If t is not a type parameter, the implied type set consists of just t. +// In any case, typeset is guaranteed to call yield at least once. +func typeset(typ types.Type, yield func(t, u types.Type) bool) { + switch typ := types.Unalias(typ).(type) { + case *types.TypeParam, *types.Interface: + terms := termListOf(typ) + if len(terms) == 0 { + yield(nil, nil) + return + } + for _, term := range terms { + u := types.Unalias(term.Type()) + if !term.Tilde() { + u = u.Underlying() + } + if debug { + assert(types.Identical(u, u.Underlying()), "Unalias(x) == under(x) for ~x terms") + } + if !yield(term.Type(), u) { + break + } + } + return + default: + yield(typ, typ.Underlying()) + } +} + +// termListOf returns the type set of typ as a normalized term set. Returns an empty set on an error. +func termListOf(typ types.Type) []*types.Term { + return typeutil.NewTypeSet(typ).Terms +} + +// typeSetIsEmpty returns true if a typeset is empty. +func typeSetIsEmpty(typ types.Type) bool { + var empty bool + typeset(typ, func(t, _ types.Type) bool { + empty = t == nil + return false + }) + return empty +} + +// isBytestring returns true if T has the same terms as interface{[]byte | string}. +// These act like a core type for some operations: slice expressions, append and copy. +// +// See https://go.dev/ref/spec#Core_types for the details on bytestring. +func isBytestring(T types.Type) bool { + U := T.Underlying() + if _, ok := U.(*types.Interface); !ok { + return false + } + + hasBytes, hasString := false, false + ok := underIs(U, func(t types.Type) bool { + switch { + case isString(t): + hasString = true + return true + case isByteSlice(t): + hasBytes = true + return true + default: + return false + } + }) + return ok && hasBytes && hasString +} + +// underIs calls f with the underlying types of the type terms +// of the type set of typ and reports whether all calls to f returned true. +// If there are no specific terms, underIs returns the result of f(nil). +func underIs(typ types.Type, f func(types.Type) bool) bool { + var ok bool + typeset(typ, func(t, u types.Type) bool { + ok = f(u) + return ok + }) + return ok +} + +// indexType returns the element type and index mode of a IndexExpr over a type. +// It returns an invalid mode if the type is not indexable; this should never occur in a well-typed program. +func indexType(typ types.Type) (types.Type, indexMode) { + switch U := typ.Underlying().(type) { + case *types.Array: + return U.Elem(), ixArrVar + case *types.Pointer: + if arr, ok := U.Elem().Underlying().(*types.Array); ok { + return arr.Elem(), ixVar + } + case *types.Slice: + return U.Elem(), ixVar + case *types.Map: + return U.Elem(), ixMap + case *types.Basic: + return tByte, ixValue // must be a string + case *types.Interface: + var elem types.Type + mode := ixInvalid + typeset(typ, func(t, _ types.Type) bool { + if t == nil { + return false // empty set + } + e, m := indexType(t) + if elem == nil { + elem, mode = e, m + } + if debug && !types.Identical(elem, e) { // if type checked, just a sanity check + mode = ixInvalid + return false + } + // Update the mode to the most constrained address type. + mode = mode.meet(m) + return mode != ixInvalid + }) + return elem, mode + } + return nil, ixInvalid +} + +// An indexMode specifies the (addressing) mode of an index operand. +// +// Addressing mode of an index operation is based on the set of +// underlying types. +// Hasse diagram of the indexMode meet semi-lattice: +// +// ixVar ixMap +// | | +// ixArrVar | +// | | +// ixValue | +// \ / +// ixInvalid +type indexMode byte + +const ( + ixInvalid indexMode = iota // index is invalid + ixValue // index is a computed value (not addressable) + ixArrVar // like ixVar, but index operand contains an array + ixVar // index is an addressable variable + ixMap // index is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment) +) + +// meet is the address type that is constrained by both x and y. +func (x indexMode) meet(y indexMode) indexMode { + if (x == ixMap || y == ixMap) && x != y { + return ixInvalid + } + // Use int representation and return min. + if x < y { + return y + } + return x +} diff --git a/tools/vendor/honnef.co/go/tools/go/ir/util.go b/tools/vendor/honnef.co/go/tools/go/ir/util.go index 97fe9c5f5..d845c9a1f 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/util.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/util.go @@ -13,16 +13,17 @@ import ( "go/types" "io" "os" + "sync" + _ "unsafe" // for go:linkname hack - "honnef.co/go/tools/go/ast/astutil" "honnef.co/go/tools/go/types/typeutil" "golang.org/x/exp/typeparams" ) -//// AST utilities +type unit struct{} -func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) } +//// AST utilities // isBlankIdent returns true iff e is an Ident with name "_". // They have no associated types.Object, and thus no type. @@ -33,14 +34,66 @@ func isBlankIdent(e ast.Expr) bool { //// Type utilities. Some of these belong in go/types. -// isPointer returns true for types whose underlying type is a pointer, -// and for type parameters whose core type is a pointer. -func isPointer(typ types.Type) bool { - if ctyp := typeutil.CoreType(typ); ctyp != nil { - _, ok := ctyp.(*types.Pointer) - return ok +// isString reports whether t is exactly a string type. +// t is assumed to be an Underlying type (not Named or Alias). +func isString(t types.Type) bool { + basic, ok := t.(*types.Basic) + return ok && basic.Info()&types.IsString != 0 +} + +// isByteSlice reports whether t is of the form []~bytes. +// t is assumed to be an Underlying type (not Named or Alias). +func isByteSlice(t types.Type) bool { + if b, ok := t.(*types.Slice); ok { + e, _ := b.Elem().Underlying().(*types.Basic) + return e != nil && e.Kind() == types.Byte + } + return false +} + +// isRuneSlice reports whether t is of the form []~runes. +// t is assumed to be an Underlying type (not Named or Alias). +func isRuneSlice(t types.Type) bool { + if b, ok := t.(*types.Slice); ok { + e, _ := b.Elem().Underlying().(*types.Basic) + return e != nil && e.Kind() == types.Rune } - _, ok := typ.Underlying().(*types.Pointer) + return false +} + +// isBasicConvTypes returns true when the type set of a type +// can be one side of a Convert operation. This is when: +// - All are basic, []byte, or []rune. +// - At least 1 is basic. +// - At most 1 is []byte or []rune. +func isBasicConvTypes(typ types.Type) bool { + basics, cnt := 0, 0 + ok := underIs(typ, func(t types.Type) bool { + cnt++ + if isBasic(t) { + basics++ + return true + } + return isByteSlice(t) || isRuneSlice(t) + }) + return ok && basics >= 1 && cnt-basics <= 1 +} + +// isPointer reports whether t's underlying type is a pointer. +func isPointer(t types.Type) bool { + return is[*types.Pointer](t.Underlying()) +} + +// isPointerCore reports whether t's core type is a pointer. +// +// (Most pointer manipulation is related to receivers, in which case +// isPointer is appropriate. tecallers can use isPointer(t). +func isPointerCore(t types.Type) bool { + return is[*types.Pointer](typeutil.CoreType(t)) +} + +func is[T any](x any) bool { + _, ok := x.(T) return ok } @@ -67,6 +120,44 @@ func recvType(obj *types.Func) types.Type { return obj.Type().(*types.Signature).Recv().Type() } +// fieldOf returns the index'th field of the (core type of) a struct type; +// otherwise returns nil. +func fieldOf(typ types.Type, index int) *types.Var { + if st, ok := typeutil.CoreType(typ).(*types.Struct); ok { + if 0 <= index && index < st.NumFields() { + return st.Field(index) + } + } + return nil +} + +// isUntyped reports whether typ is the type of an untyped constant. +func isUntyped(typ types.Type) bool { + // No Underlying/Unalias: untyped constant types cannot be Named or Alias. + b, ok := typ.(*types.Basic) + return ok && b.Info()&types.IsUntyped != 0 +} + +// declaredWithin reports whether an object is declared within a function. +// +// obj must not be a method or a field. +func declaredWithin(obj types.Object, fn *types.Func) bool { + if obj.Pos() != token.NoPos { + return fn.Scope().Contains(obj.Pos()) // trust the positions if they exist. + } + if fn.Pkg() != obj.Pkg() { + return false // fast path for different packages + } + + // Traverse Parent() scopes for fn.Scope(). + for p := obj.Parent(); p != nil; p = p.Parent() { + if p == fn.Scope() { + return true + } + } + return false +} + // logStack prints the formatted "start" message to stderr and // returns a closure that prints the corresponding "end" message. // Call using 'defer logStack(...)()' to show builder stack on panic. @@ -102,61 +193,244 @@ func makeLen(T types.Type) *Builtin { } } -type StackMap struct { - m []map[Value]Value +func assert(x bool, msg string) { + if !x { + panic(msg) + } } -func (m *StackMap) Push() { - m.m = append(m.m, map[Value]Value{}) -} +// BlockMap is a mapping from basic blocks (identified by their indices) to values. +type BlockMap[T any] []T -func (m *StackMap) Pop() { - m.m = m.m[:len(m.m)-1] +// isBasic reports whether t is a basic type. +func isBasic(t types.Type) bool { + _, ok := t.(*types.Basic) + return ok } -func (m *StackMap) Get(key Value) (Value, bool) { - for i := len(m.m) - 1; i >= 0; i-- { - if v, ok := m.m[i][key]; ok { - return v, true +// receiverTypeArgs returns the type arguments to a method's receiver. +// Returns an empty list if the receiver does not have type arguments. +func receiverTypeArgs(method *types.Func) []types.Type { + recv := method.Signature().Recv() + named := func() (named *types.Named) { + t := recv.Type() + if ptr, ok := types.Unalias(t).(*types.Pointer); ok { + t = ptr.Elem() } + named, _ = types.Unalias(t).(*types.Named) + return named + }() + if named == nil { + return nil // recv is anonymous struct/interface + } + ts := named.TypeArgs() + if ts.Len() == 0 { + return nil + } + targs := make([]types.Type, ts.Len()) + for i := 0; i < ts.Len(); i++ { + targs[i] = ts.At(i) + } + return targs +} + +// recvAsFirstArg takes a method signature and returns a function +// signature with receiver as the first parameter. +func recvAsFirstArg(sig *types.Signature) *types.Signature { + params := make([]*types.Var, 0, 1+sig.Params().Len()) + params = append(params, sig.Recv()) + for v := range sig.Params().Variables() { + params = append(params, v) + } + return types.NewSignatureType(nil, nil, nil, types.NewTuple(params...), sig.Results(), sig.Variadic()) +} + +// instance returns whether an expression is a simple or qualified identifier +// that is a generic instantiation. +func instance(info *types.Info, expr ast.Expr) bool { + // Compare the logic here against go/types.instantiatedIdent, + // which also handles *IndexExpr and *IndexListExpr. + var id *ast.Ident + switch x := expr.(type) { + case *ast.Ident: + id = x + case *ast.SelectorExpr: + id = x.Sel + default: + return false + } + _, ok := info.Instances[id] + return ok +} + +// isNonTypeParamInterface reports whether t is an interface type but not a type parameter. +func isNonTypeParamInterface(t types.Type) bool { + return !typeparams.IsTypeParam(t) && types.IsInterface(t) +} + +// instanceArgs returns the Instance[id].TypeArgs as a slice. +func instanceArgs(info *types.Info, id *ast.Ident) []types.Type { + targList := info.Instances[id].TypeArgs + if targList == nil { + return nil + } + + targs := make([]types.Type, targList.Len()) + for i, n := 0, targList.Len(); i < n; i++ { + targs[i] = targList.At(i) } - return nil, false + return targs } -func (m *StackMap) Set(k Value, v Value) { - m.m[len(m.m)-1][k] = v +// Mapping of a type T to a canonical instance C s.t. types.Identical(T, C). +// Thread-safe. +type canonizer struct { + mu sync.Mutex + types typeutil.Map[types.Type] // map from type to a canonical instance + lists typeListMap // map from a list of types to a canonical instance } -// Unwrap recursively unwraps Sigma and Copy nodes. -func Unwrap(v Value) Value { - for { - switch vv := v.(type) { - case *Sigma: - v = vv.X - case *Copy: - v = vv.X - default: - return v +func newCanonizer() *canonizer { + c := &canonizer{} + h := typeutil.MakeHasher() + c.types.SetHasher(h) + c.lists.hasher = h + return c +} + +// List returns a canonical representative of a list of types. +// Representative of the empty list is nil. +func (c *canonizer) List(ts []types.Type) *typeList { + if len(ts) == 0 { + return nil + } + + unaliasAll := func(ts []types.Type) []types.Type { + // Is there some top level alias? + var found bool + for _, t := range ts { + if _, ok := t.(*types.Alias); ok { + found = true + break + } + } + if !found { + return ts // no top level alias } + + cp := make([]types.Type, len(ts)) // copy with top level aliases removed. + for i, t := range ts { + cp[i] = types.Unalias(t) + } + return cp } + l := unaliasAll(ts) + + c.mu.Lock() + defer c.mu.Unlock() + return c.lists.rep(l) } -func assert(x bool) { - if !x { - panic("failed assertion") +// Type returns a canonical representative of type T. +// Removes top-level aliases. +// +// For performance, reasons the canonical instance is order-dependent, +// and may contain deeply nested aliases. +func (c *canonizer) Type(T types.Type) types.Type { + T = types.Unalias(T) // remove the top level alias. + + c.mu.Lock() + defer c.mu.Unlock() + + if r, _ := c.types.At(T); r != nil { + return r } + c.types.Set(T, T) + return T } -// BlockMap is a mapping from basic blocks (identified by their indices) to values. -type BlockMap[T any] []T +// A type for representing a canonized list of types. +type typeList []types.Type -// isBasic reports whether t is a basic type. -func isBasic(t types.Type) bool { - _, ok := t.(*types.Basic) - return ok +func (l *typeList) identical(ts []types.Type) bool { + if l == nil { + return len(ts) == 0 + } + n := len(*l) + if len(ts) != n { + return false + } + for i, left := range *l { + right := ts[i] + if !types.Identical(left, right) { + return false + } + } + return true } -// isNonTypeParamInterface reports whether t is an interface type but not a type parameter. -func isNonTypeParamInterface(t types.Type) bool { - return !typeparams.IsTypeParam(t) && types.IsInterface(t) +type typeListMap struct { + hasher typeutil.Hasher + buckets map[uint32][]*typeList } + +// rep returns a canonical representative of a slice of types. +func (m *typeListMap) rep(ts []types.Type) *typeList { + if m == nil || len(ts) == 0 { + return nil + } + + if m.buckets == nil { + m.buckets = make(map[uint32][]*typeList) + } + + h := m.hash(ts) + bucket := m.buckets[h] + for _, l := range bucket { + if l.identical(ts) { + return l + } + } + + // not present. create a representative. + cp := make(typeList, len(ts)) + copy(cp, ts) + rep := &cp + + m.buckets[h] = append(bucket, rep) + return rep +} + +func (m *typeListMap) hash(ts []types.Type) uint32 { + if m == nil { + return 0 + } + // Some smallish prime far away from typeutil.Hash. + n := len(ts) + h := uint32(13619) + 2*uint32(n) + for i := range n { + h += 3 * m.hasher.Hash(ts[i]) + } + return h +} + +// instantiateMethod instantiates m with targs and returns a canonical representative for this method. +func (canon *canonizer) instantiateMethod(m *types.Func, targs []types.Type, ctxt *types.Context) *types.Func { + recv := recvType(m) + if p, ok := types.Unalias(recv).(*types.Pointer); ok { + recv = p.Elem() + } + named := types.Unalias(recv).(*types.Named) + inst, err := types.Instantiate(ctxt, named.Origin(), targs, false) + if err != nil { + panic(err) + } + rep := canon.Type(inst) + obj, _, _ := types.LookupFieldOrMethod(rep, true, m.Pkg(), m.Name()) + return obj.(*types.Func) +} + +// Exposed to irutil using the linkname hack. +// +//go:linkname isSyntactic honnef.co/go/tools/go/ir.isSyntactic +func isSyntactic(pkg *Package) bool { return pkg.syntax } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/wrappers.go b/tools/vendor/honnef.co/go/tools/go/ir/wrappers.go index 6b358a7c6..1d7b3e485 100644 --- a/tools/vendor/honnef.co/go/tools/go/ir/wrappers.go +++ b/tools/vendor/honnef.co/go/tools/go/ir/wrappers.go @@ -26,7 +26,7 @@ import ( // -- wrappers ----------------------------------------------------------- -// makeWrapper returns a synthetic method that delegates to the +// createWrapper returns a synthetic method that delegates to the // declared method denoted by meth.Obj(), first performing any // necessary pointer indirections or field selections implied by meth. // @@ -36,65 +36,92 @@ import ( // following axes of variation when making changes: // - optional receiver indirection // - optional implicit field selections +// - optional method type arguments // - meth.Obj() may denote a concrete or an interface method // - the result may be a thunk or a wrapper. -// -// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) -func makeWrapper(prog *Program, sel *types.Selection) *Function { - obj := sel.Obj().(*types.Func) // the declared function - sig := sel.Type().(*types.Signature) // type of this wrapper +func createWrapper(prog *Program, sel *selection, targs []types.Type) *Function { + obj := sel.obj.(*types.Func) // the declared function + name, sig := maybeInstance(prog, obj.Name(), sel.typ.(*types.Signature), targs) var recv *types.Var // wrapper's receiver or thunk's params[0] - name := obj.Name() - var description Synthetic - var start int // first regular param - if sel.Kind() == types.MethodExpr { + var description string + if sel.kind == types.MethodExpr { name += "$thunk" - description = SyntheticThunk + description = "thunk" recv = sig.Params().At(0) - start = 1 } else { - description = SyntheticWrapper + description = "wrapper" recv = sig.Recv() } + description = fmt.Sprintf("%s for %s", description, obj) if prog.mode&LogSource != 0 { - defer logStack("make %s to (%s)", description, recv.Type())() + defer logStack("create %s to (%s)", description, recv.Type())() } - fn := &Function{ - name: name, - method: sel, - object: obj, - Signature: sig, - Synthetic: description, - Prog: prog, - functionBody: new(functionBody), + /* method wrapper */ + return &Function{ + name: name, + method: sel, + object: obj, + Signature: sig, + Synthetic: description, + Prog: prog, + pos: obj.Pos(), + typeargs: targs, + // wrappers have no syntax + build: (*builder).buildWrapper, + info: nil, + goversion: "", + } +} + +// maybeInstance returns name and sig instantiated to reflect any type arguments in targs. +func maybeInstance(prog *Program, name string, sig *types.Signature, targs []types.Type) (string, *types.Signature) { + if len(targs) > 0 { + name = fmt.Sprintf("%s%s", name, targstr(targs)) + instSig, err := types.Instantiate(prog.ctxt, sig, targs, false) + if err != nil { + // validate was false, we should never get an error + panic(err) + } + sig = prog.canon.Type(instSig).(*types.Signature) + } + return name, sig +} + +// buildWrapper builds fn.Body for a method wrapper. +func (b *builder) buildWrapper(fn *Function) { + var recv *types.Var // wrapper's receiver or thunk's params[0] + var start int // first regular param + if fn.method.kind == types.MethodExpr { + recv = fn.Signature.Params().At(0) + start = 1 + } else { + recv = fn.Signature.Recv() } - fn.initHTML(prog.PrintFunc) + fn.startBody() fn.addSpilledParam(recv, nil) createParams(fn, start) - indices := sel.Index() + indices := fn.method.index var v Value = fn.Locals[0] // spilled receiver - if isPointer(sel.Recv()) { + if isPointer(fn.method.recv) { v = emitLoad(fn, v, nil) // For simple indirection wrappers, perform an informative nil-check: // "value method (T).f called using nil *T pointer" - if len(indices) == 1 && !isPointer(recvType(obj)) { + if len(indices) == 1 && !isPointer(recvType(fn.object)) { var c Call c.Call.Value = &Builtin{ - name: "ir:wrapnilchk", - sig: types.NewSignatureType(nil, nil, nil, - types.NewTuple(anonVar(sel.Recv()), anonVar(tString), anonVar(tString)), - types.NewTuple(anonVar(sel.Recv())), false), + name: "ssa:wrapnilchk", + sig: types.NewSignatureType(nil, nil, nil, types.NewTuple(anonVar(fn.method.recv), anonVar(tString), anonVar(tString)), types.NewTuple(anonVar(fn.method.recv)), false), } c.Call.Args = []Value{ v, - emitConst(fn, stringConst(deref(sel.Recv()).String(), nil)), - emitConst(fn, stringConst(sel.Obj().Name(), nil)), + stringConst(deref(fn.method.recv).String(), nil), + stringConst(fn.method.obj.Name(), nil), } c.setType(v.Type()) v = fn.emit(&c, nil) @@ -116,22 +143,21 @@ func makeWrapper(prog *Program, sel *types.Selection) *Function { // address of implicit C field. var c Call - if r := recvType(obj); !types.IsInterface(r) { // concrete method + if r := recvType(fn.object); !types.IsInterface(r) { // concrete method if !isPointer(r) { v = emitLoad(fn, v, nil) } - c.Call.Value = prog.declaredFunc(obj) + c.Call.Value = fn.Prog.objectMethod(fn.object, fn.typeargs, b) c.Call.Args = append(c.Call.Args, v) } else { - c.Call.Method = obj - c.Call.Value = emitLoad(fn, v, nil) + c.Call.Method = fn.object + c.Call.Value = emitLoad(fn, v, nil) // interface (possibly a typeparam) } for _, arg := range fn.Params[1:] { c.Call.Args = append(c.Call.Args, arg) } emitTailCall(fn, &c, nil) fn.finishBody() - return fn } // createParams creates parameters for wrapper method fn based on its @@ -146,7 +172,7 @@ func createParams(fn *Function, start int) { // -- bounds ----------------------------------------------------------- -// makeBound returns a bound method wrapper (or "bound"), a synthetic +// createBound returns a bound method wrapper (or "bound"), a synthetic // function that delegates to a concrete or interface method denoted // by obj. The resulting function has no receiver, but has one free // variable which will be used as the method's receiver in the @@ -165,56 +191,63 @@ func createParams(fn *Function, start int) { // // f := func() { return t.meth() } // -// Unlike makeWrapper, makeBound need perform no indirection or field +// Unlike createWrapper, createBound need perform no indirection or field // selections because that can be done before the closure is // constructed. -// -// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu) -func makeBound(prog *Program, obj *types.Func) *Function { - prog.methodsMu.Lock() - defer prog.methodsMu.Unlock() +func createBound(prog *Program, obj *types.Func, targs []types.Type) *Function { + description := fmt.Sprintf("bound method wrapper for %s", obj) if prog.mode&LogSource != 0 { - defer logStack("%s", SyntheticBound)() + defer logStack("%s", description)() } + name, sig := maybeInstance(prog, obj.Name(), obj.Type().(*types.Signature), targs) + + /* bound method wrapper */ fn := &Function{ - name: obj.Name() + "$bound", - object: obj, - Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver - Synthetic: SyntheticBound, - Prog: prog, - functionBody: new(functionBody), + name: name + "$bound", + object: obj, + Signature: changeRecv(sig, nil), // drop receiver + Synthetic: description, + Prog: prog, + pos: obj.Pos(), + typeargs: targs, + // wrappers have no syntax + build: (*builder).buildBound, + info: nil, + goversion: "", } - fn.initHTML(prog.PrintFunc) + fn.FreeVars = []*FreeVar{{name: "recv", typ: recvType(obj), parent: fn}} // (cyclic) + return fn +} - fv := &FreeVar{name: "recv", typ: recvType(obj), parent: fn} - fn.FreeVars = []*FreeVar{fv} +// buildBound builds fn.Body for a bound method closure. +func (b *builder) buildBound(fn *Function) { fn.startBody() createParams(fn, 0) var c Call - if !types.IsInterface(recvType(obj)) { // concrete - c.Call.Value = prog.declaredFunc(obj) - c.Call.Args = []Value{fv} + recv := fn.FreeVars[0] + if !types.IsInterface(recvType(fn.object)) { // concrete + c.Call.Value = fn.Prog.objectMethod(fn.object, fn.typeargs, b) + c.Call.Args = []Value{recv} } else { - c.Call.Value = fv - c.Call.Method = obj + c.Call.Method = fn.object + c.Call.Value = recv // interface (possibly a typeparam) } for _, arg := range fn.Params { c.Call.Args = append(c.Call.Args, arg) } emitTailCall(fn, &c, nil) fn.finishBody() - return fn } // -- thunks ----------------------------------------------------------- -// makeThunk returns a thunk, a synthetic function that delegates to a -// concrete or interface method denoted by sel.Obj(). The resulting +// createThunk returns a thunk, a synthetic function that delegates to a +// concrete or interface method denoted by sel.obj. The resulting // function has no receiver, but has an additional (first) regular // parameter. // -// Precondition: sel.Kind() == types.MethodExpr. +// Precondition: sel.kind == types.MethodExpr. // // type T int or: type T interface { meth() } // func (t T) meth() @@ -225,17 +258,12 @@ func makeBound(prog *Program, obj *types.Func) *Function { // f is a synthetic wrapper defined as if by: // // f := func(t T) { return t.meth() } -// -// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu) -func makeThunk(prog *Program, sel *types.Selection) *Function { - if sel.Kind() != types.MethodExpr { +func createThunk(prog *Program, sel *selection, targs []types.Type) *Function { + if sel.kind != types.MethodExpr { panic(sel) } - prog.methodsMu.Lock() - defer prog.methodsMu.Unlock() - - fn := makeWrapper(prog, sel) + fn := createWrapper(prog, sel, targs) if fn.Signature.Recv() != nil { panic(fn) // unexpected receiver } @@ -246,97 +274,84 @@ func changeRecv(s *types.Signature, recv *types.Var) *types.Signature { return types.NewSignatureType(recv, nil, nil, s.Params(), s.Results(), s.Variadic()) } -// makeInstance creates a wrapper function with signature sig that calls the generic function fn. -// If targs is not nil, fn is a function and targs describes the concrete type arguments. -// If targs is nil, fn is a method and the type arguments are derived from the receiver. -func makeInstance(prog *Program, fn *Function, sig *types.Signature, targs *types.TypeList) *Function { - if sig.Recv() != nil { - assert(targs == nil) - // Methods don't have their own type parameters, but the receiver does - targs = types.Unalias(deref(sig.Recv().Type())).(*types.Named).TypeArgs() - } else { - assert(targs != nil) - } +// A local version of *types.Selection. +// Needed for some additional control, such as creating a MethodExpr for an instantiation. +type selection struct { + kind types.SelectionKind + recv types.Type + typ types.Type + obj types.Object + index []int + indirect bool +} - wrapper := fn.generics.At(targs) - if wrapper != nil { - return wrapper +func toSelection(sel *types.Selection) *selection { + return &selection{ + kind: sel.Kind(), + recv: sel.Recv(), + typ: sel.Type(), + obj: sel.Obj(), + index: sel.Index(), + indirect: sel.Indirect(), } +} - var name string - if sig.Recv() != nil { - name = fn.name - } else { - name = fmt.Sprintf("%s$generic#%d", fn.name, fn.generics.Len()) - } - w := &Function{ - name: name, - object: fn.object, - Signature: sig, - Synthetic: SyntheticGeneric, - Prog: prog, - functionBody: new(functionBody), - } - w.initHTML(prog.PrintFunc) - w.startBody() +// -- instantiations -------------------------------------------------- + +// buildInstantiationWrapper builds the body of an instantiation +// wrapper fn. The body calls the original generic function, +// bracketed by ChangeType conversions on its arguments and results. +func (b *builder) buildInstantiationWrapper(fn *Function) { + orig := fn.topLevelOrigin + sig := fn.Signature + + fn.startBody() if sig.Recv() != nil { - w.addParamVar(sig.Recv(), nil) + fn.addParamVar(sig.Recv(), nil) } - createParams(w, 0) + createParams(fn, 0) + + // Create body. Add a call to origin generic function + // and make type changes between argument and parameters, + // as well as return values. var c Call - c.Call.Value = fn - tresults := fn.Signature.Results() - if tresults.Len() == 1 { - c.typ = tresults.At(0).Type() + c.Call.Value = orig + if res := orig.Signature.Results(); res.Len() == 1 { + c.typ = res.At(0).Type() } else { - c.typ = tresults - } - - changeType := func(v Value, typ types.Type) Value { - if types.Identical(v.Type(), typ) { - return v - } - var c ChangeType - c.X = v - c.typ = typ - return w.emit(&c, nil) + c.typ = res } - for i, arg := range w.Params { - if sig.Recv() != nil { - if i == 0 { - c.Call.Args = append(c.Call.Args, changeType(w.Params[0], fn.Signature.Recv().Type())) - } else { - c.Call.Args = append(c.Call.Args, changeType(arg, fn.Signature.Params().At(i-1).Type())) - } + // parameter of instance becomes an argument to the call + // to the original generic function. + argOffset := 0 + for i, arg := range fn.Params { + var typ types.Type + if i == 0 && sig.Recv() != nil { + typ = orig.Signature.Recv().Type() + argOffset = 1 } else { - c.Call.Args = append(c.Call.Args, changeType(arg, fn.Signature.Params().At(i).Type())) + typ = orig.Signature.Params().At(i - argOffset).Type() } + c.Call.Args = append(c.Call.Args, emitTypeCoercion(fn, arg, typ, nil)) } - for arg := range targs.Types() { - c.Call.TypeArgs = append(c.Call.TypeArgs, arg) - } - results := w.emit(&c, nil) + + results := fn.emit(&c, nil) var ret Return - switch tresults.Len() { + switch res := sig.Results(); res.Len() { case 0: + // no results, do nothing. case 1: - ret.Results = []Value{changeType(results, sig.Results().At(0).Type())} + ret.Results = []Value{emitTypeCoercion(fn, results, res.At(0).Type(), nil)} default: - for i := 0; i < tresults.Len(); i++ { - v := emitExtract(w, results, i, nil) - ret.Results = append(ret.Results, changeType(v, sig.Results().At(i).Type())) + for i := 0; i < sig.Results().Len(); i++ { + v := emitExtract(fn, results, i, nil) + ret.Results = append(ret.Results, emitTypeCoercion(fn, v, res.At(i).Type(), nil)) } } - w.Exit = w.newBasicBlock("exit") - emitJump(w, w.Exit, nil) - w.currentBlock = w.Exit - w.emit(&ret, nil) - w.currentBlock = nil + fn.emit(&ret, nil) + fn.currentBlock = nil - w.finishBody() - - fn.generics.Set(targs, w) - return w + fn.finishBody() } diff --git a/tools/vendor/honnef.co/go/tools/go/ir/write.go b/tools/vendor/honnef.co/go/tools/go/ir/write.go deleted file mode 100644 index 139c8cf32..000000000 --- a/tools/vendor/honnef.co/go/tools/go/ir/write.go +++ /dev/null @@ -1,5 +0,0 @@ -package ir - -func NewJump(parent *BasicBlock) *Jump { - return &Jump{anInstruction{block: parent}} -} diff --git a/tools/vendor/honnef.co/go/tools/go/types/typeutil/typeparams.go b/tools/vendor/honnef.co/go/tools/go/types/typeutil/typeparams.go index 15b20a74f..a53c0955d 100644 --- a/tools/vendor/honnef.co/go/tools/go/types/typeutil/typeparams.go +++ b/tools/vendor/honnef.co/go/tools/go/types/typeutil/typeparams.go @@ -49,6 +49,9 @@ func (ts TypeSet) CoreType() types.Type { if !ok { return nil } + if !types.Identical(ch1.Elem(), ch2.Elem()) { + return nil + } if ch1.Dir() == types.SendRecv { // typ is currently a bidirectional channel. The term's type is either also bidirectional, or // unidirectional. Use the term's type. @@ -100,7 +103,12 @@ func Any(typ types.Type, fn func(*types.Term) bool) bool { return NewTypeSet(typ).Any(fn) } -func IsSlice(term *types.Term) bool { - _, ok := term.Type().Underlying().(*types.Slice) +func IsType[T types.Type](term *types.Term) bool { + _, ok := term.Type().Underlying().(T) return ok } + +//go:fix inline +func IsSlice(term *types.Term) bool { + return IsType[*types.Slice](term) +} diff --git a/tools/vendor/honnef.co/go/tools/go/types/typeutil/unify.go b/tools/vendor/honnef.co/go/tools/go/types/typeutil/unify.go new file mode 100644 index 000000000..ced507caa --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/go/types/typeutil/unify.go @@ -0,0 +1,345 @@ +// 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. + +// The code in this file is copied from +// x/tools/gopls/internal/golang/implementation.go + +package typeutil + +import ( + "fmt" + "go/types" + "iter" + "reflect" +) + +// Unify reports whether the types of x and y match. +// +// If unifier is nil, unify reports only whether it succeeded. +// If unifier is non-nil, it is populated with the values +// of type parameters determined during a successful unification. +// If unification succeeds without binding a type parameter, that parameter +// will not be present in the map. +// +// On entry, the unifier's contents are treated as the values of already-bound type +// parameters, constraining the unification. +// +// For example, if unifier is an empty (not nil) map on entry, then the types +// +// func[T any](T, int) +// +// and +// +// func[U any](bool, U) +// +// will unify, with T=bool and U=int. +// That is, the contents of unifier after unify returns will be +// +// {T: bool, U: int} +// +// where "T" is the type parameter T and "bool" is the basic type for bool. +// +// But if unifier is {T: int} is int on entry, then unification will fail, because T +// does not unify with bool. +// +// Unify does not preserve aliases. For example, given the following: +// +// type String = string +// type A[T] = T +// +// unification succeeds with T bound to string, not String. +// +// See also: unify in cache/methodsets/fingerprint, which implements +// unification for type fingerprints, for the global index. +// +// BUG: literal interfaces are not handled properly. But this function is currently +// used only for signatures, where such types are very rare. +func Unify(x, y types.Type, unifier map[*types.TypeParam]types.Type) bool { + // bindings[tp] is the binding for type parameter tp. + // Although type parameters are nominally bound to types, each bindings[tp] + // is a pointer to a type, so unbound variables that unify can share a binding. + bindings := map[*types.TypeParam]*types.Type{} + + // Bindings is initialized with pointers to the provided types. + for tp, t := range unifier { + bindings[tp] = &t + } + + // bindingFor returns the *types.Type in bindings for tp if tp is not nil, + // creating one if needed. + bindingFor := func(tp *types.TypeParam) *types.Type { + if tp == nil { + return nil + } + b := bindings[tp] + if b == nil { + b = new(types.Type) + bindings[tp] = b + } + return b + } + + // bind sets b to t if b does not occur in t. + bind := func(b *types.Type, t types.Type) bool { + for tp := range typeParams(t) { + if b == bindings[tp] { + return false // failed "occurs" check + } + } + *b = t + return true + } + + // uni performs the actual unification. + depth := 0 + var uni func(x, y types.Type) bool + uni = func(x, y types.Type) bool { + // Panic if recursion gets too deep, to detect bugs before + // overflowing the stack. + depth++ + defer func() { depth-- }() + if depth > 100 { + panic("unify: max depth exceeded") + } + + x = types.Unalias(x) + y = types.Unalias(y) + + tpx, _ := x.(*types.TypeParam) + tpy, _ := y.(*types.TypeParam) + if tpx != nil || tpy != nil { + // Identical type params unify. + if tpx == tpy { + return true + } + bx := bindingFor(tpx) + by := bindingFor(tpy) + + // If both args are type params and neither is bound, have them share a binding. + if bx != nil && by != nil && *bx == nil && *by == nil { + // Arbitrarily give y's binding to x. + bindings[tpx] = by + return true + } + // Treat param bindings like original args in what follows. + if bx != nil && *bx != nil { + x = *bx + } + if by != nil && *by != nil { + y = *by + } + // If the x param is unbound, bind it to y. + if bx != nil && *bx == nil { + return bind(bx, y) + } + // If the y param is unbound, bind it to x. + if by != nil && *by == nil { + return bind(by, x) + } + // Unify the binding of a bound parameter. + return uni(x, y) + } + + // Neither arg is a type param. + + if reflect.TypeOf(x) != reflect.TypeOf(y) { + return false // mismatched types + } + + switch x := x.(type) { + case *types.Array: + y := y.(*types.Array) + return x.Len() == y.Len() && + uni(x.Elem(), y.Elem()) + + case *types.Basic: + y := y.(*types.Basic) + return x.Kind() == y.Kind() + + case *types.Chan: + y := y.(*types.Chan) + return x.Dir() == y.Dir() && + uni(x.Elem(), y.Elem()) + + case *types.Interface: + y := y.(*types.Interface) + // TODO(adonovan,jba): fix: for correctness, we must check + // that both interfaces have the same set of methods + // modulo type parameters, while avoiding the risk of + // unbounded interface recursion. + // + // Since non-empty interface literals are vanishingly + // rare in methods signatures, we ignore this for now. + // If more precision is needed we could compare method + // names and arities, still without full recursion. + return x.NumMethods() == y.NumMethods() + + case *types.Map: + y := y.(*types.Map) + return uni(x.Key(), y.Key()) && + uni(x.Elem(), y.Elem()) + + case *types.Named: + y := y.(*types.Named) + if x.Origin() != y.Origin() { + return false // different named types + } + xtargs := x.TypeArgs() + ytargs := y.TypeArgs() + if xtargs.Len() != ytargs.Len() { + return false // arity error (ill-typed) + } + for i := range xtargs.Len() { + if !uni(xtargs.At(i), ytargs.At(i)) { + return false // mismatched type args + } + } + return true + + case *types.Pointer: + y := y.(*types.Pointer) + return uni(x.Elem(), y.Elem()) + + case *types.Signature: + y := y.(*types.Signature) + return x.Variadic() == y.Variadic() && + uni(x.Params(), y.Params()) && + uni(x.Results(), y.Results()) + + case *types.Slice: + y := y.(*types.Slice) + return uni(x.Elem(), y.Elem()) + + case *types.Struct: + y := y.(*types.Struct) + if x.NumFields() != y.NumFields() { + return false + } + for i := range x.NumFields() { + xf := x.Field(i) + yf := y.Field(i) + if xf.Embedded() != yf.Embedded() || + xf.Name() != yf.Name() || + x.Tag(i) != y.Tag(i) || + !xf.Exported() && xf.Pkg() != yf.Pkg() || + !uni(xf.Type(), yf.Type()) { + return false + } + } + return true + + case *types.Tuple: + y := y.(*types.Tuple) + if x.Len() != y.Len() { + return false + } + for i := range x.Len() { + if !uni(x.At(i).Type(), y.At(i).Type()) { + return false + } + } + return true + + default: // incl. *Union, *TypeParam + panic(fmt.Sprintf("unexpected Type %#v", x)) + } + } + + if !uni(x, y) { + clear(unifier) + return false + } + + // Populate the input map with the resulting types. + if unifier != nil { + for tparam, tptr := range bindings { + unifier[tparam] = *tptr + } + } + return true +} + +// typeParams yields all the free type parameters within t that are relevant for +// unification. +// +// Note: this function is tailored for the specific needs of the unification algorithm. +// Don't try to use it for other purposes, see [typeparams.Free] instead. +func typeParams(t types.Type) iter.Seq[*types.TypeParam] { + return func(yield func(*types.TypeParam) bool) { + seen := map[*types.TypeParam]bool{} // yield each type param only once + + // tps(t) yields each TypeParam in t and returns false to stop. + var tps func(types.Type) bool + tps = func(t types.Type) bool { + t = types.Unalias(t) + + switch t := t.(type) { + case *types.TypeParam: + if seen[t] { + return true + } + seen[t] = true + return yield(t) + + case *types.Basic: + return true + + case *types.Array: + return tps(t.Elem()) + + case *types.Chan: + return tps(t.Elem()) + + case *types.Interface: + // TODO(jba): implement. + return true + + case *types.Map: + return tps(t.Key()) && tps(t.Elem()) + + case *types.Named: + if t.Origin() == t { + // generic type: look at type params + return every(t.TypeParams().TypeParams(), + func(tp *types.TypeParam) bool { return tps(tp) }) + } + // instantiated type: look at type args + return every(t.TypeArgs().Types(), tps) + + case *types.Pointer: + return tps(t.Elem()) + + case *types.Signature: + return tps(t.Params()) && tps(t.Results()) + + case *types.Slice: + return tps(t.Elem()) + + case *types.Struct: + return every(t.Fields(), + func(v *types.Var) bool { return tps(v.Type()) }) + + case *types.Tuple: + return every(t.Variables(), + func(v *types.Var) bool { return tps(v.Type()) }) + + default: // incl. *Union + panic(fmt.Sprintf("unexpected Type %#v", t)) + } + } + + tps(t) + } +} + +// every reports whether every pred(t) for t in seq returns true, +// stopping at the first false element. +func every[T any](seq iter.Seq[T], pred func(T) bool) bool { + for t := range seq { + if !pred(t) { + return false + } + } + return true +} diff --git a/tools/vendor/honnef.co/go/tools/go/types/typeutil/util.go b/tools/vendor/honnef.co/go/tools/go/types/typeutil/util.go index bb506622b..ffbd4837c 100644 --- a/tools/vendor/honnef.co/go/tools/go/types/typeutil/util.go +++ b/tools/vendor/honnef.co/go/tools/go/types/typeutil/util.go @@ -144,9 +144,10 @@ func IsTypeWithName(typ types.Type, name string) bool { } } -// IsPointerLike returns true if type T is like a pointer. This returns true for all nillable types, -// unsafe.Pointer, and type sets where at least one term is pointer-like. -func IsPointerLike(T types.Type) bool { +// MaybePointerLike returns true if type T is like a pointer. This returns true +// for all nillable types, unsafe.Pointer, type sets where at least one term is +// pointer-like, and type sets without structural restrictions.. +func MaybePointerLike(T types.Type) bool { switch T := T.Underlying().(type) { case *types.Interface: if T.IsMethodSet() { @@ -157,11 +158,11 @@ func IsPointerLike(T types.Type) bool { return false } for _, term := range terms { - if IsPointerLike(term.Type()) { + if MaybePointerLike(term.Type()) { return true } } - return false + return len(terms) == 0 } case *types.Chan, *types.Map, *types.Signature, *types.Pointer, *types.Slice: return true diff --git a/tools/vendor/honnef.co/go/tools/internal/iterutil/iterutil.go b/tools/vendor/honnef.co/go/tools/internal/iterutil/iterutil.go new file mode 100644 index 000000000..e2f1f093b --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/iterutil/iterutil.go @@ -0,0 +1,21 @@ +package iterutil + +import "iter" + +func All[T any](seq iter.Seq[T], fn func(T) bool) bool { + for v := range seq { + if !fn(v) { + return false + } + } + return true +} + +func Any[T any](seq iter.Seq[T], fn func(T) bool) bool { + for v := range seq { + if fn(v) { + return true + } + } + return false +} diff --git a/tools/vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go b/tools/vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go index 5db18e305..1e2cc4938 100644 --- a/tools/vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go +++ b/tools/vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go @@ -11,7 +11,6 @@ package buildir import ( - "go/types" "reflect" "honnef.co/go/tools/go/ir" @@ -63,20 +62,10 @@ func run(pass *analysis.Pass) (any, error) { prog.SetNoReturn(cfgs.NoReturn) - // Create IR packages for all imports. - // Order is not significant. - created := make(map[*types.Package]bool) - var createAll func(pkgs []*types.Package) - createAll = func(pkgs []*types.Package) { - for _, p := range pkgs { - if !created[p] { - created[p] = true - prog.CreatePackage(p, nil, nil, true) - createAll(p.Imports()) - } - } + // Create IR packages for direct imports. + for _, p := range pass.Pkg.Imports() { + prog.CreatePackage(p, nil, nil, true) } - createAll(pass.Pkg.Imports()) // Create and build the primary package. irpkg := prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false) diff --git a/tools/vendor/honnef.co/go/tools/internal/sharedcheck/lint.go b/tools/vendor/honnef.co/go/tools/internal/sharedcheck/lint.go index f07496dd2..6bae3f786 100644 --- a/tools/vendor/honnef.co/go/tools/internal/sharedcheck/lint.go +++ b/tools/vendor/honnef.co/go/tools/internal/sharedcheck/lint.go @@ -13,7 +13,6 @@ import ( "honnef.co/go/tools/analysis/report" "honnef.co/go/tools/go/ast/astutil" "honnef.co/go/tools/go/ir" - "honnef.co/go/tools/go/ir/irutil" "honnef.co/go/tools/go/types/typeutil" "honnef.co/go/tools/internal/passes/buildir" @@ -58,7 +57,7 @@ func CheckRangeStringRunes(pass *analysis.Pass) (any, error) { // Expect two refs: one for obtaining the length of the slice, // one for accessing the elements - if len(irutil.FilterDebug(*refs)) != 2 { + if len(*refs) != 2 { // TODO(dh): right now, we check that only one place // refers to our slice. This will miss cases such as // ranging over the slice twice. Ideally, we'd ensure that diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/aliases/aliases.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/aliases/aliases.go new file mode 100644 index 000000000..a4ae04bc7 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/aliases/aliases.go @@ -0,0 +1,18 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package aliases + +import ( + "go/token" + "go/types" +) + +// New creates a new TypeName in Package pkg that +// is an alias for the type rhs. +func New(pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName { + tname := types.NewTypeName(pos, pkg, name, nil) + types.NewAlias(tname, rhs).SetTypeParams(tparams) + return tname +} diff --git a/tools/vendor/honnef.co/go/tools/internal/analysisinternal/typeindex/typeindex.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/analysis/typeindex/typeindex.go similarity index 80% rename from tools/vendor/honnef.co/go/tools/internal/analysisinternal/typeindex/typeindex.go rename to tools/vendor/honnef.co/go/tools/internal/xtools-internal/analysis/typeindex/typeindex.go index 44d207c0c..1bd7b78e8 100644 --- a/tools/vendor/honnef.co/go/tools/internal/analysisinternal/typeindex/typeindex.go +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/analysis/typeindex/typeindex.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Package typeindex defines an analyzer that provides a -// [golang.org/x/tools/internal/typesinternal/typeindex.Index]. +// [honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex.Index]. // // Like [golang.org/x/tools/go/analysis/passes/inspect], it is // intended to be used as a helper by other analyzers; it reports no @@ -16,13 +16,13 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" - "honnef.co/go/tools/internal/typesinternal/typeindex" + "honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex" ) var Analyzer = &analysis.Analyzer{ Name: "typeindex", Doc: "indexes of type information for later passes", - URL: "https://pkg.go.dev/golang.org/x/tools/internal/analysisinternal/typeindex", + URL: "https://pkg.go.dev/honnef.co/go/tools/internal/xtools-internal/analysis/typeindex", Run: func(pass *analysis.Pass) (any, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) return typeindex.New(inspect, pass.Pkg, pass.TypesInfo), nil diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/clone.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/clone.go new file mode 100644 index 000000000..2c9b6bb48 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/clone.go @@ -0,0 +1,71 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "go/ast" + "reflect" +) + +// CloneNode returns a deep copy of a Node. +// It omits pointers to ast.{Scope,Object} variables. +func CloneNode[T ast.Node](n T) T { + return cloneNode(n).(T) +} + +func cloneNode(n ast.Node) ast.Node { + var clone func(x reflect.Value) reflect.Value + set := func(dst, src reflect.Value) { + src = clone(src) + if src.IsValid() { + dst.Set(src) + } + } + clone = func(x reflect.Value) reflect.Value { + switch x.Kind() { + case reflect.Pointer: + if x.IsNil() { + return x + } + // Skip fields of types potentially involved in cycles. + switch x.Interface().(type) { + case *ast.Object, *ast.Scope: + return reflect.Zero(x.Type()) + } + y := reflect.New(x.Type().Elem()) + set(y.Elem(), x.Elem()) + return y + + case reflect.Struct: + y := reflect.New(x.Type()).Elem() + for i := 0; i < x.Type().NumField(); i++ { + set(y.Field(i), x.Field(i)) + } + return y + + case reflect.Slice: + if x.IsNil() { + return x + } + y := reflect.MakeSlice(x.Type(), x.Len(), x.Cap()) + for i := 0; i < x.Len(); i++ { + set(y.Index(i), x.Index(i)) + } + return y + + case reflect.Interface: + y := reflect.New(x.Type()).Elem() + set(y, x.Elem()) + return y + + case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.UnsafePointer: + panic(x) // unreachable in AST + + default: + return x // bool, string, number + } + } + return clone(reflect.ValueOf(n)).Interface().(ast.Node) +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/comment.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/comment.go new file mode 100644 index 000000000..40a347214 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/comment.go @@ -0,0 +1,143 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "go/ast" + "go/token" + "iter" + "sort" + "strings" +) + +// Deprecation returns the paragraph of the doc comment that starts with the +// conventional "Deprecation: " marker, as defined by +// https://go.dev/wiki/Deprecated, or "" if the documented symbol is not +// deprecated. +func Deprecation(doc *ast.CommentGroup) string { + for p := range strings.SplitSeq(doc.Text(), "\n\n") { + // There is still some ambiguity for deprecation message. This function + // only returns the paragraph introduced by "Deprecated: ". More + // information related to the deprecation may follow in additional + // paragraphs, but the deprecation message should be able to stand on + // its own. See golang/go#38743. + if strings.HasPrefix(p, "Deprecated: ") { + return p + } + } + return "" +} + +// -- plundered from the future (CL 605517, issue #68021) -- + +// TODO(adonovan): replace with ast.Directive in go1.26 (#68021). +// Beware of our local mods to handle analysistest +// "want" comments on the same line. + +// A directive is a comment line with special meaning to the Go +// toolchain or another tool. It has the form: +// +// //tool:name args +// +// The "tool:" portion is missing for the three directives named +// line, extern, and export. +// +// See https://go.dev/doc/comment#Syntax for details of Go comment +// syntax and https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives +// for details of directives used by the Go compiler. +type Directive struct { + Pos token.Pos // of preceding "//" + Tool string + Name string + Args string // may contain internal spaces +} + +// isDirective reports whether c is a comment directive. +// This code is also in go/printer. +func isDirective(c string) bool { + // "//line " is a line directive. + // "//extern " is for gccgo. + // "//export " is for cgo. + // (The // has been removed.) + if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") { + 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 +} + +// Directives returns the directives within the comment. +func Directives(g *ast.CommentGroup) (res []*Directive) { + if g != nil { + // Avoid (*ast.CommentGroup).Text() as it swallows directives. + for _, c := range g.List { + if len(c.Text) > 2 && + c.Text[1] == '/' && + c.Text[2] != ' ' && + isDirective(c.Text[2:]) { + + tool, nameargs, ok := strings.Cut(c.Text[2:], ":") + if !ok { + // Must be one of {line,extern,export}. + tool, nameargs = "", tool + } + name, args, _ := strings.Cut(nameargs, " ") // tab?? + // Permit an additional line comment after the args, chiefly to support + // [golang.org/x/tools/go/analysis/analysistest]. + args, _, _ = strings.Cut(args, "//") + res = append(res, &Directive{ + Pos: c.Slash, + Tool: tool, + Name: name, + Args: strings.TrimSpace(args), + }) + } + } + } + return +} + +// Comments returns an iterator over the comments overlapping the specified interval. +// Comments are sorted by position in the file, so we can use binary search. +func Comments(file *ast.File, start, end token.Pos) iter.Seq[*ast.Comment] { + return func(yield func(*ast.Comment) bool) { + // Find the first comment group that overlaps the range. + i := sort.Search(len(file.Comments), func(i int) bool { + return file.Comments[i].End() >= start + }) + for _, cg := range file.Comments[i:] { + if cg.Pos() > end { + return + } + // Find the first comment in the group that overlaps the range. + j := sort.Search(len(cg.List), func(j int) bool { + return cg.List[j].End() >= start + }) + for _, co := range cg.List[j:] { + if co.Pos() > end { + return + } + if !yield(co) { + return + } + } + } + } +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/cursor.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/cursor.go new file mode 100644 index 000000000..74e75d145 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/cursor.go @@ -0,0 +1,38 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "go/ast" + + "golang.org/x/tools/go/ast/edge" + "golang.org/x/tools/go/ast/inspector" +) + +// UnparenCursor returns the cursor for an expression with any +// enclosing parentheses removed, similar to [ast.Unparen]. +// It is often prudent to call this before switching on the +// type of cur.Node(). +// +// See also [UnparenEnclosingCursor]. +func UnparenCursor(cur inspector.Cursor) inspector.Cursor { + for is[*ast.ParenExpr](cur) { + cur, _ = cur.FirstChild() + } + return cur +} + +// UnparenEnclosingCursor returns the first element of +// the [Cursor.Enclosing] sequence that is not itself enclosed +// in parens. It is often prudent to call this before switching on +// cur.ParentEdge(). +// +// See also [UnparenCursor]. +func UnparenEnclosingCursor(cur inspector.Cursor) inspector.Cursor { + for cur.ParentEdgeKind() == edge.ParenExpr_X { + cur = cur.Parent() + } + return cur +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/equal.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/equal.go new file mode 100644 index 000000000..210f39238 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/equal.go @@ -0,0 +1,107 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "go/ast" + "go/token" + "reflect" +) + +// Equal reports whether two nodes are structurally equal, +// ignoring fields of type [token.Pos], [ast.Object], +// and [ast.Scope], and comments. +// +// The operands x and y may be nil. +// A nil slice is not equal to an empty slice. +// +// The provided function determines whether two identifiers +// should be considered identical. +func Equal(x, y ast.Node, identical func(x, y *ast.Ident) bool) bool { + if x == nil || y == nil { + return x == y + } + return equal(reflect.ValueOf(x), reflect.ValueOf(y), identical) +} + +// EqualSyntax reports whether x and y are equal. +// Identifiers are considered equal if they are spelled the same. +// Comments are ignored. +func EqualSyntax(x, y ast.Expr) bool { + sameName := func(x, y *ast.Ident) bool { return x.Name == y.Name } + return Equal(x, y, sameName) +} + +func equal(x, y reflect.Value, identical func(x, y *ast.Ident) bool) bool { + // Ensure types are the same + if x.Type() != y.Type() { + return false + } + switch x.Kind() { + case reflect.Pointer: + if x.IsNil() || y.IsNil() { + return x.IsNil() == y.IsNil() + } + switch t := x.Interface().(type) { + // Skip fields of types potentially involved in cycles. + case *ast.Object, *ast.Scope, *ast.CommentGroup: + return true + case *ast.Ident: + return identical(t, y.Interface().(*ast.Ident)) + default: + return equal(x.Elem(), y.Elem(), identical) + } + + case reflect.Interface: + if x.IsNil() || y.IsNil() { + return x.IsNil() == y.IsNil() + } + return equal(x.Elem(), y.Elem(), identical) + + case reflect.Struct: + for i := range x.NumField() { + xf := x.Field(i) + yf := y.Field(i) + // Skip position fields. + if xpos, ok := xf.Interface().(token.Pos); ok { + ypos := yf.Interface().(token.Pos) + // Numeric value of a Pos is not significant but its "zeroness" is, + // because it is often significant, e.g. CallExpr.Variadic(Ellipsis), ChanType.Arrow. + if xpos.IsValid() != ypos.IsValid() { + return false + } + } else if !equal(xf, yf, identical) { + return false + } + } + return true + + case reflect.Slice: + if x.IsNil() || y.IsNil() { + return x.IsNil() == y.IsNil() + } + if x.Len() != y.Len() { + return false + } + for i := range x.Len() { + if !equal(x.Index(i), y.Index(i), identical) { + return false + } + } + return true + + case reflect.String: + return x.String() == y.String() + + case reflect.Bool: + return x.Bool() == y.Bool() + + case reflect.Int: + return x.Int() == y.Int() + + default: + panic(x) + } +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/fields.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/fields.go new file mode 100644 index 000000000..8b81ea47a --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/fields.go @@ -0,0 +1,35 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "go/ast" + "iter" +) + +// FlatFields 'flattens' an ast.FieldList, returning an iterator over each +// (name, field) combination in the list. For unnamed fields, the identifier is +// nil. +func FlatFields(list *ast.FieldList) iter.Seq2[*ast.Ident, *ast.Field] { + return func(yield func(*ast.Ident, *ast.Field) bool) { + if list == nil { + return + } + + for _, field := range list.List { + if len(field.Names) == 0 { + if !yield(nil, field) { + return + } + } else { + for _, name := range field.Names { + if !yield(name, field) { + return + } + } + } + } + } +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/purge.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/purge.go new file mode 100644 index 000000000..d0a39415d --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/purge.go @@ -0,0 +1,93 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package astutil provides various AST utility functions for gopls. +package astutil + +import ( + "bytes" + "go/scanner" + "go/token" +) + +// PurgeFuncBodies returns a copy of src in which the contents of each +// outermost {...} region have been deleted, except for struct and +// interface type bodies and the bodies of length-elided array +// literals ([...]T), whose element count is part of the type. It +// includes function bodies, function-literal bodies, and the bodies +// of slice, map, and explicitly-sized array composite literals (whose +// contents don't affect the type of the enclosing declaration). This +// reduces the amount of work required to parse the top-level +// declarations. +// +// PurgeFuncBodies does not preserve newlines or position information. +// Also, if the input is invalid, parsing the output of +// PurgeFuncBodies may result in a different tree due to its effects +// on parser error recovery. +func PurgeFuncBodies(src []byte) []byte { + // Destroy the content of any {...}-bracketed regions that are + // not immediately preceded by a "struct" or "interface" token, + // and that are not the body of a length-elided array literal. + // That includes function bodies, switch/select bodies, and most + // composite literals; this will lead to non-void functions that + // don't have return statements, which of course is a type error, + // but that's ok. + + var out bytes.Buffer + file := token.NewFileSet().AddFile("", -1, len(src)) + var sc scanner.Scanner + sc.Init(file, src, nil, 0) + var prev token.Token + var cursor int // last consumed src offset + var braces []token.Pos // stack of unclosed braces, or -1 for a region we preserve + var ellipsis bool // saw "[...]" not yet consumed by a literal-body "{" + for { + pos, tok, _ := sc.Scan() + if tok == token.EOF { + break + } + switch tok { + case token.COMMENT: + // TODO(adonovan): opt: skip, to save an estimated 20% of time. + + case token.SEMICOLON: + ellipsis = false + + case token.RBRACK: + // "...]" occurs only in the array-type prefix of a + // composite literal; variadic "..." is followed by + // a type or ")", never "]". + if prev == token.ELLIPSIS { + ellipsis = true + } + + case token.LBRACE: + if prev == token.STRUCT || prev == token.INTERFACE { + pos = -1 // type body: preserve (don't consume ellipsis) + } else if ellipsis { + pos = -1 // [...]T literal body: preserve + ellipsis = false + } + braces = append(braces, pos) + + case token.RBRACE: + if last := len(braces) - 1; last >= 0 { + top := braces[last] + braces = braces[:last] + if top < 0 { + // preserve + } else if len(braces) == 0 { // toplevel only + // Delete {...} body. + start := file.Offset(top) + end := file.Offset(pos) + out.Write(src[cursor : start+len("{")]) + cursor = end + } + } + } + prev = tok + } + out.Write(src[cursor:]) + return out.Bytes() +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/stringlit.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/stringlit.go new file mode 100644 index 000000000..eb49d4512 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/stringlit.go @@ -0,0 +1,103 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "fmt" + "go/ast" + "go/token" + "strconv" + "unicode/utf8" +) + +// RangeInStringLiteral calculates the positional range within a string literal +// corresponding to the specified start and end byte offsets within the logical string. +func RangeInStringLiteral(lit *ast.BasicLit, start, end int) (Range, error) { + startPos, err := PosInStringLiteral(lit, start) + if err != nil { + return Range{}, fmt.Errorf("start: %v", err) + } + endPos, err := PosInStringLiteral(lit, end) + if err != nil { + return Range{}, fmt.Errorf("end: %v", err) + } + return Range{startPos, endPos}, nil +} + +// PosInStringLiteral returns the position within a string literal +// corresponding to the specified byte offset within the logical +// string that it denotes. +func PosInStringLiteral(lit *ast.BasicLit, offset int) (token.Pos, error) { + raw := lit.Value + + value, err := strconv.Unquote(raw) + if err != nil { + return 0, err + } + if !(0 <= offset && offset <= len(value)) { + return 0, fmt.Errorf("invalid offset") + } + + pos, _ := walkStringLiteral(lit, lit.End(), offset) + return pos, nil +} + +// OffsetInStringLiteral returns the byte offset within the logical (unquoted) +// string corresponding to the specified source position. +func OffsetInStringLiteral(lit *ast.BasicLit, pos token.Pos) (int, error) { + if !NodeContainsPos(lit, pos) { + return 0, fmt.Errorf("invalid position") + } + + raw := lit.Value + + value, err := strconv.Unquote(raw) + if err != nil { + return 0, err + } + + _, offset := walkStringLiteral(lit, pos, len(value)) + return offset, nil +} + +// walkStringLiteral iterates through the raw string literal to map between +// a file position and a logical byte offset. It stops when it reaches +// either the targetPos or the targetOffset. +// +// TODO(hxjiang): consider making an iterator. +func walkStringLiteral(lit *ast.BasicLit, targetPos token.Pos, targetOffset int) (token.Pos, int) { + raw := lit.Value + norm := int(lit.End()-lit.Pos()) > len(lit.Value) + + // remove quotes + quote := raw[0] // '"' or '`' + raw = raw[1 : len(raw)-1] + + var ( + i = 0 // byte index within logical value + pos = lit.Pos() + 1 // position within literal + ) + + for raw != "" { + r, _, rest, _ := strconv.UnquoteChar(raw, quote) // can't fail + sz := len(raw) - len(rest) // length of literal char in raw bytes + + nextPos := pos + token.Pos(sz) + if norm && r == '\n' { + nextPos++ + } + nextI := i + utf8.RuneLen(r) // length of logical char in "cooked" bytes + + if nextPos > targetPos || nextI > targetOffset { + break + } + + raw = raw[sz:] + i = nextI + pos = nextPos + } + + return pos, i +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/unpack.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/unpack.go new file mode 100644 index 000000000..3d2fb04f7 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/unpack.go @@ -0,0 +1,61 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "go/ast" + + "honnef.co/go/tools/internal/xtools-internal/typeparams" +) + +// UnpackRecv unpacks a receiver type expression, reporting whether it is a +// pointer receiver, along with the type name identifier and any receiver type +// parameter identifiers. +// +// Copied (with modifications) from go/types. +func UnpackRecv(rtyp ast.Expr) (ptr bool, rname *ast.Ident, tparams []*ast.Ident) { +L: // unpack receiver type + // This accepts invalid receivers such as ***T and does not + // work for other invalid receivers, but we don't care. The + // validity of receiver expressions is checked elsewhere. + for { + switch t := rtyp.(type) { + case *ast.ParenExpr: + rtyp = t.X + case *ast.StarExpr: + ptr = true + rtyp = t.X + default: + break L + } + } + + // unpack type parameters, if any + switch rtyp.(type) { + case *ast.IndexExpr, *ast.IndexListExpr: + var indices []ast.Expr + rtyp, _, indices, _ = typeparams.UnpackIndexExpr(rtyp) + for _, arg := range indices { + var par *ast.Ident + switch arg := arg.(type) { + case *ast.Ident: + par = arg + default: + // ignore errors + } + if par == nil { + par = &ast.Ident{NamePos: arg.Pos(), Name: "_"} + } + tparams = append(tparams, par) + } + } + + // unpack receiver name + if name, _ := rtyp.(*ast.Ident); name != nil { + rname = name + } + + return +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/util.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/util.go new file mode 100644 index 000000000..6d974db68 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/util.go @@ -0,0 +1,261 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "fmt" + "go/ast" + "go/printer" + "go/token" + "strings" + + "golang.org/x/tools/go/ast/inspector" + "honnef.co/go/tools/internal/xtools-internal/moreiters" +) + +// NodeContains reports whether the Pos/End range of node n encloses +// the given range. +// +// It is inclusive of both end points, to allow hovering (etc) when +// the cursor is immediately after a node. +// +// Like [NodeRange], it treats the range of an [ast.File] as the +// file's complete extent. +// +// Precondition: n must not be nil. +func NodeContains(n ast.Node, rng Range) bool { + return NodeRange(n).Contains(rng) +} + +// NodeContainsPos reports whether the Pos/End range of node n encloses +// the given pos. +// +// Like [NodeRange], it treats the range of an [ast.File] as the +// file's complete extent. +func NodeContainsPos(n ast.Node, pos token.Pos) bool { + return NodeRange(n).ContainsPos(pos) +} + +// EnclosingFile returns the syntax tree for the file enclosing c. +// +// TODO(adonovan): promote this to a method of Cursor. +func EnclosingFile(c inspector.Cursor) *ast.File { + c, _ = moreiters.First(c.Enclosing((*ast.File)(nil))) + return c.Node().(*ast.File) +} + +// DocComment returns the doc comment for a node, if any. +func DocComment(n ast.Node) *ast.CommentGroup { + switch n := n.(type) { + case *ast.FuncDecl: + return n.Doc + case *ast.GenDecl: + return n.Doc + case *ast.ValueSpec: + return n.Doc + case *ast.TypeSpec: + return n.Doc + case *ast.File: + return n.Doc + case *ast.ImportSpec: + return n.Doc + case *ast.Field: + return n.Doc + } + return nil +} + +// Format returns a string representation of the node n. +func Format(fset *token.FileSet, n ast.Node) string { + var buf strings.Builder + printer.Fprint(&buf, fset, n) // ignore errors + return buf.String() +} + +// -- Range -- + +// Range is a Pos interval. +// It implements [analysis.Range] and [ast.Node]. +type Range struct{ Start, EndPos token.Pos } + +// RangeOf constructs a Range. +// +// RangeOf exists to pacify the "unkeyed literal" (composites) vet +// check. It would be nice if there were a way for a type to add +// itself to the allowlist. +func RangeOf(start, end token.Pos) Range { return Range{start, end} } + +// NodeRange returns the extent of node n as a Range. +// +// For unfortunate historical reasons, the Pos/End extent of an +// ast.File runs from the start of its package declaration---excluding +// copyright comments, build tags, and package documentation---to the +// end of its last declaration, excluding any trailing comments. So, +// as a special case, if n is an [ast.File], NodeContains uses +// n.FileStart <= pos && pos <= n.FileEnd to report whether the +// position lies anywhere within the file. +func NodeRange(n ast.Node) Range { + if file, ok := n.(*ast.File); ok { + return Range{file.FileStart, file.FileEnd} // entire file + } + return Range{n.Pos(), n.End()} +} + +func (r Range) Pos() token.Pos { return r.Start } +func (r Range) End() token.Pos { return r.EndPos } + +// ContainsPos reports whether the range (inclusive of both end points) +// includes the specified position. +func (r Range) ContainsPos(pos token.Pos) bool { + return r.Contains(RangeOf(pos, pos)) +} + +// Contains reports whether the range (inclusive of both end points) +// includes the specified range. +func (r Range) Contains(rng Range) bool { + return r.Start <= rng.Start && rng.EndPos <= r.EndPos +} + +// IsValid reports whether the range is valid. +func (r Range) IsValid() bool { return r.Start.IsValid() && r.Start <= r.EndPos } + +// -- + +// Select returns the syntax nodes identified by a user's text +// selection. It returns three nodes: the innermost node that wholly +// encloses the selection; and the first and last nodes that are +// wholly enclosed by the selection. +// +// For example, given this selection: +// +// { f(); g(); /* comment */ } +// ~~~~~~~~~~~ +// +// Select returns the enclosing BlockStmt, the f() CallExpr, and the g() CallExpr. +// +// If the selection does not wholly enclose any nodes, Select returns an error +// and invalid start/end nodes, but it may return a valid enclosing node. +// +// Callers that require exactly one syntax tree (e.g. just f() or just +// g()) should check that the returned start and end nodes are +// identical. +// +// This function is intended to be called early in the handling of a +// user's request, since it is tolerant of sloppy selection including +// extraneous whitespace and comments. Use it in new code instead of +// PathEnclosingInterval. When the exact extent of a node is known, +// use [Cursor.FindByPos] instead. +// +// TODO(hxjiang): Consider refactoring the function signature. It is currently +// confusing that an error is returned even when a valid enclosing node is +// successfully found. Consider grouping all cursors into one struct. +func Select(curFile inspector.Cursor, start, end token.Pos) (_enclosing, _start, _end inspector.Cursor, _ error) { + curEnclosing, ok := curFile.FindByPos(start, end) + if !ok { + return noCursor, noCursor, noCursor, fmt.Errorf("invalid selection") + } + + // Find the first and last node wholly within the (start, end) range. + // We'll narrow the effective selection to them, to exclude whitespace. + // (This matches the functionality of PathEnclosingInterval.) + var curStart, curEnd inspector.Cursor + rng := RangeOf(start, end) + for cur := range curEnclosing.Preorder() { + if rng.Contains(NodeRange(cur.Node())) { + // The start node has the least Pos. + if !curStart.Valid() { + curStart = cur + } + // The end node has the greatest End. + // End positions do not change monotonically, + // so we must compute the max. + if !curEnd.Valid() || + cur.Node().End() > curEnd.Node().End() { + curEnd = cur + } + } + } + if !curStart.Valid() { + // The selection is valid (inside curEnclosing) but contains no + // complete nodes. This happens for point selections (start == end), + // or selections covering only only spaces, comments, and punctuation + // tokens. + // Return the enclosing node so the caller can still use the context. + return curEnclosing, noCursor, noCursor, fmt.Errorf("invalid selection") + } + return curEnclosing, curStart, curEnd, nil +} + +var noCursor inspector.Cursor + +// MaybeParenthesize returns new, possibly wrapped in parens if needed +// to preserve operator precedence when it replaces old, whose parent +// is parentNode. +// +// (This would be more naturally written in terms of Cursor, but one of +// the callers--the inliner--does not have cursors handy.) +func MaybeParenthesize(parentNode ast.Node, old, new ast.Expr) ast.Expr { + if needsParens(parentNode, old, new) { + new = &ast.ParenExpr{X: new} + } + return new +} + +func needsParens(parentNode ast.Node, old, new ast.Expr) bool { + // An expression beneath a non-expression + // has no precedence ambiguity. + parent, ok := parentNode.(ast.Expr) + if !ok { + return false + } + + precedence := func(n ast.Node) int { + switch n := n.(type) { + case *ast.UnaryExpr, *ast.StarExpr: + return token.UnaryPrec + case *ast.BinaryExpr: + return n.Op.Precedence() + } + return -1 + } + + // Parens are not required if the new node + // is not unary or binary. + newprec := precedence(new) + if newprec < 0 { + return false + } + + // Parens are required if parent and child are both + // unary or binary and the parent has higher precedence. + if precedence(parent) > newprec { + return true + } + + // Was the old node the operand of a postfix operator? + // f().sel + // f()[i:j] + // f()[i] + // f().(T) + // f()(x) + switch parent := parent.(type) { + case *ast.SelectorExpr: + return parent.X == old + case *ast.IndexExpr: + return parent.X == old + case *ast.SliceExpr: + return parent.X == old + case *ast.TypeAssertExpr: + return parent.X == old + case *ast.CallExpr: + return parent.Fun == old + } + return false +} + +func is[T any](n any) bool { + _, ok := n.(T) + return ok +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/allpaths.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/allpaths.go new file mode 100644 index 000000000..44732557d --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/allpaths.go @@ -0,0 +1,24 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package graph + +// AllPaths returns the set of nodes that are part of at least one path from src to dst. +func AllPaths[NodeID comparable](g Graph[NodeID], src, dst NodeID) map[NodeID]bool { + // We intersect the forward closure of 'src' with + // the reverse closure of 'dst'. This is not the most + // efficient implementation, but it's the clearest, + // and the previous one had bugs. + + fwd := Reachable(g, src) + rev := Reachable(Transpose(g), dst) + + // Intersection + for n := range fwd { + if !rev[n] { + delete(fwd, n) + } + } + return fwd +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/compact.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/compact.go new file mode 100644 index 000000000..ae3270670 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/compact.go @@ -0,0 +1,89 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package graph + +import "iter" + +// A CompactGraph is a Graph with nodes that are compactly numbered from [0, +// NumNodes()). +// +// Compactly numbered graphs are useful for many graph algorithms, and many +// graph representations are naturally compact. +// +// To compact an arbitrary graph, use [Compact]. +type CompactGraph interface { + Graph[int] + IsCompact() +} + +// A nodePreserving graph is a transformation of another graph that preserves +// node IDs. +type nodePreserving interface { + Graph[int] + unwrapPreservingNodes() Graph[int] +} + +// Compact takes a Graph with arbitrary NodeIDs and returns a compact graph. +// +// If g implements [CompactGraph], it assumes g is already compact and simply +// returns g and an identity mapping. +func Compact[NodeID comparable](g Graph[NodeID]) (CompactGraph, *Index[NodeID]) { + // If it's already compact, simply return it. + if gc, ok := g.(CompactGraph); ok { + // The above assertion ensures NodeID is int, so we know this type + // assertion will always succeed. + return gc, any(NewIdentityIndex(gc.NumNodes())).(*Index[NodeID]) + } + + // If it's a transformation and the underlying graph is compact, we can use + // an identity index. Though we still need to build a compactGraph to + // satisfy the CompactGraph interface. + g2, _ := g.(nodePreserving) + for g2 != nil { + unwrapped := g2.unwrapPreservingNodes() + if gc, ok := unwrapped.(CompactGraph); ok { + index := any(NewIdentityIndex(gc.NumNodes())).(*Index[NodeID]) + cg := compactGraph[NodeID]{g, index} + return &cg, index + } + g2, _ = unwrapped.(nodePreserving) + } + + // Nope, just build an index. + cg := compactGraph[NodeID]{g, NewIndex(g.Nodes())} + return &cg, cg.m +} + +type compactGraph[NodeID comparable] struct { + g Graph[NodeID] + m *Index[NodeID] +} + +func (g *compactGraph[NodeID]) Nodes() iter.Seq[int] { + return func(yield func(int) bool) { + for i := range g.g.NumNodes() { + if !yield(i) { + break + } + } + } +} + +func (g *compactGraph[NodeID]) NumNodes() int { + return g.g.NumNodes() +} + +func (g *compactGraph[NodeID]) Out(node int) iter.Seq[int] { + id := g.m.Value(node) + return func(yield func(int) bool) { + for nid := range g.g.Out(id) { + if !yield(g.m.Index(nid)) { + break + } + } + } +} + +func (g *compactGraph[NodeID]) IsCompact() {} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/graph.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/graph.go new file mode 100644 index 000000000..fdea880ef --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/graph.go @@ -0,0 +1,33 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package graph provides a common abstraction for directed graphs and standard +// graph algorithms. +// +// In general, this package does not provide or assume any concrete graph +// representation. It's up to the caller of this package to implement the +// [Graph] interface, either directly or as an adapter around another type. +package graph + +import "iter" + +// A Graph implements a directed graph where nodes in the graph are identified +// by the NodeID type. +// +// If a concrete graph type stores additional information about nodes and/or +// edges, it will conventionally provide methods of the form: +// +// Node(node NodeID) nodeInfo +// Edge(from, to NodeID) edgeInfo +type Graph[NodeID comparable] interface { + // Nodes yields all nodes in this graph. + Nodes() iter.Seq[NodeID] + + // NumNodes returns the total number of nodes in this graph. + NumNodes() int + + // Out yields the out-edges of node. Out must be deterministic, though + // otherwise there is no constraint on the order of the returned sequence. + Out(node NodeID) iter.Seq[NodeID] +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/index.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/index.go new file mode 100644 index 000000000..394213e48 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/index.go @@ -0,0 +1,96 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package graph + +import ( + "fmt" + "iter" + "slices" +) + +// An Index is an immutable, bijective map between [0, N) and an ordered list of keys. +type Index[Key comparable] struct { + // There are three Index representations: + // + // - If identN > 0, an identity map of [0, identN). + // - If index == nil, a sorted integer index in values. + // - Otherwise, a full index in values and index. + + identN int + values []Key + index map[Key]int +} + +// NewIndex returns an index for the specified list of values. +func NewIndex[Key comparable](values iter.Seq[Key]) *Index[Key] { + vs := slices.Collect(values) + if len(vs) == 0 { + return new(Index[Key]) + } + + // Fast path: a naturally sorted list needs no index. (Sadly, there's no way + // to ask "is Key ordered?") + if vi, ok := any(vs).([]int); ok && slices.IsSorted(vi) { + return &Index[Key]{values: vs} + } + + index := make(map[Key]int, len(vs)) + for i, v := range vs { + index[v] = i + } + return &Index[Key]{values: vs, index: index} +} + +// NewIdentityIndex returns an index that maps [0, n) to [0, n). +func NewIdentityIndex(n int) *Index[int] { + if n < 0 { + panic("n < 0") + } + // If n == 0, this is actually a "sorted integer index", but it doesn't + // matter because everything is out of bounds either way. + return &Index[int]{identN: n} +} + +// Value maps an index to a key. +func (ix *Index[Key]) Value(index int) Key { + if ix.identN > 0 { + if index < 0 || index >= ix.identN { + panic(fmt.Sprintf("index %d out of range [0, %d)", index, ix.identN)) + } + return any(index).(Key) + } + if index < 0 || index >= len(ix.values) { + panic(fmt.Sprintf("index %d out of range [0, %d)", index, ix.identN)) + } + return ix.values[index] +} + +// Index maps a key to an index. +func (ix *Index[Key]) Index(key Key) int { + if key, ok := any(key).(int); ok { + // Integer-only optimizations. + switch { + case ix.identN > 0: + // Identity. + if 0 <= key && key < ix.identN { + return key + } + goto oob + + case ix.index == nil: + // Sorted integers. + if i, ok := slices.BinarySearch(any(ix.values).([]int), key); ok { + return i + } + goto oob + } + } + if i, ok := ix.index[key]; ok { + return i + } + +oob: + panic(fmt.Sprintf("key %v not in index", key)) +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/order.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/order.go new file mode 100644 index 000000000..795a434d6 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/order.go @@ -0,0 +1,94 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package graph + +import "slices" + +// Postorder returns the sequence of nodes in the spanning DAG of g, in +// postorder. +// +// For rootless subgraphs, it breaks cycles by starting at the lowest numbered +// node. +// +// This algorithm runs in O(V + E) time and O(V + E) space. +func Postorder[NodeID comparable](g Graph[NodeID]) []NodeID { + cg, nodeMap := Compact(g) + + numNodes := cg.NumNodes() + if numNodes == 0 { + return nil + } + + result := make([]NodeID, 0, numNodes) + visited := newBitset(numNodes) + onStack := newBitset(numNodes) + + // visit performs a Depth-First Search. + var visit func(u int) + visit = func(u int) { + if !visited.add(u) { + return + } + onStack.add(u) + + for v := range cg.Out(u) { + if onStack.contains(v) { + // Cycle detected (back-edge). + // To resolve, we simply skip processing this edge further in the + // current recursion, effectively "breaking" the cycle at this point. + continue + } + visit(v) + } + + onStack.remove(u) + // Post-order: add to result after all descendants are processed. + result = append(result, nodeMap.Value(u)) + } + + // Visit every node in ascending order to ensure stability. + for u := range numNodes { + visit(u) + } + + return result +} + +// ReversePostorder returns the nodes of the graph in reverse post-order. +// +// If g is a directed acyclic graph (DAG), the result is a topological sort of +// g. +// +// See [Postorder] for how this handles back-edges and cycles. +// +// This algorithm runs in O(V + E) time and O(V + E) space. +func ReversePostorder[NodeID comparable](g Graph[NodeID]) []NodeID { + result := Postorder(g) + slices.Reverse(result) + return result +} + +// bitset is a simple fixed-size bitset used to reduce memory overhead. +type bitset []uint64 + +func newBitset(n int) bitset { + return make(bitset, (n+63)/64) +} + +func (b bitset) add(u int) bool { + if b.contains(u) { + return false + } + b[u/64] |= 1 << (u % 64) + return true +} + +func (b bitset) remove(u int) { + b[u/64] &= ^(1 << (u % 64)) +} + +func (b bitset) contains(u int) bool { + return b[u/64]&(1<<(u%64)) != 0 +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/reachable.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/reachable.go new file mode 100644 index 000000000..dd14eae8d --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/reachable.go @@ -0,0 +1,23 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package graph + +// Reachable returns the set of nodes reachable from the given roots. +func Reachable[NodeID comparable](g Graph[NodeID], roots ...NodeID) map[NodeID]bool { + seen := make(map[NodeID]bool) + var visit func(node NodeID) + visit = func(node NodeID) { + if !seen[node] { + seen[node] = true + for e := range g.Out(node) { + visit(e) + } + } + } + for _, root := range roots { + visit(root) + } + return seen +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/scc.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/scc.go new file mode 100644 index 000000000..fb4764f1e --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/scc.go @@ -0,0 +1,39 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package graph + +import "slices" + +// SCCs computes the strongly connected components of the graph g. +func SCCs[NodeID comparable](g Graph[NodeID]) [][]NodeID { + // Use Kosaraju's algorithm. Tarjan is overkill here. + + // Forward pass + S := Postorder(g) + + // Reverse pass + gt := Transpose(g) + seen := make(map[NodeID]bool) + var scc []NodeID + var sccs [][]NodeID + var rvisit func(NodeID) + rvisit = func(u NodeID) { + if !seen[u] { + seen[u] = true + scc = append(scc, u) + for v := range gt.Out(u) { + rvisit(v) + } + } + } + for _, root := range slices.Backward(S) { + if !seen[root] { + scc = nil + rvisit(root) + sccs = append(sccs, scc) + } + } + return sccs +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/shortest.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/shortest.go new file mode 100644 index 000000000..e7457e8b7 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/shortest.go @@ -0,0 +1,45 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package graph + +import "slices" + +// ShortestPath returns a shortest path from src to dst in g. +// It returns the path as a slice of nodes starting with src and ending with dst. +// If no path is found, it returns nil. +func ShortestPath[NodeID comparable](g Graph[NodeID], src, dst NodeID) []NodeID { + if src == dst { + return []NodeID{src} + } + + pred := make(map[NodeID]NodeID) + queue := []NodeID{src} + // Mark src as seen. + pred[src] = src + + for len(queue) > 0 { + n := queue[0] + queue = queue[1:] + + if n == dst { + // Reconstruct path + var path []NodeID + for curr := dst; curr != src; curr = pred[curr] { + path = append(path, curr) + } + path = append(path, src) + slices.Reverse(path) + return path + } + + for v := range g.Out(n) { + if _, seen := pred[v]; !seen { + pred[v] = n + queue = append(queue, v) + } + } + } + return nil +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/transpose.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/transpose.go new file mode 100644 index 000000000..431fc5f65 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/graph/transpose.go @@ -0,0 +1,51 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package graph + +import ( + "iter" + "slices" +) + +type transpose[NodeID comparable] struct { + Graph Graph[NodeID] + preds map[NodeID][]NodeID +} + +// transpose returns a graph like g but with all edges reversed. Node IDs are +// identical to the underlying graph. +// +// Transpose preserves compactness. +func Transpose[NodeID comparable](g Graph[NodeID]) Graph[NodeID] { + if g, ok := g.(transpose[NodeID]); ok { + // Transpose(Transpose(g)) == g + return g.Graph + } + + preds := make(map[NodeID][]NodeID) + for nid := range g.Nodes() { + for succ := range g.Out(nid) { + preds[succ] = append(preds[succ], nid) + } + } + return transpose[NodeID]{g, preds} +} + +func (t transpose[NodeID]) NumNodes() int { + return len(t.preds) +} + +func (t transpose[NodeID]) Nodes() iter.Seq[NodeID] { + return t.Graph.Nodes() +} + +func (t transpose[NodeID]) Out(n NodeID) iter.Seq[NodeID] { + return slices.Values(t.preds[n]) +} + +//lint:ignore U1000 False positive in Staticcheck 2026.1 and older. +func (t transpose[NodeID]) unwrapPreservingNodes() Graph[NodeID] { + return t.Graph +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/moreiters/iters.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/moreiters/iters.go new file mode 100644 index 000000000..3dc81c4db --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/moreiters/iters.go @@ -0,0 +1,63 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package moreiters + +import "iter" + +// First returns the first value of seq and true. +// If seq is empty, it returns the zero value of T and false. +func First[T any](seq iter.Seq[T]) (z T, ok bool) { + for t := range seq { + return t, true + } + return z, false +} + +// Contains reports whether x is an element of the sequence seq. +func Contains[T comparable](seq iter.Seq[T], x T) bool { + for cand := range seq { + if cand == x { + return true + } + } + return false +} + +// Every reports whether every pred(t) for t in seq returns true, +// stopping at the first false element. +func Every[T any](seq iter.Seq[T], pred func(T) bool) bool { + for t := range seq { + if !pred(t) { + return false + } + } + return true +} + +// Any reports whether any pred(t) for t in seq returns true. +func Any[T any](seq iter.Seq[T], pred func(T) bool) bool { + for t := range seq { + if pred(t) { + return true + } + } + return false +} + +// Len returns the number of elements in the sequence (by iterating). +func Len[T any](seq iter.Seq[T]) (n int) { + for range seq { + n++ + } + return +} + +// Empty reports whether the sequence contains no elements. +func Empty[T any](seq iter.Seq[T]) bool { + for range seq { + return false + } + return true +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/common.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/common.go new file mode 100644 index 000000000..cdae2b8e8 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/common.go @@ -0,0 +1,68 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package typeparams contains common utilities for writing tools that +// interact with generic Go code, as introduced with Go 1.18. It +// supplements the standard library APIs. Notably, the StructuralTerms +// API computes a minimal representation of the structural +// restrictions on a type parameter. +// +// An external version of these APIs is available in the +// golang.org/x/exp/typeparams module. +package typeparams + +import ( + "go/ast" + "go/token" + "go/types" +) + +// UnpackIndexExpr extracts data from AST nodes that represent index +// expressions. +// +// For an ast.IndexExpr, the resulting indices slice will contain exactly one +// index expression. For an ast.IndexListExpr (go1.18+), it may have a variable +// number of index expressions. +// +// For nodes that don't represent index expressions, the first return value of +// UnpackIndexExpr will be nil. +func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) { + switch e := n.(type) { + case *ast.IndexExpr: + return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack + case *ast.IndexListExpr: + return e.X, e.Lbrack, e.Indices, e.Rbrack + } + return nil, token.NoPos, nil, token.NoPos +} + +// PackIndexExpr returns an *ast.IndexExpr or *ast.IndexListExpr, depending on +// the cardinality of indices. Calling PackIndexExpr with len(indices) == 0 +// will panic. +func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.Expr { + switch len(indices) { + case 0: + panic("empty indices") + case 1: + return &ast.IndexExpr{ + X: x, + Lbrack: lbrack, + Index: indices[0], + Rbrack: rbrack, + } + default: + return &ast.IndexListExpr{ + X: x, + Lbrack: lbrack, + Indices: indices, + Rbrack: rbrack, + } + } +} + +// IsTypeParam reports whether t is a type parameter (or an alias of one). +func IsTypeParam(t types.Type) bool { + _, ok := types.Unalias(t).(*types.TypeParam) + return ok +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/coretype.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/coretype.go new file mode 100644 index 000000000..2e05de464 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/coretype.go @@ -0,0 +1,157 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typeparams + +import ( + "fmt" + "go/types" +) + +// CoreType returns the core type of T or nil if T does not have a core type. +// +// As of Go1.25, the notion of a core type has been removed from the language spec. +// See https://go.dev/blog/coretypes for more details. +// TODO(mkalil): We should eventually consider removing all uses of CoreType. +func CoreType(T types.Type) types.Type { + U := T.Underlying() + if _, ok := U.(*types.Interface); !ok { + return U // for non-interface types, + } + + terms, err := NormalTerms(U) + if len(terms) == 0 || err != nil { + // len(terms) -> empty type set of interface. + // err != nil => U is invalid, exceeds complexity bounds, or has an empty type set. + return nil // no core type. + } + + U = terms[0].Type().Underlying() + var identical int // i in [0,identical) => Identical(U, terms[i].Type().Underlying()) + for identical = 1; identical < len(terms); identical++ { + if !types.Identical(U, terms[identical].Type().Underlying()) { + break + } + } + + if identical == len(terms) { + // From the deprecated core types spec: + // "There is a single type U which is the underlying type of all types in the type set of T" + return U + } + ch, ok := U.(*types.Chan) + if !ok { + return nil // no core type as identical < len(terms) and U is not a channel. + } + // From the deprecated core types spec: + // "the type chan E if T contains only bidirectional channels, or the type chan<- E or + // <-chan E depending on the direction of the directional channels present." + for chans := identical; chans < len(terms); chans++ { + curr, ok := terms[chans].Type().Underlying().(*types.Chan) + if !ok { + return nil + } + if !types.Identical(ch.Elem(), curr.Elem()) { + return nil // channel elements are not identical. + } + if ch.Dir() == types.SendRecv { + // ch is bidirectional. We can safely always use curr's direction. + ch = curr + } else if curr.Dir() != types.SendRecv && ch.Dir() != curr.Dir() { + // ch and curr are not bidirectional and not the same direction. + return nil + } + } + return ch +} + +// NormalTerms returns a slice of terms representing the normalized structural +// type restrictions of a type, if any. +// +// For all types other than *types.TypeParam, *types.Interface, and +// *types.Union, this is just a single term with Tilde() == false and +// Type() == typ. For *types.TypeParam, *types.Interface, and *types.Union, see +// below. +// +// Structural type restrictions of a type parameter are created via +// non-interface types embedded in its constraint interface (directly, or via a +// chain of interface embeddings). For example, in the declaration type +// T[P interface{~int; m()}] int the structural restriction of the type +// parameter P is ~int. +// +// With interface embedding and unions, the specification of structural type +// restrictions may be arbitrarily complex. For example, consider the +// following: +// +// type A interface{ ~string|~[]byte } +// +// type B interface{ int|string } +// +// type C interface { ~string|~int } +// +// type T[P interface{ A|B; C }] int +// +// In this example, the structural type restriction of P is ~string|int: A|B +// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, +// which when intersected with C (~string|~int) yields ~string|int. +// +// NormalTerms computes these expansions and reductions, producing a +// "normalized" form of the embeddings. A structural restriction is normalized +// if it is a single union containing no interface terms, and is minimal in the +// sense that removing any term changes the set of types satisfying the +// constraint. It is left as a proof for the reader that, modulo sorting, there +// is exactly one such normalized form. +// +// Because the minimal representation always takes this form, NormalTerms +// returns a slice of tilde terms corresponding to the terms of the union in +// the normalized structural restriction. An error is returned if the type is +// invalid, exceeds complexity bounds, or has an empty type set. In the latter +// case, NormalTerms returns ErrEmptyTypeSet. +// +// NormalTerms makes no guarantees about the order of terms, except that it +// is deterministic. +func NormalTerms(T types.Type) ([]*types.Term, error) { + // typeSetOf(T) == typeSetOf(Unalias(T)) + typ := types.Unalias(T) + if named, ok := typ.(*types.Named); ok { + typ = named.Underlying() + } + switch typ := typ.(type) { + case *types.TypeParam: + return StructuralTerms(typ) + case *types.Union: + return UnionTermSet(typ) + case *types.Interface: + return InterfaceTermSet(typ) + default: + return []*types.Term{types.NewTerm(false, T)}, nil + } +} + +// Deref returns the type of the variable pointed to by t, +// if t's core type is a pointer; otherwise it returns t. +// +// Do not assume that Deref(T)==T implies T is not a pointer: +// consider "type T *T", for example. +// +// TODO(adonovan): ideally this would live in typesinternal, but that +// creates an import cycle. Move there when we melt this package down. +func Deref(t types.Type) types.Type { + if ptr, ok := CoreType(t).(*types.Pointer); ok { + return ptr.Elem() + } + return t +} + +// MustDeref returns the type of the variable pointed to by t. +// It panics if t's core type is not a pointer. +// +// TODO(adonovan): ideally this would live in typesinternal, but that +// creates an import cycle. Move there when we melt this package down. +func MustDeref(t types.Type) types.Type { + if ptr, ok := CoreType(t).(*types.Pointer); ok { + return ptr.Elem() + } + panic(fmt.Sprintf("%v is not a pointer", t)) +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/free.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/free.go new file mode 100644 index 000000000..4c391876e --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/free.go @@ -0,0 +1,129 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typeparams + +import ( + "go/types" +) + +// Free is a memoization of the set of free type parameters within a +// type. It makes a sequence of calls to [Free.Has] for overlapping +// types more efficient. The zero value is ready for use. +// +// NOTE: Adapted from go/types/infer.go. If it is later exported, factor. +type Free struct { + seen map[types.Type]bool +} + +// Has reports whether the specified type has a free type parameter. +func (w *Free) Has(typ types.Type) (res bool) { + // detect cycles + if x, ok := w.seen[typ]; ok { + return x + } + if w.seen == nil { + w.seen = make(map[types.Type]bool) + } + w.seen[typ] = false + defer func() { + w.seen[typ] = res + }() + + switch t := typ.(type) { + case nil, *types.Basic: // TODO(gri) should nil be handled here? + break + + case *types.Alias: + if t.TypeParams().Len() > t.TypeArgs().Len() { + return true // This is an uninstantiated Alias. + } + // The expansion of an alias can have free type parameters, + // whether or not the alias itself has type parameters: + // + // func _[K comparable]() { + // type Set = map[K]bool // free(Set) = {K} + // type MapTo[V] = map[K]V // free(Map[foo]) = {V} + // } + // + // So, we must Unalias. + return w.Has(types.Unalias(t)) + + case *types.Array: + return w.Has(t.Elem()) + + case *types.Slice: + return w.Has(t.Elem()) + + case *types.Struct: + for i, n := 0, t.NumFields(); i < n; i++ { + if w.Has(t.Field(i).Type()) { + return true + } + } + + case *types.Pointer: + return w.Has(t.Elem()) + + case *types.Tuple: + n := t.Len() + for i := range n { + if w.Has(t.At(i).Type()) { + return true + } + } + + case *types.Signature: + // t.tparams may not be nil if we are looking at a signature + // of a generic function type (or an interface method) that is + // part of the type we're testing. We don't care about these type + // parameters. + // Similarly, the receiver of a method may declare (rather than + // use) type parameters, we don't care about those either. + // Thus, we only need to look at the input and result parameters. + return w.Has(t.Params()) || w.Has(t.Results()) + + case *types.Interface: + for i, n := 0, t.NumMethods(); i < n; i++ { + if w.Has(t.Method(i).Type()) { + return true + } + } + terms, err := InterfaceTermSet(t) + if err != nil { + return false // ill typed + } + for _, term := range terms { + if w.Has(term.Type()) { + return true + } + } + + case *types.Map: + return w.Has(t.Key()) || w.Has(t.Elem()) + + case *types.Chan: + return w.Has(t.Elem()) + + case *types.Named: + args := t.TypeArgs() + if params := t.TypeParams(); params.Len() > args.Len() { + return true // this is an uninstantiated named type. + } + for i, n := 0, args.Len(); i < n; i++ { + if w.Has(args.At(i)) { + return true + } + } + return w.Has(t.Underlying()) // recurse for types local to parameterized functions + + case *types.TypeParam: + return true + + default: + panic(t) // unreachable + } + + return false +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/normalize.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/normalize.go new file mode 100644 index 000000000..ba3678719 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/normalize.go @@ -0,0 +1,214 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typeparams + +import ( + "errors" + "fmt" + "go/types" + "os" + "strings" +) + +const debug = false + +var ErrEmptyTypeSet = errors.New("empty type set") + +// StructuralTerms returns a slice of terms representing the normalized +// structural type restrictions of a type parameter, if any. +// +// Structural type restrictions of a type parameter are created via +// non-interface types embedded in its constraint interface (directly, or via a +// chain of interface embeddings). For example, in the declaration +// +// type T[P interface{~int; m()}] int +// +// the structural restriction of the type parameter P is ~int. +// +// With interface embedding and unions, the specification of structural type +// restrictions may be arbitrarily complex. For example, consider the +// following: +// +// type A interface{ ~string|~[]byte } +// +// type B interface{ int|string } +// +// type C interface { ~string|~int } +// +// type T[P interface{ A|B; C }] int +// +// In this example, the structural type restriction of P is ~string|int: A|B +// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, +// which when intersected with C (~string|~int) yields ~string|int. +// +// StructuralTerms computes these expansions and reductions, producing a +// "normalized" form of the embeddings. A structural restriction is normalized +// if it is a single union containing no interface terms, and is minimal in the +// sense that removing any term changes the set of types satisfying the +// constraint. It is left as a proof for the reader that, modulo sorting, there +// is exactly one such normalized form. +// +// Because the minimal representation always takes this form, StructuralTerms +// returns a slice of tilde terms corresponding to the terms of the union in +// the normalized structural restriction. An error is returned if the +// constraint interface is invalid, exceeds complexity bounds, or has an empty +// type set. In the latter case, StructuralTerms returns ErrEmptyTypeSet. +// +// StructuralTerms makes no guarantees about the order of terms, except that it +// is deterministic. +func StructuralTerms(tparam *types.TypeParam) ([]*types.Term, error) { + constraint := tparam.Constraint() + if constraint == nil { + return nil, fmt.Errorf("%s has nil constraint", tparam) + } + iface, _ := constraint.Underlying().(*types.Interface) + if iface == nil { + return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying()) + } + return InterfaceTermSet(iface) +} + +// InterfaceTermSet computes the normalized terms for a constraint interface, +// returning an error if the term set cannot be computed or is empty. In the +// latter case, the error will be ErrEmptyTypeSet. +// +// See the documentation of StructuralTerms for more information on +// normalization. +func InterfaceTermSet(iface *types.Interface) ([]*types.Term, error) { + return computeTermSet(iface) +} + +// UnionTermSet computes the normalized terms for a union, returning an error +// if the term set cannot be computed or is empty. In the latter case, the +// error will be ErrEmptyTypeSet. +// +// See the documentation of StructuralTerms for more information on +// normalization. +func UnionTermSet(union *types.Union) ([]*types.Term, error) { + return computeTermSet(union) +} + +func computeTermSet(typ types.Type) ([]*types.Term, error) { + tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0) + if err != nil { + return nil, err + } + if tset.terms.isEmpty() { + return nil, ErrEmptyTypeSet + } + if tset.terms.isAll() { + return nil, nil + } + var terms []*types.Term + for _, term := range tset.terms { + terms = append(terms, types.NewTerm(term.tilde, term.typ)) + } + return terms, nil +} + +// A termSet holds the normalized set of terms for a given type. +// +// The name termSet is intentionally distinct from 'type set': a type set is +// all types that implement a type (and includes method restrictions), whereas +// a term set just represents the structural restrictions on a type. +type termSet struct { + complete bool + terms termlist +} + +func indentf(depth int, format string, args ...any) { + fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...) +} + +func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth int) (res *termSet, err error) { + if t == nil { + panic("nil type") + } + + if debug { + indentf(depth, "%s", t.String()) + defer func() { + if err != nil { + indentf(depth, "=> %s", err) + } else { + indentf(depth, "=> %s", res.terms.String()) + } + }() + } + + const maxTermCount = 100 + if tset, ok := seen[t]; ok { + if !tset.complete { + return nil, fmt.Errorf("cycle detected in the declaration of %s", t) + } + return tset, nil + } + + // Mark the current type as seen to avoid infinite recursion. + tset := new(termSet) + defer func() { + tset.complete = true + }() + seen[t] = tset + + switch u := t.Underlying().(type) { + case *types.Interface: + // The term set of an interface is the intersection of the term sets of its + // embedded types. + tset.terms = allTermlist + for embedded := range u.EmbeddedTypes() { + if _, ok := embedded.Underlying().(*types.TypeParam); ok { + return nil, fmt.Errorf("invalid embedded type %T", embedded) + } + tset2, err := computeTermSetInternal(embedded, seen, depth+1) + if err != nil { + return nil, err + } + tset.terms = tset.terms.intersect(tset2.terms) + } + case *types.Union: + // The term set of a union is the union of term sets of its terms. + tset.terms = nil + for t := range u.Terms() { + var terms termlist + switch t.Type().Underlying().(type) { + case *types.Interface: + tset2, err := computeTermSetInternal(t.Type(), seen, depth+1) + if err != nil { + return nil, err + } + terms = tset2.terms + case *types.TypeParam, *types.Union: + // A stand-alone type parameter or union is not permitted as union + // term. + return nil, fmt.Errorf("invalid union term %T", t) + default: + if t.Type() == types.Typ[types.Invalid] { + continue + } + terms = termlist{{t.Tilde(), t.Type()}} + } + tset.terms = tset.terms.union(terms) + if len(tset.terms) > maxTermCount { + return nil, fmt.Errorf("exceeded max term count %d", maxTermCount) + } + } + case *types.TypeParam: + panic("unreachable") + default: + // For all other types, the term set is just a single non-tilde term + // holding the type itself. + if u != types.Typ[types.Invalid] { + tset.terms = termlist{{false, t}} + } + } + return tset, nil +} + +// under is a facade for the go/types internal function of the same name. It is +// used by typeterm.go. +func under(t types.Type) types.Type { + return t.Underlying() +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/termlist.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/termlist.go new file mode 100644 index 000000000..9bc29143f --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/termlist.go @@ -0,0 +1,169 @@ +// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT. +// Source: ../../cmd/compile/internal/types2/termlist.go + +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by copytermlist.go DO NOT EDIT. + +package typeparams + +import ( + "go/types" + "strings" +) + +// A termlist represents the type set represented by the union +// t1 ∪ y2 ∪ ... tn of the type sets of the terms t1 to tn. +// A termlist is in normal form if all terms are disjoint. +// termlist operations don't require the operands to be in +// normal form. +type termlist []*term + +// allTermlist represents the set of all types. +// It is in normal form. +var allTermlist = termlist{new(term)} + +// termSep is the separator used between individual terms. +const termSep = " | " + +// String prints the termlist exactly (without normalization). +func (xl termlist) String() string { + if len(xl) == 0 { + return "∅" + } + var buf strings.Builder + for i, x := range xl { + if i > 0 { + buf.WriteString(termSep) + } + buf.WriteString(x.String()) + } + return buf.String() +} + +// isEmpty reports whether the termlist xl represents the empty set of types. +func (xl termlist) isEmpty() bool { + // If there's a non-nil term, the entire list is not empty. + // If the termlist is in normal form, this requires at most + // one iteration. + for _, x := range xl { + if x != nil { + return false + } + } + return true +} + +// isAll reports whether the termlist xl represents the set of all types. +func (xl termlist) isAll() bool { + // If there's a 𝓤 term, the entire list is 𝓤. + // If the termlist is in normal form, this requires at most + // one iteration. + for _, x := range xl { + if x != nil && x.typ == nil { + return true + } + } + return false +} + +// norm returns the normal form of xl. +func (xl termlist) norm() termlist { + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix asymptotic performance + used := make([]bool, len(xl)) + var rl termlist + for i, xi := range xl { + if xi == nil || used[i] { + continue + } + for j := i + 1; j < len(xl); j++ { + xj := xl[j] + if xj == nil || used[j] { + continue + } + if u1, u2 := xi.union(xj); u2 == nil { + // If we encounter a 𝓤 term, the entire list is 𝓤. + // Exit early. + // (Note that this is not just an optimization; + // if we continue, we may end up with a 𝓤 term + // and other terms and the result would not be + // in normal form.) + if u1.typ == nil { + return allTermlist + } + xi = u1 + used[j] = true // xj is now unioned into xi - ignore it in future iterations + } + } + rl = append(rl, xi) + } + return rl +} + +// union returns the union xl ∪ yl. +func (xl termlist) union(yl termlist) termlist { + return append(xl, yl...).norm() +} + +// intersect returns the intersection xl ∩ yl. +func (xl termlist) intersect(yl termlist) termlist { + if xl.isEmpty() || yl.isEmpty() { + return nil + } + + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix asymptotic performance + var rl termlist + for _, x := range xl { + for _, y := range yl { + if r := x.intersect(y); r != nil { + rl = append(rl, r) + } + } + } + return rl.norm() +} + +// equal reports whether xl and yl represent the same type set. +func (xl termlist) equal(yl termlist) bool { + // TODO(gri) this should be more efficient + return xl.subsetOf(yl) && yl.subsetOf(xl) +} + +// includes reports whether t ∈ xl. +func (xl termlist) includes(t types.Type) bool { + for _, x := range xl { + if x.includes(t) { + return true + } + } + return false +} + +// supersetOf reports whether y ⊆ xl. +func (xl termlist) supersetOf(y *term) bool { + for _, x := range xl { + if y.subsetOf(x) { + return true + } + } + return false +} + +// subsetOf reports whether xl ⊆ yl. +func (xl termlist) subsetOf(yl termlist) bool { + if yl.isEmpty() { + return xl.isEmpty() + } + + // each term x of xl must be a subset of yl + for _, x := range xl { + if !yl.supersetOf(x) { + return false // x is not a subset yl + } + } + return true +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/typeterm.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/typeterm.go new file mode 100644 index 000000000..fa758cdc9 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/typeterm.go @@ -0,0 +1,172 @@ +// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT. +// Source: ../../cmd/compile/internal/types2/typeterm.go + +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by copytermlist.go DO NOT EDIT. + +package typeparams + +import "go/types" + +// A term describes elementary type sets: +// +// ∅: (*term)(nil) == ∅ // set of no types (empty set) +// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) +// T: &term{false, T} == {T} // set of type T +// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t +type term struct { + tilde bool // valid if typ != nil + typ types.Type +} + +func (x *term) String() string { + switch { + case x == nil: + return "∅" + case x.typ == nil: + return "𝓤" + case x.tilde: + return "~" + x.typ.String() + default: + return x.typ.String() + } +} + +// equal reports whether x and y represent the same type set. +func (x *term) equal(y *term) bool { + // easy cases + switch { + case x == nil || y == nil: + return x == y + case x.typ == nil || y.typ == nil: + return x.typ == y.typ + } + // ∅ ⊂ x, y ⊂ 𝓤 + + return x.tilde == y.tilde && types.Identical(x.typ, y.typ) +} + +// union returns the union x ∪ y: zero, one, or two non-nil terms. +func (x *term) union(y *term) (_, _ *term) { + // easy cases + switch { + case x == nil && y == nil: + return nil, nil // ∅ ∪ ∅ == ∅ + case x == nil: + return y, nil // ∅ ∪ y == y + case y == nil: + return x, nil // x ∪ ∅ == x + case x.typ == nil: + return x, nil // 𝓤 ∪ y == 𝓤 + case y.typ == nil: + return y, nil // x ∪ 𝓤 == 𝓤 + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return x, y // x ∪ y == (x, y) if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ∪ ~t == ~t + // ~t ∪ T == ~t + // T ∪ ~t == ~t + // T ∪ T == T + if x.tilde || !y.tilde { + return x, nil + } + return y, nil +} + +// intersect returns the intersection x ∩ y. +func (x *term) intersect(y *term) *term { + // easy cases + switch { + case x == nil || y == nil: + return nil // ∅ ∩ y == ∅ and ∩ ∅ == ∅ + case x.typ == nil: + return y // 𝓤 ∩ y == y + case y.typ == nil: + return x // x ∩ 𝓤 == x + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return nil // x ∩ y == ∅ if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ∩ ~t == ~t + // ~t ∩ T == T + // T ∩ ~t == T + // T ∩ T == T + if !x.tilde || y.tilde { + return x + } + return y +} + +// includes reports whether t ∈ x. +func (x *term) includes(t types.Type) bool { + // easy cases + switch { + case x == nil: + return false // t ∈ ∅ == false + case x.typ == nil: + return true // t ∈ 𝓤 == true + } + // ∅ ⊂ x ⊂ 𝓤 + + u := t + if x.tilde { + u = under(u) + } + return types.Identical(x.typ, u) +} + +// subsetOf reports whether x ⊆ y. +func (x *term) subsetOf(y *term) bool { + // easy cases + switch { + case x == nil: + return true // ∅ ⊆ y == true + case y == nil: + return false // x ⊆ ∅ == false since x != ∅ + case y.typ == nil: + return true // x ⊆ 𝓤 == true + case x.typ == nil: + return false // 𝓤 ⊆ y == false since y != 𝓤 + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return false // x ⊆ y == false if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ⊆ ~t == true + // ~t ⊆ T == false + // T ⊆ ~t == true + // T ⊆ T == true + return !x.tilde || y.tilde +} + +// disjoint reports whether x ∩ y == ∅. +// x.typ and y.typ must not be nil. +func (x *term) disjoint(y *term) bool { + if debug && (x.typ == nil || y.typ == nil) { + panic("invalid argument(s)") + } + ux := x.typ + if y.tilde { + ux = under(ux) + } + uy := y.typ + if x.tilde { + uy = under(uy) + } + return !types.Identical(ux, uy) +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/classify_call.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/classify_call.go new file mode 100644 index 000000000..7ebe9768b --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/classify_call.go @@ -0,0 +1,137 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "fmt" + "go/ast" + "go/types" + _ "unsafe" // for go:linkname hack +) + +// CallKind describes the function position of an [*ast.CallExpr]. +type CallKind int + +const ( + CallStatic CallKind = iota // static call to known function + CallInterface // dynamic call through an interface method + CallDynamic // dynamic call of a func value + CallBuiltin // call to a builtin function + CallConversion // a conversion (not a call) +) + +var callKindNames = []string{ + "CallStatic", + "CallInterface", + "CallDynamic", + "CallBuiltin", + "CallConversion", +} + +func (k CallKind) String() string { + if i := int(k); i >= 0 && i < len(callKindNames) { + return callKindNames[i] + } + return fmt.Sprintf("typeutil.CallKind(%d)", k) +} + +// ClassifyCall classifies the function position of a call expression ([*ast.CallExpr]). +// It distinguishes among true function calls, calls to builtins, and type conversions, +// and further classifies function calls as static calls (where the function is known), +// dynamic interface calls, and other dynamic calls. +// +// For the declarations: +// +// func f() {} +// func g[T any]() {} +// var v func() +// var s []func() +// type I interface { M() } +// var i I +// +// ClassifyCall returns the following: +// +// f() CallStatic +// g[int]() CallStatic +// i.M() CallInterface +// min(1, 2) CallBuiltin +// v() CallDynamic +// s[0]() CallDynamic +// int(x) CallConversion +// []byte("") CallConversion +func ClassifyCall(info *types.Info, call *ast.CallExpr) CallKind { + if info.Types == nil { + panic("ClassifyCall: info.Types is nil") + } + tv := info.Types[call.Fun] + if tv.IsType() { + return CallConversion + } + if tv.IsBuiltin() { + return CallBuiltin + } + obj := info.Uses[UsedIdent(info, call.Fun)] + // Classify the call by the type of the object, if any. + switch obj := obj.(type) { + case *types.Func: + if interfaceMethod(obj) { + return CallInterface + } + return CallStatic + default: + return CallDynamic + } +} + +// UsedIdent returns the identifier such that info.Uses[UsedIdent(info, e)] +// is the [types.Object] used by e, if any. +// +// If e is one of various forms of reference: +// +// f, c, v, T lexical reference +// pkg.X qualified identifier +// f[T] or pkg.F[K,V] instantiations of the above kinds +// expr.f field or method value selector +// T.f method expression selector +// +// UsedIdent returns the identifier whose is associated value in [types.Info.Uses] +// is the object to which it refers. +// +// For the declarations: +// +// func F[T any] {...} +// type I interface { M() } +// var ( +// x int +// s struct { f int } +// a []int +// i I +// ) +// +// UsedIdent returns the following: +// +// Expr UsedIdent +// x x +// s.f f +// F[int] F +// i.M M +// I.M M +// min min +// int int +// 1 nil +// a[0] nil +// []byte nil +// +// Note: if e is an instantiated function or method, UsedIdent returns +// the corresponding generic function or method on the generic type. +func UsedIdent(info *types.Info, e ast.Expr) *ast.Ident { + return usedIdent(info, e) +} + +//go:linkname usedIdent golang.org/x/tools/go/types/typeutil.usedIdent +func usedIdent(info *types.Info, e ast.Expr) *ast.Ident + +//go:linkname interfaceMethod golang.org/x/tools/go/types/typeutil.interfaceMethod +func interfaceMethod(f *types.Func) bool diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/element.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/element.go new file mode 100644 index 000000000..89eeea165 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/element.go @@ -0,0 +1,137 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "fmt" + "go/types" + + "golang.org/x/tools/go/types/typeutil" +) + +// ForEachElement calls f for type T and each type reachable from its +// type through reflection. It does this by recursively stripping off +// type constructors; in addition, for each named type N, the type *N +// is added to the result as it may have additional methods. +// +// The caller must provide an initially empty set used to de-duplicate +// identical types, potentially across multiple calls to ForEachElement. +// (Its final value holds all the elements seen, matching the arguments +// passed to f.) +// +// TODO(adonovan): share/harmonize with go/callgraph/rta. +func ForEachElement(rtypes *typeutil.Map, msets *typeutil.MethodSetCache, T types.Type, f func(types.Type)) { + var visit func(T types.Type, skip bool) + visit = func(T types.Type, skip bool) { + if !skip { + if seen, _ := rtypes.Set(T, true).(bool); seen { + return // de-dup + } + + f(T) // notify caller of new element type + } + + // Recursion over signatures of each method. + tmset := msets.MethodSet(T) + for method := range tmset.Methods() { + sig := method.Type().(*types.Signature) + if sig.TypeParams() != nil { + continue // skip type-parameterized methods + } + + // It is tempting to call visit(sig, false) + // but, as noted in golang.org/cl/65450043, + // the Signature.Recv field is ignored by + // types.Identical and typeutil.Map, which + // is confusing at best. + // + // More importantly, the true signature rtype + // reachable from a method using reflection + // has no receiver but an extra ordinary parameter. + // For the Read method of io.Reader we want: + // func(Reader, []byte) (int, error) + // but here sig is: + // func([]byte) (int, error) + // with .Recv = Reader (though it is hard to + // notice because it doesn't affect Signature.String + // or types.Identical). + // + // TODO(adonovan): construct and visit the correct + // non-method signature with an extra parameter + // (though since unnamed func types have no methods + // there is essentially no actual demand for this). + // + // TODO(adonovan): document whether or not it is + // safe to skip non-exported methods (as RTA does). + visit(sig.Params(), true) // skip the Tuple + visit(sig.Results(), true) // skip the Tuple + } + + switch T := T.(type) { + case *types.Alias: + visit(types.Unalias(T), skip) // emulates the pre-Alias behavior + + case *types.Basic: + // nop + + case *types.Interface: + // nop---handled by recursion over method set. + + case *types.Pointer: + visit(T.Elem(), false) + + case *types.Slice: + visit(T.Elem(), false) + + case *types.Chan: + visit(T.Elem(), false) + + case *types.Map: + visit(T.Key(), false) + visit(T.Elem(), false) + + case *types.Signature: + if T.Recv() != nil { + panic(fmt.Sprintf("Signature %s has Recv %s", T, T.Recv())) + } + visit(T.Params(), true) // skip the Tuple + visit(T.Results(), true) // skip the Tuple + + case *types.Named: + // A pointer-to-named type can be derived from a named + // type via reflection. It may have methods too. + visit(types.NewPointer(T), false) + + // Consider 'type T struct{S}' where S has methods. + // Reflection provides no way to get from T to struct{S}, + // only to S, so the method set of struct{S} is unwanted, + // so set 'skip' flag during recursion. + visit(T.Underlying(), true) // skip the unnamed type + + case *types.Array: + visit(T.Elem(), false) + + case *types.Struct: + for i, n := 0, T.NumFields(); i < n; i++ { + // TODO(adonovan): document whether or not + // it is safe to skip non-exported fields. + visit(T.Field(i).Type(), false) + } + + case *types.Tuple: + for i, n := 0, T.Len(); i < n; i++ { + visit(T.At(i).Type(), false) + } + + case *types.TypeParam, *types.Union: + // forEachReachable must not be called on parameterized types. + panic(fmt.Sprintf("ForEachElement called on type containing %T", T)) + + default: + panic(fmt.Sprintf("ForEachElement called on unexpected type %T", T)) + } + } + visit(T, false) +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode.go new file mode 100644 index 000000000..bd9447416 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode.go @@ -0,0 +1,1558 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +type ErrorCode int + +// This file defines the error codes that can be produced during type-checking. +// Collectively, these codes provide an identifier that may be used to +// implement special handling for certain types of errors. +// +// Error codes should be fine-grained enough that the exact nature of the error +// can be easily determined, but coarse enough that they are not an +// implementation detail of the type checking algorithm. As a rule-of-thumb, +// errors should be considered equivalent if there is a theoretical refactoring +// of the type checker in which they are emitted in exactly one place. For +// example, the type checker emits different error messages for "too many +// arguments" and "too few arguments", but one can imagine an alternative type +// checker where this check instead just emits a single "wrong number of +// arguments", so these errors should have the same code. +// +// Error code names should be as brief as possible while retaining accuracy and +// distinctiveness. In most cases names should start with an adjective +// describing the nature of the error (e.g. "invalid", "unused", "misplaced"), +// and end with a noun identifying the relevant language object. For example, +// "DuplicateDecl" or "InvalidSliceExpr". For brevity, naming follows the +// convention that "bad" implies a problem with syntax, and "invalid" implies a +// problem with types. + +const ( + // InvalidSyntaxTree occurs if an invalid syntax tree is provided + // to the type checker. It should never happen. + InvalidSyntaxTree ErrorCode = -1 +) + +const ( + _ ErrorCode = iota + + // Test is reserved for errors that only apply while in self-test mode. + Test + + /* package names */ + + // BlankPkgName occurs when a package name is the blank identifier "_". + // + // Per the spec: + // "The PackageName must not be the blank identifier." + BlankPkgName + + // MismatchedPkgName occurs when a file's package name doesn't match the + // package name already established by other files. + MismatchedPkgName + + // InvalidPkgUse occurs when a package identifier is used outside of a + // selector expression. + // + // Example: + // import "fmt" + // + // var _ = fmt + InvalidPkgUse + + /* imports */ + + // BadImportPath occurs when an import path is not valid. + BadImportPath + + // BrokenImport occurs when importing a package fails. + // + // Example: + // import "amissingpackage" + BrokenImport + + // ImportCRenamed occurs when the special import "C" is renamed. "C" is a + // pseudo-package, and must not be renamed. + // + // Example: + // import _ "C" + ImportCRenamed + + // UnusedImport occurs when an import is unused. + // + // Example: + // import "fmt" + // + // func main() {} + UnusedImport + + /* initialization */ + + // InvalidInitCycle occurs when an invalid cycle is detected within the + // initialization graph. + // + // Example: + // var x int = f() + // + // func f() int { return x } + InvalidInitCycle + + /* decls */ + + // DuplicateDecl occurs when an identifier is declared multiple times. + // + // Example: + // var x = 1 + // var x = 2 + DuplicateDecl + + // InvalidDeclCycle occurs when a declaration cycle is not valid. + // + // Example: + // import "unsafe" + // + // type T struct { + // a [n]int + // } + // + // var n = unsafe.Sizeof(T{}) + InvalidDeclCycle + + // InvalidTypeCycle occurs when a cycle in type definitions results in a + // type that is not well-defined. + // + // Example: + // import "unsafe" + // + // type T [unsafe.Sizeof(T{})]int + InvalidTypeCycle + + /* decls > const */ + + // InvalidConstInit occurs when a const declaration has a non-constant + // initializer. + // + // Example: + // var x int + // const _ = x + InvalidConstInit + + // InvalidConstVal occurs when a const value cannot be converted to its + // target type. + // + // TODO(findleyr): this error code and example are not very clear. Consider + // removing it. + // + // Example: + // const _ = 1 << "hello" + InvalidConstVal + + // InvalidConstType occurs when the underlying type in a const declaration + // is not a valid constant type. + // + // Example: + // const c *int = 4 + InvalidConstType + + /* decls > var (+ other variable assignment codes) */ + + // UntypedNilUse occurs when the predeclared (untyped) value nil is used to + // initialize a variable declared without an explicit type. + // + // Example: + // var x = nil + UntypedNilUse + + // WrongAssignCount occurs when the number of values on the right-hand side + // of an assignment or initialization expression does not match the number + // of variables on the left-hand side. + // + // Example: + // var x = 1, 2 + WrongAssignCount + + // UnassignableOperand occurs when the left-hand side of an assignment is + // not assignable. + // + // Example: + // func f() { + // const c = 1 + // c = 2 + // } + UnassignableOperand + + // NoNewVar occurs when a short variable declaration (':=') does not declare + // new variables. + // + // Example: + // func f() { + // x := 1 + // x := 2 + // } + NoNewVar + + // MultiValAssignOp occurs when an assignment operation (+=, *=, etc) does + // not have single-valued left-hand or right-hand side. + // + // Per the spec: + // "In assignment operations, both the left- and right-hand expression lists + // must contain exactly one single-valued expression" + // + // Example: + // func f() int { + // x, y := 1, 2 + // x, y += 1 + // return x + y + // } + MultiValAssignOp + + // InvalidIfaceAssign occurs when a value of type T is used as an + // interface, but T does not implement a method of the expected interface. + // + // Example: + // type I interface { + // f() + // } + // + // type T int + // + // var x I = T(1) + InvalidIfaceAssign + + // InvalidChanAssign occurs when a chan assignment is invalid. + // + // Per the spec, a value x is assignable to a channel type T if: + // "x is a bidirectional channel value, T is a channel type, x's type V and + // T have identical element types, and at least one of V or T is not a + // defined type." + // + // Example: + // type T1 chan int + // type T2 chan int + // + // var x T1 + // // Invalid assignment because both types are named + // var _ T2 = x + InvalidChanAssign + + // IncompatibleAssign occurs when the type of the right-hand side expression + // in an assignment cannot be assigned to the type of the variable being + // assigned. + // + // Example: + // var x []int + // var _ int = x + IncompatibleAssign + + // UnaddressableFieldAssign occurs when trying to assign to a struct field + // in a map value. + // + // Example: + // func f() { + // m := make(map[string]struct{i int}) + // m["foo"].i = 42 + // } + UnaddressableFieldAssign + + /* decls > type (+ other type expression codes) */ + + // NotAType occurs when the identifier used as the underlying type in a type + // declaration or the right-hand side of a type alias does not denote a type. + // + // Example: + // var S = 2 + // + // type T S + NotAType + + // InvalidArrayLen occurs when an array length is not a constant value. + // + // Example: + // var n = 3 + // var _ = [n]int{} + InvalidArrayLen + + // BlankIfaceMethod occurs when a method name is '_'. + // + // Per the spec: + // "The name of each explicitly specified method must be unique and not + // blank." + // + // Example: + // type T interface { + // _(int) + // } + BlankIfaceMethod + + // IncomparableMapKey occurs when a map key type does not support the == and + // != operators. + // + // Per the spec: + // "The comparison operators == and != must be fully defined for operands of + // the key type; thus the key type must not be a function, map, or slice." + // + // Example: + // var x map[T]int + // + // type T []int + IncomparableMapKey + + // InvalidIfaceEmbed occurs when a non-interface type is embedded in an + // interface. + // + // Example: + // type T struct {} + // + // func (T) m() + // + // type I interface { + // T + // } + InvalidIfaceEmbed + + // InvalidPtrEmbed occurs when an embedded field is of the pointer form *T, + // and T itself is itself a pointer, an unsafe.Pointer, or an interface. + // + // Per the spec: + // "An embedded field must be specified as a type name T or as a pointer to + // a non-interface type name *T, and T itself may not be a pointer type." + // + // Example: + // type T *int + // + // type S struct { + // *T + // } + InvalidPtrEmbed + + /* decls > func and method */ + + // BadRecv occurs when a method declaration does not have exactly one + // receiver parameter. + // + // Example: + // func () _() {} + BadRecv + + // InvalidRecv occurs when a receiver type expression is not of the form T + // or *T, or T is a pointer type. + // + // Example: + // type T struct {} + // + // func (**T) m() {} + InvalidRecv + + // DuplicateFieldAndMethod occurs when an identifier appears as both a field + // and method name. + // + // Example: + // type T struct { + // m int + // } + // + // func (T) m() {} + DuplicateFieldAndMethod + + // DuplicateMethod occurs when two methods on the same receiver type have + // the same name. + // + // Example: + // type T struct {} + // func (T) m() {} + // func (T) m(i int) int { return i } + DuplicateMethod + + /* decls > special */ + + // InvalidBlank occurs when a blank identifier is used as a value or type. + // + // Per the spec: + // "The blank identifier may appear as an operand only on the left-hand side + // of an assignment." + // + // Example: + // var x = _ + InvalidBlank + + // InvalidIota occurs when the predeclared identifier iota is used outside + // of a constant declaration. + // + // Example: + // var x = iota + InvalidIota + + // MissingInitBody occurs when an init function is missing its body. + // + // Example: + // func init() + MissingInitBody + + // InvalidInitSig occurs when an init function declares parameters or + // results. + // + // Example: + // func init() int { return 1 } + InvalidInitSig + + // InvalidInitDecl occurs when init is declared as anything other than a + // function. + // + // Example: + // var init = 1 + InvalidInitDecl + + // InvalidMainDecl occurs when main is declared as anything other than a + // function, in a main package. + InvalidMainDecl + + /* exprs */ + + // TooManyValues occurs when a function returns too many values for the + // expression context in which it is used. + // + // Example: + // func ReturnTwo() (int, int) { + // return 1, 2 + // } + // + // var x = ReturnTwo() + TooManyValues + + // NotAnExpr occurs when a type expression is used where a value expression + // is expected. + // + // Example: + // type T struct {} + // + // func f() { + // T + // } + NotAnExpr + + /* exprs > const */ + + // TruncatedFloat occurs when a float constant is truncated to an integer + // value. + // + // Example: + // var _ int = 98.6 + TruncatedFloat + + // NumericOverflow occurs when a numeric constant overflows its target type. + // + // Example: + // var x int8 = 1000 + NumericOverflow + + /* exprs > operation */ + + // UndefinedOp occurs when an operator is not defined for the type(s) used + // in an operation. + // + // Example: + // var c = "a" - "b" + UndefinedOp + + // MismatchedTypes occurs when operand types are incompatible in a binary + // operation. + // + // Example: + // var a = "hello" + // var b = 1 + // var c = a - b + MismatchedTypes + + // DivByZero occurs when a division operation is provable at compile + // time to be a division by zero. + // + // Example: + // const divisor = 0 + // var x int = 1/divisor + DivByZero + + // NonNumericIncDec occurs when an increment or decrement operator is + // applied to a non-numeric value. + // + // Example: + // func f() { + // var c = "c" + // c++ + // } + NonNumericIncDec + + /* exprs > ptr */ + + // UnaddressableOperand occurs when the & operator is applied to an + // unaddressable expression. + // + // Example: + // var x = &1 + UnaddressableOperand + + // InvalidIndirection occurs when a non-pointer value is indirected via the + // '*' operator. + // + // Example: + // var x int + // var y = *x + InvalidIndirection + + /* exprs > [] */ + + // NonIndexableOperand occurs when an index operation is applied to a value + // that cannot be indexed. + // + // Example: + // var x = 1 + // var y = x[1] + NonIndexableOperand + + // InvalidIndex occurs when an index argument is not of integer type, + // negative, or out-of-bounds. + // + // Example: + // var s = [...]int{1,2,3} + // var x = s[5] + // + // Example: + // var s = []int{1,2,3} + // var _ = s[-1] + // + // Example: + // var s = []int{1,2,3} + // var i string + // var _ = s[i] + InvalidIndex + + // SwappedSliceIndices occurs when constant indices in a slice expression + // are decreasing in value. + // + // Example: + // var _ = []int{1,2,3}[2:1] + SwappedSliceIndices + + /* operators > slice */ + + // NonSliceableOperand occurs when a slice operation is applied to a value + // whose type is not sliceable, or is unaddressable. + // + // Example: + // var x = [...]int{1, 2, 3}[:1] + // + // Example: + // var x = 1 + // var y = 1[:1] + NonSliceableOperand + + // InvalidSliceExpr occurs when a three-index slice expression (a[x:y:z]) is + // applied to a string. + // + // Example: + // var s = "hello" + // var x = s[1:2:3] + InvalidSliceExpr + + /* exprs > shift */ + + // InvalidShiftCount occurs when the right-hand side of a shift operation is + // either non-integer, negative, or too large. + // + // Example: + // var ( + // x string + // y int = 1 << x + // ) + InvalidShiftCount + + // InvalidShiftOperand occurs when the shifted operand is not an integer. + // + // Example: + // var s = "hello" + // var x = s << 2 + InvalidShiftOperand + + /* exprs > chan */ + + // InvalidReceive occurs when there is a channel receive from a value that + // is either not a channel, or is a send-only channel. + // + // Example: + // func f() { + // var x = 1 + // <-x + // } + InvalidReceive + + // InvalidSend occurs when there is a channel send to a value that is not a + // channel, or is a receive-only channel. + // + // Example: + // func f() { + // var x = 1 + // x <- "hello!" + // } + InvalidSend + + /* exprs > literal */ + + // DuplicateLitKey occurs when an index is duplicated in a slice, array, or + // map literal. + // + // Example: + // var _ = []int{0:1, 0:2} + // + // Example: + // var _ = map[string]int{"a": 1, "a": 2} + DuplicateLitKey + + // MissingLitKey occurs when a map literal is missing a key expression. + // + // Example: + // var _ = map[string]int{1} + MissingLitKey + + // InvalidLitIndex occurs when the key in a key-value element of a slice or + // array literal is not an integer constant. + // + // Example: + // var i = 0 + // var x = []string{i: "world"} + InvalidLitIndex + + // OversizeArrayLit occurs when an array literal exceeds its length. + // + // Example: + // var _ = [2]int{1,2,3} + OversizeArrayLit + + // MixedStructLit occurs when a struct literal contains a mix of positional + // and named elements. + // + // Example: + // var _ = struct{i, j int}{i: 1, 2} + MixedStructLit + + // InvalidStructLit occurs when a positional struct literal has an incorrect + // number of values. + // + // Example: + // var _ = struct{i, j int}{1,2,3} + InvalidStructLit + + // MissingLitField occurs when a struct literal refers to a field that does + // not exist on the struct type. + // + // Example: + // var _ = struct{i int}{j: 2} + MissingLitField + + // DuplicateLitField occurs when a struct literal contains duplicated + // fields. + // + // Example: + // var _ = struct{i int}{i: 1, i: 2} + DuplicateLitField + + // UnexportedLitField occurs when a positional struct literal implicitly + // assigns an unexported field of an imported type. + UnexportedLitField + + // InvalidLitField occurs when a field name is not a valid identifier. + // + // Example: + // var _ = struct{i int}{1: 1} + InvalidLitField + + // UntypedLit occurs when a composite literal omits a required type + // identifier. + // + // Example: + // type outer struct{ + // inner struct { i int } + // } + // + // var _ = outer{inner: {1}} + UntypedLit + + // InvalidLit occurs when a composite literal expression does not match its + // type. + // + // Example: + // type P *struct{ + // x int + // } + // var _ = P {} + InvalidLit + + /* exprs > selector */ + + // AmbiguousSelector occurs when a selector is ambiguous. + // + // Example: + // type E1 struct { i int } + // type E2 struct { i int } + // type T struct { E1; E2 } + // + // var x T + // var _ = x.i + AmbiguousSelector + + // UndeclaredImportedName occurs when a package-qualified identifier is + // undeclared by the imported package. + // + // Example: + // import "go/types" + // + // var _ = types.NotAnActualIdentifier + UndeclaredImportedName + + // UnexportedName occurs when a selector refers to an unexported identifier + // of an imported package. + // + // Example: + // import "reflect" + // + // type _ reflect.flag + UnexportedName + + // UndeclaredName occurs when an identifier is not declared in the current + // scope. + // + // Example: + // var x T + UndeclaredName + + // MissingFieldOrMethod occurs when a selector references a field or method + // that does not exist. + // + // Example: + // type T struct {} + // + // var x = T{}.f + MissingFieldOrMethod + + /* exprs > ... */ + + // BadDotDotDotSyntax occurs when a "..." occurs in a context where it is + // not valid. + // + // Example: + // var _ = map[int][...]int{0: {}} + BadDotDotDotSyntax + + // NonVariadicDotDotDot occurs when a "..." is used on the final argument to + // a non-variadic function. + // + // Example: + // func printArgs(s []string) { + // for _, a := range s { + // println(a) + // } + // } + // + // func f() { + // s := []string{"a", "b", "c"} + // printArgs(s...) + // } + NonVariadicDotDotDot + + // MisplacedDotDotDot occurs when a "..." is used somewhere other than the + // final argument to a function call. + // + // Example: + // func printArgs(args ...int) { + // for _, a := range args { + // println(a) + // } + // } + // + // func f() { + // a := []int{1,2,3} + // printArgs(0, a...) + // } + MisplacedDotDotDot + + // InvalidDotDotDotOperand occurs when a "..." operator is applied to a + // single-valued operand. + // + // Example: + // func printArgs(args ...int) { + // for _, a := range args { + // println(a) + // } + // } + // + // func f() { + // a := 1 + // printArgs(a...) + // } + // + // Example: + // func args() (int, int) { + // return 1, 2 + // } + // + // func printArgs(args ...int) { + // for _, a := range args { + // println(a) + // } + // } + // + // func g() { + // printArgs(args()...) + // } + InvalidDotDotDotOperand + + // InvalidDotDotDot occurs when a "..." is used in a non-variadic built-in + // function. + // + // Example: + // var s = []int{1, 2, 3} + // var l = len(s...) + InvalidDotDotDot + + /* exprs > built-in */ + + // UncalledBuiltin occurs when a built-in function is used as a + // function-valued expression, instead of being called. + // + // Per the spec: + // "The built-in functions do not have standard Go types, so they can only + // appear in call expressions; they cannot be used as function values." + // + // Example: + // var _ = copy + UncalledBuiltin + + // InvalidAppend occurs when append is called with a first argument that is + // not a slice. + // + // Example: + // var _ = append(1, 2) + InvalidAppend + + // InvalidCap occurs when an argument to the cap built-in function is not of + // supported type. + // + // See https://golang.org/ref/spec#Length_and_capacity for information on + // which underlying types are supported as arguments to cap and len. + // + // Example: + // var s = 2 + // var x = cap(s) + InvalidCap + + // InvalidClose occurs when close(...) is called with an argument that is + // not of channel type, or that is a receive-only channel. + // + // Example: + // func f() { + // var x int + // close(x) + // } + InvalidClose + + // InvalidCopy occurs when the arguments are not of slice type or do not + // have compatible type. + // + // See https://golang.org/ref/spec#Appending_and_copying_slices for more + // information on the type requirements for the copy built-in. + // + // Example: + // func f() { + // var x []int + // y := []int64{1,2,3} + // copy(x, y) + // } + InvalidCopy + + // InvalidComplex occurs when the complex built-in function is called with + // arguments with incompatible types. + // + // Example: + // var _ = complex(float32(1), float64(2)) + InvalidComplex + + // InvalidDelete occurs when the delete built-in function is called with a + // first argument that is not a map. + // + // Example: + // func f() { + // m := "hello" + // delete(m, "e") + // } + InvalidDelete + + // InvalidImag occurs when the imag built-in function is called with an + // argument that does not have complex type. + // + // Example: + // var _ = imag(int(1)) + InvalidImag + + // InvalidLen occurs when an argument to the len built-in function is not of + // supported type. + // + // See https://golang.org/ref/spec#Length_and_capacity for information on + // which underlying types are supported as arguments to cap and len. + // + // Example: + // var s = 2 + // var x = len(s) + InvalidLen + + // SwappedMakeArgs occurs when make is called with three arguments, and its + // length argument is larger than its capacity argument. + // + // Example: + // var x = make([]int, 3, 2) + SwappedMakeArgs + + // InvalidMake occurs when make is called with an unsupported type argument. + // + // See https://golang.org/ref/spec#Making_slices_maps_and_channels for + // information on the types that may be created using make. + // + // Example: + // var x = make(int) + InvalidMake + + // InvalidReal occurs when the real built-in function is called with an + // argument that does not have complex type. + // + // Example: + // var _ = real(int(1)) + InvalidReal + + /* exprs > assertion */ + + // InvalidAssert occurs when a type assertion is applied to a + // value that is not of interface type. + // + // Example: + // var x = 1 + // var _ = x.(float64) + InvalidAssert + + // ImpossibleAssert occurs for a type assertion x.(T) when the value x of + // interface cannot have dynamic type T, due to a missing or mismatching + // method on T. + // + // Example: + // type T int + // + // func (t *T) m() int { return int(*t) } + // + // type I interface { m() int } + // + // var x I + // var _ = x.(T) + ImpossibleAssert + + /* exprs > conversion */ + + // InvalidConversion occurs when the argument type cannot be converted to the + // target. + // + // See https://golang.org/ref/spec#Conversions for the rules of + // convertibility. + // + // Example: + // var x float64 + // var _ = string(x) + InvalidConversion + + // InvalidUntypedConversion occurs when there is no valid implicit + // conversion from an untyped value satisfying the type constraints of the + // context in which it is used. + // + // Example: + // var _ = 1 + "" + InvalidUntypedConversion + + /* offsetof */ + + // BadOffsetofSyntax occurs when unsafe.Offsetof is called with an argument + // that is not a selector expression. + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Offsetof(x) + BadOffsetofSyntax + + // InvalidOffsetof occurs when unsafe.Offsetof is called with a method + // selector, rather than a field selector, or when the field is embedded via + // a pointer. + // + // Per the spec: + // + // "If f is an embedded field, it must be reachable without pointer + // indirections through fields of the struct. " + // + // Example: + // import "unsafe" + // + // type T struct { f int } + // type S struct { *T } + // var s S + // var _ = unsafe.Offsetof(s.f) + // + // Example: + // import "unsafe" + // + // type S struct{} + // + // func (S) m() {} + // + // var s S + // var _ = unsafe.Offsetof(s.m) + InvalidOffsetof + + /* control flow > scope */ + + // UnusedExpr occurs when a side-effect free expression is used as a + // statement. Such a statement has no effect. + // + // Example: + // func f(i int) { + // i*i + // } + UnusedExpr + + // UnusedVar occurs when a variable is declared but unused. + // + // Example: + // func f() { + // x := 1 + // } + UnusedVar + + // MissingReturn occurs when a function with results is missing a return + // statement. + // + // Example: + // func f() int {} + MissingReturn + + // WrongResultCount occurs when a return statement returns an incorrect + // number of values. + // + // Example: + // func ReturnOne() int { + // return 1, 2 + // } + WrongResultCount + + // OutOfScopeResult occurs when the name of a value implicitly returned by + // an empty return statement is shadowed in a nested scope. + // + // Example: + // func factor(n int) (i int) { + // for i := 2; i < n; i++ { + // if n%i == 0 { + // return + // } + // } + // return 0 + // } + OutOfScopeResult + + /* control flow > if */ + + // InvalidCond occurs when an if condition is not a boolean expression. + // + // Example: + // func checkReturn(i int) { + // if i { + // panic("non-zero return") + // } + // } + InvalidCond + + /* control flow > for */ + + // InvalidPostDecl occurs when there is a declaration in a for-loop post + // statement. + // + // Example: + // func f() { + // for i := 0; i < 10; j := 0 {} + // } + InvalidPostDecl + + // InvalidChanRange occurs when a send-only channel used in a range + // expression. + // + // Example: + // func sum(c chan<- int) { + // s := 0 + // for i := range c { + // s += i + // } + // } + InvalidChanRange + + // InvalidIterVar occurs when two iteration variables are used while ranging + // over a channel. + // + // Example: + // func f(c chan int) { + // for k, v := range c { + // println(k, v) + // } + // } + InvalidIterVar + + // InvalidRangeExpr occurs when the type of a range expression is not array, + // slice, string, map, or channel. + // + // Example: + // func f(i int) { + // for j := range i { + // println(j) + // } + // } + InvalidRangeExpr + + /* control flow > switch */ + + // MisplacedBreak occurs when a break statement is not within a for, switch, + // or select statement of the innermost function definition. + // + // Example: + // func f() { + // break + // } + MisplacedBreak + + // MisplacedContinue occurs when a continue statement is not within a for + // loop of the innermost function definition. + // + // Example: + // func sumeven(n int) int { + // proceed := func() { + // continue + // } + // sum := 0 + // for i := 1; i <= n; i++ { + // if i % 2 != 0 { + // proceed() + // } + // sum += i + // } + // return sum + // } + MisplacedContinue + + // MisplacedFallthrough occurs when a fallthrough statement is not within an + // expression switch. + // + // Example: + // func typename(i interface{}) string { + // switch i.(type) { + // case int64: + // fallthrough + // case int: + // return "int" + // } + // return "unsupported" + // } + MisplacedFallthrough + + // DuplicateCase occurs when a type or expression switch has duplicate + // cases. + // + // Example: + // func printInt(i int) { + // switch i { + // case 1: + // println("one") + // case 1: + // println("One") + // } + // } + DuplicateCase + + // DuplicateDefault occurs when a type or expression switch has multiple + // default clauses. + // + // Example: + // func printInt(i int) { + // switch i { + // case 1: + // println("one") + // default: + // println("One") + // default: + // println("1") + // } + // } + DuplicateDefault + + // BadTypeKeyword occurs when a .(type) expression is used anywhere other + // than a type switch. + // + // Example: + // type I interface { + // m() + // } + // var t I + // var _ = t.(type) + BadTypeKeyword + + // InvalidTypeSwitch occurs when .(type) is used on an expression that is + // not of interface type. + // + // Example: + // func f(i int) { + // switch x := i.(type) {} + // } + InvalidTypeSwitch + + // InvalidExprSwitch occurs when a switch expression is not comparable. + // + // Example: + // func _() { + // var a struct{ _ func() } + // switch a /* ERROR cannot switch on a */ { + // } + // } + InvalidExprSwitch + + /* control flow > select */ + + // InvalidSelectCase occurs when a select case is not a channel send or + // receive. + // + // Example: + // func checkChan(c <-chan int) bool { + // select { + // case c: + // return true + // default: + // return false + // } + // } + InvalidSelectCase + + /* control flow > labels and jumps */ + + // UndeclaredLabel occurs when an undeclared label is jumped to. + // + // Example: + // func f() { + // goto L + // } + UndeclaredLabel + + // DuplicateLabel occurs when a label is declared more than once. + // + // Example: + // func f() int { + // L: + // L: + // return 1 + // } + DuplicateLabel + + // MisplacedLabel occurs when a break or continue label is not on a for, + // switch, or select statement. + // + // Example: + // func f() { + // L: + // a := []int{1,2,3} + // for _, e := range a { + // if e > 10 { + // break L + // } + // println(a) + // } + // } + MisplacedLabel + + // UnusedLabel occurs when a label is declared but not used. + // + // Example: + // func f() { + // L: + // } + UnusedLabel + + // JumpOverDecl occurs when a label jumps over a variable declaration. + // + // Example: + // func f() int { + // goto L + // x := 2 + // L: + // x++ + // return x + // } + JumpOverDecl + + // JumpIntoBlock occurs when a forward jump goes to a label inside a nested + // block. + // + // Example: + // func f(x int) { + // goto L + // if x > 0 { + // L: + // print("inside block") + // } + // } + JumpIntoBlock + + /* control flow > calls */ + + // InvalidMethodExpr occurs when a pointer method is called but the argument + // is not addressable. + // + // Example: + // type T struct {} + // + // func (*T) m() int { return 1 } + // + // var _ = T.m(T{}) + InvalidMethodExpr + + // WrongArgCount occurs when too few or too many arguments are passed by a + // function call. + // + // Example: + // func f(i int) {} + // var x = f() + WrongArgCount + + // InvalidCall occurs when an expression is called that is not of function + // type. + // + // Example: + // var x = "x" + // var y = x() + InvalidCall + + /* control flow > suspended */ + + // UnusedResults occurs when a restricted expression-only built-in function + // is suspended via go or defer. Such a suspension discards the results of + // these side-effect free built-in functions, and therefore is ineffectual. + // + // Example: + // func f(a []int) int { + // defer len(a) + // return i + // } + UnusedResults + + // InvalidDefer occurs when a deferred expression is not a function call, + // for example if the expression is a type conversion. + // + // Example: + // func f(i int) int { + // defer int32(i) + // return i + // } + InvalidDefer + + // InvalidGo occurs when a go expression is not a function call, for example + // if the expression is a type conversion. + // + // Example: + // func f(i int) int { + // go int32(i) + // return i + // } + InvalidGo + + // All codes below were added in Go 1.17. + + /* decl */ + + // BadDecl occurs when a declaration has invalid syntax. + BadDecl + + // RepeatedDecl occurs when an identifier occurs more than once on the left + // hand side of a short variable declaration. + // + // Example: + // func _() { + // x, y, y := 1, 2, 3 + // } + RepeatedDecl + + /* unsafe */ + + // InvalidUnsafeAdd occurs when unsafe.Add is called with a + // length argument that is not of integer type. + // + // Example: + // import "unsafe" + // + // var p unsafe.Pointer + // var _ = unsafe.Add(p, float64(1)) + InvalidUnsafeAdd + + // InvalidUnsafeSlice occurs when unsafe.Slice is called with a + // pointer argument that is not of pointer type or a length argument + // that is not of integer type, negative, or out of bounds. + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Slice(x, 1) + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Slice(&x, float64(1)) + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Slice(&x, -1) + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Slice(&x, uint64(1) << 63) + InvalidUnsafeSlice + + // All codes below were added in Go 1.18. + + /* features */ + + // UnsupportedFeature occurs when a language feature is used that is not + // supported at this Go version. + UnsupportedFeature + + /* type params */ + + // NotAGenericType occurs when a non-generic type is used where a generic + // type is expected: in type or function instantiation. + // + // Example: + // type T int + // + // var _ T[int] + NotAGenericType + + // WrongTypeArgCount occurs when a type or function is instantiated with an + // incorrect number of type arguments, including when a generic type or + // function is used without instantiation. + // + // Errors involving failed type inference are assigned other error codes. + // + // Example: + // type T[p any] int + // + // var _ T[int, string] + // + // Example: + // func f[T any]() {} + // + // var x = f + WrongTypeArgCount + + // CannotInferTypeArgs occurs when type or function type argument inference + // fails to infer all type arguments. + // + // Example: + // func f[T any]() {} + // + // func _() { + // f() + // } + // + // Example: + // type N[P, Q any] struct{} + // + // var _ N[int] + CannotInferTypeArgs + + // InvalidTypeArg occurs when a type argument does not satisfy its + // corresponding type parameter constraints. + // + // Example: + // type T[P ~int] struct{} + // + // var _ T[string] + InvalidTypeArg // arguments? InferenceFailed + + // InvalidInstanceCycle occurs when an invalid cycle is detected + // within the instantiation graph. + // + // Example: + // func f[T any]() { f[*T]() } + InvalidInstanceCycle + + // InvalidUnion occurs when an embedded union or approximation element is + // not valid. + // + // Example: + // type _ interface { + // ~int | interface{ m() } + // } + InvalidUnion + + // MisplacedConstraintIface occurs when a constraint-type interface is used + // outside of constraint position. + // + // Example: + // type I interface { ~int } + // + // var _ I + MisplacedConstraintIface + + // InvalidMethodTypeParams occurs when methods have type parameters. + // + // It cannot be encountered with an AST parsed using go/parser. + InvalidMethodTypeParams + + // MisplacedTypeParam occurs when a type parameter is used in a place where + // it is not permitted. + // + // Example: + // type T[P any] P + // + // Example: + // type T[P any] struct{ *P } + MisplacedTypeParam + + // InvalidUnsafeSliceData occurs when unsafe.SliceData is called with + // an argument that is not of slice type. It also occurs if it is used + // in a package compiled for a language version before go1.20. + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.SliceData(x) + InvalidUnsafeSliceData + + // InvalidUnsafeString occurs when unsafe.String is called with + // a length argument that is not of integer type, negative, or + // out of bounds. It also occurs if it is used in a package + // compiled for a language version before go1.20. + // + // Example: + // import "unsafe" + // + // var b [10]byte + // var _ = unsafe.String(&b[0], -1) + InvalidUnsafeString + + // InvalidUnsafeStringData occurs if it is used in a package + // compiled for a language version before go1.20. + _ // not used anymore + +) diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode_string.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode_string.go new file mode 100644 index 000000000..15ecf7c5d --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode_string.go @@ -0,0 +1,179 @@ +// Code generated by "stringer -type=ErrorCode"; DO NOT EDIT. + +package typesinternal + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[InvalidSyntaxTree - -1] + _ = x[Test-1] + _ = x[BlankPkgName-2] + _ = x[MismatchedPkgName-3] + _ = x[InvalidPkgUse-4] + _ = x[BadImportPath-5] + _ = x[BrokenImport-6] + _ = x[ImportCRenamed-7] + _ = x[UnusedImport-8] + _ = x[InvalidInitCycle-9] + _ = x[DuplicateDecl-10] + _ = x[InvalidDeclCycle-11] + _ = x[InvalidTypeCycle-12] + _ = x[InvalidConstInit-13] + _ = x[InvalidConstVal-14] + _ = x[InvalidConstType-15] + _ = x[UntypedNilUse-16] + _ = x[WrongAssignCount-17] + _ = x[UnassignableOperand-18] + _ = x[NoNewVar-19] + _ = x[MultiValAssignOp-20] + _ = x[InvalidIfaceAssign-21] + _ = x[InvalidChanAssign-22] + _ = x[IncompatibleAssign-23] + _ = x[UnaddressableFieldAssign-24] + _ = x[NotAType-25] + _ = x[InvalidArrayLen-26] + _ = x[BlankIfaceMethod-27] + _ = x[IncomparableMapKey-28] + _ = x[InvalidIfaceEmbed-29] + _ = x[InvalidPtrEmbed-30] + _ = x[BadRecv-31] + _ = x[InvalidRecv-32] + _ = x[DuplicateFieldAndMethod-33] + _ = x[DuplicateMethod-34] + _ = x[InvalidBlank-35] + _ = x[InvalidIota-36] + _ = x[MissingInitBody-37] + _ = x[InvalidInitSig-38] + _ = x[InvalidInitDecl-39] + _ = x[InvalidMainDecl-40] + _ = x[TooManyValues-41] + _ = x[NotAnExpr-42] + _ = x[TruncatedFloat-43] + _ = x[NumericOverflow-44] + _ = x[UndefinedOp-45] + _ = x[MismatchedTypes-46] + _ = x[DivByZero-47] + _ = x[NonNumericIncDec-48] + _ = x[UnaddressableOperand-49] + _ = x[InvalidIndirection-50] + _ = x[NonIndexableOperand-51] + _ = x[InvalidIndex-52] + _ = x[SwappedSliceIndices-53] + _ = x[NonSliceableOperand-54] + _ = x[InvalidSliceExpr-55] + _ = x[InvalidShiftCount-56] + _ = x[InvalidShiftOperand-57] + _ = x[InvalidReceive-58] + _ = x[InvalidSend-59] + _ = x[DuplicateLitKey-60] + _ = x[MissingLitKey-61] + _ = x[InvalidLitIndex-62] + _ = x[OversizeArrayLit-63] + _ = x[MixedStructLit-64] + _ = x[InvalidStructLit-65] + _ = x[MissingLitField-66] + _ = x[DuplicateLitField-67] + _ = x[UnexportedLitField-68] + _ = x[InvalidLitField-69] + _ = x[UntypedLit-70] + _ = x[InvalidLit-71] + _ = x[AmbiguousSelector-72] + _ = x[UndeclaredImportedName-73] + _ = x[UnexportedName-74] + _ = x[UndeclaredName-75] + _ = x[MissingFieldOrMethod-76] + _ = x[BadDotDotDotSyntax-77] + _ = x[NonVariadicDotDotDot-78] + _ = x[MisplacedDotDotDot-79] + _ = x[InvalidDotDotDotOperand-80] + _ = x[InvalidDotDotDot-81] + _ = x[UncalledBuiltin-82] + _ = x[InvalidAppend-83] + _ = x[InvalidCap-84] + _ = x[InvalidClose-85] + _ = x[InvalidCopy-86] + _ = x[InvalidComplex-87] + _ = x[InvalidDelete-88] + _ = x[InvalidImag-89] + _ = x[InvalidLen-90] + _ = x[SwappedMakeArgs-91] + _ = x[InvalidMake-92] + _ = x[InvalidReal-93] + _ = x[InvalidAssert-94] + _ = x[ImpossibleAssert-95] + _ = x[InvalidConversion-96] + _ = x[InvalidUntypedConversion-97] + _ = x[BadOffsetofSyntax-98] + _ = x[InvalidOffsetof-99] + _ = x[UnusedExpr-100] + _ = x[UnusedVar-101] + _ = x[MissingReturn-102] + _ = x[WrongResultCount-103] + _ = x[OutOfScopeResult-104] + _ = x[InvalidCond-105] + _ = x[InvalidPostDecl-106] + _ = x[InvalidChanRange-107] + _ = x[InvalidIterVar-108] + _ = x[InvalidRangeExpr-109] + _ = x[MisplacedBreak-110] + _ = x[MisplacedContinue-111] + _ = x[MisplacedFallthrough-112] + _ = x[DuplicateCase-113] + _ = x[DuplicateDefault-114] + _ = x[BadTypeKeyword-115] + _ = x[InvalidTypeSwitch-116] + _ = x[InvalidExprSwitch-117] + _ = x[InvalidSelectCase-118] + _ = x[UndeclaredLabel-119] + _ = x[DuplicateLabel-120] + _ = x[MisplacedLabel-121] + _ = x[UnusedLabel-122] + _ = x[JumpOverDecl-123] + _ = x[JumpIntoBlock-124] + _ = x[InvalidMethodExpr-125] + _ = x[WrongArgCount-126] + _ = x[InvalidCall-127] + _ = x[UnusedResults-128] + _ = x[InvalidDefer-129] + _ = x[InvalidGo-130] + _ = x[BadDecl-131] + _ = x[RepeatedDecl-132] + _ = x[InvalidUnsafeAdd-133] + _ = x[InvalidUnsafeSlice-134] + _ = x[UnsupportedFeature-135] + _ = x[NotAGenericType-136] + _ = x[WrongTypeArgCount-137] + _ = x[CannotInferTypeArgs-138] + _ = x[InvalidTypeArg-139] + _ = x[InvalidInstanceCycle-140] + _ = x[InvalidUnion-141] + _ = x[MisplacedConstraintIface-142] + _ = x[InvalidMethodTypeParams-143] + _ = x[MisplacedTypeParam-144] + _ = x[InvalidUnsafeSliceData-145] + _ = x[InvalidUnsafeString-146] +} + +const ( + _ErrorCode_name_0 = "InvalidSyntaxTree" + _ErrorCode_name_1 = "TestBlankPkgNameMismatchedPkgNameInvalidPkgUseBadImportPathBrokenImportImportCRenamedUnusedImportInvalidInitCycleDuplicateDeclInvalidDeclCycleInvalidTypeCycleInvalidConstInitInvalidConstValInvalidConstTypeUntypedNilUseWrongAssignCountUnassignableOperandNoNewVarMultiValAssignOpInvalidIfaceAssignInvalidChanAssignIncompatibleAssignUnaddressableFieldAssignNotATypeInvalidArrayLenBlankIfaceMethodIncomparableMapKeyInvalidIfaceEmbedInvalidPtrEmbedBadRecvInvalidRecvDuplicateFieldAndMethodDuplicateMethodInvalidBlankInvalidIotaMissingInitBodyInvalidInitSigInvalidInitDeclInvalidMainDeclTooManyValuesNotAnExprTruncatedFloatNumericOverflowUndefinedOpMismatchedTypesDivByZeroNonNumericIncDecUnaddressableOperandInvalidIndirectionNonIndexableOperandInvalidIndexSwappedSliceIndicesNonSliceableOperandInvalidSliceExprInvalidShiftCountInvalidShiftOperandInvalidReceiveInvalidSendDuplicateLitKeyMissingLitKeyInvalidLitIndexOversizeArrayLitMixedStructLitInvalidStructLitMissingLitFieldDuplicateLitFieldUnexportedLitFieldInvalidLitFieldUntypedLitInvalidLitAmbiguousSelectorUndeclaredImportedNameUnexportedNameUndeclaredNameMissingFieldOrMethodBadDotDotDotSyntaxNonVariadicDotDotDotMisplacedDotDotDotInvalidDotDotDotOperandInvalidDotDotDotUncalledBuiltinInvalidAppendInvalidCapInvalidCloseInvalidCopyInvalidComplexInvalidDeleteInvalidImagInvalidLenSwappedMakeArgsInvalidMakeInvalidRealInvalidAssertImpossibleAssertInvalidConversionInvalidUntypedConversionBadOffsetofSyntaxInvalidOffsetofUnusedExprUnusedVarMissingReturnWrongResultCountOutOfScopeResultInvalidCondInvalidPostDeclInvalidChanRangeInvalidIterVarInvalidRangeExprMisplacedBreakMisplacedContinueMisplacedFallthroughDuplicateCaseDuplicateDefaultBadTypeKeywordInvalidTypeSwitchInvalidExprSwitchInvalidSelectCaseUndeclaredLabelDuplicateLabelMisplacedLabelUnusedLabelJumpOverDeclJumpIntoBlockInvalidMethodExprWrongArgCountInvalidCallUnusedResultsInvalidDeferInvalidGoBadDeclRepeatedDeclInvalidUnsafeAddInvalidUnsafeSliceUnsupportedFeatureNotAGenericTypeWrongTypeArgCountCannotInferTypeArgsInvalidTypeArgInvalidInstanceCycleInvalidUnionMisplacedConstraintIfaceInvalidMethodTypeParamsMisplacedTypeParamInvalidUnsafeSliceDataInvalidUnsafeString" +) + +var ( + _ErrorCode_index_1 = [...]uint16{0, 4, 16, 33, 46, 59, 71, 85, 97, 113, 126, 142, 158, 174, 189, 205, 218, 234, 253, 261, 277, 295, 312, 330, 354, 362, 377, 393, 411, 428, 443, 450, 461, 484, 499, 511, 522, 537, 551, 566, 581, 594, 603, 617, 632, 643, 658, 667, 683, 703, 721, 740, 752, 771, 790, 806, 823, 842, 856, 867, 882, 895, 910, 926, 940, 956, 971, 988, 1006, 1021, 1031, 1041, 1058, 1080, 1094, 1108, 1128, 1146, 1166, 1184, 1207, 1223, 1238, 1251, 1261, 1273, 1284, 1298, 1311, 1322, 1332, 1347, 1358, 1369, 1382, 1398, 1415, 1439, 1456, 1471, 1481, 1490, 1503, 1519, 1535, 1546, 1561, 1577, 1591, 1607, 1621, 1638, 1658, 1671, 1687, 1701, 1718, 1735, 1752, 1767, 1781, 1795, 1806, 1818, 1831, 1848, 1861, 1872, 1885, 1897, 1906, 1913, 1925, 1941, 1959, 1977, 1992, 2009, 2028, 2042, 2062, 2074, 2098, 2121, 2139, 2161, 2180} +) + +func (i ErrorCode) String() string { + switch { + case i == -1: + return _ErrorCode_name_0 + case 1 <= i && i <= 146: + i -= 1 + return _ErrorCode_name_1[_ErrorCode_index_1[i]:_ErrorCode_index_1[i+1]] + default: + return "ErrorCode(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/fx.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/fx.go new file mode 100644 index 000000000..c846a53d5 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/fx.go @@ -0,0 +1,88 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "go/ast" + "go/token" + "go/types" +) + +// NoEffects reports whether the expression has no side effects, i.e., it +// does not modify the memory state. This function is conservative: it may +// return false even when the expression has no effect. +func NoEffects(info *types.Info, expr ast.Expr) bool { + noEffects := true + ast.Inspect(expr, func(n ast.Node) bool { + switch v := n.(type) { + case nil, *ast.Ident, *ast.BasicLit, *ast.BinaryExpr, *ast.ParenExpr, + *ast.SelectorExpr, *ast.IndexExpr, *ast.SliceExpr, *ast.TypeAssertExpr, + *ast.StarExpr, *ast.CompositeLit, + // non-expressions that may appear within expressions + *ast.KeyValueExpr, + *ast.FieldList, + *ast.Field, + *ast.Ellipsis, + *ast.IndexListExpr: + // No effect. + + case *ast.ArrayType, + *ast.StructType, + *ast.ChanType, + *ast.FuncType, + *ast.MapType, + *ast.InterfaceType: + // Type syntax: no effects, recursively. + // Prune descent. + return false + + case *ast.UnaryExpr: + // Channel send <-ch has effects. + if v.Op == token.ARROW { + noEffects = false + } + + case *ast.CallExpr: + // Type conversion has no effects. + if !info.Types[v.Fun].IsType() { + if CallsPureBuiltin(info, v) { + // A call such as len(e) has no effects of its + // own, though the subexpression e might. + } else { + noEffects = false + } + } + + case *ast.FuncLit: + // A FuncLit has no effects, but do not descend into it. + return false + + default: + // All other expressions have effects + noEffects = false + } + + return noEffects + }) + return noEffects +} + +// CallsPureBuiltin reports whether call is a call of a built-in +// function that is a pure computation over its operands (analogous to +// a + operator). Because it does not depend on program state, it may +// be evaluated at any point--though not necessarily at multiple +// points (consider new, make). +func CallsPureBuiltin(info *types.Info, call *ast.CallExpr) bool { + if id, ok := ast.Unparen(call.Fun).(*ast.Ident); ok { + if b, ok := info.ObjectOf(id).(*types.Builtin); ok { + switch b.Name() { + case "len", "cap", "complex", "imag", "real", "make", "new", "max", "min": + return true + } + // Not: append clear close copy delete panic print println recover + } + } + return false +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/isnamed.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/isnamed.go new file mode 100644 index 000000000..e0d63c46c --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/isnamed.go @@ -0,0 +1,71 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "go/types" + "slices" +) + +// IsTypeNamed reports whether t is (or is an alias for) a +// package-level defined type with the given package path and one of +// the given names. It returns false if t is nil. +// +// This function avoids allocating the concatenation of "pkg.Name", +// which is important for the performance of syntax matching. +func IsTypeNamed(t types.Type, pkgPath string, names ...string) bool { + if named, ok := types.Unalias(t).(*types.Named); ok { + tname := named.Obj() + return tname != nil && + IsPackageLevel(tname) && + tname.Pkg().Path() == pkgPath && + slices.Contains(names, tname.Name()) + } + return false +} + +// IsPointerToNamed reports whether t is (or is an alias for) a pointer to a +// package-level defined type with the given package path and one of the given +// names. It returns false if t is not a pointer type. +func IsPointerToNamed(t types.Type, pkgPath string, names ...string) bool { + r := Unpointer(t) + if r == t { + return false + } + return IsTypeNamed(r, pkgPath, names...) +} + +// IsFunctionNamed reports whether obj is a package-level function +// defined in the given package and has one of the given names. +// It returns false if obj is nil. +// +// This function avoids allocating the concatenation of "pkg.Name", +// which is important for the performance of syntax matching. +func IsFunctionNamed(obj types.Object, pkgPath string, names ...string) bool { + f, ok := obj.(*types.Func) + return ok && + IsPackageLevel(obj) && + f.Pkg().Path() == pkgPath && + f.Signature().Recv() == nil && + slices.Contains(names, f.Name()) +} + +// IsMethodNamed reports whether obj is a method defined on a +// package-level type with the given package and type name, and has +// one of the given names. It returns false if obj is nil. +// +// This function avoids allocating the concatenation of "pkg.TypeName.Name", +// which is important for the performance of syntax matching. +func IsMethodNamed(obj types.Object, pkgPath string, typeName string, names ...string) bool { + if fn, ok := obj.(*types.Func); ok { + if recv := fn.Signature().Recv(); recv != nil { + _, T := ReceiverNamed(recv) + return T != nil && + IsTypeNamed(T, pkgPath, typeName) && + slices.Contains(names, fn.Name()) + } + } + return false +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/qualifier.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/qualifier.go new file mode 100644 index 000000000..4e2756fc4 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/qualifier.go @@ -0,0 +1,54 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "go/ast" + "go/types" + "strconv" +) + +// FileQualifier returns a [types.Qualifier] function that qualifies +// imported symbols appropriately based on the import environment of a given +// file. +// If the same package is imported multiple times, the last appearance is +// recorded. +// +// TODO(adonovan): this function ignores the effect of shadowing. It +// should accept a [token.Pos] and a [types.Info] and compute only the +// set of imports that are not shadowed at that point, analogous to +// [analysis.AddImport]. It could also compute (as a side +// effect) the set of additional imports required to ensure that there +// is an accessible import for each necessary package, making it +// converge even more closely with AddImport. +func FileQualifier(f *ast.File, pkg *types.Package) types.Qualifier { + // Construct mapping of import paths to their defined names. + // It is only necessary to look at renaming imports. + imports := make(map[string]string) + for _, imp := range f.Imports { + if imp.Name != nil && imp.Name.Name != "_" { + path, _ := strconv.Unquote(imp.Path.Value) + imports[path] = imp.Name.Name + } + } + + // Define qualifier to replace full package paths with names of the imports. + return func(p *types.Package) string { + if p == nil || p == pkg { + return "" + } + + if name, ok := imports[p.Path()]; ok { + if name == "." { + return "" + } else { + return name + } + } + + // If there is no local renaming, fall back to the package name. + return p.Name() + } +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/recv.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/recv.go new file mode 100644 index 000000000..8352ea761 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/recv.go @@ -0,0 +1,44 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "go/types" +) + +// ReceiverNamed returns the named type (if any) associated with the +// type of recv, which may be of the form N or *N, or aliases thereof. +// It also reports whether a Pointer was present. +// +// The named result may be nil if recv is from a method on an +// anonymous interface or struct types or in ill-typed code. +func ReceiverNamed(recv *types.Var) (isPtr bool, named *types.Named) { + t := recv.Type() + if ptr, ok := types.Unalias(t).(*types.Pointer); ok { + isPtr = true + t = ptr.Elem() + } + named, _ = types.Unalias(t).(*types.Named) + return +} + +// Unpointer returns T given *T or an alias thereof. +// For all other types it is the identity function. +// It does not look at underlying types. +// The result may be an alias. +// +// Use this function to strip off the optional pointer on a receiver +// in a field or method selection, without losing the named type +// (which is needed to compute the method set). +// +// See also [typeparams.MustDeref], which removes one level of +// indirection from the type, regardless of named types (analogous to +// a LOAD instruction). +func Unpointer(t types.Type) types.Type { + if ptr, ok := types.Unalias(t).(*types.Pointer); ok { + return ptr.Elem() + } + return t +} diff --git a/tools/vendor/honnef.co/go/tools/internal/typesinternal/typeindex/typeindex.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex/typeindex.go similarity index 74% rename from tools/vendor/honnef.co/go/tools/internal/typesinternal/typeindex/typeindex.go rename to tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex/typeindex.go index fccc2ba72..9a8ac36eb 100644 --- a/tools/vendor/honnef.co/go/tools/internal/typesinternal/typeindex/typeindex.go +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex/typeindex.go @@ -17,13 +17,10 @@ import ( "golang.org/x/tools/go/ast/edge" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/types/typeutil" + "honnef.co/go/tools/internal/xtools-internal/astutil" + "honnef.co/go/tools/internal/xtools-internal/typesinternal" ) -// IsPackageLevel reports whether obj is a package-level symbol. -func IsPackageLevel(obj types.Object) bool { - return obj.Pkg() != nil && obj.Parent() == obj.Pkg().Scope() -} - // New constructs an Index for the package of type-annotated syntax // // TODO(adonovan): accept a FileSet too? @@ -59,28 +56,63 @@ func New(inspect *inspector.Inspector, pkg *types.Package, info *types.Info) *In if obj := info.Uses[n]; obj != nil { // Index indirect dependencies (via fields and methods). - if !IsPackageLevel(obj) { + if !typesinternal.IsPackageLevel(obj) { addPackage(obj.Pkg()) } - us, ok := ix.uses[obj] - if !ok { - us = &uses{} - us.code = us.initial[:0] - ix.uses[obj] = us - } - delta := cur.Index() - us.last - if delta < 0 { - panic("non-monotonic") + for { + us, ok := ix.uses[obj] + if !ok { + us = &uses{} + us.code = us.initial[:0] + ix.uses[obj] = us + } + delta := cur.Index() - us.last + if delta < 0 { + panic("non-monotonic") + } + us.code = binary.AppendUvarint(us.code, uint64(delta)) + us.last = cur.Index() + + // If n is a selection of a field or method of an instantiated + // type, also record a use of the generic field or method. + obj, ok = objectOrigin(obj) + if !ok { + break + } } - us.code = binary.AppendUvarint(us.code, uint64(delta)) - us.last = cur.Index() } } } return ix } +// objectOrigin returns the generic object for obj if it is a field or +// method of an instantied type; zero otherwise. +// +// (This operation is appropriate only for selections. +// Lexically resolved references always resolve to the generic. +// Although Named and Alias types also use Origin to express +// an instance/generic distinction, that's in the domain +// of Types; their TypeName objects always refer to the generic.) +func objectOrigin(obj types.Object) (types.Object, bool) { + var origin types.Object + switch obj := obj.(type) { + case *types.Func: + if obj.Signature().Recv() != nil { + origin = obj.Origin() // G[int].method -> G[T].method + } + case *types.Var: + if obj.IsField() { + origin = obj.Origin() // G[int].field -> G[T].field + } + } + if origin != nil && origin != obj { + return origin, true + } + return nil, false +} + // An Index holds an index mapping [types.Object] symbols to their syntax. // In effect, it is the inverse of [types.Info]. type Index struct { @@ -110,6 +142,10 @@ type uses struct { // Uses returns the sequence of Cursors of [*ast.Ident]s in this package // that refer to obj. If obj is nil, the sequence is empty. +// +// Uses, unlike the Uses field of [types.Info], records additional +// entries mapping fields and methods of generic types to references +// through their corresponding instantiated objects. func (ix *Index) Uses(obj types.Object) iter.Seq[inspector.Cursor] { return func(yield func(inspector.Cursor) bool) { if uses := ix.uses[obj]; uses != nil { @@ -182,10 +218,9 @@ func (ix *Index) Selection(path, typename, name string) types.Object { func (ix *Index) Calls(callee types.Object) iter.Seq[inspector.Cursor] { return func(yield func(inspector.Cursor) bool) { for cur := range ix.Uses(callee) { - ek, _ := cur.ParentEdge() - // The call may be of the form f() or x.f(), // optionally with parens; ascend from f to call. + // See logic in [typesinternal.UsedIdent], to which this is dual. // // It is tempting but wrong to use the first // CallExpr ancestor: we have to make sure the @@ -194,25 +229,20 @@ func (ix *Index) Calls(callee types.Object) iter.Seq[inspector.Cursor] { // Avoiding Enclosing is also significantly faster. // inverse unparen: f -> (f) - for ek == edge.ParenExpr_X { - cur = cur.Parent() - ek, _ = cur.ParentEdge() - } + cur = astutil.UnparenEnclosingCursor(cur) - // ascend selector: f -> x.f - if ek == edge.SelectorExpr_Sel { - cur = cur.Parent() - ek, _ = cur.ParentEdge() + // ascend selector (or qualified identifier): f -> x.f + if cur.ParentEdgeKind() == edge.SelectorExpr_Sel { + cur = astutil.UnparenEnclosingCursor(cur.Parent()) } - // inverse unparen again - for ek == edge.ParenExpr_X { - cur = cur.Parent() - ek, _ = cur.ParentEdge() + // ascend typeparams: f -> f[T]; f -> f[T1, T2] + if ek := cur.ParentEdgeKind(); ek == edge.IndexExpr_X || ek == edge.IndexListExpr_X { + cur = astutil.UnparenEnclosingCursor(cur.Parent()) } // ascend from f or x.f to call - if ek == edge.CallExpr_Fun { + if cur.ParentEdgeKind() == edge.CallExpr_Fun { curCall := cur.Parent() call := curCall.Node().(*ast.CallExpr) if typeutil.Callee(ix.info, call) == callee { diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/types.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/types.go new file mode 100644 index 000000000..50942b778 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/types.go @@ -0,0 +1,272 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package typesinternal provides helpful operators for dealing with +// go/types: +// +// - operators for querying typed syntax trees (e.g. [Imports], [IsFunctionNamed]); +// - functions for converting types to strings or syntax (e.g. [TypeExpr], FileQualifier]); +// - helpers for working with the [go/types] API (e.g. [NewTypesInfo]); +// - access to internal go/types APIs that are not yet +// exported (e.g. [SetUsesCgo], [ErrorCodeStartEnd], [VarKind]); and +// - common algorithms related to types (e.g. [TooNewStdSymbols]). +// +// See also: +// - [honnef.co/go/tools/internal/xtools-internal/astutil], for operations on untyped syntax; +// - [honnef.co/go/tools/internal/xtools-internal/analysisinernal], for helpers for analyzers; +// - [honnef.co/go/tools/internal/xtools-internal/refactor], for operators to compute text edits. +package typesinternal + +import ( + "go/ast" + "go/token" + "go/types" + "iter" + "reflect" + + "golang.org/x/tools/go/ast/inspector" +) + +func SetUsesCgo(conf *types.Config) bool { + v := reflect.ValueOf(conf).Elem() + + f := v.FieldByName("go115UsesCgo") + if !f.IsValid() { + f = v.FieldByName("UsesCgo") + if !f.IsValid() { + return false + } + } + + *(*bool)(f.Addr().UnsafePointer()) = true + + return true +} + +// ErrorCodeStartEnd extracts additional information from types.Error values +// generated by Go version 1.16 and later: the error code, start position, and +// end position. If all positions are valid, start <= err.Pos <= end. +// +// If the data could not be read, the final result parameter will be false. +// +// TODO(adonovan): eliminate start/end when proposal #71803 is accepted. +func ErrorCodeStartEnd(err types.Error) (code ErrorCode, start, end token.Pos, ok bool) { + var data [3]int + // By coincidence all of these fields are ints, which simplifies things. + v := reflect.ValueOf(err) + for i, name := range []string{"go116code", "go116start", "go116end"} { + f := v.FieldByName(name) + if !f.IsValid() { + return 0, 0, 0, false + } + data[i] = int(f.Int()) + } + return ErrorCode(data[0]), token.Pos(data[1]), token.Pos(data[2]), true +} + +// NameRelativeTo returns a types.Qualifier that qualifies members of +// all packages other than pkg, using only the package name. +// (By contrast, [types.RelativeTo] uses the complete package path, +// which is often excessive.) +// +// If pkg is nil, it is equivalent to [*types.Package.Name]. +// +// TODO(adonovan): all uses of this with TypeString should be +// eliminated when https://go.dev/issues/75604 is resolved. +func NameRelativeTo(pkg *types.Package) types.Qualifier { + return func(other *types.Package) string { + if pkg != nil && pkg == other { + return "" // same package; unqualified + } + return other.Name() + } +} + +// TypeNameFor returns the type name symbol for the specified type, if +// it is a [*types.Alias], [*types.Named], [*types.TypeParam], or a +// [*types.Basic] representing a type. +// +// For all other types, and for Basic types representing a builtin, +// constant, or nil, it returns nil. Be careful not to convert the +// resulting nil pointer to a [types.Object]! +// +// If t is the type of a constant, it may be an "untyped" type, which +// has no TypeName. To access the name of such types (e.g. "untyped +// int"), use [types.Basic.Name]. +func TypeNameFor(t types.Type) *types.TypeName { + switch t := t.(type) { + case *types.Alias: + return t.Obj() + case *types.Named: + return t.Obj() + case *types.TypeParam: + return t.Obj() + case *types.Basic: + // See issues #71886 and #66890 for some history. + if tname, ok := types.Universe.Lookup(t.Name()).(*types.TypeName); ok { + return tname + } + } + return nil +} + +// A NamedOrAlias is a [types.Type] that is named (as +// defined by the spec) and capable of bearing type parameters: it +// abstracts aliases ([types.Alias]) and defined types +// ([types.Named]). +// +// Every type declared by an explicit "type" declaration is a +// NamedOrAlias. (Built-in type symbols may additionally +// have type [types.Basic], which is not a NamedOrAlias, +// though the spec regards them as "named"; see [TypeNameFor].) +// +// NamedOrAlias cannot expose the Origin method, because +// [types.Alias.Origin] and [types.Named.Origin] have different +// (covariant) result types; use [Origin] instead. +type NamedOrAlias interface { + types.Type + Obj() *types.TypeName + TypeArgs() *types.TypeList + TypeParams() *types.TypeParamList + SetTypeParams(tparams []*types.TypeParam) +} + +var ( + _ NamedOrAlias = (*types.Alias)(nil) + _ NamedOrAlias = (*types.Named)(nil) +) + +// Origin returns the generic type of the Named or Alias type t if it +// is instantiated, otherwise it returns t. +func Origin(t NamedOrAlias) NamedOrAlias { + switch t := t.(type) { + case *types.Alias: + return t.Origin() + case *types.Named: + return t.Origin() + } + return t +} + +// IsPackageLevel reports whether obj is a package-level symbol. +func IsPackageLevel(obj types.Object) bool { + return obj.Pkg() != nil && obj.Parent() == obj.Pkg().Scope() +} + +// NewTypesInfo returns a *types.Info with all maps populated. +func NewTypesInfo() *types.Info { + return &types.Info{ + Types: map[ast.Expr]types.TypeAndValue{}, + Instances: map[*ast.Ident]types.Instance{}, + Defs: map[*ast.Ident]types.Object{}, + Uses: map[*ast.Ident]types.Object{}, + Implicits: map[ast.Node]types.Object{}, + Selections: map[*ast.SelectorExpr]*types.Selection{}, + Scopes: map[ast.Node]*types.Scope{}, + FileVersions: map[*ast.File]string{}, + } +} + +// EnclosingScope returns the innermost block logically enclosing the cursor. +func EnclosingScope(info *types.Info, cur inspector.Cursor) *types.Scope { + for cur := range cur.Enclosing() { + n := cur.Node() + // A function's Scope is associated with its FuncType. + 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") +} + +// Imports reports whether path is imported by pkg. +func Imports(pkg *types.Package, path string) bool { + for _, imp := range pkg.Imports() { + if imp.Path() == path { + return true + } + } + return false +} + +// ObjectKind returns a description of the object's kind. +// +// from objectKind in go/types +func ObjectKind(obj types.Object) string { + switch obj := obj.(type) { + case *types.PkgName: + return "package name" + case *types.Const: + return "constant" + case *types.TypeName: + if obj.IsAlias() { + return "type alias" + } else if _, ok := obj.Type().(*types.TypeParam); ok { + return "type parameter" + } else { + return "defined type" + } + case *types.Var: + switch obj.Kind() { + case PackageVar: + return "package-level variable" + case LocalVar: + return "local variable" + case RecvVar: + return "receiver" + case ParamVar: + return "parameter" + case ResultVar: + return "result variable" + case FieldVar: + return "struct field" + } + case *types.Func: + if obj.Signature().Recv() != nil { + return "method" + } else { + return "function" + } + case *types.Label: + return "label" + case *types.Builtin: + return "built-in function" + case *types.Nil: + return "untyped nil" + } + return "unknown symbol" +} + +// ImplicitFieldSelections returns the sequence of implicit embedded fields +// traversed by the given selection. It skips the final leaf field or method. +// The boolean component indicates whether the traversal traversed a pointer. +func ImplicitFieldSelections(seln types.Selection) iter.Seq2[*types.Var, bool] { + return func(yield func(*types.Var, bool) bool) { + var ( + t = seln.Recv() + indices = seln.Index() + ) + for _, idx := range indices[:len(indices)-1] { + ptr, isPtr := t.Underlying().(*types.Pointer) + if isPtr { + t = ptr.Elem() + } + structType, ok := t.Underlying().(*types.Struct) + if !ok { + break + } + field := structType.Field(idx) + if !yield(field, isPtr) { + break + } + t = field.Type() + } + } +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind.go new file mode 100644 index 000000000..26499cdd2 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind.go @@ -0,0 +1,23 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.25 + +package typesinternal + +import "go/types" + +type VarKind = types.VarKind + +const ( + PackageVar = types.PackageVar + LocalVar = types.LocalVar + RecvVar = types.RecvVar + ParamVar = types.ParamVar + ResultVar = types.ResultVar + FieldVar = types.FieldVar +) + +func GetVarKind(v *types.Var) VarKind { return v.Kind() } +func SetVarKind(v *types.Var, kind VarKind) { v.SetKind(kind) } diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind_go124.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind_go124.go new file mode 100644 index 000000000..17b1804b4 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind_go124.go @@ -0,0 +1,39 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.25 + +package typesinternal + +import "go/types" + +type VarKind uint8 + +const ( + _ VarKind = iota // (not meaningful) + PackageVar // a package-level variable + LocalVar // a local variable + RecvVar // a method receiver variable + ParamVar // a function parameter variable + ResultVar // a function result variable + FieldVar // a struct field +) + +func (kind VarKind) String() string { + return [...]string{ + 0: "VarKind(0)", + PackageVar: "PackageVar", + LocalVar: "LocalVar", + RecvVar: "RecvVar", + ParamVar: "ParamVar", + ResultVar: "ResultVar", + FieldVar: "FieldVar", + }[kind] +} + +// GetVarKind returns an invalid VarKind. +func GetVarKind(v *types.Var) VarKind { return 0 } + +// SetVarKind has no effect. +func SetVarKind(v *types.Var, kind VarKind) {} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/zerovalue.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/zerovalue.go new file mode 100644 index 000000000..d612a7102 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/zerovalue.go @@ -0,0 +1,381 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "strings" +) + +// ZeroString returns the string representation of the zero value for any type t. +// The boolean result indicates whether the type is or contains an invalid type +// or a non-basic (constraint) interface type. +// +// Even for invalid input types, ZeroString may return a partially correct +// string representation. The caller should use the returned isValid boolean +// to determine the validity of the expression. +// +// When assigning to a wider type (such as 'any'), it's the caller's +// responsibility to handle any necessary type conversions. +// +// This string can be used on the right-hand side of an assignment where the +// left-hand side has that explicit type. +// References to named types are qualified by an appropriate (optional) +// qualifier function. +// Exception: This does not apply to tuples. Their string representation is +// informational only and cannot be used in an assignment. +// +// See [ZeroExpr] for a variant that returns an [ast.Expr]. +func ZeroString(t types.Type, qual types.Qualifier) (_ string, isValid bool) { + switch t := t.(type) { + case *types.Basic: + switch { + case t.Info()&types.IsBoolean != 0: + return "false", true + case t.Info()&types.IsNumeric != 0: + return "0", true + case t.Info()&types.IsString != 0: + return `""`, true + case t.Kind() == types.UnsafePointer: + fallthrough + case t.Kind() == types.UntypedNil: + return "nil", true + case t.Kind() == types.Invalid: + return "invalid", false + default: + panic(fmt.Sprintf("ZeroString for unexpected type %v", t)) + } + + case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature: + return "nil", true + + case *types.Interface: + if !t.IsMethodSet() { + return "invalid", false + } + return "nil", true + + case *types.Named: + switch under := t.Underlying().(type) { + case *types.Struct, *types.Array: + return types.TypeString(t, qual) + "{}", true + default: + return ZeroString(under, qual) + } + + case *types.Alias: + switch t.Underlying().(type) { + case *types.Struct, *types.Array: + return types.TypeString(t, qual) + "{}", true + default: + // A type parameter can have alias but alias type's underlying type + // can never be a type parameter. + // Use types.Unalias to preserve the info of type parameter instead + // of call Underlying() going right through and get the underlying + // type of the type parameter which is always an interface. + return ZeroString(types.Unalias(t), qual) + } + + case *types.Array, *types.Struct: + return types.TypeString(t, qual) + "{}", true + + case *types.TypeParam: + // Assumes func new is not shadowed. + return "*new(" + types.TypeString(t, qual) + ")", true + + case *types.Tuple: + // Tuples are not normal values. + // We are currently format as "(t[0], ..., t[n])". Could be something else. + isValid := true + components := make([]string, t.Len()) + for i := 0; i < t.Len(); i++ { + comp, ok := ZeroString(t.At(i).Type(), qual) + + components[i] = comp + isValid = isValid && ok + } + return "(" + strings.Join(components, ", ") + ")", isValid + + case *types.Union: + // Variables of these types cannot be created, so it makes + // no sense to ask for their zero value. + panic(fmt.Sprintf("invalid type for a variable: %v", t)) + + default: + panic(t) // unreachable. + } +} + +// ZeroExpr returns the ast.Expr representation of the zero value for any type t. +// The boolean result indicates whether the type is or contains an invalid type +// or a non-basic (constraint) interface type. +// +// Even for invalid input types, ZeroExpr may return a partially correct ast.Expr +// representation. The caller should use the returned isValid boolean to determine +// the validity of the expression. +// +// This function is designed for types suitable for variables and should not be +// used with Tuple or Union types.References to named types are qualified by an +// appropriate (optional) qualifier function. +// +// See [ZeroString] for a variant that returns a string. +func ZeroExpr(t types.Type, qual types.Qualifier) (_ ast.Expr, isValid bool) { + switch t := t.(type) { + case *types.Basic: + switch { + case t.Info()&types.IsBoolean != 0: + return &ast.Ident{Name: "false"}, true + case t.Info()&types.IsNumeric != 0: + return &ast.BasicLit{Kind: token.INT, Value: "0"}, true + case t.Info()&types.IsString != 0: + return &ast.BasicLit{Kind: token.STRING, Value: `""`}, true + case t.Kind() == types.UnsafePointer: + fallthrough + case t.Kind() == types.UntypedNil: + return ast.NewIdent("nil"), true + case t.Kind() == types.Invalid: + return &ast.BasicLit{Kind: token.STRING, Value: `"invalid"`}, false + default: + panic(fmt.Sprintf("ZeroExpr for unexpected type %v", t)) + } + + case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature: + return ast.NewIdent("nil"), true + + case *types.Interface: + if !t.IsMethodSet() { + return &ast.BasicLit{Kind: token.STRING, Value: `"invalid"`}, false + } + return ast.NewIdent("nil"), true + + case *types.Named: + switch under := t.Underlying().(type) { + case *types.Struct, *types.Array: + return &ast.CompositeLit{ + Type: TypeExpr(t, qual), + }, true + default: + return ZeroExpr(under, qual) + } + + case *types.Alias: + switch t.Underlying().(type) { + case *types.Struct, *types.Array: + return &ast.CompositeLit{ + Type: TypeExpr(t, qual), + }, true + default: + return ZeroExpr(types.Unalias(t), qual) + } + + case *types.Array, *types.Struct: + return &ast.CompositeLit{ + Type: TypeExpr(t, qual), + }, true + + case *types.TypeParam: + return &ast.StarExpr{ // *new(T) + X: &ast.CallExpr{ + // Assumes func new is not shadowed. + Fun: ast.NewIdent("new"), + Args: []ast.Expr{ + ast.NewIdent(t.Obj().Name()), + }, + }, + }, true + + case *types.Tuple: + // Unlike ZeroString, there is no ast.Expr can express tuple by + // "(t[0], ..., t[n])". + panic(fmt.Sprintf("invalid type for a variable: %v", t)) + + case *types.Union: + // Variables of these types cannot be created, so it makes + // no sense to ask for their zero value. + panic(fmt.Sprintf("invalid type for a variable: %v", t)) + + default: + panic(t) // unreachable. + } +} + +// TypeExpr returns syntax for the specified type. References to named types +// are qualified by an appropriate (optional) qualifier function. +// It may panic for types such as Tuple or Union. +// +// See also https://go.dev/issues/75604, which will provide a robust +// Type-to-valid-Go-syntax formatter. +func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr { + switch t := t.(type) { + case *types.Basic: + switch t.Kind() { + case types.UnsafePointer: + return &ast.SelectorExpr{X: ast.NewIdent(qual(types.NewPackage("unsafe", "unsafe"))), Sel: ast.NewIdent("Pointer")} + default: + return ast.NewIdent(t.Name()) + } + + case *types.Pointer: + return &ast.UnaryExpr{ + Op: token.MUL, + X: TypeExpr(t.Elem(), qual), + } + + case *types.Array: + return &ast.ArrayType{ + Len: &ast.BasicLit{ + Kind: token.INT, + Value: fmt.Sprintf("%d", t.Len()), + }, + Elt: TypeExpr(t.Elem(), qual), + } + + case *types.Slice: + return &ast.ArrayType{ + Elt: TypeExpr(t.Elem(), qual), + } + + case *types.Map: + return &ast.MapType{ + Key: TypeExpr(t.Key(), qual), + Value: TypeExpr(t.Elem(), qual), + } + + case *types.Chan: + dir := ast.ChanDir(t.Dir()) + if t.Dir() == types.SendRecv { + dir = ast.SEND | ast.RECV + } + return &ast.ChanType{ + Dir: dir, + Value: TypeExpr(t.Elem(), qual), + } + + case *types.Signature: + var params []*ast.Field + for v := range t.Params().Variables() { + params = append(params, &ast.Field{ + Type: TypeExpr(v.Type(), qual), + Names: []*ast.Ident{ + { + Name: v.Name(), + }, + }, + }) + } + if t.Variadic() { + last := params[len(params)-1] + last.Type = &ast.Ellipsis{Elt: last.Type.(*ast.ArrayType).Elt} + } + var returns []*ast.Field + for v := range t.Results().Variables() { + returns = append(returns, &ast.Field{ + Type: TypeExpr(v.Type(), qual), + }) + } + return &ast.FuncType{ + Params: &ast.FieldList{ + List: params, + }, + Results: &ast.FieldList{ + List: returns, + }, + } + + case *types.TypeParam: + pkgName := qual(t.Obj().Pkg()) + if pkgName == "" || t.Obj().Pkg() == nil { + return ast.NewIdent(t.Obj().Name()) + } + return &ast.SelectorExpr{ + X: ast.NewIdent(pkgName), + Sel: ast.NewIdent(t.Obj().Name()), + } + + // types.TypeParam also implements interface NamedOrAlias. To differentiate, + // case TypeParam need to be present before case NamedOrAlias. + // TODO(hxjiang): remove this comment once TypeArgs() is added to interface + // NamedOrAlias. + case NamedOrAlias: + var expr ast.Expr = ast.NewIdent(t.Obj().Name()) + if pkgName := qual(t.Obj().Pkg()); pkgName != "." && pkgName != "" { + expr = &ast.SelectorExpr{ + X: ast.NewIdent(pkgName), + Sel: expr.(*ast.Ident), + } + } + + // TODO(hxjiang): call t.TypeArgs after adding method TypeArgs() to + // typesinternal.NamedOrAlias. + if hasTypeArgs, ok := t.(interface{ TypeArgs() *types.TypeList }); ok { + if typeArgs := hasTypeArgs.TypeArgs(); typeArgs != nil && typeArgs.Len() > 0 { + var indices []ast.Expr + for t0 := range typeArgs.Types() { + indices = append(indices, TypeExpr(t0, qual)) + } + expr = &ast.IndexListExpr{ + X: expr, + Indices: indices, + } + } + } + + return expr + + case *types.Struct: + return ast.NewIdent(t.String()) + + case *types.Interface: + return ast.NewIdent(t.String()) + + case *types.Union: + if t.Len() == 0 { + panic("Union type should have at least one term") + } + // Same as go/ast, the return expression will put last term in the + // Y field at topmost level of BinaryExpr. + // For union of type "float32 | float64 | int64", the structure looks + // similar to: + // { + // X: { + // X: float32, + // Op: | + // Y: float64, + // } + // Op: |, + // Y: int64, + // } + var union ast.Expr + for i := range t.Len() { + term := t.Term(i) + termExpr := TypeExpr(term.Type(), qual) + if term.Tilde() { + termExpr = &ast.UnaryExpr{ + Op: token.TILDE, + X: termExpr, + } + } + if i == 0 { + union = termExpr + } else { + union = &ast.BinaryExpr{ + X: union, + Op: token.OR, + Y: termExpr, + } + } + } + return union + + case *types.Tuple: + panic("invalid input type types.Tuple") + + default: + panic("unreachable") + } +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/features.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/features.go new file mode 100644 index 000000000..360a5b552 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/features.go @@ -0,0 +1,49 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package versions + +// This file contains predicates for working with file versions to +// decide when a tool should consider a language feature enabled. + +// named constants, to avoid misspelling +const ( + Go1_17 = "go1.17" + Go1_18 = "go1.18" + Go1_19 = "go1.19" + Go1_20 = "go1.20" + Go1_21 = "go1.21" + Go1_22 = "go1.22" + Go1_23 = "go1.23" + Go1_24 = "go1.24" + Go1_25 = "go1.25" + Go1_26 = "go1.26" + Go1_27 = "go1.27" +) + +// Future is an invalid unknown Go version sometime in the future. +// Do not use directly with Compare. +const Future = "" + +// AtLeast reports whether the file version v comes after a Go release. +// +// Use this predicate to enable a behavior once a certain Go release +// has happened (and stays enabled in the future). +func AtLeast(v, release string) bool { + if v == Future { + return true // an unknown future version is always after y. + } + return Compare(Lang(v), Lang(release)) >= 0 +} + +// Before reports whether the file version v is strictly before a Go release. +// +// Use this predicate to disable a behavior once a certain Go release +// has happened (and stays enabled in the future). +func Before(v, release string) bool { + if v == Future { + return false // an unknown future version happens after y. + } + return Compare(Lang(v), Lang(release)) < 0 +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/gover.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/gover.go new file mode 100644 index 000000000..bbabcd22e --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/gover.go @@ -0,0 +1,172 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This is a fork of internal/gover for use by x/tools until +// go1.21 and earlier are no longer supported by x/tools. + +package versions + +import "strings" + +// A gover is a parsed Go gover: major[.Minor[.Patch]][kind[pre]] +// The numbers are the original decimal strings to avoid integer overflows +// and since there is very little actual math. (Probably overflow doesn't matter in practice, +// but at the time this code was written, there was an existing test that used +// go1.99999999999, which does not fit in an int on 32-bit platforms. +// The "big decimal" representation avoids the problem entirely.) +type gover struct { + major string // decimal + minor string // decimal or "" + patch string // decimal or "" + kind string // "", "alpha", "beta", "rc" + pre string // decimal or "" +} + +// compare returns -1, 0, or +1 depending on whether +// x < y, x == y, or x > y, interpreted as toolchain versions. +// The versions x and y must not begin with a "go" prefix: just "1.21" not "go1.21". +// Malformed versions compare less than well-formed versions and equal to each other. +// The language version "1.21" compares less than the release candidate and eventual releases "1.21rc1" and "1.21.0". +func compare(x, y string) int { + vx := parse(x) + vy := parse(y) + + if c := cmpInt(vx.major, vy.major); c != 0 { + return c + } + if c := cmpInt(vx.minor, vy.minor); c != 0 { + return c + } + if c := cmpInt(vx.patch, vy.patch); c != 0 { + return c + } + if c := strings.Compare(vx.kind, vy.kind); c != 0 { // "" < alpha < beta < rc + return c + } + if c := cmpInt(vx.pre, vy.pre); c != 0 { + return c + } + return 0 +} + +// lang returns the Go language version. For example, lang("1.2.3") == "1.2". +func lang(x string) string { + v := parse(x) + if v.minor == "" || v.major == "1" && v.minor == "0" { + return v.major + } + return v.major + "." + v.minor +} + +// isValid reports whether the version x is valid. +func isValid(x string) bool { + return parse(x) != gover{} +} + +// parse parses the Go version string x into a version. +// It returns the zero version if x is malformed. +func parse(x string) gover { + var v gover + + // Parse major version. + var ok bool + v.major, x, ok = cutInt(x) + if !ok { + return gover{} + } + if x == "" { + // Interpret "1" as "1.0.0". + v.minor = "0" + v.patch = "0" + return v + } + + // Parse . before minor version. + if x[0] != '.' { + return gover{} + } + + // Parse minor version. + v.minor, x, ok = cutInt(x[1:]) + if !ok { + return gover{} + } + if x == "" { + // Patch missing is same as "0" for older versions. + // Starting in Go 1.21, patch missing is different from explicit .0. + if cmpInt(v.minor, "21") < 0 { + v.patch = "0" + } + return v + } + + // Parse patch if present. + if x[0] == '.' { + v.patch, x, ok = cutInt(x[1:]) + if !ok || x != "" { + // Note that we are disallowing prereleases (alpha, beta, rc) for patch releases here (x != ""). + // Allowing them would be a bit confusing because we already have: + // 1.21 < 1.21rc1 + // But a prerelease of a patch would have the opposite effect: + // 1.21.3rc1 < 1.21.3 + // We've never needed them before, so let's not start now. + return gover{} + } + return v + } + + // Parse prerelease. + i := 0 + for i < len(x) && (x[i] < '0' || '9' < x[i]) { + if x[i] < 'a' || 'z' < x[i] { + return gover{} + } + i++ + } + if i == 0 { + return gover{} + } + v.kind, x = x[:i], x[i:] + if x == "" { + return v + } + v.pre, x, ok = cutInt(x) + if !ok || x != "" { + return gover{} + } + + return v +} + +// cutInt scans the leading decimal number at the start of x to an integer +// and returns that value and the rest of the string. +func cutInt(x string) (n, rest string, ok bool) { + i := 0 + for i < len(x) && '0' <= x[i] && x[i] <= '9' { + i++ + } + if i == 0 || x[0] == '0' && i != 1 { // no digits or unnecessary leading zero + return "", "", false + } + return x[:i], x[i:], true +} + +// cmpInt returns cmp.Compare(x, y) interpreting x and y as decimal numbers. +// (Copied from golang.org/x/mod/semver's compareInt.) +func cmpInt(x, y string) int { + if x == y { + return 0 + } + if len(x) < len(y) { + return -1 + } + if len(x) > len(y) { + return +1 + } + if x < y { + return -1 + } else { + return +1 + } +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/types.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/types.go new file mode 100644 index 000000000..0fc10ce4e --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/types.go @@ -0,0 +1,33 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package versions + +import ( + "go/ast" + "go/types" +) + +// FileVersion returns a file's Go version. +// The reported version is an unknown Future version if a +// version cannot be determined. +func FileVersion(info *types.Info, file *ast.File) string { + // In tools built with Go >= 1.22, the Go version of a file + // follow a cascades of sources: + // 1) types.Info.FileVersion, which follows the cascade: + // 1.a) file version (ast.File.GoVersion), + // 1.b) the package version (types.Config.GoVersion), or + // 2) is some unknown Future version. + // + // File versions require a valid package version to be provided to types + // in Config.GoVersion. Config.GoVersion is either from the package's module + // or the toolchain (go run). This value should be provided by go/packages + // or unitchecker.Config.GoVersion. + if v := info.FileVersions[file]; IsValid(v) { + return v + } + // Note: we could instead return runtime.Version() [if valid]. + // This would act as a max version on what a tool can support. + return Future +} diff --git a/tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/versions.go b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/versions.go new file mode 100644 index 000000000..8d1f7453d --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/internal/xtools-internal/versions/versions.go @@ -0,0 +1,57 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package versions + +import ( + "strings" +) + +// Note: If we use build tags to use go/versions when go >=1.22, +// we run into go.dev/issue/53737. Under some operations users would see an +// import of "go/versions" even if they would not compile the file. +// For example, during `go get -u ./...` (go.dev/issue/64490) we do not try to include +// For this reason, this library just a clone of go/versions for the moment. + +// Lang returns the Go language version for version x. +// If x is not a valid version, Lang returns the empty string. +// For example: +// +// Lang("go1.21rc2") = "go1.21" +// Lang("go1.21.2") = "go1.21" +// Lang("go1.21") = "go1.21" +// Lang("go1") = "go1" +// Lang("bad") = "" +// Lang("1.21") = "" +func Lang(x string) string { + v := lang(stripGo(x)) + if v == "" { + return "" + } + return x[:2+len(v)] // "go"+v without allocation +} + +// Compare returns -1, 0, or +1 depending on whether +// x < y, x == y, or x > y, interpreted as Go versions. +// The versions x and y must begin with a "go" prefix: "go1.21" not "1.21". +// Invalid versions, including the empty string, compare less than +// valid versions and equal to each other. +// The language version "go1.21" compares less than the +// release candidate and eventual releases "go1.21rc1" and "go1.21.0". +// Custom toolchain suffixes are ignored during comparison: +// "go1.21.0" and "go1.21.0-bigcorp" are equal. +func Compare(x, y string) int { return compare(stripGo(x), stripGo(y)) } + +// IsValid reports whether the version x is valid. +func IsValid(x string) bool { return isValid(stripGo(x)) } + +// stripGo converts from a "go1.21" version to a "1.21" version. +// If v does not start with "go", stripGo returns the empty string (a known invalid version). +func stripGo(v string) string { + v, _, _ = strings.Cut(v, "-") // strip -bigcorp suffix. + if len(v) < 2 || v[:2] != "go" { + return "" + } + return v[2:] +} diff --git a/tools/vendor/honnef.co/go/tools/knowledge/arg.go b/tools/vendor/honnef.co/go/tools/knowledge/arg.go index 4fab2eba1..c477ec4f3 100644 --- a/tools/vendor/honnef.co/go/tools/knowledge/arg.go +++ b/tools/vendor/honnef.co/go/tools/knowledge/arg.go @@ -28,6 +28,7 @@ var Args = map[string]int{ "fmt.Sprintf.a[0]": 1, "fmt.Sprintf.format": 0, "json.Marshal.v": 0, + "json.MarshalIndent.v": 0, "json.Unmarshal.v": 1, "len.v": 0, "make.size[0]": 1, @@ -60,6 +61,7 @@ var Args = map[string]int{ "time.Parse.layout": 0, "time.Sleep.d": 0, "xml.Marshal.v": 0, + "xml.MarshalIndent.v": 0, "xml.Unmarshal.v": 1, } diff --git a/tools/vendor/honnef.co/go/tools/knowledge/deprecated.go b/tools/vendor/honnef.co/go/tools/knowledge/deprecated.go index 89ff8d9e0..256f37568 100644 --- a/tools/vendor/honnef.co/go/tools/knowledge/deprecated.go +++ b/tools/vendor/honnef.co/go/tools/knowledge/deprecated.go @@ -246,6 +246,11 @@ var StdlibDeprecations = map[string]Deprecation{ "crypto/rsa.EncryptPKCS1v15": {"go1.26", DeprecatedNeverUse}, "(net/http/httputil.ReverseProxy).Director": {"go1.26", "go1.20"}, + + // Note that the available alternative, testing/cryptotest.SetGlobalRandom + // is only an alternative for testing, not all possible uses of the + // deprecated field. + "(crypto/tls.Config).Rand": {"go1.27", "go1.26"}, } // Last imported from GOROOT/api/go1.26.txt at d3ddc4854429185e6e06ca1f7628bb790404abb5. diff --git a/tools/vendor/honnef.co/go/tools/quickfix/qf1002/qf1002.go b/tools/vendor/honnef.co/go/tools/quickfix/qf1002/qf1002.go index 84b847232..dd5221671 100644 --- a/tools/vendor/honnef.co/go/tools/quickfix/qf1002/qf1002.go +++ b/tools/vendor/honnef.co/go/tools/quickfix/qf1002/qf1002.go @@ -121,7 +121,7 @@ func run(pass *analysis.Pass) (any, error) { } func findSwitchPairs(pass *analysis.Pass, expr ast.Expr, pairs *[]*ast.BinaryExpr) bool { - binexpr, ok := astutil.Unparen(expr).(*ast.BinaryExpr) + binexpr, ok := ast.Unparen(expr).(*ast.BinaryExpr) if !ok { return false } diff --git a/tools/vendor/honnef.co/go/tools/quickfix/qf1003/qf1003.go b/tools/vendor/honnef.co/go/tools/quickfix/qf1003/qf1003.go index f8be70d4c..7958795a3 100644 --- a/tools/vendor/honnef.co/go/tools/quickfix/qf1003/qf1003.go +++ b/tools/vendor/honnef.co/go/tools/quickfix/qf1003/qf1003.go @@ -180,7 +180,7 @@ nodeLoop: } func findSwitchPairs(pass *analysis.Pass, expr ast.Expr, pairs *[]*ast.BinaryExpr) bool { - binexpr, ok := astutil.Unparen(expr).(*ast.BinaryExpr) + binexpr, ok := ast.Unparen(expr).(*ast.BinaryExpr) if !ok { return false } diff --git a/tools/vendor/honnef.co/go/tools/quickfix/qf1004/qf1004.go b/tools/vendor/honnef.co/go/tools/quickfix/qf1004/qf1004.go index e18c4d53d..bde8c6710 100644 --- a/tools/vendor/honnef.co/go/tools/quickfix/qf1004/qf1004.go +++ b/tools/vendor/honnef.co/go/tools/quickfix/qf1004/qf1004.go @@ -8,8 +8,8 @@ import ( "honnef.co/go/tools/analysis/edit" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" - typeindexanalyzer "honnef.co/go/tools/internal/analysisinternal/typeindex" - "honnef.co/go/tools/internal/typesinternal/typeindex" + typeindexanalyzer "honnef.co/go/tools/internal/xtools-internal/analysis/typeindex" + "honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex" "golang.org/x/tools/go/analysis" ) diff --git a/tools/vendor/honnef.co/go/tools/simple/s1005/s1005.go b/tools/vendor/honnef.co/go/tools/simple/s1005/s1005.go index 466f38a4f..bd3dcbddd 100644 --- a/tools/vendor/honnef.co/go/tools/simple/s1005/s1005.go +++ b/tools/vendor/honnef.co/go/tools/simple/s1005/s1005.go @@ -27,11 +27,9 @@ var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Text: `In many cases, assigning to the blank identifier is unnecessary.`, Before: ` for _ = range s {} -x, _ = someMap[key] _ = <-ch`, After: ` for range s{} -x = someMap[key] <-ch`, Since: "2017.1", MergeIf: lint.MergeIfAny, @@ -45,9 +43,7 @@ var ( (AssignStmt [_ (Ident "_")] _ - (Or - (IndexExpr _ _) - (UnaryExpr "<-" _))) `) + (UnaryExpr "<-" _)) `) checkUnnecessaryBlankQ2 = pattern.MustParse(` (AssignStmt (Ident "_") _ recv@(UnaryExpr "<-" _))`) @@ -55,6 +51,10 @@ var ( func run(pass *analysis.Pass) (any, error) { fn1 := func(node ast.Node) { + // We don't check for 'x, _ = m[k]', which might be used to indicate + // that one knows that there might be no entry and that one doesn't + // care. + if _, ok := code.Match(pass, checkUnnecessaryBlankQ1, node); ok { r := *node.(*ast.AssignStmt) r.Lhs = r.Lhs[0:1] diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/analysis.go b/tools/vendor/honnef.co/go/tools/staticcheck/analysis.go index fe8642aba..81ab81c99 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/analysis.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/analysis.go @@ -82,7 +82,6 @@ import ( "honnef.co/go/tools/staticcheck/sa5008" "honnef.co/go/tools/staticcheck/sa5009" "honnef.co/go/tools/staticcheck/sa5010" - "honnef.co/go/tools/staticcheck/sa5011" "honnef.co/go/tools/staticcheck/sa5012" "honnef.co/go/tools/staticcheck/sa6000" "honnef.co/go/tools/staticcheck/sa6001" @@ -99,6 +98,7 @@ import ( "honnef.co/go/tools/staticcheck/sa9007" "honnef.co/go/tools/staticcheck/sa9008" "honnef.co/go/tools/staticcheck/sa9009" + "honnef.co/go/tools/staticcheck/sa9010" ) var Analyzers = []*lint.Analyzer{ @@ -180,7 +180,6 @@ var Analyzers = []*lint.Analyzer{ sa5008.SCAnalyzer, sa5009.SCAnalyzer, sa5010.SCAnalyzer, - sa5011.SCAnalyzer, sa5012.SCAnalyzer, sa6000.SCAnalyzer, sa6001.SCAnalyzer, @@ -197,4 +196,5 @@ var Analyzers = []*lint.Analyzer{ sa9007.SCAnalyzer, sa9008.SCAnalyzer, sa9009.SCAnalyzer, + sa9010.SCAnalyzer, } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa1002/sa1002.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa1002/sa1002.go index ddc207675..4f7159af0 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa1002/sa1002.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa1002/sa1002.go @@ -20,7 +20,10 @@ var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Run: callcheck.Analyzer(rules), }, Doc: &lint.RawDocumentation{ - Title: `Invalid format in \'time.Parse\'`, + Title: `Invalid format in \'time.Parse\'`, + Text: `\'time.Parse\' requires a layout string that uses Go's reference time: +\"Mon Jan 2 15:04:05 MST 2006\". The layout must represent this date and time +exactly. See https://pkg.go.dev/time#pkg-constants for layout examples.`, Since: "2017.1", Severity: lint.SeverityError, MergeIf: lint.MergeIfAny, diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa1012/sa1012.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa1012/sa1012.go index 16bd07984..b04fd8c88 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa1012/sa1012.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa1012/sa1012.go @@ -21,7 +21,10 @@ var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Requires: code.RequiredAnalyzers, }, Doc: &lint.RawDocumentation{ - Title: `A nil \'context.Context\' is being passed to a function, consider using \'context.TODO\' instead`, + Title: `A nil \'context.Context\' is being passed to a function, consider using \'context.TODO\' instead`, + Text: `The context package prohibits the use of a \'nil\' context. +If no parent context is available, a new context should be used, +e.g. \'context.TODO\' or \'context.Background\'.`, Since: "2017.1", Severity: lint.SeverityWarning, MergeIf: lint.MergeIfAny, diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa1014/sa1014.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa1014/sa1014.go index 90d676af2..528aa3399 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa1014/sa1014.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa1014/sa1014.go @@ -18,7 +18,11 @@ var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Run: callcheck.Analyzer(checkUnmarshalPointerRules), }, Doc: &lint.RawDocumentation{ - Title: `Non-pointer value passed to \'Unmarshal\' or \'Decode\'`, + Title: `Non-pointer value passed to \'Unmarshal\' or \'Decode\'`, + Text: `Functions such as \'encoding/json.Unmarshal\' and +\'(*encoding/json.Decoder).Decode\' require a pointer to the value that should +be populated. Passing a non-pointer value results in the function returning an +error at runtime, as it cannot modify the target value.`, Since: "2017.1", Severity: lint.SeverityError, MergeIf: lint.MergeIfAny, diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa1019/sa1019.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa1019/sa1019.go index 97040e8f5..0fb9b307f 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa1019/sa1019.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa1019/sa1019.go @@ -99,50 +99,33 @@ func run(pass *analysis.Pass) (any, error) { case knowledge.DeprecatedNeverUse: report.Report(pass, node, fmt.Sprintf("%s has been deprecated since %s because it shouldn't be used: %s", - report.Render(pass, node), formatGoVersion(std.DeprecatedSince), depr.Msg)) + deprecatedObjName, formatGoVersion(std.DeprecatedSince), depr.Msg)) case std.DeprecatedSince, knowledge.DeprecatedUseNoLonger: report.Report(pass, node, fmt.Sprintf("%s has been deprecated since %s: %s", - report.Render(pass, node), formatGoVersion(std.DeprecatedSince), depr.Msg)) + deprecatedObjName, formatGoVersion(std.DeprecatedSince), depr.Msg)) default: report.Report(pass, node, fmt.Sprintf("%s has been deprecated since %s and an alternative has been available since %s: %s", - report.Render(pass, node), formatGoVersion(std.DeprecatedSince), formatGoVersion(std.AlternativeAvailableSince), depr.Msg)) + deprecatedObjName, formatGoVersion(std.DeprecatedSince), formatGoVersion(std.AlternativeAvailableSince), depr.Msg)) } } else { - report.Report(pass, node, fmt.Sprintf("%s is deprecated: %s", report.Render(pass, node), depr.Msg)) + report.Report(pass, node, fmt.Sprintf("%s is deprecated: %s", deprecatedObjName, depr.Msg)) } } var tfn types.Object stack := 0 - fn := func(node ast.Node, push bool) bool { - if !push { - stack-- - return false - } - stack++ - if stack == 1 { - tfn = nil - } - if fn, ok := node.(*ast.FuncDecl); ok { - tfn = pass.TypesInfo.ObjectOf(fn.Name) - } - - // FIXME(dh): this misses dot-imported objects - sel, ok := node.(*ast.SelectorExpr) - if !ok { - return true - } + checkIdentObj := func(sel *ast.SelectorExpr) bool { obj := pass.TypesInfo.ObjectOf(sel.Sel) + if obj_, ok := obj.(*types.Func); ok { obj = obj_.Origin() } if obj.Pkg() == nil { return true } - if obj.Pkg() == pass.Pkg { // A package is allowed to use its own deprecated objects return true @@ -165,8 +148,59 @@ func run(pass *analysis.Pass) (any, error) { return true } + node := ast.Node(sel) + if pass.TypesInfo.Types[sel.X].IsType() { + node = sel.Sel + } if depr, ok := deprs.Objects[obj]; ok { - handleDeprecation(depr, sel, code.SelectorName(pass, sel), obj.Pkg().Path(), tfn) + handleDeprecation(depr, node, code.SelectorName(pass, sel), obj.Pkg().Path(), tfn) + } + return true + } + + fn := func(node ast.Node, push bool) bool { + if !push { + stack-- + return false + } + stack++ + if stack == 1 { + tfn = nil + } + if fn, ok := node.(*ast.FuncDecl); ok { + tfn = pass.TypesInfo.ObjectOf(fn.Name) + } + + switch v := node.(type) { + // FIXME(dh): this misses dot-imported objects + case *ast.SelectorExpr: + return checkIdentObj(v) + + case *ast.CompositeLit: + litType := pass.TypesInfo.Types[v.Type] + if !litType.IsType() { + // This is probably unreachable. + return true + } + if _, ok := litType.Type.Underlying().(*types.Struct); !ok { + // We don't want to look at expressions in map initializers, for + // example. + return true + } + for _, elt := range v.Elts { + kv, ok := elt.(*ast.KeyValueExpr) + if !ok { + return true + } + key, ok := kv.Key.(*ast.Ident) + if !ok { + // This is probably unreachable, since we're looking at keys + // in a struct initializer. + return true + } + sel := &ast.SelectorExpr{X: v.Type, Sel: key} + checkIdentObj(sel) + } } return true } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa1020/sa1020.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa1020/sa1020.go index aa1aeae65..9d6256a27 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa1020/sa1020.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa1020/sa1020.go @@ -20,7 +20,12 @@ var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Run: callcheck.Analyzer(checkListenAddressRules), }, Doc: &lint.RawDocumentation{ - Title: `Using an invalid host:port pair with a \'net.Listen\'-related function`, + Title: `Using an invalid host:port pair with a \'net.Listen\'-related function`, + Text: `Functions such as \'net.Listen\', \'net.ListenTCP\', and similar, +expect a valid network address in the form of host:port. The host, the port, +or both, can be omitted, e.g. \'localhost:8080\', \':8080\' or \':\' are valid +host:port pairs. +See https://pkg.go.dev/net#Listen for the full documentation.`, Since: "2017.1", Severity: lint.SeverityError, MergeIf: lint.MergeIfAny, diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa1025/sa1025.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa1025/sa1025.go index 9b7b6afc6..2ac361ae4 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa1025/sa1025.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa1025/sa1025.go @@ -43,7 +43,7 @@ func run(pass *analysis.Pass) (any, error) { if refs == nil { continue } - for _, ref := range irutil.FilterDebug(*refs) { + for _, ref := range *refs { ifstmt, ok := ref.(*ir.If) if !ok { continue diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa1026/sa1026.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa1026/sa1026.go index 0c685c3a4..0aa99fc63 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa1026/sa1026.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa1026/sa1026.go @@ -31,7 +31,9 @@ var Analyzer = SCAnalyzer.Analyzer var rules = map[string]callcheck.Check{ "encoding/json.Marshal": checkJSON, + "encoding/json.MarshalIndent": checkJSON, "encoding/xml.Marshal": checkXML, + "encoding/xml.MarshalIndent": checkXML, "(*encoding/json.Encoder).Encode": checkJSON, "(*encoding/xml.Encoder).Encode": checkXML, } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa1030/sa1030.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa1030/sa1030.go index 1a274ce1a..05069387f 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa1030/sa1030.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa1030/sa1030.go @@ -128,7 +128,7 @@ func validateFloatFormat(arg *callcheck.Argument) { switch val { case 'b', 'e', 'E', 'f', 'g', 'G', 'x', 'X': default: - arg.Invalid(fmt.Sprintf("'fmt' argument is invalid: unknown format %q", val)) + arg.Invalid(fmt.Sprintf("'fmt' argument is invalid: unknown format %q", rune(val))) } } } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa2000/sa2000.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa2000/sa2000.go index 008742921..721f269d5 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa2000/sa2000.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa2000/sa2000.go @@ -19,7 +19,10 @@ var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Requires: code.RequiredAnalyzers, }, Doc: &lint.RawDocumentation{ - Title: `\'sync.WaitGroup.Add\' called inside the goroutine, leading to a race condition`, + Title: `\'(*sync.WaitGroup).Add\' called inside the goroutine, leading to a race condition`, + Text: `\'(*sync.WaitGroup).Add\' must be called before starting the goroutine +it is meant to wait for. Calling \'Add\' inside the goroutine creates a race +condition between the call to \'Add\' and the call to \'Wait\'.`, Since: "2017.1", Severity: lint.SeverityWarning, MergeIf: lint.MergeIfAny, diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa2001/sa2001.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa2001/sa2001.go index e248250fe..5e3f83e1e 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa2001/sa2001.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa2001/sa2001.go @@ -7,7 +7,6 @@ import ( "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" - "honnef.co/go/tools/go/ast/astutil" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" @@ -67,7 +66,7 @@ func run(pass *analysis.Pass) (any, error) { if !ok { return nil, "", false } - call, ok := astutil.Unparen(expr.X).(*ast.CallExpr) + call, ok := ast.Unparen(expr.X).(*ast.CallExpr) if !ok { return nil, "", false } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa2003/sa2003.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa2003/sa2003.go index a7163493a..c6fb7f0f9 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa2003/sa2003.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa2003/sa2003.go @@ -20,7 +20,19 @@ var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Requires: []*analysis.Analyzer{buildir.Analyzer}, }, Doc: &lint.RawDocumentation{ - Title: `Deferred \'Lock\' right after locking, likely meant to defer \'Unlock\' instead`, + Title: `Deferred \'Lock\' right after locking, likely meant to defer \'Unlock\' instead`, + Text: `Deferring a call to \'Lock\' immediately after locking is almost always +a typo. For example: + + mu.Lock() + defer mu.Lock() + +While this does not strictly guarantee a deadlock depending on how the +surrounding code is structured, it is highly likely to be a mistake. +The intended code was likely this: + + mu.Lock() + defer mu.Unlock()`, Since: "2017.1", Severity: lint.SeverityWarning, MergeIf: lint.MergeIfAny, @@ -32,7 +44,7 @@ var Analyzer = SCAnalyzer.Analyzer func run(pass *analysis.Pass) (any, error) { for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { for _, block := range fn.Blocks { - instrs := irutil.FilterDebug(block.Instrs) + instrs := block.Instrs if len(instrs) < 2 { continue } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa4003/sa4003.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa4003/sa4003.go index a399c338f..eb36c4182 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa4003/sa4003.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa4003/sa4003.go @@ -49,12 +49,10 @@ func run(pass *analysis.Pass) (any, error) { fn := func(node ast.Node) { expr := node.(*ast.BinaryExpr) tx := pass.TypesInfo.TypeOf(expr.X) - basic, ok := tx.Underlying().(*types.Basic) - if !ok { - return - } + tset := typeutil.NewTypeSet(tx) - // We only check for the math constants and integer literals, not for all constant expressions. This is to avoid + // We only check for the math constants and integer literals, not for + // all constant expressions. This is to avoid // false positives when constant values differ under different build tags. var ( maxMathConst string @@ -63,6 +61,54 @@ func run(pass *analysis.Pass) (any, error) { minLiteral constant.Value ) + allUnsigned := tset.All(func(t *types.Term) bool { + if basic, ok := t.Type().Underlying().(*types.Basic); ok { + return basic.Info()&types.IsUnsigned != 0 + } + return false + }) + + if allUnsigned { + isZeroLiteral := func(expr ast.Expr) bool { + return code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0)) + } + if (expr.Op == token.LSS && isZeroLiteral(expr.Y)) || + (expr.Op == token.GTR && isZeroLiteral(expr.X)) { + report.Report( + pass, + expr, + fmt.Sprintf("no value of type %s is less than 0", tx), + report.FilterGenerated(), + ) + } + if expr.Op == token.GEQ && isZeroLiteral(expr.Y) || + expr.Op == token.LEQ && isZeroLiteral(expr.X) { + report.Report( + pass, + expr, + fmt.Sprintf("every value of type %s is >= 0", tx), + report.FilterGenerated(), + ) + } + } + + core := tset.CoreType() + if core == nil { + // All remaining checks are only relevant when the type set + // contains a single underlying type. + // + // If we had a 'var x uint8 | uint16', + // then the type checker wouldn't permit a check such as + // 'if x <= math.MaxUint16', because the constant cannot be converted to all + // types in the type set. + return + } + + basic, ok := core.(*types.Basic) + if !ok { + return + } + switch basic.Kind() { case types.Uint8: maxMathConst = "math.MaxUint8" @@ -81,7 +127,8 @@ func run(pass *analysis.Pass) (any, error) { minLiteral = constant.MakeUint64(0) maxLiteral = constant.MakeUint64(math.MaxUint64) case types.Uint: - // TODO(dh): we could chose 32 bit vs 64 bit depending on the file's build tags + // TODO(dh): we could chose 32 bit vs 64 bit depending on the + // file's build tags maxMathConst = "math.MaxUint64" minLiteral = constant.MakeUint64(0) maxLiteral = constant.MakeUint64(math.MaxUint64) @@ -107,7 +154,8 @@ func run(pass *analysis.Pass) (any, error) { minLiteral = constant.MakeInt64(math.MinInt64) maxLiteral = constant.MakeInt64(math.MaxInt64) case types.Int: - // TODO(dh): we could chose 32 bit vs 64 bit depending on the file's build tags + // TODO(dh): we could chose 32 bit vs 64 bit depending on the + // file's build tags minMathConst = "math.MinInt64" maxMathConst = "math.MaxInt64" minLiteral = constant.MakeInt64(math.MinInt64) @@ -120,37 +168,53 @@ func run(pass *analysis.Pass) (any, error) { } return code.IsIntegerLiteral(pass, expr, c) } - isZeroLiteral := func(expr ast.Expr) bool { - return code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0)) - } - if (expr.Op == token.GTR || expr.Op == token.GEQ) && (isobj(expr.Y, maxMathConst) || isLiteral(expr.Y, maxLiteral)) || - (expr.Op == token.LSS || expr.Op == token.LEQ) && (isobj(expr.X, maxMathConst) || isLiteral(expr.X, maxLiteral)) { - report.Report(pass, expr, fmt.Sprintf("no value of type %s is greater than %s", basic, maxMathConst), report.FilterGenerated()) + x, y, op := expr.X, expr.Y, expr.Op + switch op { + case token.GEQ, token.GTR: + case token.LEQ: + x, y = y, x + op = token.GEQ + case token.LSS: + x, y = y, x + op = token.GTR + default: + return } - if expr.Op == token.LEQ && (isobj(expr.Y, maxMathConst) || isLiteral(expr.Y, maxLiteral)) || - expr.Op == token.GEQ && (isobj(expr.X, maxMathConst) || isLiteral(expr.X, maxLiteral)) { - report.Report(pass, expr, fmt.Sprintf("every value of type %s is <= %s", basic, maxMathConst), report.FilterGenerated()) + if isobj(y, maxMathConst) || isLiteral(y, maxLiteral) { + report.Report( + pass, + expr, + fmt.Sprintf("no value of type %s is greater than %s", tx, maxMathConst), + report.FilterGenerated(), + ) + } + if op == token.GEQ && (isobj(x, maxMathConst) || isLiteral(x, maxLiteral)) { + report.Report( + pass, + expr, + fmt.Sprintf("every value of type %s is <= %s", tx, maxMathConst), + report.FilterGenerated(), + ) } - if (basic.Info() & types.IsUnsigned) != 0 { - if (expr.Op == token.LSS && isZeroLiteral(expr.Y)) || - (expr.Op == token.GTR && isZeroLiteral(expr.X)) { - report.Report(pass, expr, fmt.Sprintf("no value of type %s is less than 0", basic), report.FilterGenerated()) - } - if expr.Op == token.GEQ && isZeroLiteral(expr.Y) || - expr.Op == token.LEQ && isZeroLiteral(expr.X) { - report.Report(pass, expr, fmt.Sprintf("every value of type %s is >= 0", basic), report.FilterGenerated()) - } - } else { - if (expr.Op == token.LSS || expr.Op == token.LEQ) && (isobj(expr.Y, minMathConst) || isLiteral(expr.Y, minLiteral)) || - (expr.Op == token.GTR || expr.Op == token.GEQ) && (isobj(expr.X, minMathConst) || isLiteral(expr.X, minLiteral)) { - report.Report(pass, expr, fmt.Sprintf("no value of type %s is less than %s", basic, minMathConst), report.FilterGenerated()) + if !allUnsigned { + if isobj(x, minMathConst) || isLiteral(x, minLiteral) { + report.Report( + pass, + expr, + fmt.Sprintf("no value of type %s is less than %s", tx, minMathConst), + report.FilterGenerated(), + ) } - if expr.Op == token.GEQ && (isobj(expr.Y, minMathConst) || isLiteral(expr.Y, minLiteral)) || - expr.Op == token.LEQ && (isobj(expr.X, minMathConst) || isLiteral(expr.X, minLiteral)) { - report.Report(pass, expr, fmt.Sprintf("every value of type %s is >= %s", basic, minMathConst), report.FilterGenerated()) + if op == token.GEQ && (isobj(y, minMathConst) || isLiteral(y, minLiteral)) { + report.Report( + pass, + expr, + fmt.Sprintf("every value of type %s is >= %s", tx, minMathConst), + report.FilterGenerated(), + ) } } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa4005/sa4005.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa4005/sa4005.go index f900ed21d..c46a038ec 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa4005/sa4005.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa4005/sa4005.go @@ -50,7 +50,7 @@ fnLoop: } recv := fn.Params[0] - refs := irutil.FilterDebug(*recv.Referrers()) + refs := *recv.Referrers() if len(refs) != 1 { continue } @@ -74,8 +74,6 @@ fnLoop: writes[ref.Field] = append(writes[ref.Field], refref) case *ir.Load: reads[ref.Field] = append(reads[ref.Field], refref) - case *ir.DebugRef: - continue default: // this should be safe… if the field address // escapes, then alloc.Heap will be true. @@ -98,8 +96,6 @@ fnLoop: for i := 0; i < recv.Type().Underlying().(*types.Struct).NumFields(); i++ { reads[i] = append(reads[i], ref) } - case *ir.DebugRef: - continue default: continue fnLoop } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa4006/sa4006.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa4006/sa4006.go index dac27b752..04865002a 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa4006/sa4006.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa4006/sa4006.go @@ -3,6 +3,7 @@ package sa4006 import ( "fmt" "go/ast" + "go/token" "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/facts/generated" @@ -74,15 +75,6 @@ func run(pass *analysis.Pass) (any, error) { } for _, ref := range *refs { switch ref := ref.(type) { - case *ir.DebugRef: - case *ir.Sigma: - if seen == nil { - seen = map[ir.Value]struct{}{} - } - seen[v] = struct{}{} - if hasUse(ref, seen) { - return true - } case *ir.Phi: if seen == nil { seen = map[ir.Value]struct{}{} @@ -99,6 +91,22 @@ func run(pass *analysis.Pass) (any, error) { } ast.Inspect(node, func(node ast.Node) bool { + inc, ok := node.(*ast.IncDecStmt) + if ok { + val, _ := fn.ValueForExpr(inc.X) + if val == nil { + return true + } + if _, ok := val.(*ir.Const); ok { + // a zero-valued constant, for example in 'foo := []string(nil)' + return true + } + if !hasUse(val, nil) { + report.Report(pass, inc, fmt.Sprintf("this value of %s is never used", inc.X)) + } + return true + } + assign, ok := node.(*ast.AssignStmt) if !ok { return true @@ -137,7 +145,12 @@ func run(pass *analysis.Pass) (any, error) { } val, _ := fn.ValueForExpr(rhs) if val == nil { - continue + if assign.Tok != token.ASSIGN { // +=, *=, etc. + val, _ = fn.ValueForExpr(lhs) + } + if val == nil { + continue + } } if _, ok := val.(*ir.Const); ok { diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa4008/sa4008.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa4008/sa4008.go index 41c56e355..f6c89eeea 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa4008/sa4008.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa4008/sa4008.go @@ -82,23 +82,9 @@ func run(pass *analysis.Pass) (any, error) { if v == nil || isAddr { return true } - switch v := v.(type) { + switch v.(type) { case *ir.Phi: - ops := v.Operands(nil) - if len(ops) != 2 { - return true - } - _, ok := (*ops[0]).(*ir.Const) - if !ok { - return true - } - sigma, ok := (*ops[1]).(*ir.Sigma) - if !ok { - return true - } - if sigma.X != v { - return true - } + return true case *ir.Load: return true } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa4009/sa4009.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa4009/sa4009.go index 8ae8dd27e..7c94d5e37 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa4009/sa4009.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa4009/sa4009.go @@ -7,7 +7,6 @@ import ( "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "honnef.co/go/tools/go/ir" - "honnef.co/go/tools/go/ir/irutil" "honnef.co/go/tools/internal/passes/buildir" "golang.org/x/tools/go/analysis" @@ -65,7 +64,7 @@ func run(pass *analysis.Pass) (any, error) { if refs == nil { continue } - if len(irutil.FilterDebug(*refs)) != 0 { + if len(*refs) != 0 { continue } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa4010/sa4010.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa4010/sa4010.go index 2e11e6f26..f445aa7c1 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa4010/sa4010.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa4010/sa4010.go @@ -16,7 +16,10 @@ var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Requires: []*analysis.Analyzer{buildir.Analyzer}, }, Doc: &lint.RawDocumentation{ - Title: `The result of \'append\' will never be observed anywhere`, + Title: `The result of \'append\' will never be observed anywhere`, + Text: `Calls to \'append\' produce a new slice value. When the result of +\'append\' is assigned to a variable that is never subsequently read, the +append operation may have an unintended effect.`, Since: "2017.1", Severity: lint.SeverityWarning, MergeIf: lint.MergeIfAll, @@ -54,7 +57,6 @@ func run(pass *analysis.Pass) (any, error) { // This graph must only consist of the following instructions: // // - phi - // - sigma // - slice // - const nil // - MakeSlice @@ -62,8 +64,7 @@ func run(pass *analysis.Pass) (any, error) { // - calls to append // // If this step succeeds, we look at all referrers of the values found in the first step, recursively. - // These referrers must either be in the set of values found in the first step, - // be DebugRefs, + // These referrers must either be in the set of values found in the first step // or fulfill the same type requirements as step 1, with the exception of appends, which are forbidden. // // If both steps succeed then we know that the backing array hasn't been aliased in an observable manner. @@ -90,8 +91,6 @@ func run(pass *analysis.Pass) (any, error) { } } return true - case *ir.Sigma: - return validateArgument(v.X, seen) case *ir.Slice: return validateArgument(v.X, seen) case *ir.Const: @@ -112,28 +111,27 @@ func run(pass *analysis.Pass) (any, error) { var validateReferrers func(v ir.Value, seen map[ir.Instruction]struct{}) bool validateReferrers = func(v ir.Value, seen map[ir.Instruction]struct{}) bool { - for _, ref := range *v.Referrers() { - if _, ok := seen[ref]; ok { - continue - } - - seen[ref] = struct{}{} - switch ref.(type) { - case *ir.Phi: - case *ir.Sigma: - case *ir.Slice: - case *ir.Const: - case *ir.MakeSlice: - case *ir.Alloc: - case *ir.DebugRef: - default: - return false - } + if refs := v.Referrers(); refs != nil { + for _, ref := range *refs { + if _, ok := seen[ref]; ok { + continue + } - if ref, ok := ref.(ir.Value); ok { - if !validateReferrers(ref, seen) { + seen[ref] = struct{}{} + switch ref.(type) { + case *ir.Phi: + case *ir.Slice: + case *ir.MakeSlice: + case *ir.Alloc: + default: return false } + + if ref, ok := ref.(ir.Value); ok { + if !validateReferrers(ref, seen) { + return false + } + } } } return true @@ -157,14 +155,9 @@ func run(pass *analysis.Pass) (any, error) { continue } visited[ref] = true - if _, ok := ref.(*ir.DebugRef); ok { - continue - } switch ref := ref.(type) { case *ir.Phi: walkRefs(*ref.Referrers()) - case *ir.Sigma: - walkRefs(*ref.Referrers()) case ir.Value: if !isAppend(ref) { isUsed = true @@ -195,8 +188,9 @@ func run(pass *analysis.Pass) (any, error) { seen2 := map[ir.Instruction]struct{}{} for k := range seen { - // the only values we allow are also instructions, so this type assertion cannot fail - seen2[k.(ir.Instruction)] = struct{}{} + if k, ok := k.(ir.Instruction); ok { + seen2[k] = struct{}{} + } } seen2[ins] = struct{}{} failed := false diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa4017/sa4017.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa4017/sa4017.go index 6177d17e6..8302249f1 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa4017/sa4017.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa4017/sa4017.go @@ -9,7 +9,6 @@ import ( "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "honnef.co/go/tools/go/ir" - "honnef.co/go/tools/go/ir/irutil" "honnef.co/go/tools/go/types/typeutil" "honnef.co/go/tools/internal/passes/buildir" @@ -60,7 +59,7 @@ fnLoop: continue } refs := ins.Referrers() - if refs == nil || len(irutil.FilterDebug(*refs)) > 0 { + if refs == nil || len(*refs) > 0 { continue } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa4023/sa4023.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa4023/sa4023.go index 4ab13c835..67ca67c68 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa4023/sa4023.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa4023/sa4023.go @@ -7,7 +7,6 @@ import ( "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/facts/nilness" - "honnef.co/go/tools/analysis/facts/typedness" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "honnef.co/go/tools/go/ir" @@ -23,7 +22,7 @@ var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Analyzer: &analysis.Analyzer{ Name: "SA4023", Run: run, - Requires: []*analysis.Analyzer{buildir.Analyzer, typedness.Analysis, nilness.Analysis}, + Requires: []*analysis.Analyzer{buildir.Analyzer, nilness.Analysis}, }, Doc: &lint.RawDocumentation{ Title: `Impossible comparison of interface value with untyped nil`, @@ -102,8 +101,7 @@ func run(pass *analysis.Pass) (any, error) { // known typed nils, or typed unknown nilness are being returned. irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR) - typedness := pass.ResultOf[typedness.Analysis].(*typedness.Result) - nilness := pass.ResultOf[nilness.Analysis].(*nilness.Result) + nilnessRes := pass.ResultOf[nilness.Analysis].(*nilness.Result) for _, fn := range irpkg.SrcFuncs { for _, b := range fn.Blocks { for _, instr := range b.Instrs { @@ -111,17 +109,16 @@ func run(pass *analysis.Pass) (any, error) { if !ok || !(binop.Op == token.EQL || binop.Op == token.NEQ) { continue } - if _, ok := binop.X.Type().Underlying().(*types.Interface); !ok || typeparams.IsTypeParam(binop.X.Type()) { + if !types.IsInterface(binop.X.Type()) || typeparams.IsTypeParam(binop.X.Type()) { // TODO support swapped X and Y continue } k, ok := binop.Y.(*ir.Const) if !ok || !k.IsNil() { - // if binop.X is an interface, then binop.Y can - // only be a Const if its untyped. A typed nil - // constant would first be passed to - // MakeInterface. + // if binop.X is an interface, then binop.Y can only be a + // Const if its untyped. A typed nil constant would first + // be passed to MakeInterface. continue } @@ -159,8 +156,9 @@ func run(pass *analysis.Pass) (any, error) { terms, err := typeparams.NormalTerms(x.X.Type()) if len(terms) == 0 || err != nil { - // Type is a type parameter with no type terms (or we couldn't determine the terms). Such a type - // _can_ be nil when put in an interface value. + // Type is a type parameter with no type terms (or we + // couldn't determine the terms). Such a type _can_ be + // nil when put in an interface value. continue } @@ -168,8 +166,11 @@ func run(pass *analysis.Pass) (any, error) { report.Report(pass, binop, fmt.Sprintf("this comparison is %s true", qualifier), report.Related(x.X, "the lhs of the comparison gets its value from here and has a concrete type")) } else { - // we can't generate related information for this, so make the diagnostic itself slightly more useful - report.Report(pass, binop, fmt.Sprintf("this comparison is %s true; the lhs of the comparison has been assigned a concretely typed value", qualifier)) + // we can't generate related information for this, so + // make the diagnostic itself slightly more useful + report.Report(pass, binop, + fmt.Sprintf("this comparison is %s true; the lhs of the comparison has been assigned a concretely typed value", + qualifier)) } continue } @@ -177,11 +178,21 @@ func run(pass *analysis.Pass) (any, error) { continue } - isNil, onlyGlobal := nilness.MayReturnNil(obj, idx) - if typedness.MustReturnTyped(obj, idx) && isNil && !onlyGlobal && !code.IsInTest(pass, binop) { - // Don't flag these comparisons in tests. Tests - // may be explicitly enforcing the invariant that - // a value isn't nil. + nillity := nilnessRes.Nilness(obj, idx) + // We aim to detect interface values that are never nil (i.e., + // always typed) but may contain typed nils. This usually + // points to a bug in the producer of the value. We + // intentionally do not flag comparisons with non-nil interface + // values whose stored value is also never nil (such as the + // return value of errors.New)--those are more likely to be + // defensive code, protecting against a future untyped nil. + if nillity.Outer == nilness.NeverNil && + (nillity.Inner == nilness.MaybeNil || nillity.Inner == nilness.AlwaysNil) && + !code.IsInTest(pass, binop) && + !irutil.IsTrivial(irpkg.Pkg.Prog.FuncValue(obj)) { + // Don't flag these comparisons in tests. Tests may be + // explicitly enforcing the invariant that a value isn't + // nil. var qualifier string switch binop.Op { @@ -194,8 +205,11 @@ func run(pass *analysis.Pass) (any, error) { } report.Report(pass, binop, fmt.Sprintf("this comparison is %s true", qualifier), // TODO support swapped X and Y - report.Related(binop.X, fmt.Sprintf("the lhs of the comparison is the %s return value of this function call", report.Ordinal(idx+1))), - report.Related(obj, fmt.Sprintf("%s never returns a nil interface value", typeutil.FuncName(obj)))) + report.Related(binop.X, + fmt.Sprintf("the lhs of the comparison is the %s return value of this function call", + report.Ordinal(idx+1))), + report.Related(obj, + fmt.Sprintf("%s never returns a nil interface value", typeutil.FuncName(obj)))) } } } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa4031/sa4031.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa4031/sa4031.go index e91e5ecd1..3da5ba0b6 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa4031/sa4031.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa4031/sa4031.go @@ -95,8 +95,6 @@ func run(pass *analysis.Pass) (any, error) { values = append(values, v) } return neverNil(v.X, false) - case *ir.Sigma: - return neverNil(v.X, true) case *ir.Phi: for _, e := range v.Edges { if !neverNil(e, true) { diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa5007/sa5007.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa5007/sa5007.go index d9a1dd7e4..f0819c8a4 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa5007/sa5007.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa5007/sa5007.go @@ -47,16 +47,11 @@ func run(pass *analysis.Pass) (any, error) { } block := site.Block() - for _, b := range fn.Blocks { + for b := range fn.Exits() { if block.Dominates(b) { continue } - if len(b.Instrs) == 0 { - continue - } - if _, ok := b.Control().(*ir.Return); ok { - return - } + return } report.Report(pass, site, "infinite recursive call") }) diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa5008/jsonv2.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa5008/jsonv2.go index be5339d53..6724ae1ae 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa5008/jsonv2.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa5008/jsonv2.go @@ -141,7 +141,7 @@ func validateJSONTag(pass *analysis.Pass, field *ast.Field, tag string) { report.Report(pass, field.Tag, fmt.Sprintf("invalid appearance of unknown `case:%s` tag value", rawOpt)) } - case "inline": + case "embed", "inline": case "unknown": case "omitzero": case "omitempty": diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa5009/sa5009.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa5009/sa5009.go index 7eaa095c8..f0ab7ecc8 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa5009/sa5009.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa5009/sa5009.go @@ -252,7 +252,7 @@ func checkImpl(carg *callcheck.Argument, f ir.Value, args []ir.Value) { } } - if flags&isPointer != 0 && typeutil.IsPointerLike(T) { + if flags&isPointer != 0 && typeutil.MaybePointerLike(T) { return true } if flags&isPseudoPointer != 0 { diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa5011/sa5011.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa5011/sa5011.go deleted file mode 100644 index 7b1b13332..000000000 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa5011/sa5011.go +++ /dev/null @@ -1,221 +0,0 @@ -package sa5011 - -import ( - "go/types" - - "honnef.co/go/tools/analysis/lint" - "honnef.co/go/tools/analysis/report" - "honnef.co/go/tools/go/ir" - "honnef.co/go/tools/go/types/typeutil" - "honnef.co/go/tools/internal/passes/buildir" - - "golang.org/x/tools/go/analysis" -) - -var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ - Analyzer: &analysis.Analyzer{ - Name: "SA5011", - Run: run, - Requires: []*analysis.Analyzer{buildir.Analyzer}, - }, - Doc: &lint.RawDocumentation{ - Title: `Possible nil pointer dereference`, - - Text: `A pointer is being dereferenced unconditionally, while -also being checked against nil in another place. This suggests that -the pointer may be nil and dereferencing it may panic. This is -commonly a result of improperly ordered code or missing return -statements. Consider the following examples: - - func fn(x *int) { - fmt.Println(*x) - - // This nil check is equally important for the previous dereference - if x != nil { - foo(*x) - } - } - - func TestFoo(t *testing.T) { - x := compute() - if x == nil { - t.Errorf("nil pointer received") - } - - // t.Errorf does not abort the test, so if x is nil, the next line will panic. - foo(*x) - } - -Staticcheck tries to deduce which functions abort control flow. -For example, it is aware that a function will not continue -execution after a call to \'panic\' or \'log.Fatal\'. However, sometimes -this detection fails, in particular in the presence of -conditionals. Consider the following example: - - func Log(msg string, level int) { - fmt.Println(msg) - if level == levelFatal { - os.Exit(1) - } - } - - func Fatal(msg string) { - Log(msg, levelFatal) - } - - func fn(x *int) { - if x == nil { - Fatal("unexpected nil pointer") - } - fmt.Println(*x) - } - -Staticcheck will flag the dereference of \'x\', even though it is perfectly -safe. Staticcheck is not able to deduce that a call to -Fatal will exit the program. For the time being, the easiest -workaround is to modify the definition of Fatal like so: - - func Fatal(msg string) { - Log(msg, levelFatal) - panic("unreachable") - } - -We also hard-code functions from common logging packages such as -logrus. Please file an issue if we're missing support for a -popular package.`, - Since: "2020.1", - Severity: lint.SeverityWarning, - MergeIf: lint.MergeIfAny, - }, -}) - -var Analyzer = SCAnalyzer.Analyzer - -func run(pass *analysis.Pass) (any, error) { - // This is an extremely trivial check that doesn't try to reason - // about control flow. That is, phis and sigmas do not propagate - // any information. As such, we can flag this: - // - // _ = *x - // if x == nil { return } - // - // but we cannot flag this: - // - // if x == nil { println(x) } - // _ = *x - // - // but we can flag this, because the if's body doesn't use x: - // - // if x == nil { println("this is bad") } - // _ = *x - // - // nor many other variations of conditional uses of or assignments to x. - // - // However, even this trivial implementation finds plenty of - // real-world bugs, such as dereference before nil pointer check, - // or using t.Error instead of t.Fatal when encountering nil - // pointers. - // - // On the flip side, our naive implementation avoids false positives in branches, such as - // - // if x != nil { _ = *x } - // - // due to the same lack of propagating information through sigma - // nodes. x inside the branch will be independent of the x in the - // nil pointer check. - // - // - // We could implement a more powerful check, but then we'd be - // getting false positives instead of false negatives because - // we're incapable of deducing relationships between variables. - // For example, a function might return a pointer and an error, - // and the error being nil guarantees that the pointer is not nil. - // Depending on the surrounding code, the pointer may still end up - // being checked against nil in one place, and guarded by a check - // on the error in another, which would lead to us marking some - // loads as unsafe. - // - // Unfortunately, simply hard-coding the relationship between - // return values wouldn't eliminate all false positives, either. - // Many other more subtle relationships exist. An abridged example - // from real code: - // - // if a == nil && b == nil { return } - // c := fn(a) - // if c != "" { _ = *a } - // - // where `fn` is guaranteed to return a non-empty string if a - // isn't nil. - // - // We choose to err on the side of false negatives. - - isNilConst := func(v ir.Value) bool { - if typeutil.IsPointerLike(v.Type()) { - if k, ok := v.(*ir.Const); ok { - return k.IsNil() - } - } - return false - } - - for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { - maybeNil := map[ir.Value]ir.Instruction{} - for _, b := range fn.Blocks { - for _, instr := range b.Instrs { - // Originally we looked at all ir.BinOp, but that would lead to calls like 'assert(x != nil)' causing false positives. - // Restrict ourselves to actual if statements, as these are more likely to affect control flow in a way we can observe. - if instr, ok := instr.(*ir.If); ok { - if cond, ok := instr.Cond.(*ir.BinOp); ok { - if isNilConst(cond.X) { - maybeNil[cond.Y] = cond - } - if isNilConst(cond.Y) { - maybeNil[cond.X] = cond - } - } - } - } - } - - for _, b := range fn.Blocks { - for _, instr := range b.Instrs { - var ptr ir.Value - switch instr := instr.(type) { - case *ir.Load: - ptr = instr.X - case *ir.Store: - ptr = instr.Addr - case *ir.IndexAddr: - ptr = instr.X - if typeutil.All(ptr.Type(), func(term *types.Term) bool { - if _, ok := term.Type().Underlying().(*types.Slice); ok { - return true - } - return false - }) { - // indexing a nil slice does not cause a nil pointer panic - // - // Note: This also works around the bad lowering of range loops over slices - // (https://github.com/dominikh/go-tools/issues/1053) - continue - } - case *ir.FieldAddr: - ptr = instr.X - } - if ptr != nil { - switch ptr.(type) { - case *ir.Alloc, *ir.FieldAddr, *ir.IndexAddr: - // these cannot be nil - continue - } - if r, ok := maybeNil[ptr]; ok { - report.Report(pass, instr, "possible nil pointer dereference", - report.Related(r, "this check suggests that the pointer can be nil")) - } - } - } - } - } - - return nil, nil -} diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa5012/sa5012.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa5012/sa5012.go index 4c3911980..51dc39fc0 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa5012/sa5012.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa5012/sa5012.go @@ -12,6 +12,7 @@ import ( "honnef.co/go/tools/go/ir" "honnef.co/go/tools/go/ir/irutil" "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/iterutil" "honnef.co/go/tools/internal/passes/buildir" "golang.org/x/tools/go/analysis" @@ -140,7 +141,7 @@ func findSliceLenChecks(pass *analysis.Pass) { for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { for _, b := range fn.Blocks { // all paths go through this block - if !b.Dominates(fn.Exit) { + if !iterutil.All(fn.Returns(), b.Dominates) { continue } @@ -220,7 +221,7 @@ func findIndirectSliceLenChecks(pass *analysis.Pass) { for _, b := range fn.Blocks { // all paths go through this block - if !b.Dominates(fn.Exit) { + if !iterutil.All(fn.Returns(), b.Dominates) { continue } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa6001/sa6001.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa6001/sa6001.go index 9a1c86ab2..c164967dc 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa6001/sa6001.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa6001/sa6001.go @@ -85,25 +85,18 @@ func run(pass *analysis.Pass) (any, error) { continue } refs := ins.Referrers() - // need at least two (DebugRef) references: the - // conversion and the *ast.Ident - if refs == nil || len(*refs) < 2 { - continue - } - ident := false - // skip first reference, that's the conversion itself - for _, ref := range (*refs)[1:] { + anyUses := false + for _, ref := range *refs { switch ref := ref.(type) { - case *ir.DebugRef: - if _, ok := ref.Expr.(*ast.Ident); !ok { - // the string seems to be used somewhere - // unexpected; the default branch should - // catch this already, but be safe - continue insLoop + case *ir.MapLookup: + anyUses = true + if src, ok := ref.Source().(*ast.IndexExpr); ok { + if _, ok := src.Index.(*ast.Ident); !ok { + continue insLoop + } } else { - ident = true + continue insLoop } - case *ir.MapLookup: default: // the string is used somewhere else than a // map lookup @@ -111,9 +104,7 @@ func run(pass *analysis.Pass) (any, error) { } } - // the result of the conversion wasn't assigned to an - // identifier - if !ident { + if !anyUses { continue } report.Report(pass, ins, "m[string(key)] would be more efficient than k := string(key); m[k]") diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa6002/sa6002.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa6002/sa6002.go index 91ec73030..edfe60a32 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa6002/sa6002.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa6002/sa6002.go @@ -45,7 +45,7 @@ var rules = map[string]callcheck.Check{ arg := call.Args[knowledge.Arg("(*sync.Pool).Put.x")] typ := arg.Value.Value.Type() _, isSlice := typ.Underlying().(*types.Slice) - if !typeutil.IsPointerLike(typ) || isSlice { + if !typeutil.MaybePointerLike(typ) || isSlice { arg.Invalid("argument should be pointer-like to avoid allocations") } }, diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa6005/sa6005.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa6005/sa6005.go index 387875a79..05cfc1cd9 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa6005/sa6005.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa6005/sa6005.go @@ -1,6 +1,7 @@ package sa6005 import ( + "fmt" "go/ast" "go/token" @@ -60,16 +61,18 @@ var ( func run(pass *analysis.Pass) (any, error) { for node, m := range code.Matches(pass, checkToLowerToUpperComparisonQ) { rn := pattern.NodeToAST(checkToLowerToUpperComparisonR.Root, m.State).(ast.Expr) + method := "strings.EqualFold" if m.State["tok"].(token.Token) == token.NEQ { rn = &ast.UnaryExpr{ Op: token.NOT, X: rn, } + method = "!" + method } report.Report(pass, node, - "should use strings.EqualFold instead", - report.Fixes(edit.Fix("Replace with strings.EqualFold", edit.ReplaceWithNode(pass.Fset, node, rn)))) + fmt.Sprintf("should use %s instead", method), + report.Fixes(edit.Fix("replace with "+method, edit.ReplaceWithNode(pass.Fset, node, rn)))) } return nil, nil } diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa9005/sa9005.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa9005/sa9005.go index 94c1f5dc9..d367ecd6c 100644 --- a/tools/vendor/honnef.co/go/tools/staticcheck/sa9005/sa9005.go +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa9005/sa9005.go @@ -50,7 +50,9 @@ var rules = map[string]callcheck.Check{ // // Also, should we flag gob? "encoding/json.Marshal": check(knowledge.Arg("json.Marshal.v"), "MarshalJSON", "MarshalText"), + "encoding/json.MarshalIndent": check(knowledge.Arg("json.MarshalIndent.v"), "MarshalJSON", "MarshalText"), "encoding/xml.Marshal": check(knowledge.Arg("xml.Marshal.v"), "MarshalXML", "MarshalText"), + "encoding/xml.MarshalIndent": check(knowledge.Arg("xml.MarshalIndent.v"), "MarshalXML", "MarshalText"), "(*encoding/json.Encoder).Encode": check(knowledge.Arg("(*encoding/json.Encoder).Encode.v"), "MarshalJSON", "MarshalText"), "(*encoding/xml.Encoder).Encode": check(knowledge.Arg("(*encoding/xml.Encoder).Encode.v"), "MarshalXML", "MarshalText"), diff --git a/tools/vendor/honnef.co/go/tools/staticcheck/sa9010/sa9010.go b/tools/vendor/honnef.co/go/tools/staticcheck/sa9010/sa9010.go new file mode 100644 index 000000000..c171ba1b8 --- /dev/null +++ b/tools/vendor/honnef.co/go/tools/staticcheck/sa9010/sa9010.go @@ -0,0 +1,60 @@ +package sa9010 + +import ( + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA9010", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Returned function should be called in defer`, + Text: ` +If you have a function such as: + + func f() func() { + // Do something. + return func() { + // Do something. + } + } + +Then calling that in defer: + + defer f() + +Is almost always a mistake, since you typically want to call the returned +function: + + defer f()() +`, + Since: "2026.2", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(n ast.Node) { + def := n.(*ast.DeferStmt) + if _, ok := pass.TypesInfo.TypeOf(def.Call).Underlying().(*types.Signature); ok { + report.Report(pass, def, "deferred return function not called") + } + } + + code.Preorder(pass, fn, (*ast.DeferStmt)(nil)) + return nil, nil +} diff --git a/tools/vendor/honnef.co/go/tools/unused/implements.go b/tools/vendor/honnef.co/go/tools/unused/implements.go index 1207e4a8c..f7c379664 100644 --- a/tools/vendor/honnef.co/go/tools/unused/implements.go +++ b/tools/vendor/honnef.co/go/tools/unused/implements.go @@ -2,6 +2,8 @@ package unused import ( "go/types" + + "honnef.co/go/tools/go/types/typeutil" ) // lookupMethod returns the index of and method with matching package and name, or (-1, nil). @@ -61,7 +63,8 @@ func implements(V types.Type, T *types.Interface, msV *types.MethodSet) ([]*type // A concrete type implements T if it implements all methods of T. var sels []*types.Selection - var c methodsChecker + + mapping := map[*types.TypeParam]types.Type{} for m := range T.Methods() { sel := msV.Lookup(m.Pkg(), m.Name()) if sel == nil { @@ -73,77 +76,27 @@ func implements(V types.Type, T *types.Interface, msV *types.MethodSet) ([]*type return nil, false } - if !c.methodIsCompatible(f, m) { + if ok := typeutil.Unify(f.Type(), m.Type(), mapping); !ok { return nil, false } sels = append(sels, sel) } - return sels, true -} - -type methodsChecker struct { - typeParams map[*types.TypeParam]types.Type -} - -// Currently, this doesn't support methods like `foo(x []T)`. -func (c *methodsChecker) methodIsCompatible(implFunc *types.Func, interfaceFunc *types.Func) bool { - if types.Identical(implFunc.Type(), interfaceFunc.Type()) { - return true - } - implSig, implOk := implFunc.Type().(*types.Signature) - interfaceSig, interfaceOk := interfaceFunc.Type().(*types.Signature) - if !implOk || !interfaceOk { - // probably not reachable. handle conservatively. - return false - } - - if !c.typesAreCompatible(implSig.Params(), interfaceSig.Params()) { - return false - } - - if !c.typesAreCompatible(implSig.Results(), interfaceSig.Results()) { - return false - } - - return true -} - -func (c *methodsChecker) typesAreCompatible(implTypes, interfaceTypes *types.Tuple) bool { - if implTypes.Len() != interfaceTypes.Len() { - return false - } - for i := 0; i < implTypes.Len(); i++ { - if !c.typeIsCompatible(implTypes.At(i).Type(), interfaceTypes.At(i).Type()) { - return false + for tparam, targ := range mapping { + // This checks constraints on a best-effort basis, erring on the side + // of accepting too many types. + if !satisfiesConstraint(targ, tparam) { + return nil, false } } - return true + return sels, true } -func (c *methodsChecker) typeIsCompatible(implType, interfaceType types.Type) bool { - if types.Identical(implType, interfaceType) { - return true - } - // We only support trivial use of type parameters. This isn't fully compatible with compiler type checking yet. - tp, ok := interfaceType.(*types.TypeParam) - if !ok { - return false - } - if c.typeParams == nil { - c.typeParams = make(map[*types.TypeParam]types.Type) - } - if c.typeParams[tp] == nil { - if !satisfiesConstraint(implType, tp) { - return false - } - c.typeParams[tp] = implType +func satisfiesConstraint(t types.Type, tp *types.TypeParam) bool { + if t == nil { + // t is nil when we unify two type parameters. return true } - return types.Identical(c.typeParams[tp], implType) -} - -func satisfiesConstraint(t types.Type, tp *types.TypeParam) bool { bound := tp.Constraint().Underlying().(*types.Interface) return types.Satisfies(t, bound) } diff --git a/tools/vendor/honnef.co/go/tools/unused/unused.go b/tools/vendor/honnef.co/go/tools/unused/unused.go index 99060a35b..fd7b5c04b 100644 --- a/tools/vendor/honnef.co/go/tools/unused/unused.go +++ b/tools/vendor/honnef.co/go/tools/unused/unused.go @@ -748,6 +748,20 @@ func (g *graph) read(node ast.Node, by types.Object) { g.read(kv.Value, by) } } + if g.opts.FieldWritesAreUses && !unkeyed { + for _, elt := range node.Elts { + kv := elt.(*ast.KeyValueExpr) + fname := kv.Key.(*ast.Ident).Name + _, index, _ := types.LookupFieldOrMethod(typ, true, g.pkg, fname) + + cur := typ + for _, step := range index[:len(index)-1] { + field := cur.Field(step) + g.use(field, by) + cur = typeutil.CoreType(field.Type()).(*types.Struct) + } + } + } } else { for _, elt := range node.Elts { g.read(elt, by) @@ -1340,7 +1354,7 @@ func (g *graph) stmt(stmt ast.Stmt, by types.Object) { g.read(comm.Chan, by) g.read(comm.Value, by) case *ast.ExprStmt: - g.read(astutil.Unparen(comm.X).(*ast.UnaryExpr).X, by) + g.read(ast.Unparen(comm.X).(*ast.UnaryExpr).X, by) case *ast.AssignStmt: for _, lhs := range comm.Lhs { g.write(lhs, by) diff --git a/tools/vendor/modules.txt b/tools/vendor/modules.txt index 44ba2dca1..88545743b 100644 --- a/tools/vendor/modules.txt +++ b/tools/vendor/modules.txt @@ -19,6 +19,14 @@ dev.gaijin.team/go/exhaustruct/v4/analyzer dev.gaijin.team/go/exhaustruct/v4/internal/comment dev.gaijin.team/go/exhaustruct/v4/internal/pattern dev.gaijin.team/go/exhaustruct/v4/internal/structure +# dev.gaijin.team/go/exhaustruct/v5 v5.0.3 +## explicit; go 1.24.0 +dev.gaijin.team/go/exhaustruct/v5/analyzer +dev.gaijin.team/go/exhaustruct/v5/internal/astutil +dev.gaijin.team/go/exhaustruct/v5/internal/cache +dev.gaijin.team/go/exhaustruct/v5/internal/directive +dev.gaijin.team/go/exhaustruct/v5/internal/pattern +dev.gaijin.team/go/exhaustruct/v5/internal/structure # dev.gaijin.team/go/golib v0.8.1 ## explicit; go 1.24 dev.gaijin.team/go/golib/e @@ -121,8 +129,8 @@ github.com/blizzy78/varnamelen # github.com/bombsimon/wsl/v4 v4.7.0 ## explicit; go 1.23 github.com/bombsimon/wsl/v4 -# github.com/bombsimon/wsl/v5 v5.8.0 -## explicit; go 1.25.0 +# github.com/bombsimon/wsl/v5 v5.9.0 +## explicit; go 1.26.0 github.com/bombsimon/wsl/v5 # github.com/breml/bidichk v0.3.3 ## explicit; go 1.23.0 @@ -321,11 +329,12 @@ github.com/golangci/dupl/syntax/golang # github.com/golangci/go-printf-func-name v0.1.1 ## explicit; go 1.23.0 github.com/golangci/go-printf-func-name/pkg/analyzer -# github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d -## explicit; go 1.22.0 -github.com/golangci/gofmt/gofmt -# github.com/golangci/golangci-lint/v2 v2.12.2 -## explicit; go 1.25.0 +# github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792 +## explicit; go 1.26.0 +github.com/golangci/gofmt +github.com/golangci/gofmt/internal +# github.com/golangci/golangci-lint/v2 v2.13.1 +## explicit; go 1.26.0 github.com/golangci/golangci-lint/v2/cmd/golangci-lint github.com/golangci/golangci-lint/v2/internal/cache github.com/golangci/golangci-lint/v2/internal/errorutil @@ -647,11 +656,12 @@ github.com/mattn/go-isatty # github.com/mattn/go-runewidth v0.0.28 ## explicit; go 1.23 github.com/mattn/go-runewidth -# github.com/mgechev/revive v1.15.0 -## explicit; go 1.25.0 +# github.com/mgechev/revive v1.16.0 +## explicit; go 1.26.0 github.com/mgechev/revive/config github.com/mgechev/revive/formatter github.com/mgechev/revive/internal/astutils +github.com/mgechev/revive/internal/config github.com/mgechev/revive/internal/ifelse github.com/mgechev/revive/internal/rule github.com/mgechev/revive/internal/syncset @@ -682,8 +692,8 @@ github.com/nishanths/exhaustive # github.com/nishanths/predeclared v0.2.2 ## explicit; go 1.14 github.com/nishanths/predeclared/passes/predeclared -# github.com/nunnatsa/ginkgolinter v0.23.1 -## explicit; go 1.25.0 +# github.com/nunnatsa/ginkgolinter v0.24.0 +## explicit; go 1.26.0 github.com/nunnatsa/ginkgolinter github.com/nunnatsa/ginkgolinter/config github.com/nunnatsa/ginkgolinter/internal/expression @@ -799,8 +809,8 @@ github.com/sashamelentyev/interfacebloat/pkg/analyzer ## explicit; go 1.23.0 github.com/sashamelentyev/usestdlibvars/pkg/analyzer github.com/sashamelentyev/usestdlibvars/pkg/analyzer/internal/mapping -# github.com/securego/gosec/v2 v2.28.0 -## explicit; go 1.25.8 +# github.com/securego/gosec/v2 v2.29.0 +## explicit; go 1.25.0 github.com/securego/gosec/v2 github.com/securego/gosec/v2/analyzers github.com/securego/gosec/v2/cwe @@ -852,7 +862,7 @@ github.com/stbenjam/no-sprintf-host-port/pkg/analyzer # github.com/stretchr/objx v0.5.3 ## explicit; go 1.20 github.com/stretchr/objx -# github.com/stretchr/testify v1.12.0 +# github.com/stretchr/testify v1.12.1 ## explicit; go 1.17 github.com/stretchr/testify/assert github.com/stretchr/testify/assert/yaml @@ -900,6 +910,7 @@ github.com/uudashr/iface/internal/directive github.com/uudashr/iface/opaque github.com/uudashr/iface/unexported github.com/uudashr/iface/unused +github.com/uudashr/iface/unusedmethod # github.com/xen0n/gosmopolitan v1.3.0 ## explicit; go 1.23.0 github.com/xen0n/gosmopolitan @@ -949,8 +960,8 @@ go.uber.org/zap/zapcore # go.yaml.in/yaml/v3 v3.0.5 ## explicit; go 1.16 go.yaml.in/yaml/v3 -# golang.org/x/exp/typeparams v0.0.0-20260813180055-c1d0aacb2297 -## explicit; go 1.25.0 +# golang.org/x/exp/typeparams v0.0.0-20260824195058-e88cd73687aa +## explicit; go 1.26.0 golang.org/x/exp/typeparams # golang.org/x/mod v0.40.0 ## explicit; go 1.25.0 @@ -1124,10 +1135,12 @@ google.golang.org/protobuf/types/known/timestamppb # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 -# honnef.co/go/tools v0.7.0 -## explicit; go 1.25.0 +# honnef.co/go/tools v0.8.1 +## explicit; go 1.26.0 honnef.co/go/tools/analysis/callcheck honnef.co/go/tools/analysis/code +honnef.co/go/tools/analysis/dfa +honnef.co/go/tools/analysis/dfa/dense honnef.co/go/tools/analysis/edit honnef.co/go/tools/analysis/facts/deprecated honnef.co/go/tools/analysis/facts/directives @@ -1135,7 +1148,6 @@ honnef.co/go/tools/analysis/facts/generated honnef.co/go/tools/analysis/facts/nilness honnef.co/go/tools/analysis/facts/purity honnef.co/go/tools/analysis/facts/tokenfile -honnef.co/go/tools/analysis/facts/typedness honnef.co/go/tools/analysis/lint honnef.co/go/tools/analysis/report honnef.co/go/tools/config @@ -1143,10 +1155,18 @@ honnef.co/go/tools/go/ast/astutil honnef.co/go/tools/go/ir honnef.co/go/tools/go/ir/irutil honnef.co/go/tools/go/types/typeutil -honnef.co/go/tools/internal/analysisinternal/typeindex +honnef.co/go/tools/internal/iterutil honnef.co/go/tools/internal/passes/buildir honnef.co/go/tools/internal/sharedcheck -honnef.co/go/tools/internal/typesinternal/typeindex +honnef.co/go/tools/internal/xtools-internal/aliases +honnef.co/go/tools/internal/xtools-internal/analysis/typeindex +honnef.co/go/tools/internal/xtools-internal/astutil +honnef.co/go/tools/internal/xtools-internal/graph +honnef.co/go/tools/internal/xtools-internal/moreiters +honnef.co/go/tools/internal/xtools-internal/typeparams +honnef.co/go/tools/internal/xtools-internal/typesinternal +honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex +honnef.co/go/tools/internal/xtools-internal/versions honnef.co/go/tools/knowledge honnef.co/go/tools/pattern honnef.co/go/tools/printf @@ -1281,7 +1301,6 @@ honnef.co/go/tools/staticcheck/sa5007 honnef.co/go/tools/staticcheck/sa5008 honnef.co/go/tools/staticcheck/sa5009 honnef.co/go/tools/staticcheck/sa5010 -honnef.co/go/tools/staticcheck/sa5011 honnef.co/go/tools/staticcheck/sa5012 honnef.co/go/tools/staticcheck/sa6000 honnef.co/go/tools/staticcheck/sa6001 @@ -1298,6 +1317,7 @@ honnef.co/go/tools/staticcheck/sa9006 honnef.co/go/tools/staticcheck/sa9007 honnef.co/go/tools/staticcheck/sa9008 honnef.co/go/tools/staticcheck/sa9009 +honnef.co/go/tools/staticcheck/sa9010 honnef.co/go/tools/stylecheck honnef.co/go/tools/stylecheck/st1000 honnef.co/go/tools/stylecheck/st1001 @@ -1325,9 +1345,9 @@ mvdan.cc/gofumpt/internal/govendor/go/doc/comment mvdan.cc/gofumpt/internal/govendor/go/format mvdan.cc/gofumpt/internal/govendor/go/printer mvdan.cc/gofumpt/internal/version -# mvdan.cc/unparam v0.0.0-20260818115549-3f964bcb5673 -## explicit; go 1.25.0 +# mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 +## explicit; go 1.26.0 mvdan.cc/unparam/check -# github.com/charmbracelet/bubbles/v2 => charm.land/bubbles/v2 v2.1.1 +# github.com/charmbracelet/bubbles/v2 => charm.land/bubbles/v2 v2.2.1 # github.com/charmbracelet/bubbletea/v2 => charm.land/bubbletea/v2 v2.0.8 # github.com/charmbracelet/lipgloss/v2 => charm.land/lipgloss/v2 v2.0.6 diff --git a/tools/vendor/mvdan.cc/unparam/check/check.go b/tools/vendor/mvdan.cc/unparam/check/check.go index 6bd6fdb6e..8015b78ae 100644 --- a/tools/vendor/mvdan.cc/unparam/check/check.go +++ b/tools/vendor/mvdan.cc/unparam/check/check.go @@ -650,8 +650,6 @@ resLoop: c.addIssue(fn, res.Pos(), "result %s is never used", name) } - fnIsGeneric := fn.TypeParams().Len() > 0 - for i, par := range fn.Params { if paramsBy != "" { continue // we can't change the params @@ -666,13 +664,9 @@ resLoop: } t := par.Type() // asking for the size of a type param would panic, as it is unknowable - if !fnIsGeneric || !containsTypeParam(t) { - if stdSizes.Sizeof(par.Type()) == 0 { - c.debug(" skip - zero size\n") - continue - } - } else { - c.debug(" examine - type parameter\n") + if !containsTypeParam(t) && stdSizes.Sizeof(t) == 0 { + c.debug(" skip - zero size\n") + continue } reason := "is unused" constStr := c.alwaysReceivedConst(callSites, par, i) @@ -686,6 +680,9 @@ resLoop: } } +// containsTypeParam reports whether computing the size of t requires knowing +// the size of a type parameter. It only follows the types that [types.Sizes] +// descends into; the rest have a fixed size. func containsTypeParam(t types.Type) bool { switch t := types.Unalias(t).(type) { case *types.TypeParam, *types.Union: @@ -700,12 +697,7 @@ func containsTypeParam(t types.Type) bool { case *types.Array: return containsTypeParam(t.Elem()) case *types.Named: - args := t.TypeArgs() - for t0 := range args.Types() { - if containsTypeParam(t0) { - return true - } - } + return containsTypeParam(t.Underlying()) } return false } diff --git a/vendor/charm.land/bubbles/v2/LICENSE b/vendor/charm.land/bubbles/v2/LICENSE new file mode 100644 index 000000000..01d14e6ae --- /dev/null +++ b/vendor/charm.land/bubbles/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2026 Charmbracelet, Inc. + +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. diff --git a/vendor/charm.land/bubbles/v2/cursor/cursor.go b/vendor/charm.land/bubbles/v2/cursor/cursor.go new file mode 100644 index 000000000..662ad18fe --- /dev/null +++ b/vendor/charm.land/bubbles/v2/cursor/cursor.go @@ -0,0 +1,241 @@ +// Package cursor provides a virtual cursor to support the textinput and +// textarea elements. +package cursor + +import ( + "context" + "sync/atomic" + "time" + + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" +) + +const defaultBlinkSpeed = time.Millisecond * 530 + +// Internal ID management. Used during animating to ensure that frame messages +// are received only by spinner components that sent them. +var lastID int64 + +func nextID() int { + return int(atomic.AddInt64(&lastID, 1)) +} + +// initialBlinkMsg initializes cursor blinking. +type initialBlinkMsg struct{} + +// BlinkMsg signals that the cursor should blink. It contains metadata that +// allows us to tell if the blink message is the one we're expecting. +type BlinkMsg struct { + id int + tag int +} + +// blinkCanceled is sent when a blink operation is canceled. +type blinkCanceled struct{} + +// blinkCtx manages cursor blinking. +type blinkCtx struct { + ctx context.Context + cancel context.CancelFunc +} + +// Mode describes the behavior of the cursor. +type Mode int + +// Available cursor modes. +const ( + CursorBlink Mode = iota + CursorStatic + CursorHide +) + +// String returns the cursor mode in a human-readable format. This method is +// provisional and for informational purposes only. +func (c Mode) String() string { + return [...]string{ + "blink", + "static", + "hidden", + }[c] +} + +// Model is the Bubble Tea model for this cursor element. +type Model struct { + // Style styles the cursor block. + Style lipgloss.Style + + // TextStyle is the style used for the cursor when it is blinking + // (hidden), i.e. displaying normal text. + TextStyle lipgloss.Style + + // BlinkSpeed is the speed at which the cursor blinks. This has no effect + // unless [CursorMode] is not set to [CursorBlink]. + BlinkSpeed time.Duration + + // IsBlinked is the state of the cursor blink. When true, the cursor is + // hidden. + IsBlinked bool + + // char is the character under the cursor + char string + + // The ID of this Model as it relates to other cursors + id int + + // focus indicates whether the containing input is focused + focus bool + + // Used to manage cursor blink + blinkCtx *blinkCtx + + // The ID of the blink message we're expecting to receive. + blinkTag int + + // mode determines the behavior of the cursor + mode Mode +} + +// New creates a new model with default settings. +func New() Model { + return Model{ + id: nextID(), + BlinkSpeed: defaultBlinkSpeed, + IsBlinked: true, + mode: CursorBlink, + + blinkCtx: &blinkCtx{ + ctx: context.Background(), + }, + } +} + +// Update updates the cursor. +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + switch msg := msg.(type) { + case initialBlinkMsg: + // We accept all initialBlinkMsgs generated by the Blink command. + + if m.mode != CursorBlink || !m.focus { + return m, nil + } + + cmd := m.Blink() + return m, cmd + + case tea.FocusMsg: + return m, m.Focus() + + case tea.BlurMsg: + m.Blur() + return m, nil + + case BlinkMsg: + // We're choosy about whether to accept blinkMsgs so that our cursor + // only exactly when it should. + + // Is this model blink-able? + if m.mode != CursorBlink || !m.focus { + return m, nil + } + + // Were we expecting this blink message? + if msg.id != m.id || msg.tag != m.blinkTag { + return m, nil + } + + var cmd tea.Cmd + if m.mode == CursorBlink { + m.IsBlinked = !m.IsBlinked + cmd = m.Blink() + } + return m, cmd + + case blinkCanceled: // no-op + return m, nil + } + return m, nil +} + +// Mode returns the model's cursor mode. For available cursor modes, see +// type Mode. +func (m Model) Mode() Mode { + return m.mode +} + +// SetMode sets the model's cursor mode. This method returns a command. +// +// For available cursor modes, see type CursorMode. +func (m *Model) SetMode(mode Mode) tea.Cmd { + // Adjust the mode value if it's value is out of range + if mode < CursorBlink || mode > CursorHide { + return nil + } + m.mode = mode + m.IsBlinked = m.mode == CursorHide || !m.focus + if mode == CursorBlink { + return Blink + } + return nil +} + +// Blink is a command used to manage cursor blinking. +func (m *Model) Blink() tea.Cmd { + if m.mode != CursorBlink { + return nil + } + + if m.blinkCtx != nil && m.blinkCtx.cancel != nil { + m.blinkCtx.cancel() + } + + ctx, cancel := context.WithTimeout(m.blinkCtx.ctx, m.BlinkSpeed) + m.blinkCtx.cancel = cancel + + m.blinkTag++ + blinkMsg := BlinkMsg{id: m.id, tag: m.blinkTag} + + return func() tea.Msg { + defer cancel() + <-ctx.Done() + if ctx.Err() == context.DeadlineExceeded { + return blinkMsg + } + return blinkCanceled{} + } +} + +// Blink is a command used to initialize cursor blinking. +func Blink() tea.Msg { + return initialBlinkMsg{} +} + +// Focus focuses the cursor to allow it to blink if desired. +func (m *Model) Focus() tea.Cmd { + m.focus = true + m.IsBlinked = m.mode == CursorHide // show the cursor unless we've explicitly hidden it + + if m.mode == CursorBlink && m.focus { + return m.Blink() + } + return nil +} + +// Blur blurs the cursor. +func (m *Model) Blur() { + m.focus = false + m.IsBlinked = true +} + +// SetChar sets the character under the cursor. +func (m *Model) SetChar(char string) { + m.char = char +} + +// View displays the cursor. +func (m Model) View() string { + if m.IsBlinked { + return m.TextStyle.Inline(true).Render(m.char) + } + return m.Style.Inline(true).Reverse(true).Render(m.char) +} diff --git a/vendor/charm.land/bubbles/v2/internal/runeutil/runeutil.go b/vendor/charm.land/bubbles/v2/internal/runeutil/runeutil.go new file mode 100644 index 000000000..3d5b2886a --- /dev/null +++ b/vendor/charm.land/bubbles/v2/internal/runeutil/runeutil.go @@ -0,0 +1,102 @@ +// Package runeutil provides utility functions for tidying up incoming runes +// from Key messages. +package runeutil + +import ( + "unicode" + "unicode/utf8" +) + +// Sanitizer is a helper for bubble widgets that want to process +// Runes from input key messages. +type Sanitizer interface { + // Sanitize removes control characters from runes in a KeyRunes + // message, and optionally replaces newline/carriage return/tabs by a + // specified character. + // + // The rune array is modified in-place if possible. In that case, the + // returned slice is the original slice shortened after the control + // characters have been removed/translated. + Sanitize(runes []rune) []rune +} + +// NewSanitizer constructs a rune sanitizer. +func NewSanitizer(opts ...Option) Sanitizer { + s := sanitizer{ + replaceNewLine: []rune("\n"), + replaceTab: []rune(" "), + } + for _, o := range opts { + s = o(s) + } + return &s +} + +// Option is the type of option that can be passed to Sanitize(). +type Option func(sanitizer) sanitizer + +// ReplaceTabs replaces tabs by the specified string. +func ReplaceTabs(tabRepl string) Option { + return func(s sanitizer) sanitizer { + s.replaceTab = []rune(tabRepl) + return s + } +} + +// ReplaceNewlines replaces newline characters by the specified string. +func ReplaceNewlines(nlRepl string) Option { + return func(s sanitizer) sanitizer { + s.replaceNewLine = []rune(nlRepl) + return s + } +} + +func (s *sanitizer) Sanitize(runes []rune) []rune { + // dstrunes are where we are storing the result. + dstrunes := runes[:0:len(runes)] + // copied indicates whether dstrunes is an alias of runes + // or a copy. We need a copy when dst moves past src. + // We use this as an optimization to avoid allocating + // a new rune slice in the common case where the output + // is smaller or equal to the input. + copied := false + + for src := range runes { + r := runes[src] + switch { + case r == utf8.RuneError: + // skip + + case r == '\r' || r == '\n': + if len(dstrunes)+len(s.replaceNewLine) > src && !copied { + dst := len(dstrunes) + dstrunes = make([]rune, dst, len(runes)+len(s.replaceNewLine)) + copy(dstrunes, runes[:dst]) + copied = true + } + dstrunes = append(dstrunes, s.replaceNewLine...) + + case r == '\t': + if len(dstrunes)+len(s.replaceTab) > src && !copied { + dst := len(dstrunes) + dstrunes = make([]rune, dst, len(runes)+len(s.replaceTab)) + copy(dstrunes, runes[:dst]) + copied = true + } + dstrunes = append(dstrunes, s.replaceTab...) + + case unicode.IsControl(r): + // Other control characters: skip. + + default: + // Keep the character. + dstrunes = append(dstrunes, runes[src]) + } + } + return dstrunes +} + +type sanitizer struct { + replaceNewLine []rune + replaceTab []rune +} diff --git a/vendor/charm.land/bubbles/v2/key/key.go b/vendor/charm.land/bubbles/v2/key/key.go new file mode 100644 index 000000000..0c79a7a2e --- /dev/null +++ b/vendor/charm.land/bubbles/v2/key/key.go @@ -0,0 +1,140 @@ +// Package key provides some types and functions for generating user-definable +// keymappings useful in Bubble Tea components. There are a few different ways +// you can define a keymapping with this package. Here's one example: +// +// type KeyMap struct { +// Up key.Binding +// Down key.Binding +// } +// +// var DefaultKeyMap = KeyMap{ +// Up: key.NewBinding( +// key.WithKeys("k", "up"), // actual keybindings +// key.WithHelp("↑/k", "move up"), // corresponding help text +// ), +// Down: key.NewBinding( +// key.WithKeys("j", "down"), +// key.WithHelp("↓/j", "move down"), +// ), +// } +// +// func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// switch msg := msg.(type) { +// case tea.KeyPressMsg: +// switch { +// case key.Matches(msg, DefaultKeyMap.Up): +// // The user pressed up +// case key.Matches(msg, DefaultKeyMap.Down): +// // The user pressed down +// } +// } +// +// // ... +// } +// +// The help information, which is not used in the example above, can be used +// to render help text for keystrokes in your views. +package key + +import "fmt" + +// Binding describes a set of keybindings and, optionally, their associated +// help text. +type Binding struct { + keys []string + help Help + disabled bool +} + +// BindingOpt is an initialization option for a keybinding. It's used as an +// argument to NewBinding. +type BindingOpt func(*Binding) + +// NewBinding returns a new keybinding from a set of BindingOpt options. +func NewBinding(opts ...BindingOpt) Binding { + b := &Binding{} + for _, opt := range opts { + opt(b) + } + return *b +} + +// WithKeys initializes a keybinding with the given keystrokes. +func WithKeys(keys ...string) BindingOpt { + return func(b *Binding) { + b.keys = keys + } +} + +// WithHelp initializes a keybinding with the given help text. +func WithHelp(key, desc string) BindingOpt { + return func(b *Binding) { + b.help = Help{Key: key, Desc: desc} + } +} + +// WithDisabled initializes a disabled keybinding. +func WithDisabled() BindingOpt { + return func(b *Binding) { + b.disabled = true + } +} + +// SetKeys sets the keys for the keybinding. +func (b *Binding) SetKeys(keys ...string) { + b.keys = keys +} + +// Keys returns the keys for the keybinding. +func (b Binding) Keys() []string { + return b.keys +} + +// SetHelp sets the help text for the keybinding. +func (b *Binding) SetHelp(key, desc string) { + b.help = Help{Key: key, Desc: desc} +} + +// Help returns the Help information for the keybinding. +func (b Binding) Help() Help { + return b.help +} + +// Enabled returns whether or not the keybinding is enabled. Disabled +// keybindings won't be activated and won't show up in help. Keybindings are +// enabled by default. +func (b Binding) Enabled() bool { + return !b.disabled && b.keys != nil +} + +// SetEnabled enables or disables the keybinding. +func (b *Binding) SetEnabled(v bool) { + b.disabled = !v +} + +// Unbind removes the keys and help from this binding, effectively nullifying +// it. This is a step beyond disabling it, since applications can enable +// or disable key bindings based on application state. +func (b *Binding) Unbind() { + b.keys = nil + b.help = Help{} +} + +// Help is help information for a given keybinding. +type Help struct { + Key string + Desc string +} + +// Matches checks if the given key matches the given bindings. +func Matches[Key fmt.Stringer](k Key, b ...Binding) bool { + keys := k.String() + for _, binding := range b { + for _, v := range binding.keys { + if keys == v && binding.Enabled() { + return true + } + } + } + return false +} diff --git a/vendor/charm.land/bubbles/v2/textinput/styles.go b/vendor/charm.land/bubbles/v2/textinput/styles.go new file mode 100644 index 000000000..3e53a4a63 --- /dev/null +++ b/vendor/charm.land/bubbles/v2/textinput/styles.go @@ -0,0 +1,96 @@ +package textinput + +import ( + "image/color" + "time" + + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" +) + +// DefaultStyles returns the default styles for focused and blurred states for +// the textarea. +func DefaultStyles(isDark bool) Styles { + lightDark := lipgloss.LightDark(isDark) + + var s Styles + s.Focused = StyleState{ + Placeholder: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), + Suggestion: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), + Prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")), + Text: lipgloss.NewStyle(), + } + s.Blurred = StyleState{ + Placeholder: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), + Suggestion: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), + Prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")), + Text: lipgloss.NewStyle().Foreground(lightDark(lipgloss.Color("245"), lipgloss.Color("7"))), + } + s.Cursor = CursorStyle{ + Color: lipgloss.Color("7"), + Shape: tea.CursorBlock, + Blink: true, + } + return s +} + +// DefaultLightStyles returns the default styles for a light background. +func DefaultLightStyles() Styles { + return DefaultStyles(false) +} + +// DefaultDarkStyles returns the default styles for a dark background. +func DefaultDarkStyles() Styles { + return DefaultStyles(true) +} + +// Styles are the styles for the textarea, separated into focused and blurred +// states. The appropriate styles will be chosen based on the focus state of +// the textarea. +type Styles struct { + Focused StyleState + Blurred StyleState + Cursor CursorStyle +} + +// StyleState that will be applied to the text area. +// +// StyleState can be applied to focused and unfocused states to change the styles +// depending on the focus state. +// +// For an introduction to styling with Lip Gloss see: +// https://github.com/charmbracelet/lipgloss +type StyleState struct { + Text lipgloss.Style + Placeholder lipgloss.Style + Suggestion lipgloss.Style + Prompt lipgloss.Style +} + +// CursorStyle is the style for real and virtual cursors. +type CursorStyle struct { + // Style styles the cursor block. + // + // For real cursors, the foreground color set here will be used as the + // cursor color. + Color color.Color + + // Shape is the cursor shape. The following shapes are available: + // + // - tea.CursorBlock + // - tea.CursorUnderline + // - tea.CursorBar + // + // This is only used for real cursors. + Shape tea.CursorShape + + // CursorBlink determines whether or not the cursor should blink. + Blink bool + + // BlinkSpeed is the speed at which the virtual cursor blinks. This has no + // effect on real cursors as well as no effect if the cursor is set not to + // [CursorBlink]. + // + // By default, the blink speed is set to about 500ms. + BlinkSpeed time.Duration +} diff --git a/vendor/charm.land/bubbles/v2/textinput/textinput.go b/vendor/charm.land/bubbles/v2/textinput/textinput.go new file mode 100644 index 000000000..363089b2d --- /dev/null +++ b/vendor/charm.land/bubbles/v2/textinput/textinput.go @@ -0,0 +1,968 @@ +// Package textinput provides a text input component for Bubble Tea +// applications. +package textinput + +import ( + "reflect" + "slices" + "strings" + "unicode" + + "charm.land/bubbles/v2/cursor" + "charm.land/bubbles/v2/internal/runeutil" + "charm.land/bubbles/v2/key" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" + "github.com/atotto/clipboard" + rw "github.com/mattn/go-runewidth" + "github.com/rivo/uniseg" +) + +// Internal messages for clipboard operations. +type ( + pasteMsg string + pasteErrMsg struct{ error } +) + +// EchoMode sets the input behavior of the text input field. +type EchoMode int + +const ( + // EchoNormal displays text as is. This is the default behavior. + EchoNormal EchoMode = iota + + // EchoPassword displays the EchoCharacter mask instead of actual + // characters. This is commonly used for password fields. + EchoPassword + + // EchoNone displays nothing as characters are entered. This is commonly + // seen for password fields on the command line. + EchoNone +) + +// ValidateFunc is a function that returns an error if the input is invalid. +type ValidateFunc func(string) error + +// KeyMap is the key bindings for different actions within the textinput. +type KeyMap struct { + CharacterForward key.Binding + CharacterBackward key.Binding + WordForward key.Binding + WordBackward key.Binding + DeleteWordBackward key.Binding + DeleteWordForward key.Binding + DeleteAfterCursor key.Binding + DeleteBeforeCursor key.Binding + DeleteCharacterBackward key.Binding + DeleteCharacterForward key.Binding + LineStart key.Binding + LineEnd key.Binding + Paste key.Binding + AcceptSuggestion key.Binding + NextSuggestion key.Binding + PrevSuggestion key.Binding +} + +// DefaultKeyMap is the default set of key bindings for navigating and acting +// upon the textinput. +func DefaultKeyMap() KeyMap { + return KeyMap{ + CharacterForward: key.NewBinding(key.WithKeys("right", "ctrl+f")), + CharacterBackward: key.NewBinding(key.WithKeys("left", "ctrl+b")), + WordForward: key.NewBinding(key.WithKeys("alt+right", "ctrl+right", "alt+f")), + WordBackward: key.NewBinding(key.WithKeys("alt+left", "ctrl+left", "alt+b")), + DeleteWordBackward: key.NewBinding(key.WithKeys("alt+backspace", "ctrl+w")), + DeleteWordForward: key.NewBinding(key.WithKeys("alt+delete", "alt+d")), + DeleteAfterCursor: key.NewBinding(key.WithKeys("ctrl+k")), + DeleteBeforeCursor: key.NewBinding(key.WithKeys("ctrl+u")), + DeleteCharacterBackward: key.NewBinding(key.WithKeys("backspace", "ctrl+h")), + DeleteCharacterForward: key.NewBinding(key.WithKeys("delete", "ctrl+d")), + LineStart: key.NewBinding(key.WithKeys("home", "ctrl+a")), + LineEnd: key.NewBinding(key.WithKeys("end", "ctrl+e")), + Paste: key.NewBinding(key.WithKeys("ctrl+v")), + AcceptSuggestion: key.NewBinding(key.WithKeys("tab")), + NextSuggestion: key.NewBinding(key.WithKeys("down", "ctrl+n")), + PrevSuggestion: key.NewBinding(key.WithKeys("up", "ctrl+p")), + } +} + +// Model is the Bubble Tea model for this text input element. +type Model struct { + Err error + + // General settings. + Prompt string + Placeholder string + EchoMode EchoMode + EchoCharacter rune + + // useVirtualCursor determines whether or not to use the virtual cursor. If + // set to false, use [Model.Cursor] to return a real cursor for rendering. + useVirtualCursor bool + + // Virtual cursor manager. + virtualCursor cursor.Model + + // CharLimit is the maximum amount of characters this input element will + // accept. If 0 or less, there's no limit. + CharLimit int + + // Styling. FocusedStyle and BlurredStyle are used to style the textarea in + // focused and blurred states. + styles Styles + + // Width is the maximum number of characters that can be displayed at once. + // It essentially treats the text field like a horizontally scrolling + // viewport. If 0 or less this setting is ignored. + width int + + // KeyMap encodes the keybindings recognized by the widget. + KeyMap KeyMap + + // Underlying text value. + value []rune + + // focus indicates whether user input focus should be on this input + // component. When false, ignore keyboard input and hide the cursor. + focus bool + + // Cursor position. + pos int + + // Used to emulate a viewport when width is set and the content is + // overflowing. + offset int + offsetRight int + + // Validate is a function that checks whether or not the text within the + // input is valid. If it is not valid, the `Err` field will be set to the + // error returned by the function. If the function is not defined, all + // input is considered valid. + Validate ValidateFunc + + // rune sanitizer for input. + rsan runeutil.Sanitizer + + // Should the input suggest to complete + ShowSuggestions bool + + // suggestions is a list of suggestions that may be used to complete the + // input. + suggestions [][]rune + matchedSuggestions [][]rune + currentSuggestionIndex int +} + +// New creates a new model with default settings. +func New() Model { + m := Model{ + Prompt: "> ", + EchoCharacter: '*', + CharLimit: 0, + styles: DefaultDarkStyles(), + ShowSuggestions: false, + useVirtualCursor: true, + virtualCursor: cursor.New(), + KeyMap: DefaultKeyMap(), + suggestions: [][]rune{}, + value: nil, + focus: false, + pos: 0, + } + m.updateVirtualCursorStyle() + return m +} + +// VirtualCursor returns whether the model is using a virtual cursor. +func (m Model) VirtualCursor() bool { + return m.useVirtualCursor +} + +// SetVirtualCursor sets whether the model should use a virtual cursor. If +// disabled, use [Model.Cursor] to return a real cursor for rendering. +func (m *Model) SetVirtualCursor(v bool) { + m.useVirtualCursor = v + m.updateVirtualCursorStyle() +} + +// Styles returns the current set of styles. +func (m Model) Styles() Styles { + return m.styles +} + +// SetStyles sets the styles for the text input. +func (m *Model) SetStyles(s Styles) { + m.styles = s + m.updateVirtualCursorStyle() +} + +// Width returns the width of the text input. +func (m Model) Width() int { + return m.width +} + +// SetWidth sets the width of the text input. +func (m *Model) SetWidth(w int) { + m.width = w +} + +// SetValue sets the value of the text input. +func (m *Model) SetValue(s string) { + // Clean up any special characters in the input provided by the + // caller. This avoids bugs due to e.g. tab characters and whatnot. + runes := m.san().Sanitize([]rune(s)) + err := m.validate(runes) + m.setValueInternal(runes, err) +} + +func (m *Model) setValueInternal(runes []rune, err error) { + m.Err = err + + empty := len(m.value) == 0 + + if m.CharLimit > 0 && len(runes) > m.CharLimit { + m.value = runes[:m.CharLimit] + } else { + m.value = runes + } + if (m.pos == 0 && empty) || m.pos > len(m.value) { + m.SetCursor(len(m.value)) + } + m.handleOverflow() +} + +// Value returns the value of the text input. +func (m Model) Value() string { + return string(m.value) +} + +// Position returns the cursor position. +func (m Model) Position() int { + return m.pos +} + +// SetCursor moves the cursor to the given position. If the position is +// out of bounds the cursor will be moved to the start or end accordingly. +func (m *Model) SetCursor(pos int) { + m.pos = clamp(pos, 0, len(m.value)) + m.handleOverflow() +} + +// CursorStart moves the cursor to the start of the input field. +func (m *Model) CursorStart() { + m.SetCursor(0) +} + +// CursorEnd moves the cursor to the end of the input field. +func (m *Model) CursorEnd() { + m.SetCursor(len(m.value)) +} + +// Focused returns the focus state on the model. +func (m Model) Focused() bool { + return m.focus +} + +// Focus sets the focus state on the model. When the model is in focus it can +// receive keyboard input and the cursor will be shown. +func (m *Model) Focus() tea.Cmd { + m.focus = true + return m.virtualCursor.Focus() +} + +// Blur removes the focus state on the model. When the model is blurred it can +// not receive keyboard input and the cursor will be hidden. +func (m *Model) Blur() { + m.focus = false + m.virtualCursor.Blur() +} + +// Reset sets the input to its default state with no input. +func (m *Model) Reset() { + m.value = nil + m.SetCursor(0) +} + +// SetSuggestions sets the suggestions for the input. +func (m *Model) SetSuggestions(suggestions []string) { + m.suggestions = make([][]rune, len(suggestions)) + for i, s := range suggestions { + m.suggestions[i] = []rune(s) + } + + m.updateSuggestions() +} + +// rsan initializes or retrieves the rune sanitizer. +func (m *Model) san() runeutil.Sanitizer { + if m.rsan == nil { + // Textinput has all its input on a single line so collapse + // newlines/tabs to single spaces. + m.rsan = runeutil.NewSanitizer( + runeutil.ReplaceTabs(" "), runeutil.ReplaceNewlines(" ")) + } + return m.rsan +} + +func (m *Model) insertRunesFromUserInput(v []rune) { + // Clean up any special characters in the input provided by the + // clipboard. This avoids bugs due to e.g. tab characters and + // whatnot. + paste := m.san().Sanitize(v) + + var availSpace int + if m.CharLimit > 0 { + availSpace = m.CharLimit - len(m.value) + + // If the char limit's been reached, cancel. + if availSpace <= 0 { + return + } + + // If there's not enough space to paste the whole thing cut the pasted + // runes down so they'll fit. + if availSpace < len(paste) { + paste = paste[:availSpace] + } + } + + // Stuff before and after the cursor + head := m.value[:m.pos] + tailSrc := m.value[m.pos:] + tail := make([]rune, len(tailSrc)) + copy(tail, tailSrc) + + // Insert pasted runes + for _, r := range paste { + head = append(head, r) + m.pos++ + if m.CharLimit > 0 { + availSpace-- + if availSpace <= 0 { + break + } + } + } + + // Put it all back together + value := append(head, tail...) + inputErr := m.validate(value) + m.setValueInternal(value, inputErr) +} + +// If a max width is defined, perform some logic to treat the visible area +// as a horizontally scrolling viewport. +func (m *Model) handleOverflow() { + if m.Width() <= 0 || uniseg.StringWidth(string(m.value)) <= m.Width() { + m.offset = 0 + m.offsetRight = len(m.value) + return + } + + // Correct right offset if we've deleted characters + m.offsetRight = min(m.offsetRight, len(m.value)) + + if m.pos < m.offset { + m.offset = m.pos + + w := 0 + i := 0 + runes := m.value[m.offset:] + + for i < len(runes) && w <= m.Width() { + w += rw.RuneWidth(runes[i]) + if w <= m.Width()+1 { + i++ + } + } + + m.offsetRight = m.offset + i + } else if m.pos >= m.offsetRight { + m.offsetRight = m.pos + + w := 0 + runes := m.value[:m.offsetRight] + i := len(runes) - 1 + + for i > 0 && w < m.Width() { + w += rw.RuneWidth(runes[i]) + if w <= m.Width() { + i-- + } + } + + m.offset = m.offsetRight - (len(runes) - 1 - i) + } +} + +// deleteBeforeCursor deletes all text before the cursor. +func (m *Model) deleteBeforeCursor() { + m.value = m.value[m.pos:] + m.Err = m.validate(m.value) + m.offset = 0 + m.SetCursor(0) +} + +// deleteAfterCursor deletes all text after the cursor. If input is masked +// delete everything after the cursor so as not to reveal word breaks in the +// masked input. +func (m *Model) deleteAfterCursor() { + m.value = m.value[:m.pos] + m.Err = m.validate(m.value) + m.SetCursor(len(m.value)) +} + +// deleteWordBackward deletes the word left to the cursor. +func (m *Model) deleteWordBackward() { + if m.pos == 0 || len(m.value) == 0 { + return + } + + if m.EchoMode != EchoNormal { + m.deleteBeforeCursor() + return + } + + // Linter note: it's critical that we acquire the initial cursor position + // here prior to altering it via SetCursor() below. As such, moving this + // call into the corresponding if clause does not apply here. + oldPos := m.pos + + m.SetCursor(m.pos - 1) + for unicode.IsSpace(m.value[m.pos]) { + if m.pos <= 0 { + break + } + // ignore series of whitespace before cursor + m.SetCursor(m.pos - 1) + } + + for m.pos > 0 { + if !unicode.IsSpace(m.value[m.pos]) { + m.SetCursor(m.pos - 1) + } else { + if m.pos > 0 { + // keep the previous space + m.SetCursor(m.pos + 1) + } + break + } + } + + if oldPos > len(m.value) { + m.value = m.value[:m.pos] + } else { + m.value = append(m.value[:m.pos], m.value[oldPos:]...) + } + m.Err = m.validate(m.value) +} + +// deleteWordForward deletes the word right to the cursor. If input is masked +// delete everything after the cursor so as not to reveal word breaks in the +// masked input. +func (m *Model) deleteWordForward() { + if m.pos >= len(m.value) || len(m.value) == 0 { + return + } + + if m.EchoMode != EchoNormal { + m.deleteAfterCursor() + return + } + + oldPos := m.pos + m.SetCursor(m.pos + 1) + for unicode.IsSpace(m.value[m.pos]) { + // ignore series of whitespace after cursor + m.SetCursor(m.pos + 1) + + if m.pos >= len(m.value) { + break + } + } + + for m.pos < len(m.value) { + if !unicode.IsSpace(m.value[m.pos]) { + m.SetCursor(m.pos + 1) + } else { + break + } + } + + if m.pos > len(m.value) { + m.value = m.value[:oldPos] + } else { + m.value = append(m.value[:oldPos], m.value[m.pos:]...) + } + m.Err = m.validate(m.value) + + m.SetCursor(oldPos) +} + +// wordBackward moves the cursor one word to the left. If input is masked, move +// input to the start so as not to reveal word breaks in the masked input. +func (m *Model) wordBackward() { + if m.pos == 0 || len(m.value) == 0 { + return + } + + if m.EchoMode != EchoNormal { + m.CursorStart() + return + } + + i := m.pos - 1 + for i >= 0 { + if unicode.IsSpace(m.value[i]) { + m.SetCursor(m.pos - 1) + i-- + } else { + break + } + } + + for i >= 0 { + if !unicode.IsSpace(m.value[i]) { + m.SetCursor(m.pos - 1) + i-- + } else { + break + } + } +} + +// wordForward moves the cursor one word to the right. If the input is masked, +// move input to the end so as not to reveal word breaks in the masked input. +func (m *Model) wordForward() { + if m.pos >= len(m.value) || len(m.value) == 0 { + return + } + + if m.EchoMode != EchoNormal { + m.CursorEnd() + return + } + + i := m.pos + for i < len(m.value) { + if unicode.IsSpace(m.value[i]) { + m.SetCursor(m.pos + 1) + i++ + } else { + break + } + } + + for i < len(m.value) { + if !unicode.IsSpace(m.value[i]) { + m.SetCursor(m.pos + 1) + i++ + } else { + break + } + } +} + +func (m Model) echoTransform(v string) string { + switch m.EchoMode { + case EchoPassword: + return strings.Repeat(string(m.EchoCharacter), uniseg.StringWidth(v)) + case EchoNone: + return "" + case EchoNormal: + return v + default: + return v + } +} + +// Update is the Bubble Tea update loop. +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + if !m.focus { + return m, nil + } + + // Need to check for completion before, because key is configurable and might be double assigned + keyMsg, ok := msg.(tea.KeyPressMsg) + if ok && key.Matches(keyMsg, m.KeyMap.AcceptSuggestion) { + if m.canAcceptSuggestion() { + m.value = append(m.value, m.matchedSuggestions[m.currentSuggestionIndex][len(m.value):]...) + m.CursorEnd() + } + } + + // Let's remember where the position of the cursor currently is so that if + // the cursor position changes, we can reset the blink. + oldPos := m.pos + + switch msg := msg.(type) { + case tea.KeyPressMsg: + switch { + case key.Matches(msg, m.KeyMap.DeleteWordBackward): + m.deleteWordBackward() + case key.Matches(msg, m.KeyMap.DeleteCharacterBackward): + m.Err = nil + if len(m.value) > 0 { + m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...) + m.Err = m.validate(m.value) + if m.pos > 0 { + m.SetCursor(m.pos - 1) + } + } + case key.Matches(msg, m.KeyMap.WordBackward): + m.wordBackward() + case key.Matches(msg, m.KeyMap.CharacterBackward): + if m.pos > 0 { + m.SetCursor(m.pos - 1) + } + case key.Matches(msg, m.KeyMap.WordForward): + m.wordForward() + case key.Matches(msg, m.KeyMap.CharacterForward): + if m.pos < len(m.value) { + m.SetCursor(m.pos + 1) + } + case key.Matches(msg, m.KeyMap.LineStart): + m.CursorStart() + case key.Matches(msg, m.KeyMap.DeleteCharacterForward): + if len(m.value) > 0 && m.pos < len(m.value) { + m.value = slices.Delete(m.value, m.pos, m.pos+1) + m.Err = m.validate(m.value) + } + case key.Matches(msg, m.KeyMap.LineEnd): + m.CursorEnd() + case key.Matches(msg, m.KeyMap.DeleteAfterCursor): + m.deleteAfterCursor() + case key.Matches(msg, m.KeyMap.DeleteBeforeCursor): + m.deleteBeforeCursor() + case key.Matches(msg, m.KeyMap.Paste): + return m, Paste + case key.Matches(msg, m.KeyMap.DeleteWordForward): + m.deleteWordForward() + case key.Matches(msg, m.KeyMap.NextSuggestion): + m.nextSuggestion() + case key.Matches(msg, m.KeyMap.PrevSuggestion): + m.previousSuggestion() + default: + // Input one or more regular characters. + m.insertRunesFromUserInput([]rune(msg.Text)) + } + + // Check again if can be completed + // because value might be something that does not match the completion prefix + m.updateSuggestions() + + case tea.PasteMsg: + m.insertRunesFromUserInput([]rune(msg.Content)) + + case pasteMsg: + m.insertRunesFromUserInput([]rune(msg)) + + case pasteErrMsg: + m.Err = msg + } + + var cmds []tea.Cmd + var cmd tea.Cmd + + if m.useVirtualCursor { + m.virtualCursor, cmd = m.virtualCursor.Update(msg) + cmds = append(cmds, cmd) + + // If the cursor position changed, reset the blink state. This is a + // small UX nuance that makes cursor movement obvious and feel snappy. + if oldPos != m.pos && m.virtualCursor.Mode() == cursor.CursorBlink { + m.virtualCursor.IsBlinked = false + cmds = append(cmds, m.virtualCursor.Blink()) + } + } + + m.handleOverflow() + return m, tea.Batch(cmds...) +} + +// View renders the textinput in its current state. +func (m Model) View() string { + // Placeholder text + if len(m.value) == 0 && m.Placeholder != "" { + return m.placeholderView() + } + + styles := m.activeStyle() + + styleText := styles.Text.Inline(true).Render + + value := m.value[m.offset:m.offsetRight] + pos := max(0, m.pos-m.offset) + v := styleText(m.echoTransform(string(value[:pos]))) + + if pos < len(value) { //nolint:nestif + char := m.echoTransform(string(value[pos])) + m.virtualCursor.SetChar(char) + v += m.virtualCursor.View() // cursor and text under it + v += styleText(m.echoTransform(string(value[pos+1:]))) // text after cursor + v += m.completionView(0) // suggested completion + } else { + if m.focus && m.canAcceptSuggestion() { + suggestion := m.matchedSuggestions[m.currentSuggestionIndex] + if len(value) < len(suggestion) { + m.virtualCursor.TextStyle = styles.Suggestion + m.virtualCursor.SetChar(m.echoTransform(string(suggestion[pos]))) + v += m.virtualCursor.View() + v += m.completionView(1) + } else { + m.virtualCursor.SetChar(" ") + v += m.virtualCursor.View() + } + } else { + m.virtualCursor.SetChar(" ") + v += m.virtualCursor.View() + } + } + + // If a max width and background color were set fill the empty spaces with + // the background color. + valWidth := uniseg.StringWidth(string(value)) + if m.Width() > 0 && valWidth <= m.Width() { + padding := max(0, m.Width()-valWidth) + if valWidth+padding <= m.Width() && pos < len(value) { + padding++ + } + v += styleText(strings.Repeat(" ", padding)) + } + + return m.promptView() + v +} + +func (m Model) promptView() string { + return m.activeStyle().Prompt.Render(m.Prompt) +} + +// placeholderView returns the prompt and placeholder view, if any. +func (m Model) placeholderView() string { + var ( + v string + styles = m.activeStyle() + render = styles.Placeholder.Render + ) + + p := make([]rune, m.Width()+1) + copy(p, []rune(m.Placeholder)) + + m.virtualCursor.TextStyle = styles.Placeholder + m.virtualCursor.SetChar(string(p[:1])) + v += m.virtualCursor.View() + + // If the entire placeholder is already set and no padding is needed, finish + if m.Width() < 1 && len(p) <= 1 { + return styles.Prompt.Render(m.Prompt) + v + } + + // If Width is set then size placeholder accordingly + if m.Width() > 0 { + // available width is width - len + cursor offset of 1 + minWidth := lipgloss.Width(m.Placeholder) + availWidth := m.Width() - minWidth + 1 + + // if width < len, 'subtract'(add) number to len and dont add padding + if availWidth < 0 { + minWidth += availWidth + availWidth = 0 + } + // append placeholder[len] - cursor, append padding + v += render(string(p[1:minWidth])) + v += render(strings.Repeat(" ", availWidth)) + } else { + // if there is no width, the placeholder can be any length + v += render(string(p[1:])) + } + + return styles.Prompt.Render(m.Prompt) + v +} + +// Blink is a command used to initialize cursor blinking. +func Blink() tea.Msg { + return cursor.Blink() +} + +// Paste is a command for pasting from the clipboard into the text input. +func Paste() tea.Msg { + str, err := clipboard.ReadAll() + if err != nil { + return pasteErrMsg{err} + } + return pasteMsg(str) +} + +func clamp(v, low, high int) int { + if high < low { + low, high = high, low + } + return min(high, max(low, v)) +} + +func (m Model) completionView(offset int) string { + if !m.canAcceptSuggestion() { + return "" + } + value := m.value + suggestion := m.matchedSuggestions[m.currentSuggestionIndex] + if len(value) < len(suggestion) { + return m.activeStyle().Suggestion.Inline(true). + Render(string(suggestion[len(value)+offset:])) + } + return "" +} + +func (m *Model) getSuggestions(sugs [][]rune) []string { + suggestions := make([]string, len(sugs)) + for i, s := range sugs { + suggestions[i] = string(s) + } + return suggestions +} + +// AvailableSuggestions returns the list of available suggestions. +func (m *Model) AvailableSuggestions() []string { + return m.getSuggestions(m.suggestions) +} + +// MatchedSuggestions returns the list of matched suggestions. +func (m *Model) MatchedSuggestions() []string { + return m.getSuggestions(m.matchedSuggestions) +} + +// CurrentSuggestionIndex returns the currently selected suggestion index. +func (m *Model) CurrentSuggestionIndex() int { + return m.currentSuggestionIndex +} + +// CurrentSuggestion returns the currently selected suggestion. +func (m *Model) CurrentSuggestion() string { + if m.currentSuggestionIndex >= len(m.matchedSuggestions) { + return "" + } + + return string(m.matchedSuggestions[m.currentSuggestionIndex]) +} + +// canAcceptSuggestion returns whether there is an acceptable suggestion to +// autocomplete the current value. +func (m *Model) canAcceptSuggestion() bool { + return len(m.matchedSuggestions) > 0 +} + +// updateSuggestions refreshes the list of matching suggestions. +func (m *Model) updateSuggestions() { + if !m.ShowSuggestions { + return + } + + if len(m.value) <= 0 || len(m.suggestions) <= 0 { + m.matchedSuggestions = [][]rune{} + return + } + + matches := [][]rune{} + for _, s := range m.suggestions { + suggestion := string(s) + + if strings.HasPrefix(strings.ToLower(suggestion), strings.ToLower(string(m.value))) { + matches = append(matches, []rune(suggestion)) + } + } + if !reflect.DeepEqual(matches, m.matchedSuggestions) { + m.currentSuggestionIndex = 0 + } + + m.matchedSuggestions = matches +} + +// nextSuggestion selects the next suggestion. +func (m *Model) nextSuggestion() { + m.currentSuggestionIndex = (m.currentSuggestionIndex + 1) + if m.currentSuggestionIndex >= len(m.matchedSuggestions) { + m.currentSuggestionIndex = 0 + } +} + +// previousSuggestion selects the previous suggestion. +func (m *Model) previousSuggestion() { + m.currentSuggestionIndex = (m.currentSuggestionIndex - 1) + if m.currentSuggestionIndex < 0 { + m.currentSuggestionIndex = len(m.matchedSuggestions) - 1 + } +} + +func (m Model) validate(v []rune) error { + if m.Validate != nil { + return m.Validate(string(v)) + } + return nil +} + +// Cursor returns a [tea.Cursor] for rendering a real cursor in a Bubble Tea +// program. This requires that [Model.VirtualCursor] is set to false. +// +// Note that you will almost certainly also need to adjust the offset cursor +// position per the textarea's per the textarea's position in the terminal. +// +// Example: +// +// // In your top-level View function: +// f := tea.NewFrame(m.textarea.View()) +// f.Cursor = m.textarea.Cursor() +// f.Cursor.Position.X += offsetX +// f.Cursor.Position.Y += offsetY +func (m Model) Cursor() *tea.Cursor { + if m.useVirtualCursor || !m.Focused() { + return nil + } + + w := lipgloss.Width + + promptWidth := w(m.promptView()) + xOffset := m.Position() + + promptWidth + if m.width > 0 { + xOffset = min(xOffset, m.width+promptWidth) + } + + style := m.styles.Cursor + c := tea.NewCursor(xOffset, 0) + c.Blink = style.Blink + c.Color = style.Color + c.Shape = style.Shape + return c +} + +// updateVirtualCursorStyle sets styling on the virtual cursor based on the +// textarea's style settings. +func (m *Model) updateVirtualCursorStyle() { + if !m.useVirtualCursor { + // Hide the virtual cursor if we're using a real cursor. + m.virtualCursor.SetMode(cursor.CursorHide) + return + } + + m.virtualCursor.Style = lipgloss.NewStyle().Foreground(m.styles.Cursor.Color) + + // By default, the blink speed of the cursor is set to a default + // internally. + if m.styles.Cursor.Blink { + if m.styles.Cursor.BlinkSpeed > 0 { + m.virtualCursor.BlinkSpeed = m.styles.Cursor.BlinkSpeed + } + m.virtualCursor.SetMode(cursor.CursorBlink) + return + } + m.virtualCursor.SetMode(cursor.CursorStatic) +} + +// activeStyle returns the appropriate set of styles to use depending on +// whether the textarea is focused or blurred. +func (m Model) activeStyle() *StyleState { + if m.focus { + return &m.styles.Focused + } + return &m.styles.Blurred +} diff --git a/vendor/charm.land/bubbletea/v2/.gitattributes b/vendor/charm.land/bubbletea/v2/.gitattributes new file mode 100644 index 000000000..6c929d480 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/.gitattributes @@ -0,0 +1 @@ +*.golden -text diff --git a/vendor/charm.land/bubbletea/v2/.gitignore b/vendor/charm.land/bubbletea/v2/.gitignore new file mode 100644 index 000000000..abd7c0612 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +.envrc + +examples/fullscreen/fullscreen +examples/help/help +examples/http/http +examples/list-default/list-default +examples/list-fancy/list-fancy +examples/list-simple/list-simple +examples/mouse/mouse +examples/pager/pager +examples/progress-download/color_vortex.blend +examples/progress-download/progress-download +examples/simple/simple +examples/spinner/spinner +examples/textinput/textinput +examples/textinputs/textinputs +examples/views/views +tutorials/basics/basics +tutorials/commands/commands +.idea +coverage.txt +dist/ diff --git a/vendor/charm.land/bubbletea/v2/.golangci.yml b/vendor/charm.land/bubbletea/v2/.golangci.yml new file mode 100644 index 000000000..c90f03161 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/.golangci.yml @@ -0,0 +1,47 @@ +version: "2" +run: + tests: false +linters: + enable: + - bodyclose + - exhaustive + - goconst + - godot + - gomoddirectives + - goprintffuncname + - gosec + - misspell + - nakedret + - nestif + - nilerr + - noctx + - nolintlint + - prealloc + - revive + - rowserrcheck + - sqlclosecheck + - tparallel + - unconvert + - unparam + - whitespace + - wrapcheck + exclusions: + rules: + - text: '(slog|log)\.\w+' + linters: + - noctx + generated: lax + presets: + - common-false-positives + settings: + exhaustive: + default-signifies-exhaustive: true +issues: + max-issues-per-linter: 0 + max-same-issues: 0 +formatters: + enable: + - gofumpt + - goimports + exclusions: + generated: lax diff --git a/vendor/charm.land/bubbletea/v2/.goreleaser.yml b/vendor/charm.land/bubbletea/v2/.goreleaser.yml new file mode 100644 index 000000000..3353d0202 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/.goreleaser.yml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json +version: 2 +includes: + - from_url: + url: charmbracelet/meta/main/goreleaser-lib.yaml diff --git a/vendor/charm.land/bubbletea/v2/LICENSE b/vendor/charm.land/bubbletea/v2/LICENSE new file mode 100644 index 000000000..01d14e6ae --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2026 Charmbracelet, Inc. + +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. diff --git a/vendor/charm.land/bubbletea/v2/README.md b/vendor/charm.land/bubbletea/v2/README.md new file mode 100644 index 000000000..31c0e83ef --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/README.md @@ -0,0 +1,402 @@ +# Bubble Tea + +

    +
    + Latest Release + GoDoc + Build Status +

    + +The fun, functional and stateful way to build terminal apps. A Go framework +based on [The Elm Architecture][elm]. Bubble Tea is well-suited for simple and +complex terminal applications, either inline, full-window, or a mix of both. + +

    + Bubble Tea Example +

    + +Bubble Tea is in use in production and includes a number of features and +performance optimizations we’ve added along the way. Among those is +a high-performance cell-based renderer, built-in color downsampling, +declarative views, high-fidelity keyboard and mouse handling, native clipboard +support, and more. + +To get started, see the tutorial below, the [examples][examples], the +[docs][docs], and some common [resources](#libraries-we-use-with-bubble-tea). + +> [!TIP] +> +> Upgrading from v1? Check out the [upgrade guide](./UPGRADE_GUIDE_V2.md), or +> point your LLM at it and let it go to town. + +## By the way + +Be sure to check out [Bubbles][bubbles], a library of common UI components for Bubble Tea. + +

    + Bubbles Badge   + Text Input Example from Bubbles +

    + +--- + +## Tutorial + +Bubble Tea is based on the functional design paradigms of [The Elm +Architecture][elm], which happens to work nicely with Go. It's a delightful way +to build applications. + +This tutorial assumes you have a working knowledge of Go. + +By the way, the non-annotated source code for this program is available +[on GitHub][tut-source]. + +[elm]: https://guide.elm-lang.org/architecture/ +[tut-source]: https://github.com/charmbracelet/bubbletea/tree/main/tutorials/basics + +### Enough! Let's get to it. + +For this tutorial, we're making a shopping list. + +To start we'll define our package and import some libraries. Our only external +import will be the Bubble Tea library, which we'll call `tea` for short. + +```go +package main + +// These imports will be used later in the tutorial. If you save the file +// now, Go might complain they are unused, but that's fine. +// You may also need to run `go mod tidy` to download bubbletea and its +// dependencies. +import ( + "fmt" + "os" + + tea "charm.land/bubbletea/v2" +) +``` + +Bubble Tea programs are comprised of a **model** that describes the application +state and three simple methods on that model: + +- **Init**, a function that returns an initial command for the application to run. +- **Update**, a function that handles incoming events and updates the model accordingly. +- **View**, a function that renders the UI based on the data in the model. + +### The Model + +So let's start by defining our model which will store our application's state. +It can be any type, but a `struct` usually makes the most sense. + +```go +type model struct { + choices []string // items on the to-do list + cursor int // which to-do list item our cursor is pointing at + selected map[int]struct{} // which to-do items are selected +} +``` + +## Initialization + +Next, we’ll define our application’s initial state. `Init` can return a `Cmd` +that could perform some initial I/O. For now, we don’t need to do any I/O, so +for the command, we’ll just return `nil`, which translates to “no command.” + +```go +func initialModel() model { + return model{ + // Our to-do list is a grocery list + choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"}, + + // A map which indicates which choices are selected. We're using + // the map like a mathematical set. The keys refer to the indexes + // of the `choices` slice, above. + selected: make(map[int]struct{}), + } +} +``` + +After that, we’ll define our application’s initial state in the `Init` method. `Init` +can return a `Cmd` that could perform some initial I/O. For now, we don't need +to do any I/O, so for the command, we'll just return `nil`, which translates to +"no command." + +```go +func (m model) Init() tea.Cmd { + // Just return `nil`, which means "no I/O right now, please." + return nil +} +``` + +### The Update Method + +Next up is the update method. The update function is called when “things +happen.” Its job is to look at what has happened and return an updated model in +response. It can also return a `Cmd` to make more things happen, but for now +don't worry about that part. + +In our case, when a user presses the down arrow, `Update`’s job is to notice +that the down arrow was pressed and move the cursor accordingly (or not). + +The “something happened” comes in the form of a `Msg`, which can be any type. +Messages are the result of some I/O that took place, such as a keypress, timer +tick, or a response from a server. + +We usually figure out which type of `Msg` we received with a type switch, but +you could also use a type assertion. + +For now, we'll just deal with `tea.KeyPressMsg` messages, which are +automatically sent to the update function when keys are pressed. + +```go +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + + // Is it a key press? + case tea.KeyPressMsg: + + // Cool, what was the actual key pressed? + switch msg.String() { + + // These keys should exit the program. + case "ctrl+c", "q": + return m, tea.Quit + + // The "up" and "k" keys move the cursor up + case "up", "k": + if m.cursor > 0 { + m.cursor-- + } + + // The "down" and "j" keys move the cursor down + case "down", "j": + if m.cursor < len(m.choices)-1 { + m.cursor++ + } + + // The "enter" key and the space bar toggle the selected state + // for the item that the cursor is pointing at. + case "enter", "space": + _, ok := m.selected[m.cursor] + if ok { + delete(m.selected, m.cursor) + } else { + m.selected[m.cursor] = struct{}{} + } + } + } + + // Return the updated model to the Bubble Tea runtime for processing. + // Note that we're not returning a command. + return m, nil +} +``` + +You may have noticed that ctrl+c and q above return +a `tea.Quit` command with the model. That’s a special command which instructs +the Bubble Tea runtime to quit, exiting the program. + +### The View Method + +At last, it’s time to render our UI. Of all the methods, the view is the +simplest. We look at the model in its current state and use it to build a +`tea.View`. The view declares our UI content and, optionally, terminal features +like alt screen mode, mouse tracking, cursor position, and more. + +Because the view describes the entire UI of your application, you don’t have to +worry about redrawing logic and stuff like that. Bubble Tea takes care of it +for you. + +```go +func (m model) View() tea.View { + // The header + s := "What should we buy at the market?\n\n" + + // Iterate over our choices + for i, choice := range m.choices { + + // Is the cursor pointing at this choice? + cursor := " " // no cursor + if m.cursor == i { + cursor = ">" // cursor! + } + + // Is this choice selected? + checked := " " // not selected + if _, ok := m.selected[i]; ok { + checked = "x" // selected! + } + + // Render the row + s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice) + } + + // The footer + s += "\nPress q to quit.\n" + + // Send the UI for rendering + return tea.NewView(s) +} +``` + +### All Together Now + +The last step is to simply run our program. We pass our initial model to +`tea.NewProgram` and let it rip: + +```go +func main() { + p := tea.NewProgram(initialModel()) + if _, err := p.Run(); err != nil { + fmt.Printf("Alas, there's been an error: %v", err) + os.Exit(1) + } +} +``` + +## What’s Next? + +This tutorial covers the basics of building an interactive terminal UI, but +in the real world you'll also need to perform I/O. To learn about that have a +look at the [Command Tutorial][cmd]. It's pretty simple. + +There are also several [Bubble Tea examples][examples] available and, of course, +there are [Go Docs][docs]. + +[cmd]: https://github.com/charmbracelet/bubbletea/tree/main/tutorials/commands/ +[examples]: https://github.com/charmbracelet/bubbletea/tree/main/examples +[docs]: https://pkg.go.dev/charm.land/bubbletea/v2?tab=doc + +## Debugging + +### Debugging with Delve + +Since Bubble Tea apps assume control of stdin and stdout, you’ll need to run +delve in headless mode and then connect to it: + +```bash +# Start the debugger +$ dlv debug --headless --api-version=2 --listen=127.0.0.1:43000 . +API server listening at: 127.0.0.1:43000 + +# Connect to it from another terminal +$ dlv connect 127.0.0.1:43000 +``` + +If you do not explicitly supply the `--listen` flag, the port used will vary +per run, so passing this in makes the debugger easier to use from a script +or your IDE of choice. + +Additionally, we pass in `--api-version=2` because delve defaults to version 1 +for backwards compatibility reasons. However, delve recommends using version 2 +for all new development and some clients may no longer work with version 1. +For more information, see the [Delve documentation](https://github.com/go-delve/delve/tree/master/Documentation/api). + +### Logging Stuff + +You can’t really log to stdout with Bubble Tea because your TUI is busy +occupying that! You can, however, log to a file by including something like +the following prior to starting your Bubble Tea program: + +```go +if len(os.Getenv("DEBUG")) > 0 { + f, err := tea.LogToFile("debug.log", "debug") + if err != nil { + fmt.Println("fatal:", err) + os.Exit(1) + } + defer f.Close() +} +``` + +To see what’s being logged in real time, run `tail -f debug.log` while you run +your program in another window. + +## Libraries we use with Bubble Tea + +- [Bubbles][bubbles]: Common Bubble Tea components such as text inputs, viewports, spinners and so on +- [Lip Gloss][lipgloss]: Style, format and layout tools for terminal applications +- [Harmonica][harmonica]: A spring animation library for smooth, natural motion +- [BubbleZone][bubblezone]: Easy mouse event tracking for Bubble Tea components +- [ntcharts][ntcharts]: A terminal charting library built for Bubble Tea and [Lip Gloss][lipgloss] + +[bubbles]: https://github.com/charmbracelet/bubbles +[lipgloss]: https://github.com/charmbracelet/lipgloss +[harmonica]: https://github.com/charmbracelet/harmonica +[bubblezone]: https://github.com/lrstanley/bubblezone +[ntcharts]: https://github.com/NimbleMarkets/ntcharts + +## Bubble Tea in the Wild + +There are over [18,000 applications](https://github.com/charmbracelet/bubbletea/network/dependents) built with Bubble Tea! Here are a handful of ’em. + +### Staff favourites + +- [chezmoi](https://github.com/twpayne/chezmoi): securely manage your dotfiles across multiple machines +- [circumflex](https://github.com/bensadeh/circumflex): read Hacker News in the terminal +- [gh-dash](https://www.github.com/dlvhdr/gh-dash): a GitHub CLI extension for PRs and issues +- [Tetrigo](https://github.com/Broderick-Westrope/tetrigo): Tetris in the terminal +- [Signls](https://github.com/emprcl/signls): a generative midi sequencer designed for composition and live performance +- [Superfile](https://github.com/yorukot/superfile): a super file manager + +### In Industry + +- Microsoft Azure – [Aztify](https://github.com/Azure/aztfy): bring Microsoft Azure resources under Terraform +- Daytona – [Daytona](https://github.com/daytonaio/daytona): an AI infrastructure platform +- Cockroach Labs – [CockroachDB](https://github.com/cockroachdb/cockroach): a cloud-native, high-availability distributed SQL database +- Truffle Security Co. – [Trufflehog](https://github.com/trufflesecurity/trufflehog): find leaked credentials +- NVIDIA – [container-canary](https://github.com/NVIDIA/container-canary): a container validator +- AWS – [eks-node-viewer](https://github.com/awslabs/eks-node-viewer): a tool for visualizing dynamic node usage within an EKS cluster +- MinIO – [mc](https://github.com/minio/mc): the official [MinIO](https://min.io) client +- Ubuntu – [Authd](https://github.com/ubuntu/authd): an authentication daemon for cloud-based identity providers + +### Charm stuff + +- [Glow](https://github.com/charmbracelet/glow): a markdown reader, browser, and online markdown stash +- [Huh?](https://github.com/charmbracelet/huh): an interactive prompt and form toolkit +- [Mods](https://github.com/charmbracelet/mods): AI on the CLI, built for pipelines +- [Wishlist](https://github.com/charmbracelet/wishlist): an SSH directory (and bastion!) + +### There’s so much more where that came from + +For more applications built with Bubble Tea see [Charm & Friends][community]. +Is there something cool you made with Bubble Tea you want to share? [PRs][community] are +welcome! + +## Contributing + +See [contributing][contribute]. + +[contribute]: https://github.com/charmbracelet/bubbletea/contribute + +## Feedback + +We’d love to hear your thoughts on this project. Feel free to drop us a note! + +- [Twitter](https://twitter.com/charmcli) +- [The Fediverse](https://mastodon.social/@charmcli) +- [Discord](https://charm.sh/chat) + +## Acknowledgments + +Bubble Tea is based on the paradigms of [The Elm Architecture][elm] by Evan +Czaplicki et alia and the excellent [go-tea][gotea] by TJ Holowaychuk. It’s +inspired by the many great [_Zeichenorientierte Benutzerschnittstellen_][zb] +of days past. + +[elm]: https://guide.elm-lang.org/architecture/ +[gotea]: https://github.com/tj/go-tea +[zb]: https://de.wikipedia.org/wiki/Zeichenorientierte_Benutzerschnittstelle +[community]: https://github.com/charm-and-friends/charm-in-the-wild + +## License + +[MIT](https://github.com/charmbracelet/bubbletea/raw/main/LICENSE) + +--- + +Part of [Charm](https://charm.sh). + +The Charm logo + +Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة diff --git a/vendor/charm.land/bubbletea/v2/Taskfile.yaml b/vendor/charm.land/bubbletea/v2/Taskfile.yaml new file mode 100644 index 000000000..35072035d --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/Taskfile.yaml @@ -0,0 +1,14 @@ +# https://taskfile.dev + +version: '3' + +tasks: + lint: + desc: Run lint + cmds: + - golangci-lint run + + test: + desc: Run tests + cmds: + - go test ./... {{.CLI_ARGS}} diff --git a/vendor/charm.land/bubbletea/v2/UPGRADE_GUIDE_V2.md b/vendor/charm.land/bubbletea/v2/UPGRADE_GUIDE_V2.md new file mode 100644 index 000000000..2ca7b0a08 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/UPGRADE_GUIDE_V2.md @@ -0,0 +1,573 @@ +# Bubble Tea v2 Upgrade Guide + +This guide covers everything you need to change when upgrading from Bubble Tea v1 to v2. For a tour of all the exciting new features, check out the [What's New](https://github.com/charmbracelet/bubbletea/releases/tag/v2.0.0) doc. + +> [!NOTE] +> We don't take API changes lightly and strive to make the upgrade process as simple as possible. If something feels way off, let us know. + +## Migration Checklist + +Here's the short version — a checklist you can follow top to bottom. Each item links to the relevant section below. + +- [ ] [Update import paths](#import-paths) +- [ ] [Change `View() string` to `View() tea.View`](#view-returns-a-teaview-now) +- [ ] [Replace `tea.KeyMsg` with `tea.KeyPressMsg`](#key-messages) +- [ ] [Update key fields: `msg.Type` / `msg.Runes` / `msg.Alt`](#key-messages) +- [ ] [Replace `case " ":` with `case "space":`](#key-messages) +- [ ] [Update mouse message usage](#mouse-messages) +- [ ] [Rename mouse button constants](#mouse-messages) +- [ ] [Remove old program options → use View fields](#removed-program-options) +- [ ] [Remove imperative commands → use View fields](#removed-commands) +- [ ] [Remove old program methods](#removed-program-methods) +- [ ] [Rename `tea.WindowSize()` → `tea.RequestWindowSize`](#renamed-apis) +- [ ] [Replace `tea.Sequentially(...)` → `tea.Sequence(...)`](#renamed-apis) + +## Import Paths + +The module path changed to a vanity domain. Lip Gloss moved too. + +```go +// Before +import tea "github.com/charmbracelet/bubbletea" +import "github.com/charmbracelet/lipgloss" + +// After +import tea "charm.land/bubbletea/v2" +import "charm.land/lipgloss/v2" +``` + +## The Big Idea: Declarative Views + +The single biggest change in v2 is the shift from **imperative commands** to **declarative View fields**. In v1, you'd use program options like `tea.WithAltScreen()` and commands like `tea.EnterAltScreen` to toggle terminal features on and off. In v2, you just set fields on the `tea.View` struct in your `View()` method and Bubble Tea handles the rest. + +This means: no more startup option flags, no more toggle commands, no more fighting over state. Just declare what you want and Bubble Tea will make it so. + +```go +// v1: imperative — scattered across NewProgram, Init, and Update +p := tea.NewProgram(model{}, tea.WithAltScreen(), tea.WithMouseCellMotion()) + +// v2: declarative — everything lives in View() +func (m model) View() tea.View { + v := tea.NewView("Hello!") + v.AltScreen = true + v.MouseMode = tea.MouseModeCellMotion + return v +} +``` + +Keep this in mind as you go through the rest of the guide — most of the "removed" things simply moved into View fields. + +## View Returns a `tea.View` Now + +The `View()` method no longer returns a `string`. It returns a `tea.View` struct. + +```go +// Before: +func (m model) View() string { + return "Hello, world!" +} + +// After: +func (m model) View() tea.View { + return tea.NewView("Hello, world!") +} +``` + +You can also use the longer form if you need to set additional fields: + +```go +func (m model) View() tea.View { + var v tea.View + v.SetContent("Hello, world!") + v.AltScreen = true + return v +} +``` + +The `tea.View` struct has fields for everything that used to be controlled by options and commands: + +| View Field | What It Does | +|---|---| +| `Content` | The rendered string (set via `SetContent()` or `NewView()`) | +| `AltScreen` | Enter/exit the alternate screen buffer | +| `MouseMode` | `MouseModeNone`, `MouseModeCellMotion`, or `MouseModeAllMotion` | +| `ReportFocus` | Enable focus/blur event reporting | +| `DisableBracketedPasteMode` | Disable bracketed paste | +| `WindowTitle` | Set the terminal window title | +| `Cursor` | Control cursor position, shape, color, and blink | +| `ForegroundColor` | Set the terminal foreground color | +| `BackgroundColor` | Set the terminal background color | +| `ProgressBar` | Show a native terminal progress bar | +| `KeyboardEnhancements` | Request keyboard enhancement features | +| `OnMouse` | Intercept mouse messages based on view content | + +## Key Messages + +Key messages got a major overhaul. Here's the quick rundown: + +### `tea.KeyMsg` is now an interface + +In v1, `tea.KeyMsg` was a struct you'd match on for key presses. In v2, it's an **interface** that covers both key presses and releases. For most code, you want `tea.KeyPressMsg`: + +```go +// Before: +case tea.KeyMsg: + switch msg.String() { + case "q": + return m, tea.Quit + } + +// After: +case tea.KeyPressMsg: + switch msg.String() { + case "q": + return m, tea.Quit + } +``` + +If you want to handle both presses and releases, use `tea.KeyMsg` and type-switch inside: + +```go +case tea.KeyMsg: + switch key := msg.(type) { + case tea.KeyPressMsg: + // key press + case tea.KeyReleaseMsg: + // key release + } +``` + +### Key fields changed + +| v1 | v2 | Notes | +|---|---|---| +| `msg.Type` | `msg.Code` | A `rune` — can be `tea.KeyEnter`, `'a'`, etc. | +| `msg.Runes` | `msg.Text` | Now a `string`, not `[]rune` | +| `msg.Alt` | `msg.Mod` | `msg.Mod.Contains(tea.ModAlt)` for alt, etc. | +| `tea.KeyRune` | — | Check `len(msg.Text) > 0` instead | +| `tea.KeyCtrlC` | — | Use `msg.String() == "ctrl+c"` or check `msg.Code` + `msg.Mod` | + +### Space bar changed + +Space bar now returns `"space"` instead of `" "` when using `msg.String()`: + +```go +// Before: +case " ": + +// After: +case "space": +``` + +`key.Code` is still `' '` and `key.Text` is still `" "`, but `String()` returns `"space"`. + +### Ctrl+key matching + +```go +// Before: +case tea.KeyCtrlC: + // ctrl+c + +// After (option A — string matching): +case tea.KeyPressMsg: + switch msg.String() { + case "ctrl+c": + // ctrl+c + } + +// After (option B — field matching): +case tea.KeyPressMsg: + if msg.Code == 'c' && msg.Mod == tea.ModCtrl { + // ctrl+c + } +``` + +### New Key fields + +These are new in v2 and don't have v1 equivalents: + +- **`key.ShiftedCode`** — the shifted key code (e.g., `'B'` when pressing shift+b) +- **`key.BaseCode`** — the key on a US PC-101 layout (handy for international keyboards) +- **`key.IsRepeat`** — whether the key is auto-repeating (Kitty protocol / Windows Console only) +- **`key.Keystroke()`** — like `String()` but always includes modifier info + +## Paste Messages + +Paste events no longer come in as `tea.KeyMsg` with a `Paste` flag. They're now their own message types: + +```go +// Before: +case tea.KeyMsg: + if msg.Paste { + m.text += string(msg.Runes) + } + +// After: +case tea.PasteMsg: + m.text += msg.Content +case tea.PasteStartMsg: + // paste started +case tea.PasteEndMsg: + // paste ended +``` + +## Mouse Messages + +### `tea.MouseMsg` is now an interface + +In v1, `tea.MouseMsg` was a struct with `X`, `Y`, `Button`, etc. In v2, it's an **interface**. You get the coordinates by calling `msg.Mouse()`: + +```go +// Before: +case tea.MouseMsg: + x, y := msg.X, msg.Y + +// After: +case tea.MouseMsg: + mouse := msg.Mouse() + x, y := mouse.X, mouse.Y +``` + +### Mouse events are split by type + +Instead of checking `msg.Action`, match on specific message types: + +```go +// Before: +case tea.MouseMsg: + if msg.Action == tea.MouseActionPress && msg.Button == tea.MouseButtonLeft { + // left click + } + +// After: +case tea.MouseClickMsg: + if msg.Button == tea.MouseLeft { + // left click + } +case tea.MouseReleaseMsg: + // release +case tea.MouseWheelMsg: + // scroll +case tea.MouseMotionMsg: + // movement +``` + +### Button constants renamed + +| v1 | v2 | +|---|---| +| `tea.MouseButtonLeft` | `tea.MouseLeft` | +| `tea.MouseButtonRight` | `tea.MouseRight` | +| `tea.MouseButtonMiddle` | `tea.MouseMiddle` | +| `tea.MouseButtonWheelUp` | `tea.MouseWheelUp` | +| `tea.MouseButtonWheelDown` | `tea.MouseWheelDown` | +| `tea.MouseButtonWheelLeft` | `tea.MouseWheelLeft` | +| `tea.MouseButtonWheelRight` | `tea.MouseWheelRight` | + +### `tea.MouseEvent` → `tea.Mouse` + +The `MouseEvent` struct is gone. The new `Mouse` struct has `X`, `Y`, `Button`, and `Mod` fields. + +### Mouse mode is now a View field + +```go +// Before: +p := tea.NewProgram(model{}, tea.WithMouseCellMotion()) + +// After: +func (m model) View() tea.View { + v := tea.NewView("...") + v.MouseMode = tea.MouseModeCellMotion + return v +} +``` + +## Removed Program Options + +These options no longer exist. They all moved to View fields. + +| Removed Option | Do This Instead | +|---|---| +| `tea.WithAltScreen()` | `view.AltScreen = true` | +| `tea.WithMouseCellMotion()` | `view.MouseMode = tea.MouseModeCellMotion` | +| `tea.WithMouseAllMotion()` | `view.MouseMode = tea.MouseModeAllMotion` | +| `tea.WithReportFocus()` | `view.ReportFocus = true` | +| `tea.WithoutBracketedPaste()` | `view.DisableBracketedPasteMode = true` | +| `tea.WithInputTTY()` | Just remove it — v2 always opens the TTY for input automatically | +| `tea.WithANSICompressor()` | Just remove it — the new renderer handles optimization automatically | + +## Removed Commands + +These commands no longer exist. Set the corresponding View field instead. + +| Removed Command | Do This Instead | +|---|---| +| `tea.EnterAltScreen` | `view.AltScreen = true` | +| `tea.ExitAltScreen` | `view.AltScreen = false` | +| `tea.EnableMouseCellMotion` | `view.MouseMode = tea.MouseModeCellMotion` | +| `tea.EnableMouseAllMotion` | `view.MouseMode = tea.MouseModeAllMotion` | +| `tea.DisableMouse` | `view.MouseMode = tea.MouseModeNone` | +| `tea.HideCursor` | `view.Cursor = nil` | +| `tea.ShowCursor` | `view.Cursor = &tea.Cursor{...}` or `tea.NewCursor(x, y)` | +| `tea.EnableBracketedPaste` | `view.DisableBracketedPasteMode = false` | +| `tea.DisableBracketedPaste` | `view.DisableBracketedPasteMode = true` | +| `tea.EnableReportFocus` | `view.ReportFocus = true` | +| `tea.DisableReportFocus` | `view.ReportFocus = false` | +| `tea.SetWindowTitle("...")` | `view.WindowTitle = "..."` | + +## Removed Program Methods + +These methods on `*Program` are gone. + +| Removed Method | Do This Instead | +|---|---| +| `p.Start()` | `p.Run()` | +| `p.StartReturningModel()` | `p.Run()` | +| `p.EnterAltScreen()` | `view.AltScreen = true` in `View()` | +| `p.ExitAltScreen()` | `view.AltScreen = false` in `View()` | +| `p.EnableMouseCellMotion()` | `view.MouseMode` in `View()` | +| `p.DisableMouseCellMotion()` | `view.MouseMode = tea.MouseModeNone` in `View()` | +| `p.EnableMouseAllMotion()` | `view.MouseMode` in `View()` | +| `p.DisableMouseAllMotion()` | `view.MouseMode = tea.MouseModeNone` in `View()` | +| `p.SetWindowTitle(...)` | `view.WindowTitle` in `View()` | + +## Renamed APIs + +| v1 | v2 | Notes | +|---|---|---| +| `tea.Sequentially(...)` | `tea.Sequence(...)` | `Sequentially` was already deprecated in v1 | +| `tea.WindowSize()` | `tea.RequestWindowSize` | Now returns `Msg` directly, not a `Cmd` | + +## New Program Options + +These are new in v2: + +| Option | What It Does | +|---|---| +| `tea.WithColorProfile(p)` | Force a specific color profile (great for testing) | +| `tea.WithWindowSize(w, h)` | Set initial terminal size (great for testing) | + +## Complete Before & After + +Here's a minimal but complete program showing the most common migration patterns side by side. + +**v1:** + +```go +package main + +import ( + "fmt" + "os" + + tea "github.com/charmbracelet/bubbletea" +) + +type model struct { + count int +} + +func (m model) Init() tea.Cmd { + return nil +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "q", "ctrl+c": + return m, tea.Quit + case " ": + m.count++ + } + case tea.MouseMsg: + if msg.Action == tea.MouseActionPress && msg.Button == tea.MouseButtonLeft { + m.count++ + } + } + return m, nil +} + +func (m model) View() string { + return fmt.Sprintf("Count: %d\n\nSpace or click to increment. q to quit.\n", m.count) +} + +func main() { + p := tea.NewProgram(model{}, tea.WithAltScreen(), tea.WithMouseCellMotion()) + if _, err := p.Run(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} +``` + +**v2:** + +```go +package main + +import ( + "fmt" + "os" + + tea "charm.land/bubbletea/v2" +) + +type model struct { + count int +} + +func (m model) Init() tea.Cmd { + return nil +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyPressMsg: + switch msg.String() { + case "q", "ctrl+c": + return m, tea.Quit + case "space": + m.count++ + } + case tea.MouseClickMsg: + if msg.Button == tea.MouseLeft { + m.count++ + } + } + return m, nil +} + +func (m model) View() tea.View { + v := tea.NewView(fmt.Sprintf("Count: %d\n\nSpace or click to increment. q to quit.\n", m.count)) + v.AltScreen = true + v.MouseMode = tea.MouseModeCellMotion + return v +} + +func main() { + p := tea.NewProgram(model{}) + if _, err := p.Run(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} +``` + +Notice how the `NewProgram` call got simpler? All the terminal feature flags moved into `View()` where they belong. + +## Quick Reference + +A flat old → new lookup table. Handy for search-and-replace and LLM-assisted migration. + +### Import Paths + +| v1 | v2 | +|---|---| +| `github.com/charmbracelet/bubbletea` | `charm.land/bubbletea/v2` | +| `github.com/charmbracelet/lipgloss` | `charm.land/lipgloss/v2` | + +### Model Interface + +| v1 | v2 | +|---|---| +| `View() string` | `View() tea.View` | + +### Key Events + +| v1 | v2 | +|---|---| +| `tea.KeyMsg` (struct) | `tea.KeyPressMsg` for presses, `tea.KeyMsg` (interface) for both | +| `msg.Type` | `msg.Code` | +| `msg.Runes` | `msg.Text` (string, not `[]rune`) | +| `msg.Alt` | `msg.Mod.Contains(tea.ModAlt)` | +| `tea.KeyRune` | check `len(msg.Text) > 0` | +| `tea.KeyCtrlC` | `msg.Code == 'c' && msg.Mod == tea.ModCtrl` or `msg.String() == "ctrl+c"` | +| `case " ":` (space) | `case "space":` | + +### Mouse Events + +| v1 | v2 | +|---|---| +| `tea.MouseMsg` (struct) | `tea.MouseMsg` (interface) — call `.Mouse()` for the data | +| `tea.MouseEvent` | `tea.Mouse` | +| `tea.MouseButtonLeft` | `tea.MouseLeft` | +| `tea.MouseButtonRight` | `tea.MouseRight` | +| `tea.MouseButtonMiddle` | `tea.MouseMiddle` | +| `tea.MouseButtonWheelUp` | `tea.MouseWheelUp` | +| `tea.MouseButtonWheelDown` | `tea.MouseWheelDown` | +| `msg.X`, `msg.Y` (direct) | `msg.Mouse().X`, `msg.Mouse().Y` | + +### Options → View Fields + +| v1 Option | v2 View Field | +|---|---| +| `tea.WithAltScreen()` | `view.AltScreen = true` | +| `tea.WithMouseCellMotion()` | `view.MouseMode = tea.MouseModeCellMotion` | +| `tea.WithMouseAllMotion()` | `view.MouseMode = tea.MouseModeAllMotion` | +| `tea.WithReportFocus()` | `view.ReportFocus = true` | +| `tea.WithoutBracketedPaste()` | `view.DisableBracketedPasteMode = true` | + +### Commands → View Fields + +| v1 Command | v2 View Field | +|---|---| +| `tea.EnterAltScreen` / `tea.ExitAltScreen` | `view.AltScreen = true/false` | +| `tea.EnableMouseCellMotion` | `view.MouseMode = tea.MouseModeCellMotion` | +| `tea.EnableMouseAllMotion` | `view.MouseMode = tea.MouseModeAllMotion` | +| `tea.DisableMouse` | `view.MouseMode = tea.MouseModeNone` | +| `tea.HideCursor` / `tea.ShowCursor` | `view.Cursor = nil` / `view.Cursor = &tea.Cursor{...}` | +| `tea.EnableBracketedPaste` / `tea.DisableBracketedPaste` | `view.DisableBracketedPasteMode = false/true` | +| `tea.EnableReportFocus` / `tea.DisableReportFocus` | `view.ReportFocus = true/false` | +| `tea.SetWindowTitle("...")` | `view.WindowTitle = "..."` | + +### Removed Options (No Replacement Needed) + +| v1 Option | What Happened | +|---|---| +| `tea.WithInputTTY()` | v2 always opens the TTY for input automatically | +| `tea.WithANSICompressor()` | The new renderer handles optimization automatically | + +### Removed Program Methods + +| v1 Method | v2 Replacement | +|---|---| +| `p.Start()` | `p.Run()` | +| `p.StartReturningModel()` | `p.Run()` | +| `p.EnterAltScreen()` | `view.AltScreen = true` in `View()` | +| `p.ExitAltScreen()` | `view.AltScreen = false` in `View()` | +| `p.EnableMouseCellMotion()` | `view.MouseMode` in `View()` | +| `p.DisableMouseCellMotion()` | `view.MouseMode = tea.MouseModeNone` in `View()` | +| `p.EnableMouseAllMotion()` | `view.MouseMode` in `View()` | +| `p.DisableMouseAllMotion()` | `view.MouseMode = tea.MouseModeNone` in `View()` | +| `p.SetWindowTitle(...)` | `view.WindowTitle` in `View()` | + +### Other Renames + +| v1 | v2 | +|---|---| +| `tea.Sequentially(...)` | `tea.Sequence(...)` | +| `tea.WindowSize()` | `tea.RequestWindowSize` (now returns `Msg`, not `Cmd`) | + +### New Program Options + +| Option | Description | +|---|---| +| `tea.WithColorProfile(p)` | Force a specific color profile | +| `tea.WithWindowSize(w, h)` | Set initial window size (great for testing) | + +## Feedback + +Have thoughts on the v2 upgrade? We'd _love_ to hear about it. Let us know on… + +- [Discord](https://charm.land/chat) +- [Matrix](https://charm.land/matrix) +- [Email](mailto:vt100@charm.land) + +--- + +Part of [Charm](https://charm.land). + +The Charm logo + +Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة diff --git a/vendor/charm.land/bubbletea/v2/clipboard.go b/vendor/charm.land/bubbletea/v2/clipboard.go new file mode 100644 index 000000000..237a589ed --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/clipboard.go @@ -0,0 +1,70 @@ +package tea + +// ClipboardMsg is a clipboard read message event. This message is emitted when +// a terminal receives an OSC52 clipboard read message event. +type ClipboardMsg struct { + Content string + Selection byte +} + +// Clipboard returns the clipboard selection type. This will be one of the +// following values: +// +// - c: System clipboard. +// - p: Primary clipboard (X11/Wayland only). +func (e ClipboardMsg) Clipboard() byte { + return e.Selection +} + +// String returns the string representation of the clipboard message. +func (e ClipboardMsg) String() string { + return e.Content +} + +// setClipboardMsg is an internal message used to set the system clipboard +// using OSC52. +type setClipboardMsg string + +// SetClipboard produces a command that sets the system clipboard using OSC52. +// Note that OSC52 is not supported in all terminals. +func SetClipboard(s string) Cmd { + return func() Msg { + return setClipboardMsg(s) + } +} + +// readClipboardMsg is an internal message used to read the system clipboard +// using OSC52. +type readClipboardMsg struct{} + +// ReadClipboard produces a command that reads the system clipboard using OSC52. +// Note that OSC52 is not supported in all terminals. +func ReadClipboard() Msg { + return readClipboardMsg{} +} + +// setPrimaryClipboardMsg is an internal message used to set the primary +// clipboard using OSC52. +type setPrimaryClipboardMsg string + +// SetPrimaryClipboard produces a command that sets the primary clipboard using +// OSC52. Primary clipboard selection is a feature present in X11 and Wayland +// only. +// Note that OSC52 is not supported in all terminals. +func SetPrimaryClipboard(s string) Cmd { + return func() Msg { + return setPrimaryClipboardMsg(s) + } +} + +// readPrimaryClipboardMsg is an internal message used to read the primary +// clipboard using OSC52. +type readPrimaryClipboardMsg struct{} + +// ReadPrimaryClipboard produces a command that reads the primary clipboard +// using OSC52. Primary clipboard selection is a feature present in X11 and +// Wayland only. +// Note that OSC52 is not supported in all terminals. +func ReadPrimaryClipboard() Msg { + return readPrimaryClipboardMsg{} +} diff --git a/vendor/charm.land/bubbletea/v2/color.go b/vendor/charm.land/bubbletea/v2/color.go new file mode 100644 index 000000000..54514e4b3 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/color.go @@ -0,0 +1,91 @@ +package tea + +import ( + "image/color" + + uv "github.com/charmbracelet/ultraviolet" +) + +// backgroundColorMsg is a message that requests the terminal background color. +type backgroundColorMsg struct{} + +// RequestBackgroundColor is a command that requests the terminal background color. +func RequestBackgroundColor() Msg { + return backgroundColorMsg{} +} + +// foregroundColorMsg is a message that requests the terminal foreground color. +type foregroundColorMsg struct{} + +// RequestForegroundColor is a command that requests the terminal foreground color. +func RequestForegroundColor() Msg { + return foregroundColorMsg{} +} + +// cursorColorMsg is a message that requests the terminal cursor color. +type cursorColorMsg struct{} + +// RequestCursorColor is a command that requests the terminal cursor color. +func RequestCursorColor() Msg { + return cursorColorMsg{} +} + +// ForegroundColorMsg represents a foreground color message. This message is +// emitted when the program requests the terminal foreground color with the +// [RequestForegroundColor] Cmd. +type ForegroundColorMsg struct{ color.Color } + +// String returns the hex representation of the color. +func (e ForegroundColorMsg) String() string { + return uv.ForegroundColorEvent(e).String() +} + +// IsDark returns whether the color is dark. +func (e ForegroundColorMsg) IsDark() bool { + return uv.ForegroundColorEvent(e).IsDark() +} + +// BackgroundColorMsg represents a background color message. This message is +// emitted when the program requests the terminal background color with the +// [RequestBackgroundColor] Cmd. +// +// This is commonly used in [Update.Init] to get the terminal background color +// for style definitions. For that you'll want to call +// [BackgroundColorMsg.IsDark] to determine if the color is dark or light. For +// example: +// +// func (m Model) Init() (Model, Cmd) { +// return m, RequestBackgroundColor() +// } +// +// func (m Model) Update(msg Msg) (Model, Cmd) { +// switch msg := msg.(type) { +// case BackgroundColorMsg: +// m.styles = newStyles(msg.IsDark()) +// } +// } +type BackgroundColorMsg struct{ color.Color } + +// String returns the hex representation of the color. +func (e BackgroundColorMsg) String() string { + return uv.BackgroundColorEvent(e).String() +} + +// IsDark returns whether the color is dark. +func (e BackgroundColorMsg) IsDark() bool { + return uv.BackgroundColorEvent(e).IsDark() +} + +// CursorColorMsg represents a cursor color change message. This message is +// emitted when the program requests the terminal cursor color. +type CursorColorMsg struct{ color.Color } + +// String returns the hex representation of the color. +func (e CursorColorMsg) String() string { + return uv.CursorColorEvent(e).String() +} + +// IsDark returns whether the color is dark. +func (e CursorColorMsg) IsDark() bool { + return uv.CursorColorEvent(e).IsDark() +} diff --git a/vendor/charm.land/bubbletea/v2/commands.go b/vendor/charm.land/bubbletea/v2/commands.go new file mode 100644 index 000000000..4497c3a38 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/commands.go @@ -0,0 +1,175 @@ +package tea + +import ( + "time" +) + +// Batch performs a bunch of commands concurrently with no ordering guarantees +// about the results. Use a Batch to return several commands. +// +// Example: +// +// func (m model) Init() (Model, Cmd) { +// return m, tea.Batch(someCommand, someOtherCommand) +// } +func Batch(cmds ...Cmd) Cmd { + return compactCmds[BatchMsg](cmds) +} + +// BatchMsg is a message used to perform a bunch of commands concurrently with +// no ordering guarantees. You can send a BatchMsg with Batch. +type BatchMsg []Cmd + +// Sequence runs the given commands one at a time, in order. Contrast this with +// Batch, which runs commands concurrently. +func Sequence(cmds ...Cmd) Cmd { + return compactCmds[sequenceMsg](cmds) +} + +// sequenceMsg is used internally to run the given commands in order. +type sequenceMsg []Cmd + +// compactCmds ignores any nil commands in cmds, and returns the most direct +// command possible. That is, considering the non-nil commands, if there are +// none it returns nil, if there is exactly one it returns that command +// directly, else it returns the non-nil commands as type T. +func compactCmds[T ~[]Cmd](cmds []Cmd) Cmd { + var validCmds []Cmd + for _, c := range cmds { + if c == nil { + continue + } + validCmds = append(validCmds, c) + } + switch len(validCmds) { + case 0: + return nil + case 1: + return validCmds[0] + default: + return func() Msg { + return T(validCmds) + } + } +} + +// Every is a command that ticks in sync with the system clock. So, if you +// wanted to tick with the system clock every second, minute or hour you +// could use this. It's also handy for having different things tick in sync. +// +// Because we're ticking with the system clock the tick will likely not run for +// the entire specified duration. For example, if we're ticking for one minute +// and the clock is at 12:34:20 then the next tick will happen at 12:35:00, 40 +// seconds later. +// +// To produce the command, pass a duration and a function which returns +// a message containing the time at which the tick occurred. +// +// type TickMsg time.Time +// +// cmd := Every(time.Second, func(t time.Time) Msg { +// return TickMsg(t) +// }) +// +// Beginners' note: Every sends a single message and won't automatically +// dispatch messages at an interval. To do that, you'll want to return another +// Every command after receiving your tick message. For example: +// +// type TickMsg time.Time +// +// // Send a message every second. +// func tickEvery() Cmd { +// return Every(time.Second, func(t time.Time) Msg { +// return TickMsg(t) +// }) +// } +// +// func (m model) Init() (Model, Cmd) { +// // Start ticking. +// return m, tickEvery() +// } +// +// func (m model) Update(msg Msg) (Model, Cmd) { +// switch msg.(type) { +// case TickMsg: +// // Return your Every command again to loop. +// return m, tickEvery() +// } +// return m, nil +// } +// +// Every is analogous to Tick in the Elm Architecture. +func Every(duration time.Duration, fn func(time.Time) Msg) Cmd { + n := time.Now() + d := n.Truncate(duration).Add(duration).Sub(n) + t := time.NewTimer(d) + return func() Msg { + ts := <-t.C + t.Stop() + for len(t.C) > 0 { + <-t.C + } + return fn(ts) + } +} + +// Tick produces a command at an interval independent of the system clock at +// the given duration. That is, the timer begins precisely when invoked, +// and runs for its entire duration. +// +// To produce the command, pass a duration and a function which returns +// a message containing the time at which the tick occurred. +// +// type TickMsg time.Time +// +// cmd := Tick(time.Second, func(t time.Time) Msg { +// return TickMsg(t) +// }) +// +// Beginners' note: Tick sends a single message and won't automatically +// dispatch messages at an interval. To do that, you'll want to return another +// Tick command after receiving your tick message. For example: +// +// type TickMsg time.Time +// +// func doTick() Cmd { +// return Tick(time.Second, func(t time.Time) Msg { +// return TickMsg(t) +// }) +// } +// +// func (m model) Init() (Model, Cmd) { +// // Start ticking. +// return m, doTick() +// } +// +// func (m model) Update(msg Msg) (Model, Cmd) { +// switch msg.(type) { +// case TickMsg: +// // Return your Tick command again to loop. +// return m, doTick() +// } +// return m, nil +// } +func Tick(d time.Duration, fn func(time.Time) Msg) Cmd { + t := time.NewTimer(d) + return func() Msg { + ts := <-t.C + t.Stop() + for len(t.C) > 0 { + <-t.C + } + return fn(ts) + } +} + +type windowSizeMsg struct{} + +// RequestWindowSize is a command that queries the terminal for its current +// size. It delivers the results to Update via a [WindowSizeMsg]. Keep in mind +// that WindowSizeMsgs will automatically be delivered to Update when the +// [Program] starts and when the window dimensions change so in many cases you +// will not need to explicitly invoke this command. +func RequestWindowSize() Msg { + return windowSizeMsg{} +} diff --git a/vendor/charm.land/bubbletea/v2/cursed_renderer.go b/vendor/charm.land/bubbletea/v2/cursed_renderer.go new file mode 100644 index 000000000..8eaafb23d --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/cursed_renderer.go @@ -0,0 +1,854 @@ +package tea + +import ( + "bytes" + "fmt" + "image/color" + "io" + "runtime" + "strings" + "sync" + + "github.com/charmbracelet/colorprofile" + uv "github.com/charmbracelet/ultraviolet" + "github.com/charmbracelet/x/ansi" + "github.com/lucasb-eyer/go-colorful" +) + +type cursedRenderer struct { + w io.Writer + buf bytes.Buffer // updates buffer to be flushed to [w] + scr *uv.TerminalRenderer + cellbuf uv.ScreenBuffer + lastView *View + env []string + term string // the terminal type $TERM + width, height int + mu sync.Mutex + profile colorprofile.Profile + logger uv.Logger + view View + hardTabs bool // whether to use hard tabs to optimize cursor movements + backspace bool // whether to use backspace to optimize cursor movements + mapnl bool + syncdUpdates bool // whether to use synchronized output mode for updates + starting bool // indicates whether the renderer is starting after being stopped +} + +var _ renderer = &cursedRenderer{} + +func newCursedRenderer(w io.Writer, env []string, width, height int) (s *cursedRenderer) { + s = new(cursedRenderer) + s.w = w + s.env = env + s.term = uv.Environ(env).Getenv("TERM") + s.width, s.height = width, height // This needs to happen before [cursedRenderer.reset]. + s.cellbuf = uv.NewScreenBuffer(s.width, s.height) + reset(s) + return +} + +// setLogger sets the logger for the renderer. +func (s *cursedRenderer) setLogger(logger uv.Logger) { + s.mu.Lock() + s.logger = logger + s.mu.Unlock() +} + +// setOptimizations sets the cursor movement optimizations. +func (s *cursedRenderer) setOptimizations(hardTabs, backspace, mapnl bool) { + s.mu.Lock() + s.hardTabs = hardTabs + s.backspace = backspace + s.mapnl = mapnl + if s.hardTabs { + s.scr.SetTabStops(s.width) + } else { + s.scr.SetTabStops(-1) + } + s.scr.SetBackspace(s.backspace) + s.scr.SetMapNewline(s.mapnl) + s.mu.Unlock() +} + +// start implements renderer. +func (s *cursedRenderer) start() { + s.mu.Lock() + defer s.mu.Unlock() + + // Mark that we're starting. This is used to restore some state when + // starting the renderer again after it was stopped. + s.starting = true + + if s.lastView == nil { + return + } + + if s.lastView.AltScreen { + enableAltScreen(s, true, true) + } + enableTextCursor(s, s.lastView.Cursor != nil) + if s.lastView.Cursor != nil { + if s.lastView.Cursor.Color != nil { + col, ok := colorful.MakeColor(s.lastView.Cursor.Color) + if ok { + _, _ = s.scr.WriteString(ansi.SetCursorColor(col.Hex())) + } + } + curStyle := encodeCursorStyle(s.lastView.Cursor.Shape, s.lastView.Cursor.Blink) + if curStyle != 0 && curStyle != 1 { + _, _ = s.scr.WriteString(ansi.SetCursorStyle(curStyle)) + } + } + if s.lastView.ForegroundColor != nil { + col, ok := colorful.MakeColor(s.lastView.ForegroundColor) + if ok { + _, _ = s.scr.WriteString(ansi.SetForegroundColor(col.Hex())) + } + } + if s.lastView.BackgroundColor != nil { + col, ok := colorful.MakeColor(s.lastView.BackgroundColor) + if ok { + _, _ = s.scr.WriteString(ansi.SetBackgroundColor(col.Hex())) + } + } + if !s.lastView.DisableBracketedPasteMode { + _, _ = s.scr.WriteString(ansi.SetModeBracketedPaste) + } + if s.lastView.ReportFocus { + _, _ = s.scr.WriteString(ansi.SetModeFocusEvent) + } + switch s.lastView.MouseMode { + case MouseModeNone: + case MouseModeCellMotion: + _, _ = s.scr.WriteString(ansi.SetModeMouseButtonEvent + ansi.SetModeMouseExtSgr) + case MouseModeAllMotion: + _, _ = s.scr.WriteString(ansi.SetModeMouseAnyEvent + ansi.SetModeMouseExtSgr) + } + if s.lastView.WindowTitle != "" { + _, _ = s.scr.WriteString(ansi.SetWindowTitle(s.lastView.WindowTitle)) + } + if s.lastView.ProgressBar != nil { + setProgressBar(s, s.lastView.ProgressBar) + } + // Enable modifyOtherKeys and Kitty keyboard protocol. + // Both can coexist; terminals ignore what they don't support. + _, _ = s.scr.WriteString(ansi.SetModifyOtherKeys2) + + kittyFlags := keyboardEnhancementsFlags(s.lastView.KeyboardEnhancements) + _, _ = s.scr.WriteString(ansi.KittyKeyboard(kittyFlags, 1)) +} + +// close implements renderer. +func (s *cursedRenderer) close() (err error) { + s.mu.Lock() + defer s.mu.Unlock() + + // Exit the altScreen and show cursor before closing. It's important that + // we don't change the [cursedRenderer] altScreen and cursorHidden states + // so that we can restore them when we start the renderer again. This is + // used when the user suspends the program and then resumes it. + if lv := s.lastView; lv != nil { //nolint:nestif + // NOTE: The Kitty keyboard specs specify that the terminal should have + // two registries for the main and alt screens. We disable keyboard + // enhancements whenever we enter/exit alt screen mode in + // [cursedRenderer.flush]. + // Here, we reset the keyboard protocol of the last screen used + // assuming the other screen is already reset when we switched screens. + _, _ = s.buf.WriteString(ansi.ResetModifyOtherKeys) + _, _ = s.buf.WriteString(ansi.KittyKeyboard(0, 1)) + + // Go to the bottom of the screen. + // We need to go to the bottom of the screen regardless of whether + // we're in alt screen mode or not to avoid leaving the cursor in the + // middle in terminals that don't support alt screen mode. + s.scr.MoveTo(0, s.cellbuf.Height()-1) + _ = s.scr.Flush() // we need to flush to write the cursor movement + if lv.AltScreen { + enableAltScreen(s, false, true) + } else { + _, _ = s.scr.WriteString(ansi.EraseScreenBelow) + } + if lv.Cursor == nil { + enableTextCursor(s, true) + } + if !lv.DisableBracketedPasteMode { + _, _ = s.scr.WriteString(ansi.ResetModeBracketedPaste) + } + if lv.ReportFocus { + _, _ = s.scr.WriteString(ansi.ResetModeFocusEvent) + } + switch lv.MouseMode { + case MouseModeNone: + case MouseModeCellMotion, MouseModeAllMotion: + _, _ = s.scr.WriteString(ansi.ResetModeMouseButtonEvent + + ansi.ResetModeMouseAnyEvent + + ansi.ResetModeMouseExtSgr) + } + + if lv.WindowTitle != "" { + // Clear the window title if it was set. + _, _ = s.scr.WriteString(ansi.SetWindowTitle("")) + } + if lc := lv.Cursor; lc != nil { + curShape := encodeCursorStyle(lc.Shape, lc.Blink) + if curShape != 0 && curShape != 1 { + // Reset the cursor style to default if it was set to something other + // blinking block. + _, _ = s.scr.WriteString(ansi.SetCursorStyle(0)) + } + + if lc.Color != nil { + _, _ = s.scr.WriteString(ansi.ResetCursorColor) + } + } + + if lv.BackgroundColor != nil { + _, _ = s.scr.WriteString(ansi.ResetBackgroundColor) + } + if lv.ForegroundColor != nil { + _, _ = s.scr.WriteString(ansi.ResetForegroundColor) + } + if lv.ProgressBar != nil && lv.ProgressBar.State != ProgressBarNone { + _, _ = s.scr.WriteString(ansi.ResetProgressBar) + } + } + + if s.cellbuf.Method == ansi.GraphemeWidth { + // Make sure to turn off Unicode mode (2027) + _, _ = s.scr.WriteString(ansi.ResetModeUnicodeCore) + } + + if err := s.scr.Flush(); err != nil { + return fmt.Errorf("bubbletea: error closing screen writer: %w", err) + } + + if s.buf.Len() > 0 { + if s.logger != nil { + s.logger.Printf("output: %q", s.buf.String()) + } + if _, err := io.Copy(s.w, &s.buf); err != nil { + return fmt.Errorf("bubbletea: error writing to screen: %w", err) + } + s.buf.Reset() + } + + x, y := s.scr.Position() + + // We want to clear the renderer state but not the cursor position. This is + // because we might be putting the tea process in the background, run some + // other process, and then return to the tea process. We want to keep the + // cursor position so that we can continue where we left off. + reset(s) + s.scr.SetPosition(x, y) + + return nil +} + +// writeString implements renderer. +func (s *cursedRenderer) writeString(str string) (int, error) { + s.mu.Lock() + defer s.mu.Unlock() + + return s.scr.WriteString(str) //nolint:wrapcheck +} + +// flush implements renderer. +func (s *cursedRenderer) flush(closing bool) error { + s.mu.Lock() + defer s.mu.Unlock() + + view := s.view + frameArea := uv.Rect(0, 0, s.width, s.height) + if len(view.Content) == 0 { + // If the component is nil, we should clear the screen buffer. + frameArea.Max.Y = 0 + } + + content := uv.NewStyledString(view.Content) + if !view.AltScreen { + // We need to resizes the screen based on the frame height and + // terminal width. This is because the frame height can change based on + // the content of the frame. For example, if the frame contains a list + // of items, the height of the frame will be the number of items in the + // list. This is different from the alt screen buffer, which has a + // fixed height and width. + frameHeight := content.Height() + if frameHeight != frameArea.Dy() { + frameArea.Max.Y = frameHeight + } + } + + // Restore tab stops if we have tab optimizations enabled. + if s.starting && s.hardTabs { + _, _ = s.scr.WriteString(ansi.SetTabEvery8Columns) + } + + if !s.starting && !closing && s.lastView != nil && viewEquals(s.lastView, &view) && frameArea == s.cellbuf.Bounds() { + // No changes, nothing to do. + return nil + } + + // We're no longer starting. + s.starting = false + + if frameArea != s.cellbuf.Bounds() { + s.scr.Erase() // Force a full redraw to avoid artifacts. + + // We need to reset the touched lines buffer to match the new height. + s.cellbuf.Touched = nil + + // Resize the screen buffer to match the frame area. This is necessary + // to ensure that the screen buffer is the same size as the frame area + // and to avoid rendering issues when the frame area is smaller than + // the screen buffer. + s.cellbuf.Resize(frameArea.Dx(), frameArea.Dy()) + } + + // Clear our screen buffer before copying the new frame into it to ensure + // we erase any old content. + s.cellbuf.Clear() + content.Draw(s.cellbuf, s.cellbuf.Bounds()) + + // If the frame height is greater than the screen height, we drop the + // lines from the top of the buffer. + if frameHeight := frameArea.Dy(); frameHeight > s.height { + s.cellbuf.Lines = s.cellbuf.Lines[frameHeight-s.height:] + } + + // Alt screen mode. + shouldUpdateAltScreen := (s.lastView == nil && view.AltScreen) || (s.lastView != nil && s.lastView.AltScreen != view.AltScreen) + if shouldUpdateAltScreen { + // We want to enter/exit altscreen mode but defer writing the actual + // sequences until we flush the rest of the updates. This is because we + // control the cursor visibility and we need to ensure that happens + // after entering/exiting alt screen mode. Some terminals have + // different cursor visibility states for main and alt screen modes and + // this ensures we handle that correctly. + enableAltScreen(s, view.AltScreen, false) + } + + // bracketed paste mode. + if s.lastView == nil || view.DisableBracketedPasteMode != s.lastView.DisableBracketedPasteMode { + if !view.DisableBracketedPasteMode { + _, _ = s.scr.WriteString(ansi.SetModeBracketedPaste) + } else if s.lastView != nil { + _, _ = s.scr.WriteString(ansi.ResetModeBracketedPaste) + } + } + + // report focus events mode. + if s.lastView == nil || s.lastView.ReportFocus != view.ReportFocus { + if view.ReportFocus { + _, _ = s.scr.WriteString(ansi.SetModeFocusEvent) + } else if s.lastView != nil { + _, _ = s.scr.WriteString(ansi.ResetModeFocusEvent) + } + } + + // mouse events mode. + if s.lastView == nil || view.MouseMode != s.lastView.MouseMode { + switch view.MouseMode { + case MouseModeNone: + if s.lastView != nil && s.lastView.MouseMode != MouseModeNone { + _, _ = s.scr.WriteString(ansi.ResetModeMouseButtonEvent + + ansi.ResetModeMouseAnyEvent + + ansi.ResetModeMouseExtSgr) + } + case MouseModeCellMotion: + if s.lastView != nil && s.lastView.MouseMode == MouseModeAllMotion { + _, _ = s.scr.WriteString(ansi.ResetModeMouseAnyEvent) + } + _, _ = s.scr.WriteString(ansi.SetModeMouseButtonEvent + ansi.SetModeMouseExtSgr) + case MouseModeAllMotion: + if s.lastView != nil && s.lastView.MouseMode == MouseModeCellMotion { + _, _ = s.scr.WriteString(ansi.ResetModeMouseButtonEvent) + } + _, _ = s.scr.WriteString(ansi.SetModeMouseAnyEvent + ansi.SetModeMouseExtSgr) + } + } + + // Set window title. + if s.lastView == nil || view.WindowTitle != s.lastView.WindowTitle { + if s.lastView != nil || view.WindowTitle != "" { + _, _ = s.scr.WriteString(ansi.SetWindowTitle(view.WindowTitle)) + } + } + + // kitty keyboard protocol + if s.lastView == nil || view.KeyboardEnhancements != s.lastView.KeyboardEnhancements || + view.AltScreen != s.lastView.AltScreen { + // NOTE: We need to reset the keyboard protocol when switching + // between main and alt screen. This is because the specs specify + // two different states for the main and alt screen. + + // Enable modifyOtherKeys and Kitty keyboard protocol. + _, _ = s.scr.WriteString(ansi.SetModifyOtherKeys2) + + kittyFlags := keyboardEnhancementsFlags(view.KeyboardEnhancements) + _, _ = s.scr.WriteString(ansi.KittyKeyboard(kittyFlags, 1)) + if !closing { + // Request keyboard enhancements when they change + _, _ = s.scr.WriteString(ansi.RequestKittyKeyboard) + } + } + + // Set terminal colors. + var ( + cc, lcc color.Color + lfg, lbg color.Color + ) + if view.Cursor != nil { + cc = view.Cursor.Color + } + if s.lastView != nil { + if s.lastView.Cursor != nil { + lcc = s.lastView.Cursor.Color + } + lfg = s.lastView.ForegroundColor + lbg = s.lastView.BackgroundColor + } + for _, c := range []struct { + newColor color.Color + oldColor color.Color + reset string + setter func(string) string + }{ + {newColor: cc, oldColor: lcc, reset: ansi.ResetCursorColor, setter: ansi.SetCursorColor}, + {newColor: view.ForegroundColor, oldColor: lfg, reset: ansi.ResetForegroundColor, setter: ansi.SetForegroundColor}, + {newColor: view.BackgroundColor, oldColor: lbg, reset: ansi.ResetBackgroundColor, setter: ansi.SetBackgroundColor}, + } { + if c.newColor != c.oldColor { + if c.newColor == nil { + // Reset the color if it was set to nil. + _, _ = s.scr.WriteString(c.reset) + } else { + // Set the color. + col, ok := colorful.MakeColor(c.newColor) + if ok { + _, _ = s.scr.WriteString(c.setter(col.Hex())) + } + } + } + } + + // Set cursor shape and blink if set. + var ccStyle, lcStyle int + var lcur *Cursor + ccur := view.Cursor + if lv := s.lastView; lv != nil { + lcur = lv.Cursor + } + if ccur != nil { + ccStyle = encodeCursorStyle(ccur.Shape, ccur.Blink) + } + if lcur != nil { + lcStyle = encodeCursorStyle(lcur.Shape, lcur.Blink) + } + if ccStyle != lcStyle { + _, _ = s.scr.WriteString(ansi.SetCursorStyle(ccStyle)) + } + + // Render progress bar if it's changed. + if (s.lastView == nil && view.ProgressBar != nil && view.ProgressBar.State != ProgressBarNone) || + (s.lastView != nil && (s.lastView.ProgressBar == nil) != (view.ProgressBar == nil)) || + (s.lastView != nil && s.lastView.ProgressBar != nil && view.ProgressBar != nil && *s.lastView.ProgressBar != *view.ProgressBar) { + // Render or clear the progress bar if it was added or removed. + setProgressBar(s, view.ProgressBar) + } + + // Render and queue changes to the screen buffer. + s.scr.Render(s.cellbuf.RenderBuffer) + + if cur := view.Cursor; cur != nil { + // MoveTo must come after [uv.TerminalRenderer.Render] because the + // cursor position might get updated during rendering. + s.scr.MoveTo(view.Cursor.X, view.Cursor.Y) + } else if !view.AltScreen { + // We don't want the cursor to be dangling at the end of the line in + // inline mode because it can cause unwanted line wraps in some + // terminals. So we move it to the beginning of the next line if + // necessary. + // This is only needed when the cursor is hidden because when it's + // visible, we already set its position above. + x, y := s.scr.Position() + if x >= s.width-1 { + s.scr.MoveTo(0, y) + } + } + + if err := s.scr.Flush(); err != nil { + return fmt.Errorf("bubbletea: error flushing screen writer: %w", err) + } + + // Check if we have any render updates to flush. + hasUpdates := s.buf.Len() > 0 + + // Cursor visibility. + didShowCursor := s.lastView != nil && s.lastView.Cursor != nil + showCursor := view.Cursor != nil + hideCursor := !showCursor + shouldUpdateCursorVis := (s.lastView == nil || didShowCursor != showCursor) || shouldUpdateAltScreen + + // Build final output buffer with synchronized output or hide/show cursor + // updates. But first, enter/exit alt screen mode if needed. + // + // Here, we have two scenarios: + // 1. Synchronized output updates are supported. In this case, we want to + // wrap all updates, unless it's just a cursor visibility change, in + // synchronized output mode. This is because synchronized output mode + // takes care of rendering the updates atomically. In the case of + // just a cursor visibility change, we don't need to enter + // synchronized output mode because it's just a single sequence to + // flush out to the terminal. + // + // 2. We don't have synchronized output updates support. In this case, and + // if the cursor is visible or should be visible, we wrap the updates + // with hide/show cursor sequences to try and mitigate cursor + // flickering. This is terminal dependent and may still result in + // flickering in some terminals. It's the best effort we can do instead + // of showing the cursor flying around the screen during updates. + + var buf bytes.Buffer + if shouldUpdateAltScreen { + // We always disable keyboard enhancements when switching screens + // because the terminal is expected to have two different keyboard + // registries for main and alt screens. + _, _ = buf.WriteString(ansi.ResetModifyOtherKeys) + _, _ = buf.WriteString(ansi.KittyKeyboard(0, 1)) + if view.AltScreen { + // Entering alt screen mode. + buf.WriteString(ansi.SetModeAltScreenSaveCursor) + } else { + // Exiting alt screen mode. + buf.WriteString(ansi.ResetModeAltScreenSaveCursor) + } + } + + if s.syncdUpdates { + if hasUpdates { + // We have synchronized output updates enabled. + buf.WriteString(ansi.SetModeSynchronizedOutput) + } + if shouldUpdateCursorVis && hideCursor { + // Do we need to update the cursor visibility to hidden? If so, do + // it here before writing any updates to the buffer. + _, _ = buf.WriteString(ansi.ResetModeTextCursorEnable) + } + } else if (shouldUpdateCursorVis && hideCursor) || (hasUpdates && showCursor && didShowCursor) { + _, _ = buf.WriteString(ansi.ResetModeTextCursorEnable) + } + + if hasUpdates { + buf.Write(s.buf.Bytes()) + } + + if s.syncdUpdates { + if shouldUpdateCursorVis && showCursor { + // Do we need to update the cursor visibility to visible? If so, do + // it here after writing any updates to the buffer. + _, _ = buf.WriteString(ansi.SetModeTextCursorEnable) + } + if hasUpdates { + // Close synchronized output mode. + buf.WriteString(ansi.ResetModeSynchronizedOutput) + } + } else if (shouldUpdateCursorVis && showCursor) || (hasUpdates && showCursor && didShowCursor) { + _, _ = buf.WriteString(ansi.SetModeTextCursorEnable) + } + + // Reset internal screen renderer buffer. + s.buf.Reset() + + // If our updates flush buffer has content, write it to the output writer. + if buf.Len() > 0 { + if s.logger != nil { + s.logger.Printf("output: %q", buf.String()) + } + if _, err := io.Copy(s.w, &buf); err != nil { + return fmt.Errorf("bubbletea: error flushing update to the writer: %w", err) + } + } + + s.lastView = &view + + return nil +} + +// render implements renderer. +func (s *cursedRenderer) render(v View) { + s.mu.Lock() + defer s.mu.Unlock() + + s.view = v +} + +// reset implements renderer. +func (s *cursedRenderer) reset() { + s.mu.Lock() + reset(s) + s.mu.Unlock() +} + +func reset(s *cursedRenderer) { + s.buf.Reset() + scr := uv.NewTerminalRenderer(&s.buf, s.env) + scr.SetColorProfile(s.profile) + scr.SetRelativeCursor(true) // Always start in inline mode + scr.SetFullscreen(false) // Always start in inline mode + if s.hardTabs { + scr.SetTabStops(s.width) + } else { + scr.SetTabStops(-1) + } + scr.SetBackspace(s.backspace) + scr.SetMapNewline(s.mapnl) + scr.SetScrollOptim(runtime.GOOS != "windows") // disable scroll optimization on Windows due to bugs in some terminals + s.scr = scr +} + +// setColorProfile implements renderer. +func (s *cursedRenderer) setColorProfile(p colorprofile.Profile) { + s.mu.Lock() + s.profile = p + s.scr.SetColorProfile(p) + s.mu.Unlock() +} + +// resize implements renderer. +func (s *cursedRenderer) resize(w, h int) { + s.mu.Lock() + // We need to mark the screen for clear to force a redraw. However, we + // only do so if we're using alt screen or the width has changed. + // That's because redrawing is expensive and we can avoid it if the + // width hasn't changed in inline mode. On the other hand, when using + // alt screen mode, we always want to redraw because some terminals + // would scroll the screen and our content would be lost. + s.scr.Erase() + s.width, s.height = w, h + s.scr.Resize(s.width, s.height) + s.mu.Unlock() +} + +// clearScreen implements renderer. +func (s *cursedRenderer) clearScreen() { + s.mu.Lock() + // Move the cursor to the top left corner of the screen and trigger a full + // screen redraw. + s.scr.MoveTo(0, 0) + s.scr.Erase() + s.mu.Unlock() +} + +// enableAltScreen sets the alt screen mode. +// Note that this writes to the buffer directly if write is true. +func enableAltScreen(s *cursedRenderer, enable bool, write bool) { + if enable { + enterAltScreen(s, write) + } else { + exitAltScreen(s, write) + } +} + +func enterAltScreen(s *cursedRenderer, write bool) { + s.scr.SaveCursor() + if write { + s.buf.WriteString(ansi.SetModeAltScreenSaveCursor) + } + s.scr.SetFullscreen(true) + s.scr.SetRelativeCursor(false) + s.scr.Erase() +} + +func exitAltScreen(s *cursedRenderer, write bool) { + s.scr.Erase() + s.scr.SetRelativeCursor(true) + s.scr.SetFullscreen(false) + if write { + s.buf.WriteString(ansi.ResetModeAltScreenSaveCursor) + } + s.scr.RestoreCursor() +} + +// enableTextCursor sets the text cursor mode. +func enableTextCursor(s *cursedRenderer, enable bool) { + if enable { + _, _ = s.scr.WriteString(ansi.SetModeTextCursorEnable) + } else { + _, _ = s.scr.WriteString(ansi.ResetModeTextCursorEnable) + } +} + +// setSyncdUpdates implements renderer. +func (s *cursedRenderer) setSyncdUpdates(syncd bool) { + s.mu.Lock() + s.syncdUpdates = syncd + s.mu.Unlock() +} + +// setWidthMethod implements renderer. +func (s *cursedRenderer) setWidthMethod(method ansi.Method) { + s.mu.Lock() + if method == ansi.GraphemeWidth { + // Turn on Unicode mode (2027) for accurate grapheme width calculation. + // This is needed for proper rendering of wide characters and emojis. + _, _ = s.scr.WriteString(ansi.SetModeUnicodeCore) + } else if s.cellbuf.Method == ansi.GraphemeWidth { + // Turn off Unicode mode if we're switching away from grapheme width + // calculation to avoid issues with some terminals that might still be + // in Unicode mode and render characters incorrectly. + _, _ = s.scr.WriteString(ansi.ResetModeUnicodeCore) + } + s.cellbuf.Method = method + s.mu.Unlock() +} + +// insertAbove implements renderer. +func (s *cursedRenderer) insertAbove(str string) error { + s.mu.Lock() + defer s.mu.Unlock() + + if len(str) == 0 { + return nil + } + + var sb strings.Builder + w, h := s.cellbuf.Width(), s.cellbuf.Height() + _, y := s.scr.Position() + + // We need to scroll the screen up by the number of lines in the queue. + sb.WriteByte('\r') + down := h - y - 1 + if down > 0 { + sb.WriteString(ansi.CursorDown(down)) + } + + lines := strings.Split(str, "\n") + offset := len(lines) + for _, line := range lines { + lineWidth := ansi.StringWidth(line) + if w > 0 && lineWidth > w { + offset += (lineWidth / w) + } + } + + // Scroll the screen up by the offset to make room for the new lines. + sb.WriteString(strings.Repeat("\n", offset)) + + // XXX: Now go to the top of the screen, insert new lines, and write + // the queued strings. It is important to use [Screen.moveCursor] + // instead of [Screen.move] because we don't want to perform any checks + // on the cursor position. + up := offset + h - 1 + sb.WriteString(ansi.CursorUp(up)) + sb.WriteString(ansi.InsertLine(offset)) + for _, line := range lines { + sb.WriteString(line) + sb.WriteString(ansi.EraseLineRight) + sb.WriteString("\r\n") + } + + s.scr.SetPosition(0, 0) + + if s.logger != nil { + s.logger.Printf("insert above: %q", sb.String()) + } + + _, err := io.WriteString(s.w, sb.String()) + if err != nil { + return fmt.Errorf("bubbletea: error writing insert above to the writer: %w", err) + } + + return nil +} + +// onMouse implements renderer. +func (s *cursedRenderer) onMouse(m MouseMsg) Cmd { + if s.lastView != nil && s.lastView.OnMouse != nil { + return s.lastView.OnMouse(m) + } + return nil +} + +func setProgressBar(s *cursedRenderer, pb *ProgressBar) { + if pb == nil { + _, _ = s.scr.WriteString(ansi.ResetProgressBar) + return + } + + var seq string + switch pb.State { + case ProgressBarNone: + seq = ansi.ResetProgressBar + case ProgressBarDefault: + seq = ansi.SetProgressBar(pb.Value) + case ProgressBarError: + seq = ansi.SetErrorProgressBar(pb.Value) + case ProgressBarIndeterminate: + seq = ansi.SetIndeterminateProgressBar + case ProgressBarWarning: + seq = ansi.SetWarningProgressBar(pb.Value) + } + if seq != "" { + _, _ = s.scr.WriteString(seq) + } +} + +func viewEquals(a, b *View) bool { + if a == nil || b == nil { + return false + } + + if a.Content != b.Content || + a.AltScreen != b.AltScreen || + a.DisableBracketedPasteMode != b.DisableBracketedPasteMode || + a.ReportFocus != b.ReportFocus || + a.MouseMode != b.MouseMode || + a.WindowTitle != b.WindowTitle || + a.ForegroundColor != b.ForegroundColor || + a.BackgroundColor != b.BackgroundColor || + a.KeyboardEnhancements != b.KeyboardEnhancements { + return false + } + + if (a.Cursor == nil) != (b.Cursor == nil) { + return false + } + if a.Cursor != nil && b.Cursor != nil { + if a.Cursor.X != b.Cursor.X || + a.Cursor.Y != b.Cursor.Y || + a.Cursor.Shape != b.Cursor.Shape || + a.Cursor.Blink != b.Cursor.Blink || + a.Cursor.Color != b.Cursor.Color { + return false + } + } + + if (a.ProgressBar == nil) != (b.ProgressBar == nil) { + return false + } + if a.ProgressBar != nil && b.ProgressBar != nil { + if *a.ProgressBar != *b.ProgressBar { + return false + } + } + + return true +} + +func keyboardEnhancementsFlags(ke KeyboardEnhancements) int { + flags := 1 // always enable basic key disambiguation + if ke.ReportEventTypes { + flags |= ansi.KittyReportEventTypes + } + if ke.ReportAlternateKeys { + flags |= ansi.KittyReportAlternateKeys + } + if ke.ReportAllKeysAsEscapeCodes { + flags |= ansi.KittyReportAllKeysAsEscapeCodes + } + if ke.ReportAssociatedText { + flags |= ansi.KittyReportAssociatedKeys + } + return flags +} diff --git a/vendor/charm.land/bubbletea/v2/cursor.go b/vendor/charm.land/bubbletea/v2/cursor.go new file mode 100644 index 000000000..3375a9652 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/cursor.go @@ -0,0 +1,28 @@ +package tea + +// Position represents a position in the terminal. +type Position struct{ X, Y int } + +// CursorPositionMsg is a message that represents the terminal cursor position. +type CursorPositionMsg struct { + X, Y int +} + +// CursorShape represents a terminal cursor shape. +type CursorShape int + +// Cursor shapes. +const ( + CursorBlock CursorShape = iota + CursorUnderline + CursorBar +) + +// requestCursorPosMsg is a message that requests the cursor position. +type requestCursorPosMsg struct{} + +// RequestCursorPosition is a command that requests the cursor position. +// The cursor position will be sent as a [CursorPositionMsg] message. +func RequestCursorPosition() Msg { + return requestCursorPosMsg{} +} diff --git a/vendor/charm.land/bubbletea/v2/environ.go b/vendor/charm.land/bubbletea/v2/environ.go new file mode 100644 index 000000000..8217b02f4 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/environ.go @@ -0,0 +1,34 @@ +package tea + +import uv "github.com/charmbracelet/ultraviolet" + +// EnvMsg is a message that represents the environment variables of the +// program. This is useful for getting the environment variables of programs +// running in a remote session like SSH. In that case, using [os.Getenv] would +// return the server's environment variables, not the client's. +// +// This message is sent to the program when it starts. +// +// Example: +// +// switch msg := msg.(type) { +// case EnvMsg: +// // What terminal type is being used? +// term := msg.Getenv("TERM") +// } +type EnvMsg uv.Environ + +// Getenv returns the value of the environment variable named by the key. If +// the variable is not present in the environment, the value returned will be +// the empty string. +func (msg EnvMsg) Getenv(key string) (v string) { + return uv.Environ(msg).Getenv(key) +} + +// LookupEnv retrieves the value of the environment variable named by the key. +// If the variable is present in the environment the value (which may be empty) +// is returned and the boolean is true. Otherwise the returned value will be +// empty and the boolean will be false. +func (msg EnvMsg) LookupEnv(key string) (s string, v bool) { + return uv.Environ(msg).LookupEnv(key) +} diff --git a/vendor/charm.land/bubbletea/v2/exec.go b/vendor/charm.land/bubbletea/v2/exec.go new file mode 100644 index 000000000..e8af5c42f --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/exec.go @@ -0,0 +1,129 @@ +package tea + +import ( + "io" + "os" + "os/exec" +) + +// execMsg is used internally to run an ExecCommand sent with Exec. +type execMsg struct { + cmd ExecCommand + fn ExecCallback +} + +// Exec is used to perform arbitrary I/O in a blocking fashion, effectively +// pausing the Program while execution is running and resuming it when +// execution has completed. +// +// Most of the time you'll want to use ExecProcess, which runs an exec.Cmd. +// +// For non-interactive i/o you should use a Cmd (that is, a tea.Cmd). +func Exec(c ExecCommand, fn ExecCallback) Cmd { + return func() Msg { + return execMsg{cmd: c, fn: fn} + } +} + +// ExecProcess runs the given *exec.Cmd in a blocking fashion, effectively +// pausing the Program while the command is running. After the *exec.Cmd exists +// the Program resumes. It's useful for spawning other interactive applications +// such as editors and shells from within a Program. +// +// To produce the command, pass an *exec.Cmd and a function which returns +// a message containing the error which may have occurred when running the +// ExecCommand. +// +// type VimFinishedMsg struct { err error } +// +// c := exec.Command("vim", "file.txt") +// +// cmd := ExecProcess(c, func(err error) Msg { +// return VimFinishedMsg{err: err} +// }) +// +// Or, if you don't care about errors, you could simply: +// +// cmd := ExecProcess(exec.Command("vim", "file.txt"), nil) +// +// For non-interactive i/o you should use a Cmd (that is, a tea.Cmd). +func ExecProcess(c *exec.Cmd, fn ExecCallback) Cmd { + return Exec(wrapExecCommand(c), fn) +} + +// ExecCallback is used when executing an *exec.Command to return a message +// with an error, which may or may not be nil. +type ExecCallback func(error) Msg + +// ExecCommand can be implemented to execute things in a blocking fashion in +// the current terminal. +type ExecCommand interface { + Run() error + SetStdin(io.Reader) + SetStdout(io.Writer) + SetStderr(io.Writer) +} + +// wrapExecCommand wraps an exec.Cmd so that it satisfies the ExecCommand +// interface so it can be used with Exec. +func wrapExecCommand(c *exec.Cmd) ExecCommand { + return &osExecCommand{Cmd: c} +} + +// osExecCommand is a layer over an exec.Cmd that satisfies the ExecCommand +// interface. +type osExecCommand struct{ *exec.Cmd } + +// SetStdin sets stdin on underlying exec.Cmd to the given io.Reader. +func (c *osExecCommand) SetStdin(r io.Reader) { + // If unset, have the command use the same input as the terminal. + if c.Stdin == nil { + c.Stdin = r + } +} + +// SetStdout sets stdout on underlying exec.Cmd to the given io.Writer. +func (c *osExecCommand) SetStdout(w io.Writer) { + // If unset, have the command use the same output as the terminal. + if c.Stdout == nil { + c.Stdout = w + } +} + +// SetStderr sets stderr on the underlying exec.Cmd to the given io.Writer. +func (c *osExecCommand) SetStderr(w io.Writer) { + // If unset, use stderr for the command's stderr + if c.Stderr == nil { + c.Stderr = w + } +} + +// exec runs an ExecCommand and delivers the results to the program as a Msg. +func (p *Program) exec(c ExecCommand, fn ExecCallback) { + if err := p.releaseTerminal(false); err != nil { + // If we can't release input, abort. + if fn != nil { + go p.Send(fn(err)) + } + return + } + + c.SetStdin(p.input) + c.SetStdout(p.output) + c.SetStderr(os.Stderr) + + // Execute system command. + if err := c.Run(); err != nil { + _ = p.RestoreTerminal() // also try to restore the terminal. + if fn != nil { + go p.Send(fn(err)) + } + return + } + + // Have the program re-capture input. + err := p.RestoreTerminal() + if fn != nil { + go p.Send(fn(err)) + } +} diff --git a/vendor/charm.land/bubbletea/v2/focus.go b/vendor/charm.land/bubbletea/v2/focus.go new file mode 100644 index 000000000..4d34bea6f --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/focus.go @@ -0,0 +1,9 @@ +package tea + +// FocusMsg represents a terminal focus message. +// This occurs when the terminal gains focus. +type FocusMsg struct{} + +// BlurMsg represents a terminal blur message. +// This occurs when the terminal loses focus. +type BlurMsg struct{} diff --git a/vendor/charm.land/bubbletea/v2/input.go b/vendor/charm.land/bubbletea/v2/input.go new file mode 100644 index 000000000..db274fb0b --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/input.go @@ -0,0 +1,54 @@ +package tea + +import ( + uv "github.com/charmbracelet/ultraviolet" +) + +// translateInputEvent translates an input event into a Bubble Tea Msg. +func (p *Program) translateInputEvent(e uv.Event) Msg { + switch e := e.(type) { + case uv.ClipboardEvent: + return ClipboardMsg(e) + case uv.ForegroundColorEvent: + return ForegroundColorMsg(e) + case uv.BackgroundColorEvent: + return BackgroundColorMsg(e) + case uv.CursorColorEvent: + return CursorColorMsg(e) + case uv.CursorPositionEvent: + return CursorPositionMsg(e) + case uv.FocusEvent: + return FocusMsg(e) + case uv.BlurEvent: + return BlurMsg(e) + case uv.KeyPressEvent: + return KeyPressMsg(e) + case uv.KeyReleaseEvent: + return KeyReleaseMsg(e) + case uv.MouseClickEvent: + return MouseClickMsg(e) + case uv.MouseMotionEvent: + return MouseMotionMsg(e) + case uv.MouseReleaseEvent: + return MouseReleaseMsg(e) + case uv.MouseWheelEvent: + return MouseWheelMsg(e) + case uv.PasteEvent: + return PasteMsg(e) + case uv.PasteStartEvent: + return PasteStartMsg(e) + case uv.PasteEndEvent: + return PasteEndMsg(e) + case uv.WindowSizeEvent: + return WindowSizeMsg(e) + case uv.CapabilityEvent: + return CapabilityMsg(e) + case uv.TerminalVersionEvent: + return TerminalVersionMsg(e) + case uv.KeyboardEnhancementsEvent: + return KeyboardEnhancementsMsg(e) + case uv.ModeReportEvent: + return ModeReportMsg(e) + } + return e +} diff --git a/vendor/charm.land/bubbletea/v2/key.go b/vendor/charm.land/bubbletea/v2/key.go new file mode 100644 index 000000000..b16e53c2f --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/key.go @@ -0,0 +1,371 @@ +package tea + +import ( + "fmt" + + uv "github.com/charmbracelet/ultraviolet" +) + +const ( + // KeyExtended is a special key code used to signify that a key event + // contains multiple runes. + KeyExtended = uv.KeyExtended +) + +// Special key symbols. +const ( + + // Special keys. + + KeyUp = uv.KeyUp + KeyDown = uv.KeyDown + KeyRight = uv.KeyRight + KeyLeft = uv.KeyLeft + KeyBegin = uv.KeyBegin + KeyFind = uv.KeyFind + KeyInsert = uv.KeyInsert + KeyDelete = uv.KeyDelete + KeySelect = uv.KeySelect + KeyPgUp = uv.KeyPgUp + KeyPgDown = uv.KeyPgDown + KeyHome = uv.KeyHome + KeyEnd = uv.KeyEnd + + // Keypad keys. + + KeyKpEnter = uv.KeyKpEnter + KeyKpEqual = uv.KeyKpEqual + KeyKpMultiply = uv.KeyKpMultiply + KeyKpPlus = uv.KeyKpPlus + KeyKpComma = uv.KeyKpComma + KeyKpMinus = uv.KeyKpMinus + KeyKpDecimal = uv.KeyKpDecimal + KeyKpDivide = uv.KeyKpDivide + KeyKp0 = uv.KeyKp0 + KeyKp1 = uv.KeyKp1 + KeyKp2 = uv.KeyKp2 + KeyKp3 = uv.KeyKp3 + KeyKp4 = uv.KeyKp4 + KeyKp5 = uv.KeyKp5 + KeyKp6 = uv.KeyKp6 + KeyKp7 = uv.KeyKp7 + KeyKp8 = uv.KeyKp8 + KeyKp9 = uv.KeyKp9 + + // The following are keys defined in the Kitty keyboard protocol. + // XXX: Investigate the names of these keys. + KeyKpSep = uv.KeyKpSep + KeyKpUp = uv.KeyKpUp + KeyKpDown = uv.KeyKpDown + KeyKpLeft = uv.KeyKpLeft + KeyKpRight = uv.KeyKpRight + KeyKpPgUp = uv.KeyKpPgUp + KeyKpPgDown = uv.KeyKpPgDown + KeyKpHome = uv.KeyKpHome + KeyKpEnd = uv.KeyKpEnd + KeyKpInsert = uv.KeyKpInsert + KeyKpDelete = uv.KeyKpDelete + KeyKpBegin = uv.KeyKpBegin + + // Function keys. + + KeyF1 = uv.KeyF1 + KeyF2 = uv.KeyF2 + KeyF3 = uv.KeyF3 + KeyF4 = uv.KeyF4 + KeyF5 = uv.KeyF5 + KeyF6 = uv.KeyF6 + KeyF7 = uv.KeyF7 + KeyF8 = uv.KeyF8 + KeyF9 = uv.KeyF9 + KeyF10 = uv.KeyF10 + KeyF11 = uv.KeyF11 + KeyF12 = uv.KeyF12 + KeyF13 = uv.KeyF13 + KeyF14 = uv.KeyF14 + KeyF15 = uv.KeyF15 + KeyF16 = uv.KeyF16 + KeyF17 = uv.KeyF17 + KeyF18 = uv.KeyF18 + KeyF19 = uv.KeyF19 + KeyF20 = uv.KeyF20 + KeyF21 = uv.KeyF21 + KeyF22 = uv.KeyF22 + KeyF23 = uv.KeyF23 + KeyF24 = uv.KeyF24 + KeyF25 = uv.KeyF25 + KeyF26 = uv.KeyF26 + KeyF27 = uv.KeyF27 + KeyF28 = uv.KeyF28 + KeyF29 = uv.KeyF29 + KeyF30 = uv.KeyF30 + KeyF31 = uv.KeyF31 + KeyF32 = uv.KeyF32 + KeyF33 = uv.KeyF33 + KeyF34 = uv.KeyF34 + KeyF35 = uv.KeyF35 + KeyF36 = uv.KeyF36 + KeyF37 = uv.KeyF37 + KeyF38 = uv.KeyF38 + KeyF39 = uv.KeyF39 + KeyF40 = uv.KeyF40 + KeyF41 = uv.KeyF41 + KeyF42 = uv.KeyF42 + KeyF43 = uv.KeyF43 + KeyF44 = uv.KeyF44 + KeyF45 = uv.KeyF45 + KeyF46 = uv.KeyF46 + KeyF47 = uv.KeyF47 + KeyF48 = uv.KeyF48 + KeyF49 = uv.KeyF49 + KeyF50 = uv.KeyF50 + KeyF51 = uv.KeyF51 + KeyF52 = uv.KeyF52 + KeyF53 = uv.KeyF53 + KeyF54 = uv.KeyF54 + KeyF55 = uv.KeyF55 + KeyF56 = uv.KeyF56 + KeyF57 = uv.KeyF57 + KeyF58 = uv.KeyF58 + KeyF59 = uv.KeyF59 + KeyF60 = uv.KeyF60 + KeyF61 = uv.KeyF61 + KeyF62 = uv.KeyF62 + KeyF63 = uv.KeyF63 + + // The following are keys defined in the Kitty keyboard protocol. + // XXX: Investigate the names of these keys. + + KeyCapsLock = uv.KeyCapsLock + KeyScrollLock = uv.KeyScrollLock + KeyNumLock = uv.KeyNumLock + KeyPrintScreen = uv.KeyPrintScreen + KeyPause = uv.KeyPause + KeyMenu = uv.KeyMenu + + KeyMediaPlay = uv.KeyMediaPlay + KeyMediaPause = uv.KeyMediaPause + KeyMediaPlayPause = uv.KeyMediaPlayPause + KeyMediaReverse = uv.KeyMediaReverse + KeyMediaStop = uv.KeyMediaStop + KeyMediaFastForward = uv.KeyMediaFastForward + KeyMediaRewind = uv.KeyMediaRewind + KeyMediaNext = uv.KeyMediaNext + KeyMediaPrev = uv.KeyMediaPrev + KeyMediaRecord + + KeyLowerVol = uv.KeyLowerVol + KeyRaiseVol = uv.KeyRaiseVol + KeyMute = uv.KeyMute + + KeyLeftShift = uv.KeyLeftShift + KeyLeftAlt = uv.KeyLeftAlt + KeyLeftCtrl = uv.KeyLeftCtrl + KeyLeftSuper = uv.KeyLeftSuper + KeyLeftHyper = uv.KeyLeftHyper + KeyLeftMeta = uv.KeyLeftMeta + KeyRightShift = uv.KeyRightShift + KeyRightAlt = uv.KeyRightAlt + KeyRightCtrl = uv.KeyRightCtrl + KeyRightSuper = uv.KeyRightSuper + KeyRightHyper = uv.KeyRightHyper + KeyRightMeta = uv.KeyRightMeta + KeyIsoLevel3Shift = uv.KeyIsoLevel3Shift + KeyIsoLevel5Shift = uv.KeyIsoLevel5Shift + + // Special names in C0. + + KeyBackspace = uv.KeyBackspace + KeyTab = uv.KeyTab + KeyEnter = uv.KeyEnter + KeyReturn = uv.KeyReturn + KeyEscape = uv.KeyEscape + KeyEsc = uv.KeyEsc + + // Special names in G0. + + KeySpace = uv.KeySpace +) + +// KeyPressMsg represents a key press message. +type KeyPressMsg Key + +// String implements [fmt.Stringer] and is quite useful for matching key +// events. For details, on what this returns see [Key.String]. +func (k KeyPressMsg) String() string { + return Key(k).String() +} + +// Keystroke returns the keystroke representation of the [Key]. While less type +// safe than looking at the individual fields, it will usually be more +// convenient and readable to use this method when matching against keys. +// +// Note that modifier keys are always printed in the following order: +// - ctrl +// - alt +// - shift +// - meta +// - hyper +// - super +// +// For example, you'll always see "ctrl+shift+alt+a" and never +// "shift+ctrl+alt+a". +func (k KeyPressMsg) Keystroke() string { + return uv.Key(k).Keystroke() +} + +// Key returns the underlying key event. This is a syntactic sugar for casting +// the key event to a [Key]. +func (k KeyPressMsg) Key() Key { + return Key(k) +} + +// KeyReleaseMsg represents a key release message. +type KeyReleaseMsg Key + +// String implements [fmt.Stringer] and is quite useful for matching key +// events. For details, on what this returns see [Key.String]. +func (k KeyReleaseMsg) String() string { + return Key(k).String() +} + +// Keystroke returns the keystroke representation of the [Key]. While less type +// safe than looking at the individual fields, it will usually be more +// convenient and readable to use this method when matching against keys. +// +// Note that modifier keys are always printed in the following order: +// - ctrl +// - alt +// - shift +// - meta +// - hyper +// - super +// +// For example, you'll always see "ctrl+shift+alt+a" and never +// "shift+ctrl+alt+a". +func (k KeyReleaseMsg) Keystroke() string { + return uv.Key(k).Keystroke() +} + +// Key returns the underlying key event. This is a convenience method and +// syntactic sugar to satisfy the [KeyMsg] interface, and cast the key event to +// [Key]. +func (k KeyReleaseMsg) Key() Key { + return Key(k) +} + +// KeyMsg represents a key event. This can be either a key press or a key +// release event. +type KeyMsg interface { + fmt.Stringer + + // Key returns the underlying key event. + Key() Key +} + +// Key represents a Key press or release event. It contains information about +// the Key pressed, like the runes, the type of Key, and the modifiers pressed. +// There are a couple general patterns you could use to check for key presses +// or releases: +// +// // Switch on the string representation of the key (shorter) +// switch msg := msg.(type) { +// case KeyPressMsg: +// switch msg.String() { +// case "enter": +// fmt.Println("you pressed enter!") +// case "a": +// fmt.Println("you pressed a!") +// } +// } +// +// // Switch on the key type (more foolproof) +// switch msg := msg.(type) { +// case KeyMsg: +// // catch both KeyPressMsg and KeyReleaseMsg +// switch key := msg.Key(); key.Code { +// case KeyEnter: +// fmt.Println("you pressed enter!") +// default: +// switch key.Text { +// case "a": +// fmt.Println("you pressed a!") +// } +// } +// } +// +// Note that [Key.Text] will be empty for special keys like [KeyEnter], +// [KeyTab], and for keys that don't represent printable characters like key +// combos with modifier keys. In other words, [Key.Text] is populated only for +// keys that represent printable characters shifted or unshifted (like 'a', +// 'A', '1', '!', etc.). +type Key struct { + // Text contains the actual characters received. This usually the same as + // [Key.Code]. When [Key.Text] is non-empty, it indicates that the key + // pressed represents printable character(s). + Text string + + // Mod represents modifier keys, like [ModCtrl], [ModAlt], and so on. + Mod KeyMod + + // Code represents the key pressed. This is usually a special key like + // [KeyTab], [KeyEnter], [KeyF1], or a printable character like 'a'. + Code rune + + // ShiftedCode is the actual, shifted key pressed by the user. For example, + // if the user presses shift+a, or caps lock is on, [Key.ShiftedCode] will + // be 'A' and [Key.Code] will be 'a'. + // + // In the case of non-latin keyboards, like Arabic, [Key.ShiftedCode] is the + // unshifted key on the keyboard. + // + // This is only available with the Kitty Keyboard Protocol or the Windows + // Console API. + ShiftedCode rune + + // BaseCode is the key pressed according to the standard PC-101 key layout. + // On international keyboards, this is the key that would be pressed if the + // keyboard was set to US PC-101 layout. + // + // For example, if the user presses 'q' on a French AZERTY keyboard, + // [Key.BaseCode] will be 'q'. + // + // This is only available with the Kitty Keyboard Protocol or the Windows + // Console API. + BaseCode rune + + // IsRepeat indicates whether the key is being held down and sending events + // repeatedly. + // + // This is only available with the Kitty Keyboard Protocol or the Windows + // Console API. + IsRepeat bool +} + +// String implements [fmt.Stringer] and is quite useful for matching key +// events. It will return the textual representation of the [Key] if there is +// one, otherwise, it will fallback to [Key.Keystroke]. +// +// For example, you'll always get "?" and instead of "shift+/" on a US ANSI +// keyboard. +func (k Key) String() string { + return uv.Key(k).String() +} + +// Keystroke returns the keystroke representation of the [Key]. While less type +// safe than looking at the individual fields, it will usually be more +// convenient and readable to use this method when matching against keys. +// +// Note that modifier keys are always printed in the following order: +// - ctrl +// - alt +// - shift +// - meta +// - hyper +// - super +// +// For example, you'll always see "ctrl+shift+alt+a" and never +// "shift+ctrl+alt+a". +func (k Key) Keystroke() string { + return uv.Key(k).Keystroke() +} diff --git a/vendor/charm.land/bubbletea/v2/keyboard.go b/vendor/charm.land/bubbletea/v2/keyboard.go new file mode 100644 index 000000000..33747a456 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/keyboard.go @@ -0,0 +1,59 @@ +package tea + +import ( + "github.com/charmbracelet/x/ansi" +) + +// KeyboardEnhancementsMsg is a message that gets sent when the terminal +// supports keyboard enhancements. +type KeyboardEnhancementsMsg struct { + // Flags is a bitmask of enabled keyboard enhancement features. A non-zero + // value indicates that at least we have key disambiguation support. + // + // See [ansi.KittyReportEventTypes] and other constants for details. + // + // Example: + // + // ```go + // // The hard way + // if msg.Flags&ansi.KittyReportEventTypes != 0 { + // // Terminal supports reporting different key event types + // } + // + // // The easy way + // if msg.SupportsEventTypes() { + // // Terminal supports reporting different key event types + // } + // ``` + Flags int +} + +// SupportsKeyDisambiguation returns whether the terminal supports key +// disambiguation (e.g., distinguishing between different modifier keys). +func (k KeyboardEnhancementsMsg) SupportsKeyDisambiguation() bool { + return k.Flags > 0 +} + +// SupportsEventTypes returns whether the terminal supports reporting +// different types of key events (press, release, and repeat). +func (k KeyboardEnhancementsMsg) SupportsEventTypes() bool { + return k.Flags&ansi.KittyReportEventTypes != 0 +} + +// SupportsAlternateKeys returns whether the terminal supports reporting +// alternate key codes. +func (k KeyboardEnhancementsMsg) SupportsAlternateKeys() bool { + return k.Flags&ansi.KittyReportAlternateKeys != 0 +} + +// SupportsAllKeysAsEscapeCodes returns whether the terminal supports reporting +// all keys as escape codes. +func (k KeyboardEnhancementsMsg) SupportsAllKeysAsEscapeCodes() bool { + return k.Flags&ansi.KittyReportAllKeysAsEscapeCodes != 0 +} + +// SupportsAssociatedText returns whether the terminal supports reporting +// associated text with key events. +func (k KeyboardEnhancementsMsg) SupportsAssociatedText() bool { + return k.Flags&ansi.KittyReportAssociatedKeys != 0 +} diff --git a/vendor/charm.land/bubbletea/v2/logging.go b/vendor/charm.land/bubbletea/v2/logging.go new file mode 100644 index 000000000..349758cbc --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/logging.go @@ -0,0 +1,53 @@ +package tea + +import ( + "fmt" + "io" + "log" + "os" + "unicode" +) + +// LogToFile sets up default logging to log to a file. This is helpful as we +// can't print to the terminal since our TUI is occupying it. If the file +// doesn't exist it will be created. +// +// Don't forget to close the file when you're done with it. +// +// f, err := LogToFile("debug.log", "debug") +// if err != nil { +// fmt.Println("fatal:", err) +// os.Exit(1) +// } +// defer f.Close() +func LogToFile(path string, prefix string) (*os.File, error) { + return LogToFileWith(path, prefix, log.Default()) +} + +// LogOptionsSetter is an interface implemented by stdlib's log and charm's log +// libraries. +type LogOptionsSetter interface { + SetOutput(io.Writer) + SetPrefix(string) +} + +// LogToFileWith does allows to call LogToFile with a custom LogOptionsSetter. +func LogToFileWith(path string, prefix string, log LogOptionsSetter) (*os.File, error) { + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) //nolint:mnd + if err != nil { + return nil, fmt.Errorf("error opening file for logging: %w", err) + } + log.SetOutput(f) + + // Add a space after the prefix if a prefix is being specified and it + // doesn't already have a trailing space. + if len(prefix) > 0 { + finalChar := prefix[len(prefix)-1] + if !unicode.IsSpace(rune(finalChar)) { + prefix += " " + } + } + log.SetPrefix(prefix) + + return f, nil +} diff --git a/vendor/charm.land/bubbletea/v2/mod.go b/vendor/charm.land/bubbletea/v2/mod.go new file mode 100644 index 000000000..f85a4c32c --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/mod.go @@ -0,0 +1,27 @@ +package tea + +import uv "github.com/charmbracelet/ultraviolet" + +// KeyMod represents modifier keys. +type KeyMod = uv.KeyMod + +// Modifier keys. +const ( + ModShift = uv.ModShift + ModAlt = uv.ModAlt + ModCtrl = uv.ModCtrl + ModMeta = uv.ModMeta + + // These modifiers are used with the Kitty protocol. + // XXX: Meta and Super are swapped in the Kitty protocol, + // this is to preserve compatibility with XTerm modifiers. + + ModHyper = uv.ModHyper + ModSuper = uv.ModSuper // Windows/Command keys + + // These are key lock states. + + ModCapsLock = uv.ModCapsLock + ModNumLock = uv.ModNumLock + ModScrollLock = uv.ModScrollLock // Defined in Windows API only +) diff --git a/vendor/charm.land/bubbletea/v2/mouse.go b/vendor/charm.land/bubbletea/v2/mouse.go new file mode 100644 index 000000000..05b83128b --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/mouse.go @@ -0,0 +1,144 @@ +package tea + +import ( + "fmt" + + uv "github.com/charmbracelet/ultraviolet" +) + +// MouseButton represents the button that was pressed during a mouse message. +type MouseButton = uv.MouseButton + +// Mouse event buttons +// +// This is based on X11 mouse button codes. +// +// 1 = left button +// 2 = middle button (pressing the scroll wheel) +// 3 = right button +// 4 = turn scroll wheel up +// 5 = turn scroll wheel down +// 6 = push scroll wheel left +// 7 = push scroll wheel right +// 8 = 4th button (aka browser backward button) +// 9 = 5th button (aka browser forward button) +// 10 +// 11 +// +// Other buttons are not supported. +const ( + MouseNone = uv.MouseNone + MouseLeft = uv.MouseLeft + MouseMiddle = uv.MouseMiddle + MouseRight = uv.MouseRight + MouseWheelUp = uv.MouseWheelUp + MouseWheelDown = uv.MouseWheelDown + MouseWheelLeft = uv.MouseWheelLeft + MouseWheelRight = uv.MouseWheelRight + MouseBackward = uv.MouseBackward + MouseForward = uv.MouseForward + MouseButton10 = uv.MouseButton10 + MouseButton11 +) + +// MouseMsg represents a mouse message. This is a generic mouse message that +// can represent any kind of mouse event. +type MouseMsg interface { + fmt.Stringer + + // Mouse returns the underlying mouse event. + Mouse() Mouse +} + +// Mouse represents a Mouse message. Use [MouseMsg] to represent all mouse +// messages. +// +// The X and Y coordinates are zero-based, with (0,0) being the upper left +// corner of the terminal. +// +// // Catch all mouse events +// switch msg := msg.(type) { +// case MouseMsg: +// m := msg.Mouse() +// fmt.Println("Mouse event:", m.X, m.Y, m) +// } +// +// // Only catch mouse click events +// switch msg := msg.(type) { +// case MouseClickMsg: +// fmt.Println("Mouse click event:", msg.X, msg.Y, msg) +// } +type Mouse struct { + X, Y int + Button MouseButton + Mod KeyMod +} + +// String returns a string representation of the mouse message. +func (m Mouse) String() (s string) { + return uv.Mouse(m).String() +} + +// MouseClickMsg represents a mouse button click message. +type MouseClickMsg Mouse + +// String returns a string representation of the mouse click message. +func (e MouseClickMsg) String() string { + return Mouse(e).String() +} + +// Mouse returns the underlying mouse event. This is a convenience method and +// syntactic sugar to satisfy the [MouseMsg] interface, and cast the mouse +// event to [Mouse]. +func (e MouseClickMsg) Mouse() Mouse { + return Mouse(e) +} + +// MouseReleaseMsg represents a mouse button release message. +type MouseReleaseMsg Mouse + +// String returns a string representation of the mouse release message. +func (e MouseReleaseMsg) String() string { + return Mouse(e).String() +} + +// Mouse returns the underlying mouse event. This is a convenience method and +// syntactic sugar to satisfy the [MouseMsg] interface, and cast the mouse +// event to [Mouse]. +func (e MouseReleaseMsg) Mouse() Mouse { + return Mouse(e) +} + +// MouseWheelMsg represents a mouse wheel message event. +type MouseWheelMsg Mouse + +// String returns a string representation of the mouse wheel message. +func (e MouseWheelMsg) String() string { + return Mouse(e).String() +} + +// Mouse returns the underlying mouse event. This is a convenience method and +// syntactic sugar to satisfy the [MouseMsg] interface, and cast the mouse +// event to [Mouse]. +func (e MouseWheelMsg) Mouse() Mouse { + return Mouse(e) +} + +// MouseMotionMsg represents a mouse motion message. +type MouseMotionMsg Mouse + +// String returns a string representation of the mouse motion message. +func (e MouseMotionMsg) String() string { + m := Mouse(e) + if m.Button != 0 { + return m.String() + "+motion" + } + return m.String() + "motion" +} + +// Mouse returns the underlying mouse event. This is a convenience method and +// syntactic sugar to satisfy the [MouseMsg] interface, and cast the mouse +// event to [Mouse]. +func (e MouseMotionMsg) Mouse() Mouse { + return Mouse(e) +} diff --git a/vendor/charm.land/bubbletea/v2/nil_renderer.go b/vendor/charm.land/bubbletea/v2/nil_renderer.go new file mode 100644 index 000000000..1edafea7e --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/nil_renderer.go @@ -0,0 +1,53 @@ +package tea + +import ( + "github.com/charmbracelet/colorprofile" + "github.com/charmbracelet/x/ansi" +) + +// nilRenderer is a no-op renderer. It implements the Renderer interface but +// doesn't render anything to the terminal. +type nilRenderer struct{} + +var _ renderer = nilRenderer{} + +// start implements renderer. +func (n nilRenderer) start() {} + +// clearScreen implements renderer. +func (n nilRenderer) clearScreen() {} + +// insertAbove implements renderer. +func (n nilRenderer) insertAbove(string) error { return nil } + +// resize implements renderer. +func (n nilRenderer) resize(int, int) {} + +// setColorProfile implements renderer. +func (n nilRenderer) setColorProfile(colorprofile.Profile) {} + +// flush implements the Renderer interface. +func (nilRenderer) flush(bool) error { return nil } + +// close implements the Renderer interface. +func (nilRenderer) close() error { return nil } + +// render implements the Renderer interface. +func (nilRenderer) render(View) {} + +// reset implements the Renderer interface. +func (nilRenderer) reset() {} + +// writeString implements the Renderer interface. +func (nilRenderer) writeString(string) (int, error) { return 0, nil } + +// setSyncdUpdates implements the Renderer interface. +func (n nilRenderer) setSyncdUpdates(bool) {} + +// setWidthMethod implements the Renderer interface. +func (n nilRenderer) setWidthMethod(ansi.Method) {} + +// onMouse implements the Renderer interface. +func (n nilRenderer) onMouse(MouseMsg) Cmd { + return nil +} diff --git a/vendor/charm.land/bubbletea/v2/options.go b/vendor/charm.land/bubbletea/v2/options.go new file mode 100644 index 000000000..dfd8cda4f --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/options.go @@ -0,0 +1,168 @@ +package tea + +import ( + "context" + "io" + "sync/atomic" + + "github.com/charmbracelet/colorprofile" +) + +// ProgramOption is used to set options when initializing a Program. Program can +// accept a variable number of options. +// +// Example usage: +// +// p := NewProgram(model, WithInput(someInput), WithOutput(someOutput)) +type ProgramOption func(*Program) + +// WithContext lets you specify a context in which to run the Program. This is +// useful if you want to cancel the execution from outside. When a Program gets +// cancelled it will exit with an error ErrProgramKilled. +func WithContext(ctx context.Context) ProgramOption { + return func(p *Program) { + p.externalCtx = ctx + } +} + +// WithOutput sets the output which, by default, is stdout. In most cases you +// won't need to use this. +func WithOutput(output io.Writer) ProgramOption { + return func(p *Program) { + p.output = output + } +} + +// WithInput sets the input which, by default, is stdin. In most cases you +// won't need to use this. To disable input entirely pass nil. +// +// p := NewProgram(model, WithInput(nil)) +func WithInput(input io.Reader) ProgramOption { + return func(p *Program) { + p.input = input + p.disableInput = input == nil + } +} + +// WithEnvironment sets the environment variables that the program will use. +// This useful when the program is running in a remote session (e.g. SSH) and +// you want to pass the environment variables from the remote session to the +// program. +// +// Example: +// +// var sess ssh.Session // ssh.Session is a type from the github.com/charmbracelet/ssh package +// pty, _, _ := sess.Pty() +// environ := append(sess.Environ(), "TERM="+pty.Term) +// p := tea.NewProgram(model, tea.WithEnvironment(environ) +func WithEnvironment(env []string) ProgramOption { + return func(p *Program) { + p.environ = env + } +} + +// WithoutSignalHandler disables the signal handler that Bubble Tea sets up for +// Programs. This is useful if you want to handle signals yourself. +func WithoutSignalHandler() ProgramOption { + return func(p *Program) { + p.disableSignalHandler = true + } +} + +// WithoutCatchPanics disables the panic catching that Bubble Tea does by +// default. If panic catching is disabled the terminal will be in a fairly +// unusable state after a panic because Bubble Tea will not perform its usual +// cleanup on exit. +func WithoutCatchPanics() ProgramOption { + return func(p *Program) { + p.disableCatchPanics = true + } +} + +// WithoutSignals will ignore OS signals. +// This is mainly useful for testing. +func WithoutSignals() ProgramOption { + return func(p *Program) { + atomic.StoreUint32(&p.ignoreSignals, 1) + } +} + +// WithoutRenderer disables the renderer. When this is set output and log +// statements will be plainly sent to stdout (or another output if one is set) +// without any rendering and redrawing logic. In other words, printing and +// logging will behave the same way it would in a non-TUI commandline tool. +// This can be useful if you want to use the Bubble Tea framework for a non-TUI +// application, or to provide an additional non-TUI mode to your Bubble Tea +// programs. For example, your program could behave like a daemon if output is +// not a TTY. +func WithoutRenderer() ProgramOption { + return func(p *Program) { + p.disableRenderer = true + } +} + +// WithFilter supplies an event filter that will be invoked before Bubble Tea +// processes a tea.Msg. The event filter can return any tea.Msg which will then +// get handled by Bubble Tea instead of the original event. If the event filter +// returns nil, the event will be ignored and Bubble Tea will not process it. +// +// As an example, this could be used to prevent a program from shutting down if +// there are unsaved changes. +// +// Example: +// +// func filter(m tea.Model, msg tea.Msg) tea.Msg { +// if _, ok := msg.(tea.QuitMsg); !ok { +// return msg +// } +// +// model := m.(myModel) +// if model.hasChanges { +// return nil +// } +// +// return msg +// } +// +// p := tea.NewProgram(Model{}, tea.WithFilter(filter)); +// +// if _,err := p.Run(); err != nil { +// fmt.Println("Error running program:", err) +// os.Exit(1) +// } +func WithFilter(filter func(Model, Msg) Msg) ProgramOption { + return func(p *Program) { + p.filter = filter + } +} + +// WithFPS sets a custom maximum FPS at which the renderer should run. If +// less than 1, the default value of 60 will be used. If over 120, the FPS +// will be capped at 120. +func WithFPS(fps int) ProgramOption { + return func(p *Program) { + p.fps = fps + } +} + +// WithColorProfile sets the color profile that the program will use. This is +// useful when you want to force a specific color profile. By default, Bubble +// Tea will try to detect the terminal's color profile from environment +// variables and terminfo capabilities. Use [tea.WithEnvironment] to set custom +// environment variables. +func WithColorProfile(profile colorprofile.Profile) ProgramOption { + return func(p *Program) { + p.profile = &profile + } +} + +// WithWindowSize sets the initial size of the terminal window. This is useful +// when you need to set the initial size of the terminal window, for example +// during testing or when you want to run your program in a non-interactive +// environment. +func WithWindowSize(width, height int) ProgramOption { + return func(p *Program) { + p.width = width + p.height = height + } +} diff --git a/vendor/charm.land/bubbletea/v2/paste.go b/vendor/charm.land/bubbletea/v2/paste.go new file mode 100644 index 000000000..6a3f54c1f --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/paste.go @@ -0,0 +1,20 @@ +package tea + +// PasteMsg is an message that is emitted when a terminal receives pasted text +// using bracketed-paste. +type PasteMsg struct { + Content string +} + +// String returns the pasted content as a string. +func (p PasteMsg) String() string { + return p.Content +} + +// PasteStartMsg is an message that is emitted when the terminal starts the +// bracketed-paste text. +type PasteStartMsg struct{} + +// PasteEndMsg is an message that is emitted when the terminal ends the +// bracketed-paste text. +type PasteEndMsg struct{} diff --git a/vendor/charm.land/bubbletea/v2/profile.go b/vendor/charm.land/bubbletea/v2/profile.go new file mode 100644 index 000000000..f67186636 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/profile.go @@ -0,0 +1,15 @@ +package tea + +import "github.com/charmbracelet/colorprofile" + +// ColorProfileMsg is a message that describes the terminal's color profile. +// This message is send to the program's update function when the program is +// started. +// +// To upgrade the terminal color profile, use the `tea.RequestCapability` +// command to request the `RGB` and `Tc` terminfo capabilities. Bubble Tea will +// then cache the terminal's color profile and send a `ColorProfileMsg` to the +// program's update function. +type ColorProfileMsg struct { + colorprofile.Profile +} diff --git a/vendor/charm.land/bubbletea/v2/raw.go b/vendor/charm.land/bubbletea/v2/raw.go new file mode 100644 index 000000000..fb6ae44dd --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/raw.go @@ -0,0 +1,37 @@ +package tea + +// RawMsg is a message that contains a string to be printed to the terminal +// without any intermediate processing. +type RawMsg struct { + Msg any +} + +// Raw is a command that prints the given string to the terminal without any +// formatting. +// +// This is intended for advanced use cases where you need to query the terminal +// or send escape sequences directly. Don't use this unless you know what +// you're doing :) +// +// Example: +// +// func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// switch msg := msg.(type) { +// case input.PrimaryDeviceAttributesEvent: +// for _, attr := range msg { +// if attr == 4 { +// // We have Sixel graphics support! +// break +// } +// } +// } +// +// // Request the terminal primary device attributes to detect Sixel graphics +// // support. +// return m, tea.Raw(ansi.RequestPrimaryDeviceAttributes) +// } +func Raw(r any) Cmd { + return func() Msg { + return RawMsg{r} + } +} diff --git a/vendor/charm.land/bubbletea/v2/renderer.go b/vendor/charm.land/bubbletea/v2/renderer.go new file mode 100644 index 000000000..e8d792b44 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/renderer.go @@ -0,0 +1,104 @@ +package tea + +import ( + "fmt" + + "github.com/charmbracelet/colorprofile" + "github.com/charmbracelet/x/ansi" +) + +const ( + // defaultFramerate specifies the maximum interval at which we should + // update the view. + defaultFPS = 60 + maxFPS = 120 +) + +// renderer is the interface for Bubble Tea renderers. +type renderer interface { + // start starts the renderer. + start() + + // close closes the renderer and flushes any remaining data. + close() error + + // render renders a frame to the output. + render(View) + + // flush flushes the renderer's buffer to the output. + flush(closing bool) error + + // reset resets the renderer's state to its initial state. + reset() + + // insertAbove inserts unmanaged lines above the renderer. + insertAbove(string) error + + // setSyncdUpdates sets whether to use synchronized updates. + setSyncdUpdates(bool) + + // setWidthMethod sets the method for calculating the width of the terminal. + setWidthMethod(ansi.Method) + + // resize notify the renderer of a terminal resize. + resize(int, int) + + // setColorProfile sets the color profile. + setColorProfile(colorprofile.Profile) + + // clearScreen clears the screen. + clearScreen() + + // writeString writes a string to the renderer's output. + writeString(string) (int, error) + + // onMouse handles a mouse event. + onMouse(MouseMsg) Cmd +} + +type printLineMessage struct { + messageBody string +} + +// Println prints above the Program. This output is unmanaged by the program and +// will persist across renders by the Program. +// +// Unlike fmt.Println (but similar to log.Println) the message will be print on +// its own line. +// +// If the altscreen is active no output will be printed. +func Println(args ...any) Cmd { + return func() Msg { + return printLineMessage{ + messageBody: fmt.Sprint(args...), + } + } +} + +// Printf prints above the Program. It takes a format template followed by +// values similar to fmt.Printf. This output is unmanaged by the program and +// will persist across renders by the Program. +// +// Unlike fmt.Printf (but similar to log.Printf) the message will be print on +// its own line. +// +// If the altscreen is active no output will be printed. +func Printf(template string, args ...any) Cmd { + return func() Msg { + return printLineMessage{ + messageBody: fmt.Sprintf(template, args...), + } + } +} + +// encodeCursorStyle returns the integer value for the given cursor style and +// blink state. +func encodeCursorStyle(style CursorShape, blink bool) int { + // We're using the ANSI escape sequence values for cursor styles. + // We need to map both [style] and [steady] to the correct value. + style = (style * 2) + 1 //nolint:mnd + if !blink { + style++ + } + return int(style) +} diff --git a/vendor/charm.land/bubbletea/v2/screen.go b/vendor/charm.land/bubbletea/v2/screen.go new file mode 100644 index 000000000..f835bb376 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/screen.go @@ -0,0 +1,68 @@ +package tea + +import "github.com/charmbracelet/x/ansi" + +// WindowSizeMsg is used to report the terminal size. It's sent to Update once +// initially and then on every terminal resize. Note that Windows does not +// have support for reporting when resizes occur as it does not support the +// SIGWINCH signal. +type WindowSizeMsg struct { + Width int + Height int +} + +// ClearScreen is a special command that tells the program to clear the screen +// before the next update. This can be used to move the cursor to the top left +// of the screen and clear visual clutter when the alt screen is not in use. +// +// Note that it should never be necessary to call ClearScreen() for regular +// redraws. +func ClearScreen() Msg { + return clearScreenMsg{} +} + +// clearScreenMsg is an internal message that signals to clear the screen. +// You can send a clearScreenMsg with ClearScreen. +type clearScreenMsg struct{} + +// ModeReportMsg is a message that represents a mode report event (DECRPM). +// +// This is sent by the terminal in response to a request for a terminal mode +// report (DECRQM). It indicates the current setting of a specific terminal +// mode like cursor visibility, mouse tracking, etc. +// +// Example: +// +// ```go +// func (m model) Init() tea.Cmd { +// // Does my terminal support reporting focus events? +// return tea.Raw(ansi.RequestModeFocusEvent) +// } +// +// func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// switch msg := msg.(type) { +// case tea.ModeReportMsg: +// if msg.Mode == ansi.ModeFocusEvent && !msg.Value.IsNotRecognized() { +// // Terminal supports focus events +// m.supportsFocus = true +// } +// } +// return m, nil +// } +// +// func (m model) View() tea.View { +// var view tea.View +// view.ReportFocus = m.supportsFocus +// view.SetContent(fmt.Sprintf("Terminal supports focus events: %v", m.supportsFocus)) +// return view +// } +// ``` +// +// See: https://vt100.net/docs/vt510-rm/DECRPM.html +type ModeReportMsg struct { + // Mode is the mode number. + Mode ansi.Mode + + // Value is the mode value. + Value ansi.ModeSetting +} diff --git a/vendor/charm.land/bubbletea/v2/signals_unix.go b/vendor/charm.land/bubbletea/v2/signals_unix.go new file mode 100644 index 000000000..409540385 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/signals_unix.go @@ -0,0 +1,33 @@ +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || aix || zos +// +build darwin dragonfly freebsd linux netbsd openbsd solaris aix zos + +package tea + +import ( + "os" + "os/signal" + "syscall" +) + +// listenForResize sends messages (or errors) when the terminal resizes. +// Argument output should be the file descriptor for the terminal; usually +// os.Stdout. +func (p *Program) listenForResize(done chan struct{}) { + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGWINCH) + + defer func() { + signal.Stop(sig) + close(done) + }() + + for { + select { + case <-p.ctx.Done(): + return + case <-sig: + } + + p.checkResize() + } +} diff --git a/vendor/charm.land/bubbletea/v2/signals_windows.go b/vendor/charm.land/bubbletea/v2/signals_windows.go new file mode 100644 index 000000000..2fc6f8ae7 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/signals_windows.go @@ -0,0 +1,10 @@ +//go:build windows +// +build windows + +package tea + +// listenForResize is not available on windows because windows does not +// implement syscall.SIGWINCH. +func (p *Program) listenForResize(done chan struct{}) { + close(done) +} diff --git a/vendor/charm.land/bubbletea/v2/tea.go b/vendor/charm.land/bubbletea/v2/tea.go new file mode 100644 index 000000000..cd98092a5 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/tea.go @@ -0,0 +1,1437 @@ +// Package tea provides a framework for building rich terminal user interfaces +// based on the paradigms of The Elm Architecture. It's well-suited for simple +// and complex terminal applications, either inline, full-window, or a mix of +// both. It's been battle-tested in several large projects and is +// production-ready. +// +// A tutorial is available at https://github.com/charmbracelet/bubbletea/tree/master/tutorials +// +// Example programs can be found at https://github.com/charmbracelet/bubbletea/tree/master/examples +package tea + +import ( + "bytes" + "context" + "errors" + "fmt" + "image/color" + "io" + "log" + "os" + "os/signal" + "runtime" + "runtime/debug" + "strconv" + "strings" + "sync" + "sync/atomic" + "syscall" + "time" + + "github.com/charmbracelet/colorprofile" + uv "github.com/charmbracelet/ultraviolet" + "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/x/term" + "github.com/muesli/cancelreader" +) + +// ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic. +var ErrProgramPanic = errors.New("program experienced a panic") + +// ErrProgramKilled is returned by [Program.Run] when the program gets killed. +var ErrProgramKilled = errors.New("program was killed") + +// ErrInterrupted is returned by [Program.Run] when the program get a SIGINT +// signal, or when it receives a [InterruptMsg]. +var ErrInterrupted = errors.New("program was interrupted") + +// Msg contain data from the result of a IO operation. Msgs trigger the update +// function and, henceforth, the UI. +type Msg = uv.Event + +// Model contains the program's state as well as its core functions. +type Model interface { + // Init is the first function that will be called. It returns an optional + // initial command. To not perform an initial command return nil. + Init() Cmd + + // Update is called when a message is received. Use it to inspect messages + // and, in response, update the model and/or send a command. + Update(Msg) (Model, Cmd) + + // View renders the program's UI, which can be a string or a [Layer]. The + // view is rendered after every Update. + View() View +} + +// NewView is a helper function to create a new [View] with the given styled +// string. A styled string represents text with styles and hyperlinks encoded +// as ANSI escape codes. +// +// Example: +// +// ```go +// v := tea.NewView("Hello, World!") +// ``` +func NewView(s string) View { + var view View + view.SetContent(s) + return view +} + +// View represents a terminal view that can be composed of multiple layers. +// It can also contain a cursor that will be rendered on top of the layers. +type View struct { + // Content is the screen content of the view. It holds styled strings that + // will be rendered to the terminal when the view is rendered. + // + // A styled string represents text with styles and hyperlinks encoded as + // ANSI escape codes. + // + // Example: + // + // ```go + // v := tea.NewView("Hello, World!") + // ``` + Content string + + // OnMouse is an optional mouse message handler that can be used to + // intercept mouse messages that depends on view content from last render. + // It can be useful for implementing view-specific behavior without + // breaking the unidirectional data flow of Bubble Tea. + // + // Example: + // + // ```go + // content := "Hello, World!" + // v := tea.NewView(content) + // v.OnMouse = func(msg tea.MouseMsg) tea.Cmd { + // return func() tea.Msg { + // m := msg.Mouse() + // // Check if the mouse is within the bounds of "World!" + // start := strings.Index(content, "World!") + // end := start + len("World!") + // if m.Y == 0 && m.X >= start && m.X < end { + // // Mouse is over "World!" + // return MyCustomMsg{ + // MouseMsg: msg, + // } + // } + // } + // } + // return nil + // } + // return v + // ``` + OnMouse func(msg MouseMsg) Cmd + + // Cursor represents the cursor position, style, and visibility on the + // screen. When not nil, the cursor will be shown at the specified + // position. + Cursor *Cursor + + // BackgroundColor when not nil, sets the terminal background color. Use + // nil to reset to the terminal's default background color. + BackgroundColor color.Color + + // ForegroundColor when not nil, sets the terminal foreground color. Use + // nil to reset to the terminal's default foreground color. + ForegroundColor color.Color + + // WindowTitle sets the terminal window title. Support depends on the + // terminal. + WindowTitle string + + // ProgressBar when not nil, shows a progress bar in the terminal's + // progress bar section. Support depends on the terminal. + ProgressBar *ProgressBar + + // AltScreen puts the program in the alternate screen buffer + // (i.e. the program goes into full window mode). Note that the altscreen will + // be automatically exited when the program quits. + // + // Example: + // + // func (m model) View() tea.View { + // v := tea.NewView("Hello, World!") + // v.AltScreen = true + // return v + // } + // + AltScreen bool + + // ReportFocus enables reporting when the terminal gains and loses focus. + // When this is enabled [FocusMsg] and [BlurMsg] messages will be sent to + // your Update method. + // + // Note that while most terminals and multiplexers support focus reporting, + // some do not. Also note that tmux needs to be configured to report focus + // events. + ReportFocus bool + + // DisableBracketedPasteMode disables bracketed paste mode for this view. + DisableBracketedPasteMode bool + + // MouseMode sets the mouse mode for this view. It can be one of + // [MouseModeNone], [MouseModeCellMotion], or [MouseModeAllMotion]. + MouseMode MouseMode + + // KeyboardEnhancements describes what keyboard enhancement features Bubble + // Tea should request from the terminal. + // + // Bubble Tea supports requesting the following keyboard enhancement features: + // - ReportEventTypes: requests the terminal to report key repeat and + // release events. + // + // If the terminal supports any of these features, your program will + // receive a [KeyboardEnhancementsMsg] that indicates which features are + // available. + KeyboardEnhancements KeyboardEnhancements +} + +// KeyboardEnhancements describes the requested keyboard enhancement features. +// If the terminal supports any of them, it will respond with a +// [KeyboardEnhancementsMsg] that indicates which features are supported. + +// KeyboardEnhancements defines different keyboard enhancement features that +// can be requested from the terminal. + +// KeyboardEnhancements defines different keyboard enhancement features that +// can be requested from the terminal. +// +// By default, Bubble Tea requests basic key disambiguation features from the +// terminal. If the terminal supports keyboard enhancements, or any of its +// additional features, it will respond with a [KeyboardEnhancementsMsg] that +// indicates which features are supported. +// +// Example: +// +// ```go +// func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// switch msg := msg.(type) { +// case tea.KeyboardEnhancementsMsg: +// // We have basic key disambiguation support. +// // We can handle "shift+enter", "ctrl+i", etc. +// m.keyboardEnhancements = msg +// if msg.ReportEventTypes { +// // Even better! We can now handle key repeat and release events. +// } +// case tea.KeyPressMsg: +// switch msg.String() { +// case "shift+enter": +// // Handle shift+enter +// // This would not be possible without keyboard enhancements. +// case "ctrl+j": +// // Handle ctrl+j +// } +// case tea.KeyReleaseMsg: +// // Whoa! A key was released! +// } +// +// return m, nil +// } +// +// func (m model) View() tea.View { +// v := tea.NewView("Press some keys!") +// // Request reporting key repeat and release events. +// v.KeyboardEnhancements.ReportEventTypes = true +// return v +// } +// ``` +type KeyboardEnhancements struct { + // ReportEventTypes requests the terminal to report key repeat and release + // events. + // If supported, your program will receive [KeyReleaseMsg]s and + // [KeyPressMsg] with the [Key.IsRepeat] field set indicating that this is + // a it's part of a key repeat sequence. + ReportEventTypes bool + + // ReportAlternateKeys requests the terminal to report alternate key values + // in addition to the main ones. + // Note that only key events represented as escape codes will affected by + // this enhancement. + ReportAlternateKeys bool + + // ReportAllKeysAsEscapeCodes requests the terminal to report all key + // events, including plain text keys, as escape codes. + // When this is enabled, text won't be sent as plain text but instead as + // escape codes that encode the key value and modifiers. + ReportAllKeysAsEscapeCodes bool + + // ReportAssociatedText requests the terminal to report the text associated + // with key events. + // Note that this is an enhancement to + // [KeyboardEnhancements.ReportAllKeysAsEscapeCodes] and only has an effect + // if that is enabled. + ReportAssociatedText bool +} + +// SetContent is a helper method to set the content of a [View] with a styled +// string. A styled string represents text with styles and hyperlinks encoded +// as ANSI escape codes. +// +// Example: +// +// ```go +// var v tea.View +// v.SetContent("Hello, World!") +// ``` +func (v *View) SetContent(s string) { + v.Content = s +} + +// MouseMode represents the mouse mode of a view. +type MouseMode int + +const ( + // MouseModeNone disables mouse events. + MouseModeNone MouseMode = iota + + // MouseModeCellMotion enables mouse click, release, and wheel events. + // Mouse movement events are also captured if a mouse button is pressed + // (i.e., drag events). Cell motion mode is better supported than all + // motion mode. + // + // This will try to enable the mouse in extended mode (SGR), if that is not + // supported by the terminal it will fall back to normal mode (X10). + MouseModeCellMotion + + // MouseModeAllMotion enables all mouse events, including click, release, + // wheel, and movement events. You will receive mouse movement events even + // when no buttons are pressed. + // + // This will try to enable the mouse in extended mode (SGR), if that is not + // supported by the terminal it will fall back to normal mode (X10). + MouseModeAllMotion +) + +// ProgressBarState represents the state of the progress bar. +type ProgressBarState int + +// Progress bar states. +const ( + ProgressBarNone ProgressBarState = iota + ProgressBarDefault + ProgressBarError + ProgressBarIndeterminate + ProgressBarWarning +) + +// String return a human-readable value for the given [ProgressBarState]. +func (s ProgressBarState) String() string { + return [...]string{ + "None", + "Default", + "Error", + "Indeterminate", + "Warning", + }[s] +} + +// ProgressBar represents the terminal progress bar. +// +// Support depends on the terminal. +// +// See https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences +type ProgressBar struct { + // State is the current state of the progress bar. It can be one of + // [ProgressBarNone], [ProgressBarDefault], [ProgressBarError], + // [ProgressBarIndeterminate], and [ProgressBarWarn]. + State ProgressBarState + // Value is the current value of the progress bar. It should be between + // 0 and 100. + Value int +} + +// NewProgressBar returns a new progress bar with the given state and value. +// The value is ignored if the state is [ProgressBarNone] or +// [ProgressBarIndeterminate]. +func NewProgressBar(state ProgressBarState, value int) *ProgressBar { + return &ProgressBar{ + State: state, + Value: min(max(value, 0), 100), + } +} + +// Cursor represents a cursor on the terminal screen. +type Cursor struct { + // Position is a [Position] that determines the cursor's position on the + // screen relative to the top left corner of the frame. + Position + + // Color is a [color.Color] that determines the cursor's color. + Color color.Color + + // Shape is a [CursorShape] that determines the cursor's shape. + Shape CursorShape + + // Blink is a boolean that determines whether the cursor should blink. + Blink bool +} + +// NewCursor returns a new cursor with the default settings and the given +// position. +func NewCursor(x, y int) *Cursor { + return &Cursor{ + Position: Position{X: x, Y: y}, + Color: nil, + Shape: CursorBlock, + Blink: true, + } +} + +// Cmd is an IO operation that returns a message when it's complete. If it's +// nil it's considered a no-op. Use it for things like HTTP requests, timers, +// saving and loading from disk, and so on. +// +// Note that there's almost never a reason to use a command to send a message +// to another part of your program. That can almost always be done in the +// update function. +type Cmd func() Msg + +// channelHandlers manages the series of channels returned by various processes. +// It allows us to wait for those processes to terminate before exiting the +// program. +type channelHandlers struct { + handlers []chan struct{} + mu sync.RWMutex +} + +// Adds a channel to the list of handlers. We wait for all handlers to terminate +// gracefully on shutdown. +func (h *channelHandlers) add(ch chan struct{}) { + h.mu.Lock() + h.handlers = append(h.handlers, ch) + h.mu.Unlock() +} + +// shutdown waits for all handlers to terminate. +func (h *channelHandlers) shutdown() { + var wg sync.WaitGroup + + h.mu.RLock() + defer h.mu.RUnlock() + + for _, ch := range h.handlers { + wg.Add(1) + go func(ch chan struct{}) { + <-ch + wg.Done() + }(ch) + } + wg.Wait() +} + +// Program is a terminal user interface. +type Program struct { + // disableInput disables all input. This is useful for programs that + // don't need input, like a progress bar or a spinner. + disableInput bool + + // disableSignalHandler disables the signal handler that Bubble Tea sets up + // for Programs. This is useful if you want to handle signals yourself. + disableSignalHandler bool + + // disableCatchPanics disables the panic catching that Bubble Tea does by + // default. If panic catching is disabled the terminal will be in a fairly + // unusable state after a panic because Bubble Tea will not perform its usual + // cleanup on exit. + disableCatchPanics bool + + // filter supplies an event filter that will be invoked before Bubble Tea + // processes a tea.Msg. The event filter can return any tea.Msg which will + // then get handled by Bubble Tea instead of the original event. If the + // event filter returns nil, the event will be ignored and Bubble Tea will + // not process it. + // + // As an example, this could be used to prevent a program from shutting + // down if there are unsaved changes. + // + // Example: + // + // func filter(m tea.Model, msg tea.Msg) tea.Msg { + // if _, ok := msg.(tea.QuitMsg); !ok { + // return msg + // } + // + // model := m.(myModel) + // if model.hasChanges { + // return nil + // } + // + // return msg + // } + // + // p := tea.NewProgram(Model{}); + // p.filter = filter + // + // if _,err := p.Run(context.Background()); err != nil { + // fmt.Println("Error running program:", err) + // os.Exit(1) + // } + filter func(Model, Msg) Msg + + // fps sets a custom maximum fps at which the renderer should run. If less + // than 1, the default value of 60 will be used. If over 120, the fps will + // be capped at 120. + fps int + + // initialModel is the initial model for the program and is the only + // required field when creating a new program. + initialModel Model + + // disableRenderer prevents the program from rendering to the terminal. + // This can be useful for running daemon-like programs that don't require a + // UI but still want to take advantage of Bubble Tea's architecture. + disableRenderer bool + + // handlers is a list of channels that need to be waited on before the + // program can exit. + handlers channelHandlers + + // ctx is the programs's internal context for signalling internal teardown. + // It is built and derived from the externalCtx in NewProgram(). + ctx context.Context + cancel context.CancelFunc + + // externalCtx is a context that was passed in via WithContext, otherwise defaulting + // to ctx.Background() (in case it was not), the internal context is derived from it. + externalCtx context.Context + + msgs chan Msg + errs chan error + finished chan struct{} + shutdownOnce sync.Once + + profile *colorprofile.Profile // the terminal color profile + + // where to send output, this will usually be os.Stdout. + output io.Writer + outputBuf bytes.Buffer // buffer used to queue commands to be sent to the output + + // ttyOutput is null if output is not a TTY. + ttyOutput term.File + previousOutputState *term.State + renderer renderer + + // the environment variables for the program, defaults to os.Environ(). + environ uv.Environ + // the program's logger for debugging. + logger uv.Logger + + // where to read inputs from, this will usually be os.Stdin. + input io.Reader + // ttyInput is null if input is not a TTY. + ttyInput term.File + previousTtyInputState *term.State + cancelReader cancelreader.CancelReader + inputScanner *uv.TerminalReader + readLoopDone chan struct{} + + // modes keeps track of terminal modes that have been enabled or disabled. + ignoreSignals uint32 + + // ticker is the ticker that will be used to write to the renderer. + ticker *time.Ticker + + // once is used to stop the renderer. + once sync.Once + + // rendererDone is used to stop the renderer. + rendererDone chan struct{} + + // Initial window size. Mainly used for testing. + width, height int + + // whether to use hard tabs to optimize cursor movements + useHardTabs bool + // whether to use backspace to optimize cursor movements + useBackspace bool + + mu sync.Mutex +} + +// Quit is a special command that tells the Bubble Tea program to exit. +func Quit() Msg { + return QuitMsg{} +} + +// QuitMsg signals that the program should quit. You can send a [QuitMsg] with +// [Quit]. +type QuitMsg struct{} + +// Suspend is a special command that tells the Bubble Tea program to suspend. +func Suspend() Msg { + return SuspendMsg{} +} + +// SuspendMsg signals the program should suspend. +// This usually happens when ctrl+z is pressed on common programs, but since +// bubbletea puts the terminal in raw mode, we need to handle it in a +// per-program basis. +// +// You can send this message with [Suspend()]. +type SuspendMsg struct{} + +// ResumeMsg can be listen to do something once a program is resumed back +// from a suspend state. +type ResumeMsg struct{} + +// InterruptMsg signals the program should suspend. +// This usually happens when ctrl+c is pressed on common programs, but since +// bubbletea puts the terminal in raw mode, we need to handle it in a +// per-program basis. +// +// You can send this message with [Interrupt()]. +type InterruptMsg struct{} + +// Interrupt is a special command that tells the Bubble Tea program to +// interrupt. +func Interrupt() Msg { + return InterruptMsg{} +} + +// NewProgram creates a new [Program]. +func NewProgram(model Model, opts ...ProgramOption) *Program { + p := &Program{ + initialModel: model, + msgs: make(chan Msg), + errs: make(chan error, 1), + rendererDone: make(chan struct{}), + } + + // Apply all options to the program. + for _, opt := range opts { + opt(p) + } + + // A context can be provided with a ProgramOption, but if none was provided + // we'll use the default background context. + if p.externalCtx == nil { + p.externalCtx = context.Background() + } + // Initialize context and teardown channel. + p.ctx, p.cancel = context.WithCancel(p.externalCtx) + + // if no output was set, set it to stdout + if p.output == nil { + p.output = os.Stdout + } + + // if no environment was set, set it to os.Environ() + if p.environ == nil { + p.environ = os.Environ() + } + + if p.fps < 1 { + p.fps = defaultFPS + } else if p.fps > maxFPS { + p.fps = maxFPS + } + + tracePath, traceOk := os.LookupEnv("TEA_TRACE") + if traceOk && len(tracePath) > 0 { + // We have a trace filepath. + if f, err := os.OpenFile(tracePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600); err == nil { + p.logger = log.New(f, "bubbletea: ", log.LstdFlags|log.Lshortfile) + } + } + + return p +} + +func (p *Program) handleSignals() chan struct{} { + ch := make(chan struct{}) + + // Listen for SIGINT and SIGTERM. + // + // In most cases ^C will not send an interrupt because the terminal will be + // in raw mode and ^C will be captured as a keystroke and sent along to + // Program.Update as a KeyMsg. When input is not a TTY, however, ^C will be + // caught here. + // + // SIGTERM is sent by unix utilities (like kill) to terminate a process. + go func() { + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) + defer func() { + signal.Stop(sig) + close(ch) + }() + + for { + select { + case <-p.ctx.Done(): + return + + case s := <-sig: + if atomic.LoadUint32(&p.ignoreSignals) == 0 { + switch s { + case syscall.SIGINT: + p.msgs <- InterruptMsg{} + default: + p.msgs <- QuitMsg{} + } + return + } + } + } + }() + + return ch +} + +// handleResize handles terminal resize events. +func (p *Program) handleResize() chan struct{} { + ch := make(chan struct{}) + + if p.ttyOutput != nil { + // Listen for window resizes. + go p.listenForResize(ch) + } else { + close(ch) + } + + return ch +} + +// handleCommands runs commands in a goroutine and sends the result to the +// program's message channel. +func (p *Program) handleCommands(cmds chan Cmd) chan struct{} { + ch := make(chan struct{}) + + go func() { + defer close(ch) + + for { + select { + case <-p.ctx.Done(): + return + + case cmd := <-cmds: + if cmd == nil { + continue + } + + // Don't wait on these goroutines, otherwise the shutdown + // latency would get too large as a Cmd can run for some time + // (e.g. tick commands that sleep for half a second). It's not + // possible to cancel them so we'll have to leak the goroutine + // until Cmd returns. + go func() { + // Recover from panics. + if !p.disableCatchPanics { + defer func() { + if r := recover(); r != nil { + p.recoverFromPanic(r) + } + }() + } + + msg := cmd() // this can be long. + p.Send(msg) + }() + } + } + }() + + return ch +} + +// eventLoop is the central message loop. It receives and handles the default +// Bubble Tea messages, update the model and triggers redraws. +func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { + for { + select { + case <-p.ctx.Done(): + return model, nil + + case err := <-p.errs: + return model, err + + case msg := <-p.msgs: + msg = p.translateInputEvent(msg) + + // Filter messages. + if p.filter != nil { + msg = p.filter(model, msg) + } + if msg == nil { + continue + } + + // Handle special internal messages. + switch msg := msg.(type) { + case QuitMsg: + return model, nil + + case InterruptMsg: + return model, ErrInterrupted + + case SuspendMsg: + if suspendSupported { + p.suspend() + } + + case CapabilityMsg: + switch msg.Content { + case "RGB", "Tc": + if *p.profile != colorprofile.TrueColor { + tc := colorprofile.TrueColor + p.profile = &tc + go p.Send(ColorProfileMsg{*p.profile}) + } + } + + case ModeReportMsg: + switch msg.Mode { + case ansi.ModeSynchronizedOutput: + if msg.Value == ansi.ModeReset { + // The terminal supports synchronized output and it's + // currently disabled, so we can enable it on the renderer. + p.renderer.setSyncdUpdates(true) + } + case ansi.ModeUnicodeCore: + if msg.Value == ansi.ModeReset || msg.Value == ansi.ModeSet || msg.Value == ansi.ModePermanentlySet { + p.renderer.setWidthMethod(ansi.GraphemeWidth) + } + } + + case MouseMsg: + switch msg.(type) { + case MouseClickMsg, MouseReleaseMsg, MouseWheelMsg, MouseMotionMsg: + // Only send mouse messages to the renderer if they are an + // actual mouse event. + if cmd := p.renderer.onMouse(msg); cmd != nil { + go p.Send(cmd()) + } + } + + case readClipboardMsg: + p.execute(ansi.RequestSystemClipboard) + + case setClipboardMsg: + p.execute(ansi.SetSystemClipboard(string(msg))) + + case readPrimaryClipboardMsg: + p.execute(ansi.RequestPrimaryClipboard) + + case setPrimaryClipboardMsg: + p.execute(ansi.SetPrimaryClipboard(string(msg))) + + case backgroundColorMsg: + p.execute(ansi.RequestBackgroundColor) + + case foregroundColorMsg: + p.execute(ansi.RequestForegroundColor) + + case cursorColorMsg: + p.execute(ansi.RequestCursorColor) + + case execMsg: + // NB: this blocks. + p.exec(msg.cmd, msg.fn) + + case terminalVersion: + p.execute(ansi.RequestNameVersion) + + case requestCapabilityMsg: + p.execute(ansi.RequestTermcap(string(msg))) + + case BatchMsg: + go p.execBatchMsg(msg) + continue + + case sequenceMsg: + go p.execSequenceMsg(msg) + continue + + case WindowSizeMsg: + p.renderer.resize(msg.Width, msg.Height) + + case windowSizeMsg: + go p.checkResize() + + case requestCursorPosMsg: + p.execute(ansi.RequestCursorPositionReport) + + case RawMsg: + p.execute(fmt.Sprint(msg.Msg)) + + case printLineMessage: + p.renderer.insertAbove(msg.messageBody) //nolint:errcheck,gosec + + case clearScreenMsg: + p.renderer.clearScreen() + + case ColorProfileMsg: + p.renderer.setColorProfile(msg.Profile) + } + + var cmd Cmd + model, cmd = model.Update(msg) // run update + + select { + case <-p.ctx.Done(): + return model, nil + case cmds <- cmd: // process command (if any) + } + + p.render(model) // render view + } + } +} + +// render renders the given view to the renderer. +func (p *Program) render(model Model) { + if p.renderer != nil { + p.renderer.render(model.View()) // send view to renderer + } +} + +func (p *Program) execSequenceMsg(msg sequenceMsg) { + if !p.disableCatchPanics { + defer func() { + if r := recover(); r != nil { + p.recoverFromGoPanic(r) + } + }() + } + + // Execute commands one at a time, in order. + for _, cmd := range msg { + if cmd == nil { + continue + } + msg := cmd() + switch msg := msg.(type) { + case BatchMsg: + p.execBatchMsg(msg) + case sequenceMsg: + p.execSequenceMsg(msg) + default: + p.Send(msg) + } + } +} + +func (p *Program) execBatchMsg(msg BatchMsg) { + if !p.disableCatchPanics { + defer func() { + if r := recover(); r != nil { + p.recoverFromGoPanic(r) + } + }() + } + + // Execute commands one at a time. + var wg sync.WaitGroup + for _, cmd := range msg { + if cmd == nil { + continue + } + wg.Add(1) + go func() { + defer wg.Done() + + if !p.disableCatchPanics { + defer func() { + if r := recover(); r != nil { + p.recoverFromGoPanic(r) + } + }() + } + + msg := cmd() + switch msg := msg.(type) { + case BatchMsg: + p.execBatchMsg(msg) + case sequenceMsg: + p.execSequenceMsg(msg) + default: + p.Send(msg) + } + }() + } + + wg.Wait() // wait for all commands from batch msg to finish +} + +// shouldQuerySynchronizedOutput determines whether the terminal should be +// queried for various capabilities. +// +// This function checks for terminals that are known to support mode 2026, +// while excluding SSH sessions which may be unreliable, unless it's a +// known-good terminal like Windows Terminal. +// +// The function returns true for: +// - Terminals without TERM_PROGRAM set and not in SSH sessions +// - Windows Terminal (WT_SESSION is set) +// - Terminals with TERM_PROGRAM set (except Apple Terminal) and not in SSH sessions +// - Specific terminal types: ghostty, wezterm, alacritty, kitty, rio +func shouldQuerySynchronizedOutput(environ uv.Environ) bool { + termType := environ.Getenv("TERM") + termProg, okTermProg := environ.LookupEnv("TERM_PROGRAM") + _, okSSHTTY := environ.LookupEnv("SSH_TTY") + _, okWTSession := environ.LookupEnv("WT_SESSION") + + return (!okTermProg && !okSSHTTY) || + okWTSession || + (okTermProg && !strings.Contains(termProg, "Apple") && !okSSHTTY) || + strings.Contains(termType, "ghostty") || + strings.Contains(termType, "wezterm") || + strings.Contains(termType, "alacritty") || + strings.Contains(termType, "kitty") || + strings.Contains(termType, "rio") +} + +// Run initializes the program and runs its event loops, blocking until it gets +// terminated by either [Program.Quit], [Program.Kill], or its signal handler. +// Returns the final model. +func (p *Program) Run() (returnModel Model, returnErr error) { + if p.initialModel == nil { + return nil, errors.New("bubbletea: InitialModel cannot be nil") + } + + // Initialize context and teardown channel. + p.handlers = channelHandlers{} + cmds := make(chan Cmd) + + p.finished = make(chan struct{}) + defer func() { + close(p.finished) + }() + + defer p.cancel() + + if p.disableInput { + p.input = nil + } else if p.input == nil { + p.input = os.Stdin + if !term.IsTerminal(os.Stdin.Fd()) { + ttyIn, _, err := OpenTTY() + if err != nil { + return p.initialModel, fmt.Errorf("bubbletea: error opening TTY: %w", err) + } + p.input = ttyIn + } + } + + // Handle signals. + if !p.disableSignalHandler { + p.handlers.add(p.handleSignals()) + } + + // Recover from panics. + if !p.disableCatchPanics { + defer func() { + if r := recover(); r != nil { + returnErr = fmt.Errorf("%w: %w", ErrProgramKilled, ErrProgramPanic) + p.recoverFromPanic(r) + } + }() + } + + // Check if output is a TTY before entering raw mode, hiding the cursor and + // so on. + if err := p.initTerminal(); err != nil { + return p.initialModel, err + } + + // Get the initial window size. + width, height := p.width, p.height + if p.ttyOutput != nil { + // Set the initial size of the terminal. + w, h, err := term.GetSize(p.ttyOutput.Fd()) + if err != nil { + return p.initialModel, fmt.Errorf("bubbletea: error getting terminal size: %w", err) + } + + width, height = w, h + } + + p.width, p.height = width, height + resizeMsg := WindowSizeMsg{Width: p.width, Height: p.height} + + if p.renderer == nil { + if p.disableRenderer { + p.renderer = &nilRenderer{} + } else { + // If no renderer is set use the cursed one. + r := newCursedRenderer( + p.output, + p.environ, + p.width, + p.height, + ) + r.setLogger(p.logger) + // XXX: This breaks many things especially when we want the output + // to be compatible with terminals that are not necessary a TTY. + // This was originally done to work around a Wish emulated-pty + // issue where when a PTY session is detected, and we don't + // allocate a real PTY, the terminal settings (Termios and WinCon) + // don't change and the we end up working in cooked mode instead of + // raw mode. See issue #1572. + mapNl := runtime.GOOS != "windows" && p.ttyInput == nil + r.setOptimizations(p.useHardTabs, p.useBackspace, mapNl) + p.renderer = r + } + } + + // Get the color profile and send it to the program. + if p.profile == nil { + cp := colorprofile.Detect(p.output, p.environ) + p.profile = &cp + } + + // Set the color profile on the renderer and send it to the program. + p.renderer.setColorProfile(*p.profile) + go p.Send(ColorProfileMsg{*p.profile}) + + // Send the initial size to the program. + go p.Send(resizeMsg) + p.renderer.resize(resizeMsg.Width, resizeMsg.Height) + + // Send the environment variables used by the program. + go p.Send(EnvMsg(p.environ)) + + // Init the input reader and initial model. + model := p.initialModel + if p.input != nil { + if err := p.initInputReader(false); err != nil { + return model, err + } + } + + // Start the renderer. + p.startRenderer() + + if !p.disableRenderer && shouldQuerySynchronizedOutput(p.environ) { + // Query for synchronized updates support (mode 2026) and unicode core + // (mode 2027). If the terminal supports it, the renderer will enable + // it once we get the response. + p.execute(ansi.RequestModeSynchronizedOutput + + ansi.RequestModeUnicodeCore) + } + + // Initialize the program. + initCmd := model.Init() + if initCmd != nil { + ch := make(chan struct{}) + p.handlers.add(ch) + + go func() { + defer close(ch) + + select { + case cmds <- initCmd: + case <-p.ctx.Done(): + } + }() + } + + // Render the initial view. + p.render(model) + + // Handle resize events. + p.handlers.add(p.handleResize()) + + // Process commands. + p.handlers.add(p.handleCommands(cmds)) + + // Run event loop, handle updates and draw. + var err error + model, err = p.eventLoop(model, cmds) + + if err == nil && len(p.errs) > 0 { + err = <-p.errs // Drain a leftover error in case eventLoop crashed. + } + + killed := p.externalCtx.Err() != nil || p.ctx.Err() != nil || err != nil + if killed { + if err == nil && p.externalCtx.Err() != nil { + // Return also as context error the cancellation of an external context. + // This is the context the user knows about and should be able to act on. + err = fmt.Errorf("%w: %w", ErrProgramKilled, p.externalCtx.Err()) + } else if err == nil && p.ctx.Err() != nil { + // Return only that the program was killed (not the internal mechanism). + // The user does not know or need to care about the internal program context. + err = ErrProgramKilled + } else { + // Return that the program was killed and also the error that caused it. + err = fmt.Errorf("%w: %w", ErrProgramKilled, err) + } + } else { + // Graceful shutdown of the program (not killed): + // Ensure we rendered the final state of the model. + p.render(model) + } + + // Restore terminal state. + p.shutdown(killed) + + return model, err +} + +// Send sends a message to the main update function, effectively allowing +// messages to be injected from outside the program for interoperability +// purposes. +// +// If the program hasn't started yet this will be a blocking operation. +// If the program has already been terminated this will be a no-op, so it's safe +// to send messages after the program has exited. +func (p *Program) Send(msg Msg) { + select { + case <-p.ctx.Done(): + case p.msgs <- msg: + } +} + +// Quit is a convenience function for quitting Bubble Tea programs. Use it +// when you need to shut down a Bubble Tea program from the outside. +// +// If you wish to quit from within a Bubble Tea program use the Quit command. +// +// If the program is not running this will be a no-op, so it's safe to call +// if the program is unstarted or has already exited. +func (p *Program) Quit() { + p.Send(Quit()) +} + +// Kill stops the program immediately and restores the former terminal state. +// The final render that you would normally see when quitting will be skipped. +// [program.Run] returns a [ErrProgramKilled] error. +func (p *Program) Kill() { + p.shutdown(true) +} + +// Wait waits/blocks until the underlying Program finished shutting down. +func (p *Program) Wait() { + <-p.finished +} + +// execute writes the given sequence to the program output. +func (p *Program) execute(seq string) { + p.mu.Lock() + _, _ = p.outputBuf.WriteString(seq) + p.mu.Unlock() +} + +// flush flushes the output buffer to the program output. +func (p *Program) flush() error { + p.mu.Lock() + defer p.mu.Unlock() + + if p.outputBuf.Len() == 0 { + return nil + } + if p.logger != nil { + p.logger.Printf("output: %q", p.outputBuf.String()) + } + _, err := p.output.Write(p.outputBuf.Bytes()) + p.outputBuf.Reset() + if err != nil { + return fmt.Errorf("error writing to output: %w", err) + } + return nil +} + +// shutdown performs operations to free up resources and restore the terminal +// to its original state. +func (p *Program) shutdown(kill bool) { + p.shutdownOnce.Do(func() { + p.cancel() + + // Wait for all handlers to finish. + p.handlers.shutdown() + + // Check if the cancel reader has been setup before waiting and closing. + if p.cancelReader != nil { + // Wait for input loop to finish. + if p.cancelReader.Cancel() { + if !kill { + p.waitForReadLoop() + } + } + _ = p.cancelReader.Close() + } + + if p.renderer != nil { + p.stopRenderer(kill) + } + + _ = p.restoreTerminalState() + }) +} + +// recoverFromPanic recovers from a panic, prints the stack trace, and restores +// the terminal to a usable state. +func (p *Program) recoverFromPanic(r interface{}) { + select { + case p.errs <- ErrProgramPanic: + default: + } + p.shutdown(true) // Ok to call here, p.Run() cannot do it anymore. + // We use "\r\n" to ensure the output is formatted even when restoring the + // terminal does not work or when raw mode is still active. + rec := strings.ReplaceAll(fmt.Sprintf("%s", r), "\n", "\r\n") + fmt.Fprintf(os.Stderr, "Caught panic:\r\n\r\n%s\r\n\r\nRestoring terminal...\r\n\r\n", rec) + stack := strings.ReplaceAll(fmt.Sprintf("%s\n", debug.Stack()), "\n", "\r\n") + fmt.Fprint(os.Stderr, stack) + if v, err := strconv.ParseBool(os.Getenv("TEA_DEBUG")); err == nil && v { + f, err := os.Create(fmt.Sprintf("bubbletea-panic-%d.log", time.Now().Unix())) + if err == nil { + defer f.Close() //nolint:errcheck + fmt.Fprintln(f, rec) //nolint:errcheck + fmt.Fprintln(f) //nolint:errcheck + fmt.Fprintln(f, stack) //nolint:errcheck + } + } +} + +// recoverFromGoPanic recovers from a goroutine panic, prints a stack trace and +// signals for the program to be killed and terminal restored to a usable state. +func (p *Program) recoverFromGoPanic(r interface{}) { + select { + case p.errs <- ErrProgramPanic: + default: + } + p.cancel() + // We use "\r\n" to ensure the output is formatted even when restoring the + // terminal does not work or when raw mode is still active. + rec := strings.ReplaceAll(fmt.Sprintf("%s", r), "\n", "\r\n") + fmt.Fprintf(os.Stderr, "Caught panic:\r\n\r\n%s\r\n\r\nRestoring terminal...\r\n\r\n", rec) + stack := strings.ReplaceAll(fmt.Sprintf("%s\n", debug.Stack()), "\n", "\r\n") + fmt.Fprint(os.Stderr, stack) + if v, err := strconv.ParseBool(os.Getenv("TEA_DEBUG")); err == nil && v { + f, err := os.Create(fmt.Sprintf("bubbletea-panic-%d.log", time.Now().Unix())) + if err == nil { + defer f.Close() //nolint:errcheck + fmt.Fprintln(f, rec) //nolint:errcheck + fmt.Fprintln(f) //nolint:errcheck + fmt.Fprintln(f, stack) //nolint:errcheck + } + } +} + +// ReleaseTerminal restores the original terminal state and cancels the input +// reader. You can return control to the Program with RestoreTerminal. +func (p *Program) ReleaseTerminal() error { + return p.releaseTerminal(false) +} + +func (p *Program) releaseTerminal(reset bool) error { + atomic.StoreUint32(&p.ignoreSignals, 1) + if p.cancelReader != nil { + p.cancelReader.Cancel() + } + + p.waitForReadLoop() + + if p.renderer != nil { + p.stopRenderer(false) + if reset { + p.renderer.reset() + } + } + + return p.restoreTerminalState() +} + +// RestoreTerminal reinitializes the Program's input reader, restores the +// terminal to the former state when the program was running, and repaints. +// Use it to reinitialize a Program after running ReleaseTerminal. +func (p *Program) RestoreTerminal() error { + atomic.StoreUint32(&p.ignoreSignals, 0) + + if err := p.initTerminal(); err != nil { + return err + } + if err := p.initInputReader(false); err != nil { + return err + } + + p.startRenderer() + + // If the output is a terminal, it may have been resized while another + // process was at the foreground, in which case we may not have received + // SIGWINCH. Detect any size change now and propagate the new size as + // needed. + go p.checkResize() + + // Flush queued commands. + return p.flush() +} + +// Println prints above the Program. This output is unmanaged by the program +// and will persist across renders by the Program. +// +// If the altscreen is active no output will be printed. +func (p *Program) Println(args ...any) { + p.msgs <- printLineMessage{ + messageBody: fmt.Sprint(args...), + } +} + +// Printf prints above the Program. It takes a format template followed by +// values similar to fmt.Printf. This output is unmanaged by the program and +// will persist across renders by the Program. +// +// Unlike fmt.Printf (but similar to log.Printf) the message will be print on +// its own line. +// +// If the altscreen is active no output will be printed. +func (p *Program) Printf(template string, args ...any) { + p.msgs <- printLineMessage{ + messageBody: fmt.Sprintf(template, args...), + } +} + +// startRenderer starts the renderer. +func (p *Program) startRenderer() { + framerate := time.Second / time.Duration(p.fps) + if p.ticker == nil { + p.ticker = time.NewTicker(framerate) + } else { + // If the ticker already exists, it has been stopped and we need to + // reset it. + p.ticker.Reset(framerate) + } + + // Since the renderer can be restarted after a stop, we need to reset + // the done channel and its corresponding sync.Once. + p.once = sync.Once{} + + // Start the renderer. + p.renderer.start() + go func() { + for { + select { + case <-p.rendererDone: + p.ticker.Stop() + return + + case <-p.ticker.C: + _ = p.flush() + _ = p.renderer.flush(false) + } + } + }() +} + +// stopRenderer stops the renderer. +// If kill is true, the renderer will be stopped immediately without flushing +// the last frame. +func (p *Program) stopRenderer(kill bool) { + // Stop the renderer before acquiring the mutex to avoid a deadlock. + p.once.Do(func() { + p.rendererDone <- struct{}{} + }) + + if !kill { + // flush locks the mutex + _ = p.renderer.flush(true) + } + + _ = p.renderer.close() +} diff --git a/vendor/charm.land/bubbletea/v2/termcap.go b/vendor/charm.land/bubbletea/v2/termcap.go new file mode 100644 index 000000000..c66d95ae5 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/termcap.go @@ -0,0 +1,48 @@ +package tea + +// requestCapabilityMsg is an internal message that requests the terminal to +// send its Termcap/Terminfo response. +type requestCapabilityMsg string + +// RequestCapability is a command that requests the terminal to send its +// Termcap/Terminfo response for the given capability. +// +// Bubble Tea recognizes the following capabilities and will use them to +// upgrade the program's color profile: +// - "RGB" Xterm direct color +// - "Tc" True color support +// +// Note: that some terminal's like Apple's Terminal.app do not support this and +// will send the wrong response to the terminal breaking the program's output. +// +// When the Bubble Tea advertises a non-TrueColor profile, you can use this +// command to query the terminal for its color capabilities. Example: +// +// switch msg := msg.(type) { +// case tea.ColorProfileMsg: +// if msg.Profile != colorprofile.TrueColor { +// return m, tea.Batch( +// tea.RequestCapability("RGB"), +// tea.RequestCapability("Tc"), +// ) +// } +// } +func RequestCapability(s string) Cmd { + return func() Msg { + return requestCapabilityMsg(s) + } +} + +// CapabilityMsg represents a Termcap/Terminfo response event. Termcap +// responses are generated by the terminal in response to RequestTermcap +// (XTGETTCAP) requests. +// +// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands +type CapabilityMsg struct { + Content string +} + +// String returns the capability content as a string. +func (c CapabilityMsg) String() string { + return c.Content +} diff --git a/vendor/charm.land/bubbletea/v2/termios_bsd.go b/vendor/charm.land/bubbletea/v2/termios_bsd.go new file mode 100644 index 000000000..cc716e585 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/termios_bsd.go @@ -0,0 +1,13 @@ +//go:build dragonfly || freebsd +// +build dragonfly freebsd + +package tea + +import ( + "github.com/charmbracelet/x/term" + "golang.org/x/sys/unix" +) + +func (p *Program) checkOptimizedMovements(s *term.State) { + p.useHardTabs = s.Oflag&unix.TABDLY == unix.TAB0 +} diff --git a/vendor/charm.land/bubbletea/v2/termios_other.go b/vendor/charm.land/bubbletea/v2/termios_other.go new file mode 100644 index 000000000..d4ede6d04 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/termios_other.go @@ -0,0 +1,8 @@ +//go:build !windows && !darwin && !dragonfly && !freebsd && !linux && !solaris && !aix +// +build !windows,!darwin,!dragonfly,!freebsd,!linux,!solaris,!aix + +package tea + +import "github.com/charmbracelet/x/term" + +func (*Program) checkOptimizedMovements(*term.State) {} diff --git a/vendor/charm.land/bubbletea/v2/termios_unix.go b/vendor/charm.land/bubbletea/v2/termios_unix.go new file mode 100644 index 000000000..3ee0e9948 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/termios_unix.go @@ -0,0 +1,14 @@ +//go:build darwin || linux || solaris || aix +// +build darwin linux solaris aix + +package tea + +import ( + "github.com/charmbracelet/x/term" + "golang.org/x/sys/unix" +) + +func (p *Program) checkOptimizedMovements(s *term.State) { + p.useHardTabs = s.Oflag&unix.TABDLY == unix.TAB0 + p.useBackspace = s.Lflag&unix.BSDLY == unix.BS0 +} diff --git a/vendor/charm.land/bubbletea/v2/termios_windows.go b/vendor/charm.land/bubbletea/v2/termios_windows.go new file mode 100644 index 000000000..c743652c8 --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/termios_windows.go @@ -0,0 +1,11 @@ +//go:build windows +// +build windows + +package tea + +import "github.com/charmbracelet/x/term" + +func (p *Program) checkOptimizedMovements(*term.State) { + p.useHardTabs = true + p.useBackspace = true +} diff --git a/vendor/charm.land/bubbletea/v2/tty.go b/vendor/charm.land/bubbletea/v2/tty.go new file mode 100644 index 000000000..12b86493b --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/tty.go @@ -0,0 +1,136 @@ +package tea + +import ( + "fmt" + "os" + "time" + + uv "github.com/charmbracelet/ultraviolet" + "github.com/charmbracelet/x/term" +) + +func (p *Program) suspend() { + if err := p.releaseTerminal(true); err != nil { + // If we can't release input, abort. + return + } + + suspendProcess() + + _ = p.RestoreTerminal() + go p.Send(ResumeMsg{}) +} + +func (p *Program) initTerminal() error { + if p.disableRenderer { + return nil + } + return p.initInput() +} + +// restoreTerminalState restores the terminal to the state prior to running the +// Bubble Tea program. +func (p *Program) restoreTerminalState() error { + // Flush queued commands. + _ = p.flush() + + return p.restoreInput() +} + +// restoreInput restores the tty input to its original state. +func (p *Program) restoreInput() error { + if p.ttyInput != nil && p.previousTtyInputState != nil { + if err := term.Restore(p.ttyInput.Fd(), p.previousTtyInputState); err != nil { + return fmt.Errorf("bubbletea: error restoring console: %w", err) + } + } + if p.ttyOutput != nil && p.previousOutputState != nil { + if err := term.Restore(p.ttyOutput.Fd(), p.previousOutputState); err != nil { + return fmt.Errorf("bubbletea: error restoring console: %w", err) + } + } + return nil +} + +// initInputReader (re)commences reading inputs. +func (p *Program) initInputReader(cancel bool) error { + if cancel && p.cancelReader != nil { + p.cancelReader.Cancel() + p.waitForReadLoop() + } + + term := p.environ.Getenv("TERM") + + // Initialize the input reader. + // This need to be done after the terminal has been initialized and set to + // raw mode. + + var err error + p.cancelReader, err = uv.NewCancelReader(p.input) + if err != nil { + return fmt.Errorf("bubbletea: could not create cancelable reader: %w", err) + } + + drv := uv.NewTerminalReader(p.cancelReader, term) + drv.SetLogger(p.logger) + p.inputScanner = drv + p.readLoopDone = make(chan struct{}) + + go p.readLoop() + + return nil +} + +func (p *Program) readLoop() { + defer close(p.readLoopDone) + + if err := p.inputScanner.StreamEvents(p.ctx, p.msgs); err != nil { + select { + case <-p.ctx.Done(): + return + case p.errs <- err: + } + } +} + +// waitForReadLoop waits for the cancelReader to finish its read loop. +func (p *Program) waitForReadLoop() { + select { + case <-p.readLoopDone: + case <-time.After(500 * time.Millisecond): //nolint:mnd + // The read loop hangs, which means the input + // cancelReader's cancel function has returned true even + // though it was not able to cancel the read. + } +} + +// checkResize detects the current size of the output and informs the program +// via a WindowSizeMsg. +func (p *Program) checkResize() { + if p.ttyOutput == nil { + // can't query window size + return + } + + w, h, err := term.GetSize(p.ttyOutput.Fd()) + if err != nil { + select { + case <-p.ctx.Done(): + case p.errs <- err: + } + + return + } + + p.width, p.height = w, h + p.Send(WindowSizeMsg{Width: w, Height: h}) +} + +// OpenTTY opens the running terminal's TTY for reading and writing. +func OpenTTY() (*os.File, *os.File, error) { + in, out, err := uv.OpenTTY() + if err != nil { + return nil, nil, fmt.Errorf("bubbletea: could not open TTY: %w", err) + } + return in, out, nil +} diff --git a/vendor/charm.land/bubbletea/v2/tty_unix.go b/vendor/charm.land/bubbletea/v2/tty_unix.go new file mode 100644 index 000000000..4981a560a --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/tty_unix.go @@ -0,0 +1,47 @@ +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || aix || zos +// +build darwin dragonfly freebsd linux netbsd openbsd solaris aix zos + +package tea + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/charmbracelet/x/term" +) + +func (p *Program) initInput() (err error) { + // Check if input is a terminal + if f, ok := p.input.(term.File); ok && term.IsTerminal(f.Fd()) { + p.ttyInput = f + p.previousTtyInputState, err = term.MakeRaw(p.ttyInput.Fd()) + if err != nil { + return fmt.Errorf("error entering raw mode: %w", err) + } + + // OPTIM: We can use hard tabs and backspaces to optimize cursor + // movements. This is based on termios settings support and whether + // they exist and enabled. + p.checkOptimizedMovements(p.previousTtyInputState) + } + + if f, ok := p.output.(term.File); ok && term.IsTerminal(f.Fd()) { + p.ttyOutput = f + } + + return nil +} + +const suspendSupported = true + +// Send SIGTSTP to the entire process group. +func suspendProcess() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGCONT) + defer signal.Stop(c) + _ = syscall.Kill(0, syscall.SIGTSTP) + // blocks until a CONT happens... + <-c +} diff --git a/vendor/charm.land/bubbletea/v2/tty_windows.go b/vendor/charm.land/bubbletea/v2/tty_windows.go new file mode 100644 index 000000000..27b31182a --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/tty_windows.go @@ -0,0 +1,64 @@ +//go:build windows +// +build windows + +package tea + +import ( + "fmt" + + "github.com/charmbracelet/x/term" + "golang.org/x/sys/windows" +) + +func (p *Program) initInput() (err error) { + // Save stdin state and enable VT input + // We also need to enable VT + // input here. + if f, ok := p.input.(term.File); ok && term.IsTerminal(f.Fd()) { + p.ttyInput = f + p.previousTtyInputState, err = term.MakeRaw(p.ttyInput.Fd()) + if err != nil { + return fmt.Errorf("error making terminal raw: %w", err) + } + + // Enable VT input + var mode uint32 + if err := windows.GetConsoleMode(windows.Handle(p.ttyInput.Fd()), &mode); err != nil { + return fmt.Errorf("error getting console mode: %w", err) + } + + if err := windows.SetConsoleMode(windows.Handle(p.ttyInput.Fd()), mode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err != nil { + return fmt.Errorf("error setting console mode: %w", err) + } + } + + // Save output screen buffer state and enable VT processing. + if f, ok := p.output.(term.File); ok && term.IsTerminal(f.Fd()) { + p.ttyOutput = f + p.previousOutputState, err = term.GetState(f.Fd()) + if err != nil { + return fmt.Errorf("error getting terminal state: %w", err) + } + + var mode uint32 + if err := windows.GetConsoleMode(windows.Handle(p.ttyOutput.Fd()), &mode); err != nil { + return fmt.Errorf("error getting console mode: %w", err) + } + + if err := windows.SetConsoleMode(windows.Handle(p.ttyOutput.Fd()), + mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING| + windows.DISABLE_NEWLINE_AUTO_RETURN); err != nil { + return fmt.Errorf("error setting console mode: %w", err) + } + + //nolint:godox + // TODO: check if we can optimize cursor movements on Windows. + p.checkOptimizedMovements(p.previousOutputState) + } + + return //nolint:nakedret +} + +const suspendSupported = false + +func suspendProcess() {} diff --git a/vendor/charm.land/bubbletea/v2/xterm.go b/vendor/charm.land/bubbletea/v2/xterm.go new file mode 100644 index 000000000..0ffcd08fe --- /dev/null +++ b/vendor/charm.land/bubbletea/v2/xterm.go @@ -0,0 +1,22 @@ +package tea + +// TerminalVersionMsg is a message that represents the terminal version. +type TerminalVersionMsg struct { + Name string +} + +// String returns the terminal name as a string. +func (t TerminalVersionMsg) String() string { + return t.Name +} + +// terminalVersion is an internal message that queries the terminal for its +// version using XTVERSION. +type terminalVersion struct{} + +// RequestTerminalVersion is a command that queries the terminal for its +// version using XTVERSION. Note that some terminals may not support this +// command. +func RequestTerminalVersion() Msg { + return terminalVersion{} +} diff --git a/vendor/charm.land/lipgloss/v2/.editorconfig b/vendor/charm.land/lipgloss/v2/.editorconfig new file mode 100644 index 000000000..5de2df8c5 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/.editorconfig @@ -0,0 +1,18 @@ +# https://editorconfig.org/ + +root = true + +[*] +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.go] +indent_style = tab +indent_size = 8 + +[*.golden] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/vendor/charm.land/lipgloss/v2/.gitattributes b/vendor/charm.land/lipgloss/v2/.gitattributes new file mode 100644 index 000000000..d5273520a --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/.gitattributes @@ -0,0 +1 @@ +*.golden linguist-generated=true -text diff --git a/vendor/charm.land/lipgloss/v2/.gitignore b/vendor/charm.land/lipgloss/v2/.gitignore new file mode 100644 index 000000000..3b478f575 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/.gitignore @@ -0,0 +1,3 @@ +ssh_example_ed25519* +/tmp +**/.crush/** diff --git a/vendor/charm.land/lipgloss/v2/.golangci.yml b/vendor/charm.land/lipgloss/v2/.golangci.yml new file mode 100644 index 000000000..c90f03161 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/.golangci.yml @@ -0,0 +1,47 @@ +version: "2" +run: + tests: false +linters: + enable: + - bodyclose + - exhaustive + - goconst + - godot + - gomoddirectives + - goprintffuncname + - gosec + - misspell + - nakedret + - nestif + - nilerr + - noctx + - nolintlint + - prealloc + - revive + - rowserrcheck + - sqlclosecheck + - tparallel + - unconvert + - unparam + - whitespace + - wrapcheck + exclusions: + rules: + - text: '(slog|log)\.\w+' + linters: + - noctx + generated: lax + presets: + - common-false-positives + settings: + exhaustive: + default-signifies-exhaustive: true +issues: + max-issues-per-linter: 0 + max-same-issues: 0 +formatters: + enable: + - gofumpt + - goimports + exclusions: + generated: lax diff --git a/vendor/charm.land/lipgloss/v2/.goreleaser.yml b/vendor/charm.land/lipgloss/v2/.goreleaser.yml new file mode 100644 index 000000000..c61970e07 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/.goreleaser.yml @@ -0,0 +1,5 @@ +includes: + - from_url: + url: charmbracelet/meta/main/goreleaser-lib.yaml +# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json + diff --git a/vendor/charm.land/lipgloss/v2/LICENSE b/vendor/charm.land/lipgloss/v2/LICENSE new file mode 100644 index 000000000..9f60dc1fc --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2026 Charmbracelet, Inc. + +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. diff --git a/vendor/charm.land/lipgloss/v2/README.md b/vendor/charm.land/lipgloss/v2/README.md new file mode 100644 index 000000000..4fda64003 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/README.md @@ -0,0 +1,996 @@ +# Lip Gloss + +

    +
    + Latest Release + GoDoc + Build Status +

    + +

    Style definitions for nice terminal layouts. Built with TUIs in mind.

    + +![Lip Gloss example](https://github.com/user-attachments/assets/92560e60-d70e-4ce0-b39e-a60bb933356b) + +Lip Gloss takes an expressive, declarative approach to terminal rendering. +Users familiar with CSS will feel at home with Lip Gloss. + +```go +import "charm.land/lipgloss/v2" + +var style = lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("#FAFAFA")). + Background(lipgloss.Color("#7D56F4")). + PaddingTop(2). + PaddingLeft(4). + Width(22) + +lipgloss.Println(style.Render("Hello, kitty")) +``` + +## Installation + +```bash +go get charm.land/lipgloss/v2 +``` + +> [!TIP] +> +> Upgrading from v1? Check out the [upgrade guide](./UPGRADE_GUIDE_V2.md), or +> point your LLM at it and let it go to town. + +## Colors + +Lip Gloss supports the following color profiles: + +### ANSI 16 colors (4-bit) + +```go +lipgloss.Color("5") // magenta +lipgloss.Color("9") // red +lipgloss.Color("12") // light blue +``` + +### ANSI 256 Colors (8-bit) + +```go +lipgloss.Color("86") // aqua +lipgloss.Color("201") // hot pink +lipgloss.Color("202") // orange +``` + +### True Color (16,777,216 colors; 24-bit) + +```go +lipgloss.Color("#0000FF") // good ol' 100% blue +lipgloss.Color("#04B575") // a green +lipgloss.Color("#3C3C3C") // a dark gray +``` + +...as well as a 1-bit ASCII profile, which is black and white only. + +There are also named constants for the 16 standard ANSI colors: + +```go +lipgloss.Black +lipgloss.Red +lipgloss.Green +lipgloss.Yellow +lipgloss.Blue +lipgloss.Magenta +lipgloss.Cyan +lipgloss.White +lipgloss.BrightBlack +lipgloss.BrightRed +lipgloss.BrightGreen +lipgloss.BrightYellow +lipgloss.BrightBlue +lipgloss.BrightMagenta +lipgloss.BrightCyan +lipgloss.BrightWhite +``` + +### Automatically Downsampling Colors + +Some users don't have Truecolor terminals. Other times, output might not +support color at all (for example, in logs). Lip Gloss was designed to handle +this gracefully by automatically downsampling colors to the best available +profile. + +If you're using Lip Gloss with Bubble Tea, there’s nothing to do. If you're +using Lip Gloss standalone, just use `lipgloss.Println` or `lipgloss.Sprint` +(and their variants). + +For more, see [advanced color usage](#advanced-color-usage). + +### Color Utilities + +Lip Gloss ships with a handful of handy tools for working with colors: + +```go +c := lipgloss.Color("#EB4268") // Sriracha sauce color +dark := lipgloss.Darken(c, 0.5) // dark Sriracha sauce +light := lipgloss.Lighten(c, 0.35) // light Sriracha sauce +green := lipgloss.Complementary(c) // greenish Sriracha sauce +withAlpha := lipgloss.Alpha(c, 0.2) // watered down Sriracha sauce +``` + +### Advanced Color Tooling + +Lip Gloss also supports color blending, automatically choosing light or dark +variants of colors at runtime, and a lot more. For details, see [Advanced Color +Usage](#advanced-color-usage) and [the docs][docs]. + +## Inline Formatting + +Lip Gloss supports the usual ANSI text formatting options: + +```go +var style = lipgloss.NewStyle(). + Bold(true). + Italic(true). + Faint(true). + Blink(true). + Strikethrough(true). + Underline(true). + Reverse(true) +``` + +### Underline Styles + +Beyond simple on/off, underlines support multiple styles and custom colors: + +```go +s := lipgloss.NewStyle(). + UnderlineStyle(lipgloss.UnderlineCurly). + UnderlineColor(lipgloss.Color("#FF0000")) +``` + +Available styles: `UnderlineNone`, `UnderlineSingle`, `UnderlineDouble`, +`UnderlineCurly`, `UnderlineDotted`, `UnderlineDashed`. + +### Hyperlinks + +Styles can render clickable hyperlinks in supporting terminals: + +```go +s := lipgloss.NewStyle(). + Foreground(lipgloss.Color("#7B2FBE")). + Hyperlink("https://charm.land") + +lipgloss.Println(s.Render("Visit Charm")) +``` + +In unsupported terminals this will degrade gracefully and hyperlinks will +simply not render. + +## Block-Level Formatting + +Lip Gloss also supports rules for block-level formatting: + +```go +// Padding +var style = lipgloss.NewStyle(). + PaddingTop(2). + PaddingRight(4). + PaddingBottom(2). + PaddingLeft(4) + +// Margins +var style = lipgloss.NewStyle(). + MarginTop(2). + MarginRight(4). + MarginBottom(2). + MarginLeft(4) +``` + +There is also shorthand syntax for margins and padding, which follows the same +format as CSS: + +```go +// 2 cells on all sides +lipgloss.NewStyle().Padding(2) + +// 2 cells on the top and bottom, 4 cells on the left and right +lipgloss.NewStyle().Margin(2, 4) + +// 1 cell on the top, 4 cells on the sides, 2 cells on the bottom +lipgloss.NewStyle().Padding(1, 4, 2) + +// Clockwise, starting from the top: 2 cells on the top, 4 on the right, 3 on +// the bottom, and 1 on the left +lipgloss.NewStyle().Margin(2, 4, 3, 1) +``` + +You can also customize the characters used for padding and margin fill: + +```go +s := lipgloss.NewStyle(). + Padding(1, 2). + PaddingChar('·'). + Margin(1, 2). + MarginChar('░') +``` + +## Aligning Text + +You can align paragraphs of text to the left, right, or center. + +```go +var style = lipgloss.NewStyle(). + Width(24). + Align(lipgloss.Left). // align it left + Align(lipgloss.Right). // no wait, align it right + Align(lipgloss.Center) // just kidding, align it in the center +``` + +## Width and Height + +Setting a minimum width and height is simple and straightforward. + +```go +var style = lipgloss.NewStyle(). + SetString("What’s for lunch?"). + Width(24). + Height(32). + Foreground(lipgloss.Color("63")) +``` + +## Borders + +Adding borders is easy: + +```go +// Add a purple, rectangular border +var style = lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("63")) + +// Set a rounded, yellow-on-purple border to the top and left +var anotherStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.RoundedBorder()). + BorderForeground(lipgloss.Color("228")). + BorderBackground(lipgloss.Color("63")). + BorderTop(true). + BorderLeft(true) + +// Make your own border +var myCuteBorder = lipgloss.Border{ + Top: "._.:*:", + Bottom: "._.:*:", + Left: "|*", + Right: "|*", + TopLeft: "*", + TopRight: "*", + BottomLeft: "*", + BottomRight: "*", +} +``` + +There are also shorthand functions for defining borders, which follow a similar +pattern to the margin and padding shorthand functions. + +```go +// Add a thick border to the top and bottom +lipgloss.NewStyle(). + Border(lipgloss.ThickBorder(), true, false) + +// Add a double border to the top and left sides. Rules are set clockwise +// from top. +lipgloss.NewStyle(). + Border(lipgloss.DoubleBorder(), true, false, false, true) +``` + +You can also pass multiple colors to a border for a gradient effect: + +```go +s := lipgloss.NewStyle(). + Border(lipgloss.RoundedBorder()). + BorderForegroundBlend(lipgloss.Color("#FF0000"), lipgloss.Color("#0000FF")) +``` + +For more on borders see [the docs](https://pkg.go.dev/charm.land/lipgloss/v2#Border). + +## Copying Styles + +Just use assignment: + +```go +style := lipgloss.NewStyle().Foreground(lipgloss.Color("219")) + +copiedStyle := style // this is a true copy + +wildStyle := style.Blink(true) // this is also true copy, with blink added +``` + +Since `Style` is a pure value type, assigning a style to another effectively +creates a new copy of the style without mutating the original. + +## Inheritance + +Styles can inherit rules from other styles. When inheriting, only unset rules +on the receiver are inherited. + +```go +var styleA = lipgloss.NewStyle(). + Foreground(lipgloss.Color("229")). + Background(lipgloss.Color("63")) + +// Only the background color will be inherited here, because the foreground +// color will have been already set: +var styleB = lipgloss.NewStyle(). + Foreground(lipgloss.Color("201")). + Inherit(styleA) +``` + +## Unsetting Rules + +All rules can be unset: + +```go +var style = lipgloss.NewStyle(). + Bold(true). // make it bold + UnsetBold(). // jk don't make it bold + Background(lipgloss.Color("227")). // yellow background + UnsetBackground() // never mind +``` + +When a rule is unset, it won’t be inherited or copied. + +## Enforcing Rules + +Sometimes, such as when developing a component, you want to make sure style +definitions respect their intended purpose in the UI. This is where `Inline` +and `MaxWidth`, and `MaxHeight` come in: + +```go +// Force rendering onto a single line, ignoring margins, padding, and borders. +someStyle.Inline(true).Render("yadda yadda") + +// Also limit rendering to five cells +someStyle.Inline(true).MaxWidth(5).Render("yadda yadda") + +// Limit rendering to a 5x5 cell block +someStyle.MaxWidth(5).MaxHeight(5).Render("yadda yadda") +``` + +## Tabs + +The tab character (`\t`) is rendered differently in different terminals (often +as 8 spaces, sometimes 4). Because of this inconsistency, Lip Gloss converts +tabs to 4 spaces at render time. This behavior can be changed on a per-style +basis, however: + +```go +style := lipgloss.NewStyle() // tabs will render as 4 spaces, the default +style = style.TabWidth(2) // render tabs as 2 spaces +style = style.TabWidth(0) // remove tabs entirely +style = style.TabWidth(lipgloss.NoTabConversion) // leave tabs intact +``` + +## Wrapping + +The `Wrap` function wraps text while preserving ANSI styles and hyperlinks +across line boundaries: + +```go +wrapped := lipgloss.Wrap(styledText, 40, " ") +``` + +## Rendering + +Generally, you just call the `Render(string...)` method on a `lipgloss.Style`: + +```go +style := lipgloss.NewStyle().Bold(true).SetString("Hello,") +lipgloss.Println(style.Render("kitty.")) // Hello, kitty. +lipgloss.Println(style.Render("puppy.")) // Hello, puppy. +``` + +But you could also use the Stringer interface: + +```go +var style = lipgloss.NewStyle().SetString("你好,猫咪。").Bold(true) +lipgloss.Println(style) // 你好,猫咪。 +``` + +## Utilities + +In addition to pure styling, Lip Gloss also ships with some utilities to help +assemble your layouts. + +### Compositing + +

    xx

    + +Lip Gloss includes a powerful, cell-based compositor for rendering layered +content: + +```go +// Create some layers. +a := lipgloss.NewLayer(pickles).X(4).Y(2).Z(1) +b := lipgloss.NewLayer(bitterMelon).X(22).Y(1) +c := lipgloss.NewLayer(sriracha).X(11).Y(7) + +// Composite 'em and render. +output := compositor.Compose(a, b, c).Render() +``` + +For a more thorough example, see [the canvas +example](./examples/canvas/main.go). For reference, including how to detect +mouse clicks on layers, see [the docs][docs]. + +### Joining Paragraphs + +Horizontally and vertically joining paragraphs is a cinch. + +```go +// Horizontally join three paragraphs along their bottom edges +lipgloss.JoinHorizontal(lipgloss.Bottom, paragraphA, paragraphB, paragraphC) + +// Vertically join two paragraphs along their center axes +lipgloss.JoinVertical(lipgloss.Center, paragraphA, paragraphB) + +// Horizontally join three paragraphs, with the shorter ones aligning 20% +// from the top of the tallest +lipgloss.JoinHorizontal(0.2, paragraphA, paragraphB, paragraphC) +``` + +### Measuring Width and Height + +Sometimes you’ll want to know the width and height of text blocks when building +your layouts. + +```go +// Render a block of text. +var style = lipgloss.NewStyle(). + Width(40). + Padding(2) +var block string = style.Render(someLongString) + +// Get the actual, physical dimensions of the text block. +width := lipgloss.Width(block) +height := lipgloss.Height(block) + +// Here's a shorthand function. +w, h := lipgloss.Size(block) +``` + +### Blending Colors + +You can blend colors in one or two dimensions for gradient effects: + +```go +// 1-dimentinoal gradient +colors := lipgloss.Blend1D(10, lipgloss.Color("#FF0000"), lipgloss.Color("#0000FF")) + +// 2-dimensional gradient with rotation +colors := lipgloss.Blend2D(80, 24, 45.0, color1, color2, color3) +``` + +### Placing Text in Whitespace + +Sometimes you’ll simply want to place a block of text in whitespace. This is +a lightweight alternative to compositing. + +```go +// Center a paragraph horizontally in a space 80 cells wide. The height of +// the block returned will be as tall as the input paragraph. +block := lipgloss.PlaceHorizontal(80, lipgloss.Center, fancyStyledParagraph) + +// Place a paragraph at the bottom of a space 30 cells tall. The width of +// the text block returned will be as wide as the input paragraph. +block := lipgloss.PlaceVertical(30, lipgloss.Bottom, fancyStyledParagraph) + +// Place a paragraph in the bottom right corner of a 30x80 cell space. +block := lipgloss.Place(30, 80, lipgloss.Right, lipgloss.Bottom, fancyStyledParagraph) +``` + +You can also style the whitespace. For details, see [the docs][docs]. + +## Rendering Tables + +Lip Gloss ships with a table rendering sub-package. + +```go +import "charm.land/lipgloss/v2/table" +``` + +Define some rows of data. + +```go +rows := [][]string{ + {"Chinese", "您好", "你好"}, + {"Japanese", "こんにちは", "やあ"}, + {"Arabic", "أهلين", "أهلا"}, + {"Russian", "Здравствуйте", "Привет"}, + {"Spanish", "Hola", "¿Qué tal?"}, +} +``` + +Use the table package to style and render the table. + +```go +var ( + purple = lipgloss.Color("99") + gray = lipgloss.Color("245") + lightGray = lipgloss.Color("241") + + headerStyle = lipgloss.NewStyle().Foreground(purple).Bold(true).Align(lipgloss.Center) + cellStyle = lipgloss.NewStyle().Padding(0, 1).Width(14) + oddRowStyle = cellStyle.Foreground(gray) + evenRowStyle = cellStyle.Foreground(lightGray) +) + +t := table.New(). + Border(lipgloss.NormalBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(purple)). + StyleFunc(func(row, col int) lipgloss.Style { + switch { + case row == table.HeaderRow: + return headerStyle + case row%2 == 0: + return evenRowStyle + default: + return oddRowStyle + } + }). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + +// You can also add tables row-by-row +t.Row("English", "You look absolutely fabulous.", "How's it going?") +``` + +Print the table. + +```go +lipgloss.Println(t) +``` + +![Table Example](https://github.com/charmbracelet/lipgloss/assets/42545625/6e4b70c4-f494-45da-a467-bdd27df30d5d) + +### Table Borders + +There are helpers to generate tables in markdown or ASCII style: + +#### Markdown Table + +```go +table.New().Border(lipgloss.MarkdownBorder()).BorderTop(false).BorderBottom(false) +``` + +``` +| LANGUAGE | FORMAL | INFORMAL | +|----------|--------------|-----------| +| Chinese | Nǐn hǎo | Nǐ hǎo | +| French | Bonjour | Salut | +| Russian | Zdravstvuyte | Privet | +| Spanish | Hola | ¿Qué tal? | +``` + +#### ASCII Table + +```go +table.New().Border(lipgloss.ASCIIBorder()) +``` + +``` ++----------+--------------+-----------+ +| LANGUAGE | FORMAL | INFORMAL | ++----------+--------------+-----------+ +| Chinese | Nǐn hǎo | Nǐ hǎo | +| French | Bonjour | Salut | +| Russian | Zdravstvuyte | Privet | +| Spanish | Hola | ¿Qué tal? | ++----------+--------------+-----------+ +``` + +For more on tables see [the docs][docs] and [examples](https://github.com/charmbracelet/lipgloss/tree/master/examples/table). + +## Rendering Lists + +Lip Gloss ships with a list rendering sub-package. + +```go +import "charm.land/lipgloss/v2/list" +``` + +Define a new list. + +```go +l := list.New("A", "B", "C") +``` + +Print the list. + +```go +lipgloss.Println(l) + +// • A +// • B +// • C +``` + +Lists have the ability to nest. + +```go +l := list.New( + "A", list.New("Artichoke"), + "B", list.New("Baking Flour", "Bananas", "Barley", "Bean Sprouts"), + "C", list.New("Cashew Apple", "Cashews", "Coconut Milk", "Curry Paste", "Currywurst"), + "D", list.New("Dill", "Dragonfruit", "Dried Shrimp"), + "E", list.New("Eggs"), + "F", list.New("Fish Cake", "Furikake"), + "J", list.New("Jicama"), + "K", list.New("Kohlrabi"), + "L", list.New("Leeks", "Lentils", "Licorice Root"), +) +``` + +Print the list. + +```go +lipgloss.Println(l) +``` + +

    +image +

    + +Lists can be customized via their enumeration function as well as using +`lipgloss.Style`s. + +```go +enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("99")).MarginRight(1) +itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212")).MarginRight(1) + +l := list.New( + "Glossier", + "Claire's Boutique", + "Nyx", + "Mac", + "Milk", + ). + Enumerator(list.Roman). + EnumeratorStyle(enumeratorStyle). + ItemStyle(itemStyle) +``` + +Print the list. + +

    +List example +

    + +In addition to the predefined enumerators (`Arabic`, `Alphabet`, `Roman`, `Bullet`, `Tree`), +you may also define your own custom enumerator: + +```go +l := list.New("Duck", "Duck", "Duck", "Duck", "Goose", "Duck", "Duck") + +func DuckDuckGooseEnumerator(l list.Items, i int) string { + if l.At(i).Value() == "Goose" { + return "Honk →" + } + return "" +} + +l = l.Enumerator(DuckDuckGooseEnumerator) +``` + +Print the list: + +

    +image +

    + +If you need, you can also build lists incrementally: + +```go +l := list.New() + +for i := 0; i < repeat; i++ { + l.Item("Lip Gloss") +} +``` + +## Rendering Trees + +Lip Gloss ships with a tree rendering sub-package. + +```go +import "charm.land/lipgloss/v2/tree" +``` + +Define a new tree. + +```go +t := tree.Root("."). + Child("A", "B", "C") +``` + +Print the tree. + +```go +lipgloss.Println(t) + +// . +// ├── A +// ├── B +// └── C +``` + +Trees have the ability to nest. + +```go +t := tree.Root("."). + Child("macOS"). + Child( + tree.New(). + Root("Linux"). + Child("NixOS"). + Child("Arch Linux (btw)"). + Child("Void Linux"), + ). + Child( + tree.New(). + Root("BSD"). + Child("FreeBSD"). + Child("OpenBSD"), + ) +``` + +Print the tree. + +```go +lipgloss.Println(t) +``` + +

    +Tree Example (simple) +

    + +Trees can be customized via their enumeration function as well as using +`lipgloss.Style`s. + +```go +enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("63")).MarginRight(1) +rootStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("35")) +itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212")) + +t := tree. + Root("⁜ Makeup"). + Child( + "Glossier", + "Fenty Beauty", + tree.New().Child( + "Gloss Bomb Universal Lip Luminizer", + "Hot Cheeks Velour Blushlighter", + ), + "Nyx", + "Mac", + "Milk", + ). + Enumerator(tree.RoundedEnumerator). + EnumeratorStyle(enumeratorStyle). + RootStyle(rootStyle). + ItemStyle(itemStyle) +``` + +Print the tree. + +

    +Tree Example (makeup) +

    + +The predefined enumerators for trees are `DefaultEnumerator` and `RoundedEnumerator`. + +If you need, you can also build trees incrementally: + +```go +t := tree.New() + +for i := 0; i < repeat; i++ { + t.Child("Lip Gloss") +} +``` + +## Advanced Color Usage + +One of the most powerful features of Lip Gloss is the ability to render +different colors at runtime depending on the user's terminal and environment, +allowing you to present the best possible user experience. + +This section shows you how to do exactly that. + +
    +Migrating from v1? + +The `compat` package provides `AdaptiveColor`, `CompleteColor`, and +`CompleteAdaptiveColor` for a quicker migration from v1. These work by +looking at `stdin` and `stdout` on a global basis: + +```go +import "charm.land/lipgloss/v2/compat" + +color := compat.AdaptiveColor{ + Light: lipgloss.Color("#f1f1f1"), + Dark: lipgloss.Color("#cccccc"), +} +``` + +Note that we don't recommend this for new code as it removes the purity from +Lip Gloss, computationally speaking, as it removes transparency around when +I/O happens, which could cause Lip Gloss to compete for resources (like stdin) +with other tools. + +
    + +### Adaptive Colors + +You can render different colors at runtime depending on whether the terminal +has a light or dark background: + +```go +hasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stdout) +lightDark := lipgloss.LightDark(hasDarkBG) + +myColor := lightDark(lipgloss.Color("#D7FFAE"), lipgloss.Color("#D75FEE")) +``` + +#### With Bubble Tea + +In Bubble Tea, request the background color, listen for a +`BackgroundColorMsg`, and respond accordingly: + +```go +func (m model) Init() tea.Cmd { + // First, send a Cmd to request the terminal background color. + return tea.RequestBackgroundColor +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.BackgroundColorMsg: + // Great, we have the background color. Now we can set up our styles + // against the color. + m.styles = newStyles(msg.IsDark()) + return m, nil + } +} + +func newStyles(bgIsDark bool) styles { + // A little ternary function that will return the appropriate color + // based on the background color. + lightDark := lipgloss.LightDark(bgIsDark) + + return styles{ + myHotStyle: lipgloss.NewStyle().Foreground(lightDark( + lipgloss.Color("#f1f1f1"), + lipgloss.Color("#333333"), + )), + } +} +``` + +#### Standalone + +If you’re not using Bubble Tea you can perform the query manually: + +```go +// What's the background color? +hasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stderr) + +// A helper function that will return the appropriate color based on the +// background. +lightDark := lipgloss.LightDark(hasDarkBG) + +// A couple colors with light and dark variants. +thisColor := lightDark(lipgloss.Color("#C5ADF9"), lipgloss.Color("#864EFF")) +thatColor := lightDark(lipgloss.Color("#37CD96"), lipgloss.Color("#22C78A")) + +a := lipgloss.NewStyle().Foreground(thisColor).Render("this") +b := lipgloss.NewStyle().Foreground(thatColor).Render("that") + +// Render the appropriate colors at runtime: +lipgloss.Fprintf(os.Stderr, "my fave colors are %s and %s", a, b) +``` + +### Complete Colors + +In some cases where you may want to specify exact values for each color profile +(ANSI 16, ANSI 156, and TrueColor). For these cases, use the `Complete` helper: + +```go +// You'll need the colorprofile package. +import "github.com/charmbracelet/colorprofile" + +// Get the color profile. +profile := colorprofile.Detect(os.Stdout, os.Environ()) + +// Create a function for rendering the appropriate color based on the profile. +var completeColor := lipgloss.Complete(profile) + +// Now we'll choose the appropriate color at runtime. +myColor := completeColor(ansiColor, ansi256Color, trueColor) +``` + +### Color Downsampling + +One of the best things about Lip Gloss is that it can automatically downsample +colors to the best available profile, stripping colors (and ANSI) entirely when +output is not a TTY. + +If you’re using Lip Gloss with Bubble Tea there’s nothing to do here: +downsampling is built into Bubble Tea v2. If you’re not using Bubble Tea, use +the Lip Gloss writer functions, which are a drop-in replacement for the `fmt` +package: + +```go +s := lipgloss.NewStyle() + .Foreground(lipgloss.Color("#EB4268")) + .Render("Hello!") + +// Downsample if needed and print to stdout. +lipgloss.Println(s) + +// Render to a variable. +downsampled := lipgloss.Sprint(s) + +// Print to stderr. +lipgloss.Fprint(os.Stderr, s) +``` + +The full set: `Print`, `Println`, `Printf`, `Fprint`, `Fprintln`, `Fprintf`, +`Sprint`, `Sprintln`, `Sprintf`. + +Need more control? Check out +[Colorprofile](https://github.com/charmbracelet/colorprofile), which Lip Gloss +uses under the hood. + +## What about [Bubble Tea][tea]? + +Lip Gloss doesn’t replace Bubble Tea. Rather, it is an excellent Bubble Tea +companion. It was designed to make assembling terminal user interface views as +simple and fun as possible so that you can focus on building your application +instead of concerning yourself with low-level layout details. + +In simple terms, you can use Lip Gloss to help build your Bubble Tea views. + +[tea]: https://github.com/charmbracelet/bubbletea + +## Rendering Markdown + +For a more document-centric rendering solution with support for things like +lists, tables, and syntax-highlighted code have a look at [Glamour][glamour], +the stylesheet-based Markdown renderer. + +[glamour]: https://github.com/charmbracelet/glamour + +## Contributing + +See [contributing][contribute]. + +[contribute]: https://github.com/charmbracelet/lipgloss/contribute + +## Feedback + +We’d love to hear your thoughts on this project. Feel free to drop us a note! + +- [Discord](https://charm.land/chat) +- [Matrix](https://charm.land/matrix) + +## License + +[MIT](https://github.com/charmbracelet/lipgloss/raw/master/LICENSE) + +--- + +Part of [Charm](https://charm.land). + +The Charm logo + +Charm热爱开源 • Charm loves open source + +[docs]: https://pkg.go.dev/charm.land/lipgloss/v2?tab=doc diff --git a/vendor/charm.land/lipgloss/v2/Taskfile.yaml b/vendor/charm.land/lipgloss/v2/Taskfile.yaml new file mode 100644 index 000000000..84fcf6c66 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/Taskfile.yaml @@ -0,0 +1,24 @@ +# https://taskfile.dev + +version: "3" + +tasks: + lint: + desc: Run base linters + cmds: + - golangci-lint run + + test: + desc: Run tests + cmds: + - go test ./... {{.CLI_ARGS}} + + test:table: + desc: Run table tests + cmds: + - go test ./table {{.CLI_ARGS}} + + test:tree: + desc: Run tree tests + cmds: + - go test ./tree {{.CLI_ARGS}} diff --git a/vendor/charm.land/lipgloss/v2/UPGRADE_GUIDE_V2.md b/vendor/charm.land/lipgloss/v2/UPGRADE_GUIDE_V2.md new file mode 100644 index 000000000..8f123f19d --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/UPGRADE_GUIDE_V2.md @@ -0,0 +1,504 @@ +# Lip Gloss v2 Upgrade Guide + +This guide covers migrating from Lip Gloss v1 (`github.com/charmbracelet/lipgloss`) +to Lip Gloss v2 (`charm.land/lipgloss/v2`). It is written for both humans and +LLMs performing automated migrations. + +--- + +## Table of Contents + +1. [Quick Start](#quick-start) +2. [Module Path](#module-path) +3. [Color System](#color-system) +4. [Renderer Removal](#renderer-removal) +5. [Printing and Color Downsampling](#printing-and-color-downsampling) +6. [Background Detection and Adaptive Colors](#background-detection-and-adaptive-colors) +7. [Whitespace Options](#whitespace-options) +8. [Underline](#underline) +9. [Style API Changes](#style-api-changes) +10. [Tree Subpackage](#tree-subpackage) +11. [Removed APIs](#removed-apis) +12. [Quick Reference Table](#quick-reference-table) + +--- + +## Quick Start + +For the fastest possible upgrade, do these two things: + +### 1. Use the `compat` package for adaptive/complete colors + +```go +import "charm.land/lipgloss/v2/compat" + +// v1 +color := lipgloss.AdaptiveColor{Light: "#f1f1f1", Dark: "#cccccc"} + +// v2 +color := compat.AdaptiveColor{Light: lipgloss.Color("#f1f1f1"), Dark: lipgloss.Color("#cccccc")} +``` + +The `compat` package reads `stdin`/`stdout` globally, just like v1. To +customize: + +```go +import ( + "charm.land/lipgloss/v2/compat" + "github.com/charmbracelet/colorprofile" +) + +func init() { + compat.HasDarkBackground = lipgloss.HasDarkBackground(os.Stdin, os.Stderr) + compat.Profile = colorprofile.Detect(os.Stderr, os.Environ()) +} +``` + +### 2. Use Lip Gloss writers for output + +```go +// v1 +fmt.Println(s) + +// v2 +lipgloss.Println(s) +``` + +This ensures colors are automatically downsampled. If you're using Bubble Tea +v2, this step is unnecessary — Bubble Tea handles it for you. + +**That's the quick path.** Read on for the full migration details. + +--- + +## Module Path + +The import path has changed. + +```go +// v1 +import "github.com/charmbracelet/lipgloss" + +// v2 +import "charm.land/lipgloss/v2" +``` + +**Install:** + +```bash +go get charm.land/lipgloss/v2 +``` + +All subpackages follow the same pattern: + +```go +// v1 +import "github.com/charmbracelet/lipgloss/table" +import "github.com/charmbracelet/lipgloss/tree" +import "github.com/charmbracelet/lipgloss/list" + +// v2 +import "charm.land/lipgloss/v2/table" +import "charm.land/lipgloss/v2/tree" +import "charm.land/lipgloss/v2/list" +``` + +**Search-and-replace pattern:** + +``` +github.com/charmbracelet/lipgloss → charm.land/lipgloss/v2 +``` + +--- + +## Color System + +This is the most significant API change. + +### `Color` is now a function, not a type + +```go +// v1 — Color is a string type +var c lipgloss.Color = "21" +var c lipgloss.Color = "#ff00ff" + +// v2 — Color is a function returning color.Color +var c color.Color = lipgloss.Color("21") +var c color.Color = lipgloss.Color("#ff00ff") +``` + +The return type is `image/color.Color` (from the standard library). + +### `TerminalColor` interface is removed + +All methods that accepted `lipgloss.TerminalColor` now accept +`image/color.Color`: + +```go +// v1 +func (s Style) Foreground(c TerminalColor) Style +func (s Style) Background(c TerminalColor) Style +func (s Style) BorderForeground(c ...TerminalColor) Style + +// v2 +func (s Style) Foreground(c color.Color) Style +func (s Style) Background(c color.Color) Style +func (s Style) BorderForeground(c ...color.Color) Style +``` + +**Migration:** Replace every `lipgloss.TerminalColor` with `color.Color` and +add `import "image/color"`. + +### `ANSIColor` is now an alias + +```go +// v1 — custom uint type +type ANSIColor uint + +// v2 — alias for ansi.IndexedColor +type ANSIColor = ansi.IndexedColor +``` + +v2 also exports named constants for the 16 basic ANSI colors: + +```go +lipgloss.Black, lipgloss.Red, lipgloss.Green, lipgloss.Yellow, +lipgloss.Blue, lipgloss.Magenta, lipgloss.Cyan, lipgloss.White, +lipgloss.BrightBlack, lipgloss.BrightRed, lipgloss.BrightGreen, +lipgloss.BrightYellow, lipgloss.BrightBlue, lipgloss.BrightMagenta, +lipgloss.BrightCyan, lipgloss.BrightWhite +``` + +### `AdaptiveColor`, `CompleteColor`, `CompleteAdaptiveColor` + +These types have been moved out of the root package. Use the `compat` package +for a drop-in replacement, or use the new `LightDark` and `Complete` helpers +for explicit control: + +```go +// v1 +color := lipgloss.AdaptiveColor{Light: "#0000ff", Dark: "#000099"} + +// v2 — using compat (quick path) +color := compat.AdaptiveColor{ + Light: lipgloss.Color("#0000ff"), + Dark: lipgloss.Color("#000099"), +} + +// v2 — using LightDark (recommended) +hasDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout) +lightDark := lipgloss.LightDark(hasDark) +color := lightDark(lipgloss.Color("#0000ff"), lipgloss.Color("#000099")) +``` + +```go +// v1 +color := lipgloss.CompleteColor{TrueColor: "#ff00ff", ANSI256: "200", ANSI: "5"} + +// v2 — using compat +color := compat.CompleteColor{ + TrueColor: lipgloss.Color("#ff00ff"), + ANSI256: lipgloss.Color("200"), + ANSI: lipgloss.Color("5"), +} + +// v2 — using Complete (recommended) +profile := colorprofile.Detect(os.Stdout, os.Environ()) +complete := lipgloss.Complete(profile) +color := complete(lipgloss.Color("5"), lipgloss.Color("200"), lipgloss.Color("#ff00ff")) +``` + +Note that `compat.AdaptiveColor` and friends take `color.Color` values for +their fields, not strings. + +--- + +## Renderer Removal + +The `Renderer` type and all associated functions are removed. In v1, every +`Style` carried a `*Renderer` pointer and the package maintained a global +default renderer. + +```go +// v1 — these no longer exist +lipgloss.DefaultRenderer() +lipgloss.SetDefaultRenderer(r) +lipgloss.NewRenderer(w, opts...) +lipgloss.ColorProfile() +lipgloss.SetColorProfile(p) +renderer.NewStyle() +``` + +**In v2, `Style` is a plain value type.** There is no renderer. Color +downsampling is handled at the output layer (see next section). + +**Migration:** + +- Replace `lipgloss.DefaultRenderer().NewStyle()` with `lipgloss.NewStyle()`. +- Replace `renderer.NewStyle()` with `lipgloss.NewStyle()`. +- Remove any `*Renderer` fields from your types. +- Remove calls to `SetColorProfile` — use `colorprofile.Detect` at the output + layer instead. + +--- + +## Printing and Color Downsampling + +In v1, color downsampling happened inside `Style.Render()` via the renderer. In +v2, `Render()` always emits full-fidelity ANSI. Downsampling happens when you +print. + +### Standalone Usage + +Use the Lip Gloss writer functions: + +```go +s := someStyle.Render("Hello!") + +// Print to stdout with automatic downsampling +lipgloss.Println(s) + +// Print to stderr +lipgloss.Fprintln(os.Stderr, s) + +// Render to a string (downsampled for stdout's profile) +str := lipgloss.Sprint(s) +``` + +The default writer targets `stdout`. To customize: + +```go +lipgloss.Writer = colorprofile.NewWriter(os.Stderr, os.Environ()) +``` + +### With Bubble Tea + +No changes needed. Bubble Tea v2 handles downsampling internally. + +--- + +## Background Detection and Adaptive Colors + +### Standalone + +v1 detected the background color automatically via the global renderer. v2 +requires explicit queries: + +```go +// v1 +hasDark := lipgloss.HasDarkBackground() + +// v2 — specify the input and output +hasDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout) +``` + +Then use `LightDark` to pick colors: + +```go +lightDark := lipgloss.LightDark(hasDark) +fg := lightDark(lipgloss.Color("#333333"), lipgloss.Color("#f1f1f1")) + +s := lipgloss.NewStyle().Foreground(fg) +``` + +### With Bubble Tea + +Request the background color in `Init` and listen for the response: + +```go +func (m model) Init() tea.Cmd { + return tea.RequestBackgroundColor +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.BackgroundColorMsg: + m.styles = newStyles(msg.IsDark()) + } + // ... +} + +func newStyles(bgIsDark bool) styles { + lightDark := lipgloss.LightDark(bgIsDark) + return styles{ + title: lipgloss.NewStyle().Foreground(lightDark( + lipgloss.Color("#333333"), + lipgloss.Color("#f1f1f1"), + )), + } +} +``` + +--- + +## Whitespace Options + +The separate foreground/background whitespace options have been replaced by a +single style option: + +```go +// v1 +lipgloss.Place(width, height, hPos, vPos, str, + lipgloss.WithWhitespaceForeground(lipgloss.Color("#333")), + lipgloss.WithWhitespaceBackground(lipgloss.Color("#000")), +) + +// v2 +lipgloss.Place(width, height, hPos, vPos, str, + lipgloss.WithWhitespaceStyle(lipgloss.NewStyle(). + Foreground(lipgloss.Color("#333")). + Background(lipgloss.Color("#000")), + ), +) +``` + +--- + +## Underline + +`Underline(bool)` still works for basic on/off. v2 adds fine-grained control: + +```go +// v1 +s := lipgloss.NewStyle().Underline(true) + +// v2 — still works +s := lipgloss.NewStyle().Underline(true) + +// v2 — new: specific styles +s := lipgloss.NewStyle().UnderlineStyle(lipgloss.UnderlineCurly) + +// v2 — new: colored underlines +s := lipgloss.NewStyle(). + UnderlineStyle(lipgloss.UnderlineSingle). + UnderlineColor(lipgloss.Color("#FF0000")) +``` + +Internally, `Underline(true)` is equivalent to `UnderlineStyle(UnderlineSingle)` +and `Underline(false)` is equivalent to `UnderlineStyle(UnderlineNone)`. + +--- + +## Style API Changes + +### `NewStyle()` is no longer tied to a Renderer + +```go +// v1 +s := lipgloss.NewStyle() // uses global renderer +s := renderer.NewStyle() // uses specific renderer + +// v2 +s := lipgloss.NewStyle() // pure value, no renderer +``` + +### Color getters return `color.Color` + +```go +// v1 +fg := s.GetForeground() // returns TerminalColor + +// v2 +fg := s.GetForeground() // returns color.Color +``` + +### New style methods + +| Method | Description | +|---|---| +| `UnderlineStyle(Underline)` | Set underline style (single, double, curly, etc.) | +| `UnderlineColor(color.Color)` | Set underline color | +| `PaddingChar(rune)` | Set the character used for padding fill | +| `MarginChar(rune)` | Set the character used for margin fill | +| `Hyperlink(link, params...)` | Set a clickable hyperlink | +| `BorderForegroundBlend(...color.Color)` | Apply gradient colors to borders | +| `BorderForegroundBlendOffset(int)` | Set the offset for border gradient | + +Each has a corresponding `Get*`, `Unset*`, and where applicable `Get*` +accessor. + +--- + +## Tree Subpackage + +The import path changes and there are new styling options: + +```go +// v1 +import "github.com/charmbracelet/lipgloss/tree" + +// v2 +import "charm.land/lipgloss/v2/tree" +``` + +New methods: + +- `IndenterStyle(lipgloss.Style)` — set a static style for tree indentation. +- `IndenterStyleFunc(func(Children, int) lipgloss.Style)` — conditionally style + indentation. +- `Width(int)` — set tree width for padding. + +--- + +## Removed APIs + +The following types and functions no longer exist in v2. This table shows each +removed symbol and its replacement. + +| v1 Symbol | v2 Replacement | +|---|---| +| `type Renderer` | Removed entirely | +| `DefaultRenderer()` | Not needed | +| `SetDefaultRenderer(r)` | Not needed | +| `NewRenderer(w, opts...)` | Not needed | +| `ColorProfile()` | `colorprofile.Detect(w, env)` | +| `SetColorProfile(p)` | Set `lipgloss.Writer.Profile` | +| `HasDarkBackground()` (no args) | `lipgloss.HasDarkBackground(in, out)` | +| `SetHasDarkBackground(b)` | Not needed — pass bool to `LightDark` | +| `type TerminalColor` | `image/color.Color` | +| `type Color string` | `func Color(string) color.Color` | +| `type ANSIColor uint` | `type ANSIColor = ansi.IndexedColor` | +| `type AdaptiveColor` | `compat.AdaptiveColor` or `LightDark` | +| `type CompleteColor` | `compat.CompleteColor` or `Complete` | +| `type CompleteAdaptiveColor` | `compat.CompleteAdaptiveColor` | +| `WithWhitespaceForeground(c)` | `WithWhitespaceStyle(s)` | +| `WithWhitespaceBackground(c)` | `WithWhitespaceStyle(s)` | +| `renderer.NewStyle()` | `lipgloss.NewStyle()` | + +--- + +## Quick Reference Table + +A side-by-side summary for common patterns: + +| Task | v1 | v2 | +|---|---|---| +| Import | `"github.com/charmbracelet/lipgloss"` | `"charm.land/lipgloss/v2"` | +| Create style | `lipgloss.NewStyle()` | `lipgloss.NewStyle()` | +| Hex color | `lipgloss.Color("#ff00ff")` | `lipgloss.Color("#ff00ff")` | +| ANSI color | `lipgloss.Color("5")` | `lipgloss.Color("5")` or `lipgloss.Magenta` | +| Adaptive color | `lipgloss.AdaptiveColor{Light: "#fff", Dark: "#000"}` | `compat.AdaptiveColor{Light: lipgloss.Color("#fff"), Dark: lipgloss.Color("#000")}` | +| Set foreground | `s.Foreground(lipgloss.Color("5"))` | `s.Foreground(lipgloss.Color("5"))` | +| Print with downsampling | `fmt.Println(s.Render("hi"))` | `lipgloss.Println(s.Render("hi"))` | +| Detect dark bg | `lipgloss.HasDarkBackground()` | `lipgloss.HasDarkBackground(os.Stdin, os.Stdout)` | +| Light/dark color | `lipgloss.AdaptiveColor{...}` | `lipgloss.LightDark(isDark)(light, dark)` | +| Whitespace styling | `WithWhitespaceForeground(c)` | `WithWhitespaceStyle(lipgloss.NewStyle().Foreground(c))` | +| Underline | `s.Underline(true)` | `s.Underline(true)` or `s.UnderlineStyle(lipgloss.UnderlineCurly)` | + +--- + +## Feedback + +Questions, issues, or feedback: + +- [Discord](https://charm.land/discord) +- [Matrix](https://charm.land/matrix) +- [Email](mailto:vt100@charm.land) + +--- + +Part of [Charm](https://charm.land). + +The Charm logo + +Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة diff --git a/vendor/charm.land/lipgloss/v2/align.go b/vendor/charm.land/lipgloss/v2/align.go new file mode 100644 index 000000000..d196213b7 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/align.go @@ -0,0 +1,82 @@ +package lipgloss + +import ( + "strings" + + "github.com/charmbracelet/x/ansi" +) + +// Perform text alignment. If the string is multi-lined, we also make all lines +// the same width by padding them with spaces. If a style is passed, use that +// to style the spaces added. +func alignTextHorizontal(str string, pos Position, width int, style *ansi.Style) string { + lines, widestLine := getLines(str) + var b strings.Builder + + for i, l := range lines { + lineWidth := ansi.StringWidth(l) + + shortAmount := widestLine - lineWidth // difference from the widest line + shortAmount += max(0, width-(shortAmount+lineWidth)) // difference from the total width, if set + + if shortAmount > 0 { + switch pos { + case Right: + s := strings.Repeat(" ", shortAmount) + if style != nil { + s = style.Styled(s) + } + l = s + l + case Center: + // Note: remainder goes on the right. + left := shortAmount / 2 //nolint:mnd + right := left + shortAmount%2 //nolint:mnd + + leftSpaces := strings.Repeat(" ", left) + rightSpaces := strings.Repeat(" ", right) + + if style != nil { + leftSpaces = style.Styled(leftSpaces) + rightSpaces = style.Styled(rightSpaces) + } + l = leftSpaces + l + rightSpaces + default: // Left + s := strings.Repeat(" ", shortAmount) + if style != nil { + s = style.Styled(s) + } + l += s + } + } + + b.WriteString(l) + if i < len(lines)-1 { + b.WriteRune('\n') + } + } + + return b.String() +} + +func alignTextVertical(str string, pos Position, height int, _ *ansi.Style) string { + strHeight := strings.Count(str, "\n") + 1 + if height < strHeight { + return str + } + + switch pos { + case Top: + return str + strings.Repeat("\n", height-strHeight) + case Center: + topPadding, bottomPadding := (height-strHeight)/2, (height-strHeight)/2 //nolint:mnd + if strHeight+topPadding+bottomPadding > height { + topPadding-- + } else if strHeight+topPadding+bottomPadding < height { + bottomPadding++ + } + return strings.Repeat("\n", topPadding) + str + strings.Repeat("\n", bottomPadding) + case Bottom: + return strings.Repeat("\n", height-strHeight) + str + } + return str +} diff --git a/vendor/charm.land/lipgloss/v2/ansi_unix.go b/vendor/charm.land/lipgloss/v2/ansi_unix.go new file mode 100644 index 000000000..b4fca7d19 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/ansi_unix.go @@ -0,0 +1,8 @@ +//go:build !windows + +package lipgloss + +import "os" + +// EnableLegacyWindowsANSI is only needed on Windows. +func EnableLegacyWindowsANSI(*os.File) {} diff --git a/vendor/charm.land/lipgloss/v2/ansi_windows.go b/vendor/charm.land/lipgloss/v2/ansi_windows.go new file mode 100644 index 000000000..8a1ef00eb --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/ansi_windows.go @@ -0,0 +1,30 @@ +//go:build windows + +package lipgloss + +import ( + "os" + + "golang.org/x/sys/windows" +) + +// EnableLegacyWindowsANSI enables support for ANSI color sequences in the +// Windows default console (cmd.exe and the PowerShell application). Note that +// this only works with Windows 10 and greater. Also note that Windows Terminal +// supports colors by default. +func EnableLegacyWindowsANSI(f *os.File) { + var mode uint32 + handle := windows.Handle(f.Fd()) + err := windows.GetConsoleMode(handle, &mode) + if err != nil { + return + } + + // See https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences + if mode&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING { + vtpmode := mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING + if err := windows.SetConsoleMode(handle, vtpmode); err != nil { + return + } + } +} diff --git a/vendor/charm.land/lipgloss/v2/blending.go b/vendor/charm.land/lipgloss/v2/blending.go new file mode 100644 index 000000000..82830f3e4 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/blending.go @@ -0,0 +1,196 @@ +package lipgloss + +import ( + "image/color" + "math" + "slices" + + "github.com/lucasb-eyer/go-colorful" +) + +// Blend1D blends a series of colors together in one linear dimension using multiple +// stops, into the provided number of steps. Uses the "CIE L*, a*, b*" (CIELAB) color-space. +// +// Note that if any of the provided colors are completely transparent, we will +// assume that the alpha value was lost in conversion from RGB -> RGBA, and we +// will set the alpha to opaque, as it's not possible to blend something completely +// transparent. +func Blend1D(steps int, stops ...color.Color) []color.Color { + if steps < 0 { + steps = 0 + } + + if steps <= len(stops) { + return stops[:steps] + } + + // Ensure they didn't provide any nil colors. + stops = slices.DeleteFunc(stops, func(c color.Color) bool { + return c == nil + }) + + if len(stops) == 0 { + return nil // We can't safely fallback. + } + + // If they only provided one valid color (or some nil colors), we will just return + // an array of that color, for the amount of steps they requested. + if len(stops) == 1 { + singleColor := stops[0] + result := make([]color.Color, steps) + for i := range result { + result[i] = singleColor + } + return result + } + + blended := make([]color.Color, steps) + + // Convert stops to colorful.Color once + cstops := make([]colorful.Color, len(stops)) + for i, k := range stops { + cstops[i], _ = colorful.MakeColor(ensureNotTransparent(k)) + } + + numSegments := len(cstops) - 1 + defaultSize := steps / numSegments + remainingSteps := steps % numSegments + + resultIndex := 0 + for i := range numSegments { + from := cstops[i] + to := cstops[i+1] + + // Calculate segment size. + segmentSize := defaultSize + if i < remainingSteps { + segmentSize++ + } + + divisor := float64(segmentSize - 1) + + // Generate colors for this segment. + for j := 0; j < segmentSize; j++ { + var blendingFactor float64 + if segmentSize > 1 { + blendingFactor = float64(j) / divisor + } + blended[resultIndex] = from.BlendLab(to, blendingFactor).Clamped() + resultIndex++ + } + } + + return blended +} + +// Blend2D blends a series of colors together in two linear dimensions using +// multiple stops, into the provided width/height. Uses the "CIE L*, a*, b*" (CIELAB) +// color-space. The angle parameter controls the rotation of the gradient (0-360°), +// where 0° is left-to-right, 45° is bottom-left to top-right (diagonal). The function +// returns colors in a 1D row-major order ([row1, row2, row3, ...]). +// +// Example of how to iterate over the result: +// +// gradient := colors.Blend2D(width, height, 180, color1, color2, color3, ...) +// gradientContent := strings.Builder{} +// for y := range height { +// for x := range width { +// index := y*width + x +// gradientContent.WriteString( +// lipgloss.NewStyle(). +// Background(gradient[index]). +// Render(" "), +// ) +// } +// if y < height-1 { // End of row. +// gradientContent.WriteString("\n") +// } +// } +// +// Note that if any of the provided colors are completely transparent, we will +// assume that the alpha value was lost in conversion from RGB -> RGBA, and we +// will set the alpha to opaque, as it's not possible to blend something completely +// transparent. +func Blend2D(width, height int, angle float64, stops ...color.Color) []color.Color { + if width < 1 { + width = 1 + } + if height < 1 { + height = 1 + } + + // Normalize angle to 0-360. + angle = math.Mod(angle, 360) + if angle < 0 { + angle += 360 + } + + // Ensure they didn't provide any nil colors. + stops = slices.DeleteFunc(stops, func(c color.Color) bool { + return c == nil + }) + + if len(stops) == 0 { + return nil // We can't safely fallback. + } + + // If they only provided one valid color (or some nil colors), we will just return + // an array of that color, for the amount of pixels they requested. + if len(stops) == 1 { + singleColor := stops[0] + result := make([]color.Color, width*height) + for i := range result { + result[i] = singleColor + } + return result + } + + // For 2D blending, we'll create a gradient along the diagonal and then sample + // from it based on the angle. We'll use the maximum dimension to ensure we have + // enough resolution for the gradient. + diagonalGradient := Blend1D(max(width, height), stops...) + + result := make([]color.Color, width*height) + + // Calculate center point for rotation. + centerX := float64(width-1) / 2.0 + centerY := float64(height-1) / 2.0 + + angleRad := angle * math.Pi / 180.0 // -> radians. + + // Pre-calculate sin and cos. + cosAngle := math.Cos(angleRad) + sinAngle := math.Sin(angleRad) + + // Calculate diagonal length for proper gradient mapping. + diagonalLength := math.Sqrt(float64(width*width + height*height)) + + // Pre-calculate gradient length for index calculation. + gradientLen := float64(len(diagonalGradient) - 1) + + for y := range height { + // Calculate the distance from center along the gradient direction. + dy := float64(y) - centerY + + for x := 0; x < width; x++ { + // Calculate the distance from center along the gradient direction. + dx := float64(x) - centerX + + rotX := dx*cosAngle - dy*sinAngle // Rotate the point by the angle. + + // Map the rotated position to the gradient. Normalize to 0-1 range based on + // the diagonal length. + gradientPos := clamp((rotX+diagonalLength/2.0)/diagonalLength, 0, 1) + + // Calculate the index in the gradient. + gradientIndex := int(gradientPos * gradientLen) + if gradientIndex >= len(diagonalGradient) { + gradientIndex = len(diagonalGradient) - 1 + } + + result[y*width+x] = diagonalGradient[gradientIndex] // -> row-major order. + } + } + + return result +} diff --git a/vendor/charm.land/lipgloss/v2/borders.go b/vendor/charm.land/lipgloss/v2/borders.go new file mode 100644 index 000000000..7f12360f6 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/borders.go @@ -0,0 +1,587 @@ +package lipgloss + +import ( + "image/color" + "slices" + "strings" + "unicode/utf8" + + "github.com/charmbracelet/x/ansi" + "github.com/clipperhouse/displaywidth" + "github.com/rivo/uniseg" +) + +// Border contains a series of values which comprise the various parts of a +// border. +type Border struct { + Top string + Bottom string + Left string + Right string + TopLeft string + TopRight string + BottomLeft string + BottomRight string + MiddleLeft string + MiddleRight string + Middle string + MiddleTop string + MiddleBottom string +} + +// GetTopSize returns the width of the top border. If borders contain runes of +// varying widths, the widest rune is returned. If no border exists on the top +// edge, 0 is returned. +func (b Border) GetTopSize() int { + return getBorderEdgeWidth(b.TopLeft, b.Top, b.TopRight) +} + +// GetRightSize returns the width of the right border. If borders contain +// runes of varying widths, the widest rune is returned. If no border exists on +// the right edge, 0 is returned. +func (b Border) GetRightSize() int { + return getBorderEdgeWidth(b.TopRight, b.Right, b.BottomRight) +} + +// GetBottomSize returns the width of the bottom border. If borders contain +// runes of varying widths, the widest rune is returned. If no border exists on +// the bottom edge, 0 is returned. +func (b Border) GetBottomSize() int { + return getBorderEdgeWidth(b.BottomLeft, b.Bottom, b.BottomRight) +} + +// GetLeftSize returns the width of the left border. If borders contain runes +// of varying widths, the widest rune is returned. If no border exists on the +// left edge, 0 is returned. +func (b Border) GetLeftSize() int { + return getBorderEdgeWidth(b.TopLeft, b.Left, b.BottomLeft) +} + +func getBorderEdgeWidth(borderParts ...string) (maxWidth int) { + for _, piece := range borderParts { + maxWidth = max(maxWidth, maxRuneWidth(piece)) + } + return maxWidth +} + +var ( + noBorder = Border{} + + normalBorder = Border{ + Top: "─", + Bottom: "─", + Left: "│", + Right: "│", + TopLeft: "┌", + TopRight: "┐", + BottomLeft: "└", + BottomRight: "┘", + MiddleLeft: "├", + MiddleRight: "┤", + Middle: "┼", + MiddleTop: "┬", + MiddleBottom: "┴", + } + + roundedBorder = Border{ + Top: "─", + Bottom: "─", + Left: "│", + Right: "│", + TopLeft: "╭", + TopRight: "╮", + BottomLeft: "╰", + BottomRight: "╯", + MiddleLeft: "├", + MiddleRight: "┤", + Middle: "┼", + MiddleTop: "┬", + MiddleBottom: "┴", + } + + blockBorder = Border{ + Top: "█", + Bottom: "█", + Left: "█", + Right: "█", + TopLeft: "█", + TopRight: "█", + BottomLeft: "█", + BottomRight: "█", + MiddleLeft: "█", + MiddleRight: "█", + Middle: "█", + MiddleTop: "█", + MiddleBottom: "█", + } + + outerHalfBlockBorder = Border{ + Top: "▀", + Bottom: "▄", + Left: "▌", + Right: "▐", + TopLeft: "▛", + TopRight: "▜", + BottomLeft: "▙", + BottomRight: "▟", + } + + innerHalfBlockBorder = Border{ + Top: "▄", + Bottom: "▀", + Left: "▐", + Right: "▌", + TopLeft: "▗", + TopRight: "▖", + BottomLeft: "▝", + BottomRight: "▘", + } + + thickBorder = Border{ + Top: "━", + Bottom: "━", + Left: "┃", + Right: "┃", + TopLeft: "┏", + TopRight: "┓", + BottomLeft: "┗", + BottomRight: "┛", + MiddleLeft: "┣", + MiddleRight: "┫", + Middle: "╋", + MiddleTop: "┳", + MiddleBottom: "┻", + } + + doubleBorder = Border{ + Top: "═", + Bottom: "═", + Left: "║", + Right: "║", + TopLeft: "╔", + TopRight: "╗", + BottomLeft: "╚", + BottomRight: "╝", + MiddleLeft: "╠", + MiddleRight: "╣", + Middle: "╬", + MiddleTop: "╦", + MiddleBottom: "╩", + } + + hiddenBorder = Border{ + Top: " ", + Bottom: " ", + Left: " ", + Right: " ", + TopLeft: " ", + TopRight: " ", + BottomLeft: " ", + BottomRight: " ", + MiddleLeft: " ", + MiddleRight: " ", + Middle: " ", + MiddleTop: " ", + MiddleBottom: " ", + } + + markdownBorder = Border{ + Top: "-", + Bottom: "-", + Left: "|", + Right: "|", + TopLeft: "|", + TopRight: "|", + BottomLeft: "|", + BottomRight: "|", + MiddleLeft: "|", + MiddleRight: "|", + Middle: "|", + MiddleTop: "|", + MiddleBottom: "|", + } + + asciiBorder = Border{ + Top: "-", + Bottom: "-", + Left: "|", + Right: "|", + TopLeft: "+", + TopRight: "+", + BottomLeft: "+", + BottomRight: "+", + MiddleLeft: "+", + MiddleRight: "+", + Middle: "+", + MiddleTop: "+", + MiddleBottom: "+", + } +) + +// NormalBorder returns a standard-type border with a normal weight and 90 +// degree corners. +func NormalBorder() Border { + return normalBorder +} + +// RoundedBorder returns a border with rounded corners. +func RoundedBorder() Border { + return roundedBorder +} + +// BlockBorder returns a border that takes the whole block. +func BlockBorder() Border { + return blockBorder +} + +// OuterHalfBlockBorder returns a half-block border that sits outside the frame. +func OuterHalfBlockBorder() Border { + return outerHalfBlockBorder +} + +// InnerHalfBlockBorder returns a half-block border that sits inside the frame. +func InnerHalfBlockBorder() Border { + return innerHalfBlockBorder +} + +// ThickBorder returns a border that's thicker than the one returned by +// NormalBorder. +func ThickBorder() Border { + return thickBorder +} + +// DoubleBorder returns a border comprised of two thin strokes. +func DoubleBorder() Border { + return doubleBorder +} + +// HiddenBorder returns a border that renders as a series of single-cell +// spaces. It's useful for cases when you want to remove a standard border but +// maintain layout positioning. This said, you can still apply a background +// color to a hidden border. +func HiddenBorder() Border { + return hiddenBorder +} + +// MarkdownBorder return a table border in markdown style. +// +// Make sure to disable top and bottom border for the best result. This will +// ensure that the output is valid markdown. +// +// table.New().Border(lipgloss.MarkdownBorder()).BorderTop(false).BorderBottom(false) +func MarkdownBorder() Border { + return markdownBorder +} + +// ASCIIBorder returns a table border with ASCII characters. +func ASCIIBorder() Border { + return asciiBorder +} + +type borderBlend struct { + topGradient []color.Color + rightGradient []color.Color + bottomGradient []color.Color + leftGradient []color.Color +} + +func (s Style) borderBlend(width, height int, colors ...color.Color) *borderBlend { + gradient := Blend1D( + (height+width+2)*2, + colors..., + ) + + // Rotate array forward or reverse based on the offset if provided. + if r := -s.getAsInt(borderForegroundBlendOffsetKey); r != 0 { + n := len(gradient) + r %= n + if r < 0 { + r += n + } + slices.Reverse(gradient[:r]) + slices.Reverse(gradient[r:]) + slices.Reverse(gradient) + } + + offset := 0 + getFromOffset := func(size int) (s []color.Color) { + s = gradient[offset : offset+size] + offset += size + return s + } + + blend := &borderBlend{ + topGradient: getFromOffset(width + 2), + rightGradient: getFromOffset(height), + bottomGradient: getFromOffset(width + 2), + leftGradient: getFromOffset(height), + } + + // bottom and left gradients are reversed because they are drawn in reverse order. + slices.Reverse(blend.bottomGradient) + slices.Reverse(blend.leftGradient) + + return blend +} + +func (s Style) applyBorder(str string) string { + var ( + border = s.getBorderStyle() + hasTop = s.getAsBool(borderTopKey, false) + hasRight = s.getAsBool(borderRightKey, false) + hasBottom = s.getAsBool(borderBottomKey, false) + hasLeft = s.getAsBool(borderLeftKey, false) + ) + + // If a border is set and no sides have been specifically turned on or off + // render borders on all sides. + if s.isBorderStyleSetWithoutSides() { + hasTop = true + hasRight = true + hasBottom = true + hasLeft = true + } + + // If no border is set or all borders are been disabled, abort. + if border == noBorder || (!hasTop && !hasRight && !hasBottom && !hasLeft) { + return str + } + + lines, width := getLines(str) + + if hasLeft { + if border.Left == "" { + border.Left = " " + } + width += maxRuneWidth(border.Left) + } + + if hasRight { + if border.Right == "" { + border.Right = " " + } + width += maxRuneWidth(border.Right) + } + + // If corners should be rendered but are set with the empty string, fill them + // with a single space. + if hasTop && hasLeft && border.TopLeft == "" { + border.TopLeft = " " + } + if hasTop && hasRight && border.TopRight == "" { + border.TopRight = " " + } + if hasBottom && hasLeft && border.BottomLeft == "" { + border.BottomLeft = " " + } + if hasBottom && hasRight && border.BottomRight == "" { + border.BottomRight = " " + } + + // Figure out which corners we should actually be using based on which + // sides are set to show. + if hasTop { + switch { + case !hasLeft && !hasRight: + border.TopLeft = "" + border.TopRight = "" + case !hasLeft: + border.TopLeft = "" + case !hasRight: + border.TopRight = "" + } + } + if hasBottom { + switch { + case !hasLeft && !hasRight: + border.BottomLeft = "" + border.BottomRight = "" + case !hasLeft: + border.BottomLeft = "" + case !hasRight: + border.BottomRight = "" + } + } + + // For now, limit corners to one rune. + border.TopLeft = getFirstRuneAsString(border.TopLeft) + border.TopRight = getFirstRuneAsString(border.TopRight) + border.BottomRight = getFirstRuneAsString(border.BottomRight) + border.BottomLeft = getFirstRuneAsString(border.BottomLeft) + + var topFG, rightFG, bottomFG, leftFG color.Color + var ( + blendFG = s.getAsColors(borderForegroundBlendKey) + topBG = s.getAsColor(borderTopBackgroundKey) + rightBG = s.getAsColor(borderRightBackgroundKey) + bottomBG = s.getAsColor(borderBottomBackgroundKey) + leftBG = s.getAsColor(borderLeftBackgroundKey) + ) + + var blend *borderBlend + if len(blendFG) > 0 { + blend = s.borderBlend(width, len(lines), blendFG...) + } else { + topFG = s.getAsColor(borderTopForegroundKey) + rightFG = s.getAsColor(borderRightForegroundKey) + bottomFG = s.getAsColor(borderBottomForegroundKey) + leftFG = s.getAsColor(borderLeftForegroundKey) + } + + var out strings.Builder + + // Render top + if hasTop { + top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width) + if blend != nil { + out.WriteString(s.styleBorderBlend(top, blend.topGradient, topBG)) + } else { + out.WriteString(s.styleBorder(top, topFG, topBG)) + } + out.WriteRune('\n') + } + + leftRunes := []rune(border.Left) + leftIndex := 0 + + rightRunes := []rune(border.Right) + rightIndex := 0 + + // Render sides + var r string + for i, l := range lines { + if hasLeft { + r = string(leftRunes[leftIndex]) + leftIndex++ + if leftIndex >= len(leftRunes) { + leftIndex = 0 + } + if blend != nil { + out.WriteString(s.styleBorder(r, blend.leftGradient[i], leftBG)) + } else { + out.WriteString(s.styleBorder(r, leftFG, leftBG)) + } + } + out.WriteString(l) + if hasRight { + r = string(rightRunes[rightIndex]) + rightIndex++ + if rightIndex >= len(rightRunes) { + rightIndex = 0 + } + if blend != nil { + out.WriteString(s.styleBorder(r, blend.rightGradient[i], rightBG)) + } else { + out.WriteString(s.styleBorder(r, rightFG, rightBG)) + } + } + if i < len(lines)-1 { + out.WriteRune('\n') + } + } + + // Render bottom + if hasBottom { + bottom := renderHorizontalEdge(border.BottomLeft, border.Bottom, border.BottomRight, width) + out.WriteRune('\n') + if blend != nil { + out.WriteString(s.styleBorderBlend(bottom, blend.bottomGradient, bottomBG)) + } else { + out.WriteString(s.styleBorder(bottom, bottomFG, bottomBG)) + } + } + + return out.String() +} + +// Render the horizontal (top or bottom) portion of a border. +func renderHorizontalEdge(left, middle, right string, width int) string { + if middle == "" { + middle = " " + } + + leftWidth := ansi.StringWidth(left) + rightWidth := ansi.StringWidth(right) + + runes := []rune(middle) + j := 0 + + out := strings.Builder{} + out.WriteString(left) + + for i := 0; i < width-leftWidth-rightWidth; { + r := runes[j] + out.WriteRune(r) + i += ansi.StringWidth(string(r)) + j++ + if j >= len(runes) { + j = 0 + } + } + + out.WriteString(right) + return out.String() +} + +// styleBorder applies foreground and background styling to a border. +func (s Style) styleBorder(border string, fg, bg color.Color) string { + if fg == noColor && bg == noColor { + return border + } + var style ansi.Style + if fg != noColor { + style = style.ForegroundColor(fg) + } + if bg != noColor { + style = style.BackgroundColor(bg) + } + return style.Styled(border) +} + +// styleBorderBlend applies foreground and background styling to a border, using blending. +func (s Style) styleBorderBlend(border string, fg []color.Color, bg color.Color) string { + var out strings.Builder + var style ansi.Style + var i int + + gr := uniseg.NewGraphemes(border) + for gr.Next() { + style = style[:0] + if fg[i] != noColor { + style = style.ForegroundColor(fg[i]) + } + if bg != noColor { + style = style.BackgroundColor(bg) + } + _, _ = out.WriteString(style.String()) + _, _ = out.Write(gr.Bytes()) + i++ + } + _, _ = out.WriteString(ansi.ResetStyle) + return out.String() +} + +func maxRuneWidth(str string) int { + switch len(str) { + case 0: + return 0 + case 1: + return displaywidth.String(str) + } + + var width int + + g := displaywidth.StringGraphemes(str) + for g.Next() { + width = max(width, g.Width()) + } + return width +} + +func getFirstRuneAsString(str string) string { + if str == "" { + return str + } + _, size := utf8.DecodeRuneInString(str) + return str[:size] +} diff --git a/vendor/charm.land/lipgloss/v2/canvas.go b/vendor/charm.land/lipgloss/v2/canvas.go new file mode 100644 index 000000000..396defb8b --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/canvas.go @@ -0,0 +1,88 @@ +package lipgloss + +import ( + uv "github.com/charmbracelet/ultraviolet" + "github.com/charmbracelet/x/ansi" +) + +// Canvas is a cell-buffer that can be used to compose and draw [uv.Drawable]s +// like [Layer]s. +// +// Composed drawables are drawn onto the canvas in the order they were +// composed, meaning later drawables will appear "on top" of earlier ones. +// +// A canvas can read, modify, and render its cell contents. +// +// It implements [uv.Screen] and [uv.Drawable]. +type Canvas struct { + scr uv.ScreenBuffer +} + +var _ uv.Screen = (*Canvas)(nil) + +// NewCanvas creates a new [Canvas] with the given size. +func NewCanvas(width, height int) *Canvas { + c := new(Canvas) + c.scr = uv.NewScreenBuffer(width, height) + c.scr.Method = ansi.GraphemeWidth + return c +} + +// Resize resizes the canvas to the given width and height. +func (c *Canvas) Resize(width, height int) { + c.scr.Resize(width, height) +} + +// Clear clears the canvas. +func (c *Canvas) Clear() { + c.scr.Clear() +} + +// Bounds implements [uv.Screen]. +func (c *Canvas) Bounds() uv.Rectangle { + return c.scr.Bounds() +} + +// Width returns the width of the canvas. +func (c *Canvas) Width() int { + return c.scr.Width() +} + +// Height returns the height of the canvas. +func (c *Canvas) Height() int { + return c.scr.Height() +} + +// CellAt implements [uv.Screen]. +func (c *Canvas) CellAt(x int, y int) *uv.Cell { + return c.scr.CellAt(x, y) +} + +// SetCell implements [uv.Screen]. +func (c *Canvas) SetCell(x int, y int, cell *uv.Cell) { + c.scr.SetCell(x, y, cell) +} + +// WidthMethod implements [uv.Screen]. +func (c *Canvas) WidthMethod() uv.WidthMethod { + return c.scr.WidthMethod() +} + +// Compose composes a [Layer] or any [uv.Drawable] onto the [Canvas]. +func (c *Canvas) Compose(drawer uv.Drawable) *Canvas { + drawer.Draw(c, c.Bounds()) + return c +} + +// Draw draws the [Canvas] onto the given [uv.Screen] within the specified +// area. +// +// It implements [uv.Drawable]. +func (c *Canvas) Draw(scr uv.Screen, area uv.Rectangle) { + c.scr.Draw(scr, area) +} + +// Render renders the canvas into a styled string. +func (c *Canvas) Render() string { + return c.scr.Render() +} diff --git a/vendor/charm.land/lipgloss/v2/color.go b/vendor/charm.land/lipgloss/v2/color.go new file mode 100644 index 000000000..7443dc10b --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/color.go @@ -0,0 +1,359 @@ +package lipgloss + +import ( + "cmp" + "errors" + "image/color" + "strconv" + "strings" + + "github.com/charmbracelet/colorprofile" + "github.com/charmbracelet/x/ansi" + "github.com/lucasb-eyer/go-colorful" +) + +func clamp[T cmp.Ordered](v, low, high T) T { + if high < low { + high, low = low, high + } + return min(high, max(low, v)) +} + +// 4-bit color constants. +const ( + Black ansi.BasicColor = iota + Red + Green + Yellow + Blue + Magenta + Cyan + White + + BrightBlack + BrightRed + BrightGreen + BrightYellow + BrightBlue + BrightMagenta + BrightCyan + BrightWhite +) + +var noColor = NoColor{} + +// NoColor is used to specify the absence of color styling. When this is active +// foreground colors will be rendered with the terminal's default text color, +// and background colors will not be drawn at all. +// +// Example usage: +// +// var style = someStyle.Background(lipgloss.NoColor{}) +type NoColor struct{} + +// RGBA returns the RGBA value of this color. Because we have to return +// something, despite this color being the absence of color, we're returning +// black with 100% opacity. +// +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. +func (n NoColor) RGBA() (r, g, b, a uint32) { + return 0x0, 0x0, 0x0, 0xFFFF //nolint:mnd +} + +// Color specifies a color by hex or ANSI256 value. For example: +// +// ansiColor := lipgloss.Color("1") // The same as lipgloss.Red +// ansi256Color := lipgloss.Color("21") +// hexColor := lipgloss.Color("#0000ff") +func Color(s string) color.Color { + if strings.HasPrefix(s, "#") { + c, err := parseHex(s) + if err != nil { + return noColor + } + return c + } + + i, err := strconv.Atoi(s) + if err != nil { + return noColor + } + + if i < 0 { + // Only positive numbers + i = -i + } + + if i < 16 { + return ansi.BasicColor(i) //nolint:gosec + } else if i < 256 { + return ANSIColor(i) //nolint:gosec + } + + r, g, b := uint8((i>>16)&0xff), uint8(i>>8&0xff), uint8(i&0xff) //nolint:gosec + return color.RGBA{R: r, G: g, B: b, A: 0xff} +} + +var errInvalidFormat = errors.New("invalid hex format") // pre-allocated. + +// parseHex parses a hex color string and returns a color.RGBA. The string can be +// in the format #RRGGBB or #RGB. This is a more performant implementation of +// [colorful.Hex]. +func parseHex(s string) (c color.RGBA, err error) { + c.A = 0xff + + if len(s) == 0 || s[0] != '#' { + return c, errInvalidFormat + } + + hexToByte := func(b byte) byte { + switch { + case b >= '0' && b <= '9': + return b - '0' + case b >= 'a' && b <= 'f': + return b - 'a' + 10 + case b >= 'A' && b <= 'F': + return b - 'A' + 10 + } + err = errInvalidFormat + return 0 + } + + switch len(s) { + case 7: + c.R = hexToByte(s[1])<<4 + hexToByte(s[2]) + c.G = hexToByte(s[3])<<4 + hexToByte(s[4]) + c.B = hexToByte(s[5])<<4 + hexToByte(s[6]) + case 4: + c.R = hexToByte(s[1]) * 17 + c.G = hexToByte(s[2]) * 17 + c.B = hexToByte(s[3]) * 17 + default: + err = errInvalidFormat + } + return c, err +} + +// RGBColor is a color specified by red, green, and blue values. +type RGBColor struct { + R uint8 + G uint8 + B uint8 +} + +// RGBA returns the RGBA value of this color. This satisfies the Go Color +// interface. +func (c RGBColor) RGBA() (r, g, b, a uint32) { + const shift = 8 + r |= uint32(c.R) << shift + g |= uint32(c.G) << shift + b |= uint32(c.B) << shift + a = 0xFFFF + return +} + +// ANSIColor is a color specified by an ANSI256 color value. +// +// Example usage: +// +// colorA := lipgloss.ANSIColor(8) +// colorB := lipgloss.ANSIColor(134) +type ANSIColor = ansi.IndexedColor + +// LightDarkFunc is a function that returns a color based on whether the +// terminal has a light or dark background. You can create one of these with +// [LightDark]. +// +// Example: +// +// lightDark := lipgloss.LightDark(hasDarkBackground) +// red, blue := lipgloss.Color("#ff0000"), lipgloss.Color("#0000ff") +// myHotColor := lightDark(red, blue) +// +// For more info see [LightDark]. +type LightDarkFunc func(light, dark color.Color) color.Color + +// LightDark is a simple helper type that can be used to choose the appropriate +// color based on whether the terminal has a light or dark background. +// +// lightDark := lipgloss.LightDark(hasDarkBackground) +// red, blue := lipgloss.Color("#ff0000"), lipgloss.Color("#0000ff") +// myHotColor := lightDark(red, blue) +// +// In practice, there are slightly different workflows between Bubble Tea and +// Lip Gloss standalone. +// +// In Bubble Tea, listen for tea.BackgroundColorMsg, which automatically +// flows through Update on start. This message will be received whenever the +// background color changes: +// +// case tea.BackgroundColorMsg: +// m.hasDarkBackground = msg.IsDark() +// +// Later, when you're rendering use: +// +// lightDark := lipgloss.LightDark(m.hasDarkBackground) +// red, blue := lipgloss.Color("#ff0000"), lipgloss.Color("#0000ff") +// myHotColor := lightDark(red, blue) +// +// In standalone Lip Gloss, the workflow is simpler: +// +// hasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stdout) +// lightDark := lipgloss.LightDark(hasDarkBG) +// red, blue := lipgloss.Color("#ff0000"), lipgloss.Color("#0000ff") +// myHotColor := lightDark(red, blue) +func LightDark(isDark bool) LightDarkFunc { + return func(light, dark color.Color) color.Color { + if isDark { + return dark + } + return light + } +} + +// isDarkColor returns whether the given color is dark (based on the luminance +// portion of the color as interpreted as HSL). +// +// Example usage: +// +// color := lipgloss.Color("#0000ff") +// if lipgloss.isDarkColor(color) { +// fmt.Println("It's dark! I love darkness!") +// } else { +// fmt.Println("It's light! Cover your eyes!") +// } +func isDarkColor(c color.Color) bool { + col, ok := colorful.MakeColor(c) + if !ok { + return true + } + + _, _, l := col.Hsl() + return l < 0.5 //nolint:mnd +} + +// CompleteFunc is a function that returns the appropriate color based on the +// given color profile. +// +// Example usage: +// +// p := colorprofile.Detect(os.Stderr, os.Environ()) +// complete := lipgloss.Complete(p) +// color := complete( +// lipgloss.Color(1), // ANSI +// lipgloss.Color(124), // ANSI256 +// lipgloss.Color("#ff34ac"), // TrueColor +// ) +// fmt.Println("Ooh, pretty color: ", color) +// +// For more info see [Complete]. +type CompleteFunc func(ansi, ansi256, truecolor color.Color) color.Color + +// Complete returns a function that will return the appropriate color based on +// the given color profile. +// +// Example usage: +// +// p := colorprofile.Detect(os.Stderr, os.Environ()) +// complete := lipgloss.Complete(p) +// color := complete( +// lipgloss.Color(1), // ANSI +// lipgloss.Color(124), // ANSI256 +// lipgloss.Color("#ff34ac"), // TrueColor +// ) +// fmt.Println("Ooh, pretty color: ", color) +func Complete(p colorprofile.Profile) CompleteFunc { + return func(ansi, ansi256, truecolor color.Color) color.Color { + switch p { //nolint:exhaustive + case colorprofile.ANSI: + return ansi + case colorprofile.ANSI256: + return ansi256 + case colorprofile.TrueColor: + return truecolor + } + return noColor + } +} + +// ensureNotTransparent ensures that the alpha value of a color is not 0, and if +// it is, we will set it to 1. This is useful for when we are converting from +// RGB -> RGBA, and the alpha value is lost in the conversion for gradient purposes. +func ensureNotTransparent(c color.Color) color.Color { + _, _, _, a := c.RGBA() + if a == 0 { + return Alpha(c, 1) + } + return c +} + +// Alpha adjusts the alpha value of a color using a 0-1 (clamped) float scale +// 0 = transparent, 1 = opaque. +func Alpha(c color.Color, alpha float64) color.Color { + if c == nil { + return nil + } + + r, g, b, _ := c.RGBA() + return color.RGBA{ + R: uint8(min(255, float64(r>>8))), + G: uint8(min(255, float64(g>>8))), + B: uint8(min(255, float64(b>>8))), + A: uint8(clamp(alpha, 0, 1) * 255), + } +} + +// Complementary returns the complementary color (180° away on color wheel) of +// the given color. This is useful for creating a contrasting color. +func Complementary(c color.Color) color.Color { + if c == nil { + return nil + } + + // Offset hue by 180°. + cf, _ := colorful.MakeColor(ensureNotTransparent(c)) + + h, s, v := cf.Hsv() + h += 180 + if h >= 360 { + h -= 360 + } else if h < 0 { + h += 360 + } + + return colorful.Hsv(h, s, v).Clamped() +} + +// Darken takes a color and makes it darker by a specific percentage (0-1, clamped). +func Darken(c color.Color, percent float64) color.Color { + if c == nil { + return nil + } + + mult := 1.0 - clamp(percent, 0, 1) + + r, g, b, a := c.RGBA() + return color.RGBA{ + R: uint8(float64(r>>8) * mult), + G: uint8(float64(g>>8) * mult), + B: uint8(float64(b>>8) * mult), + A: uint8(min(255, float64(a>>8))), + } +} + +// Lighten makes a color lighter by a specific percentage (0-1, clamped). +func Lighten(c color.Color, percent float64) color.Color { + if c == nil { + return nil + } + + add := 255 * (clamp(percent, 0, 1)) + + r, g, b, a := c.RGBA() + return color.RGBA{ + R: uint8(min(255, float64(r>>8)+add)), + G: uint8(min(255, float64(g>>8)+add)), + B: uint8(min(255, float64(b>>8)+add)), + A: uint8(min(255, float64(a>>8))), + } +} diff --git a/vendor/charm.land/lipgloss/v2/get.go b/vendor/charm.land/lipgloss/v2/get.go new file mode 100644 index 000000000..d1fe1d669 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/get.go @@ -0,0 +1,657 @@ +package lipgloss + +import ( + "image/color" + "strings" + + "github.com/charmbracelet/x/ansi" +) + +// GetBold returns the style's bold value. If no value is set false is returned. +func (s Style) GetBold() bool { + return s.getAsBool(boldKey, false) +} + +// GetItalic returns the style's italic value. If no value is set false is +// returned. +func (s Style) GetItalic() bool { + return s.getAsBool(italicKey, false) +} + +// GetUnderline returns the style's underline value. If no value is set false is +// returned. +func (s Style) GetUnderline() bool { + return s.ul != UnderlineNone +} + +// GetUnderlineStyle returns the style's underline style. If no value is set +// UnderlineNone is returned. +func (s Style) GetUnderlineStyle() Underline { + return s.ul +} + +// GetUnderlineColor returns the style's underline color. If no value is set +// NoColor{} is returned. +func (s Style) GetUnderlineColor() color.Color { + return s.getAsColor(underlineColorKey) +} + +// GetStrikethrough returns the style's strikethrough value. If no value is set false +// is returned. +func (s Style) GetStrikethrough() bool { + return s.getAsBool(strikethroughKey, false) +} + +// GetReverse returns the style's reverse value. If no value is set false is +// returned. +func (s Style) GetReverse() bool { + return s.getAsBool(reverseKey, false) +} + +// GetBlink returns the style's blink value. If no value is set false is +// returned. +func (s Style) GetBlink() bool { + return s.getAsBool(blinkKey, false) +} + +// GetFaint returns the style's faint value. If no value is set false is +// returned. +func (s Style) GetFaint() bool { + return s.getAsBool(faintKey, false) +} + +// GetForeground returns the style's foreground color. If no value is set +// NoColor{} is returned. +func (s Style) GetForeground() color.Color { + return s.getAsColor(foregroundKey) +} + +// GetBackground returns the style's background color. If no value is set +// NoColor{} is returned. +func (s Style) GetBackground() color.Color { + return s.getAsColor(backgroundKey) +} + +// GetWidth returns the style's width setting. If no width is set 0 is +// returned. +func (s Style) GetWidth() int { + return s.getAsInt(widthKey) +} + +// GetHeight returns the style's height setting. If no height is set 0 is +// returned. +func (s Style) GetHeight() int { + return s.getAsInt(heightKey) +} + +// GetAlign returns the style's implicit horizontal alignment setting. +// If no alignment is set Position.Left is returned. +func (s Style) GetAlign() Position { + v := s.getAsPosition(alignHorizontalKey) + if v == Position(0) { + return Left + } + return v +} + +// GetAlignHorizontal returns the style's implicit horizontal alignment setting. +// If no alignment is set Position.Left is returned. +func (s Style) GetAlignHorizontal() Position { + v := s.getAsPosition(alignHorizontalKey) + if v == Position(0) { + return Left + } + return v +} + +// GetAlignVertical returns the style's implicit vertical alignment setting. +// If no alignment is set Position.Top is returned. +func (s Style) GetAlignVertical() Position { + v := s.getAsPosition(alignVerticalKey) + if v == Position(0) { + return Top + } + return v +} + +// GetPadding returns the style's top, right, bottom, and left padding values, +// in that order. 0 is returned for unset values. +func (s Style) GetPadding() (top, right, bottom, left int) { + return s.getAsInt(paddingTopKey), + s.getAsInt(paddingRightKey), + s.getAsInt(paddingBottomKey), + s.getAsInt(paddingLeftKey) +} + +// GetPaddingTop returns the style's top padding. If no value is set 0 is +// returned. +func (s Style) GetPaddingTop() int { + return s.getAsInt(paddingTopKey) +} + +// GetPaddingRight returns the style's right padding. If no value is set 0 is +// returned. +func (s Style) GetPaddingRight() int { + return s.getAsInt(paddingRightKey) +} + +// GetPaddingBottom returns the style's bottom padding. If no value is set 0 is +// returned. +func (s Style) GetPaddingBottom() int { + return s.getAsInt(paddingBottomKey) +} + +// GetPaddingLeft returns the style's left padding. If no value is set 0 is +// returned. +func (s Style) GetPaddingLeft() int { + return s.getAsInt(paddingLeftKey) +} + +// GetPaddingChar returns the style's padding character. If no value is set a +// space is returned. +func (s Style) GetPaddingChar() rune { + char := s.getAsRune(paddingCharKey) + if char == 0 { + return ' ' + } + return char +} + +// GetHorizontalPadding returns the style's left and right padding. Unset +// values are measured as 0. +func (s Style) GetHorizontalPadding() int { + return s.getAsInt(paddingLeftKey) + s.getAsInt(paddingRightKey) +} + +// GetVerticalPadding returns the style's top and bottom padding. Unset values +// are measured as 0. +func (s Style) GetVerticalPadding() int { + return s.getAsInt(paddingTopKey) + s.getAsInt(paddingBottomKey) +} + +// GetColorWhitespace returns the style's whitespace coloring setting. If no +// value is set false is returned. +func (s Style) GetColorWhitespace() bool { + return s.getAsBool(colorWhitespaceKey, false) +} + +// GetMargin returns the style's top, right, bottom, and left margins, in that +// order. 0 is returned for unset values. +func (s Style) GetMargin() (top, right, bottom, left int) { + return s.getAsInt(marginTopKey), + s.getAsInt(marginRightKey), + s.getAsInt(marginBottomKey), + s.getAsInt(marginLeftKey) +} + +// GetMarginTop returns the style's top margin. If no value is set 0 is +// returned. +func (s Style) GetMarginTop() int { + return s.getAsInt(marginTopKey) +} + +// GetMarginRight returns the style's right margin. If no value is set 0 is +// returned. +func (s Style) GetMarginRight() int { + return s.getAsInt(marginRightKey) +} + +// GetMarginBottom returns the style's bottom margin. If no value is set 0 is +// returned. +func (s Style) GetMarginBottom() int { + return s.getAsInt(marginBottomKey) +} + +// GetMarginLeft returns the style's left margin. If no value is set 0 is +// returned. +func (s Style) GetMarginLeft() int { + return s.getAsInt(marginLeftKey) +} + +// GetMarginChar returns the style's padding character. If no value is set a +// space is returned. +func (s Style) GetMarginChar() rune { + char := s.getAsRune(marginCharKey) + if char == 0 { + return ' ' + } + return char +} + +// GetHorizontalMargins returns the style's left and right margins. Unset +// values are measured as 0. +func (s Style) GetHorizontalMargins() int { + return s.getAsInt(marginLeftKey) + s.getAsInt(marginRightKey) +} + +// GetVerticalMargins returns the style's top and bottom margins. Unset values +// are measured as 0. +func (s Style) GetVerticalMargins() int { + return s.getAsInt(marginTopKey) + s.getAsInt(marginBottomKey) +} + +// GetBorder returns the style's border style (type Border) and value for the +// top, right, bottom, and left in that order. If no value is set for the +// border style, Border{} is returned. For all other unset values false is +// returned. +func (s Style) GetBorder() (b Border, top, right, bottom, left bool) { + return s.getBorderStyle(), + s.getAsBool(borderTopKey, false), + s.getAsBool(borderRightKey, false), + s.getAsBool(borderBottomKey, false), + s.getAsBool(borderLeftKey, false) +} + +// GetBorderStyle returns the style's border style (type Border). If no value +// is set Border{} is returned. +func (s Style) GetBorderStyle() Border { + return s.getBorderStyle() +} + +// GetBorderTop returns the style's top border setting. If no value is set +// false is returned. +func (s Style) GetBorderTop() bool { + return s.getAsBool(borderTopKey, false) +} + +// GetBorderRight returns the style's right border setting. If no value is set +// false is returned. +func (s Style) GetBorderRight() bool { + return s.getAsBool(borderRightKey, false) +} + +// GetBorderBottom returns the style's bottom border setting. If no value is +// set false is returned. +func (s Style) GetBorderBottom() bool { + return s.getAsBool(borderBottomKey, false) +} + +// GetBorderLeft returns the style's left border setting. If no value is +// set false is returned. +func (s Style) GetBorderLeft() bool { + return s.getAsBool(borderLeftKey, false) +} + +// GetBorderTopForeground returns the style's border top foreground color. If +// no value is set NoColor{} is returned. +func (s Style) GetBorderTopForeground() color.Color { + return s.getAsColor(borderTopForegroundKey) +} + +// GetBorderRightForeground returns the style's border right foreground color. +// If no value is set NoColor{} is returned. +func (s Style) GetBorderRightForeground() color.Color { + return s.getAsColor(borderRightForegroundKey) +} + +// GetBorderBottomForeground returns the style's border bottom foreground +// color. If no value is set NoColor{} is returned. +func (s Style) GetBorderBottomForeground() color.Color { + return s.getAsColor(borderBottomForegroundKey) +} + +// GetBorderLeftForeground returns the style's border left foreground +// color. If no value is set NoColor{} is returned. +func (s Style) GetBorderLeftForeground() color.Color { + return s.getAsColor(borderLeftForegroundKey) +} + +// GetBorderForegroundBlend returns the style's border blend foreground +// colors. If no value is set, nil is returned. +func (s Style) GetBorderForegroundBlend() []color.Color { + return s.getAsColors(borderForegroundBlendKey) +} + +// GetBorderForegroundBlendOffset returns the style's border blend offset. If no +// value is set, 0 is returned. +func (s Style) GetBorderForegroundBlendOffset() int { + return s.getAsInt(borderForegroundBlendOffsetKey) +} + +// GetBorderTopBackground returns the style's border top background color. If +// no value is set NoColor{} is returned. +func (s Style) GetBorderTopBackground() color.Color { + return s.getAsColor(borderTopBackgroundKey) +} + +// GetBorderRightBackground returns the style's border right background color. +// If no value is set NoColor{} is returned. +func (s Style) GetBorderRightBackground() color.Color { + return s.getAsColor(borderRightBackgroundKey) +} + +// GetBorderBottomBackground returns the style's border bottom background +// color. If no value is set NoColor{} is returned. +func (s Style) GetBorderBottomBackground() color.Color { + return s.getAsColor(borderBottomBackgroundKey) +} + +// GetBorderLeftBackground returns the style's border left background +// color. If no value is set NoColor{} is returned. +func (s Style) GetBorderLeftBackground() color.Color { + return s.getAsColor(borderLeftBackgroundKey) +} + +// GetBorderTopWidth returns the width of the top border. If borders contain +// runes of varying widths, the widest rune is returned. If no border exists on +// the top edge, 0 is returned. +// +// Deprecated: This function simply calls Style.GetBorderTopSize. +func (s Style) GetBorderTopWidth() int { + return s.GetBorderTopSize() +} + +// GetBorderTopSize returns the width of the top border. If borders contain +// runes of varying widths, the widest rune is returned. If no border exists on +// the top edge, 0 is returned. +func (s Style) GetBorderTopSize() int { + if s.isBorderStyleSetWithoutSides() { + return 1 + } + if !s.getAsBool(borderTopKey, false) { + return 0 + } + return s.getBorderStyle().GetTopSize() +} + +// GetBorderLeftSize returns the width of the left border. If borders contain +// runes of varying widths, the widest rune is returned. If no border exists on +// the left edge, 0 is returned. +func (s Style) GetBorderLeftSize() int { + if s.isBorderStyleSetWithoutSides() { + return 1 + } + if !s.getAsBool(borderLeftKey, false) { + return 0 + } + return s.getBorderStyle().GetLeftSize() +} + +// GetBorderBottomSize returns the width of the bottom border. If borders +// contain runes of varying widths, the widest rune is returned. If no border +// exists on the left edge, 0 is returned. +func (s Style) GetBorderBottomSize() int { + if s.isBorderStyleSetWithoutSides() { + return 1 + } + if !s.getAsBool(borderBottomKey, false) { + return 0 + } + return s.getBorderStyle().GetBottomSize() +} + +// GetBorderRightSize returns the width of the right border. If borders +// contain runes of varying widths, the widest rune is returned. If no border +// exists on the right edge, 0 is returned. +func (s Style) GetBorderRightSize() int { + if s.isBorderStyleSetWithoutSides() { + return 1 + } + if !s.getAsBool(borderRightKey, false) { + return 0 + } + return s.getBorderStyle().GetRightSize() +} + +// GetHorizontalBorderSize returns the width of the horizontal borders. If +// borders contain runes of varying widths, the widest rune is returned. If no +// border exists on the horizontal edges, 0 is returned. +func (s Style) GetHorizontalBorderSize() int { + return s.GetBorderLeftSize() + s.GetBorderRightSize() +} + +// GetVerticalBorderSize returns the width of the vertical borders. If +// borders contain runes of varying widths, the widest rune is returned. If no +// border exists on the vertical edges, 0 is returned. +func (s Style) GetVerticalBorderSize() int { + return s.GetBorderTopSize() + s.GetBorderBottomSize() +} + +// GetInline returns the style's inline setting. If no value is set false is +// returned. +func (s Style) GetInline() bool { + return s.getAsBool(inlineKey, false) +} + +// GetMaxWidth returns the style's max width setting. If no value is set 0 is +// returned. +func (s Style) GetMaxWidth() int { + return s.getAsInt(maxWidthKey) +} + +// GetMaxHeight returns the style's max height setting. If no value is set 0 is +// returned. +func (s Style) GetMaxHeight() int { + return s.getAsInt(maxHeightKey) +} + +// GetTabWidth returns the style's tab width setting. If no value is set 4 is +// returned which is the implicit default. +func (s Style) GetTabWidth() int { + return s.getAsInt(tabWidthKey) +} + +// GetUnderlineSpaces returns whether or not the style is set to underline +// spaces. If not value is set false is returned. +func (s Style) GetUnderlineSpaces() bool { + return s.getAsBool(underlineSpacesKey, false) +} + +// GetStrikethroughSpaces returns whether or not the style is set to strikethrough +// spaces. If not value is set false is returned. +func (s Style) GetStrikethroughSpaces() bool { + return s.getAsBool(strikethroughSpacesKey, false) +} + +// GetHorizontalFrameSize returns the sum of the style's horizontal margins, padding +// and border widths. +// +// Provisional: this method may be renamed. +func (s Style) GetHorizontalFrameSize() int { + return s.GetHorizontalMargins() + s.GetHorizontalPadding() + s.GetHorizontalBorderSize() +} + +// GetVerticalFrameSize returns the sum of the style's vertical margins, padding +// and border widths. +// +// Provisional: this method may be renamed. +func (s Style) GetVerticalFrameSize() int { + return s.GetVerticalMargins() + s.GetVerticalPadding() + s.GetVerticalBorderSize() +} + +// GetFrameSize returns the sum of the margins, padding and border width for +// both the horizontal and vertical margins. +func (s Style) GetFrameSize() (x, y int) { + return s.GetHorizontalFrameSize(), s.GetVerticalFrameSize() +} + +// GetTransform returns the transform set on the style. If no transform is set +// nil is returned. +func (s Style) GetTransform() func(string) string { + return s.getAsTransform(transformKey) +} + +// GetHyperlink returns the hyperlink along with its parameters. If no +// hyperlink is set, empty strings are returned. +func (s Style) GetHyperlink() (link, params string) { + if s.isSet(linkKey) { + link = s.link + } + if s.isSet(linkParamsKey) { + params = s.linkParams + } + return +} + +// Returns whether or not the given property is set. +func (s Style) isSet(k propKey) bool { + return s.props.has(k) +} + +func (s Style) getAsRune(k propKey) rune { + if !s.isSet(k) { + return 0 + } + switch k { //nolint:exhaustive + case paddingCharKey: + return s.paddingChar + case marginCharKey: + return s.marginChar + } + return 0 +} + +func (s Style) getAsBool(k propKey, defaultVal bool) bool { + if !s.isSet(k) { + return defaultVal + } + return s.attrs&int(k) != 0 +} + +func (s Style) getAsColors(k propKey) (colors []color.Color) { + if !s.isSet(k) { + return nil + } + + switch k { //nolint:exhaustive + case borderForegroundBlendKey: + return s.borderBlendFgColor + } + + return nil +} + +func (s Style) getAsColor(k propKey) color.Color { + if !s.isSet(k) { + return noColor + } + + var c color.Color + switch k { //nolint:exhaustive + case foregroundKey: + c = s.fgColor + case backgroundKey: + c = s.bgColor + case marginBackgroundKey: + c = s.marginBgColor + case borderTopForegroundKey: + c = s.borderTopFgColor + case borderRightForegroundKey: + c = s.borderRightFgColor + case borderBottomForegroundKey: + c = s.borderBottomFgColor + case borderLeftForegroundKey: + c = s.borderLeftFgColor + case borderTopBackgroundKey: + c = s.borderTopBgColor + case borderRightBackgroundKey: + c = s.borderRightBgColor + case borderBottomBackgroundKey: + c = s.borderBottomBgColor + case borderLeftBackgroundKey: + c = s.borderLeftBgColor + case underlineColorKey: + c = s.ulColor + } + + if c != nil { + return c + } + + return noColor +} + +func (s Style) getAsInt(k propKey) int { + if !s.isSet(k) { + return 0 + } + switch k { //nolint:exhaustive + case widthKey: + return s.width + case heightKey: + return s.height + case paddingTopKey: + return s.paddingTop + case paddingRightKey: + return s.paddingRight + case paddingBottomKey: + return s.paddingBottom + case paddingLeftKey: + return s.paddingLeft + case marginTopKey: + return s.marginTop + case marginRightKey: + return s.marginRight + case marginBottomKey: + return s.marginBottom + case marginLeftKey: + return s.marginLeft + case borderForegroundBlendOffsetKey: + return s.borderForegroundBlendOffset + case maxWidthKey: + return s.maxWidth + case maxHeightKey: + return s.maxHeight + case tabWidthKey: + return s.tabWidth + } + return 0 +} + +func (s Style) getAsPosition(k propKey) Position { + if !s.isSet(k) { + return Position(0) + } + switch k { //nolint:exhaustive + case alignHorizontalKey: + return s.alignHorizontal + case alignVerticalKey: + return s.alignVertical + } + return Position(0) +} + +func (s Style) getBorderStyle() Border { + if !s.isSet(borderStyleKey) { + return noBorder + } + return s.borderStyle +} + +func (s Style) getAsTransform(propKey) func(string) string { + if !s.isSet(transformKey) { + return nil + } + return s.transform +} + +// Split a string into lines, additionally returning the size of the widest +// line. +func getLines(s string) (lines []string, widest int) { + s = strings.ReplaceAll(s, "\t", " ") + s = strings.ReplaceAll(s, "\r\n", "\n") + lines = strings.Split(s, "\n") + + for _, l := range lines { + w := ansi.StringWidth(l) + if widest < w { + widest = w + } + } + + return lines, widest +} + +// isBorderStyleSetWithoutSides returns true if the border style is set but no +// sides are set. This is used to determine if the border should be rendered by +// default. +func (s Style) isBorderStyleSetWithoutSides() bool { + var ( + border = s.getBorderStyle() + topSet = s.isSet(borderTopKey) + rightSet = s.isSet(borderRightKey) + bottomSet = s.isSet(borderBottomKey) + leftSet = s.isSet(borderLeftKey) + ) + return border != noBorder && !(topSet || rightSet || bottomSet || leftSet) //nolint:staticcheck +} diff --git a/vendor/charm.land/lipgloss/v2/join.go b/vendor/charm.land/lipgloss/v2/join.go new file mode 100644 index 000000000..349ae552f --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/join.go @@ -0,0 +1,175 @@ +package lipgloss + +import ( + "math" + "strings" + + "github.com/charmbracelet/x/ansi" +) + +// JoinHorizontal is a utility function for horizontally joining two +// potentially multi-lined strings along a vertical axis. The first argument is +// the position, with 0 being all the way at the top and 1 being all the way +// at the bottom. +// +// If you just want to align to the top, center or bottom you may as well just +// use the helper constants Top, Center, and Bottom. +// +// Example: +// +// blockB := "...\n...\n..." +// blockA := "...\n...\n...\n...\n..." +// +// // Join 20% from the top +// str := lipgloss.JoinHorizontal(0.2, blockA, blockB) +// +// // Join on the top edge +// str := lipgloss.JoinHorizontal(lipgloss.Top, blockA, blockB) +func JoinHorizontal(pos Position, strs ...string) string { + if len(strs) == 0 { + return "" + } + if len(strs) == 1 { + return strs[0] + } + + var ( + // Groups of strings broken into multiple lines + blocks = make([][]string, len(strs)) + + // Max line widths for the above text blocks + maxWidths = make([]int, len(strs)) + + // Height of the tallest block + maxHeight int + ) + + // Break text blocks into lines and get max widths for each text block + for i, str := range strs { + blocks[i], maxWidths[i] = getLines(str) + if len(blocks[i]) > maxHeight { + maxHeight = len(blocks[i]) + } + } + + // Add extra lines to make each side the same height + for i := range blocks { + if len(blocks[i]) >= maxHeight { + continue + } + + extraLines := make([]string, maxHeight-len(blocks[i])) + + switch pos { + case Top: + blocks[i] = append(blocks[i], extraLines...) + + case Bottom: + blocks[i] = append(extraLines, blocks[i]...) + + default: // Somewhere in the middle + n := len(extraLines) + split := int(math.Round(float64(n) * pos.value())) + top := n - split + bottom := n - top + + blocks[i] = append(extraLines[top:], blocks[i]...) + blocks[i] = append(blocks[i], extraLines[bottom:]...) + } + } + + // Merge lines + var b strings.Builder + for i := range blocks[0] { // remember, all blocks have the same number of members now + for j, block := range blocks { + b.WriteString(block[i]) + + // Also make lines the same length + b.WriteString(strings.Repeat(" ", maxWidths[j]-ansi.StringWidth(block[i]))) + } + if i < len(blocks[0])-1 { + b.WriteRune('\n') + } + } + + return b.String() +} + +// JoinVertical is a utility function for vertically joining two potentially +// multi-lined strings along a horizontal axis. The first argument is the +// position, with 0 being all the way to the left and 1 being all the way to +// the right. +// +// If you just want to align to the left, right or center you may as well just +// use the helper constants Left, Center, and Right. +// +// Example: +// +// blockB := "...\n...\n..." +// blockA := "...\n...\n...\n...\n..." +// +// // Join 20% from the top +// str := lipgloss.JoinVertical(0.2, blockA, blockB) +// +// // Join on the right edge +// str := lipgloss.JoinVertical(lipgloss.Right, blockA, blockB) +func JoinVertical(pos Position, strs ...string) string { + if len(strs) == 0 { + return "" + } + if len(strs) == 1 { + return strs[0] + } + + var ( + blocks = make([][]string, len(strs)) + maxWidth int + ) + + for i := range strs { + var w int + blocks[i], w = getLines(strs[i]) + if w > maxWidth { + maxWidth = w + } + } + + var b strings.Builder + for i, block := range blocks { + for j, line := range block { + w := maxWidth - ansi.StringWidth(line) + + switch pos { + case Left: + b.WriteString(line) + b.WriteString(strings.Repeat(" ", w)) + + case Right: + b.WriteString(strings.Repeat(" ", w)) + b.WriteString(line) + + default: // Somewhere in the middle + if w < 1 { + b.WriteString(line) + break + } + + split := int(math.Round(float64(w) * pos.value())) + right := w - split + left := w - right + + b.WriteString(strings.Repeat(" ", left)) + b.WriteString(line) + b.WriteString(strings.Repeat(" ", right)) + } + + // Write a newline as long as we're not on the last line of the + // last block. + if !(i == len(blocks)-1 && j == len(block)-1) { //nolint:staticcheck + b.WriteRune('\n') + } + } + } + + return b.String() +} diff --git a/vendor/charm.land/lipgloss/v2/layer.go b/vendor/charm.land/lipgloss/v2/layer.go new file mode 100644 index 000000000..e30d1ef2a --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/layer.go @@ -0,0 +1,327 @@ +package lipgloss + +import ( + "fmt" + "image" + "slices" + + uv "github.com/charmbracelet/ultraviolet" +) + +// Layer represents a visual layer with content and positioning. It's a pure +// data structure that defines the layer hierarchy without any computation. +type Layer struct { + id string + content string + width, height int + x, y, z int + layers []*Layer +} + +// NewLayer creates a new [Layer] with the given content and optional child layers. +func NewLayer(content string, layers ...*Layer) *Layer { + l := &Layer{ + content: content, + } + l.AddLayers(layers...) + return l +} + +// GetContent returns the content of the Layer. +func (l *Layer) GetContent() string { + return l.content +} + +// Width returns the width of the Layer. +func (l *Layer) Width() int { + return l.width +} + +// Height returns the height of the Layer. +func (l *Layer) Height() int { + return l.height +} + +// GetID returns the ID of the Layer. +func (l *Layer) GetID() string { + return l.id +} + +// ID sets the ID of the Layer. +func (l *Layer) ID(id string) *Layer { + l.id = id + return l +} + +// X sets the x-coordinate of the Layer relative to its parent. +func (l *Layer) X(x int) *Layer { + l.x = x + return l +} + +// Y sets the y-coordinate of the Layer relative to its parent. +func (l *Layer) Y(y int) *Layer { + l.y = y + return l +} + +// Z sets the z-index of the Layer relative to its parent. +func (l *Layer) Z(z int) *Layer { + l.z = z + return l +} + +// GetX returns the x-coordinate of the Layer relative to its parent. +func (l *Layer) GetX() int { + return l.x +} + +// GetY returns the y-coordinate of the Layer relative to its parent. +func (l *Layer) GetY() int { + return l.y +} + +// GetZ returns the z-index of the Layer relative to its parent. +func (l *Layer) GetZ() int { + return l.z +} + +// AddLayers adds child layers to the Layer. +func (l *Layer) AddLayers(layers ...*Layer) *Layer { + for i, layer := range layers { + if layer == nil { + panic(fmt.Sprintf("layer at index %d is nil", i)) + } + l.layers = append(l.layers, layer) + } + area := l.boundsWithOffset(0, 0) + l.width = area.Dx() + l.height = area.Dy() + return l +} + +// GetLayer returns a descendant layer by its ID, or nil if not found. +// Layers with empty IDs are skipped. +func (l *Layer) GetLayer(id string) *Layer { + if id == "" { + return nil + } + if l.id == id { + return l + } + for _, child := range l.layers { + if found := child.GetLayer(id); found != nil { + return found + } + } + return nil +} + +// MaxZ returns the maximum z-index among this layer and all its descendants. +func (l *Layer) MaxZ() int { + maxZ := l.z + for _, child := range l.layers { + childMaxZ := child.MaxZ() + if childMaxZ > maxZ { + maxZ = childMaxZ + } + } + return maxZ +} + +// boundsWithOffset calculates bounds with parent offset applied. +func (l *Layer) boundsWithOffset(parentX, parentY int) image.Rectangle { + absX := l.x + parentX + absY := l.y + parentY + + width, height := Width(l.content), Height(l.content) + bounds := image.Rectangle{ + Min: image.Pt(absX, absY), + Max: image.Pt(absX+width, absY+height), + } + + for _, child := range l.layers { + bounds = bounds.Union(child.boundsWithOffset(absX, absY)) + } + + return bounds +} + +var _ uv.Drawable = (*Layer)(nil) + +// Draw draws the content of the layer on the screen at the specified area. +func (l *Layer) Draw(scr uv.Screen, area uv.Rectangle) { + content := uv.NewStyledString(l.content) + content.Draw(scr, area) +} + +// LayerHit represents the result of a hit test on a [Layer]. +type LayerHit struct { + id string + layer *Layer + bounds image.Rectangle +} + +// Empty returns true if the LayerHit represents no hit. +func (lh LayerHit) Empty() bool { + return lh.layer == nil +} + +// ID returns the ID of the hit Layer. +func (lh LayerHit) ID() string { + return lh.id +} + +// Layer returns the layer that was hit. +func (lh LayerHit) Layer() *Layer { + return lh.layer +} + +// Bounds returns the bounds of the LayerHit. +func (lh LayerHit) Bounds() image.Rectangle { + return lh.bounds +} + +// Compositor manages the composition of layers. It flattens a layer hierarchy +// once and provides efficient drawing and hit testing operations. All computation +// related to layers happens in the Compositor. +type Compositor struct { + root *Layer + layers []compositeLayer + index map[string]*Layer + bounds image.Rectangle +} + +// compositeLayer holds a flattened layer with its calculated absolute position and bounds. +type compositeLayer struct { + layer *Layer + absX int + absY int + bounds image.Rectangle +} + +// NewCompositor creates a new Compositor with an internal root layer. Optional +// layers can be provided which will be added as children of the root. The layer +// hierarchy is flattened and sorted by z-index for efficient rendering and hit testing. +func NewCompositor(layers ...*Layer) *Compositor { + root := NewLayer("") + root.AddLayers(layers...) + c := &Compositor{ + root: root, + index: make(map[string]*Layer), + } + c.flatten() + return c +} + +// AddLayers adds layers to the compositor's root and refreshes the internal state. +func (c *Compositor) AddLayers(layers ...*Layer) *Compositor { + c.root.AddLayers(layers...) + c.flatten() + return c +} + +// flatten builds the internal flattened layer list and calculates overall bounds. +func (c *Compositor) flatten() { + c.layers = nil + c.index = make(map[string]*Layer) + c.flattenRecursive(c.root, 0, 0) + + // Sort by absolute z-index (lowest to highest for drawing) + slices.SortFunc(c.layers, func(a, b compositeLayer) int { + return a.layer.z - b.layer.z + }) + + // Calculate overall bounds + if len(c.layers) > 0 { + c.bounds = c.layers[0].bounds + for i := 1; i < len(c.layers); i++ { + c.bounds = c.bounds.Union(c.layers[i].bounds) + } + } +} + +// flattenRecursive recursively collects all layers with their absolute positions. +func (c *Compositor) flattenRecursive(layer *Layer, parentX, parentY int) { + absX := layer.x + parentX + absY := layer.y + parentY + + width, height := Width(layer.content), Height(layer.content) + bounds := image.Rectangle{ + Min: image.Pt(absX, absY), + Max: image.Pt(absX+width, absY+height), + } + + c.layers = append(c.layers, compositeLayer{ + layer: layer, + absX: absX, + absY: absY, + bounds: bounds, + }) + + // Index layer by ID if it has one + if layer.id != "" { + c.index[layer.id] = layer + } + + for _, child := range layer.layers { + c.flattenRecursive(child, absX, absY) + } +} + +// Bounds returns the overall bounds of all layers in the compositor. +func (c *Compositor) Bounds() image.Rectangle { + return c.bounds +} + +// Draw draws all layers onto the given [uv.Screen] in z-index order. +func (c *Compositor) Draw(scr uv.Screen, area image.Rectangle) { + for _, cl := range c.layers { + if cl.bounds.Overlaps(area) { + cl.layer.Draw(scr, cl.bounds) + } + } +} + +// Hit performs a hit test at the given (x, y) coordinates. If a layer is hit, +// it returns the ID of the top-most layer at that point. Layers with empty IDs +// are ignored. If no layer is hit, it returns an empty [LayerHit]. +func (c *Compositor) Hit(x, y int) LayerHit { + var hit LayerHit + pt := image.Pt(x, y) + // Check from highest z to lowest (reverse order) + for i := len(c.layers) - 1; i >= 0; i-- { + cl := c.layers[i] + if cl.layer.id != "" && pt.In(cl.bounds) { + hit.id = cl.layer.id + hit.layer = cl.layer + hit.bounds = cl.bounds + return hit + } + } + return hit +} + +// GetLayer returns a layer by its ID, or nil if not found. +// Layers with empty IDs are not indexed and cannot be retrieved. +func (c *Compositor) GetLayer(id string) *Layer { + if id == "" { + return nil + } + return c.index[id] +} + +// Refresh re-flattens the layer hierarchy. Call this after modifying the layer +// tree structure or positions to update the compositor's internal state. +func (c *Compositor) Refresh() { + c.flatten() +} + +// Render renders the compositor into a styled string. This is a helper +// function that creates a temporary canvas, draws the compositor onto it, and +// returns the resulting string. +func (c *Compositor) Render() string { + width, height := c.bounds.Dx(), c.bounds.Dy() + canvas := NewCanvas(width, height) + return canvas.Compose(c).Render() +} diff --git a/vendor/charm.land/lipgloss/v2/lipgloss.go b/vendor/charm.land/lipgloss/v2/lipgloss.go new file mode 100644 index 000000000..3fb4e6f26 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/lipgloss.go @@ -0,0 +1,3 @@ +// Package lipgloss provides style definitions for nice terminal layouts. Built +// with TUIs in mind. +package lipgloss diff --git a/vendor/charm.land/lipgloss/v2/position.go b/vendor/charm.land/lipgloss/v2/position.go new file mode 100644 index 000000000..cea67763f --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/position.go @@ -0,0 +1,134 @@ +package lipgloss + +import ( + "math" + "strings" + + "github.com/charmbracelet/x/ansi" +) + +// Position represents a position along a horizontal or vertical axis. It's in +// situations where an axis is involved, like alignment, joining, placement and +// so on. +// +// A value of 0 represents the start (the left or top) and 1 represents the end +// (the right or bottom). 0.5 represents the center. +// +// There are constants Top, Bottom, Center, Left and Right in this package that +// can be used to aid readability. +type Position float64 + +func (p Position) value() float64 { + return math.Min(1, math.Max(0, float64(p))) +} + +// Position aliases. +const ( + Top Position = 0.0 + Bottom Position = 1.0 + Center Position = 0.5 + Left Position = 0.0 + Right Position = 1.0 +) + +// Place places a string or text block vertically in an unstyled box of a given +// width or height. +func Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string { + return PlaceVertical(height, vPos, PlaceHorizontal(width, hPos, str, opts...), opts...) +} + +// PlaceHorizontal places a string or text block horizontally in an unstyled +// block of a given width. If the given width is shorter than the max width of +// the string (measured by its longest line) this will be a noop. +func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string { + lines, contentWidth := getLines(str) + gap := width - contentWidth + + if gap <= 0 { + return str + } + + ws := newWhitespace(opts...) + + var b strings.Builder + for i, l := range lines { + // Is this line shorter than the longest line? + short := max(0, contentWidth-ansi.StringWidth(l)) + + switch pos { + case Left: + b.WriteString(l) + b.WriteString(ws.render(gap + short)) + + case Right: + b.WriteString(ws.render(gap + short)) + b.WriteString(l) + + default: // somewhere in the middle + totalGap := gap + short + + split := int(math.Round(float64(totalGap) * pos.value())) + left := totalGap - split + right := totalGap - left + + b.WriteString(ws.render(left)) + b.WriteString(l) + b.WriteString(ws.render(right)) + } + + if i < len(lines)-1 { + b.WriteRune('\n') + } + } + + return b.String() +} + +// PlaceVertical places a string or text block vertically in an unstyled block +// of a given height. If the given height is shorter than the height of the +// string (measured by its newlines) then this will be a noop. +func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string { + contentHeight := strings.Count(str, "\n") + 1 + gap := height - contentHeight + + if gap <= 0 { + return str + } + + ws := newWhitespace(opts...) + + _, width := getLines(str) + emptyLine := ws.render(width) + b := strings.Builder{} + + switch pos { + case Top: + b.WriteString(str) + b.WriteRune('\n') + for i := range gap { + b.WriteString(emptyLine) + if i < gap-1 { + b.WriteRune('\n') + } + } + + case Bottom: + b.WriteString(strings.Repeat(emptyLine+"\n", gap)) + b.WriteString(str) + + default: // Somewhere in the middle + split := int(math.Round(float64(gap) * pos.value())) + top := gap - split + bottom := gap - top + + b.WriteString(strings.Repeat(emptyLine+"\n", top)) + b.WriteString(str) + + for range bottom { + b.WriteRune('\n') + b.WriteString(emptyLine) + } + } + + return b.String() +} diff --git a/vendor/charm.land/lipgloss/v2/query.go b/vendor/charm.land/lipgloss/v2/query.go new file mode 100644 index 000000000..96cd5b945 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/query.go @@ -0,0 +1,92 @@ +package lipgloss + +import ( + "fmt" + "image/color" + "os" + "runtime" + + "github.com/charmbracelet/x/term" +) + +func backgroundColor(in term.File, out term.File) (color.Color, error) { + state, err := term.MakeRaw(in.Fd()) + if err != nil { + return nil, fmt.Errorf("error setting raw state to detect background color: %w", err) + } + + defer term.Restore(in.Fd(), state) //nolint:errcheck + + bg, err := queryBackgroundColor(in, out) + if err != nil { + return nil, err + } + + return bg, nil +} + +// BackgroundColor queries the terminal's background color. Typically, you'll +// want to query against stdin and either stdout or stderr, depending on what +// you're writing to. +// +// This function is intended for standalone Lip Gloss use only. If you're using +// Bubble Tea, listen for tea.BackgroundColorMsg in your update function. +func BackgroundColor(in term.File, out term.File) (bg color.Color, err error) { + if runtime.GOOS == "windows" { //nolint:nestif + // On Windows, when the input/output is redirected or piped, we need to + // open the console explicitly. + // See https://learn.microsoft.com/en-us/windows/console/getstdhandle#remarks + if !term.IsTerminal(in.Fd()) { + f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644) //nolint:gosec + if err != nil { + return nil, fmt.Errorf("error opening CONIN$: %w", err) + } + in = f + } + if !term.IsTerminal(out.Fd()) { + f, err := os.OpenFile("CONOUT$", os.O_RDWR, 0o644) //nolint:gosec + if err != nil { + return nil, fmt.Errorf("error opening CONOUT$: %w", err) + } + out = f + } + return backgroundColor(in, out) + } + + // NOTE: On Unix, one of the given files must be a tty. + if !term.IsTerminal(in.Fd()) || !term.IsTerminal(out.Fd()) { + return nil, fmt.Errorf("input/output is not a terminal") + } + for _, f := range []term.File{in, out} { + if bg, err = backgroundColor(f, f); err == nil { + return bg, nil + } + } + + return bg, err +} + +// HasDarkBackground detects whether the terminal has a light or dark +// background. +// +// Typically, you'll want to query against stdin and either stdout or stderr +// depending on what you're writing to. +// +// hasDarkBG := HasDarkBackground(os.Stdin, os.Stdout) +// lightDark := LightDark(hasDarkBG) +// myHotColor := lightDark("#ff0000", "#0000ff") +// +// This is intended for use in standalone Lip Gloss only. In Bubble Tea, listen +// for tea.BackgroundColorMsg in your Update function. +// +// case tea.BackgroundColorMsg: +// hasDarkBackground = msg.IsDark() +// +// By default, this function will return true if it encounters an error. +func HasDarkBackground(in term.File, out term.File) bool { + bg, err := BackgroundColor(in, out) + if err != nil || bg == nil { + return true + } + return isDarkColor(bg) +} diff --git a/vendor/charm.land/lipgloss/v2/ranges.go b/vendor/charm.land/lipgloss/v2/ranges.go new file mode 100644 index 000000000..bb209f0ea --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/ranges.go @@ -0,0 +1,48 @@ +package lipgloss + +import ( + "strings" + + "github.com/charmbracelet/x/ansi" +) + +// StyleRanges applying styling to ranges in a string. Existing styles will be +// taken into account. Ranges should not overlap. +func StyleRanges(s string, ranges ...Range) string { + if len(ranges) == 0 { + return s + } + + var buf strings.Builder + lastIdx := 0 + stripped := ansi.Strip(s) + + // Use Truncate and TruncateLeft to style match.MatchedIndexes without + // losing the original option style: + for _, rng := range ranges { + // Add the text before this match + if rng.Start > lastIdx { + buf.WriteString(ansi.Cut(s, lastIdx, rng.Start)) + } + // Add the matched range with its highlight + buf.WriteString(rng.Style.Render(ansi.Cut(stripped, rng.Start, rng.End))) + lastIdx = rng.End + } + + // Add any remaining text after the last match + buf.WriteString(ansi.TruncateLeft(s, lastIdx, "")) + + return buf.String() +} + +// NewRange returns a range and style that can be used with [StyleRanges]. +func NewRange(start, end int, style Style) Range { + return Range{start, end, style} +} + +// Range is a range of text and associated styling to be used with +// [StyleRanges]. +type Range struct { + Start, End int + Style Style +} diff --git a/vendor/charm.land/lipgloss/v2/runes.go b/vendor/charm.land/lipgloss/v2/runes.go new file mode 100644 index 000000000..7a49e326c --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/runes.go @@ -0,0 +1,43 @@ +package lipgloss + +import ( + "strings" +) + +// StyleRunes apply a given style to runes at the given indices in the string. +// Note that you must provide styling options for both matched and unmatched +// runes. Indices out of bounds will be ignored. +func StyleRunes(str string, indices []int, matched, unmatched Style) string { + // Convert slice of indices to a map for easier lookups + m := make(map[int]struct{}) + for _, i := range indices { + m[i] = struct{}{} + } + + var ( + out strings.Builder + group strings.Builder + style Style + runes = []rune(str) + ) + + for i, r := range runes { + group.WriteRune(r) + + _, matches := m[i] + _, nextMatches := m[i+1] + + if matches != nextMatches || i == len(runes)-1 { + // Flush + if matches { + style = matched + } else { + style = unmatched + } + out.WriteString(style.Render(group.String())) + group.Reset() + } + } + + return out.String() +} diff --git a/vendor/charm.land/lipgloss/v2/set.go b/vendor/charm.land/lipgloss/v2/set.go new file mode 100644 index 000000000..f10b4538a --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/set.go @@ -0,0 +1,932 @@ +package lipgloss + +import ( + "image/color" + "strings" +) + +// Set a value on the underlying rules map. +func (s *Style) set(key propKey, value any) { + // We don't allow negative integers on any of our other values, so just keep + // them at zero or above. We could use uints instead, but the + // conversions are a little tedious, so we're sticking with ints for + // sake of usability. + switch key { + case foregroundKey: + s.fgColor = colorOrNil(value) + case backgroundKey: + s.bgColor = colorOrNil(value) + case underlineColorKey: + s.ulColor = colorOrNil(value) + case underlineKey: + s.ul = value.(Underline) + case widthKey: + s.width = max(0, value.(int)) + case heightKey: + s.height = max(0, value.(int)) + case alignHorizontalKey: + s.alignHorizontal = value.(Position) + case alignVerticalKey: + s.alignVertical = value.(Position) + case paddingTopKey: + s.paddingTop = max(0, value.(int)) + case paddingRightKey: + s.paddingRight = max(0, value.(int)) + case paddingBottomKey: + s.paddingBottom = max(0, value.(int)) + case paddingLeftKey: + s.paddingLeft = max(0, value.(int)) + case paddingCharKey: + s.paddingChar = value.(rune) + case marginTopKey: + s.marginTop = max(0, value.(int)) + case marginRightKey: + s.marginRight = max(0, value.(int)) + case marginBottomKey: + s.marginBottom = max(0, value.(int)) + case marginLeftKey: + s.marginLeft = max(0, value.(int)) + case marginBackgroundKey: + s.marginBgColor = colorOrNil(value) + case marginCharKey: + s.marginChar = value.(rune) + case borderStyleKey: + s.borderStyle = value.(Border) + case borderTopForegroundKey: + s.borderTopFgColor = colorOrNil(value) + case borderRightForegroundKey: + s.borderRightFgColor = colorOrNil(value) + case borderBottomForegroundKey: + s.borderBottomFgColor = colorOrNil(value) + case borderLeftForegroundKey: + s.borderLeftFgColor = colorOrNil(value) + case borderForegroundBlendKey: + s.borderBlendFgColor = value.([]color.Color) + case borderForegroundBlendOffsetKey: + s.borderForegroundBlendOffset = value.(int) + case borderTopBackgroundKey: + s.borderTopBgColor = colorOrNil(value) + case borderRightBackgroundKey: + s.borderRightBgColor = colorOrNil(value) + case borderBottomBackgroundKey: + s.borderBottomBgColor = colorOrNil(value) + case borderLeftBackgroundKey: + s.borderLeftBgColor = colorOrNil(value) + case maxWidthKey: + s.maxWidth = max(0, value.(int)) + case maxHeightKey: + s.maxHeight = max(0, value.(int)) + case tabWidthKey: + // TabWidth is the only property that may have a negative value (and + // that negative value can be no less than -1). + s.tabWidth = value.(int) + case transformKey: + s.transform = value.(func(string) string) + case linkKey: + s.link = value.(string) + case linkParamsKey: + s.linkParams = value.(string) + default: + if v, ok := value.(bool); ok { //nolint:nestif + if v { + s.attrs |= int(key) + } else { + s.attrs &^= int(key) + } + } else if attrs, ok := value.(int); ok { + // bool attrs + if attrs&int(key) != 0 { + s.attrs |= int(key) + } else { + s.attrs &^= int(key) + } + } + } + + // Set the prop on + s.props = s.props.set(key) +} + +// setFrom sets the property from another style. +func (s *Style) setFrom(key propKey, i Style) { + switch key { + case foregroundKey: + s.set(foregroundKey, i.fgColor) + case backgroundKey: + s.set(backgroundKey, i.bgColor) + case underlineColorKey: + s.set(underlineColorKey, i.ulColor) + case underlineKey: + s.set(underlineKey, i.ul) + case widthKey: + s.set(widthKey, i.width) + case heightKey: + s.set(heightKey, i.height) + case alignHorizontalKey: + s.set(alignHorizontalKey, i.alignHorizontal) + case alignVerticalKey: + s.set(alignVerticalKey, i.alignVertical) + case paddingTopKey: + s.set(paddingTopKey, i.paddingTop) + case paddingRightKey: + s.set(paddingRightKey, i.paddingRight) + case paddingBottomKey: + s.set(paddingBottomKey, i.paddingBottom) + case paddingLeftKey: + s.set(paddingLeftKey, i.paddingLeft) + case paddingCharKey: + s.set(paddingCharKey, i.paddingChar) + case marginTopKey: + s.set(marginTopKey, i.marginTop) + case marginRightKey: + s.set(marginRightKey, i.marginRight) + case marginBottomKey: + s.set(marginBottomKey, i.marginBottom) + case marginLeftKey: + s.set(marginLeftKey, i.marginLeft) + case marginBackgroundKey: + s.set(marginBackgroundKey, i.marginBgColor) + case marginCharKey: + s.set(marginCharKey, i.marginChar) + case borderStyleKey: + s.set(borderStyleKey, i.borderStyle) + case borderTopForegroundKey: + s.set(borderTopForegroundKey, i.borderTopFgColor) + case borderRightForegroundKey: + s.set(borderRightForegroundKey, i.borderRightFgColor) + case borderBottomForegroundKey: + s.set(borderBottomForegroundKey, i.borderBottomFgColor) + case borderLeftForegroundKey: + s.set(borderLeftForegroundKey, i.borderLeftFgColor) + case borderForegroundBlendKey: + s.set(borderForegroundBlendKey, i.borderBlendFgColor) + case borderForegroundBlendOffsetKey: + s.set(borderForegroundBlendOffsetKey, i.borderForegroundBlendOffset) + case borderTopBackgroundKey: + s.set(borderTopBackgroundKey, i.borderTopBgColor) + case borderRightBackgroundKey: + s.set(borderRightBackgroundKey, i.borderRightBgColor) + case borderBottomBackgroundKey: + s.set(borderBottomBackgroundKey, i.borderBottomBgColor) + case borderLeftBackgroundKey: + s.set(borderLeftBackgroundKey, i.borderLeftBgColor) + case maxWidthKey: + s.set(maxWidthKey, i.maxWidth) + case maxHeightKey: + s.set(maxHeightKey, i.maxHeight) + case tabWidthKey: + s.set(tabWidthKey, i.tabWidth) + case transformKey: + s.set(transformKey, i.transform) + default: + // Set attributes for set bool properties + s.set(key, i.attrs) + } +} + +func colorOrNil(c any) color.Color { + if c, ok := c.(color.Color); ok { + return c + } + return nil +} + +// Bold sets a bold formatting rule. +func (s Style) Bold(v bool) Style { + s.set(boldKey, v) + return s +} + +// Italic sets an italic formatting rule. In some terminal emulators this will +// render with "reverse" coloring if not italic font variant is available. +func (s Style) Italic(v bool) Style { + s.set(italicKey, v) + return s +} + +// Underline sets an underline rule. By default, underlines will not be drawn on +// whitespace like margins and padding. To change this behavior set +// [Style.UnderlineSpaces]. +func (s Style) Underline(v bool) Style { + if v { + return s.UnderlineStyle(UnderlineSingle) + } + return s.UnderlineStyle(UnderlineNone) +} + +// UnderlineStyle sets the underline style. This can be used to set the underline +// to be a single, double, curly, dotted, or dashed line. +// +// Note that not all terminal emulators support underline styles. If a style is +// not supported, it will typically fall back to a single underline but this is +// not guaranteed. This depends on the terminal emulator being used. +func (s Style) UnderlineStyle(u Underline) Style { + s.set(underlineKey, u) + return s +} + +// UnderlineColor sets the color of the underline. By default, the underline +// will be the same color as the foreground. +// +// Note that not all terminal emulators support colored underlines. If color is +// not supported, it might produce unexpected results. This depends on the +// terminal emulator being used. +func (s Style) UnderlineColor(c color.Color) Style { + s.set(underlineColorKey, c) + return s +} + +// Strikethrough sets a strikethrough rule. By default, strikes will not be +// drawn on whitespace like margins and padding. To change this behavior set +// StrikethroughSpaces. +func (s Style) Strikethrough(v bool) Style { + s.set(strikethroughKey, v) + return s +} + +// Reverse sets a rule for inverting foreground and background colors. +func (s Style) Reverse(v bool) Style { + s.set(reverseKey, v) + return s +} + +// Blink sets a rule for blinking foreground text. +func (s Style) Blink(v bool) Style { + s.set(blinkKey, v) + return s +} + +// Faint sets a rule for rendering the foreground color in a dimmer shade. +func (s Style) Faint(v bool) Style { + s.set(faintKey, v) + return s +} + +// Foreground sets a foreground color. +// +// // Sets the foreground to blue +// s := lipgloss.NewStyle().Foreground(lipgloss.Color("#0000ff")) +// +// // Removes the foreground color +// s.Foreground(lipgloss.NoColor) +func (s Style) Foreground(c color.Color) Style { + s.set(foregroundKey, c) + return s +} + +// Background sets a background color. +func (s Style) Background(c color.Color) Style { + s.set(backgroundKey, c) + return s +} + +// Width sets the width of the block before applying margins. This means your +// styled content will exactly equal the size set here. Text will wrap based on +// Padding and Borders set on the style. +func (s Style) Width(i int) Style { + s.set(widthKey, i) + return s +} + +// Height sets the height of the block before applying margins. If the height of +// the text block is less than this value after applying padding (or not), the +// block will be set to this height. +func (s Style) Height(i int) Style { + s.set(heightKey, i) + return s +} + +// Align is a shorthand method for setting horizontal and vertical alignment. +// +// With one argument, the position value is applied to the horizontal alignment. +// +// With two arguments, the value is applied to the horizontal and vertical +// alignments, in that order. +func (s Style) Align(p ...Position) Style { + if len(p) > 0 { + s.set(alignHorizontalKey, p[0]) + } + if len(p) > 1 { + s.set(alignVerticalKey, p[1]) + } + return s +} + +// AlignHorizontal sets a horizontal text alignment rule. +func (s Style) AlignHorizontal(p Position) Style { + s.set(alignHorizontalKey, p) + return s +} + +// AlignVertical sets a vertical text alignment rule. +func (s Style) AlignVertical(p Position) Style { + s.set(alignVerticalKey, p) + return s +} + +// Padding is a shorthand method for setting padding on all sides at once. +// +// With one argument, the value is applied to all sides. +// +// With two arguments, the value is applied to the vertical and horizontal +// sides, in that order. +// +// With three arguments, the value is applied to the top side, the horizontal +// sides, and the bottom side, in that order. +// +// With four arguments, the value is applied clockwise starting from the top +// side, followed by the right side, then the bottom, and finally the left. +// +// With more than four arguments no padding will be added. +func (s Style) Padding(i ...int) Style { + top, right, bottom, left, ok := whichSidesInt(i...) + if !ok { + return s + } + + s.set(paddingTopKey, top) + s.set(paddingRightKey, right) + s.set(paddingBottomKey, bottom) + s.set(paddingLeftKey, left) + return s +} + +// PaddingLeft adds padding on the left. +func (s Style) PaddingLeft(i int) Style { + s.set(paddingLeftKey, i) + return s +} + +// PaddingRight adds padding on the right. +func (s Style) PaddingRight(i int) Style { + s.set(paddingRightKey, i) + return s +} + +// PaddingTop adds padding to the top of the block. +func (s Style) PaddingTop(i int) Style { + s.set(paddingTopKey, i) + return s +} + +// PaddingBottom adds padding to the bottom of the block. +func (s Style) PaddingBottom(i int) Style { + s.set(paddingBottomKey, i) + return s +} + +// PaddingChar sets the character used for padding. This is useful for +// rendering blocks with a specific character, such as a space or a dot. +// Example of using [NBSP] as padding to prevent line breaks: +// +// ```go +// s := lipgloss.NewStyle().PaddingChar(lipgloss.NBSP) +// ``` +func (s Style) PaddingChar(r rune) Style { + s.set(paddingCharKey, r) + return s +} + +// ColorWhitespace determines whether or not the background color should be +// applied to the padding. This is true by default as it's more than likely the +// desired and expected behavior, but it can be disabled for certain graphic +// effects. +// +// Deprecated: Just use margins and padding. +func (s Style) ColorWhitespace(v bool) Style { + s.set(colorWhitespaceKey, v) + return s +} + +// Margin is a shorthand method for setting margins on all sides at once. +// +// With one argument, the value is applied to all sides. +// +// With two arguments, the value is applied to the vertical and horizontal +// sides, in that order. +// +// With three arguments, the value is applied to the top side, the horizontal +// sides, and the bottom side, in that order. +// +// With four arguments, the value is applied clockwise starting from the top +// side, followed by the right side, then the bottom, and finally the left. +// +// With more than four arguments no margin will be added. +func (s Style) Margin(i ...int) Style { + top, right, bottom, left, ok := whichSidesInt(i...) + if !ok { + return s + } + + s.set(marginTopKey, top) + s.set(marginRightKey, right) + s.set(marginBottomKey, bottom) + s.set(marginLeftKey, left) + return s +} + +// MarginLeft sets the value of the left margin. +func (s Style) MarginLeft(i int) Style { + s.set(marginLeftKey, i) + return s +} + +// MarginRight sets the value of the right margin. +func (s Style) MarginRight(i int) Style { + s.set(marginRightKey, i) + return s +} + +// MarginTop sets the value of the top margin. +func (s Style) MarginTop(i int) Style { + s.set(marginTopKey, i) + return s +} + +// MarginBottom sets the value of the bottom margin. +func (s Style) MarginBottom(i int) Style { + s.set(marginBottomKey, i) + return s +} + +// MarginBackground sets the background color of the margin. Note that this is +// also set when inheriting from a style with a background color. In that case +// the background color on that style will set the margin color on this style. +func (s Style) MarginBackground(c color.Color) Style { + s.set(marginBackgroundKey, c) + return s +} + +// MarginChar sets the character used for the margin. This is useful for +// rendering blocks with a specific character, such as a space or a dot. +func (s Style) MarginChar(r rune) Style { + s.set(marginCharKey, r) + return s +} + +// Border is shorthand for setting the border style and which sides should +// have a border at once. The variadic argument sides works as follows: +// +// With one value, the value is applied to all sides. +// +// With two values, the values are applied to the vertical and horizontal +// sides, in that order. +// +// With three values, the values are applied to the top side, the horizontal +// sides, and the bottom side, in that order. +// +// With four values, the values are applied clockwise starting from the top +// side, followed by the right side, then the bottom, and finally the left. +// +// With more than four arguments the border will be applied to all sides. +// +// Examples: +// +// // Applies borders to the top and bottom only +// lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false) +// +// // Applies rounded borders to the right and bottom only +// lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), false, true, true, false) +func (s Style) Border(b Border, sides ...bool) Style { + s.set(borderStyleKey, b) + + top, right, bottom, left, ok := whichSidesBool(sides...) + if !ok { + top = true + right = true + bottom = true + left = true + } + + s.set(borderTopKey, top) + s.set(borderRightKey, right) + s.set(borderBottomKey, bottom) + s.set(borderLeftKey, left) + + return s +} + +// BorderStyle defines the Border on a style. A Border contains a series of +// definitions for the sides and corners of a border. +// +// Note that if border visibility has not been set for any sides when setting +// the border style, the border will be enabled for all sides during rendering. +// +// You can define border characters as you'd like, though several default +// styles are included: NormalBorder(), RoundedBorder(), BlockBorder(), +// OuterHalfBlockBorder(), InnerHalfBlockBorder(), ThickBorder(), +// and DoubleBorder(). +// +// Example: +// +// lipgloss.NewStyle().BorderStyle(lipgloss.ThickBorder()) +func (s Style) BorderStyle(b Border) Style { + s.set(borderStyleKey, b) + return s +} + +// BorderTop determines whether or not to draw a top border. +func (s Style) BorderTop(v bool) Style { + s.set(borderTopKey, v) + return s +} + +// BorderRight determines whether or not to draw a right border. +func (s Style) BorderRight(v bool) Style { + s.set(borderRightKey, v) + return s +} + +// BorderBottom determines whether or not to draw a bottom border. +func (s Style) BorderBottom(v bool) Style { + s.set(borderBottomKey, v) + return s +} + +// BorderLeft determines whether or not to draw a left border. +func (s Style) BorderLeft(v bool) Style { + s.set(borderLeftKey, v) + return s +} + +// BorderForeground is a shorthand function for setting all of the +// foreground colors of the borders at once. The arguments work as follows: +// +// With one argument, the argument is applied to all sides. +// +// With two arguments, the arguments are applied to the vertical and horizontal +// sides, in that order. +// +// With three arguments, the arguments are applied to the top side, the +// horizontal sides, and the bottom side, in that order. +// +// With four arguments, the arguments are applied clockwise starting from the +// top side, followed by the right side, then the bottom, and finally the left. +// +// With more than four arguments nothing will be set. +func (s Style) BorderForeground(c ...color.Color) Style { + if len(c) == 0 { + return s + } + + top, right, bottom, left, ok := whichSidesColor(c...) + if !ok { + return s + } + + s.set(borderTopForegroundKey, top) + s.set(borderRightForegroundKey, right) + s.set(borderBottomForegroundKey, bottom) + s.set(borderLeftForegroundKey, left) + + return s +} + +// BorderTopForeground set the foreground color for the top of the border. +func (s Style) BorderTopForeground(c color.Color) Style { + s.set(borderTopForegroundKey, c) + return s +} + +// BorderRightForeground sets the foreground color for the right side of the +// border. +func (s Style) BorderRightForeground(c color.Color) Style { + s.set(borderRightForegroundKey, c) + return s +} + +// BorderBottomForeground sets the foreground color for the bottom of the +// border. +func (s Style) BorderBottomForeground(c color.Color) Style { + s.set(borderBottomForegroundKey, c) + return s +} + +// BorderLeftForeground sets the foreground color for the left side of the +// border. +func (s Style) BorderLeftForeground(c color.Color) Style { + s.set(borderLeftForegroundKey, c) + return s +} + +// BorderForegroundBlend sets the foreground colors for the border blend. At least +// 2 colors are required to use blending, otherwise this will no-op with 0 colors, +// and pass to BorderForeground with 1 color. This will override all other border +// foreground colors when used. +// +// When providing colors, in most cases (e.g. when all border sides are enabled), +// you will want to provide a wrapping-set of colors, so the start and end color +// are either the same, or very similar. For example: +// +// lipgloss.NewStyle().BorderForegroundBlend( +// lipgloss.Color("#00FA68"), +// lipgloss.Color("#9900FF"), +// lipgloss.Color("#ED5353"), +// lipgloss.Color("#9900FF"), +// lipgloss.Color("#00FA68"), +// ) +func (s Style) BorderForegroundBlend(c ...color.Color) Style { + if len(c) == 0 { + return s + } + + // Insufficient colors to use blending, pass to BorderForeground. + if len(c) == 1 { + return s.BorderForeground(c...) + } + + s.set(borderForegroundBlendKey, c) + return s +} + +// BorderForegroundBlendOffset sets the border blend offset cells, starting from +// the top left corner. Value can be positive or negative, and does not need to +// equal the dimensions of the border region. Direction (when positive) is as +// follows ("o" is starting point): +// +// o --------> +// ┌──────────┐ +// ^ │ │ | +// | │ │ | +// | │ │ | +// | │ │ v +// └──────────┘ +// <--------- +func (s Style) BorderForegroundBlendOffset(v int) Style { + s.set(borderForegroundBlendOffsetKey, v) + return s +} + +// BorderBackground is a shorthand function for setting all of the +// background colors of the borders at once. The arguments work as follows: +// +// With one argument, the argument is applied to all sides. +// +// With two arguments, the arguments are applied to the vertical and horizontal +// sides, in that order. +// +// With three arguments, the arguments are applied to the top side, the +// horizontal sides, and the bottom side, in that order. +// +// With four arguments, the arguments are applied clockwise starting from the +// top side, followed by the right side, then the bottom, and finally the left. +// +// With more than four arguments nothing will be set. +func (s Style) BorderBackground(c ...color.Color) Style { + if len(c) == 0 { + return s + } + + top, right, bottom, left, ok := whichSidesColor(c...) + if !ok { + return s + } + + s.set(borderTopBackgroundKey, top) + s.set(borderRightBackgroundKey, right) + s.set(borderBottomBackgroundKey, bottom) + s.set(borderLeftBackgroundKey, left) + + return s +} + +// BorderTopBackground sets the background color of the top of the border. +func (s Style) BorderTopBackground(c color.Color) Style { + s.set(borderTopBackgroundKey, c) + return s +} + +// BorderRightBackground sets the background color of right side the border. +func (s Style) BorderRightBackground(c color.Color) Style { + s.set(borderRightBackgroundKey, c) + return s +} + +// BorderBottomBackground sets the background color of the bottom of the +// border. +func (s Style) BorderBottomBackground(c color.Color) Style { + s.set(borderBottomBackgroundKey, c) + return s +} + +// BorderLeftBackground set the background color of the left side of the +// border. +func (s Style) BorderLeftBackground(c color.Color) Style { + s.set(borderLeftBackgroundKey, c) + return s +} + +// Inline makes rendering output one line and disables the rendering of +// margins, padding and borders. This is useful when you need a style to apply +// only to font rendering and don't want it to change any physical dimensions. +// It works well with Style.MaxWidth. +// +// Because this in intended to be used at the time of render, this method will +// not mutate the style and instead return a copy. +// +// Example: +// +// var userInput string = "..." +// var userStyle = text.Style{ /* ... */ } +// fmt.Println(userStyle.Inline(true).Render(userInput)) +func (s Style) Inline(v bool) Style { + o := s // copy + o.set(inlineKey, v) + return o +} + +// MaxWidth applies a max width to a given style. This is useful in enforcing +// a certain width at render time, particularly with arbitrary strings and +// styles. +// +// Because this in intended to be used at the time of render, this method will +// not mutate the style and instead return a copy. +// +// Example: +// +// var userInput string = "..." +// var userStyle = text.Style{ /* ... */ } +// fmt.Println(userStyle.MaxWidth(16).Render(userInput)) +func (s Style) MaxWidth(n int) Style { + o := s // copy + o.set(maxWidthKey, n) + return o +} + +// MaxHeight applies a max height to a given style. This is useful in enforcing +// a certain height at render time, particularly with arbitrary strings and +// styles. +// +// Because this in intended to be used at the time of render, this method will +// not mutate the style and instead returns a copy. +func (s Style) MaxHeight(n int) Style { + o := s // copy + o.set(maxHeightKey, n) + return o +} + +// NoTabConversion can be passed to [Style.TabWidth] to disable the replacement +// of tabs with spaces at render time. +const NoTabConversion = -1 + +// TabWidth sets the number of spaces that a tab (/t) should be rendered as. +// When set to 0, tabs will be removed. To disable the replacement of tabs with +// spaces entirely, set this to [NoTabConversion]. +// +// By default, tabs will be replaced with 4 spaces. +func (s Style) TabWidth(n int) Style { + if n <= -1 { + n = -1 + } + s.set(tabWidthKey, n) + return s +} + +// UnderlineSpaces determines whether to underline spaces between words. By +// default, this is true. Spaces can also be underlined without underlining the +// text itself. +func (s Style) UnderlineSpaces(v bool) Style { + s.set(underlineSpacesKey, v) + return s +} + +// StrikethroughSpaces determines whether to apply strikethroughs to spaces +// between words. By default, this is true. Spaces can also be struck without +// underlining the text itself. +func (s Style) StrikethroughSpaces(v bool) Style { + s.set(strikethroughSpacesKey, v) + return s +} + +// Transform applies a given function to a string at render time, allowing for +// the string being rendered to be manipuated. +// +// Example: +// +// s := NewStyle().Transform(strings.ToUpper) +// fmt.Println(s.Render("raow!") // "RAOW!" +func (s Style) Transform(fn func(string) string) Style { + s.set(transformKey, fn) + return s +} + +// Hyperlink sets a hyperlink on a style. This is useful for rendering text that +// can be clicked on in a terminal emulator that supports hyperlinks. +// +// Example: +// +// s := lipgloss.NewStyle().Hyperlink("https://charm.sh") +// s := lipgloss.NewStyle().Hyperlink("https://charm.sh", "id=1") +func (s Style) Hyperlink(link string, params ...string) Style { + s.set(linkKey, link) + if len(params) > 0 { + s.set(linkParamsKey, strings.Join(params, ":")) + } + return s +} + +// whichSidesInt is a helper method for setting values on sides of a block based +// on the number of arguments. It follows the CSS shorthand rules for blocks +// like margin, padding. and borders. Here are how the rules work: +// +// 0 args: do nothing +// 1 arg: all sides +// 2 args: top -> bottom +// 3 args: top -> horizontal -> bottom +// 4 args: top -> right -> bottom -> left +// 5+ args: do nothing. +func whichSidesInt(i ...int) (top, right, bottom, left int, ok bool) { + switch len(i) { + case 1: + top = i[0] + bottom = i[0] + left = i[0] + right = i[0] + ok = true + case 2: //nolint:mnd + top = i[0] + bottom = i[0] + left = i[1] + right = i[1] + ok = true + case 3: //nolint:mnd + top = i[0] + left = i[1] + right = i[1] + bottom = i[2] + ok = true + case 4: //nolint:mnd + top = i[0] + right = i[1] + bottom = i[2] + left = i[3] + ok = true + } + return top, right, bottom, left, ok +} + +// whichSidesBool is like whichSidesInt, except it operates on a series of +// boolean values. See the comment on whichSidesInt for details on how this +// works. +func whichSidesBool(i ...bool) (top, right, bottom, left bool, ok bool) { + switch len(i) { + case 1: + top = i[0] + bottom = i[0] + left = i[0] + right = i[0] + ok = true + case 2: //nolint:mnd + top = i[0] + bottom = i[0] + left = i[1] + right = i[1] + ok = true + case 3: //nolint:mnd + top = i[0] + left = i[1] + right = i[1] + bottom = i[2] + ok = true + case 4: //nolint:mnd + top = i[0] + right = i[1] + bottom = i[2] + left = i[3] + ok = true + } + return top, right, bottom, left, ok +} + +// whichSidesColor is like whichSides, except it operates on a series of +// boolean values. See the comment on whichSidesInt for details on how this +// works. +func whichSidesColor(i ...color.Color) (top, right, bottom, left color.Color, ok bool) { + switch len(i) { + case 1: + top = i[0] + bottom = i[0] + left = i[0] + right = i[0] + ok = true + case 2: //nolint:mnd + top = i[0] + bottom = i[0] + left = i[1] + right = i[1] + ok = true + case 3: //nolint:mnd + top = i[0] + left = i[1] + right = i[1] + bottom = i[2] + ok = true + case 4: //nolint:mnd + top = i[0] + right = i[1] + bottom = i[2] + left = i[3] + ok = true + } + return top, right, bottom, left, ok +} diff --git a/vendor/charm.land/lipgloss/v2/size.go b/vendor/charm.land/lipgloss/v2/size.go new file mode 100644 index 000000000..e0384d035 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/size.go @@ -0,0 +1,40 @@ +package lipgloss + +import ( + "strings" + + "github.com/charmbracelet/x/ansi" +) + +// Width returns the cell width of characters in the string. ANSI sequences are +// ignored and characters wider than one cell (such as Chinese characters and +// emojis) are appropriately measured. +// +// You should use this instead of len(string) or len([]rune(string) as neither +// will give you accurate results. +func Width(str string) (width int) { + for l := range strings.SplitSeq(str, "\n") { + w := ansi.StringWidth(l) + if w > width { + width = w + } + } + + return width +} + +// Height returns height of a string in cells. This is done simply by +// counting \n characters. If your output has \r\n, that sequence will be +// replaced with a \n in [Style.Render]. +func Height(str string) int { + return strings.Count(str, "\n") + 1 +} + +// Size returns the width and height of the string in cells. ANSI sequences are +// ignored and characters wider than one cell (such as Chinese characters and +// emojis) are appropriately measured. +func Size(str string) (width, height int) { + width = Width(str) + height = Height(str) + return width, height +} diff --git a/vendor/charm.land/lipgloss/v2/style.go b/vendor/charm.land/lipgloss/v2/style.go new file mode 100644 index 000000000..3cd659526 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/style.go @@ -0,0 +1,637 @@ +package lipgloss + +import ( + "image/color" + "strings" + "unicode" + + "github.com/charmbracelet/x/ansi" +) + +const ( + // NBSP is the non-breaking space rune. + NBSP = '\u00A0' + tabWidthDefault = 4 +) + +// Property for a key. +type propKey int64 + +// Available properties. +const ( + // Boolean props come first. + boldKey propKey = 1 << iota + italicKey + strikethroughKey + reverseKey + blinkKey + faintKey + underlineSpacesKey + strikethroughSpacesKey + colorWhitespaceKey + + // Non-boolean props. + underlineKey + foregroundKey + backgroundKey + underlineColorKey + widthKey + heightKey + alignHorizontalKey + alignVerticalKey + + // Padding. + paddingTopKey + paddingRightKey + paddingBottomKey + paddingLeftKey + paddingCharKey + + // Margins. + marginTopKey + marginRightKey + marginBottomKey + marginLeftKey + marginBackgroundKey + marginCharKey + + // Border runes. + borderStyleKey + + // Border edges. + borderTopKey + borderRightKey + borderBottomKey + borderLeftKey + + // Border foreground colors. + borderTopForegroundKey + borderRightForegroundKey + borderBottomForegroundKey + borderLeftForegroundKey + borderForegroundBlendKey + borderForegroundBlendOffsetKey + + // Border background colors. + borderTopBackgroundKey + borderRightBackgroundKey + borderBottomBackgroundKey + borderLeftBackgroundKey + + inlineKey + maxWidthKey + maxHeightKey + tabWidthKey + + transformKey + + // Hyperlink. + linkKey + linkParamsKey +) + +// props is a set of properties. +type props int64 + +// set sets a property. +func (p props) set(k propKey) props { + return p | props(k) +} + +// unset unsets a property. +func (p props) unset(k propKey) props { + return p &^ props(k) +} + +// has checks if a property is set. +func (p props) has(k propKey) bool { + return p&props(k) != 0 +} + +// Underline is the style of the underline. +// +// Caveats: +// - Not all terminals support all underline styles. +// - Some terminals may render unsupported styles as standard underlines. +// - Terminal themes may affect the visibility of different underline styles. +type Underline = ansi.Underline + +const ( + // UnderlineNone is no underline. + UnderlineNone = ansi.UnderlineNone + // UnderlineSingle is a single underline. This is the default when underline is enabled. + UnderlineSingle = ansi.UnderlineSingle + // UnderlineDouble is a double underline. + UnderlineDouble = ansi.UnderlineDouble + // UnderlineCurly is a curly underline. + UnderlineCurly = ansi.UnderlineCurly + // UnderlineDotted is a dotted underline. + UnderlineDotted = ansi.UnderlineDotted + // UnderlineDashed is a dashed underline. + UnderlineDashed = ansi.UnderlineDashed +) + +// NewStyle returns a new, empty Style. While it's syntactic sugar for the +// [Style]{} primitive, it's recommended to use this function for creating styles +// in case the underlying implementation changes. +func NewStyle() Style { + return Style{} +} + +// Style contains a set of rules that comprise a style as a whole. +type Style struct { + props props + value string + + // hyperlink + link, linkParams string + + // we store bool props values here + attrs int + + // props that have values + fgColor color.Color + bgColor color.Color + ulColor color.Color + + ul Underline + + width int + height int + + alignHorizontal Position + alignVertical Position + + paddingTop int + paddingRight int + paddingBottom int + paddingLeft int + paddingChar rune + + marginTop int + marginRight int + marginBottom int + marginLeft int + marginBgColor color.Color + marginChar rune + + borderStyle Border + borderTopFgColor color.Color + borderRightFgColor color.Color + borderBottomFgColor color.Color + borderLeftFgColor color.Color + borderBlendFgColor []color.Color + borderForegroundBlendOffset int + borderTopBgColor color.Color + borderRightBgColor color.Color + borderBottomBgColor color.Color + borderLeftBgColor color.Color + + maxWidth int + maxHeight int + tabWidth int + + transform func(string) string +} + +// joinString joins a list of strings into a single string separated with a +// space. +func joinString(strs ...string) string { + return strings.Join(strs, " ") +} + +// SetString sets the underlying string value for this style. To render once +// the underlying string is set, use the [Style.String]. This method is +// a convenience for cases when having a stringer implementation is handy, such +// as when using fmt.Sprintf. You can also simply define a style and render out +// strings directly with [Style.Render]. +func (s Style) SetString(strs ...string) Style { + s.value = joinString(strs...) + return s +} + +// Value returns the raw, unformatted, underlying string value for this style. +func (s Style) Value() string { + return s.value +} + +// String implements stringer for a Style, returning the rendered result based +// on the rules in this style. An underlying string value must be set with +// Style.SetString prior to using this method. +func (s Style) String() string { + return s.Render() +} + +// Copy returns a copy of this style, including any underlying string values. +// +// Deprecated: to copy just use assignment (i.e. a := b). All methods also +// return a new style. +func (s Style) Copy() Style { + return s +} + +// Inherit overlays the style in the argument onto this style by copying each explicitly +// set value from the argument style onto this style if it is not already explicitly set. +// Existing set values are kept intact and not overwritten. +// +// Margins, padding, and underlying string values are not inherited. +func (s Style) Inherit(i Style) Style { + for k := boldKey; k <= transformKey; k <<= 1 { + if !i.isSet(k) { + continue + } + + switch k { //nolint:exhaustive + case marginTopKey, marginRightKey, marginBottomKey, marginLeftKey: + // Margins are not inherited + continue + case paddingTopKey, paddingRightKey, paddingBottomKey, paddingLeftKey: + // Padding is not inherited + continue + case backgroundKey: + // The margins also inherit the background color + if !s.isSet(marginBackgroundKey) && !i.isSet(marginBackgroundKey) { + s.set(marginBackgroundKey, i.bgColor) + } + } + + if s.isSet(k) { + continue + } + + s.setFrom(k, i) + } + return s +} + +// Render applies the defined style formatting to a given string. +func (s Style) Render(strs ...string) string { + if s.value != "" { + strs = append([]string{s.value}, strs...) + } + + var ( + str = joinString(strs...) + + te ansi.Style + teSpace ansi.Style + teWhitespace ansi.Style + + bold = s.getAsBool(boldKey, false) + italic = s.getAsBool(italicKey, false) + strikethrough = s.getAsBool(strikethroughKey, false) + reverse = s.getAsBool(reverseKey, false) + blink = s.getAsBool(blinkKey, false) + faint = s.getAsBool(faintKey, false) + + fg = s.getAsColor(foregroundKey) + bg = s.getAsColor(backgroundKey) + ul = s.getAsColor(underlineColorKey) + + underline = s.ul != UnderlineNone + width = s.getAsInt(widthKey) + height = s.getAsInt(heightKey) + horizontalAlign = s.getAsPosition(alignHorizontalKey) + verticalAlign = s.getAsPosition(alignVerticalKey) + + topPadding = s.getAsInt(paddingTopKey) + rightPadding = s.getAsInt(paddingRightKey) + bottomPadding = s.getAsInt(paddingBottomKey) + leftPadding = s.getAsInt(paddingLeftKey) + + horizontalBorderSize = s.GetHorizontalBorderSize() + verticalBorderSize = s.GetVerticalBorderSize() + + colorWhitespace = s.getAsBool(colorWhitespaceKey, true) + inline = s.getAsBool(inlineKey, false) + maxWidth = s.getAsInt(maxWidthKey) + maxHeight = s.getAsInt(maxHeightKey) + + underlineSpaces = s.getAsBool(underlineSpacesKey, false) || (underline && s.getAsBool(underlineSpacesKey, true)) + strikethroughSpaces = s.getAsBool(strikethroughSpacesKey, false) || (strikethrough && s.getAsBool(strikethroughSpacesKey, true)) + + // Do we need to style whitespace (padding and space outside + // paragraphs) separately? + styleWhitespace = reverse + + // Do we need to style spaces separately? + useSpaceStyler = (underline && !underlineSpaces) || (strikethrough && !strikethroughSpaces) || underlineSpaces || strikethroughSpaces + + transform = s.getAsTransform(transformKey) + + link, linkParams = s.GetHyperlink() + ) + + if transform != nil { + str = transform(str) + } + + if s.props == 0 { + return s.maybeConvertTabs(str) + } + + if bold { + te = te.Bold() + } + if italic { + te = te.Italic(true) + } + if underline { + te = te.Underline(true) + } + if reverse { + teWhitespace = teWhitespace.Reverse(true) + te = te.Reverse(true) + } + if blink { + te = te.Blink(true) + } + if faint { + te = te.Faint() + } + + if fg != noColor { + te = te.ForegroundColor(fg) + if styleWhitespace { + teWhitespace = teWhitespace.ForegroundColor(fg) + } + if useSpaceStyler { + teSpace = teSpace.ForegroundColor(fg) + } + } + + if bg != noColor { + te = te.BackgroundColor(bg) + if colorWhitespace { + teWhitespace = teWhitespace.BackgroundColor(bg) + } + if useSpaceStyler { + teSpace = teSpace.BackgroundColor(bg) + } + } + + if ul != noColor { + te = te.UnderlineColor(ul) + if colorWhitespace { + teWhitespace = teWhitespace.UnderlineColor(ul) + } + if useSpaceStyler { + teSpace = teSpace.UnderlineColor(ul) + } + } + + if underline { + te = te.UnderlineStyle(s.ul) + } + if strikethrough { + te = te.Strikethrough(true) + } + + if underlineSpaces { + teSpace = teSpace.Underline(true) + } + if strikethroughSpaces { + teSpace = teSpace.Strikethrough(true) + } + + // Potentially convert tabs to spaces + str = s.maybeConvertTabs(str) + // carriage returns can cause strange behaviour when rendering. + str = strings.ReplaceAll(str, "\r\n", "\n") + + // Strip newlines in single line mode + if inline { + str = strings.ReplaceAll(str, "\n", "") + } + + // Include borders in block size. + width -= horizontalBorderSize + height -= verticalBorderSize + + // Word wrap + if !inline && width > 0 { + wrapAt := width - leftPadding - rightPadding + str = Wrap(str, wrapAt, "") + } + + // Render core text + { + var b strings.Builder + + isFirst := true + for line := range strings.SplitSeq(str, "\n") { + if isFirst { + isFirst = false + } else { + b.WriteRune('\n') + } + if useSpaceStyler { + // Look for spaces and apply a different styler + for _, r := range line { + if unicode.IsSpace(r) { + b.WriteString(teSpace.Styled(string(r))) + continue + } + b.WriteString(te.Styled(string(r))) + } + } else { + b.WriteString(te.Styled(line)) + } + } + + str = b.String() + + if len(link) > 0 { + str = ansi.SetHyperlink(link, linkParams) + str + ansi.ResetHyperlink() + } + } + + // Padding + if !inline { //nolint:nestif + padChar := s.paddingChar + if padChar == 0 { + padChar = ' ' + } + if leftPadding > 0 { + var st *ansi.Style + if colorWhitespace || styleWhitespace { + st = &teWhitespace + } + str = padLeft(str, leftPadding, st, padChar) + } + + if rightPadding > 0 { + var st *ansi.Style + if colorWhitespace || styleWhitespace { + st = &teWhitespace + } + str = padRight(str, rightPadding, st, padChar) + } + + if topPadding > 0 { + str = strings.Repeat("\n", topPadding) + str + } + + if bottomPadding > 0 { + str += strings.Repeat("\n", bottomPadding) + } + } + + // Height + if height > 0 { + str = alignTextVertical(str, verticalAlign, height, nil) + } + + // Set alignment. This will also pad short lines with spaces so that all + // lines are the same length, so we run it under a few different conditions + // beyond alignment. + { + numLines := strings.Count(str, "\n") + + if numLines != 0 || width != 0 { + var st *ansi.Style + if colorWhitespace || styleWhitespace { + st = &teWhitespace + } + str = alignTextHorizontal(str, horizontalAlign, width, st) + } + } + + if !inline { + str = s.applyBorder(str) + str = s.applyMargins(str, inline) + } + + // Truncate according to MaxWidth + if maxWidth > 0 { + lines := strings.Split(str, "\n") + + for i := range lines { + lines[i] = ansi.Truncate(lines[i], maxWidth, "") + } + + str = strings.Join(lines, "\n") + } + + // Truncate according to MaxHeight + if maxHeight > 0 { + lines := strings.Split(str, "\n") + height := min(maxHeight, len(lines)) + if len(lines) > 0 { + str = strings.Join(lines[:height], "\n") + } + } + + return str +} + +func (s Style) maybeConvertTabs(str string) string { + tw := tabWidthDefault + if s.isSet(tabWidthKey) { + tw = s.getAsInt(tabWidthKey) + } + switch tw { + case -1: + return str + case 0: + return strings.ReplaceAll(str, "\t", "") + default: + return strings.ReplaceAll(str, "\t", strings.Repeat(" ", tw)) + } +} + +func (s Style) applyMargins(str string, inline bool) string { + var ( + topMargin = s.getAsInt(marginTopKey) + rightMargin = s.getAsInt(marginRightKey) + bottomMargin = s.getAsInt(marginBottomKey) + leftMargin = s.getAsInt(marginLeftKey) + + style ansi.Style + ) + + bgc := s.getAsColor(marginBackgroundKey) + if bgc != noColor { + style = style.BackgroundColor(bgc) + } + + // Add left and right margin + marginChar := s.marginChar + if marginChar == 0 { + marginChar = ' ' + } + str = padLeft(str, leftMargin, &style, marginChar) + str = padRight(str, rightMargin, &style, marginChar) + + // Top/bottom margin + if !inline { + _, width := getLines(str) + spaces := strings.Repeat(" ", width) + + if topMargin > 0 { + str = style.Styled(strings.Repeat(spaces+"\n", topMargin)) + str + } + if bottomMargin > 0 { + str += style.Styled(strings.Repeat("\n"+spaces, bottomMargin)) + } + } + + return str +} + +// Apply left padding. +func padLeft(str string, n int, style *ansi.Style, r rune) string { + return pad(str, -n, style, r) +} + +// Apply right padding. +func padRight(str string, n int, style *ansi.Style, r rune) string { + return pad(str, n, style, r) +} + +// pad adds padding to either the left or right side of a string. +// Positive values add to the right side while negative values +// add to the left side. +// r is the rune to use for padding. We use " " for margins and +// "\u00A0" for padding so that the padding is preserved when the +// string is copied and pasted. +func pad(str string, n int, style *ansi.Style, r rune) string { + if n == 0 { + return str + } + + sp := strings.Repeat(string(r), abs(n)) + if style != nil { + sp = style.Styled(sp) + } + + b := strings.Builder{} + isFirst := true + for line := range strings.SplitSeq(str, "\n") { + if isFirst { + isFirst = false + } else { + b.WriteRune('\n') + } + switch { + // pad right + case n > 0: + b.WriteString(line) + b.WriteString(sp) + // pad left + default: + b.WriteString(sp) + b.WriteString(line) + } + } + + return b.String() +} + +func abs(a int) int { + if a < 0 { + return -a + } + + return a +} diff --git a/vendor/charm.land/lipgloss/v2/terminal.go b/vendor/charm.land/lipgloss/v2/terminal.go new file mode 100644 index 000000000..a336abc77 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/terminal.go @@ -0,0 +1,124 @@ +package lipgloss + +import ( + "fmt" + "image/color" + "io" + "strings" + "time" + + uv "github.com/charmbracelet/ultraviolet" + "github.com/charmbracelet/x/ansi" +) + +// queryBackgroundColor queries the terminal for the background color. +// If the terminal does not support querying the background color, nil is +// returned. +// +// Note: you will need to set the input to raw mode before calling this +// function. +// +// state, _ := term.MakeRaw(in.Fd()) +// defer term.Restore(in.Fd(), state) +// +// copied from x/term@v0.1.3. +func queryBackgroundColor(in io.Reader, out io.Writer) (c color.Color, err error) { + err = queryTerminal(in, out, defaultQueryTimeout, + func(seq string, pa *ansi.Parser) bool { + switch { + case ansi.HasOscPrefix(seq): + switch pa.Command() { + case 11: // OSC 11 + parts := strings.Split(string(pa.Data()), ";") + if len(parts) != 2 { + break // invalid, but we still need to parse the next sequence + } + c = ansi.XParseColor(parts[1]) + } + case ansi.HasCsiPrefix(seq): + switch pa.Command() { + case ansi.Command('?', 0, 'c'): // DA1 + return false + } + } + return true + }, ansi.RequestBackgroundColor+ansi.RequestPrimaryDeviceAttributes) + return +} + +const defaultQueryTimeout = time.Second * 2 + +// queryTerminalFilter is a function that filters input events using a type +// switch. If false is returned, the QueryTerminal function will stop reading +// input. +type queryTerminalFilter func(seq string, pa *ansi.Parser) bool + +// queryTerminal queries the terminal for support of various features and +// returns a list of response events. +// Most of the time, you will need to set stdin to raw mode before calling this +// function. +// Note: This function will block until the terminal responds or the timeout +// is reached. +// copied from x/term@v0.1.3. +func queryTerminal( + in io.Reader, + out io.Writer, + timeout time.Duration, + filter queryTerminalFilter, + query string, +) error { + // We use [uv.NewCancelReader] because it uses a different Windows + // implementation than the on in the [cancelreader] library, which uses + // the Cancel IO API to cancel reads instead of using Overlapped IO. + rd, err := uv.NewCancelReader(in) + if err != nil { + return fmt.Errorf("could not create cancel reader: %w", err) + } + + defer rd.Close() //nolint: errcheck + + done := make(chan struct{}, 1) + defer close(done) + go func() { + select { + case <-done: + case <-time.After(timeout): + rd.Cancel() + } + }() + + if _, err := io.WriteString(out, query); err != nil { + return fmt.Errorf("could not write query: %w", err) + } + + pa := ansi.GetParser() + defer ansi.PutParser(pa) + + var acc []byte // Accumulate partial responses before filtering + var buf [256]byte // 256 bytes should be enough for most responses + var state byte + for { + n, err := rd.Read(buf[:]) + if err != nil { + return fmt.Errorf("could not read from input: %w", err) + } + + p := buf[:] + for n > 0 { + seq, _, read, newState := ansi.DecodeSequence(p[:n], state, pa) + acc = append(acc, seq...) + + if newState == ansi.NormalState { + if !filter(string(acc), pa) { + return nil + } + + acc = acc[:0] + } + + state = newState + n -= read + p = p[read:] + } + } +} diff --git a/vendor/charm.land/lipgloss/v2/unset.go b/vendor/charm.land/lipgloss/v2/unset.go new file mode 100644 index 000000000..b81ee882a --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/unset.go @@ -0,0 +1,359 @@ +package lipgloss + +// unset unsets a property from a style. +func (s *Style) unset(key propKey) { + s.props = s.props.unset(key) +} + +// UnsetBold removes the bold style rule, if set. +func (s Style) UnsetBold() Style { + s.unset(boldKey) + return s +} + +// UnsetItalic removes the italic style rule, if set. +func (s Style) UnsetItalic() Style { + s.unset(italicKey) + return s +} + +// UnsetUnderline removes the underline style rule, if set. +func (s Style) UnsetUnderline() Style { + return s.Underline(false) +} + +// UnsetStrikethrough removes the strikethrough style rule, if set. +func (s Style) UnsetStrikethrough() Style { + s.unset(strikethroughKey) + return s +} + +// UnsetReverse removes the reverse style rule, if set. +func (s Style) UnsetReverse() Style { + s.unset(reverseKey) + return s +} + +// UnsetBlink removes the blink style rule, if set. +func (s Style) UnsetBlink() Style { + s.unset(blinkKey) + return s +} + +// UnsetFaint removes the faint style rule, if set. +func (s Style) UnsetFaint() Style { + s.unset(faintKey) + return s +} + +// UnsetForeground removes the foreground style rule, if set. +func (s Style) UnsetForeground() Style { + s.unset(foregroundKey) + return s +} + +// UnsetBackground removes the background style rule, if set. +func (s Style) UnsetBackground() Style { + s.unset(backgroundKey) + return s +} + +// UnsetWidth removes the width style rule, if set. +func (s Style) UnsetWidth() Style { + s.unset(widthKey) + return s +} + +// UnsetHeight removes the height style rule, if set. +func (s Style) UnsetHeight() Style { + s.unset(heightKey) + return s +} + +// UnsetAlign removes the horizontal and vertical text alignment style rule, if set. +func (s Style) UnsetAlign() Style { + s.unset(alignHorizontalKey) + s.unset(alignVerticalKey) + return s +} + +// UnsetAlignHorizontal removes the horizontal text alignment style rule, if set. +func (s Style) UnsetAlignHorizontal() Style { + s.unset(alignHorizontalKey) + return s +} + +// UnsetAlignVertical removes the vertical text alignment style rule, if set. +func (s Style) UnsetAlignVertical() Style { + s.unset(alignVerticalKey) + return s +} + +// UnsetPadding removes all padding style rules. +func (s Style) UnsetPadding() Style { + s.unset(paddingLeftKey) + s.unset(paddingRightKey) + s.unset(paddingTopKey) + s.unset(paddingBottomKey) + s.unset(paddingCharKey) + return s +} + +// UnsetPaddingChar removes the padding character style rule, if set. +func (s Style) UnsetPaddingChar() Style { + s.unset(paddingCharKey) + return s +} + +// UnsetPaddingLeft removes the left padding style rule, if set. +func (s Style) UnsetPaddingLeft() Style { + s.unset(paddingLeftKey) + return s +} + +// UnsetPaddingRight removes the right padding style rule, if set. +func (s Style) UnsetPaddingRight() Style { + s.unset(paddingRightKey) + return s +} + +// UnsetPaddingTop removes the top padding style rule, if set. +func (s Style) UnsetPaddingTop() Style { + s.unset(paddingTopKey) + return s +} + +// UnsetPaddingBottom removes the bottom padding style rule, if set. +func (s Style) UnsetPaddingBottom() Style { + s.unset(paddingBottomKey) + return s +} + +// UnsetColorWhitespace removes the rule for coloring padding, if set. +func (s Style) UnsetColorWhitespace() Style { + s.unset(colorWhitespaceKey) + return s +} + +// UnsetMargins removes all margin style rules. +func (s Style) UnsetMargins() Style { + s.unset(marginLeftKey) + s.unset(marginRightKey) + s.unset(marginTopKey) + s.unset(marginBottomKey) + return s +} + +// UnsetMarginLeft removes the left margin style rule, if set. +func (s Style) UnsetMarginLeft() Style { + s.unset(marginLeftKey) + return s +} + +// UnsetMarginRight removes the right margin style rule, if set. +func (s Style) UnsetMarginRight() Style { + s.unset(marginRightKey) + return s +} + +// UnsetMarginTop removes the top margin style rule, if set. +func (s Style) UnsetMarginTop() Style { + s.unset(marginTopKey) + return s +} + +// UnsetMarginBottom removes the bottom margin style rule, if set. +func (s Style) UnsetMarginBottom() Style { + s.unset(marginBottomKey) + return s +} + +// UnsetMarginBackground removes the margin's background color. Note that the +// margin's background color can be set from the background color of another +// style during inheritance. +func (s Style) UnsetMarginBackground() Style { + s.unset(marginBackgroundKey) + return s +} + +// UnsetBorderStyle removes the border style rule, if set. +func (s Style) UnsetBorderStyle() Style { + s.unset(borderStyleKey) + return s +} + +// UnsetBorderTop removes the border top style rule, if set. +func (s Style) UnsetBorderTop() Style { + s.unset(borderTopKey) + return s +} + +// UnsetBorderRight removes the border right style rule, if set. +func (s Style) UnsetBorderRight() Style { + s.unset(borderRightKey) + return s +} + +// UnsetBorderBottom removes the border bottom style rule, if set. +func (s Style) UnsetBorderBottom() Style { + s.unset(borderBottomKey) + return s +} + +// UnsetBorderLeft removes the border left style rule, if set. +func (s Style) UnsetBorderLeft() Style { + s.unset(borderLeftKey) + return s +} + +// UnsetBorderForeground removes all border foreground color styles, if set. +func (s Style) UnsetBorderForeground() Style { + s.unset(borderTopForegroundKey) + s.unset(borderRightForegroundKey) + s.unset(borderBottomForegroundKey) + s.unset(borderLeftForegroundKey) + return s +} + +// UnsetBorderTopForeground removes the top border foreground color rule, +// if set. +func (s Style) UnsetBorderTopForeground() Style { + s.unset(borderTopForegroundKey) + return s +} + +// UnsetBorderRightForeground removes the right border foreground color rule, +// if set. +func (s Style) UnsetBorderRightForeground() Style { + s.unset(borderRightForegroundKey) + return s +} + +// UnsetBorderBottomForeground removes the bottom border foreground color +// rule, if set. +func (s Style) UnsetBorderBottomForeground() Style { + s.unset(borderBottomForegroundKey) + return s +} + +// UnsetBorderLeftForeground removes the left border foreground color rule, +// if set. +func (s Style) UnsetBorderLeftForeground() Style { + s.unset(borderLeftForegroundKey) + return s +} + +// UnsetBorderForegroundBlend removes the border blend foreground color rules, +// if set. +func (s Style) UnsetBorderForegroundBlend() Style { + s.unset(borderForegroundBlendKey) + return s +} + +// UnsetBorderForegroundBlendOffset removes the border blend offset style rule, +// if set. +func (s Style) UnsetBorderForegroundBlendOffset() Style { + s.unset(borderForegroundBlendOffsetKey) + return s +} + +// UnsetBorderBackground removes all border background color styles, if +// set. +func (s Style) UnsetBorderBackground() Style { + s.unset(borderTopBackgroundKey) + s.unset(borderRightBackgroundKey) + s.unset(borderBottomBackgroundKey) + s.unset(borderLeftBackgroundKey) + return s +} + +// UnsetBorderTopBackgroundColor removes the top border background color rule, +// if set. +// +// Deprecated: This function simply calls Style.UnsetBorderTopBackground. +func (s Style) UnsetBorderTopBackgroundColor() Style { + return s.UnsetBorderTopBackground() +} + +// UnsetBorderTopBackground removes the top border background color rule, +// if set. +func (s Style) UnsetBorderTopBackground() Style { + s.unset(borderTopBackgroundKey) + return s +} + +// UnsetBorderRightBackground removes the right border background color +// rule, if set. +func (s Style) UnsetBorderRightBackground() Style { + s.unset(borderRightBackgroundKey) + return s +} + +// UnsetBorderBottomBackground removes the bottom border background color +// rule, if set. +func (s Style) UnsetBorderBottomBackground() Style { + s.unset(borderBottomBackgroundKey) + return s +} + +// UnsetBorderLeftBackground removes the left border color rule, if set. +func (s Style) UnsetBorderLeftBackground() Style { + s.unset(borderLeftBackgroundKey) + return s +} + +// UnsetInline removes the inline style rule, if set. +func (s Style) UnsetInline() Style { + s.unset(inlineKey) + return s +} + +// UnsetMaxWidth removes the max width style rule, if set. +func (s Style) UnsetMaxWidth() Style { + s.unset(maxWidthKey) + return s +} + +// UnsetMaxHeight removes the max height style rule, if set. +func (s Style) UnsetMaxHeight() Style { + s.unset(maxHeightKey) + return s +} + +// UnsetTabWidth removes the tab width style rule, if set. +func (s Style) UnsetTabWidth() Style { + s.unset(tabWidthKey) + return s +} + +// UnsetUnderlineSpaces removes the value set by UnderlineSpaces. +func (s Style) UnsetUnderlineSpaces() Style { + s.unset(underlineSpacesKey) + return s +} + +// UnsetStrikethroughSpaces removes the value set by StrikethroughSpaces. +func (s Style) UnsetStrikethroughSpaces() Style { + s.unset(strikethroughSpacesKey) + return s +} + +// UnsetTransform removes the value set by Transform. +func (s Style) UnsetTransform() Style { + s.unset(transformKey) + return s +} + +// UnsetHyperlink removes the value set by Hyperlink. +func (s Style) UnsetHyperlink() Style { + s.unset(linkKey) + s.unset(linkParamsKey) + s.link, s.linkParams = "", "" // save memory + return s +} + +// UnsetString sets the underlying string value to the empty string. +func (s Style) UnsetString() Style { + s.value = "" + return s +} diff --git a/vendor/charm.land/lipgloss/v2/whitespace.go b/vendor/charm.land/lipgloss/v2/whitespace.go new file mode 100644 index 000000000..e353a076b --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/whitespace.go @@ -0,0 +1,76 @@ +package lipgloss + +import ( + "strings" + + "github.com/charmbracelet/x/ansi" +) + +// whitespace is a whitespace renderer. +type whitespace struct { + chars string + style Style +} + +// newWhitespace creates a new whitespace renderer. +func newWhitespace(opts ...WhitespaceOption) *whitespace { + w := &whitespace{} + for _, opt := range opts { + opt(w) + } + return w +} + +// Render whitespaces. +func (w whitespace) render(width int) string { + if w.chars == "" { + w.chars = " " + } + + r := []rune(w.chars) + j := 0 + b := strings.Builder{} + + // Cycle through runes and print them into the whitespace. + for i := 0; i < width; { + b.WriteRune(r[j]) + // Measure the width of the rune we just wrote, ensuring we always + // make progress to avoid infinite loops with zero-width characters + // like tabs. + runeWidth := ansi.StringWidth(string(r[j])) + if runeWidth < 1 { + runeWidth = 1 + } + i += runeWidth + j++ + if j >= len(r) { + j = 0 + } + } + + // Fill any extra gaps white spaces. This might be necessary if any runes + // are more than one cell wide, which could leave a one-rune gap. + short := width - ansi.StringWidth(b.String()) + if short > 0 { + b.WriteString(strings.Repeat(" ", short)) + } + + return w.style.Render(b.String()) +} + +// WhitespaceOption sets a styling rule for rendering whitespace. +type WhitespaceOption func(*whitespace) + +// WithWhitespaceStyle sets the style for the whitespace. +func WithWhitespaceStyle(s Style) WhitespaceOption { + return func(w *whitespace) { + w.style = s + } +} + +// WithWhitespaceChars sets the characters to be rendered in the whitespace. +func WithWhitespaceChars(s string) WhitespaceOption { + return func(w *whitespace) { + w.chars = s + } +} diff --git a/vendor/charm.land/lipgloss/v2/wrap.go b/vendor/charm.land/lipgloss/v2/wrap.go new file mode 100644 index 000000000..ca0a1e239 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/wrap.go @@ -0,0 +1,107 @@ +package lipgloss + +import ( + "bytes" + "io" + + uv "github.com/charmbracelet/ultraviolet" + "github.com/charmbracelet/x/ansi" +) + +// Wrap wraps the given string to the given width, preserving ANSI styles and links. +func Wrap(s string, width int, breakpoints string) string { + var buf bytes.Buffer + s = ansi.Wrap(s, width, breakpoints) + w := NewWrapWriter(&buf) + defer w.Close() //nolint:errcheck + _, _ = io.WriteString(w, s) + return buf.String() +} + +// WrapWriter is a writer that writes to a buffer and keeps track of the +// current pen style and link state for the purpose of wrapping with newlines. +// +// When it encounters a newline, it resets the style and link, writes the +// newline, and then reapplies the style and link to the next line. +type WrapWriter struct { + w io.Writer + p *ansi.Parser + style uv.Style + link uv.Link +} + +// NewWrapWriter returns a new [WrapWriter]. +func NewWrapWriter(w io.Writer) *WrapWriter { + pw := &WrapWriter{w: w} + pw.p = ansi.GetParser() + handleCsi := func(cmd ansi.Cmd, params ansi.Params) { + if cmd == 'm' { + uv.ReadStyle(params, &pw.style) + } + } + handleOsc := func(cmd int, data []byte) { + if cmd == 8 { + uv.ReadLink(data, &pw.link) + } + } + pw.p.SetHandler(ansi.Handler{ + HandleCsi: handleCsi, + HandleOsc: handleOsc, + }) + return pw +} + +// Style returns the current pen style. +func (w *WrapWriter) Style() uv.Style { + return w.style +} + +// Link returns the current pen link. +func (w *WrapWriter) Link() uv.Link { + return w.link +} + +// Write writes to the buffer. +func (w *WrapWriter) Write(p []byte) (int, error) { + for i := range p { + b := p[i] + w.p.Advance(b) + if b == '\n' { + if !w.style.IsZero() { + _, _ = w.w.Write([]byte(ansi.ResetStyle)) + } + if !w.link.IsZero() { + _, _ = w.w.Write([]byte(ansi.ResetHyperlink())) + } + } + + _, _ = w.w.Write([]byte{b}) + if b == '\n' { + if !w.link.IsZero() { + _, _ = w.w.Write([]byte(ansi.SetHyperlink(w.link.URL, w.link.Params))) + } + if !w.style.IsZero() { + _, _ = w.w.Write([]byte(w.style.String())) + } + } + } + + return len(p), nil +} + +// Close closes the writer, resets the style and link if necessary, and releases +// its parser. Calling it is performance critical, but forgetting it does not +// cause safety issues or leaks. +func (w *WrapWriter) Close() error { + if !w.style.IsZero() { + _, _ = w.w.Write([]byte(ansi.ResetStyle)) + } + if !w.link.IsZero() { + _, _ = w.w.Write([]byte(ansi.ResetHyperlink())) + } + if w.p != nil { + ansi.PutParser(w.p) + w.p = nil + } + return nil +} diff --git a/vendor/charm.land/lipgloss/v2/writer.go b/vendor/charm.land/lipgloss/v2/writer.go new file mode 100644 index 000000000..78b48b8d3 --- /dev/null +++ b/vendor/charm.land/lipgloss/v2/writer.go @@ -0,0 +1,160 @@ +package lipgloss + +import ( + "bytes" + "fmt" + "io" + "os" + + "github.com/charmbracelet/colorprofile" +) + +// Writer is the default writer that prints to stdout, automatically +// downsampling colors when necessary. +var Writer = colorprofile.NewWriter(os.Stdout, os.Environ()) + +// Println to stdout, automatically downsampling colors when necessary, ending +// with a trailing newline. +// +// Example: +// +// str := NewStyle(). +// Foreground(lipgloss.Color("#6a00ff")). +// Render("breakfast") +// +// Println("Time for a", str, "sandwich!") +func Println(v ...any) (int, error) { + return fmt.Fprintln(Writer, v...) //nolint:wrapcheck +} + +// Printf prints formatted text to stdout, automatically downsampling colors +// when necessary. +// +// Example: +// +// str := NewStyle(). +// Foreground(lipgloss.Color("#6a00ff")). +// Render("knuckle") +// +// Printf("Time for a %s sandwich!\n", str) +func Printf(format string, v ...any) (int, error) { + return fmt.Fprintf(Writer, format, v...) //nolint:wrapcheck +} + +// Print to stdout, automatically downsampling colors when necessary. +// +// Example: +// +// str := NewStyle(). +// Foreground(lipgloss.Color("#6a00ff")). +// Render("Who wants marmalade?\n") +// +// Print(str) +func Print(v ...any) (int, error) { + return fmt.Fprint(Writer, v...) //nolint:wrapcheck +} + +// Fprint pritnts to the given writer, automatically downsampling colors when +// necessary. +// +// Example: +// +// str := NewStyle(). +// Foreground(lipgloss.Color("#6a00ff")). +// Render("guzzle") +// +// Fprint(os.Stderr, "I %s horchata pretty much all the time.\n", str) +func Fprint(w io.Writer, v ...any) (int, error) { + return fmt.Fprint(colorprofile.NewWriter(w, os.Environ()), v...) //nolint:wrapcheck +} + +// Fprintln prints to the given writer, automatically downsampling colors when +// necessary, and ending with a trailing newline. +// +// Example: +// +// str := NewStyle(). +// Foreground(lipgloss.Color("#6a00ff")). +// Render("Sandwich time!") +// +// Fprintln(os.Stderr, str) +func Fprintln(w io.Writer, v ...any) (int, error) { + return fmt.Fprintln(colorprofile.NewWriter(w, os.Environ()), v...) //nolint:wrapcheck +} + +// Fprintf prints text to a writer, against the given format, automatically +// downsampling colors when necessary. +// +// Example: +// +// str := NewStyle(). +// Foreground(lipgloss.Color("#6a00ff")). +// Render("artichokes") +// +// Fprintf(os.Stderr, "I really love %s!\n", food) +func Fprintf(w io.Writer, format string, v ...any) (int, error) { + return fmt.Fprintf(colorprofile.NewWriter(w, os.Environ()), format, v...) //nolint:wrapcheck +} + +// Sprint returns a string for stdout, automatically downsampling colors when +// necessary. +// +// Example: +// +// str := NewStyle(). +// Faint(true). +// Foreground(lipgloss.Color("#6a00ff")). +// Render("I love to eat") +// +// str = Sprint(str) +func Sprint(v ...any) string { + var buf bytes.Buffer + w := colorprofile.Writer{ + Forward: &buf, + Profile: Writer.Profile, + } + fmt.Fprint(&w, v...) //nolint:errcheck + return buf.String() +} + +// Sprintln returns a string for stdout, automatically downsampling colors when +// necessary, and ending with a trailing newline. +// +// Example: +// +// str := NewStyle(). +// Bold(true). +// Foreground(lipgloss.Color("#6a00ff")). +// Render("Yummy!") +// +// str = Sprintln(str) +func Sprintln(v ...any) string { + var buf bytes.Buffer + w := colorprofile.Writer{ + Forward: &buf, + Profile: Writer.Profile, + } + fmt.Fprintln(&w, v...) //nolint:errcheck + return buf.String() +} + +// Sprintf returns a formatted string for stdout, automatically downsampling +// colors when necessary. +// +// Example: +// +// str := NewStyle(). +// Bold(true). +// Foreground(lipgloss.Color("#fccaee")). +// Render("Cantaloupe") +// +// str = Sprintf("I really love %s!", str) +func Sprintf(format string, v ...any) string { + var buf bytes.Buffer + w := colorprofile.Writer{ + Forward: &buf, + Profile: Writer.Profile, + } + fmt.Fprintf(&w, format, v...) //nolint:errcheck + return buf.String() +} diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go index fe4599e3d..97f4de259 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go @@ -77,9 +77,18 @@ const ( ) // CertFromPEM converts a PEM file (.pem or .key) for use with [NewCredFromCert]. The file -// must contain the public certificate and the private key. If a PEM block is encrypted and -// password is not an empty string, it attempts to decrypt the PEM blocks using the password. +// must contain the public certificate and the unencrypted private key. // Multiple certs are due to certificate chaining for use cases like TLS that sign from root to leaf. +// +// Encrypted PEM private keys are not supported. Legacy RFC 1423 encrypted PEM blocks (identified +// by a DEK-Info header, e.g. "DEK-Info: DES-EDE3-CBC,...") rely on a weak key derivation function +// (a single MD5 iteration) and obsolete DES/3DES ciphers, so CertFromPEM rejects them with an +// error instead of decrypting them. Provide the private key unencrypted and protect it with +// filesystem permissions; you can remove legacy encryption with, for example: +// +// openssl pkcs8 -topk8 -nocrypt -in legacy.key -out key.pem +// +// The password parameter is retained for backward compatibility and is ignored. func CertFromPEM(pemData []byte, password string) ([]*x509.Certificate, crypto.PrivateKey, error) { var certs []*x509.Certificate var priv crypto.PrivateKey @@ -89,16 +98,8 @@ func CertFromPEM(pemData []byte, password string) ([]*x509.Certificate, crypto.P break } - //nolint:staticcheck // x509.IsEncryptedPEMBlock and x509.DecryptPEMBlock are deprecated. They are used here only to support a usecase. - if x509.IsEncryptedPEMBlock(block) { - b, err := x509.DecryptPEMBlock(block, []byte(password)) - if err != nil { - return nil, nil, fmt.Errorf("could not decrypt encrypted PEM block: %v", err) - } - block, _ = pem.Decode(b) - if block == nil { - return nil, nil, fmt.Errorf("encounter encrypted PEM block that did not decode") - } + if _, encrypted := block.Headers["DEK-Info"]; encrypted { + return nil, nil, fmt.Errorf("legacy RFC 1423 encrypted PEM blocks are not supported because they use a weak key derivation function (single MD5 iteration) and obsolete DES/3DES ciphers; provide the private key unencrypted (e.g. `openssl pkcs8 -topk8 -nocrypt -in legacy.key -out key.pem`) and protect it with filesystem permissions") } switch block.Type { diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/items.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/items.go index b7d1a670b..cda000afc 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/items.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/items.go @@ -180,7 +180,7 @@ func NewIDToken(homeID, env, realm, clientID, idToken string) IDToken { HomeAccountID: homeID, Environment: env, Realm: realm, - CredentialType: "IDToken", + CredentialType: "IdToken", ClientID: clientID, Secret: idToken, } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go index 07702ef77..bf0af2629 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go @@ -44,8 +44,8 @@ const ( loginMicrosoftOnline = defaultHost ) -// validRegion matches Azure region names: lowercase alphanumeric and hyphens only. -var validRegion = regexp.MustCompile(`^[a-z][a-z0-9-]*$`) +// validRegion matches Azure region names that are valid lowercase ASCII DNS labels. +var validRegion = regexp.MustCompile(`^[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$`) // jsonCaller is an interface that allows us to mock the JSONCall method. type jsonCaller interface { @@ -669,14 +669,14 @@ func (c Client) AADInstanceDiscovery(ctx context.Context, authorityInfo Info) (I var err error resp := InstanceDiscoveryResponse{} if authorityInfo.Region != "" && authorityInfo.Region != autoDetectRegion { + if !validRegion.MatchString(authorityInfo.Region) { + return resp, fmt.Errorf("invalid region %q: region must be a lowercase ASCII DNS label of at most 63 characters", authorityInfo.Region) + } region = authorityInfo.Region } else if authorityInfo.Region == autoDetectRegion { region = detectRegion(ctx) } if region != "" { - if !validRegion.MatchString(region) { - return resp, fmt.Errorf("invalid region %q: region must contain only lowercase alphanumeric characters and hyphens", region) - } environment := authorityInfo.Host switch environment { case loginMicrosoft, loginWindows, loginSTSWindows, defaultHost: @@ -717,8 +717,10 @@ func (c Client) AADInstanceDiscovery(ctx context.Context, authorityInfo Info) (I func detectRegion(ctx context.Context) string { region := os.Getenv(regionName) if region != "" { - region = strings.ReplaceAll(region, " ", "") - return strings.ToLower(region) + if validRegion.MatchString(region) { + return region + } + return "" } // HTTP call to IMDS endpoint to get region // Refer : https://identitydivision.visualstudio.com/DevEx/_git/AuthLibrariesApiReview?path=%2FPinAuthToRegion%2FAAD%20SDK%20Proposal%20to%20Pin%20Auth%20to%20region.md&_a=preview&version=GBdev @@ -760,6 +762,9 @@ func parseRegionFromIMDSResponse(body []byte) string { if err := json.Unmarshal(body, &parsed); err != nil { return "" } + if !validRegion.MatchString(parsed.Location) { + return "" + } return parsed.Location } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.go index e3d19886e..99e1205e5 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.go @@ -34,15 +34,14 @@ type MexDocument struct { bindings map[string]wsEndpointData } -func updateEndpoint(cached *Endpoint, found Endpoint) { - if cached == nil || cached.Version == TrustUnknown { - *cached = found - return +func updateEndpoint(cached Endpoint, found Endpoint) Endpoint { + if cached.Version == TrustUnknown { + return found } - if (*cached).Version == Trust2005 && found.Version == Trust13 { - *cached = found - return + if cached.Version == Trust2005 && found.Version == Trust13 { + return found } + return cached } // TODO(msal): Someone needs to write tests for everything below. @@ -147,9 +146,9 @@ func endpoints(defs Definitions, bindings map[string]wsEndpointData) (userPass, switch binding.EndpointType { case etUsernamePassword: - updateEndpoint(&userPass, endpoint) + userPass = updateEndpoint(userPass, endpoint) case etWindowsTransport: - updateEndpoint(&windows, endpoint) + windows = updateEndpoint(windows, endpoint) default: return Endpoint{}, Endpoint{}, errors.New("found unknown port type in MEX document") } diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go index d19d8376a..1305c467f 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go @@ -8,4 +8,4 @@ package version const SKU = "MSAL.Go" // Version is the version of this client package that is communicated to the server. -const Version = "1.8.0" +const Version = "1.9.0" diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/managedidentity.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/managedidentity.go index ecb0fbeca..d4815be9c 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/managedidentity.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/managedidentity.go @@ -10,6 +10,7 @@ without using credentials. package managedidentity import ( + "bytes" "context" "encoding/json" "fmt" @@ -61,11 +62,11 @@ const ( wwwAuthenticateHeaderName = "www-authenticate" // UAMI query parameter name - miQueryParameterClientId = "client_id" - miQueryParameterObjectId = "object_id" - miQueryParameterPrincipalId = "principal_id" - miQueryParameterResourceIdIMDS = "msi_res_id" - miQueryParameterResourceId = "mi_res_id" + miQueryParameterClientId = "client_id" + miQueryParameterObjectId = "object_id" + miQueryParameterPrincipalId = "principal_id" + miQueryParameterMsiResourceId = "msi_res_id" + miQueryParameterResourceId = "mi_res_id" // IMDS imdsDefaultEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token" @@ -177,6 +178,7 @@ type Client struct { httpClient ops.HTTPClient miType ID source Source + serviceFabricURL string authParams authority.AuthParams retryPolicyEnabled bool canRefresh *atomic.Value @@ -198,7 +200,8 @@ func WithClaims(claims string) AcquireTokenOption { } } -// WithHTTPClient allows for a custom HTTP client to be set. +// WithHTTPClient allows for a custom HTTP client to be set. Service Fabric requires a standard +// *http.Client with a *http.Transport and does not support custom TLS dialing or verification. func WithHTTPClient(httpClient ops.HTTPClient) ClientOption { return func(c *Client) { c.httpClient = httpClient @@ -223,11 +226,6 @@ func New(id ID, options ...ClientOption) (Client, error) { // Check for user-assigned restrictions based on the source switch source { - case AzureArc: - switch id.(type) { - case UserAssignedClientID, UserAssignedResourceID, UserAssignedObjectID: - return Client{}, errors.New("Azure Arc doesn't support user-assigned managed identities") - } case AzureML: switch id.(type) { case UserAssignedObjectID, UserAssignedResourceID: @@ -274,6 +272,14 @@ func New(id ID, options ...ClientOption) (Client, error) { for _, option := range options { option(&client) } + if source == ServiceFabric { + serviceFabricClient, serviceFabricURL, err := serviceFabricCertificateVerifiedHTTPClient(client.httpClient) + if err != nil { + return Client{}, err + } + client.httpClient = serviceFabricClient + client.serviceFabricURL = serviceFabricURL + } fakeAuthInfo, err := authority.NewInfoFromAuthorityURI("https://login.microsoftonline.com/managed_identity", false, true) if err != nil { return Client{}, err @@ -414,7 +420,7 @@ func (c Client) acquireTokenForAzureML(ctx context.Context, resource string) (Au } func (c Client) acquireTokenForServiceFabric(ctx context.Context, resource string) (AuthResult, error) { - req, err := createServiceFabricAuthRequest(ctx, resource) + req, err := createServiceFabricAuthRequest(ctx, c.serviceFabricURL, resource) if err != nil { return AuthResult{}, err } @@ -426,7 +432,7 @@ func (c Client) acquireTokenForServiceFabric(ctx context.Context, resource strin } func (c Client) acquireTokenForAzureArc(ctx context.Context, resource string) (AuthResult, error) { - req, err := createAzureArcAuthRequest(ctx, resource, "") + req, err := createAzureArcAuthRequest(ctx, c.miType, resource, "") if err != nil { return AuthResult{}, err } @@ -446,7 +452,7 @@ func (c Client) acquireTokenForAzureArc(ctx context.Context, resource string) (A return AuthResult{}, err } - secondRequest, err := createAzureArcAuthRequest(ctx, resource, string(secret)) + secondRequest, err := createAzureArcAuthRequest(ctx, c.miType, resource, string(secret)) if err != nil { return AuthResult{}, err } @@ -455,9 +461,77 @@ func (c Client) acquireTokenForAzureArc(ctx context.Context, resource string) (A if err != nil { return AuthResult{}, err } + if err := verifyAzureArcUserAssignedIdentity(c.miType, tokenResponse); err != nil { + return AuthResult{}, err + } return authResultFromToken(c.authParams, tokenResponse) } +// setUserAssignedQueryParam adds the user-assigned identity selector to params. Azure Arc and IMDS +// both use the IMDS "msi_res_id" spelling for the resource id; a system-assigned identity adds +// nothing. An unsupported ID type is rejected (New already validates the caller's input). +func setUserAssignedQueryParam(params url.Values, id ID) error { + switch t := id.(type) { + case UserAssignedClientID: + params.Set(miQueryParameterClientId, string(t)) + case UserAssignedResourceID: + params.Set(miQueryParameterMsiResourceId, string(t)) + case UserAssignedObjectID: + params.Set(miQueryParameterObjectId, string(t)) + case systemAssignedValue: + default: + return fmt.Errorf("unsupported type %T", id) + } + return nil +} + +// verifyAzureArcUserAssignedIdentity fails closed when a user-assigned identity was requested +// but Azure Arc did not confirm it in the token response. A legacy Azure Arc agent ignores the +// client_id / msi_res_id / object_id selector and silently returns the machine's system-assigned +// identity. An agent that supports user-assigned managed identity echoes the identity it used in +// the token response; when that echo is missing or does not match the requested selector, MSAL +// must not hand back a token for a different identity than the one requested. +func verifyAzureArcUserAssignedIdentity(id ID, token accesstokens.TokenResponse) error { + // Reuse the request selector mapping so the request and validation stay in lock-step. + selector := url.Values{} + if err := setUserAssignedQueryParam(selector, id); err != nil { + return err + } + if len(selector) == 0 { + // System-assigned: there is no requested identity to confirm. + return nil + } + var name, requested string + for k := range selector { + name, requested = k, selector.Get(k) + } + // Accept either resource-id spelling on the echo as a safety net; Azure Arc returns msi_res_id. + keys := []string{name} + if name == miQueryParameterMsiResourceId { + keys = append(keys, miQueryParameterResourceId) + } + echoed := additionalStringField(token.AdditionalFields, keys...) + // Compare case-insensitively: client_id / object_id are GUIDs, and an ARM resource id can + // legitimately differ in segment casing. + if echoed == "" || !strings.EqualFold(echoed, requested) { + return errors.New("azure arc did not confirm the requested user-assigned managed identity in the token response; the agent likely does not support user-assigned managed identities and returned the system-assigned identity") + } + return nil +} + +// additionalStringField returns the first non-empty string value among the given keys from a +// token response's additional (untyped) fields. +func additionalStringField(fields map[string]interface{}, keys ...string) string { + for _, k := range keys { + if v, ok := fields[k]; ok { + if s, ok := v.(string); ok && s != "" { + return s + } + } + } + return "" +} + func authResultFromToken(authParams authority.AuthParams, token accesstokens.TokenResponse) (AuthResult, error) { if cacheManager == nil { return AuthResult{}, errors.New("cache instance is nil") @@ -490,33 +564,71 @@ func contains[T comparable](list []T, element T) bool { return false } +// bufferResponseBody reads resp.Body fully into memory and replaces it with an +// in-memory reader. This lets the caller consume the response after the +// per-attempt context that produced it has been canceled. See issue #634. +func bufferResponseBody(resp *http.Response) error { + if resp == nil || resp.Body == nil { + return nil + } + body, err := io.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + resp.Body = io.NopCloser(bytes.NewReader(body)) + return nil +} + // retry performs an HTTP request with retries based on the provided options. func (c Client) retry(maxRetries int, req *http.Request) (*http.Response, error) { var resp *http.Response var err error + // cancelPrev cancels the context of the previous attempt. It is invoked only + // after that attempt's body has been drained, so the transport connection can + // still be reused, while avoiding the resource retention of deferring every + // per-attempt cancel until retry() returns. + var cancelPrev context.CancelFunc + retrylist := retryStatusCodes + if c.source == DefaultToIMDS { + retrylist = retryCodesForIMDS + } for attempt := 0; attempt < maxRetries; attempt++ { tryCtx, tryCancel := context.WithTimeout(req.Context(), time.Minute) - defer tryCancel() if resp != nil && resp.Body != nil { _, _ = io.Copy(io.Discard, resp.Body) resp.Body.Close() } + if cancelPrev != nil { + cancelPrev() + } + cancelPrev = tryCancel cloneReq := req.Clone(tryCtx) resp, err = c.httpClient.Do(cloneReq) - retrylist := retryStatusCodes - if c.source == DefaultToIMDS { - retrylist = retryCodesForIMDS - } - if err == nil && !contains(retrylist, resp.StatusCode) { - return resp, nil + succeeded := err == nil && !contains(retrylist, resp.StatusCode) + if succeeded || attempt == maxRetries-1 { + // Buffer the body into memory while tryCtx is still alive so the + // caller can read resp.Body after we cancel this attempt's context. + // Without this, the deferred/explicit cancel would race the caller's + // read and surface as "context canceled" on an otherwise successful + // response. See issue #634. + if bufErr := bufferResponseBody(resp); bufErr != nil && err == nil { + err = bufErr + } + tryCancel() + return resp, err } select { case <-time.After(time.Second): case <-req.Context().Done(): err = req.Context().Err() + tryCancel() return resp, err } } + if cancelPrev != nil { + cancelPrev() + } return resp, err } @@ -566,6 +678,12 @@ func (c Client) getTokenForRequest(req *http.Request, resource string) (accessto Err: fmt.Errorf("error parsing the json error: %s", err), } } + // Capture the raw response fields so source-specific logic (such as the Azure Arc + // user-assigned identity echo check) can read fields the typed response drops. + var additionalFields map[string]interface{} + if json.Unmarshal(responseBytes, &additionalFields) == nil { + r.AdditionalFields = additionalFields + } r.GrantedScopes.Slice = append(r.GrantedScopes.Slice, resource) return r, err @@ -605,16 +723,8 @@ func createIMDSAuthRequest(ctx context.Context, id ID, resource string) (*http.R msiParameters.Set(apiVersionQueryParameterName, imdsAPIVersion) msiParameters.Set(resourceQueryParameterName, resource) - switch t := id.(type) { - case UserAssignedClientID: - msiParameters.Set(miQueryParameterClientId, string(t)) - case UserAssignedResourceID: - msiParameters.Set(miQueryParameterResourceIdIMDS, string(t)) - case UserAssignedObjectID: - msiParameters.Set(miQueryParameterObjectId, string(t)) - case systemAssignedValue: // not adding anything - default: - return nil, fmt.Errorf("unsupported type %T", id) + if err := setUserAssignedQueryParam(msiParameters, id); err != nil { + return nil, err } msiEndpoint.RawQuery = msiParameters.Encode() @@ -629,7 +739,7 @@ func createIMDSAuthRequest(ctx context.Context, id ID, resource string) (*http.R return req, nil } -func createAzureArcAuthRequest(ctx context.Context, resource string, key string) (*http.Request, error) { +func createAzureArcAuthRequest(ctx context.Context, id ID, resource string, key string) (*http.Request, error) { identityEndpoint := os.Getenv(identityEndpointEnvVar) if identityEndpoint == "" { identityEndpoint = azureArcEndpoint @@ -644,6 +754,12 @@ func createAzureArcAuthRequest(ctx context.Context, resource string, key string) msiParameters.Set(apiVersionQueryParameterName, azureArcAPIVersion) msiParameters.Set(resourceQueryParameterName, resource) + // Azure Arc honors the IMDS msi_res_id spelling for the resource-id selector; the mi_res_id + // spelling is silently ignored and returns the system-assigned identity. + if err := setUserAssignedQueryParam(msiParameters, id); err != nil { + return nil, err + } + msiEndpoint.RawQuery = msiParameters.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodGet, msiEndpoint.String(), nil) if err != nil { diff --git a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/servicefabric.go b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/servicefabric.go index 535065e9d..33c32e4b1 100644 --- a/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/servicefabric.go +++ b/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/servicefabric.go @@ -5,13 +5,127 @@ package managedidentity import ( "context" + + /* #nosec */ + "crypto/sha1" + "crypto/subtle" + "crypto/tls" + "crypto/x509" + "encoding/hex" + "errors" "net/http" "os" + "strings" + "unicode" ) -func createServiceFabricAuthRequest(ctx context.Context, resource string) (*http.Request, error) { - identityEndpoint := os.Getenv(identityEndpointEnvVar) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, identityEndpoint, nil) +// serviceFabricCertificateVerifiedHTTPClient derives a client with Service Fabric's required certificate pinning. +// Only standard clients and transports can be safely cloned and augmented without changing the +// caller's behavior for other requests. +func serviceFabricCertificateVerifiedHTTPClient(httpClient interface { + Do(*http.Request) (*http.Response, error) + CloseIdleConnections() +}) (*http.Client, string, error) { + endpoint, err := serviceFabricEndpoint() + if err != nil { + return nil, "", err + } + pin, err := serviceFabricThumbprint(os.Getenv(identityServerThumbprintEnvVar)) + if err != nil { + return nil, "", err + } + + callerClient, ok := httpClient.(*http.Client) + if !ok { + return nil, "", errors.New("Service Fabric managed identity requires a standard *http.Client") + } + derivedClient := *callerClient + + var callerTransport *http.Transport + if callerClient.Transport == nil { + var ok bool + callerTransport, ok = http.DefaultTransport.(*http.Transport) + if !ok { + return nil, "", errors.New("Service Fabric managed identity requires a standard *http.Transport") + } + } else { + var ok bool + callerTransport, ok = callerClient.Transport.(*http.Transport) + if !ok { + return nil, "", errors.New("Service Fabric managed identity requires a standard *http.Transport") + } + } + //nolint:staticcheck // DialTLS must be rejected because it bypasses TLSClientConfig. + if callerTransport.DialTLS != nil || callerTransport.DialTLSContext != nil { + return nil, "", errors.New("Service Fabric managed identity does not support a transport with custom TLS dialing") + } + if callerTransport.TLSClientConfig != nil && + (callerTransport.TLSClientConfig.VerifyPeerCertificate != nil || callerTransport.TLSClientConfig.VerifyConnection != nil) { + return nil, "", errors.New("Service Fabric managed identity does not support custom TLS verification") + } + if callerTransport.TLSNextProto != nil { + return nil, "", errors.New("Service Fabric managed identity does not support custom TLS protocol handlers") + } + derivedTransport := callerTransport.Clone() + tlsConfig := derivedTransport.TLSClientConfig.Clone() + if tlsConfig == nil { + tlsConfig = &tls.Config{} + } + tlsConfig.InsecureSkipVerify = true // #nosec G402 -- VerifyConnection below pins the Service Fabric self-signed certificate. + tlsConfig.VerifyConnection = func(connectionState tls.ConnectionState) error { + if len(connectionState.PeerCertificates) == 0 { + return errors.New("Service Fabric TLS connection did not provide a certificate") + } + if subtle.ConstantTimeCompare(serviceFabricCertificateThumbprint(connectionState.PeerCertificates[0]), pin) != 1 { + return errors.New("Service Fabric TLS certificate thumbprint did not match IDENTITY_SERVER_THUMBPRINT") + } + return nil + } + derivedTransport.TLSClientConfig = tlsConfig + derivedClient.Transport = derivedTransport + derivedClient.CheckRedirect = func(*http.Request, []*http.Request) error { + return errors.New("Service Fabric managed identity redirects are not permitted") + } + return &derivedClient, endpoint, nil +} + +func serviceFabricEndpoint() (string, error) { + endpoint := os.Getenv(identityEndpointEnvVar) + request, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + return "", err + } + if request.URL.Scheme != "https" || request.URL.Host == "" { + return "", errors.New("Service Fabric managed identity endpoint must use HTTPS") + } + return request.URL.String(), nil +} + +func serviceFabricThumbprint(value string) ([]byte, error) { + normalized := strings.Map(func(r rune) rune { + if r == ':' || unicode.IsSpace(r) { + return -1 + } + return r + }, value) + if len(normalized) != 40 { + return nil, errors.New("IDENTITY_SERVER_THUMBPRINT must be a SHA-1 certificate thumbprint") + } + thumbprint, err := hex.DecodeString(normalized) + if err != nil || len(thumbprint) != 20 { + return nil, errors.New("IDENTITY_SERVER_THUMBPRINT must be a SHA-1 certificate thumbprint") + } + return thumbprint, nil +} + +func serviceFabricCertificateThumbprint(certificate *x509.Certificate) []byte { + // Service Fabric exposes SHA-1 certificate thumbprints through IDENTITY_SERVER_THUMBPRINT. + thumbprint := sha1.Sum(certificate.Raw) /* #nosec G401 -- Service Fabric publishes SHA-1 thumbprints. */ // NOSONAR -- Service Fabric defines IDENTITY_SERVER_THUMBPRINT as the SHA-1 hash of its self-signed endpoint certificate; this compares that platform-defined identifier, not a security digest. + return thumbprint[:] +} + +func createServiceFabricAuthRequest(ctx context.Context, endpoint, resource string) (*http.Request, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, err } diff --git a/vendor/github.com/IBM/vpc-go-sdk/common/version.go b/vendor/github.com/IBM/vpc-go-sdk/common/version.go index 67a2d2054..81c4cf13a 100644 --- a/vendor/github.com/IBM/vpc-go-sdk/common/version.go +++ b/vendor/github.com/IBM/vpc-go-sdk/common/version.go @@ -1,4 +1,4 @@ package common // Version of the SDK -const Version = "0.89.0" +const Version = "0.90.0" diff --git a/vendor/github.com/IBM/vpc-go-sdk/vpcv1/vpc_v1.go b/vendor/github.com/IBM/vpc-go-sdk/vpcv1/vpc_v1.go index d3eb260b4..d98dca4bc 100644 --- a/vendor/github.com/IBM/vpc-go-sdk/vpcv1/vpc_v1.go +++ b/vendor/github.com/IBM/vpc-go-sdk/vpcv1/vpc_v1.go @@ -47,7 +47,7 @@ type VpcV1 struct { Generation *int64 // The API version, in format `YYYY-MM-DD`. For the API behavior documented here, specify any date between `2026-04-07` - // and `2026-08-13`. + // and `2026-08-19`. Version *string } @@ -68,7 +68,7 @@ type VpcV1Options struct { Generation *int64 // The API version, in format `YYYY-MM-DD`. For the API behavior documented here, specify any date between `2026-04-07` - // and `2026-08-13`. + // and `2026-08-19`. Version *string } @@ -133,7 +133,7 @@ func NewVpcV1(options *VpcV1Options) (service *VpcV1, err error) { } if options.Version == nil { - options.Version = core.StringPtr("2026-08-04") + options.Version = core.StringPtr("2026-08-18") } service = &VpcV1{ @@ -36564,7 +36564,7 @@ func (vpc *VpcV1) UpdateVPNServerRouteWithContext(ctx context.Context, updateVPN return } func getServiceComponentInfo() *core.ProblemComponent { - return core.NewProblemComponent(DefaultServiceName, "2026-08-04") + return core.NewProblemComponent(DefaultServiceName, "2026-08-18") } // AccountIdentity : Identifies an account by a unique property. @@ -49314,8 +49314,19 @@ type CreateLoadBalancerPoolOptions struct { // The load balancer identifier. LoadBalancerID *string `json:"load_balancer_id" validate:"required,ne="` - // The load balancing algorithm. The `least_connections` algorithm is only supported for load balancers that have - // `availability` with value `subnet` in the profile. + // The load balancing algorithm. + // + // - `least_connections`: Routes traffic to the pool member with the least active + // connections. Supported by `application` and `network` family load balancers that + // have `availability` with value `subnet` in the profile. + // - `round_robin`: Distributes traffic sequentially across pool members. Supported by + // `application` and `network` family load balancers. + // - `weighted_round_robin`: Distributes traffic across pool members proportionally to + // configured member weights. Supported by `application` and `network` + // family load balancers. + // - `weighted_forwarding`: Forwards the layer 4 packets across backend pools + // proportionally to configured member weights. Supported by `network` family + // load balancers with an `asymmetric_routing_supported` value of `true`. Algorithm *string `json:"algorithm" validate:"required"` // The health monitor of this pool. @@ -49388,11 +49399,23 @@ type CreateLoadBalancerPoolOptions struct { } // Constants associated with the CreateLoadBalancerPoolOptions.Algorithm property. -// The load balancing algorithm. The `least_connections` algorithm is only supported for load balancers that have -// `availability` with value `subnet` in the profile. +// The load balancing algorithm. +// +// - `least_connections`: Routes traffic to the pool member with the least active +// connections. Supported by `application` and `network` family load balancers that +// have `availability` with value `subnet` in the profile. +// - `round_robin`: Distributes traffic sequentially across pool members. Supported by +// `application` and `network` family load balancers. +// - `weighted_round_robin`: Distributes traffic across pool members proportionally to +// configured member weights. Supported by `application` and `network` +// family load balancers. +// - `weighted_forwarding`: Forwards the layer 4 packets across backend pools +// proportionally to configured member weights. Supported by `network` family +// load balancers with an `asymmetric_routing_supported` value of `true`. const ( CreateLoadBalancerPoolOptionsAlgorithmLeastConnectionsConst = "least_connections" CreateLoadBalancerPoolOptionsAlgorithmRoundRobinConst = "round_robin" + CreateLoadBalancerPoolOptionsAlgorithmWeightedForwardingConst = "weighted_forwarding" CreateLoadBalancerPoolOptionsAlgorithmWeightedRoundRobinConst = "weighted_round_robin" ) @@ -82260,6 +82283,9 @@ type LoadBalancer struct { // Indicates whether this load balancer supports advanced health checks. AdvancedHealthChecksSupported *bool `json:"advanced_health_checks_supported" validate:"required"` + // Indicates whether this load balancer supports asymmetric routing. + AsymmetricRoutingSupported *bool `json:"asymmetric_routing_supported" validate:"required"` + // The load balancer pool members attached to this load balancer. AttachedLoadBalancerPoolMembers []LoadBalancerPoolMemberReference `json:"attached_load_balancer_pool_members" validate:"required"` @@ -82487,6 +82513,11 @@ func UnmarshalLoadBalancer(m map[string]json.RawMessage, result interface{}) (er err = core.SDKErrorf(err, "", "advanced_health_checks_supported-error", common.GetComponentInfo()) return } + err = core.UnmarshalPrimitive(m, "asymmetric_routing_supported", &obj.AsymmetricRoutingSupported) + if err != nil { + err = core.SDKErrorf(err, "", "asymmetric_routing_supported-error", common.GetComponentInfo()) + return + } err = core.UnmarshalModel(m, "attached_load_balancer_pool_members", &obj.AttachedLoadBalancerPoolMembers, UnmarshalLoadBalancerPoolMemberReference) if err != nil { err = core.SDKErrorf(err, "", "attached_load_balancer_pool_members-error", common.GetComponentInfo()) @@ -84600,13 +84631,13 @@ func UnmarshalLoadBalancerListenerPolicyTarget(m map[string]json.RawMessage, res // LoadBalancerListenerPolicyTargetPatch : - If `action` is `forward_to_listener`, specify a `LoadBalancerListenerIdentity` for a // -// listener in this load balancer. -// - If `action` is `forward_to_pool`, specify a `LoadBalancerPoolIdentity` for a pool in -// this load balancer. -// - If `action` is `https_redirect`, specify a -// `LoadBalancerListenerPolicyHTTPSRedirectPatch` for a listener in this load balancer -// with a `protocol` of `https`. -// - If `action` is `redirect`, specify a `LoadBalancerListenerPolicyRedirectURLPatch`. +// listener in this load balancer. +// - If `action` is `forward_to_pool`, specify a `LoadBalancerPoolIdentity` for a pool in +// this load balancer. +// - If `action` is `https_redirect`, specify a +// `LoadBalancerListenerPolicyHTTPSRedirectPatch` for a listener in this load balancer +// with a `protocol` of `https`. +// - If `action` is `redirect`, specify a `LoadBalancerListenerPolicyRedirectURLPatch`. // // Models which "extend" this model: // - LoadBalancerListenerPolicyTargetPatchLoadBalancerPoolIdentity @@ -84725,14 +84756,14 @@ func (loadBalancerListenerPolicyTargetPatch *LoadBalancerListenerPolicyTargetPat // LoadBalancerListenerPolicyTargetPrototype : - If `action` is `forward_to_listener`, specify a `LoadBalancerListenerIdentity` in this // -// load balancer to forward to. -// - If `action` is `forward_to_pool`, use `LoadBalancerPoolIdentity` to specify a pool in -// this load balancer to forward to. -// - If `action` is `https_redirect`, use -// `LoadBalancerListenerPolicyHTTPSRedirectPrototype` to specify a listener on this -// load balancer to redirect to. -// - If `action` is `redirect`, use `LoadBalancerListenerPolicyRedirectURLPrototype`to -// specify a URL to redirect to. +// load balancer to forward to. +// - If `action` is `forward_to_pool`, use `LoadBalancerPoolIdentity` to specify a pool in +// this load balancer to forward to. +// - If `action` is `https_redirect`, use +// `LoadBalancerListenerPolicyHTTPSRedirectPrototype` to specify a listener on this +// load balancer to redirect to. +// - If `action` is `redirect`, use `LoadBalancerListenerPolicyRedirectURLPrototype`to +// specify a URL to redirect to. // // Models which "extend" this model: // - LoadBalancerListenerPolicyTargetPrototypeLoadBalancerPoolIdentity @@ -85348,6 +85379,7 @@ type LoadBalancerPool struct { const ( LoadBalancerPoolAlgorithmLeastConnectionsConst = "least_connections" LoadBalancerPoolAlgorithmRoundRobinConst = "round_robin" + LoadBalancerPoolAlgorithmWeightedForwardingConst = "weighted_forwarding" LoadBalancerPoolAlgorithmWeightedRoundRobinConst = "weighted_round_robin" ) @@ -87156,8 +87188,19 @@ func (loadBalancerPoolMemberTargetPrototype *LoadBalancerPoolMemberTargetPrototy // LoadBalancerPoolPatch : LoadBalancerPoolPatch struct type LoadBalancerPoolPatch struct { - // The load balancing algorithm. The `least_connections` algorithm is only supported for load balancers that have - // `availability` with value `subnet` in the profile. + // The load balancing algorithm. + // + // - `least_connections`: Routes traffic to the pool member with the least active + // connections. Supported by `application` and `network` family load balancers that + // have `availability` with value `subnet` in the profile. + // - `round_robin`: Distributes traffic sequentially across pool members. Supported by + // `application` and `network` family load balancers. + // - `weighted_round_robin`: Distributes traffic across pool members proportionally to + // configured member weights. Supported by `application` and `network` + // family load balancers. + // - `weighted_forwarding`: Forwards the layer 4 packets across backend pools + // proportionally to configured member weights. Supported by `network` family + // load balancers with an `asymmetric_routing_supported` value of `true`. Algorithm *string `json:"algorithm,omitempty"` // The client authentication to use for this pool. @@ -87222,11 +87265,23 @@ type LoadBalancerPoolPatch struct { } // Constants associated with the LoadBalancerPoolPatch.Algorithm property. -// The load balancing algorithm. The `least_connections` algorithm is only supported for load balancers that have -// `availability` with value `subnet` in the profile. +// The load balancing algorithm. +// +// - `least_connections`: Routes traffic to the pool member with the least active +// connections. Supported by `application` and `network` family load balancers that +// have `availability` with value `subnet` in the profile. +// - `round_robin`: Distributes traffic sequentially across pool members. Supported by +// `application` and `network` family load balancers. +// - `weighted_round_robin`: Distributes traffic across pool members proportionally to +// configured member weights. Supported by `application` and `network` +// family load balancers. +// - `weighted_forwarding`: Forwards the layer 4 packets across backend pools +// proportionally to configured member weights. Supported by `network` family +// load balancers with an `asymmetric_routing_supported` value of `true`. const ( LoadBalancerPoolPatchAlgorithmLeastConnectionsConst = "least_connections" LoadBalancerPoolPatchAlgorithmRoundRobinConst = "round_robin" + LoadBalancerPoolPatchAlgorithmWeightedForwardingConst = "weighted_forwarding" LoadBalancerPoolPatchAlgorithmWeightedRoundRobinConst = "weighted_round_robin" ) @@ -87348,8 +87403,19 @@ func (loadBalancerPoolPatch *LoadBalancerPoolPatch) AsPatch() (_patch map[string // LoadBalancerPoolPrototypeLoadBalancerContext : LoadBalancerPoolPrototypeLoadBalancerContext struct type LoadBalancerPoolPrototypeLoadBalancerContext struct { - // The load balancing algorithm. The `least_connections` algorithm is only supported for load balancers that have - // `availability` with value `subnet` in the profile. + // The load balancing algorithm. + // + // - `least_connections`: Routes traffic to the pool member with the least active + // connections. Supported by `application` and `network` family load balancers that + // have `availability` with value `subnet` in the profile. + // - `round_robin`: Distributes traffic sequentially across pool members. Supported by + // `application` and `network` family load balancers. + // - `weighted_round_robin`: Distributes traffic across pool members proportionally to + // configured member weights. Supported by `application` and `network` + // family load balancers. + // - `weighted_forwarding`: Forwards the layer 4 packets across backend pools + // proportionally to configured member weights. Supported by `network` family + // load balancers with an `asymmetric_routing_supported` value of `true`. Algorithm *string `json:"algorithm" validate:"required"` // The client authentication to use for this pool. @@ -87414,11 +87480,23 @@ type LoadBalancerPoolPrototypeLoadBalancerContext struct { } // Constants associated with the LoadBalancerPoolPrototypeLoadBalancerContext.Algorithm property. -// The load balancing algorithm. The `least_connections` algorithm is only supported for load balancers that have -// `availability` with value `subnet` in the profile. +// The load balancing algorithm. +// +// - `least_connections`: Routes traffic to the pool member with the least active +// connections. Supported by `application` and `network` family load balancers that +// have `availability` with value `subnet` in the profile. +// - `round_robin`: Distributes traffic sequentially across pool members. Supported by +// `application` and `network` family load balancers. +// - `weighted_round_robin`: Distributes traffic across pool members proportionally to +// configured member weights. Supported by `application` and `network` +// family load balancers. +// - `weighted_forwarding`: Forwards the layer 4 packets across backend pools +// proportionally to configured member weights. Supported by `network` family +// load balancers with an `asymmetric_routing_supported` value of `true`. const ( LoadBalancerPoolPrototypeLoadBalancerContextAlgorithmLeastConnectionsConst = "least_connections" LoadBalancerPoolPrototypeLoadBalancerContextAlgorithmRoundRobinConst = "round_robin" + LoadBalancerPoolPrototypeLoadBalancerContextAlgorithmWeightedForwardingConst = "weighted_forwarding" LoadBalancerPoolPrototypeLoadBalancerContextAlgorithmWeightedRoundRobinConst = "weighted_round_robin" ) @@ -87836,6 +87914,8 @@ type LoadBalancerProfile struct { AdvancedHealthChecksSupported LoadBalancerProfileAdvancedHealthCheckSupportedIntf `json:"advanced_health_checks_supported" validate:"required"` + AsymmetricRoutingSupported LoadBalancerProfileAsymmetricRoutingSupportedIntf `json:"asymmetric_routing_supported" validate:"required"` + Availability LoadBalancerProfileAvailabilityIntf `json:"availability" validate:"required"` FailsafePolicyActions LoadBalancerProfileFailsafePolicyActionsIntf `json:"failsafe_policy_actions" validate:"required"` @@ -87898,6 +87978,11 @@ func UnmarshalLoadBalancerProfile(m map[string]json.RawMessage, result interface err = core.SDKErrorf(err, "", "advanced_health_checks_supported-error", common.GetComponentInfo()) return } + err = core.UnmarshalModel(m, "asymmetric_routing_supported", &obj.AsymmetricRoutingSupported, UnmarshalLoadBalancerProfileAsymmetricRoutingSupported) + if err != nil { + err = core.SDKErrorf(err, "", "asymmetric_routing_supported-error", common.GetComponentInfo()) + return + } err = core.UnmarshalModel(m, "availability", &obj.Availability, UnmarshalLoadBalancerProfileAvailability) if err != nil { err = core.SDKErrorf(err, "", "availability-error", common.GetComponentInfo()) @@ -88066,6 +88151,49 @@ func UnmarshalLoadBalancerProfileAdvancedHealthCheckSupported(m map[string]json. return } +// LoadBalancerProfileAsymmetricRoutingSupported : LoadBalancerProfileAsymmetricRoutingSupported struct +// Models which "extend" this model: +// - LoadBalancerProfileAsymmetricRoutingSupportedFixed +// - LoadBalancerProfileAsymmetricRoutingSupportedDependent +type LoadBalancerProfileAsymmetricRoutingSupported struct { + // The type for this profile field. + Type *string `json:"type,omitempty"` + + // The value for this profile field. + Value *bool `json:"value,omitempty"` +} + +// Constants associated with the LoadBalancerProfileAsymmetricRoutingSupported.Type property. +// The type for this profile field. +const ( + LoadBalancerProfileAsymmetricRoutingSupportedTypeFixedConst = "fixed" +) + +func (*LoadBalancerProfileAsymmetricRoutingSupported) isaLoadBalancerProfileAsymmetricRoutingSupported() bool { + return true +} + +type LoadBalancerProfileAsymmetricRoutingSupportedIntf interface { + isaLoadBalancerProfileAsymmetricRoutingSupported() bool +} + +// UnmarshalLoadBalancerProfileAsymmetricRoutingSupported unmarshals an instance of LoadBalancerProfileAsymmetricRoutingSupported from the specified map of raw messages. +func UnmarshalLoadBalancerProfileAsymmetricRoutingSupported(m map[string]json.RawMessage, result interface{}) (err error) { + obj := new(LoadBalancerProfileAsymmetricRoutingSupported) + err = core.UnmarshalPrimitive(m, "type", &obj.Type) + if err != nil { + err = core.SDKErrorf(err, "", "type-error", common.GetComponentInfo()) + return + } + err = core.UnmarshalPrimitive(m, "value", &obj.Value) + if err != nil { + err = core.SDKErrorf(err, "", "value-error", common.GetComponentInfo()) + return + } + reflect.ValueOf(result).Elem().Set(reflect.ValueOf(obj)) + return +} + // LoadBalancerProfileAvailability : LoadBalancerProfileAvailability struct // Models which "extend" this model: // - LoadBalancerProfileAvailabilityFixed @@ -143251,6 +143379,72 @@ func UnmarshalLoadBalancerProfileAdvancedHealthCheckSupportedFixed(m map[string] return } +// LoadBalancerProfileAsymmetricRoutingSupportedDependent : The asymmetric routing support for a load balancer with this profile depends on its configuration. +// This model "extends" LoadBalancerProfileAsymmetricRoutingSupported +type LoadBalancerProfileAsymmetricRoutingSupportedDependent struct { + // The type for this profile field. + Type *string `json:"type" validate:"required"` +} + +// Constants associated with the LoadBalancerProfileAsymmetricRoutingSupportedDependent.Type property. +// The type for this profile field. +const ( + LoadBalancerProfileAsymmetricRoutingSupportedDependentTypeDependentConst = "dependent" +) + +func (*LoadBalancerProfileAsymmetricRoutingSupportedDependent) isaLoadBalancerProfileAsymmetricRoutingSupported() bool { + return true +} + +// UnmarshalLoadBalancerProfileAsymmetricRoutingSupportedDependent unmarshals an instance of LoadBalancerProfileAsymmetricRoutingSupportedDependent from the specified map of raw messages. +func UnmarshalLoadBalancerProfileAsymmetricRoutingSupportedDependent(m map[string]json.RawMessage, result interface{}) (err error) { + obj := new(LoadBalancerProfileAsymmetricRoutingSupportedDependent) + err = core.UnmarshalPrimitive(m, "type", &obj.Type) + if err != nil { + err = core.SDKErrorf(err, "", "type-error", common.GetComponentInfo()) + return + } + reflect.ValueOf(result).Elem().Set(reflect.ValueOf(obj)) + return +} + +// LoadBalancerProfileAsymmetricRoutingSupportedFixed : The asymmetric routing support for a load balancer with this profile. +// This model "extends" LoadBalancerProfileAsymmetricRoutingSupported +type LoadBalancerProfileAsymmetricRoutingSupportedFixed struct { + // The type for this profile field. + Type *string `json:"type" validate:"required"` + + // The value for this profile field. + Value *bool `json:"value" validate:"required"` +} + +// Constants associated with the LoadBalancerProfileAsymmetricRoutingSupportedFixed.Type property. +// The type for this profile field. +const ( + LoadBalancerProfileAsymmetricRoutingSupportedFixedTypeFixedConst = "fixed" +) + +func (*LoadBalancerProfileAsymmetricRoutingSupportedFixed) isaLoadBalancerProfileAsymmetricRoutingSupported() bool { + return true +} + +// UnmarshalLoadBalancerProfileAsymmetricRoutingSupportedFixed unmarshals an instance of LoadBalancerProfileAsymmetricRoutingSupportedFixed from the specified map of raw messages. +func UnmarshalLoadBalancerProfileAsymmetricRoutingSupportedFixed(m map[string]json.RawMessage, result interface{}) (err error) { + obj := new(LoadBalancerProfileAsymmetricRoutingSupportedFixed) + err = core.UnmarshalPrimitive(m, "type", &obj.Type) + if err != nil { + err = core.SDKErrorf(err, "", "type-error", common.GetComponentInfo()) + return + } + err = core.UnmarshalPrimitive(m, "value", &obj.Value) + if err != nil { + err = core.SDKErrorf(err, "", "value-error", common.GetComponentInfo()) + return + } + reflect.ValueOf(result).Elem().Set(reflect.ValueOf(obj)) + return +} + // LoadBalancerProfileAvailabilityDependent : The availability mode for a load balancer with this profile depends on its configuration. // This model "extends" LoadBalancerProfileAvailability type LoadBalancerProfileAvailabilityDependent struct { diff --git a/vendor/github.com/aws/aws-sdk-go-v2/aws/go_module_metadata.go b/vendor/github.com/aws/aws-sdk-go-v2/aws/go_module_metadata.go index dfcdf5f8f..2ce6d4939 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/aws/go_module_metadata.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/aws/go_module_metadata.go @@ -3,4 +3,4 @@ package aws // goModuleVersion is the tagged release for this module -const goModuleVersion = "1.43.6" +const goModuleVersion = "1.43.8" diff --git a/vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/headers.go b/vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/headers.go index d99b32ceb..0b9ddd13b 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/headers.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/headers.go @@ -58,6 +58,10 @@ var RequiredSignedHeaders = Rules{ "X-Amz-Tagging": struct{}{}, }, }, + InclusiveRules{ + Patterns{"X-Amz-Checksum-"}, + ExcludeList{Patterns{"X-Amz-Checksum-Mode"}}, + }, Patterns{"X-Amz-Object-Lock-"}, Patterns{"X-Amz-Meta-"}, } diff --git a/vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/CHANGELOG.md b/vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/CHANGELOG.md index e920b6d94..0189638f5 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/CHANGELOG.md +++ b/vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/CHANGELOG.md @@ -1,3 +1,12 @@ +# v1.4.39 (2026-08-25) + +* **Dependency Update**: Update to smithy-go v1.27.10. +* **Dependency Update**: Updated to the latest SDK module versions + +# v1.4.38 (2026-08-20) + +* **Dependency Update**: Updated to the latest SDK module versions + # v1.4.37 (2026-08-14) * **Dependency Update**: Update to smithy-go v1.27.8. diff --git a/vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/go_module_metadata.go b/vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/go_module_metadata.go index 12426cc38..8aedf873b 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/go_module_metadata.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/go_module_metadata.go @@ -3,4 +3,4 @@ package configsources // goModuleVersion is the tagged release for this module -const goModuleVersion = "1.4.37" +const goModuleVersion = "1.4.39" diff --git a/vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/CHANGELOG.md b/vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/CHANGELOG.md index 1f444f7d2..ce640e3b9 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/CHANGELOG.md +++ b/vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/CHANGELOG.md @@ -1,3 +1,12 @@ +# v2.7.39 (2026-08-25) + +* **Dependency Update**: Update to smithy-go v1.27.10. +* **Dependency Update**: Updated to the latest SDK module versions + +# v2.7.38 (2026-08-20) + +* **Dependency Update**: Updated to the latest SDK module versions + # v2.7.37 (2026-08-14) * **Dependency Update**: Update to smithy-go v1.27.8. diff --git a/vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/go_module_metadata.go b/vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/go_module_metadata.go index 3990207ed..6e5d8ea06 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/go_module_metadata.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/go_module_metadata.go @@ -3,4 +3,4 @@ package endpoints // goModuleVersion is the tagged release for this module -const goModuleVersion = "2.7.37" +const goModuleVersion = "2.7.39" diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/CHANGELOG.md b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/CHANGELOG.md index 5c2be888b..57f612b7b 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/CHANGELOG.md +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/CHANGELOG.md @@ -1,3 +1,14 @@ +# v1.323.0 (2026-08-25) + +* **Feature**: Fleet feature to support Capacity Reservation Resource Groups with Amazon EC2 Capacity Blocks and interruptible Capacity Reservations +* **Dependency Update**: Update to smithy-go v1.27.10. +* **Dependency Update**: Updated to the latest SDK module versions + +# v1.322.0 (2026-08-20) + +* **Feature**: EC2 marks UEFI instance metadata field as sensitive. +* **Dependency Update**: Updated to the latest SDK module versions + # v1.321.3 (2026-08-18) * **Documentation**: Doc release for CreateImage support for instances with local snapshots in Outpost diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_AssociateApplicationStatusCheck.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_AssociateApplicationStatusCheck.go index 18e042d65..224d067c6 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_AssociateApplicationStatusCheck.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_AssociateApplicationStatusCheck.go @@ -16,16 +16,18 @@ import ( // - You must specify either TargetTagAssociations or InstanceIds , but not both. // Specifying both results in an InvalidParameterCombination error. // -// - The application status check must already exist and belong to your account. +// - You must own the application status check. The check must already exist in +// your account. // -// - Tag keys must not be blank. +// - You must not leave tag keys blank. // -// - Maximum 50 tag associations per application status check. +// - You can create a maximum of 50 tag associations for each application status +// check. // -// - Use DisassociateApplicationStatusCheck to remove associations. +// - You can use DisassociateApplicationStatusCheck to remove associations. // -// - When you associate [tags], the application status check automatically monitors -// all current and future instances that have the specified tags. +// - You can associate [tags]so that the application status check automatically +// monitors all current and future instances that have the specified tags. // // [tags]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html func (c *Client) AssociateApplicationStatusCheck(ctx context.Context, params *AssociateApplicationStatusCheckInput, optFns ...func(*Options)) (*AssociateApplicationStatusCheckOutput, error) { @@ -50,8 +52,10 @@ type AssociateApplicationStatusCheckInput struct { // This member is required. ApplicationStatusCheckId *string - // Unique, case-sensitive identifier that you provide to ensure the idempotency of - // the request. For more information, see [Ensuring idempotency]. + // A unique, case-sensitive identifier that you provide to ensure that the + // operation completes no more than one time. If you retry a request with the same + // token, the service ignores the request but does not return an error. For more + // information, see [Ensuring idempotency]. // // [Ensuring idempotency]: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Run_Instance_Idempotency.html ClientToken *string diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_CreateApplicationStatusCheck.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_CreateApplicationStatusCheck.go index 47e71150a..f4ecd897e 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_CreateApplicationStatusCheck.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_CreateApplicationStatusCheck.go @@ -13,21 +13,22 @@ import ( // running on your instances. You can configure the protocol, port, path, and // thresholds for the health check. The following rules apply: // -// - You can create a maximum of 50 application status checks per account. +// - You can create a maximum of 50 application status checks for each account. // -// - Health checks do not start until you associate the check with instances or -// tags using AssociateApplicationStatusCheck . +// - You must associate the check with instances or tags using +// AssociateApplicationStatusCheck before health checks start. // -// - The Timeout value must be less than the Interval value. +// - You must set the Timeout value to less than the Interval value. // -// - The Path must start with a forward slash ( / ). Default: / . +// - You must start the Path with a forward slash ( / ). Default: / . // -// - If you do not specify Aggregation , it defaults to included , which means -// the check contributes to the instance-level application status. +// - You can specify Aggregation as included or excluded . If you do not specify +// a value, it defaults to included , which means the check contributes to the +// instance-level application status. // -// - Default values: Interval is 60 seconds, Timeout is 6 seconds, -// FailureThreshold is 2, SuccessThreshold is 2, StatusCodeMatcher is 200 , -// InitializationGracePeriodSeconds is 300 seconds. +// - You can use the following default values: Interval is 60 seconds, Timeout is +// 6 seconds, FailureThreshold is 2, SuccessThreshold is 2, StatusCodeMatcher is +// 200 , InitializationGracePeriodSeconds is 300 seconds. // // - You can tag the application status check during creation. For more // information, see [Tag your Amazon EC2 resources]. @@ -67,8 +68,10 @@ type CreateApplicationStatusCheckInput struct { // included | excluded . Aggregation types.AggregationStatusEnum - // Unique, case-sensitive identifier that you provide to ensure the idempotency of - // the request. For more information, see [Ensuring idempotency]. + // A unique, case-sensitive identifier that you provide to ensure that the + // operation completes no more than one time. If you retry a request with the same + // token, the service ignores the request but does not return an error. For more + // information, see [Ensuring idempotency]. // // [Ensuring idempotency]: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Run_Instance_Idempotency.html ClientToken *string diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DeleteApplicationStatusCheck.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DeleteApplicationStatusCheck.go index 8d13660f1..084b3bf01 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DeleteApplicationStatusCheck.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DeleteApplicationStatusCheck.go @@ -36,8 +36,10 @@ type DeleteApplicationStatusCheckInput struct { // This member is required. ApplicationStatusCheckId *string - // Unique, case-sensitive identifier that you provide to ensure the idempotency of - // the request. For more information, see [Ensuring idempotency]. + // A unique, case-sensitive identifier that you provide to ensure that the + // operation completes no more than one time. If you retry a request with the same + // token, the service ignores the request but does not return an error. For more + // information, see [Ensuring idempotency]. // // [Ensuring idempotency]: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Run_Instance_Idempotency.html ClientToken *string diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DescribeApplicationStatus.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DescribeApplicationStatus.go index e2a60ad50..75edf0ab6 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DescribeApplicationStatus.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DescribeApplicationStatus.go @@ -8,9 +8,8 @@ import ( "github.com/aws/smithy-go/middleware" ) -// Describes the application status for the specified instances. Returns the -// aggregated application health status for each instance. The following rules -// apply: +// Describes the aggregated application health status for the specified instances. +// The following rules apply: // // - The instance-level status is derived from all application status checks // with the aggregation setting set to included . diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DescribeApplicationStatusChecks.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DescribeApplicationStatusChecks.go index 5e13f5ace..0b345a529 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DescribeApplicationStatusChecks.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DescribeApplicationStatusChecks.go @@ -8,9 +8,10 @@ import ( "github.com/aws/smithy-go/middleware" ) -// Describes one or more application status checks. Returns configuration details -// for your application status checks, including protocol, port, path, thresholds, -// and associations. The following rules apply: +// Describes application status checks, including configuration details such as +// protocol, port, path, thresholds, and associations. Results are paginated. Use +// the NextToken parameter to retrieve additional results. The following rules +// apply: // // - If you do not specify any application status check IDs, all checks in your // account are returned. diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DisableApplicationStatusCheckSuppression.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DisableApplicationStatusCheckSuppression.go index 25b923643..363b22e79 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DisableApplicationStatusCheckSuppression.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DisableApplicationStatusCheckSuppression.go @@ -12,7 +12,7 @@ import ( // Disables suppression of application status checks for the specified instances. // After suppression is disabled, health check results resume affecting the // instance-level application status. You can specify a maximum of 100 instance IDs -// per request. +// for each request. func (c *Client) DisableApplicationStatusCheckSuppression(ctx context.Context, params *DisableApplicationStatusCheckSuppressionInput, optFns ...func(*Options)) (*DisableApplicationStatusCheckSuppressionOutput, error) { if params == nil { params = &DisableApplicationStatusCheckSuppressionInput{} @@ -30,8 +30,10 @@ func (c *Client) DisableApplicationStatusCheckSuppression(ctx context.Context, p type DisableApplicationStatusCheckSuppressionInput struct { - // Unique, case-sensitive identifier that you provide to ensure the idempotency of - // the request. For more information, see [Ensuring idempotency]. + // A unique, case-sensitive identifier that you provide to ensure that the + // operation completes no more than one time. If you retry a request with the same + // token, the service ignores the request but does not return an error. For more + // information, see [Ensuring idempotency]. // // [Ensuring idempotency]: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Run_Instance_Idempotency.html ClientToken *string diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DisassociateApplicationStatusCheck.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DisassociateApplicationStatusCheck.go index 022e323bf..d812ebeaa 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DisassociateApplicationStatusCheck.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_DisassociateApplicationStatusCheck.go @@ -43,8 +43,10 @@ type DisassociateApplicationStatusCheckInput struct { // This member is required. ApplicationStatusCheckId *string - // Unique, case-sensitive identifier that you provide to ensure the idempotency of - // the request. For more information, see [Ensuring idempotency]. + // A unique, case-sensitive identifier that you provide to ensure that the + // operation completes no more than one time. If you retry a request with the same + // token, the service ignores the request but does not return an error. For more + // information, see [Ensuring idempotency]. // // [Ensuring idempotency]: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Run_Instance_Idempotency.html ClientToken *string diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_EnableApplicationStatusCheckSuppression.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_EnableApplicationStatusCheckSuppression.go index c165a73a3..ed97cfbea 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_EnableApplicationStatusCheckSuppression.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_EnableApplicationStatusCheckSuppression.go @@ -13,7 +13,7 @@ import ( // suppressed, health checks continue to run but do not affect the instance-level // application status. The following rules apply: // -// - Maximum 100 instance IDs per request. +// - You can specify a maximum of 100 instance IDs for each request. // // - Use DisableApplicationStatusCheckSuppression to resume normal health check // reporting. @@ -37,8 +37,10 @@ func (c *Client) EnableApplicationStatusCheckSuppression(ctx context.Context, pa type EnableApplicationStatusCheckSuppressionInput struct { - // Unique, case-sensitive identifier that you provide to ensure the idempotency of - // the request. For more information, see [Ensuring idempotency]. + // A unique, case-sensitive identifier that you provide to ensure that the + // operation completes no more than one time. If you retry a request with the same + // token, the service ignores the request but does not return an error. For more + // information, see [Ensuring idempotency]. // // [Ensuring idempotency]: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Run_Instance_Idempotency.html ClientToken *string diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_ModifyApplicationStatusCheck.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_ModifyApplicationStatusCheck.go index cf746705a..c44efe515 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_ModifyApplicationStatusCheck.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/api_op_ModifyApplicationStatusCheck.go @@ -45,8 +45,10 @@ type ModifyApplicationStatusCheckInput struct { // included | excluded . Aggregation types.AggregationStatusEnum - // Unique, case-sensitive identifier that you provide to ensure the idempotency of - // the request. For more information, see [Ensuring idempotency]. + // A unique, case-sensitive identifier that you provide to ensure that the + // operation completes no more than one time. If you retry a request with the same + // token, the service ignores the request but does not return an error. For more + // information, see [Ensuring idempotency]. // // [Ensuring idempotency]: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Run_Instance_Idempotency.html ClientToken *string diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/deserializers.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/deserializers.go index fd53e66a1..4191b1d9d 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/deserializers.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/deserializers.go @@ -152966,6 +152966,128 @@ func awsEc2query_deserializeDocumentReservationValue(v **types.ReservationValue, return nil } +func awsEc2query_deserializeDocumentReservedCapacityFallbackMarketTypeList(v *[]types.ReservedCapacityFallbackMarketType, decoder smithyxml.NodeDecoder) error { + if v == nil { + return fmt.Errorf("unexpected nil of type %T", v) + } + var sv []types.ReservedCapacityFallbackMarketType + if *v == nil { + sv = make([]types.ReservedCapacityFallbackMarketType, 0) + } else { + sv = *v + } + + originalDecoder := decoder + for { + t, done, err := decoder.Token() + if err != nil { + return err + } + if done { + break + } + memberDecoder := smithyxml.WrapNodeDecoder(decoder.Decoder, t) + decoder = memberDecoder + switch { + case strings.EqualFold("item", t.Name.Local): + var col types.ReservedCapacityFallbackMarketType + val, err := decoder.Value() + if err != nil { + return err + } + if val == nil { + break + } + { + xtv := string(val) + col = types.ReservedCapacityFallbackMarketType(xtv) + } + sv = append(sv, col) + + default: + err = decoder.Decoder.Skip() + if err != nil { + return err + } + + } + decoder = originalDecoder + } + *v = sv + return nil +} + +func awsEc2query_deserializeDocumentReservedCapacityFallbackMarketTypeListUnwrapped(v *[]types.ReservedCapacityFallbackMarketType, decoder smithyxml.NodeDecoder) error { + var sv []types.ReservedCapacityFallbackMarketType + if *v == nil { + sv = make([]types.ReservedCapacityFallbackMarketType, 0) + } else { + sv = *v + } + + switch { + default: + var mv types.ReservedCapacityFallbackMarketType + t := decoder.StartEl + _ = t + val, err := decoder.Value() + if err != nil { + return err + } + if val == nil { + break + } + { + xtv := string(val) + mv = types.ReservedCapacityFallbackMarketType(xtv) + } + sv = append(sv, mv) + } + *v = sv + return nil +} +func awsEc2query_deserializeDocumentReservedCapacityFallbackOptions(v **types.ReservedCapacityFallbackOptions, decoder smithyxml.NodeDecoder) error { + if v == nil { + return fmt.Errorf("unexpected nil of type %T", v) + } + var sv *types.ReservedCapacityFallbackOptions + if *v == nil { + sv = &types.ReservedCapacityFallbackOptions{} + } else { + sv = *v + } + + for { + t, done, err := decoder.Token() + if err != nil { + return err + } + if done { + break + } + originalDecoder := decoder + decoder = smithyxml.WrapNodeDecoder(originalDecoder.Decoder, t) + switch { + case strings.EqualFold("marketTypeSet", t.Name.Local): + nodeDecoder := smithyxml.WrapNodeDecoder(decoder.Decoder, t) + if err := awsEc2query_deserializeDocumentReservedCapacityFallbackMarketTypeList(&sv.MarketTypes, nodeDecoder); err != nil { + return err + } + + default: + // Do nothing and ignore the unexpected tag element + err = decoder.Decoder.Skip() + if err != nil { + return err + } + + } + decoder = originalDecoder + } + *v = sv + return nil +} + func awsEc2query_deserializeDocumentReservedCapacityOptions(v **types.ReservedCapacityOptions, decoder smithyxml.NodeDecoder) error { if v == nil { return fmt.Errorf("unexpected nil of type %T", v) @@ -152988,12 +153110,31 @@ func awsEc2query_deserializeDocumentReservedCapacityOptions(v **types.ReservedCa originalDecoder := decoder decoder = smithyxml.WrapNodeDecoder(originalDecoder.Decoder, t) switch { + case strings.EqualFold("allocationStrategy", t.Name.Local): + val, err := decoder.Value() + if err != nil { + return err + } + if val == nil { + break + } + { + xtv := string(val) + sv.AllocationStrategy = types.ReservedCapacityAllocationStrategy(xtv) + } + case strings.EqualFold("reservationTypeSet", t.Name.Local): nodeDecoder := smithyxml.WrapNodeDecoder(decoder.Decoder, t) if err := awsEc2query_deserializeDocumentReservationTypeList(&sv.ReservationTypes, nodeDecoder); err != nil { return err } + case strings.EqualFold("reservedCapacityFallbackOptions", t.Name.Local): + nodeDecoder := smithyxml.WrapNodeDecoder(decoder.Decoder, t) + if err := awsEc2query_deserializeDocumentReservedCapacityFallbackOptions(&sv.ReservedCapacityFallbackOptions, nodeDecoder); err != nil { + return err + } + default: // Do nothing and ignore the unexpected tag element err = decoder.Decoder.Skip() diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/go_module_metadata.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/go_module_metadata.go index b48f62caf..9b7dbd815 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/go_module_metadata.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/go_module_metadata.go @@ -3,4 +3,4 @@ package ec2 // goModuleVersion is the tagged release for this module -const goModuleVersion = "1.321.3" +const goModuleVersion = "1.323.0" diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/serializers.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/serializers.go index cb9e5b8b4..d9dbb97d1 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/serializers.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/serializers.go @@ -56796,6 +56796,19 @@ func awsEc2query_serializeDocumentCapacityReservationOptionsRequest(v *types.Cap return nil } +func awsEc2query_serializeDocumentCapacityReservationResourceGroupArnSet(v []string, value query.Value) error { + if len(v) == 0 { + return nil + } + array := value.Array("Item") + + for i := range v { + av := array.Value() + av.String(v[i]) + } + return nil +} + func awsEc2query_serializeDocumentCapacityReservationSpecification(v *types.CapacityReservationSpecification, value query.Value) error { object := value.Object() _ = object @@ -58662,6 +58675,27 @@ func awsEc2query_serializeDocumentFleetBlockDeviceMappingRequestList(v []types.F return nil } +func awsEc2query_serializeDocumentFleetCapacityReservationTargetRequest(v *types.FleetCapacityReservationTargetRequest, value query.Value) error { + object := value.Object() + _ = object + + if v.CapacityReservationIds != nil { + objectKey := object.FlatKey("CapacityReservationId") + if err := awsEc2query_serializeDocumentCapacityReservationIdSet(v.CapacityReservationIds, objectKey); err != nil { + return err + } + } + + if v.CapacityReservationResourceGroupArns != nil { + objectKey := object.FlatKey("CapacityReservationResourceGroupArn") + if err := awsEc2query_serializeDocumentCapacityReservationResourceGroupArnSet(v.CapacityReservationResourceGroupArns, objectKey); err != nil { + return err + } + } + + return nil +} + func awsEc2query_serializeDocumentFleetEbsBlockDeviceRequest(v *types.FleetEbsBlockDeviceRequest, value query.Value) error { object := value.Object() _ = object @@ -64922,10 +64956,49 @@ func awsEc2query_serializeDocumentReservationTypeListRequest(v []types.FleetRese return nil } +func awsEc2query_serializeDocumentReservedCapacityFallbackMarketTypeList(v []types.ReservedCapacityFallbackMarketType, value query.Value) error { + if len(v) == 0 { + return nil + } + array := value.Array("Item") + + for i := range v { + av := array.Value() + av.String(string(v[i])) + } + return nil +} + +func awsEc2query_serializeDocumentReservedCapacityFallbackOptionsRequest(v *types.ReservedCapacityFallbackOptionsRequest, value query.Value) error { + object := value.Object() + _ = object + + if v.MarketTypes != nil { + objectKey := object.FlatKey("MarketType") + if err := awsEc2query_serializeDocumentReservedCapacityFallbackMarketTypeList(v.MarketTypes, objectKey); err != nil { + return err + } + } + + return nil +} + func awsEc2query_serializeDocumentReservedCapacityOptionsRequest(v *types.ReservedCapacityOptionsRequest, value query.Value) error { object := value.Object() _ = object + if len(v.AllocationStrategy) > 0 { + objectKey := object.Key("AllocationStrategy") + objectKey.String(string(v.AllocationStrategy)) + } + + if v.CapacityReservationTarget != nil { + objectKey := object.Key("CapacityReservationTarget") + if err := awsEc2query_serializeDocumentFleetCapacityReservationTargetRequest(v.CapacityReservationTarget, objectKey); err != nil { + return err + } + } + if v.ReservationTypes != nil { objectKey := object.FlatKey("ReservationType") if err := awsEc2query_serializeDocumentReservationTypeListRequest(v.ReservationTypes, objectKey); err != nil { @@ -64933,6 +65006,13 @@ func awsEc2query_serializeDocumentReservedCapacityOptionsRequest(v *types.Reserv } } + if v.ReservedCapacityFallbackOptions != nil { + objectKey := object.Key("ReservedCapacityFallbackOptions") + if err := awsEc2query_serializeDocumentReservedCapacityFallbackOptionsRequest(v.ReservedCapacityFallbackOptions, objectKey); err != nil { + return err + } + } + return nil } diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/types/enums.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/types/enums.go index da792c8d5..652dcbbeb 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/types/enums.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/types/enums.go @@ -3168,6 +3168,8 @@ type FleetReservationType string // Enum values for FleetReservationType const ( + FleetReservationTypeOnDemandCapacityReservation FleetReservationType = "on-demand-capacity-reservation" + FleetReservationTypeCapacityBlock FleetReservationType = "capacity-block" FleetReservationTypeInterruptibleCapacityReservation FleetReservationType = "interruptible-capacity-reservation" ) @@ -3177,6 +3179,8 @@ const ( // The ordering of this slice is not guaranteed to be stable across updates. func (FleetReservationType) Values() []FleetReservationType { return []FleetReservationType{ + "on-demand-capacity-reservation", + "capacity-block", "interruptible-capacity-reservation", } } @@ -9035,6 +9039,7 @@ const ( MarketTypeSpot MarketType = "spot" MarketTypeCapacityBlock MarketType = "capacity-block" MarketTypeInterruptibleCapacityReservation MarketType = "interruptible-capacity-reservation" + MarketTypeOnDemand MarketType = "on-demand" ) // Values returns all known values for MarketType. Note that this can be expanded @@ -9046,6 +9051,7 @@ func (MarketType) Values() []MarketType { "spot", "capacity-block", "interruptible-capacity-reservation", + "on-demand", } } @@ -10366,6 +10372,42 @@ func (ReservationType) Values() []ReservationType { } } +type ReservedCapacityAllocationStrategy string + +// Enum values for ReservedCapacityAllocationStrategy +const ( + ReservedCapacityAllocationStrategyPrioritized ReservedCapacityAllocationStrategy = "prioritized" +) + +// Values returns all known values for ReservedCapacityAllocationStrategy. Note +// that this can be expanded in the future, and so it is only as up to date as the +// client. +// +// The ordering of this slice is not guaranteed to be stable across updates. +func (ReservedCapacityAllocationStrategy) Values() []ReservedCapacityAllocationStrategy { + return []ReservedCapacityAllocationStrategy{ + "prioritized", + } +} + +type ReservedCapacityFallbackMarketType string + +// Enum values for ReservedCapacityFallbackMarketType +const ( + ReservedCapacityFallbackMarketTypeOnDemand ReservedCapacityFallbackMarketType = "on-demand" +) + +// Values returns all known values for ReservedCapacityFallbackMarketType. Note +// that this can be expanded in the future, and so it is only as up to date as the +// client. +// +// The ordering of this slice is not guaranteed to be stable across updates. +func (ReservedCapacityFallbackMarketType) Values() []ReservedCapacityFallbackMarketType { + return []ReservedCapacityFallbackMarketType{ + "on-demand", + } +} + type ReservedInstanceState string // Enum values for ReservedInstanceState diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/types/types.go b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/types/types.go index 1ab6143cc..3540c49f9 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/types/types.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/ec2/types/types.go @@ -3820,7 +3820,9 @@ type CreateFleetError struct { LaunchTemplateAndOverrides *LaunchTemplateAndOverridesResponse // Indicates if the instance that could not be launched was a Spot, On-Demand, - // Capacity Block, or Interruptible Capacity Reservation instance. + // Capacity Block for ML, or interruptible Capacity Reservation instance. If you + // are using ReservedCapacityOptions with on-demand-capacity-reservation in the + // ReservationTypes list, the value can also be on-demand-capacity-reservation . Lifecycle InstanceLifecycle noSmithyDocumentSerde @@ -3853,7 +3855,7 @@ type CreateFleetInstance struct { LaunchTemplateAndOverrides *LaunchTemplateAndOverridesResponse // Indicates if the instance that was launched is a Spot, On-Demand, Capacity - // Block, or Interruptible Capacity Reservation instance. + // Block for ML, or interruptible Capacity Reservation instance. Lifecycle InstanceLifecycle // The value is windows for Windows instances in an EC2 Fleet. Otherwise, the @@ -4634,7 +4636,9 @@ type DescribeFleetError struct { LaunchTemplateAndOverrides *LaunchTemplateAndOverridesResponse // Indicates if the instance that could not be launched was a Spot, On-Demand, - // Capacity Block, or Interruptible Capacity Reservation instance. + // Capacity Block for ML, or interruptible Capacity Reservation instance. If you + // are using ReservedCapacityOptions with on-demand-capacity-reservation in the + // ReservationTypes list, the value can also be on-demand-capacity-reservation . Lifecycle InstanceLifecycle noSmithyDocumentSerde @@ -4655,7 +4659,7 @@ type DescribeFleetsInstances struct { LaunchTemplateAndOverrides *LaunchTemplateAndOverridesResponse // Indicates if the instance that was launched is a Spot, On-Demand, Capacity - // Block, or Interruptible Capacity Reservation instance. + // Block for ML, or interruptible Capacity Reservation instance. Lifecycle InstanceLifecycle // The value is windows for Windows instances in an EC2 Fleet. Otherwise, the @@ -6620,6 +6624,22 @@ type FleetCapacityReservation struct { noSmithyDocumentSerde } +// Describes the target Capacity Reservations or Capacity Reservation Resource +// Groups for an EC2 Fleet that launches into reserved capacity. You can specify +// Capacity Reservation IDs or a Capacity Reservation Resource Group ARN, but not +// both. +type FleetCapacityReservationTargetRequest struct { + + // The IDs of the Capacity Reservations in which to launch the instances. + CapacityReservationIds []string + + // The ARNs of the Capacity Reservation Resource Groups in which to launch the + // instances. + CapacityReservationResourceGroupArns []string + + noSmithyDocumentSerde +} + // Describes an EC2 Fleet. type FleetData struct { @@ -19512,18 +19532,61 @@ type ReservationValue struct { noSmithyDocumentSerde } +// Describes the fallback behavior for an EC2 Fleet that uses reserved capacity +// when the reserved capacity is not enough to meet the target capacity. If you +// don't specify fallback options, EC2 Fleet does not fall back to any other market +// type after the specified reservation types are exhausted. +type ReservedCapacityFallbackOptions struct { + + // The instance purchasing options to fall back to when the reserved capacity is + // not enough to meet the target capacity. The only supported value is on-demand , + // which launches On-Demand Instances to fulfill the remaining target capacity. + MarketTypes []ReservedCapacityFallbackMarketType + + noSmithyDocumentSerde +} + +// Describes the fallback behavior for an EC2 Fleet that uses reserved capacity +// when the reserved capacity is not enough to meet the target capacity. If you +// don't specify fallback options, EC2 Fleet does not fall back to any other market +// type after the specified reservation types are exhausted. +type ReservedCapacityFallbackOptionsRequest struct { + + // The instance purchasing options to fall back to when the reserved capacity is + // not enough to meet the target capacity. The only supported value is on-demand , + // which launches On-Demand Instances to fulfill the remaining target capacity. + MarketTypes []ReservedCapacityFallbackMarketType + + noSmithyDocumentSerde +} + // Defines EC2 Fleet preferences for utilizing reserved capacity when -// DefaultTargetCapacityType is set to reserved-capacity . +// DefaultTargetCapacityType is set to reserved-capacity . EC2 Fleet can fulfill +// reserved capacity using On-Demand Capacity Reservations, Capacity Blocks for ML, +// and interruptible Capacity Reservations. type ReservedCapacityOptions struct { + // The strategy that determines the order in which EC2 Fleet launches instances + // across the reservation types that you specify. The only supported value is + // prioritized , which launches instances in the priority order that you specify in + // your launch template overrides. If you don't specify an allocation strategy, + // instances are launched in a random order. + AllocationStrategy ReservedCapacityAllocationStrategy + // The types of Capacity Reservations used for fulfilling the EC2 Fleet request. ReservationTypes []FleetReservationType + // The fallback behavior for the EC2 Fleet when there is not enough reserved + // capacity available to meet the target capacity. + ReservedCapacityFallbackOptions *ReservedCapacityFallbackOptions + noSmithyDocumentSerde } // Defines EC2 Fleet preferences for utilizing reserved capacity when -// DefaultTargetCapacityType is set to reserved-capacity . +// DefaultTargetCapacityType is set to reserved-capacity . EC2 Fleet can fulfill +// reserved capacity using On-Demand Capacity Reservations, Capacity Blocks for ML, +// and interruptible Capacity Reservations. // // This configuration can only be used if the EC2 Fleet is of type instant . // @@ -19531,15 +19594,36 @@ type ReservedCapacityOptions struct { // DefaultTargetCapacityType to reserved-capacity in the // TargetCapacitySpecification . // -// For more information about Interruptible Capacity Reservations, see [Launch instances into an Interruptible Capacity Reservation] in the +// For more information about interruptible Capacity Reservations, see [Launch instances into an interruptible Capacity Reservation] in the // Amazon EC2 User Guide. // -// [Launch instances into an Interruptible Capacity Reservation]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-fleet-launch-instances-interruptible-cr-walkthrough.html +// [Launch instances into an interruptible Capacity Reservation]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-fleet-launch-instances-interruptible-cr-walkthrough.html type ReservedCapacityOptionsRequest struct { + // The strategy that determines the order in which EC2 Fleet launches instances + // across the reservation types that you specify. The only supported value is + // prioritized , which launches instances in the priority order that you specify in + // your launch template overrides. If you don't specify an allocation strategy, + // instances are launched in a random order. + AllocationStrategy ReservedCapacityAllocationStrategy + + // The Capacity Reservations or Capacity Reservation Resource Groups to use for + // fulfilling the EC2 Fleet request. You can specify Capacity Reservation IDs or a + // Capacity Reservation Resource Group ARN, but not both. + CapacityReservationTarget *FleetCapacityReservationTargetRequest + // The types of Capacity Reservations to use for fulfilling the EC2 Fleet request. + // This is an ordered list: EC2 Fleet attempts to launch instances into each + // Capacity Reservation type in the order that you specify them before moving on to + // the next type. ReservationTypes []FleetReservationType + // The fallback behavior for the EC2 Fleet when there is not enough reserved + // capacity available to meet the target capacity. This member takes a + // ReservedCapacityFallbackOptionsRequest structure, in which you set MarketTypes + // to the instance purchasing options to fall back to. + ReservedCapacityFallbackOptions *ReservedCapacityFallbackOptionsRequest + noSmithyDocumentSerde } diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/CHANGELOG.md b/vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/CHANGELOG.md index bdb3895e8..34af7281b 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/CHANGELOG.md +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/CHANGELOG.md @@ -1,3 +1,7 @@ +# v1.13.18 (2026-08-25) + +* **Dependency Update**: Update to smithy-go v1.27.10. + # v1.13.17 (2026-08-14) * **Dependency Update**: Update to smithy-go v1.27.8. diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/go_module_metadata.go b/vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/go_module_metadata.go index 7c31cf92e..217c46705 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/go_module_metadata.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/go_module_metadata.go @@ -3,4 +3,4 @@ package acceptencoding // goModuleVersion is the tagged release for this module -const goModuleVersion = "1.13.17" +const goModuleVersion = "1.13.18" diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/CHANGELOG.md b/vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/CHANGELOG.md index 2886a705e..d36a51427 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/CHANGELOG.md +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/CHANGELOG.md @@ -1,3 +1,12 @@ +# v1.13.39 (2026-08-25) + +* **Dependency Update**: Update to smithy-go v1.27.10. +* **Dependency Update**: Updated to the latest SDK module versions + +# v1.13.38 (2026-08-20) + +* **Dependency Update**: Updated to the latest SDK module versions + # v1.13.37 (2026-08-14) * **Dependency Update**: Update to smithy-go v1.27.8. diff --git a/vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/go_module_metadata.go b/vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/go_module_metadata.go index 9ed94b8e2..c49c3bfa9 100644 --- a/vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/go_module_metadata.go +++ b/vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/go_module_metadata.go @@ -3,4 +3,4 @@ package presignedurl // goModuleVersion is the tagged release for this module -const goModuleVersion = "1.13.37" +const goModuleVersion = "1.13.39" diff --git a/vendor/github.com/aws/smithy-go/CHANGELOG.md b/vendor/github.com/aws/smithy-go/CHANGELOG.md index 88de0403e..2c9785d7b 100644 --- a/vendor/github.com/aws/smithy-go/CHANGELOG.md +++ b/vendor/github.com/aws/smithy-go/CHANGELOG.md @@ -1,3 +1,34 @@ +# Release (2026-08-25.2) + +## General Highlights +* **Dependency Update**: Updated to the latest SDK module versions + +## Module Highlights +* `github.com/aws/smithy-go`: v1.28.0 + * **Feature**: Set `Content-Length` inline when the request body is set via `SetStream`. The `ComputeContentLength` middleware is now deprecated. + +# Release (2026-08-25) + +## General Highlights +* **Dependency Update**: Updated to the latest SDK module versions + +## Module Highlights +* `github.com/aws/smithy-go`: v1.27.10 + * **Bug Fix**: Fix a data race on the underlying writer when an event stream is closed while an event write is in flight. + * **Bug Fix**: Restore draining the HTTP response body in `CloseResponseBody` to avoid issues with TCP connection reuse. + +# Release (2026-08-21) + +## General Highlights +* **Dependency Update**: Updated to the latest SDK module versions + +## Module Highlights +* `github.com/aws/smithy-go`: v1.27.9 + * **Bug Fix**: Fix a generic event stream exception not carrying the error code and message from its payload. + * **Bug Fix**: Fix an event stream not being closed when its connection is lost, which would cause a caller writing to the stream to block indefinitely. + * **Bug Fix**: Fix deserialization of an empty list producing a nil slice instead of an empty one. + * **Bug Fix**: Restore draining the HTTP response body in `CloseResponseBody` to avoid issues with TCP connection reuse. + # Release (2026-08-14) ## General Highlights diff --git a/vendor/github.com/aws/smithy-go/go_module_metadata.go b/vendor/github.com/aws/smithy-go/go_module_metadata.go index 005dac8f6..b42013edb 100644 --- a/vendor/github.com/aws/smithy-go/go_module_metadata.go +++ b/vendor/github.com/aws/smithy-go/go_module_metadata.go @@ -3,4 +3,4 @@ package smithy // goModuleVersion is the tagged release for this module -const goModuleVersion = "1.27.8" +const goModuleVersion = "1.28.0" diff --git a/vendor/github.com/aws/smithy-go/transport/http/eventstream.go b/vendor/github.com/aws/smithy-go/transport/http/eventstream.go index 251db8ac3..970ade30e 100644 --- a/vendor/github.com/aws/smithy-go/transport/http/eventstream.go +++ b/vendor/github.com/aws/smithy-go/transport/http/eventstream.go @@ -56,7 +56,10 @@ func NewEventStreamWriter(protocol ClientProtocol, schema *smithy.Schema, stream } func (w *EventStreamWriter) writeStream() { - defer w.Close() + defer func() { + w.err.SetError(w.eventStream.Close()) + }() + defer w.signalClose() for { select { @@ -100,11 +103,14 @@ func (w *EventStreamWriter) Send(ctx context.Context, variant *smithy.Schema, ev // Close signals end-of-stream and closes the underlying writer. Close is // safe for concurrent calls. func (w *EventStreamWriter) Close() error { + w.signalClose() + return w.err.Err() +} + +func (w *EventStreamWriter) signalClose() { w.closeOnce.Do(func() { close(w.done) - w.err.SetError(w.eventStream.Close()) }) - return w.err.Err() } // Err returns the first error encountered during writing. diff --git a/vendor/github.com/aws/smithy-go/transport/http/middleware_content_length.go b/vendor/github.com/aws/smithy-go/transport/http/middleware_content_length.go index 9969389bb..7a1c47fe3 100644 --- a/vendor/github.com/aws/smithy-go/transport/http/middleware_content_length.go +++ b/vendor/github.com/aws/smithy-go/transport/http/middleware_content_length.go @@ -14,6 +14,9 @@ type ComputeContentLength struct { // AddComputeContentLengthMiddleware adds ComputeContentLength to the middleware // stack's Build step. +// +// Deprecated: Content-Length is now set when the request body is set via +// Request.SetStream, so this middleware is no longer used. func AddComputeContentLengthMiddleware(stack *middleware.Stack) error { return stack.Build.Add(&ComputeContentLength{}, middleware.After) } diff --git a/vendor/github.com/aws/smithy-go/transport/http/request.go b/vendor/github.com/aws/smithy-go/transport/http/request.go index 5cbf6f10a..87acdfe57 100644 --- a/vendor/github.com/aws/smithy-go/transport/http/request.go +++ b/vendor/github.com/aws/smithy-go/transport/http/request.go @@ -118,6 +118,9 @@ func (r *Request) IsStreamSeekable() bool { // SetStream returns a clone of the request with the stream set to the provided // reader. May return an error if the provided reader is seekable but returns // an error. +// +// ContentLength is set to the stream's length when it can be determined, and +// left unchanged otherwise. func (r *Request) SetStream(reader io.Reader) (rc *Request, err error) { rc = r.Clone() @@ -151,6 +154,12 @@ func (r *Request) SetStream(reader io.Reader) (rc *Request, err error) { rc.isStreamSeekable = isStreamSeekable rc.streamStartPos = streamStartPos + if n, ok, err := rc.StreamLength(); err != nil { + return rc, err + } else if ok { + rc.ContentLength = n + } + return rc, err } diff --git a/vendor/github.com/charmbracelet/ultraviolet/.gitattributes b/vendor/github.com/charmbracelet/ultraviolet/.gitattributes new file mode 100644 index 000000000..6c929d480 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/.gitattributes @@ -0,0 +1 @@ +*.golden -text diff --git a/vendor/github.com/charmbracelet/ultraviolet/.golangci.yml b/vendor/github.com/charmbracelet/ultraviolet/.golangci.yml new file mode 100644 index 000000000..c90f03161 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/.golangci.yml @@ -0,0 +1,47 @@ +version: "2" +run: + tests: false +linters: + enable: + - bodyclose + - exhaustive + - goconst + - godot + - gomoddirectives + - goprintffuncname + - gosec + - misspell + - nakedret + - nestif + - nilerr + - noctx + - nolintlint + - prealloc + - revive + - rowserrcheck + - sqlclosecheck + - tparallel + - unconvert + - unparam + - whitespace + - wrapcheck + exclusions: + rules: + - text: '(slog|log)\.\w+' + linters: + - noctx + generated: lax + presets: + - common-false-positives + settings: + exhaustive: + default-signifies-exhaustive: true +issues: + max-issues-per-linter: 0 + max-same-issues: 0 +formatters: + enable: + - gofumpt + - goimports + exclusions: + generated: lax diff --git a/vendor/github.com/charmbracelet/ultraviolet/.goreleaser.yml b/vendor/github.com/charmbracelet/ultraviolet/.goreleaser.yml new file mode 100644 index 000000000..3353d0202 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/.goreleaser.yml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json +version: 2 +includes: + - from_url: + url: charmbracelet/meta/main/goreleaser-lib.yaml diff --git a/vendor/github.com/charmbracelet/ultraviolet/LICENSE b/vendor/github.com/charmbracelet/ultraviolet/LICENSE new file mode 100644 index 000000000..3217a2fb7 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Charmbracelet, Inc + +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. diff --git a/vendor/github.com/charmbracelet/ultraviolet/README.md b/vendor/github.com/charmbracelet/ultraviolet/README.md new file mode 100644 index 000000000..2163601da --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/README.md @@ -0,0 +1,125 @@ +# Ultraviolet + +Charm Ultraviolet + +

    + GoDoc + Build Status +

    + +Ultraviolet is a set of primitives for manipulating terminal emulators, with a focus on terminal user interfaces (TUIs). It provides a set of tools and abstractions for interaction that can handle user input and display dynamic, cell-based content. It’s the product of many years of research, development, collaboration and ingenuity. + +Ultraviolet is not a framework by design, however it can be used standalone to create powerful terminal applications. It’s in use in production and powers critical portions of [Bubble Tea v2][bbt] and [Lip Gloss v2][lg], and was instrumental in the development of [Crush][crush]. + +[crush]: https://github.com/charmbracelet/crush +[bbt]: https://github.com/charmbracelet/bubbletea +[lg]: https://github.com/charmbracelet/lipgloss + +## Features + +Ultraviolet is built with several core features in mind to make terminal +application development easy and performant: + +### 👺 The Cursed Renderer + +The cell-based rendering model—called _The Cursed Render_—was inspired by the infamous +[ncurses](https://invisible-island.net/ncurses/) library, which has been an +essential part of terminal applications for decades. Ultraviolet takes this +concept and modernizes it for the Go programming language, providing a more +ergonomic and efficient way to work with terminal cells without the need for +archaic technologies like `terminfo` or `termcap` databases. + +Unlike ncurses, it supports both full-window and inline use-cases as we see inline TUIs as important in maintaining user context and flow. + +### 🏎️ High Speeds and Low Bandwidth + +The built-in terminal renderer efficiently handles content updates by utilizing +a powerful cell-based diffing algorithm that minimizes the amount of data +written to the terminal using various ANSI escape sequences to accomplish this. +This allows applications to update only the parts of the terminal that have +changed, significantly improving performance and responsiveness. + +In practical terms, Ultraviolet optimizes for fast redraws that use minimal data transfer. This is very important locally and critically important over the network (for example, via SSH). + +### 💬 Universal Input + +Input handling in terminals can be complex, especially when dealing with +multiple input sources, different platforms, and ancient terminal baggage. +Ultraviolet simplifies this by providing a unified interface for handling user +input, allowing developers to focus on building their applications without +getting bogged down in the intricacies of terminal input handling. + +### 🎮 Cross-Platform Compatibility + +Ultraviolet is designed to work seamlessly across different platforms and +terminal emulators. It abstracts away the differences in terminal capabilities +and provides a consistent API for developers to work with, ensuring that +applications built with Ultraviolet will run smoothly on various systems. + +On Windows, it uses the [Windows Console API](https://learn.microsoft.com/en-us/windows/console/console-functions) to +provide a consistent experience, while on Unix-like systems, it relies on the +standard Termios API along with ANSI escape sequences to manipulate the +terminal. + +In short: Ultraviolet provides first-class support for both Unix and Windows-based systems. + +### 🧩 Extensible Architecture + +Ultraviolet is built with extensibility in mind, providing a solid API that can +be embedded into other applications or used as a foundation for building custom +terminal user interfaces. It allows developers to create their own components, +styles, and behaviors, making it a versatile tool for building terminal +applications. + +## FAQ + +### 🐈 What about other Charm libraries? + +Ultraviolet is not a replacement for existing libraries like [Bubble Tea](https://github.com/charmbracelet/bubbletea) or [Lip +Gloss](https://github.com/charmbracelet/lipgloss). Instead, it serves as a +foundation for the latest versions of both of these libraries and others like them, providing the +underlying primitives and abstractions needed to build terminal user interfaces +applications and frameworks. + +### 🛁 How is it different from Bubble Tea? + +Ultraviolet is a lower-level library that focuses on the core primitives of +terminal manipulation, rendering, and input handling. It provides the building +blocks for creating terminal applications, while Bubble Tea is a higher-level +framework that builds on top of Ultraviolet to provide a more structured and +opinionated way to build terminal user interfaces. + +### 💋 Is it a replacement for Lip Gloss? + +Simply put, no. Ultraviolet is not a replacement for Lip Gloss. Instead, it +provides the underlying rendering capabilities that Lip Gloss can use to create +styled terminal content. Lip Gloss is a higher-level library that builds on top +of Ultraviolet by utilizing the cell-based rendering model to provide a +simplified and ergonomic way to create styled terminal content and composition +of terminal user interfaces. + +## ✏️ Tutorial + +You can find a simple tutorial on how to create a UV application that displays +"Hello, World!" on the screen in the [TUTORIAL.md](./TUTORIAL.md) file. + +## Whatcha think? + +We’d love to hear your thoughts on this project. Feel free to drop us a note! + +- [Twitter](https://twitter.com/charmcli) +- [Discord](https://charm.land/discord) +- [Slack](https://charm.land/slack) +- [The Fediverse](https://mastodon.social/@charmcli) + +## License + +[MIT](./LICENSE) + +--- + +Part of [Charm](https://charm.land). + +The Charm logo + +Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة diff --git a/vendor/github.com/charmbracelet/ultraviolet/TUTORIAL.md b/vendor/github.com/charmbracelet/ultraviolet/TUTORIAL.md new file mode 100644 index 000000000..655617213 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/TUTORIAL.md @@ -0,0 +1,207 @@ +# Hello World! + +This is a simple tutorial on how to create a UV application that displays +"Hello, World!" on the screen. UV is a terminal UI toolkit that allows you to +create terminal applications with ease. It provides a simple API to handle +screen management, input handling, and rendering content. + +## Tutorial + +What does UV consist of? A UV application consists of a screen that displays +content, has some sort of input sources that can be used to interact with the +screen, and is meant to display content or programs on the screen. + +First, we need to create a screen that will display our program. UV comes with +a `Terminal` screen that is used to display content on a terminal. + +```go +t := uv.NewTerminal(os.Stdin, os.Stdout, os.Environ()) +// Or simply use... +// t := uv.DefaultTerminal() +``` + +A terminal screen has a few properties that are unique to it. For example, a +terminal screen can go into raw mode, which is important to disable echoing of +input characters, and to disable signal handling so that we can receive things +like ctrl+c without the terminal interfering with our program. + +Another important property of a terminal screen is the alternate screen buffer. +This property puts the terminal screen into a special mode that allows us to +display content without interfering with the normal screen buffer. + +In this tutorial, we will use the alternate screen buffer to display our +program so that we don't affect the normal screen buffer. + +```go +// Set the terminal to raw mode. +if err := t.MakeRaw(); err != nil { + log.Fatal(err) +} + +// Enter the alternate screen buffer. This will +// only take affect once we flush or display +// our program on the terminal screen. +t.EnterAltScreen() + +// My program +// ... + +// Make sure we leave the alternate screen buffer when we +// are done with our program. This will be called automatically +// when we use [t.Shutdown(ctx)] later. +t.LeaveAltScreen() + +// Make sure we restore the terminal to its original state +// before we exit. We don't care about errors here, but you +// can handle them if you want. This will be called automatically +// when we use [t.Shutdown(ctx)] later. +_ = t.Restore() //nolint:errcheck +``` + +Now that we have our screen set to raw mode and in the alternate screen buffer, +we can create our program that will be displayed on the screen. A program is an +abstraction layer that handles different screen types and implementations. It +only cares about displaying content on the screen. + +We need to start our program before we can display anything on the screen. This +will ensure that the program and screen are initialized and ready to display +content. Internally, this will also call `t.Start()` to start the terminal +screen. + +```go +if err := t.Start(); err != nil { + log.Fatalf("failed to start program: %v", err) +} +``` + +Let's display a simple frame with some text in it. A frame is a container that +holds the buffer we're displaying. The final cursor position we want our cursor +to be at, and the viewport area we are working with to display our content. + +```go +for i, r := range "Hello, World!" { + // We iterate over the string to display each character + // in a separate cell. Ideally, we want each cell + // to have exactly one grapheme. In this case, since + // we're using a simple ASCII string, we know that + // each character is a single grapheme with a width of 1. + var c uv.Cell + c.Content = string(r) + c.Width = 1 + t.SetCell(i, 0, &c) +} +// Now we simply render the changes and flush them +// to the terminal screen. +_ = p.Display() +``` + +Different screen models have different ways to receive input. Some models have +a remote control, while others have a touch screen. A terminal can receive +input from various peripherals usually through control codes and escape +sequences. Our terminal has a `t.Events(ctx)` method that returns a channel +which will receive events from different terminal input sources. + +```go +// We want to be able to stop the terminal input loop +// whenever we call cancel(). +ctx, cancel := context.WithCancel(context.Background()) +defer cancel() + +for ev := range t.Events(ctx) { + switch ev := ev.(type) { + case uv.WindowSizeEvent: + // Our terminal screen is resizable. This is important + // as we want to inform our terminal screen with the + // size we'd like to display our program in. + // When we're using the full terminal window size, + // we can assume that the terminal screen will + // also have the same size as our program. + // However, with inline programs, usually we want + // the height to be the height of our program. + // So if our inline program takes 10 lines, we + // want to resize the terminal screen to 10 lines + // high. + width, height := ev.Width, ev.Height + if !altscreen { + height = 10 + } + t.Resize(width, height) + case uv.KeyPressEvent: + if ev.MatchStrings("q", "ctrl+c") { + // This will stop the input loop and cancel the context. + cancel() + } + } +} +``` + +Now that we've handled displaying our program and receiving input from the +terminal, we need to handle the program's lifecycle. We need to make sure that +we restore the terminal to its original state when we exit the program. A +terminal program can be stopped gracefully using the `t.Shutdown(ctx)` method. + +```go +// We need to make sure we stop the program gracefully +// after we exit the input loop. +if err := t.Shutdown(ctx); err != nil { + log.Fatal(err) +} +``` + +Finally, let's put everything together and create a simple program that displays +a frame with "Hello, World!" in it. The program will exit when we press +ctrl+c or q. + +```go +func main() { + t := uv.NewTerminal(os.Stdin, os.Stdout, os.Environ()) + + if err := t.MakeRaw(); err != nil { + log.Fatalf("failed to make terminal raw: %v", err) + } + + if err := t.Start(); err != nil { + log.Fatalf("failed to start program: %v", err) + } + + t.EnterAltScreen() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + for ev := range t.Events(ctx) { + switch ev := ev.(type) { + case uv.WindowSizeEvent: + width, height := ev.Width, ev.Height + t.Erase() + t.Resize(width, height) + case uv.KeyPressEvent: + if ev.MatchStrings("q", "ctrl+c") { + cancel() + } + } + + for i, r := range "Hello, World!" { + var c uv.Cell + c.Content = string(r) + c.Width = 1 + t.SetCell(i, 0, &c) + } + if err := t.Display(); err != nil { + log.Fatal(err) + } + } + + if err := t.Shutdown(ctx); err != nil { + log.Fatal(err) + } +} +``` + +--- + +Part of [Charm](https://charm.sh). + +The Charm logo + +Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة diff --git a/vendor/github.com/charmbracelet/ultraviolet/border.go b/vendor/github.com/charmbracelet/ultraviolet/border.go new file mode 100644 index 000000000..e24c9a060 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/border.go @@ -0,0 +1,231 @@ +package uv + +// NormalBorder returns a standard-type border with a normal weight and 90 +// degree corners. +func NormalBorder() Border { + return Border{ + Top: Side{Content: "─"}, + Bottom: Side{Content: "─"}, + Left: Side{Content: "│"}, + Right: Side{Content: "│"}, + TopLeft: Side{Content: "┌"}, + TopRight: Side{Content: "┐"}, + BottomLeft: Side{Content: "└"}, + BottomRight: Side{Content: "┘"}, + } +} + +// RoundedBorder returns a border with rounded corners. +func RoundedBorder() Border { + return Border{ + Top: Side{Content: "─"}, + Bottom: Side{Content: "─"}, + Left: Side{Content: "│"}, + Right: Side{Content: "│"}, + TopLeft: Side{Content: "╭"}, + TopRight: Side{Content: "╮"}, + BottomLeft: Side{Content: "╰"}, + BottomRight: Side{Content: "╯"}, + } +} + +// BlockBorder returns a border that takes the whole block. +func BlockBorder() Border { + return Border{ + Top: Side{Content: "█"}, + Bottom: Side{Content: "█"}, + Left: Side{Content: "█"}, + Right: Side{Content: "█"}, + TopLeft: Side{Content: "█"}, + TopRight: Side{Content: "█"}, + BottomLeft: Side{Content: "█"}, + BottomRight: Side{Content: "█"}, + } +} + +// OuterHalfBlockBorder returns a half-block border that sits outside the frame. +func OuterHalfBlockBorder() Border { + return Border{ + Top: Side{Content: "▀"}, + Bottom: Side{Content: "▄"}, + Left: Side{Content: "▌"}, + Right: Side{Content: "▐"}, + TopLeft: Side{Content: "▛"}, + TopRight: Side{Content: "▜"}, + BottomLeft: Side{Content: "▙"}, + BottomRight: Side{Content: "▟"}, + } +} + +// InnerHalfBlockBorder returns a half-block border that sits inside the frame. +func InnerHalfBlockBorder() Border { + return Border{ + Top: Side{Content: "▄"}, + Bottom: Side{Content: "▀"}, + Left: Side{Content: "▐"}, + Right: Side{Content: "▌"}, + TopLeft: Side{Content: "▗"}, + TopRight: Side{Content: "▖"}, + BottomLeft: Side{Content: "▝"}, + BottomRight: Side{Content: "▘"}, + } +} + +// ThickBorder returns a border that's thicker than the one returned by +// NormalBorder. +func ThickBorder() Border { + return Border{ + Top: Side{Content: "━"}, + Bottom: Side{Content: "━"}, + Left: Side{Content: "┃"}, + Right: Side{Content: "┃"}, + TopLeft: Side{Content: "┏"}, + TopRight: Side{Content: "┓"}, + BottomLeft: Side{Content: "┗"}, + BottomRight: Side{Content: "┛"}, + } +} + +// DoubleBorder returns a border comprised of two thin strokes. +func DoubleBorder() Border { + return Border{ + Top: Side{Content: "═"}, + Bottom: Side{Content: "═"}, + Left: Side{Content: "║"}, + Right: Side{Content: "║"}, + TopLeft: Side{Content: "╔"}, + TopRight: Side{Content: "╗"}, + BottomLeft: Side{Content: "╚"}, + BottomRight: Side{Content: "╝"}, + } +} + +// HiddenBorder returns a border that renders as a series of single-cell +// spaces. It's useful for cases when you want to remove a standard border but +// maintain layout positioning. This said, you can still apply a background +// color to a hidden border. +func HiddenBorder() Border { + return Border{ + Top: Side{Content: " "}, + Bottom: Side{Content: " "}, + Left: Side{Content: " "}, + Right: Side{Content: " "}, + TopLeft: Side{Content: " "}, + TopRight: Side{Content: " "}, + BottomLeft: Side{Content: " "}, + BottomRight: Side{Content: " "}, + } +} + +// MarkdownBorder return a table border in markdown style. +func MarkdownBorder() Border { + return Border{ + Left: Side{Content: "|"}, + Right: Side{Content: "|"}, + TopLeft: Side{Content: "|"}, + TopRight: Side{Content: "|"}, + BottomLeft: Side{Content: "|"}, + BottomRight: Side{Content: "|"}, + } +} + +// ASCIIBorder returns a table border with ASCII characters. +func ASCIIBorder() Border { + return Border{ + Top: Side{Content: "-"}, + Bottom: Side{Content: "-"}, + Left: Side{Content: "|"}, + Right: Side{Content: "|"}, + TopLeft: Side{Content: "+"}, + TopRight: Side{Content: "+"}, + BottomLeft: Side{Content: "+"}, + BottomRight: Side{Content: "+"}, + } +} + +// Side represents a single border side with its properties. +type Side struct { + Content string + Style + Link +} + +// Border represents a border with its properties. +type Border struct { + Top Side + Bottom Side + Left Side + Right Side + TopLeft Side + TopRight Side + BottomLeft Side + BottomRight Side +} + +// Style returns a new [Border] with the given style applied to all [Side]s. +func (b Border) Style(style Style) Border { + b.Top.Style = style + b.Bottom.Style = style + b.Left.Style = style + b.Right.Style = style + b.TopLeft.Style = style + b.TopRight.Style = style + b.BottomLeft.Style = style + b.BottomRight.Style = style + return b +} + +// Link returns a new [Border] with the given link applied to all [Side]s. +func (b Border) Link(link Link) Border { + b.Top.Link = link + b.Bottom.Link = link + b.Left.Link = link + b.Right.Link = link + b.TopLeft.Link = link + b.TopRight.Link = link + b.BottomLeft.Link = link + b.BottomRight.Link = link + return b +} + +// Draw draws the border around the given component. +func (b *Border) Draw(scr Screen, area Rectangle) { + for y := area.Min.Y; y < area.Max.Y; y++ { + for x := area.Min.X; x < area.Max.X; x++ { + var cell *Cell + switch { + case y == area.Min.Y && x == area.Min.X: + cell = borderCell(scr, &b.TopLeft) + case y == area.Min.Y && x == area.Max.X-1: + cell = borderCell(scr, &b.TopRight) + case y == area.Max.Y-1 && x == area.Min.X: + cell = borderCell(scr, &b.BottomLeft) + case y == area.Max.Y-1 && x == area.Max.X-1: + cell = borderCell(scr, &b.BottomRight) + case y == area.Min.Y: + cell = borderCell(scr, &b.Top) + case y == area.Max.Y-1: + cell = borderCell(scr, &b.Bottom) + case x == area.Min.X: + cell = borderCell(scr, &b.Left) + case x == area.Max.X-1: + cell = borderCell(scr, &b.Right) + default: + continue + } + if cell == nil { + continue + } + scr.SetCell(x, y, cell) + } + } +} + +func borderCell(scr Screen, b *Side) *Cell { + c := NewCell(scr.WidthMethod(), b.Content) + if c != nil { + c.Style = b.Style + c.Link = b.Link + } + return c +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/buffer.go b/vendor/github.com/charmbracelet/ultraviolet/buffer.go new file mode 100644 index 000000000..f88164d88 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/buffer.go @@ -0,0 +1,795 @@ +package uv + +import ( + "bytes" + "image" + "io" + "strings" + + "github.com/charmbracelet/x/ansi" +) + +// Position represents a position in a coordinate system. +type Position = image.Point + +// Pos is a shorthand for creating a new [Position]. +func Pos(x, y int) Position { + return Position{X: x, Y: y} +} + +// Rectangle represents a rectangular area. +type Rectangle = image.Rectangle + +// Rect is a shorthand for creating a new [Rectangle]. +func Rect(x, y, w, h int) Rectangle { + return Rectangle{Min: image.Point{X: x, Y: y}, Max: image.Point{X: x + w, Y: y + h}} +} + +// Line represents cells in a line. +type Line []Cell + +// NewLine creates a new line with the given width, filled with empty cells. +func NewLine(width int) Line { + l := make(Line, width) + for i := range l { + l[i] = EmptyCell + } + return l +} + +// Set sets the cell at the given x position. +func (l Line) Set(x int, c *Cell) { + lineWidth := len(l) + if x < 0 || x >= lineWidth { + return + } + + // When a wide cell is partially overwritten, we need + // to fill the rest of the cell with space cells to + // avoid rendering issues. + var prev *Cell + if prev = l.At(x); prev != nil { //nolint:nestif + if pw := prev.Width; pw > 1 { + // Writing to the first wide cell + for j := 0; j < pw && x+j < lineWidth; j++ { + l[x+j] = *prev + l[x+j].Empty() + } + } else if pw == 0 { + // Writing to wide cell placeholders + for j := 1; x-j >= 0; j++ { + if wide := l.At(x - j); wide != nil { + if ww := wide.Width; ww > 1 && j < ww { + for k := range ww { + l[x-j+k] = *wide + l[x-j+k].Empty() + } + break + } + } + } + } + } + + if c == nil { + // Nil cells are treated as blank empty cells. + l[x] = EmptyCell + return + } + + l[x] = *c + cw := c.Width + if x+cw > lineWidth { + // If the cell is too wide, we write blanks with the same style. + for i := 0; i < cw && x+i < lineWidth; i++ { + l[x+i] = *c + l[x+i].Empty() + } + return + } + + if cw > 1 { + // Mark wide cells with an zero cells. + // We set the wide cell down below + for j := 1; j < cw && x+j < lineWidth; j++ { + l[x+j] = Cell{} + } + } +} + +// At returns the cell at the given x position. +// If the cell does not exist, it returns nil. +func (l Line) At(x int) *Cell { + if x < 0 || x >= len(l) { + return nil + } + + return &l[x] +} + +// String returns the string representation of the line. Any trailing spaces +// are removed. +func (l Line) String() string { + var buf strings.Builder + var pending bytes.Buffer + for _, c := range l { + if c.IsZero() { + continue + } + if c.Equal(&EmptyCell) { + pending.WriteByte(' ') + continue + } + if pending.Len() > 0 { + buf.WriteString(pending.String()) + pending.Reset() + } + buf.WriteString(c.String()) + } + + return buf.String() +} + +// Render renders the line to a string with all the required attributes and +// styles. +func (l Line) Render() string { + var buf strings.Builder + renderLine(&buf, l) + return buf.String() +} + +func renderLine(buf io.StringWriter, l Line) { + var pen Style + var link Link + var pending bytes.Buffer + + for _, c := range l { + if c.IsZero() { + continue + } + if c.Equal(&EmptyCell) { + if !pen.IsZero() { + _, _ = buf.WriteString(ansi.ResetStyle) + pen = Style{} + } + if !link.IsZero() { + _, _ = buf.WriteString(ansi.ResetHyperlink()) + link = Link{} + } + pending.WriteByte(' ') + continue + } + + if pending.Len() > 0 { + _, _ = buf.WriteString(pending.String()) + pending.Reset() + } + + if c.Style.IsZero() && !pen.IsZero() { + _, _ = buf.WriteString(ansi.ResetStyle) + pen = Style{} + } + if !c.Style.Equal(&pen) { + seq := c.Style.Diff(&pen) + _, _ = buf.WriteString(seq) + pen = c.Style + } + + // Write the URL escape sequence + if c.Link != link && link.URL != "" { + _, _ = buf.WriteString(ansi.ResetHyperlink()) + link = Link{} + } + if c.Link != link { + _, _ = buf.WriteString(ansi.SetHyperlink(c.Link.URL, c.Link.Params)) + link = c.Link + } + + _, _ = buf.WriteString(c.String()) + } + + if link.URL != "" { + _, _ = buf.WriteString(ansi.ResetHyperlink()) + } + if !pen.IsZero() { + _, _ = buf.WriteString(ansi.ResetStyle) + } +} + +// Lines represents a slice of lines. +type Lines []Line + +// Height returns the height of the lines. +func (ls Lines) Height() int { + return len(ls) +} + +// Width returns the width of the widest line. +func (ls Lines) Width() int { + maxWidth := 0 + for _, l := range ls { + maxWidth = max(maxWidth, len(l)) + } + return maxWidth +} + +// String returns the string representation of the lines. +func (ls Lines) String() string { + var buf strings.Builder + for i, l := range ls { + buf.WriteString(l.String()) + if i < len(ls)-1 { + _ = buf.WriteByte('\n') + } + } + return buf.String() +} + +// Render renders the lines to a styled string with all the required attributes +// and styles. +func (ls Lines) Render() string { + var buf strings.Builder + for i, l := range ls { + renderLine(&buf, l) + if i < len(ls)-1 { + _ = buf.WriteByte('\n') + } + } + return buf.String() +} + +// Buffer represents a cell buffer that contains the contents of a screen. +type Buffer struct { + // Lines is a slice of lines that make up the cells of the buffer. + Lines []Line +} + +var _ Drawable = (*Buffer)(nil) + +// NewBuffer creates a new buffer with the given width and height. +// This is a convenience function that initializes a new buffer and resizes it. +func NewBuffer(width int, height int) *Buffer { + b := new(Buffer) + b.Lines = make([]Line, height) + for i := range b.Lines { + b.Lines[i] = make(Line, width) + for j := range b.Lines[i] { + b.Lines[i][j] = EmptyCell + } + } + b.Resize(width, height) + return b +} + +// String returns the string representation of the buffer. +func (b *Buffer) String() string { + return Lines(b.Lines).String() +} + +// Render renders the buffer to a styled string with all the required +// attributes and styles. +func (b *Buffer) Render() string { + return Lines(b.Lines).Render() +} + +// Line returns a pointer to the line at the given y position. +// If the line does not exist, it returns nil. +func (b *Buffer) Line(y int) Line { + if y < 0 || y >= len(b.Lines) { + return nil + } + return b.Lines[y] +} + +// CellAt returns the cell at the given position. It returns nil if the +// position is out of bounds. +func (b *Buffer) CellAt(x int, y int) *Cell { + if y < 0 || y >= len(b.Lines) { + return nil + } + return b.Lines[y].At(x) +} + +// SetCell sets the cell at the given x, y position. +func (b *Buffer) SetCell(x, y int, c *Cell) { + if y < 0 || y >= len(b.Lines) { + return + } + + b.Lines[y].Set(x, c) +} + +// Height implements Screen. +func (b *Buffer) Height() int { + return len(b.Lines) +} + +// Width implements Screen. +func (b *Buffer) Width() int { + if len(b.Lines) == 0 { + return 0 + } + return len(b.Lines[0]) +} + +// Bounds returns the bounds of the buffer. +// The origin is always at (0, 0) and the maximum coordinates are determined by +// the width and height of the buffer. +func (b *Buffer) Bounds() Rectangle { + return Rect(0, 0, b.Width(), b.Height()) +} + +// Resize resizes the buffer to the given width and height. +func (b *Buffer) Resize(width int, height int) { + curWidth, curHeight := b.Width(), b.Height() + if curWidth == width && curHeight == height { + // No need to resize if the dimensions are the same. + return + } + + if width > curWidth { + line := make(Line, width-curWidth) + for i := range line { + line[i] = EmptyCell + } + for i := range b.Lines { + b.Lines[i] = append(b.Lines[i], line...) + } + } else if width < curWidth { + for i := range b.Lines { + b.Lines[i] = b.Lines[i][:width] + } + } + + if height > len(b.Lines) { + for i := len(b.Lines); i < height; i++ { + line := make(Line, width) + for j := range line { + line[j] = EmptyCell + } + b.Lines = append(b.Lines, line) + } + } else if height < len(b.Lines) { + b.Lines = b.Lines[:height] + } +} + +// Fill fills the buffer with the given cell and rectangle. +func (b *Buffer) Fill(c *Cell) { + b.FillArea(c, b.Bounds()) +} + +// FillArea fills the buffer with the given cell and rectangle. +func (b *Buffer) FillArea(c *Cell, area Rectangle) { + cellWidth := 1 + if c != nil && c.Width > 1 { + cellWidth = c.Width + } + for y := area.Min.Y; y < area.Max.Y; y++ { + for x := area.Min.X; x < area.Max.X; x += cellWidth { + b.SetCell(x, y, c) + } + } +} + +// Clear clears the buffer with space cells and rectangle. +func (b *Buffer) Clear() { + area := b.Bounds() + for y := area.Min.Y; y < area.Max.Y; y++ { + for x := area.Min.X; x < area.Max.X; x++ { + b.Lines[y][x] = EmptyCell + } + } +} + +// ClearArea clears the buffer with space cells within the specified +// rectangles. Only cells within the rectangle's bounds are affected. +func (b *Buffer) ClearArea(area Rectangle) { + b.FillArea(nil, area) +} + +// CloneArea clones the area of the buffer within the specified rectangle. If +// the area is out of bounds, it returns nil. +func (b *Buffer) CloneArea(area Rectangle) *Buffer { + bounds := b.Bounds() + if !area.In(bounds) { + return nil + } + n := NewBuffer(area.Dx(), area.Dy()) + for y := area.Min.Y; y < area.Max.Y; y++ { + for x := area.Min.X; x < area.Max.X; { + c := b.CellAt(x, y) + if c == nil || c.IsZero() { + x++ + continue + } + n.SetCell(x-area.Min.X, y-area.Min.Y, c) + x += max(c.Width, 1) + } + } + return n +} + +// Clone clones the entire buffer into a new buffer. +func (b *Buffer) Clone() *Buffer { + return b.CloneArea(b.Bounds()) +} + +// Draw draws the buffer to the given screen at the specified area. +// It implements the [Drawable] interface. +func (b *Buffer) Draw(scr Screen, area Rectangle) { + if area.Empty() { + return + } + + // Ensure the area is within the bounds of the screen. + bounds := scr.Bounds() + if !area.Overlaps(bounds) { + return + } + + for y := area.Min.Y; y < area.Max.Y; y++ { + for x := area.Min.X; x < area.Max.X; { + c := b.CellAt(x-area.Min.X, y-area.Min.Y) + if c == nil || c.IsZero() { + x++ + continue + } + scr.SetCell(x, y, c) + width := c.Width + if width <= 0 { + width = 1 + } + x += width + } + } +} + +// InsertLine inserts n lines at the given line position, with the given +// optional cell, within the specified rectangles. If no rectangles are +// specified, it inserts lines in the entire buffer. Only cells within the +// rectangle's horizontal bounds are affected. Lines are pushed out of the +// rectangle bounds and lost. This follows terminal [ansi.IL] behavior. +// It returns the pushed out lines. +func (b *Buffer) InsertLine(y, n int, c *Cell) { + b.InsertLineArea(y, n, c, b.Bounds()) +} + +// InsertLineArea inserts new lines at the given line position, with the +// given optional cell, within the rectangle bounds. Only cells within the +// rectangle's horizontal bounds are affected. Lines are pushed out of the +// rectangle bounds and lost. This follows terminal [ansi.IL] behavior. +func (b *Buffer) InsertLineArea(y, n int, c *Cell, area Rectangle) { + if n <= 0 || y < area.Min.Y || y >= area.Max.Y || y >= b.Height() { + return + } + + // Limit number of lines to insert to available space + if y+n > area.Max.Y { + n = area.Max.Y - y + } + + // Move existing lines down within the bounds + for i := area.Max.Y - 1; i >= y+n; i-- { + for x := area.Min.X; x < area.Max.X; x++ { + // We don't need to clone c here because we're just moving lines down. + b.Lines[i][x] = b.Lines[i-n][x] + } + } + + // Clear the newly inserted lines within bounds + for i := y; i < y+n; i++ { + for x := area.Min.X; x < area.Max.X; x++ { + b.SetCell(x, i, c) + } + } +} + +// DeleteLineArea deletes lines at the given line position, with the given +// optional cell, within the rectangle bounds. Only cells within the +// rectangle's bounds are affected. Lines are shifted up within the bounds and +// new blank lines are created at the bottom. This follows terminal [ansi.DL] +// behavior. +func (b *Buffer) DeleteLineArea(y, n int, c *Cell, area Rectangle) { + if n <= 0 || y < area.Min.Y || y >= area.Max.Y || y >= b.Height() { + return + } + + // Limit deletion count to available space in scroll region + if n > area.Max.Y-y { + n = area.Max.Y - y + } + + // Shift cells up within the bounds + for dst := y; dst < area.Max.Y-n; dst++ { + src := dst + n + for x := area.Min.X; x < area.Max.X; x++ { + // We don't need to clone c here because we're just moving cells up. + b.Lines[dst][x] = b.Lines[src][x] + } + } + + // Fill the bottom n lines with blank cells + for i := area.Max.Y - n; i < area.Max.Y; i++ { + for x := area.Min.X; x < area.Max.X; x++ { + b.SetCell(x, i, c) + } + } +} + +// DeleteLine deletes n lines at the given line position, with the given +// optional cell, within the specified rectangles. If no rectangles are +// specified, it deletes lines in the entire buffer. +func (b *Buffer) DeleteLine(y, n int, c *Cell) { + b.DeleteLineArea(y, n, c, b.Bounds()) +} + +// InsertCell inserts new cells at the given position, with the given optional +// cell, within the specified rectangles. If no rectangles are specified, it +// inserts cells in the entire buffer. This follows terminal [ansi.ICH] +// behavior. +func (b *Buffer) InsertCell(x, y, n int, c *Cell) { + b.InsertCellArea(x, y, n, c, b.Bounds()) +} + +// InsertCellArea inserts new cells at the given position, with the given +// optional cell, within the rectangle bounds. Only cells within the +// rectangle's bounds are affected, following terminal [ansi.ICH] behavior. +func (b *Buffer) InsertCellArea(x, y, n int, c *Cell, area Rectangle) { + if n <= 0 || y < area.Min.Y || y >= area.Max.Y || y >= b.Height() || + x < area.Min.X || x >= area.Max.X || x >= b.Width() { + return + } + + // Limit number of cells to insert to available space + if x+n > area.Max.X { + n = area.Max.X - x + } + + // Move existing cells within rectangle bounds to the right + for i := area.Max.X - 1; i >= x+n && i-n >= area.Min.X; i-- { + // We don't need to clone c here because we're just moving cells to the + // right. + b.Lines[y][i] = b.Lines[y][i-n] + } + + // Clear the newly inserted cells within rectangle bounds + for i := x; i < x+n && i < area.Max.X; i++ { + b.SetCell(i, y, c) + } +} + +// DeleteCell deletes cells at the given position, with the given optional +// cell, within the specified rectangles. If no rectangles are specified, it +// deletes cells in the entire buffer. This follows terminal [ansi.DCH] +// behavior. +func (b *Buffer) DeleteCell(x, y, n int, c *Cell) { + b.DeleteCellArea(x, y, n, c, b.Bounds()) +} + +// DeleteCellArea deletes cells at the given position, with the given +// optional cell, within the rectangle bounds. Only cells within the +// rectangle's bounds are affected, following terminal [ansi.DCH] behavior. +func (b *Buffer) DeleteCellArea(x, y, n int, c *Cell, area Rectangle) { + if n <= 0 || y < area.Min.Y || y >= area.Max.Y || y >= b.Height() || + x < area.Min.X || x >= area.Max.X || x >= b.Width() { + return + } + + // Calculate how many positions we can actually delete + remainingCells := area.Max.X - x + if n > remainingCells { + n = remainingCells + } + + // Shift the remaining cells to the left + for i := x; i < area.Max.X-n; i++ { + if i+n < area.Max.X { + // We need to use SetCell here to ensure we blank out any wide + // cells we encounter. + b.SetCell(i, y, b.CellAt(i+n, y)) + } + } + + // Fill the vacated positions with the given cell + for i := area.Max.X - n; i < area.Max.X; i++ { + b.SetCell(i, y, c) + } +} + +// ScreenBuffer is a buffer that can be used as a [Screen]. +type ScreenBuffer struct { + *RenderBuffer + Method ansi.Method +} + +var ( + _ Screen = ScreenBuffer{} + _ Drawable = ScreenBuffer{} +) + +// NewScreenBuffer creates a new ScreenBuffer with the given width and height. +func NewScreenBuffer(width, height int) ScreenBuffer { + return ScreenBuffer{ + RenderBuffer: NewRenderBuffer(width, height), + Method: ansi.WcWidth, + } +} + +// WidthMethod returns the width method used by the screen. +// It defaults to [ansi.WcWidth]. +func (s ScreenBuffer) WidthMethod() WidthMethod { + return s.Method +} + +// TrimSpace trims trailing spaces from the end of each line in the given +// string. +func TrimSpace(s string) string { + lines := strings.Split(s, "\n") + for i, line := range lines { + // Check if we have a trailing '\r' and preserve it + hasCR := strings.HasSuffix(line, "\r") + if hasCR { + line = strings.TrimSuffix(line, "\r") + } + line = strings.TrimRight(line, " ") + if hasCR { + line = line + "\r" + } + lines[i] = line + } + return strings.Join(lines, "\n") +} + +// RenderBuffer represents a buffer that keeps track of the current and new +// state of the screen, allowing for efficient rendering by only updating the +// parts of the screen that have changed. +type RenderBuffer struct { + *Buffer + Touched []*LineData +} + +// NewRenderBuffer creates a new [RenderBuffer] with the given width and height. +func NewRenderBuffer(width, height int) *RenderBuffer { + return &RenderBuffer{ + Buffer: NewBuffer(width, height), + Touched: make([]*LineData, height), + } +} + +// TouchLine marks a line n times starting at the given x position as touched. +func (b *RenderBuffer) TouchLine(x, y, n int) { + if y < 0 || y >= len(b.Lines) { + return + } + + if y >= len(b.Touched) { + b.Touched = append(b.Touched, make([]*LineData, y-len(b.Touched)+1)...) + } + + // Re-check bounds: a concurrent resize may have cleared Touched + if y >= len(b.Touched) { + return + } + + ch := b.Touched[y] + if ch == nil { + ch = &LineData{FirstCell: x, LastCell: x + n} + } else { + ch.FirstCell = min(ch.FirstCell, x) + ch.LastCell = max(ch.LastCell, x+n) + } + b.Touched[y] = ch +} + +// Touch marks the cell at the given x, y position as touched. +func (b *RenderBuffer) Touch(x, y int) { + b.TouchLine(x, y, 0) +} + +// TouchedLines returns the number of touched lines in the buffer. +func (b *RenderBuffer) TouchedLines() int { + if b.Touched == nil { + return 0 + } + count := 0 + for _, t := range b.Touched { + if t != nil { + count++ + } + } + return count +} + +// SetCell sets the cell at the given x, y position and marks the line as +// touched. +func (b *RenderBuffer) SetCell(x, y int, c *Cell) { + if !cellEqual(b.CellAt(x, y), c) { + width := 1 + if c != nil && c.Width > 0 { + width = c.Width + } + b.TouchLine(x, y, width) + } + b.Buffer.SetCell(x, y, c) +} + +// InsertLine inserts n lines at the given line position, with the given +// optional cell, within the specified rectangles. If no rectangles are +// specified, it inserts lines in the entire buffer. Only cells within the +// rectangle's horizontal bounds are affected. Lines are pushed out of the +// rectangle bounds and lost. This follows terminal [ansi.IL] behavior. +func (b *RenderBuffer) InsertLine(y, n int, c *Cell) { + b.InsertLineArea(y, n, c, b.Bounds()) +} + +// InsertLineArea inserts new lines at the given line position, with the given +// optional cell, within the rectangle bounds. Only cells within the +// rectangle's horizontal bounds are affected. Lines are pushed out of the +// rectangle bounds and lost. This follows terminal [ansi.IL] behavior. +func (b *RenderBuffer) InsertLineArea(y, n int, c *Cell, area Rectangle) { + b.Buffer.InsertLineArea(y, n, c, area) + for i := area.Min.Y; i < area.Max.Y; i++ { + b.TouchLine(area.Min.X, i, area.Max.X-area.Min.X) + b.TouchLine(area.Min.X, i-n, area.Max.X-area.Min.X) + } +} + +// DeleteLine deletes n lines at the given line position, with the given +// optional cell, within the specified rectangles. If no rectangles are +// specified, it deletes lines in the entire buffer. +func (b *RenderBuffer) DeleteLine(y, n int, c *Cell) { + b.DeleteLineArea(y, n, c, b.Bounds()) +} + +// DeleteLineArea deletes lines at the given line position, with the given +// optional cell, within the rectangle bounds. Only cells within the +// rectangle's bounds are affected. Lines are shifted up within the bounds and +// new blank lines are created at the bottom. This follows terminal [ansi.DL] +// behavior. +func (b *RenderBuffer) DeleteLineArea(y, n int, c *Cell, area Rectangle) { + b.Buffer.DeleteLineArea(y, n, c, area) + for i := area.Min.Y; i < area.Max.Y; i++ { + b.TouchLine(area.Min.X, i, area.Max.X-area.Min.X) + b.TouchLine(area.Min.X, i+n, area.Max.X-area.Min.X) + } +} + +// InsertCell inserts new cells at the given position, with the given optional +// cell, within the specified rectangles. If no rectangles are specified, it +// inserts cells in the entire buffer. This follows terminal [ansi.ICH] +// behavior. +func (b *RenderBuffer) InsertCell(x, y, n int, c *Cell) { + b.InsertCellArea(x, y, n, c, b.Bounds()) +} + +// InsertCellArea inserts new cells at the given position, with the given +// optional cell, within the rectangle bounds. Only cells within the +// rectangle's bounds are affected, following terminal [ansi.ICH] behavior. +func (b *RenderBuffer) InsertCellArea(x, y, n int, c *Cell, area Rectangle) { + b.Buffer.InsertCellArea(x, y, n, c, area) + if x+n > area.Max.X { + n = area.Max.X - x + } + b.TouchLine(x, y, n) +} + +// DeleteCell deletes cells at the given position, with the given optional +// cell, within the specified rectangles. If no rectangles are specified, it +// deletes cells in the entire buffer. This follows terminal [ansi.DCH] +// behavior. +func (b *RenderBuffer) DeleteCell(x, y, n int, c *Cell) { + b.DeleteCellArea(x, y, n, c, b.Bounds()) +} + +// DeleteCellArea deletes cells at the given position, with the given +// optional cell, within the rectangle bounds. Only cells within the +// rectangle's bounds are affected, following terminal [ansi.DCH] behavior. +func (b *RenderBuffer) DeleteCellArea(x, y, n int, c *Cell, area Rectangle) { + b.Buffer.DeleteCellArea(x, y, n, c, area) + remainingCells := area.Max.X - x + if n > remainingCells { + n = remainingCells + } + b.TouchLine(x, y, n) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/cancelreader_other.go b/vendor/github.com/charmbracelet/ultraviolet/cancelreader_other.go new file mode 100644 index 000000000..3fb3c95ce --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/cancelreader_other.go @@ -0,0 +1,16 @@ +//go:build !windows +// +build !windows + +package uv + +import ( + "io" + + "github.com/muesli/cancelreader" +) + +// NewCancelReader creates a new [cancelreader.CancelReader] that provides a +// cancelable reader interface that can be used to cancel reads. +func NewCancelReader(r io.Reader) (cancelreader.CancelReader, error) { + return cancelreader.NewReader(r) //nolint:wrapcheck +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/cancelreader_windows.go b/vendor/github.com/charmbracelet/ultraviolet/cancelreader_windows.go new file mode 100644 index 000000000..fdc2db584 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/cancelreader_windows.go @@ -0,0 +1,139 @@ +//go:build windows +// +build windows + +package uv + +import ( + "fmt" + "io" + "os" + "sync" + + xwindows "github.com/charmbracelet/x/windows" + "github.com/muesli/cancelreader" + "golang.org/x/sys/windows" +) + +type conInputReader struct { + cancelMixin + conin windows.Handle + originalMode uint32 + newMode uint32 +} + +var _ cancelreader.CancelReader = &conInputReader{} + +// NewCancelReader creates a new [cancelreader.CancelReader] that provides a +// cancelable reader interface that can be used to cancel reads. +func NewCancelReader(r io.Reader) (cancelreader.CancelReader, error) { + fallback := func(io.Reader) (cancelreader.CancelReader, error) { + return cancelreader.NewReader(r) + } + + var dummy uint32 + if f, ok := r.(cancelreader.File); !ok || f.Fd() != os.Stdin.Fd() || + // If data was piped to the standard input, it does not emit events + // anymore. We can detect this if the console mode cannot be set anymore, + // in this case, we fallback to the default cancelreader implementation. + windows.GetConsoleMode(windows.Handle(f.Fd()), &dummy) != nil { + return fallback(r) + } + + conin, err := windows.GetStdHandle(windows.STD_INPUT_HANDLE) + if err != nil { + return fallback(r) + } + + // Discard any pending input events. + if err := xwindows.FlushConsoleInputBuffer(conin); err != nil { + return fallback(r) + } + + modes := []uint32{ + windows.ENABLE_VIRTUAL_TERMINAL_INPUT, + windows.ENABLE_WINDOW_INPUT, + windows.ENABLE_EXTENDED_FLAGS, + } + + originalMode, newMode, err := prepareConsole(conin, modes...) + if err != nil { + return nil, fmt.Errorf("failed to prepare console input: %w", err) + } + + return &conInputReader{ + conin: conin, + originalMode: originalMode, + newMode: newMode, + }, nil +} + +// Cancel implements cancelreader.CancelReader. +func (r *conInputReader) Cancel() bool { + r.setCanceled() + + return windows.CancelIoEx(r.conin, nil) == nil || windows.CancelIo(r.conin) == nil +} + +// Close implements cancelreader.CancelReader. +func (r *conInputReader) Close() error { + if r.originalMode != 0 { + err := windows.SetConsoleMode(r.conin, r.originalMode) + if err != nil { + return fmt.Errorf("reset console mode: %w", err) + } + } + + return nil +} + +// Read implements cancelreader.CancelReader. +func (r *conInputReader) Read(data []byte) (int, error) { + if r.isCanceled() { + return 0, cancelreader.ErrCanceled + } + + var n uint32 + if err := windows.ReadFile(r.conin, data, &n, nil); err != nil { + return int(n), fmt.Errorf("read console input: %w", err) + } + + return int(n), nil +} + +func prepareConsole(input windows.Handle, modes ...uint32) (originalMode, newMode uint32, err error) { + err = windows.GetConsoleMode(input, &originalMode) + if err != nil { + return 0, 0, fmt.Errorf("get console mode: %w", err) + } + + for _, mode := range modes { + newMode |= mode + } + + err = windows.SetConsoleMode(input, newMode) + if err != nil { + return 0, 0, fmt.Errorf("set console mode: %w", err) + } + + return originalMode, newMode, nil +} + +// cancelMixin represents a goroutine-safe cancelation status. +type cancelMixin struct { + unsafeCanceled bool + lock sync.Mutex +} + +func (c *cancelMixin) setCanceled() { + c.lock.Lock() + defer c.lock.Unlock() + + c.unsafeCanceled = true +} + +func (c *cancelMixin) isCanceled() bool { + c.lock.Lock() + defer c.lock.Unlock() + + return c.unsafeCanceled +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/cell.go b/vendor/github.com/charmbracelet/ultraviolet/cell.go new file mode 100644 index 000000000..ea89b553a --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/cell.go @@ -0,0 +1,459 @@ +package uv + +import ( + "image/color" + "strings" + + "github.com/charmbracelet/colorprofile" + "github.com/charmbracelet/x/ansi" +) + +// EmptyCell is a cell with a single space, width of 1, and no style or link. +var EmptyCell = Cell{Content: " ", Width: 1} + +// Cell represents a single cell in the terminal screen. +type Cell struct { + // Content is the [Cell]'s content, which consists of a single grapheme + // cluster. Most of the time, this will be a single rune as well, but it + // can also be a combination of runes that form a grapheme cluster. + Content string + + // The style of the cell. Nil style means no style. Zero value prints a + // reset sequence. + Style Style + + // Link is the hyperlink of the cell. + Link Link + + // Width is the mono-spaced width of the grapheme cluster. + Width int +} + +// NewCell creates a new cell from the given string grapheme. It will only use +// the first grapheme in the string and ignore the rest. The width of the cell +// is determined using the given width method. +func NewCell(method WidthMethod, gr string) *Cell { + if len(gr) == 0 { + return &Cell{} + } + if gr == " " { + return EmptyCell.Clone() + } + return &Cell{ + Content: gr, + Width: method.StringWidth(gr), + } +} + +// String returns the string content of the cell excluding any styles, links, +// and escape sequences. +func (c *Cell) String() string { + return c.Content +} + +// Equal returns whether the cell is equal to the other cell. +func (c *Cell) Equal(o *Cell) bool { + return o != nil && + c.Width == o.Width && + c.Content == o.Content && + c.Style.Equal(&o.Style) && + c.Link.Equal(&o.Link) +} + +// IsZero returns whether the cell is an empty cell. +func (c *Cell) IsZero() bool { + return *c == Cell{} +} + +// Clone returns a copy of the cell. +func (c *Cell) Clone() (n *Cell) { + n = new(Cell) + *n = *c + return +} + +// Empty makes the cell an empty cell by setting its content to a single space +// and width to 1. +func (c *Cell) Empty() { + c.Content = " " + c.Width = 1 +} + +// NewLink creates a new hyperlink with the given URL and parameters. +func NewLink(url string, params ...string) Link { + return Link{ + URL: url, + Params: strings.Join(params, ":"), + } +} + +// Link represents a hyperlink in the terminal screen. +type Link struct { + URL string + Params string +} + +// String returns a string representation of the hyperlink. +func (h *Link) String() string { + return h.URL +} + +// Equal returns whether the hyperlink is equal to the other hyperlink. +func (h *Link) Equal(o *Link) bool { + return o != nil && *h == *o +} + +// IsZero returns whether the hyperlink is empty. +func (h *Link) IsZero() bool { + return *h == Link{} +} + +// These are the available text attributes that can be combined to create +// different styles. +const ( + AttrBold = 1 << iota + AttrFaint + AttrItalic + AttrBlink + AttrRapidBlink // Not widely supported + AttrReverse + AttrConceal + AttrStrikethrough + + AttrReset = 0 +) + +// AttrSlowBlink is an alias for AttrBlink. +// +// Deprecated: Use [AttrBlink] instead. +const ( + AttrSlowBlink = AttrBlink +) + +// Underline is the style of underline to use for text. +type Underline = ansi.Underline + +// UnderlineStyle is the style of underline to use for text. +// +// Deprecated: Use [Underline] instead. +type UnderlineStyle = ansi.Underline + +// These are the available underline styles. +const ( + UnderlineNone = ansi.UnderlineNone + UnderlineSingle = ansi.UnderlineSingle + UnderlineDouble = ansi.UnderlineDouble + UnderlineCurly = ansi.UnderlineCurly + UnderlineDotted = ansi.UnderlineDotted + UnderlineDashed = ansi.UnderlineDashed +) + +// These are the available underline styles. +// +// Deprecated: Use the constants from [Underline] instead. +const ( + UnderlineStyleNone = ansi.UnderlineNone + UnderlineStyleSingle = ansi.UnderlineSingle + UnderlineStyleDouble = ansi.UnderlineDouble + UnderlineStyleCurly = ansi.UnderlineCurly + UnderlineStyleDotted = ansi.UnderlineDotted + UnderlineStyleDashed = ansi.UnderlineDashed +) + +// Style represents the style of a cell. +type Style struct { + Fg color.Color + Bg color.Color + UnderlineColor color.Color + Underline Underline + Attrs uint8 +} + +// Equal returns true if the style is equal to the other style. +func (s *Style) Equal(o *Style) bool { + return s.Attrs == o.Attrs && + s.Underline == o.Underline && + colorEqual(s.Fg, o.Fg) && + colorEqual(s.Bg, o.Bg) && + colorEqual(s.UnderlineColor, o.UnderlineColor) +} + +// Styled wraps the given string with the style's ANSI sequences and resets. +func (s *Style) Styled(str string) string { + if s.IsZero() { + return str + } + return s.String() + str + ansi.ResetStyle +} + +// String returns the ANSI SGR sequence for the style. +func (s *Style) String() string { + if s.IsZero() { + return ansi.ResetStyle + } + + var b ansi.Style + + if s.Attrs != 0 { //nolint:nestif + if s.Attrs&AttrBold != 0 { + b = b.Bold() + } + if s.Attrs&AttrFaint != 0 { + b = b.Faint() + } + if s.Attrs&AttrItalic != 0 { + b = b.Italic(true) + } + if s.Attrs&AttrBlink != 0 { + b = b.Blink(true) + } + if s.Attrs&AttrRapidBlink != 0 { + b = b.RapidBlink(true) + } + if s.Attrs&AttrReverse != 0 { + b = b.Reverse(true) + } + if s.Attrs&AttrConceal != 0 { + b = b.Conceal(true) + } + if s.Attrs&AttrStrikethrough != 0 { + b = b.Strikethrough(true) + } + } + if s.Underline != UnderlineStyleNone { + switch s.Underline { + case UnderlineStyleSingle: + b = b.Underline(true) + case UnderlineStyleDouble: + b = b.UnderlineStyle(UnderlineStyleDouble) + case UnderlineStyleCurly: + b = b.UnderlineStyle(UnderlineStyleCurly) + case UnderlineStyleDotted: + b = b.UnderlineStyle(UnderlineStyleDotted) + case UnderlineStyleDashed: + b = b.UnderlineStyle(UnderlineStyleDashed) + } + } + if s.Fg != nil { + b = b.ForegroundColor(s.Fg) + } + if s.Bg != nil { + b = b.BackgroundColor(s.Bg) + } + if s.UnderlineColor != nil { + b = b.UnderlineColor(s.UnderlineColor) + } + + return b.String() +} + +// Diff returns the ANSI sequence that sets the style as a diff from +// another style. +func (s *Style) Diff(from *Style) string { + return StyleDiff(from, s) +} + +// StyleDiff returns the SGR ANSI sequence necessary to transition from the +// "from" style to the "to" style. +func StyleDiff(from, to *Style) string { + if from == nil && to == nil { + return "" + } + if from != nil && to != nil && from.Equal(to) { + return "" + } + if from == nil { + return to.String() + } + if to == nil || to.IsZero() { + // Resetting all styles is cheaper than calculating diffs. + // "\x1b[m" is 3 bytes vs potentially much longer sequences. + return ansi.ResetStyle + } + + // TODO: Optimize further by checking if a full reset is cheaper than + // calculating diffs. Often, it might be cheaper to reset everything and + // then set the desired styles rather than calculating diffs. Also more + // compatible with terminals that have buggy SGR implementations. + + var b ansi.Style + + if !colorEqual(from.Fg, to.Fg) { + b = b.ForegroundColor(to.Fg) + } + + if !colorEqual(from.Bg, to.Bg) { + b = b.BackgroundColor(to.Bg) + } + + if !colorEqual(from.UnderlineColor, to.UnderlineColor) { + // TODO: Investigate this. For backward compatibility, we might want to + // set this at the end instead. Because on terminals that don't support + // underline color, this might mess up some other attributes if set in + // the middle. + b = b.UnderlineColor(to.UnderlineColor) + } + + fromBold := from.Attrs&AttrBold != 0 + fromFaint := from.Attrs&AttrFaint != 0 + fromItalic := from.Attrs&AttrItalic != 0 + fromUnderline := from.Underline != UnderlineStyleNone + fromBlink := from.Attrs&AttrBlink != 0 + fromRapidBlink := from.Attrs&AttrRapidBlink != 0 + fromReverse := from.Attrs&AttrReverse != 0 + fromConceal := from.Attrs&AttrConceal != 0 + fromStrikethrough := from.Attrs&AttrStrikethrough != 0 + toBold := to.Attrs&AttrBold != 0 + toFaint := to.Attrs&AttrFaint != 0 + toItalic := to.Attrs&AttrItalic != 0 + toUnderline := to.Underline != UnderlineStyleNone + toBlink := to.Attrs&AttrBlink != 0 + toRapidBlink := to.Attrs&AttrRapidBlink != 0 + toReverse := to.Attrs&AttrReverse != 0 + toConceal := to.Attrs&AttrConceal != 0 + toStrikethrough := to.Attrs&AttrStrikethrough != 0 + + // We perform the resets first since they are single attributes and + // shouldn't interfere with others being set. + + boldChanged := fromBold != toBold + faintChanged := fromFaint != toFaint + if boldChanged || faintChanged { + if fromBold && !toBold || fromFaint && !toFaint { + b = b.Normal() + boldChanged = true + faintChanged = true + } + } + + italicChanged := fromItalic != toItalic + if italicChanged && !toItalic { + b = b.Italic(false) + } + + underlineChanged := fromUnderline != toUnderline || from.Underline != to.Underline + if underlineChanged && !toUnderline { + b = b.Underline(false) + } + + blinkChanged := fromBlink != toBlink + rapidBlinkChanged := fromRapidBlink != toRapidBlink + if blinkChanged || rapidBlinkChanged { + if fromBlink && !toBlink || fromRapidBlink && !toRapidBlink { + b = b.Blink(false) + blinkChanged = true + rapidBlinkChanged = true + } + } + + reverseChanged := fromReverse != toReverse + if reverseChanged && !toReverse { + b = b.Reverse(false) + } + + concealChanged := fromConceal != toConceal + if concealChanged && !toConceal { + b = b.Conceal(false) + } + + strikethroughChanged := fromStrikethrough != toStrikethrough + if strikethroughChanged && !toStrikethrough { + b = b.Strikethrough(false) + } + + if boldChanged && toBold { + b = b.Bold() + } + + if faintChanged && toFaint { + b = b.Faint() + } + + if italicChanged && toItalic { + b = b.Italic(true) + } + + if underlineChanged && toUnderline && to.Underline == UnderlineStyleSingle { + // We only handle single underline here since others require more + // specific handling at the end. + b = b.Underline(true) + } + + if blinkChanged && toBlink { + b = b.Blink(true) + } + + if rapidBlinkChanged && toRapidBlink { + b = b.RapidBlink(true) + } + + if reverseChanged && toReverse { + b = b.Reverse(true) + } + + if concealChanged && toConceal { + b = b.Conceal(true) + } + + if strikethroughChanged && toStrikethrough { + b = b.Strikethrough(true) + } + + // Handle special underline styles. + if underlineChanged && toUnderline && to.Underline > UnderlineStyleSingle { + b = b.UnderlineStyle(to.Underline) + } + + return b.String() +} + +func colorEqual(c, o color.Color) bool { + if c == nil && o == nil { + return true + } + if c == nil || o == nil { + return false + } + cr, cg, cb, ca := c.RGBA() + or, og, ob, oa := o.RGBA() + return cr == or && cg == og && cb == ob && ca == oa +} + +// IsZero returns true if the style is empty. +func (s *Style) IsZero() bool { + return *s == Style{} +} + +// ConvertStyle converts a style to respect the given color profile. +func ConvertStyle(s Style, p colorprofile.Profile) Style { + switch p { + case colorprofile.TrueColor: + return s + case colorprofile.ANSI, colorprofile.ANSI256: + case colorprofile.Ascii: + s.Fg = nil + s.Bg = nil + s.UnderlineColor = nil + case colorprofile.NoTTY: + return Style{} + } + + if s.Fg != nil { + s.Fg = p.Convert(s.Fg) + } + if s.Bg != nil { + s.Bg = p.Convert(s.Bg) + } + if s.UnderlineColor != nil { + s.UnderlineColor = p.Convert(s.UnderlineColor) + } + return s +} + +// ConvertLink converts a hyperlink to respect the given color profile. +func ConvertLink(h Link, p colorprofile.Profile) Link { + if p == colorprofile.NoTTY { + return Link{} + } + return h +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/console.go b/vendor/github.com/charmbracelet/ultraviolet/console.go new file mode 100644 index 000000000..7ef37fd60 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/console.go @@ -0,0 +1,219 @@ +package uv + +import ( + "io" + "os" + "runtime" + + "github.com/charmbracelet/x/term" +) + +var isWindows = runtime.GOOS == "windows" + +// File is an interface that represents a file with a file descriptor. +// +// This is typically an [os.File] like [os.Stdin] and [os.Stdout]. +type File interface { + io.ReadWriteCloser + term.File + + // Name returns the name of the file. + Name() string +} + +// Winsize represents the size of a terminal in cells and pixels. +// +// This is the same as [unix.Winsize], but defined here for cross-platform compatibility. +type Winsize struct { + Row uint16 + Col uint16 + Xpixel uint16 + Ypixel uint16 +} + +// Console represents a cross-platform console I/O interface. +type Console interface { + io.ReadWriteCloser + + // Environ returns the console's environment variables. + Environ() []string + + // Getenv retrieves the value of the environment variable named by the key. + Getenv(key string) string + + // LookupEnv retrieves the value of the environment variable named by the key + // and a boolean indicating whether the variable is present. + LookupEnv(key string) (string, bool) + + // Reader returns the input reader of the console. + Reader() io.Reader + + // Writer returns the output writer of the console. + Writer() io.Writer + + // MakeRaw puts the console input side into raw mode. + MakeRaw() (state *term.State, err error) + + // Restore restores the console to its previous state. + Restore() error + + // GetSize returns the current size of the console. + GetSize() (width, height int, err error) + + // GetWinsize returns the current size of the console in cells and pixels. + GetWinsize() (*Winsize, error) +} + +// TTY represents a Unix TTY device. It implements the [Console] interface. +type TTY struct { + *console +} + +var _ Console = (*TTY)(nil) + +// WinCon represents a Windows Console. It implements the [Console] interface. +type WinCon struct { + *console +} + +var _ Console = (*WinCon)(nil) + +// Console is a cross-platform console I/O. +type console struct { + input File + inputState *term.State + output File + outputState *term.State + environ Environ +} + +// DefaultConsole returns a new default console instance that uses standard I/O +// [os.Stdin], [os.Stdout], and [os.Environ]. +// +// To use [os.Stderr] as the output, you can create a new console with +// [NewConsole] and pass [os.Stderr] as the output parameter. +func DefaultConsole() Console { + return NewConsole(os.Stdin, os.Stdout, os.Environ()) +} + +// ControllingConsole returns a new console instance that uses the current +// controlling terminal's input and output file descriptors. +func ControllingConsole() (Console, error) { + inTty, outTty, err := OpenTTY() + if err != nil { + return nil, err + } + return NewConsole(inTty, outTty, os.Environ()), nil +} + +// NewConsole creates a new [Console] with the given input, output, and +// environment variables. +// +// You can use [OpenTTY] to open the current controlling console files and pass +// them to this function. Use [ControllingConsole] for a convenience function +// that does this for you. +// +// Use this to create a new terminal for PTY processes by passing the PTY slave +// file as the input and output and any environment variables the process +// needs. +func NewConsole(input, output File, environ []string) Console { + if input == nil { + input = os.Stdin + } + if output == nil { + output = os.Stdout + } + if environ == nil { + environ = os.Environ() + } + return newConsole(input, output, environ) +} + +// Environ returns the console's environment variables. +func (t *console) Environ() []string { + return t.environ +} + +// Writer returns the output writer of the console. +func (t *console) Writer() io.Writer { + return t.output +} + +// Reader returns the input reader of the console. +func (t *console) Reader() io.Reader { + return t.input +} + +// Write writes data to the console's output. +func (t *console) Write(p []byte) (n int, err error) { + return t.output.Write(p) +} + +// Read reads data from the console's input. +func (t *console) Read(p []byte) (n int, err error) { + return t.input.Read(p) +} + +// Getenv retrieves the value of the environment variable named by the key. +func (t *console) Getenv(key string) string { + return t.environ.Getenv(key) +} + +// LookupEnv retrieves the value of the environment variable named by the key +// and a boolean indicating whether the variable is present. +func (t *console) LookupEnv(key string) (string, bool) { + return t.environ.LookupEnv(key) +} + +// MakeRaw puts the console input side into raw mode. +func (t *console) MakeRaw() (state *term.State, err error) { + inState, outState, err := makeRaw(t.input, t.output) + if err != nil { + return nil, err + } + t.inputState = inState + t.outputState = outState + if inState != nil { + return inState, nil + } + return outState, nil +} + +// Restore restores the console to its previous state. +func (t *console) Restore() error { + if t.inputState != nil { + if err := term.Restore(t.input.Fd(), t.inputState); err != nil { + return err + } + t.inputState = nil + } + if t.outputState != nil { + if err := term.Restore(t.output.Fd(), t.outputState); err != nil { + return err + } + t.outputState = nil + } + return nil +} + +// Close restores the console to its previous state and releases resources. +func (t *console) Close() error { + if err := t.Restore(); err != nil { + return err + } + return nil +} + +// GetSize returns the current size of the console. +func (t *console) GetSize() (width, height int, err error) { + return getSize(t.input, t.output) +} + +// GetWinsize returns the current size of the console in cells and pixels. +func (t *console) GetWinsize() (*Winsize, error) { + ws, err := getWinsize(t.input, t.output) + if err != nil { + return nil, err + } + return &ws, nil +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/console_unix.go b/vendor/github.com/charmbracelet/ultraviolet/console_unix.go new file mode 100644 index 000000000..5460effff --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/console_unix.go @@ -0,0 +1,13 @@ +//go:build !windows +// +build !windows + +package uv + +func newConsole(in File, out File, env Environ) *TTY { + c := &console{ + input: in, + output: out, + environ: env, + } + return &TTY{console: c} +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/console_windows.go b/vendor/github.com/charmbracelet/ultraviolet/console_windows.go new file mode 100644 index 000000000..0836f1c38 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/console_windows.go @@ -0,0 +1,13 @@ +//go:build windows +// +build windows + +package uv + +func newConsole(in File, out File, env Environ) *WinCon { + c := &console{ + input: in, + output: out, + environ: env, + } + return &WinCon{console: c} +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/cursor.go b/vendor/github.com/charmbracelet/ultraviolet/cursor.go new file mode 100644 index 000000000..33a3843a8 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/cursor.go @@ -0,0 +1,22 @@ +package uv + +// CursorShape represents a terminal cursor shape. +type CursorShape int + +// Cursor shapes. +const ( + CursorBlock CursorShape = iota + CursorUnderline + CursorBar +) + +// Encode returns the encoded value for the cursor shape. +func (s CursorShape) Encode(blink bool) int { + // We're using the ANSI escape sequence values for cursor styles. + // We need to map both [style] and [steady] to the correct value. + s = (s * 2) + 1 //nolint:mnd + if !blink { + s++ + } + return int(s) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/decoder.go b/vendor/github.com/charmbracelet/ultraviolet/decoder.go new file mode 100644 index 000000000..45ef17d72 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/decoder.go @@ -0,0 +1,2103 @@ +package uv + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "fmt" + "image/color" + "math" + "slices" + "strings" + "unicode" + "unicode/utf8" + + "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/x/ansi/parser" + xwindows "github.com/charmbracelet/x/windows" + "github.com/rivo/uniseg" +) + +// Flags to control the behavior of the parser. +const ( + // When this flag is set, the driver will treat both Ctrl+Space and Ctrl+@ + // as the same key sequence. + // + // Historically, the ANSI specs generate NUL (0x00) on both the Ctrl+Space + // and Ctrl+@ key sequences. This flag allows the driver to treat both as + // the same key sequence. + flagCtrlAt = 1 << iota + + // When this flag is set, the driver will treat the Tab key and Ctrl+I as + // the same key sequence. + // + // Historically, the ANSI specs generate HT (0x09) on both the Tab key and + // Ctrl+I. This flag allows the driver to treat both as the same key + // sequence. + flagCtrlI + + // When this flag is set, the driver will treat the Enter key and Ctrl+M as + // the same key sequence. + // + // Historically, the ANSI specs generate CR (0x0D) on both the Enter key + // and Ctrl+M. This flag allows the driver to treat both as the same key. + flagCtrlM + + // When this flag is set, the driver will treat Escape and Ctrl+[ as + // the same key sequence. + // + // Historically, the ANSI specs generate ESC (0x1B) on both the Escape key + // and Ctrl+[. This flag allows the driver to treat both as the same key + // sequence. + flagCtrlOpenBracket + + // When this flag is set, the driver will send a BS (0x08 byte) character + // instead of a DEL (0x7F byte) character when the Backspace key is + // pressed. + // + // The VT100 terminal has both a Backspace and a Delete key. The VT220 + // terminal dropped the Backspace key and replaced it with the Delete key. + // Both terminals send a DEL character when the Delete key is pressed. + // Modern terminals and PCs later readded the Delete key but used a + // different key sequence, and the Backspace key was standardized to send a + // DEL character. + flagBackspace + + // When this flag is set, the driver will recognize the Find key instead of + // treating it as a Home key. + // + // The Find key was part of the VT220 keyboard, and is no longer used in + // modern day PCs. + flagFind + + // When this flag is set, the driver will recognize the Select key instead + // of treating it as a End key. + // + // The Symbol key was part of the VT220 keyboard, and is no longer used in + // modern day PCs. + flagSelect + + // When this flag is set, the driver will preserve function keys (F13-F63) + // as symbols. + // + // Since these keys are not part of today's standard 20th century keyboard, + // we treat them as F1-F12 modifier keys i.e. ctrl/shift/alt + Fn combos. + // Key definitions come from Terminfo, this flag is only useful when + // FlagTerminfo is not set. + flagFKeys +) + +// LegacyKeyEncoding is a set of flags that control the behavior of legacy +// terminal key encodings. Historically, ctrl+ input events produce +// control characters (0x00-0x1F) that collide with some special keys like Tab, +// Enter, and Escape. This controls the expected behavior of encoding these +// keys. +// +// This type has the following default values: +// - CtrlAt maps 0x00 [ansi.NUL] to ctrl+space instead of ctrl+@. +// - CtrlI maps 0x09 [ansi.HT] to the tab key instead of ctrl+i. +// - CtrlM maps 0x0D [ansi.CR] to the enter key instead of ctrl+m. +// - CtrlOpenBracket maps 0x1B [ansi.ESC] to the escape key instead of ctrl+[. +// - Backspace maps the backspace key to 0x08 [ansi.BS] instead of 0x7F [ansi.DEL]. +// - Find maps the legacy find key to the home key. +// - Select maps the legacy select key to the end key. +// - FKeys maps high function keys instead of treating them as Function+. +type LegacyKeyEncoding uint32 + +// CtrlAt returns a [LegacyKeyEncoding] with whether [ansi.NUL] (0x00) +// is mapped to ctrl+at instead of ctrl+space. +func (l LegacyKeyEncoding) CtrlAt(v bool) LegacyKeyEncoding { + if v { + l |= flagCtrlAt + } else { + l &^= flagCtrlAt + } + return l +} + +// CtrlI returns a [LegacyKeyEncoding] with whether [ansi.HT] (0x09) +// is mapped to ctrl+i instead of the tab key. +func (l LegacyKeyEncoding) CtrlI(v bool) LegacyKeyEncoding { + if v { + l |= flagCtrlI + } else { + l &^= flagCtrlI + } + return l +} + +// CtrlM returns a [LegacyKeyEncoding] with whether [ansi.CR] (0x0D) +// is mapped to ctrl+m instead of the enter key. +func (l LegacyKeyEncoding) CtrlM(v bool) LegacyKeyEncoding { + if v { + l |= flagCtrlM + } else { + l &^= flagCtrlM + } + return l +} + +// CtrlOpenBracket returns a [LegacyKeyEncoding] with whether [ansi.ESC] (0x1B) +// is mapped to ctrl+[ instead of the escape key. +func (l LegacyKeyEncoding) CtrlOpenBracket(v bool) LegacyKeyEncoding { + if v { + l |= flagCtrlOpenBracket + } else { + l &^= flagCtrlOpenBracket + } + return l +} + +// Backspace returns a [LegacyKeyEncoding] with whether the backspace key is +// mapped to [ansi.BS] (0x08) instead of [ansi.DEL] (0x7F). +func (l LegacyKeyEncoding) Backspace(v bool) LegacyKeyEncoding { + if v { + l |= flagBackspace + } else { + l &^= flagBackspace + } + return l +} + +// Find returns a [LegacyKeyEncoding] with whether the legacy find key +// is mapped to the home key. +func (l LegacyKeyEncoding) Find(v bool) LegacyKeyEncoding { + if v { + l |= flagFind + } else { + l &^= flagFind + } + return l +} + +// Select returns a [LegacyKeyEncoding] with whether the legacy select key +// is mapped to the end key. +func (l LegacyKeyEncoding) Select(v bool) LegacyKeyEncoding { + if v { + l |= flagSelect + } else { + l &^= flagSelect + } + return l +} + +// FKeys returns a [LegacyKeyEncoding] with whether high function keys are +// mapped to high function keys (beyond F20) instead of treating them as +// Function+ keys. +func (l LegacyKeyEncoding) FKeys(v bool) LegacyKeyEncoding { + if v { + l |= flagFKeys + } else { + l &^= flagFKeys + } + return l +} + +// EventDecoder decodes terminal input events from a byte buffer. Terminal +// input events are typically encoded as Unicode or ASCII characters, control +// codes, or ANSI escape sequences. +type EventDecoder struct { + // Legacy is the legacy key encoding flags. These flags control the + // behavior of legacy terminal key encodings. See [LegacyKeyEncoding] for + // more details. + Legacy LegacyKeyEncoding + // UseTerminfo is a flag that controls whether to use the terminal type + // Terminfo database to map escape sequences to key events. This will + // override the default key sequences handled by the parser. + UseTerminfo bool + + lastCks uint32 // the last control key state for the previous key event record. +} + +// Decode finds the first recognized event sequence and returns it along +// with its length. +// +// It will return zero and nil no sequence is recognized or when the buffer is +// empty. If a sequence is not supported, an [UnknownEvent] is returned. +// +// Example: +// +// ```go +// var decoder EventDecoder +// var events []Event +// buf := []byte("\x00" + // Ctrl+Space +// "\x1b[A" + // Up arrow +// "Hello") // Text input +// for len(buf) > 0 { +// n, ev := decoder.Decode(buf) +// if ev != nil { +// events = append(events, ev) +// } +// buf = buf[n:] +// } +// ``` +func (p *EventDecoder) Decode(buf []byte) (n int, Event Event) { + if len(buf) == 0 { + return 0, nil + } + + switch b := buf[0]; b { + case ansi.ESC: + if len(buf) == 1 { + // Escape key + return 1, KeyPressEvent{Code: KeyEscape} + } + + switch bPrime := buf[1]; bPrime { + case 'O': // Esc-prefixed SS3 + return p.parseSs3(buf) + case 'P': // Esc-prefixed DCS + return p.parseDcs(buf) + case '[': // Esc-prefixed CSI + return p.parseCsi(buf) + case ']': // Esc-prefixed OSC + return p.parseOsc(buf) + case '_': // Esc-prefixed APC + return p.parseApc(buf) + case '^': // Esc-prefixed PM + return p.parseStTerminated(ansi.PM, '^', nil)(buf) + case 'X': // Esc-prefixed SOS + return p.parseStTerminated(ansi.SOS, 'X', nil)(buf) + default: + n, e := p.Decode(buf[1:]) + if k, ok := e.(KeyPressEvent); ok { + k.Text = "" + k.Mod |= ModAlt + return n + 1, k + } + + // Not a key sequence, nor an alt modified key sequence. In that + // case, just report a single escape key. + return 1, KeyPressEvent{Code: KeyEscape} + } + case ansi.SS3: + return p.parseSs3(buf) + case ansi.DCS: + return p.parseDcs(buf) + case ansi.CSI: + return p.parseCsi(buf) + case ansi.OSC: + return p.parseOsc(buf) + case ansi.APC: + return p.parseApc(buf) + case ansi.PM: + return p.parseStTerminated(ansi.PM, '^', nil)(buf) + case ansi.SOS: + return p.parseStTerminated(ansi.SOS, 'X', nil)(buf) + default: + if b <= ansi.US || b == ansi.DEL || b == ansi.SP { + return 1, p.parseControl(b) + } else if b >= ansi.PAD && b <= ansi.APC { + // C1 control code + // UTF-8 never starts with a C1 control code + // Encode these as Ctrl+Alt+ + code := rune(b) - 0x40 + return 1, KeyPressEvent{Code: code, Mod: ModCtrl | ModAlt} + } + return p.parseUtf8(buf) + } +} + +func (p *EventDecoder) parseCsi(b []byte) (int, Event) { + if len(b) == 2 && b[0] == ansi.ESC { + // short cut if this is an alt+[ key + return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt} + } + + var cmd ansi.Cmd + var params [parser.MaxParamsSize]ansi.Param + var paramsLen int + + var i int + if b[i] == ansi.CSI || b[i] == ansi.ESC { + i++ + } + if i < len(b) && b[i-1] == ansi.ESC && b[i] == '[' { + i++ + } + + // Initial CSI byte + if i < len(b) && b[i] >= '<' && b[i] <= '?' { + cmd |= ansi.Cmd(b[i]) << parser.PrefixShift + } + + // Scan parameter bytes in the range 0x30-0x3F + var j int + for j = 0; i < len(b) && paramsLen < len(params) && b[i] >= 0x30 && b[i] <= 0x3F; i, j = i+1, j+1 { + if b[i] >= '0' && b[i] <= '9' { + if params[paramsLen] == parser.MissingParam { + params[paramsLen] = 0 + } + params[paramsLen] *= 10 + params[paramsLen] += ansi.Param(b[i]) - '0' + } + if b[i] == ':' { + params[paramsLen] |= parser.HasMoreFlag + } + if b[i] == ';' || b[i] == ':' { + paramsLen++ + if paramsLen < len(params) { + // Don't overflow the params slice + params[paramsLen] = parser.MissingParam + } + } + } + + if j > 0 && paramsLen < len(params) { + // has parameters + paramsLen++ + } + + // Scan intermediate bytes in the range 0x20-0x2F + var intermed byte + for ; i < len(b) && b[i] >= 0x20 && b[i] <= 0x2F; i++ { + intermed = b[i] + } + + // Set the intermediate byte + cmd |= ansi.Cmd(intermed) << parser.IntermedShift + + // Scan final byte in the range 0x40-0x7E + if i >= len(b) || b[i] < 0x40 || b[i] > 0x7E { + // Special case for URxvt keys + // CSI $ is an invalid sequence, but URxvt uses it for + // shift modified keys. + if intermed == '$' && b[i-1] == '$' { + buf := slices.Clone(b[:i-1]) + n, ev := p.parseCsi(append(buf, '~')) + if k, ok := ev.(KeyPressEvent); ok { + k.Mod |= ModShift + return n, k + } + } + return i, UnknownEvent(b[:i]) + } + + // Add the final byte + cmd |= ansi.Cmd(b[i]) + i++ + + pa := ansi.Params(params[:paramsLen]) + switch cmd { + case 'y' | '?'<'<'< R (which is modified F3) when the cursor is at the + // row 1. In this case, we report both messages. + // + // For a non ambiguous cursor position report, use + // [ansi.RequestExtendedCursorPosition] (DECXCPR) instead. + return i, MultiEvent{KeyPressEvent{Code: KeyF3, Mod: KeyMod(col - 1)}, m} + } + + return i, m + } + + if paramsLen != 0 { + break + } + + // Unmodified key F3 (CSI R) + fallthrough + case 'a', 'b', 'c', 'd', 'A', 'B', 'C', 'D', 'E', 'F', 'H', 'P', 'Q', 'S', 'Z': + var k KeyPressEvent + switch cmd { + case 'a', 'b', 'c', 'd': + k = KeyPressEvent{Code: KeyUp + rune(cmd-'a'), Mod: ModShift} + case 'A', 'B', 'C', 'D': + k = KeyPressEvent{Code: KeyUp + rune(cmd-'A')} + case 'E': + k = KeyPressEvent{Code: KeyBegin} + case 'F': + k = KeyPressEvent{Code: KeyEnd} + case 'H': + k = KeyPressEvent{Code: KeyHome} + case 'P', 'Q', 'R', 'S': + k = KeyPressEvent{Code: KeyF1 + rune(cmd-'P')} + case 'Z': + k = KeyPressEvent{Code: KeyTab, Mod: ModShift} + } + id, _, _ := pa.Param(0, 1) + mod, _, _ := pa.Param(1, 1) + if paramsLen > 2 && !pa[1].HasMore() || id != 1 { + break + } + if paramsLen > 1 && id == 1 && mod != -1 { + // CSI 1 ; A + k.Mod |= KeyMod(mod - 1) + } + // Don't forget to handle Kitty keyboard protocol + return i, parseKittyKeyboardExt(pa, k) + case 'M': + // Handle X10 mouse + if i+3 > len(b) { + return i, UnknownCsiEvent(b[:i]) + } + return i + 3, parseX10MouseEvent(append(b[:i], b[i:i+3]...)) + case 'y' | '$'< 1 && mod != -1 { + k.Mod |= KeyMod(mod - 1) + } + + // Handle URxvt weird keys + switch cmd { + case '~': + // Don't forget to handle Kitty keyboard protocol + return i, parseKittyKeyboardExt(pa, k) + case '^': + k.Mod |= ModCtrl + case '@': + k.Mod |= ModCtrl | ModShift + } + + return i, k + } + + case 't': + param, _, ok := pa.Param(0, 0) + if !ok { + break + } + + switch param { + case 4: // Report Terminal window size in pixels. + if paramsLen == 3 { + height, _, hOk := pa.Param(1, 0) + width, _, wOk := pa.Param(2, 0) + if !hOk || !wOk { + break + } + return i, PixelSizeEvent{Width: width, Height: height} + } + case 6: // Report Terminal character cell size. + if paramsLen == 3 { + height, _, hOk := pa.Param(1, 0) + width, _, wOk := pa.Param(2, 0) + if !hOk || !wOk { + break + } + return i, CellSizeEvent{Width: width, Height: height} + } + case 8: // Report Terminal Window size in cells. + if paramsLen == 3 { + height, _, hOk := pa.Param(1, 0) + width, _, wOk := pa.Param(2, 0) + if !hOk || !wOk { + break + } + return i, WindowSizeEvent{Width: width, Height: height} + } + case 48: // In band terminal size report. + if paramsLen == 5 { + cellHeight, _, chOk := pa.Param(1, 0) + cellWidth, _, cwOk := pa.Param(2, 0) + pixelHeight, _, phOk := pa.Param(3, 0) + pixelWidth, _, pwOk := pa.Param(4, 0) + if !chOk || !cwOk || !phOk || !pwOk { + break + } + return i, MultiEvent{ + WindowSizeEvent{Width: cellWidth, Height: cellHeight}, + PixelSizeEvent{Width: pixelWidth, Height: pixelHeight}, + } + } + } + + // Any other window operation event. + + var winop WindowOpEvent + winop.Op = param + for j := 1; j < paramsLen; j++ { + val, _, ok := pa.Param(j, 0) + if ok { + winop.Args = append(winop.Args, val) + } + } + + return i, winop + } + return i, UnknownCsiEvent(b[:i]) +} + +// parseSs3 parses a SS3 sequence. +// See https://vt100.net/docs/vt220-rm/chapter4.html#S4.4.4.2 +func (p *EventDecoder) parseSs3(b []byte) (int, Event) { + if len(b) == 2 && b[0] == ansi.ESC { + // short cut if this is an alt+O key + return 2, KeyPressEvent{Code: unicode.ToLower(rune(b[1])), Mod: ModShift | ModAlt} + } + + var i int + if b[i] == ansi.SS3 || b[i] == ansi.ESC { + i++ + } + if i < len(b) && b[i-1] == ansi.ESC && b[i] == 'O' { + i++ + } + + // Scan numbers from 0-9 + var mod int + for ; i < len(b) && b[i] >= '0' && b[i] <= '9'; i++ { + mod *= 10 + mod += int(b[i]) - '0' + } + + // Scan a GL character + // A GL character is a single byte in the range 0x21-0x7E + // See https://vt100.net/docs/vt220-rm/chapter2.html#S2.3.2 + if i >= len(b) || b[i] < 0x21 || b[i] > 0x7E { + return i, UnknownEvent(b[:i]) + } + + // GL character(s) + gl := b[i] + i++ + + var k KeyPressEvent + switch gl { + case 'a', 'b', 'c', 'd': + k = KeyPressEvent{Code: KeyUp + rune(gl-'a'), Mod: ModCtrl} + case 'A', 'B', 'C', 'D': + k = KeyPressEvent{Code: KeyUp + rune(gl-'A')} + case 'E': + k = KeyPressEvent{Code: KeyBegin} + case 'F': + k = KeyPressEvent{Code: KeyEnd} + case 'H': + k = KeyPressEvent{Code: KeyHome} + case 'P', 'Q', 'R', 'S': + k = KeyPressEvent{Code: KeyF1 + rune(gl-'P')} + case 'M': + k = KeyPressEvent{Code: KeyKpEnter} + case 'X': + k = KeyPressEvent{Code: KeyKpEqual} + case 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y': + k = KeyPressEvent{Code: KeyKpMultiply + rune(gl-'j')} + default: + return i, UnknownSs3Event(b[:i]) + } + + // Handle weird SS3 Func + if mod > 0 { + k.Mod |= KeyMod(mod - 1) + } + + return i, k +} + +func (p *EventDecoder) parseOsc(b []byte) (int, Event) { + defaultKey := func() KeyPressEvent { + return KeyPressEvent{Code: rune(b[1]), Mod: ModAlt} + } + if len(b) == 2 && b[0] == ansi.ESC { + // short cut if this is an alt+] key + return 2, defaultKey() + } + + var i int + if b[i] == ansi.OSC || b[i] == ansi.ESC { + i++ + } + if i < len(b) && b[i-1] == ansi.ESC && b[i] == ']' { + i++ + } + + // Parse OSC command + // An OSC sequence is terminated by a BEL, ESC, or ST character + var start, end int + cmd := -1 + for ; i < len(b) && b[i] >= '0' && b[i] <= '9'; i++ { + if cmd == -1 { + cmd = 0 + } else { + cmd *= 10 + } + cmd += int(b[i]) - '0' + } + + if i < len(b) && b[i] == ';' { + // mark the start of the sequence data + i++ + start = i + } + + for ; i < len(b); i++ { + // advance to the end of the sequence + if slices.Contains([]byte{ansi.BEL, ansi.ESC, ansi.ST, ansi.CAN, ansi.SUB}, b[i]) { + break + } + } + + if i >= len(b) { + return i, UnknownEvent(b[:i]) + } + + end = i // end of the sequence data + i++ + + // Check 7-bit ST (string terminator) character + switch b[i-1] { + case ansi.CAN, ansi.SUB: + return i, ignoredEvent(b[:i]) + case ansi.ESC: + if i >= len(b) || b[i] != '\\' { + if cmd == -1 || (start == 0 && end == 2) { + return 2, defaultKey() + } + + // If we don't have a valid ST terminator, then this is a + // cancelled sequence and should be ignored. + return i, ignoredEvent(b[:i]) + } + + i++ + } + + if end <= start { + return i, UnknownEvent(b[:i]) + } + + data := string(b[start:end]) + switch cmd { + case 10: + return i, ForegroundColorEvent{ansi.XParseColor(data)} + case 11: + return i, BackgroundColorEvent{ansi.XParseColor(data)} + case 12: + return i, CursorColorEvent{ansi.XParseColor(data)} + case 52: + parts := strings.Split(data, ";") + if len(parts) != 2 || len(parts[0]) < 1 { + return i, ClipboardEvent{} + } + + b64 := parts[1] + bts, err := base64.StdEncoding.DecodeString(b64) + if err != nil { + return i, ClipboardEvent{Content: parts[1]} + } + + sel := ClipboardSelection(parts[0][0]) //nolint:unconvert + return i, ClipboardEvent{Selection: sel, Content: string(bts)} + } + + return i, UnknownOscEvent(b[:i]) +} + +// parseStTerminated parses a control sequence that gets terminated by a ST character. +func (p *EventDecoder) parseStTerminated(intro8, intro7 byte, fn func([]byte) Event) func([]byte) (int, Event) { + defaultKey := func(b []byte) (int, Event) { + switch intro8 { + case ansi.SOS: + return 2, KeyPressEvent{Code: unicode.ToLower(rune(b[1])), Mod: ModShift | ModAlt} + case ansi.PM, ansi.APC: + return 2, KeyPressEvent{Code: rune(b[1]), Mod: ModAlt} + } + return 0, nil + } + return func(b []byte) (int, Event) { + if len(b) == 2 && b[0] == ansi.ESC { + return defaultKey(b) + } + + var i int + if b[i] == intro8 || b[i] == ansi.ESC { + i++ + } + if i < len(b) && b[i-1] == ansi.ESC && b[i] == intro7 { + i++ + } + + // Scan control sequence + // Most common control sequence is terminated by a ST character + // ST is a 7-bit string terminator character is (ESC \) + start := i + for ; i < len(b); i++ { + if slices.Contains([]byte{ansi.ESC, ansi.ST, ansi.CAN, ansi.SUB}, b[i]) { + break + } + } + + if i >= len(b) { + return i, UnknownEvent(b[:i]) + } + + end := i // end of the sequence data + i++ + + // Check 7-bit ST (string terminator) character + switch b[i-1] { + case ansi.CAN, ansi.SUB: + return i, ignoredEvent(b[:i]) + case ansi.ESC: + if i >= len(b) || b[i] != '\\' { + if start == end { + return defaultKey(b) + } + + // If we don't have a valid ST terminator, then this is a + // cancelled sequence and should be ignored. + return i, ignoredEvent(b[:i]) + } + + i++ + } + + // Call the function to parse the sequence and return the result + if fn != nil { + if e := fn(b[start:end]); e != nil { + return i, e + } + } + + switch intro8 { + case ansi.PM: + return i, UnknownPmEvent(b[:i]) + case ansi.SOS: + return i, UnknownSosEvent(b[:i]) + case ansi.APC: + return i, UnknownApcEvent(b[:i]) + default: + return i, UnknownEvent(b[:i]) + } + } +} + +func (p *EventDecoder) parseDcs(b []byte) (int, Event) { + if len(b) == 2 && b[0] == ansi.ESC { + // short cut if this is an alt+P key + return 2, KeyPressEvent{Code: unicode.ToLower(rune(b[1])), Mod: ModShift | ModAlt} + } + + var params [16]ansi.Param + var paramsLen int + var cmd ansi.Cmd + + // DCS sequences are introduced by DCS (0x90) or ESC P (0x1b 0x50) + var i int + if b[i] == ansi.DCS || b[i] == ansi.ESC { + i++ + } + if i < len(b) && b[i-1] == ansi.ESC && b[i] == 'P' { + i++ + } + + // initial DCS byte + if i < len(b) && b[i] >= '<' && b[i] <= '?' { + cmd |= ansi.Cmd(b[i]) << parser.PrefixShift + } + + // Scan parameter bytes in the range 0x30-0x3F + var j int + for j = 0; i < len(b) && paramsLen < len(params) && b[i] >= 0x30 && b[i] <= 0x3F; i, j = i+1, j+1 { + if b[i] >= '0' && b[i] <= '9' { + if params[paramsLen] == parser.MissingParam { + params[paramsLen] = 0 + } + params[paramsLen] *= 10 + params[paramsLen] += ansi.Param(b[i]) - '0' + } + if b[i] == ':' { + params[paramsLen] |= parser.HasMoreFlag + } + if b[i] == ';' || b[i] == ':' { + paramsLen++ + if paramsLen < len(params) { + // Don't overflow the params slice + params[paramsLen] = parser.MissingParam + } + } + } + + if j > 0 && paramsLen < len(params) { + // has parameters + paramsLen++ + } + + // Scan intermediate bytes in the range 0x20-0x2F + var intermed byte + for j := 0; i < len(b) && b[i] >= 0x20 && b[i] <= 0x2F; i, j = i+1, j+1 { + intermed = b[i] + } + + // set intermediate byte + cmd |= ansi.Cmd(intermed) << parser.IntermedShift + + // Scan final byte in the range 0x40-0x7E + if i >= len(b) || b[i] < 0x40 || b[i] > 0x7E { + return i, UnknownEvent(b[:i]) + } + + // Add the final byte + cmd |= ansi.Cmd(b[i]) + i++ + + start := i // start of the sequence data + for ; i < len(b); i++ { + if b[i] == ansi.ST || b[i] == ansi.ESC { + break + } + } + + if i >= len(b) { + return i, UnknownEvent(b[:i]) + } + + end := i // end of the sequence data + i++ + + // Check 7-bit ST (string terminator) character + if i < len(b) && b[i-1] == ansi.ESC && b[i] == '\\' { + i++ + } + + pa := ansi.Params(params[:paramsLen]) + switch cmd { + case 'r' | '+'<'< 1 { + g.Payload = parts[1] + } + return g + } + + return nil + })(b) +} + +func (p *EventDecoder) parseUtf8(b []byte) (int, Event) { + if len(b) == 0 { + return 0, nil + } + + c := b[0] + if c <= ansi.US || c == ansi.DEL { + // Control codes get handled by parseControl + return 1, p.parseControl(c) + } else if c > ansi.US && c < ansi.DEL { + // ASCII printable characters + code := rune(c) + k := KeyPressEvent{Code: code, Text: string(code)} + if unicode.IsUpper(code) { + // Convert upper case letters to lower case + shift modifier + k.Code = unicode.ToLower(code) + k.ShiftedCode = code + k.Mod |= ModShift + } + + return 1, k + } + + code, _ := utf8.DecodeRune(b) + if code == utf8.RuneError { + return 1, UnknownEvent(b[0]) + } + + cluster, _, _, _ := uniseg.FirstGraphemeCluster(b, -1) + text := string(cluster) + for i := range text { + if i > 0 { + // Use [KeyExtended] for multi-rune graphemes + code = KeyExtended + break + } + } + + return len(cluster), KeyPressEvent{Code: code, Text: text} +} + +func (p *EventDecoder) parseControl(b byte) Event { + switch b { + case ansi.NUL: + if p.Legacy&flagCtrlAt != 0 { + return KeyPressEvent{Code: '@', Mod: ModCtrl} + } + return KeyPressEvent{Code: KeySpace, Mod: ModCtrl} + case ansi.BS: + return KeyPressEvent{Code: 'h', Mod: ModCtrl} + case ansi.HT: + if p.Legacy&flagCtrlI != 0 { + return KeyPressEvent{Code: 'i', Mod: ModCtrl} + } + return KeyPressEvent{Code: KeyTab} + case ansi.CR: + if p.Legacy&flagCtrlM != 0 { + return KeyPressEvent{Code: 'm', Mod: ModCtrl} + } + return KeyPressEvent{Code: KeyEnter} + case ansi.ESC: + if p.Legacy&flagCtrlOpenBracket != 0 { + return KeyPressEvent{Code: '[', Mod: ModCtrl} + } + return KeyPressEvent{Code: KeyEscape} + case ansi.DEL: + if p.Legacy&flagBackspace != 0 { + return KeyPressEvent{Code: KeyDelete} + } + return KeyPressEvent{Code: KeyBackspace} + case ansi.SP: + return KeyPressEvent{Code: KeySpace, Text: " "} + default: + if b >= ansi.SOH && b <= ansi.SUB { + // Use lower case letters for control codes + code := rune(b + 0x60) + return KeyPressEvent{Code: code, Mod: ModCtrl} + } else if b >= ansi.FS && b <= ansi.US { + code := rune(b + 0x40) + return KeyPressEvent{Code: code, Mod: ModCtrl} + } + return UnknownEvent(b) + } +} + +func parseXTermModifyOtherKeys(params ansi.Params) Event { + // XTerm modify other keys starts with ESC [ 27 ; ; ~ + xmod, _, _ := params.Param(1, 1) + xrune, _, _ := params.Param(2, 1) + mod := KeyMod(xmod - 1) + r := rune(xrune) + + switch r { + case ansi.BS: + return KeyPressEvent{Mod: mod, Code: KeyBackspace} + case ansi.HT: + return KeyPressEvent{Mod: mod, Code: KeyTab} + case ansi.CR: + return KeyPressEvent{Mod: mod, Code: KeyEnter} + case ansi.ESC: + return KeyPressEvent{Mod: mod, Code: KeyEscape} + case ansi.DEL: + return KeyPressEvent{Mod: mod, Code: KeyBackspace} + } + + // CSI 27 ; ; ~ keys defined in XTerm modifyOtherKeys + k := KeyPressEvent{Code: r, Mod: mod} + if k.Mod <= ModShift { + k.Text = string(r) + } + + return k +} + +// Kitty Clipboard Control Sequences. +var kittyKeyMap = map[int]Key{ + ansi.BS: {Code: KeyBackspace}, + ansi.HT: {Code: KeyTab}, + ansi.CR: {Code: KeyEnter}, + ansi.ESC: {Code: KeyEscape}, + ansi.DEL: {Code: KeyBackspace}, + + 57344: {Code: KeyEscape}, + 57345: {Code: KeyEnter}, + 57346: {Code: KeyTab}, + 57347: {Code: KeyBackspace}, + 57348: {Code: KeyInsert}, + 57349: {Code: KeyDelete}, + 57350: {Code: KeyLeft}, + 57351: {Code: KeyRight}, + 57352: {Code: KeyUp}, + 57353: {Code: KeyDown}, + 57354: {Code: KeyPgUp}, + 57355: {Code: KeyPgDown}, + 57356: {Code: KeyHome}, + 57357: {Code: KeyEnd}, + 57358: {Code: KeyCapsLock}, + 57359: {Code: KeyScrollLock}, + 57360: {Code: KeyNumLock}, + 57361: {Code: KeyPrintScreen}, + 57362: {Code: KeyPause}, + 57363: {Code: KeyMenu}, + 57364: {Code: KeyF1}, + 57365: {Code: KeyF2}, + 57366: {Code: KeyF3}, + 57367: {Code: KeyF4}, + 57368: {Code: KeyF5}, + 57369: {Code: KeyF6}, + 57370: {Code: KeyF7}, + 57371: {Code: KeyF8}, + 57372: {Code: KeyF9}, + 57373: {Code: KeyF10}, + 57374: {Code: KeyF11}, + 57375: {Code: KeyF12}, + 57376: {Code: KeyF13}, + 57377: {Code: KeyF14}, + 57378: {Code: KeyF15}, + 57379: {Code: KeyF16}, + 57380: {Code: KeyF17}, + 57381: {Code: KeyF18}, + 57382: {Code: KeyF19}, + 57383: {Code: KeyF20}, + 57384: {Code: KeyF21}, + 57385: {Code: KeyF22}, + 57386: {Code: KeyF23}, + 57387: {Code: KeyF24}, + 57388: {Code: KeyF25}, + 57389: {Code: KeyF26}, + 57390: {Code: KeyF27}, + 57391: {Code: KeyF28}, + 57392: {Code: KeyF29}, + 57393: {Code: KeyF30}, + 57394: {Code: KeyF31}, + 57395: {Code: KeyF32}, + 57396: {Code: KeyF33}, + 57397: {Code: KeyF34}, + 57398: {Code: KeyF35}, + 57399: {Code: KeyKp0}, + 57400: {Code: KeyKp1}, + 57401: {Code: KeyKp2}, + 57402: {Code: KeyKp3}, + 57403: {Code: KeyKp4}, + 57404: {Code: KeyKp5}, + 57405: {Code: KeyKp6}, + 57406: {Code: KeyKp7}, + 57407: {Code: KeyKp8}, + 57408: {Code: KeyKp9}, + 57409: {Code: KeyKpDecimal}, + 57410: {Code: KeyKpDivide}, + 57411: {Code: KeyKpMultiply}, + 57412: {Code: KeyKpMinus}, + 57413: {Code: KeyKpPlus}, + 57414: {Code: KeyKpEnter}, + 57415: {Code: KeyKpEqual}, + 57416: {Code: KeyKpSep}, + 57417: {Code: KeyKpLeft}, + 57418: {Code: KeyKpRight}, + 57419: {Code: KeyKpUp}, + 57420: {Code: KeyKpDown}, + 57421: {Code: KeyKpPgUp}, + 57422: {Code: KeyKpPgDown}, + 57423: {Code: KeyKpHome}, + 57424: {Code: KeyKpEnd}, + 57425: {Code: KeyKpInsert}, + 57426: {Code: KeyKpDelete}, + 57427: {Code: KeyKpBegin}, + 57428: {Code: KeyMediaPlay}, + 57429: {Code: KeyMediaPause}, + 57430: {Code: KeyMediaPlayPause}, + 57431: {Code: KeyMediaReverse}, + 57432: {Code: KeyMediaStop}, + 57433: {Code: KeyMediaFastForward}, + 57434: {Code: KeyMediaRewind}, + 57435: {Code: KeyMediaNext}, + 57436: {Code: KeyMediaPrev}, + 57437: {Code: KeyMediaRecord}, + 57438: {Code: KeyLowerVol}, + 57439: {Code: KeyRaiseVol}, + 57440: {Code: KeyMute}, + 57441: {Code: KeyLeftShift}, + 57442: {Code: KeyLeftCtrl}, + 57443: {Code: KeyLeftAlt}, + 57444: {Code: KeyLeftSuper}, + 57445: {Code: KeyLeftHyper}, + 57446: {Code: KeyLeftMeta}, + 57447: {Code: KeyRightShift}, + 57448: {Code: KeyRightCtrl}, + 57449: {Code: KeyRightAlt}, + 57450: {Code: KeyRightSuper}, + 57451: {Code: KeyRightHyper}, + 57452: {Code: KeyRightMeta}, + 57453: {Code: KeyIsoLevel3Shift}, + 57454: {Code: KeyIsoLevel5Shift}, +} + +func init() { + // These are some faulty C0 mappings some terminals such as WezTerm have + // and doesn't follow the specs. + kittyKeyMap[ansi.NUL] = Key{Code: KeySpace, Mod: ModCtrl} + for i := ansi.SOH; i <= ansi.SUB; i++ { + if _, ok := kittyKeyMap[i]; !ok { + kittyKeyMap[i] = Key{Code: rune(i + 0x60), Mod: ModCtrl} + } + } + for i := ansi.FS; i <= ansi.US; i++ { + if _, ok := kittyKeyMap[i]; !ok { + kittyKeyMap[i] = Key{Code: rune(i + 0x40), Mod: ModCtrl} + } + } +} + +const ( + kittyShift = 1 << iota + kittyAlt + kittyCtrl + kittySuper + kittyHyper + kittyMeta + kittyCapsLock + kittyNumLock +) + +func fromKittyMod(mod int) KeyMod { + var m KeyMod + if mod&kittyShift != 0 { + m |= ModShift + } + if mod&kittyAlt != 0 { + m |= ModAlt + } + if mod&kittyCtrl != 0 { + m |= ModCtrl + } + if mod&kittySuper != 0 { + m |= ModSuper + } + if mod&kittyHyper != 0 { + m |= ModHyper + } + if mod&kittyMeta != 0 { + m |= ModMeta + } + if mod&kittyCapsLock != 0 { + m |= ModCapsLock + } + if mod&kittyNumLock != 0 { + m |= ModNumLock + } + return m +} + +// parseKittyKeyboard parses a Kitty Keyboard Protocol sequence. +// +// In `CSI u`, this is parsed as: +// +// CSI codepoint ; modifiers u +// codepoint: ASCII Dec value +// +// The Kitty Keyboard Protocol extends this with optional components that can be +// enabled progressively. The full sequence is parsed as: +// +// CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u +// +// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/ +func parseKittyKeyboard(params ansi.Params) (Event Event) { + var isRelease bool + var key Key + + // The index of parameters separated by semicolons ';'. Sub parameters are + // separated by colons ':'. + var paramIdx int + var sudIdx int // The sub parameter index + for _, p := range params { + // Kitty Keyboard Protocol has 3 optional components. + switch paramIdx { + case 0: + switch sudIdx { + case 0: + var foundKey bool + code := p.Param(1) // CSI u has a default value of 1 + key, foundKey = kittyKeyMap[code] + if !foundKey { + r := rune(code) + if !utf8.ValidRune(r) { + r = utf8.RuneError + } + + key.Code = r + } + + case 2: + // shifted key + base key + if b := rune(p.Param(1)); unicode.IsPrint(b) { + // XXX: When alternate key reporting is enabled, the protocol + // can return 3 things, the unicode codepoint of the key, + // the shifted codepoint of the key, and the standard + // PC-101 key layout codepoint. + // This is useful to create an unambiguous mapping of keys + // when using a different language layout. + key.BaseCode = b + } + fallthrough + + case 1: + // shifted key + if s := rune(p.Param(1)); unicode.IsPrint(s) { + // XXX: We swap keys here because we want the shifted key + // to be the Rune that is returned by the event. + // For example, shift+a should produce "A" not "a". + // In such a case, we set AltRune to the original key "a" + // and Rune to "A". + key.ShiftedCode = s + } + } + case 1: + switch sudIdx { + case 0: + mod := p.Param(1) + if mod > 1 { + key.Mod = fromKittyMod(mod - 1) + if key.Mod > ModShift { + // XXX: We need to clear the text if we have a modifier key + // other than a [ModShift] key. + key.Text = "" + } + } + + case 1: + switch p.Param(1) { + case 2: + key.IsRepeat = true + case 3: + isRelease = true + } + case 2: + } + case 2: + if code := p.Param(0); code != 0 { + key.Text += string(rune(code)) + } + } + + sudIdx++ + if !p.HasMore() { + paramIdx++ + sudIdx = 0 + } + } + + keyMod := key.Mod + + // Remove these lock modifiers from now on since they don't affect the text. + keyMod &^= ModNumLock + // keyMod &^= ModScrollLock // Kitty doesn't support scroll lock + + printMod := keyMod <= ModShift || keyMod == ModCapsLock || keyMod == (ModShift|ModCapsLock) + printKeyPad := key.Code >= KeyKpEqual && key.Code <= KeyKpSep + if len(key.Text) == 0 && printKeyPad && printMod { + switch { + case key.Code >= KeyKp0 && key.Code <= KeyKp9: + key.Text = string('0' + key.Code - KeyKp0) + case key.Code == KeyKpEqual: + key.Text = "=" + case key.Code == KeyKpMultiply: + key.Text = "*" + case key.Code == KeyKpPlus: + key.Text = "+" + case key.Code == KeyKpMinus: + key.Text = "-" + case key.Code == KeyKpDecimal: + key.Text = "." + case key.Code == KeyKpDivide: + key.Text = "/" + case key.Code == KeyKpSep: + key.Text = "," + } + } + + //nolint:nestif + if len(key.Text) == 0 && unicode.IsPrint(key.Code) && printMod { + if keyMod == 0 { + key.Text = string(key.Code) + } else { + desiredCase := unicode.ToLower + if keyMod.Contains(ModShift) || keyMod.Contains(ModCapsLock) { + desiredCase = unicode.ToUpper + } + if key.ShiftedCode != 0 { + key.Text = string(key.ShiftedCode) + } else { + key.Text = string(desiredCase(key.Code)) + } + } + } + + if isRelease { + return KeyReleaseEvent(key) + } + + return KeyPressEvent(key) +} + +// parseKittyKeyboardExt parses a Kitty Keyboard Protocol sequence extensions +// for non CSI u sequences. This includes things like CSI A, SS3 A and others, +// and CSI ~. +func parseKittyKeyboardExt(params ansi.Params, k KeyPressEvent) Event { + // Handle Kitty keyboard protocol + if len(params) > 2 && // We have at least 3 parameters + params[0].Param(1) == 1 && // The first parameter is 1 (defaults to 1) + params[1].HasMore() { // The second parameter is a subparameter (separated by a ":") + switch params[2].Param(1) { // The third parameter is the event type (defaults to 1) + case 2: + k.IsRepeat = true + case 3: + return KeyReleaseEvent(k) + } + } + return k +} + +func parsePrimaryDevAttrs(params ansi.Params) Event { + // Primary Device Attributes + da1 := make(PrimaryDeviceAttributesEvent, len(params)) + for i, p := range params { + if !p.HasMore() { + da1[i] = p.Param(0) + } + } + return da1 +} + +func parseSecondaryDevAttrs(params ansi.Params) Event { + // Secondary Device Attributes + da2 := make(SecondaryDeviceAttributesEvent, len(params)) + for i, p := range params { + if !p.HasMore() { + da2[i] = p.Param(0) + } + } + return da2 +} + +func parseTertiaryDevAttrs(b []byte) Event { + // Tertiary Device Attributes + // The response is a 4-digit hexadecimal number. + bts, err := hex.DecodeString(string(b)) + if err != nil { + return UnknownDcsEvent(fmt.Sprintf("\x1bP!|%s\x1b\\", b)) + } + return TertiaryDeviceAttributesEvent(bts) +} + +// Parse SGR-encoded mouse events; SGR extended mouse events. SGR mouse events +// look like: +// +// ESC [ < Cb ; Cx ; Cy (M or m) +// +// where: +// +// Cb is the encoded button code +// Cx is the x-coordinate of the mouse +// Cy is the y-coordinate of the mouse +// M is for button press, m is for button release +// +// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates +func parseSGRMouseEvent(cmd ansi.Cmd, params ansi.Params) Event { + x, _, ok := params.Param(1, 1) + if !ok { + x = 1 + } + y, _, ok := params.Param(2, 1) + if !ok { + y = 1 + } + release := cmd.Final() == 'm' + b, _, _ := params.Param(0, 0) + mod, btn, _, isMotion := parseMouseButton(b) + + // (1,1) is the upper left. We subtract 1 to normalize it to (0,0). + x-- + y-- + + m := Mouse{X: x, Y: y, Button: btn, Mod: mod} + + // Wheel buttons don't have release events + // Motion can be reported as a release event in some terminals (Windows Terminal) + if isWheel(m.Button) { + return MouseWheelEvent(m) + } else if !isMotion && release { + return MouseReleaseEvent(m) + } else if isMotion { + return MouseMotionEvent(m) + } + return MouseClickEvent(m) +} + +const x10MouseByteOffset = 32 + +// Parse X10-encoded mouse events; the simplest kind. The last release of X10 +// was December 1986, by the way. The original X10 mouse protocol limits the Cx +// and Cy coordinates to 223 (=255-032). +// +// X10 mouse events look like: +// +// ESC [M Cb Cx Cy +// +// See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking +func parseX10MouseEvent(buf []byte) Event { + v := buf[3:6] + b := int(v[0]) + if b >= x10MouseByteOffset { + // XXX: b < 32 should be impossible, but we're being defensive. + b -= x10MouseByteOffset + } + + mod, btn, isRelease, isMotion := parseMouseButton(b) + + // (1,1) is the upper left. We subtract 1 to normalize it to (0,0). + x := int(v[1]) - x10MouseByteOffset - 1 + y := int(v[2]) - x10MouseByteOffset - 1 + + m := Mouse{X: x, Y: y, Button: btn, Mod: mod} + if isWheel(m.Button) { + return MouseWheelEvent(m) + } else if isMotion { + return MouseMotionEvent(m) + } else if isRelease { + return MouseReleaseEvent(m) + } + return MouseClickEvent(m) +} + +// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates +func parseMouseButton(b int) (mod KeyMod, btn MouseButton, isRelease bool, isMotion bool) { + // mouse bit shifts + const ( + bitShift = 0b0000_0100 + bitAlt = 0b0000_1000 + bitCtrl = 0b0001_0000 + bitMotion = 0b0010_0000 + bitWheel = 0b0100_0000 + bitAdd = 0b1000_0000 // additional buttons 8-11 + + bitsMask = 0b0000_0011 + ) + + // Modifiers + if b&bitAlt != 0 { + mod |= ModAlt + } + if b&bitCtrl != 0 { + mod |= ModCtrl + } + if b&bitShift != 0 { + mod |= ModShift + } + + if b&bitAdd != 0 { + btn = MouseBackward + MouseButton(b&bitsMask) + } else if b&bitWheel != 0 { + btn = MouseWheelUp + MouseButton(b&bitsMask) + } else { + btn = MouseLeft + MouseButton(b&bitsMask) + // X10 reports a button release as 0b0000_0011 (3) + if b&bitsMask == bitsMask { + btn = MouseNone + isRelease = true + } + } + + // Motion bit doesn't get reported for wheel events. + if b&bitMotion != 0 && !isWheel(btn) { + isMotion = true + } + + return //nolint:nakedret +} + +// isWheel returns true if the mouse event is a wheel event. +func isWheel(btn MouseButton) bool { + return btn >= MouseWheelUp && btn <= MouseWheelRight +} + +type shiftable interface { + ~uint | ~uint16 | ~uint32 | ~uint64 +} + +func shift[T shiftable](x T) T { + if x > 0xff { + x >>= 8 + } + return x +} + +func colorToHex(c color.Color) string { + if c == nil { + return "" + } + r, g, b, _ := c.RGBA() + return fmt.Sprintf("#%02x%02x%02x", shift(r), shift(g), shift(b)) +} + +func getMaxMin(a, b, c float64) (ma, mi float64) { + if a > b { + ma = a + mi = b + } else { + ma = b + mi = a + } + if c > ma { + ma = c + } else if c < mi { + mi = c + } + return ma, mi +} + +func round(x float64) float64 { + return math.Round(x*1000) / 1000 +} + +// rgbToHSL converts an RGB triple to an HSL triple. +func rgbToHSL(r, g, b uint8) (h, s, l float64) { + // convert uint32 pre-multiplied value to uint8 + // The r,g,b values are divided by 255 to change the range from 0..255 to 0..1: + Rnot := float64(r) / 255 + Gnot := float64(g) / 255 + Bnot := float64(b) / 255 + Cmax, Cmin := getMaxMin(Rnot, Gnot, Bnot) + Δ := Cmax - Cmin + // Lightness calculation: + l = (Cmax + Cmin) / 2 + // Hue and Saturation Calculation: + if Δ == 0 { + h = 0 + s = 0 + } else { + switch Cmax { + case Rnot: + h = 60 * (math.Mod((Gnot-Bnot)/Δ, 6)) + case Gnot: + h = 60 * (((Bnot - Rnot) / Δ) + 2) + case Bnot: + h = 60 * (((Rnot - Gnot) / Δ) + 4) + } + if h < 0 { + h += 360 + } + + s = Δ / (1 - math.Abs((2*l)-1)) + } + + return h, round(s), round(l) +} + +// isDarkColor returns whether the given color is dark. +func isDarkColor(c color.Color) bool { + if c == nil { + return true + } + + r, g, b, _ := c.RGBA() + _, _, l := rgbToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8)) //nolint:gosec + return l < 0.5 +} + +func parseTermcap(data []byte) CapabilityEvent { + // XTGETTCAP + if len(data) == 0 { + return CapabilityEvent{""} + } + + var tc strings.Builder + split := bytes.Split(data, []byte{';'}) + for _, s := range split { + parts := bytes.SplitN(s, []byte{'='}, 2) + if len(parts) == 0 { + return CapabilityEvent{""} + } + + name, err := hex.DecodeString(string(parts[0])) + if err != nil || len(name) == 0 { + continue + } + + var value []byte + if len(parts) > 1 { + value, err = hex.DecodeString(string(parts[1])) + if err != nil { + continue + } + } + + if tc.Len() > 0 { + tc.WriteByte(';') + } + tc.WriteString(string(name)) + if len(value) > 0 { + tc.WriteByte('=') + tc.WriteString(string(value)) + } + } + + return CapabilityEvent{tc.String()} +} + +// parseWin32InputKeyEvent converts a Windows Input Record Key Event into a +// KeyPressEvent, KeyReleaseEvent, or MultiEvent including multiple of the +// former type. +// A special case is when the key is either part of VtInputMode which produces +// vkc == 0, or when a key is a UTF-16 surrogate pair. In both of these cases, +// we need to handle key encoding and properly parse the key event. +func (p *EventDecoder) parseWin32InputKeyEvent(vkc uint16, _ uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) (event Event) { + defer func() { + // Respect the repeat count. + if repeatCount > 1 { + var multi MultiEvent + for i := 0; i < int(repeatCount); i++ { + multi = append(multi, event) + } + event = multi + } + }() + defer func() { + if vkc != 0 { + p.lastCks = cks + } + }() + + var key Key + switch { + case vkc == 0: + // This is either a UTF-16 encoded pair, or an escape sequence waiting + // to be decoded. + if keyDown { + return KeyPressEvent{Code: 0, BaseCode: r, Mod: translateControlKeyState(cks)} + } + return KeyReleaseEvent{Code: 0, BaseCode: r, Mod: translateControlKeyState(cks)} + case vkc == xwindows.VK_BACK: + key.BaseCode = KeyBackspace + case vkc == xwindows.VK_TAB: + key.BaseCode = KeyTab + case vkc == xwindows.VK_RETURN: + key.BaseCode = KeyEnter + case vkc == xwindows.VK_SHIFT: + //nolint:nestif + if cks&xwindows.SHIFT_PRESSED != 0 { + if cks&xwindows.ENHANCED_KEY != 0 { + key.BaseCode = KeyRightShift + } else { + key.BaseCode = KeyLeftShift + } + } else if p.lastCks&xwindows.SHIFT_PRESSED != 0 { + if p.lastCks&xwindows.ENHANCED_KEY != 0 { + key.BaseCode = KeyRightShift + } else { + key.BaseCode = KeyLeftShift + } + } + case vkc == xwindows.VK_CONTROL: + if cks&xwindows.LEFT_CTRL_PRESSED != 0 { + key.BaseCode = KeyLeftCtrl + } else if cks&xwindows.RIGHT_CTRL_PRESSED != 0 { + key.BaseCode = KeyRightCtrl + } else if p.lastCks&xwindows.LEFT_CTRL_PRESSED != 0 { + key.BaseCode = KeyLeftCtrl + } else if p.lastCks&xwindows.RIGHT_CTRL_PRESSED != 0 { + key.BaseCode = KeyRightCtrl + } + case vkc == xwindows.VK_MENU: + if cks&xwindows.LEFT_ALT_PRESSED != 0 { + key.BaseCode = KeyLeftAlt + } else if cks&xwindows.RIGHT_ALT_PRESSED != 0 { + key.BaseCode = KeyRightAlt + } else if p.lastCks&xwindows.LEFT_ALT_PRESSED != 0 { + key.BaseCode = KeyLeftAlt + } else if p.lastCks&xwindows.RIGHT_ALT_PRESSED != 0 { + key.BaseCode = KeyRightAlt + } + case vkc == xwindows.VK_PAUSE: + key.BaseCode = KeyPause + case vkc == xwindows.VK_CAPITAL: + key.BaseCode = KeyCapsLock + case vkc == xwindows.VK_ESCAPE: + key.BaseCode = KeyEscape + case vkc == xwindows.VK_SPACE: + key.BaseCode = KeySpace + case vkc == xwindows.VK_PRIOR: + key.BaseCode = KeyPgUp + case vkc == xwindows.VK_NEXT: + key.BaseCode = KeyPgDown + case vkc == xwindows.VK_END: + key.BaseCode = KeyEnd + case vkc == xwindows.VK_HOME: + key.BaseCode = KeyHome + case vkc == xwindows.VK_LEFT: + key.BaseCode = KeyLeft + case vkc == xwindows.VK_UP: + key.BaseCode = KeyUp + case vkc == xwindows.VK_RIGHT: + key.BaseCode = KeyRight + case vkc == xwindows.VK_DOWN: + key.BaseCode = KeyDown + case vkc == xwindows.VK_SELECT: + key.BaseCode = KeySelect + case vkc == xwindows.VK_SNAPSHOT: + key.BaseCode = KeyPrintScreen + case vkc == xwindows.VK_INSERT: + key.BaseCode = KeyInsert + case vkc == xwindows.VK_DELETE: + key.BaseCode = KeyDelete + case vkc >= '0' && vkc <= '9': + key.BaseCode = rune(vkc) + case vkc >= 'A' && vkc <= 'Z': + // Convert to lowercase. + key.BaseCode = rune(vkc) + 32 + case vkc == xwindows.VK_LWIN: + key.BaseCode = KeyLeftSuper + case vkc == xwindows.VK_RWIN: + key.BaseCode = KeyRightSuper + case vkc == xwindows.VK_APPS: + key.BaseCode = KeyMenu + case vkc >= xwindows.VK_NUMPAD0 && vkc <= xwindows.VK_NUMPAD9: + key.BaseCode = rune(vkc-xwindows.VK_NUMPAD0) + KeyKp0 + key.Text = string('0' + (rune(vkc) - xwindows.VK_NUMPAD0)) + case vkc == xwindows.VK_MULTIPLY: + key.BaseCode = KeyKpMultiply + key.Text = "*" + case vkc == xwindows.VK_ADD: + key.BaseCode = KeyKpPlus + key.Text = "+" + case vkc == xwindows.VK_SEPARATOR: + key.BaseCode = KeyKpComma + key.Text = "," + case vkc == xwindows.VK_SUBTRACT: + key.BaseCode = KeyKpMinus + key.Text = "-" + case vkc == xwindows.VK_DECIMAL: + key.BaseCode = KeyKpDecimal + key.Text = "." + case vkc == xwindows.VK_DIVIDE: + key.BaseCode = KeyKpDivide + key.Text = "/" + case vkc >= xwindows.VK_F1 && vkc <= xwindows.VK_F24: + key.BaseCode = rune(vkc-xwindows.VK_F1) + KeyF1 + case vkc == xwindows.VK_NUMLOCK: + key.BaseCode = KeyNumLock + case vkc == xwindows.VK_SCROLL: + key.BaseCode = KeyScrollLock + case vkc == xwindows.VK_LSHIFT: + key.BaseCode = KeyLeftShift + case vkc == xwindows.VK_RSHIFT: + key.BaseCode = KeyRightShift + case vkc == xwindows.VK_LCONTROL: + key.BaseCode = KeyLeftCtrl + case vkc == xwindows.VK_RCONTROL: + key.BaseCode = KeyRightCtrl + case vkc == xwindows.VK_LMENU: + key.BaseCode = KeyLeftAlt + case vkc == xwindows.VK_RMENU: + key.BaseCode = KeyRightAlt + case vkc == xwindows.VK_VOLUME_MUTE: + key.BaseCode = KeyMute + case vkc == xwindows.VK_VOLUME_DOWN: + key.BaseCode = KeyLowerVol + case vkc == xwindows.VK_VOLUME_UP: + key.BaseCode = KeyRaiseVol + case vkc == xwindows.VK_MEDIA_NEXT_TRACK: + key.BaseCode = KeyMediaNext + case vkc == xwindows.VK_MEDIA_PREV_TRACK: + key.BaseCode = KeyMediaPrev + case vkc == xwindows.VK_MEDIA_STOP: + key.BaseCode = KeyMediaStop + case vkc == xwindows.VK_MEDIA_PLAY_PAUSE: + key.BaseCode = KeyMediaPlayPause + case vkc == xwindows.VK_OEM_1: + key.BaseCode = ';' + case vkc == xwindows.VK_OEM_PLUS: + key.BaseCode = '+' + case vkc == xwindows.VK_OEM_COMMA: + key.BaseCode = ',' + case vkc == xwindows.VK_OEM_MINUS: + key.BaseCode = '-' + case vkc == xwindows.VK_OEM_PERIOD: + key.BaseCode = '.' + case vkc == xwindows.VK_OEM_2: + key.BaseCode = '/' + case vkc == xwindows.VK_OEM_3: + key.BaseCode = '`' + case vkc == xwindows.VK_OEM_4: + key.BaseCode = '[' + case vkc == xwindows.VK_OEM_5: + key.BaseCode = '\\' + case vkc == xwindows.VK_OEM_6: + key.BaseCode = ']' + case vkc == xwindows.VK_OEM_7: + key.BaseCode = '\'' + } + + // AltGr is left ctrl + right alt. On non-US keyboards, this is used to type + // special characters and produce printable events. + // XXX: Should this be a KeyMod? + const altGrPressed = xwindows.LEFT_CTRL_PRESSED | xwindows.RIGHT_ALT_PRESSED + altGr := cks&altGrPressed == altGrPressed + + // Remove these lock keys from the control key state from now on. + cks &^= xwindows.NUMLOCK_ON + cks &^= xwindows.SCROLLLOCK_ON + key.Code = key.BaseCode + if !unicode.IsControl(r) { + key.Code = r + if unicode.IsPrint(key.Code) && (cks == 0 || // no modifiers pressed + cks == xwindows.SHIFT_PRESSED || // shift pressed + cks == xwindows.CAPSLOCK_ON || // caps lock on + cks == (xwindows.SHIFT_PRESSED|xwindows.CAPSLOCK_ON) || // Shift + caps lock pressed + altGr) { // AltGr pressed + // If the control key state is 0, shift is pressed, or caps lock + // then the key event is a printable event i.e. [text] is not empty. + key.Text = string(key.Code) + } + } + + key.Mod = translateControlKeyState(cks) + key = ensureKeyCase(key, cks) + if keyDown { + return KeyPressEvent(key) + } + + return KeyReleaseEvent(key) +} + +// ensureKeyCase ensures that the key's text is in the correct case based on the +// control key state. +func ensureKeyCase(key Key, cks uint32) Key { + if len(key.Text) == 0 { + return key + } + + hasShift := cks&xwindows.SHIFT_PRESSED != 0 + hasCaps := cks&xwindows.CAPSLOCK_ON != 0 + if hasShift || hasCaps { + if unicode.IsLower(key.Code) { + key.ShiftedCode = unicode.ToUpper(key.Code) + key.Text = string(key.ShiftedCode) + } + } else { + if unicode.IsUpper(key.Code) { + key.ShiftedCode = unicode.ToLower(key.Code) + key.Text = string(key.ShiftedCode) + } + } + + return key +} + +// translateControlKeyState translates the control key state from the Windows +// Console API into a Mod bitmask. +func translateControlKeyState(cks uint32) (m KeyMod) { + if cks&xwindows.LEFT_CTRL_PRESSED != 0 || cks&xwindows.RIGHT_CTRL_PRESSED != 0 { + m |= ModCtrl + } + if cks&xwindows.LEFT_ALT_PRESSED != 0 || cks&xwindows.RIGHT_ALT_PRESSED != 0 { + m |= ModAlt + } + if cks&xwindows.SHIFT_PRESSED != 0 { + m |= ModShift + } + if cks&xwindows.CAPSLOCK_ON != 0 { + m |= ModCapsLock + } + if cks&xwindows.NUMLOCK_ON != 0 { + m |= ModNumLock + } + if cks&xwindows.SCROLLLOCK_ON != 0 { + m |= ModScrollLock + } + return +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/doc.go b/vendor/github.com/charmbracelet/ultraviolet/doc.go new file mode 100644 index 000000000..af352053c --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/doc.go @@ -0,0 +1,3 @@ +// Package uv (Ultraviolet) is a library for creating terminal-based user +// interfaces in Go. +package uv diff --git a/vendor/github.com/charmbracelet/ultraviolet/environ.go b/vendor/github.com/charmbracelet/ultraviolet/environ.go new file mode 100644 index 000000000..9622b607a --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/environ.go @@ -0,0 +1,32 @@ +package uv + +import ( + "strings" +) + +// Environ is a slice of strings that represents the environment variables of +// the program. +type Environ []string + +// Getenv returns the value of the environment variable named by the key. If +// the variable is not present in the environment, the value returned will be +// the empty string. +func (p Environ) Getenv(key string) (v string) { + v, _ = p.LookupEnv(key) + return +} + +// LookupEnv retrieves the value of the environment variable named by the key. +// If the variable is present in the environment the value (which may be empty) +// is returned and the boolean is true. Otherwise the returned value will be +// empty and the boolean will be false. +func (p Environ) LookupEnv(key string) (s string, v bool) { + for i := len(p) - 1; i >= 0; i-- { + if strings.HasPrefix(p[i], key+"=") { + s = strings.TrimPrefix(p[i], key+"=") + v = true + break + } + } + return +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/event.go b/vendor/github.com/charmbracelet/ultraviolet/event.go new file mode 100644 index 000000000..bf48d147c --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/event.go @@ -0,0 +1,567 @@ +package uv + +import ( + "context" + "fmt" + "image" + "image/color" + "strings" + + "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/x/ansi/kitty" +) + +// Event represents an input event that can be received from an input source. +type Event interface{} + +// EventStreamer is an interface that defines a method to stream events from an +// input source. It takes a context and a channel to send events to. The +// streamer should block until the context is done or an error occurs. The +// channel should never be closed by the streamer, as it is the responsibility +// of the consumer to close it when done. +type EventStreamer interface { + StreamEvents(ctx context.Context, ch chan<- Event) error +} + +// UnknownEvent represents an unknown event. +type UnknownEvent string + +// String returns a string representation of the unknown event. +func (e UnknownEvent) String() string { + return fmt.Sprintf("%q", string(e)) +} + +// UnknownCsiEvent represents an unknown CSI (Control Sequence Introducer) event. +type UnknownCsiEvent string + +// String returns a string representation of the unknown CSI event. +func (e UnknownCsiEvent) String() string { + return fmt.Sprintf("%q", string(e)) +} + +// UnknownSs3Event represents an unknown SS3 (Single Shift 3) event. +type UnknownSs3Event string + +// String returns a string representation of the unknown SS3 event. +func (e UnknownSs3Event) String() string { + return fmt.Sprintf("%q", string(e)) +} + +// UnknownOscEvent represents an unknown OSC (Operating System Command) event. +type UnknownOscEvent string + +// String returns a string representation of the unknown OSC event. +func (e UnknownOscEvent) String() string { + return fmt.Sprintf("%q", string(e)) +} + +// UnknownDcsEvent represents an unknown DCS (Device Control String) event. +type UnknownDcsEvent string + +// String returns a string representation of the unknown DCS event. +func (e UnknownDcsEvent) String() string { + return fmt.Sprintf("%q", string(e)) +} + +// UnknownSosEvent represents an unknown SOS (Start of String) event. +type UnknownSosEvent string + +// String returns a string representation of the unknown SOS event. +func (e UnknownSosEvent) String() string { + return fmt.Sprintf("%q", string(e)) +} + +// UnknownPmEvent represents an unknown PM (Privacy Message) event. +type UnknownPmEvent string + +// String returns a string representation of the unknown PM event. +func (e UnknownPmEvent) String() string { + return fmt.Sprintf("%q", string(e)) +} + +// UnknownApcEvent represents an unknown APC (Application Program Command) event. +type UnknownApcEvent string + +// String returns a string representation of the unknown APC event. +func (e UnknownApcEvent) String() string { + return fmt.Sprintf("%q", string(e)) +} + +// MultiEvent represents multiple messages event. +type MultiEvent []Event + +// String returns a string representation of the multiple messages event. +func (e MultiEvent) String() string { + var sb strings.Builder + for _, ev := range e { + sb.WriteString(fmt.Sprintf("%v\n", ev)) + } + return sb.String() +} + +// Size represents the size of the terminal window. +type Size struct { + Width int + Height int +} + +// Bounds returns the bounds corresponding to the size. +func (s Size) Bounds() Rectangle { + return Rectangle{ + Min: image.Point{X: 0, Y: 0}, + Max: image.Point{X: s.Width, Y: s.Height}, + } +} + +// WindowSizeEvent represents the window size in cells. +type WindowSizeEvent Size + +// Bounds returns the bounds corresponding to the size. +func (s WindowSizeEvent) Bounds() Rectangle { + return Size(s).Bounds() +} + +// PixelSizeEvent represents the window size in pixels. +type PixelSizeEvent Size + +// Bounds returns the bounds corresponding to the size. +func (s PixelSizeEvent) Bounds() Rectangle { + return Size(s).Bounds() +} + +// CellSizeEvent represents the cell size in pixels. +type CellSizeEvent Size + +// Bounds returns the bounds corresponding to the size. +func (s CellSizeEvent) Bounds() Rectangle { + return Size(s).Bounds() +} + +// KeyPressEvent represents a key press event. +type KeyPressEvent Key + +// MatchString returns true if the [Key] matches one of the given strings. +// +// A string can be a key name like "enter", "tab", "a", or a printable +// character like "1" or " ". It can also have combinations of modifiers like +// "ctrl+a", "shift+enter", "alt+tab", "ctrl+shift+enter", etc. +func (k KeyPressEvent) MatchString(s ...string) bool { + return Key(k).MatchString(s...) +} + +// String implements [fmt.Stringer] and is quite useful for matching key +// events. For details, on what this returns see [Key.String]. +func (k KeyPressEvent) String() string { + return Key(k).String() +} + +// Keystroke returns the keystroke representation of the [Key]. While less type +// safe than looking at the individual fields, it will usually be more +// convenient and readable to use this method when matching against keys. +// +// Note that modifier keys are always printed in the following order: +// - ctrl +// - alt +// - shift +// - meta +// - hyper +// - super +// +// For example, you'll always see "ctrl+shift+alt+a" and never +// "shift+ctrl+alt+a". +func (k KeyPressEvent) Keystroke() string { + return Key(k).Keystroke() +} + +// Key returns the underlying key event. This is a syntactic sugar for casting +// the key event to a [Key]. +func (k KeyPressEvent) Key() Key { + return Key(k) +} + +// KeyReleaseEvent represents a key release event. +type KeyReleaseEvent Key + +// MatchString returns true if the [Key] matches one of the given strings. +// +// A string can be a key name like "enter", "tab", "a", or a printable +// character like "1" or " ". It can also have combinations of modifiers like +// "ctrl+a", "shift+enter", "alt+tab", "ctrl+shift+enter", etc. +func (k KeyReleaseEvent) MatchString(s ...string) bool { + return Key(k).MatchString(s...) +} + +// String implements [fmt.Stringer] and is quite useful for matching key +// events. For details, on what this returns see [Key.String]. +func (k KeyReleaseEvent) String() string { + return Key(k).String() +} + +// Keystroke returns the keystroke representation of the [Key]. While less type +// safe than looking at the individual fields, it will usually be more +// convenient and readable to use this method when matching against keys. +// +// Note that modifier keys are always printed in the following order: +// - ctrl +// - alt +// - shift +// - meta +// - hyper +// - super +// +// For example, you'll always see "ctrl+shift+alt+a" and never +// "shift+ctrl+alt+a". +func (k KeyReleaseEvent) Keystroke() string { + return Key(k).Keystroke() +} + +// Key returns the underlying key event. This is a convenience method and +// syntactic sugar to satisfy the [KeyEvent] interface, and cast the key event to +// [Key]. +func (k KeyReleaseEvent) Key() Key { + return Key(k) +} + +// KeyEvent represents a key event. This can be either a key press or a key +// release event. +type KeyEvent interface { + fmt.Stringer + + // Key returns the underlying key event. + Key() Key +} + +// MouseEvent represents a mouse message. This is a generic mouse message that +// can represent any kind of mouse event. +type MouseEvent interface { + fmt.Stringer + + // Mouse returns the underlying mouse event. + Mouse() Mouse +} + +// MouseClickEvent represents a mouse button click event. +type MouseClickEvent Mouse + +// String returns a string representation of the mouse click event. +func (e MouseClickEvent) String() string { + return Mouse(e).String() +} + +// Mouse returns the underlying mouse event. This is a convenience method and +// syntactic sugar to satisfy the [MouseEvent] interface, and cast the mouse +// event to [Mouse]. +func (e MouseClickEvent) Mouse() Mouse { + return Mouse(e) +} + +// MouseReleaseEvent represents a mouse button release event. +type MouseReleaseEvent Mouse + +// String returns a string representation of the mouse release event. +func (e MouseReleaseEvent) String() string { + return Mouse(e).String() +} + +// Mouse returns the underlying mouse event. This is a convenience method and +// syntactic sugar to satisfy the [MouseEvent] interface, and cast the mouse +// event to [Mouse]. +func (e MouseReleaseEvent) Mouse() Mouse { + return Mouse(e) +} + +// MouseWheelEvent represents a mouse wheel message event. +type MouseWheelEvent Mouse + +// String returns a string representation of the mouse wheel event. +func (e MouseWheelEvent) String() string { + return Mouse(e).String() +} + +// Mouse returns the underlying mouse event. This is a convenience method and +// syntactic sugar to satisfy the [MouseEvent] interface, and cast the mouse +// event to [Mouse]. +func (e MouseWheelEvent) Mouse() Mouse { + return Mouse(e) +} + +// MouseMotionEvent represents a mouse motion event. +type MouseMotionEvent Mouse + +// String returns a string representation of the mouse motion event. +func (e MouseMotionEvent) String() string { + m := Mouse(e) + if m.Button != 0 { + return m.String() + "+motion" + } + return m.String() + "motion" +} + +// Mouse returns the underlying mouse event. This is a convenience method and +// syntactic sugar to satisfy the [MouseEvent] interface, and cast the mouse +// event to [Mouse]. +func (e MouseMotionEvent) Mouse() Mouse { + return Mouse(e) +} + +// CursorPositionEvent represents a cursor position event. Where X is the +// zero-based column and Y is the zero-based row. +type CursorPositionEvent struct { + X, Y int +} + +// FocusEvent represents a terminal focus event. +// This occurs when the terminal gains focus. +type FocusEvent struct{} + +// BlurEvent represents a terminal blur event. +// This occurs when the terminal loses focus. +type BlurEvent struct{} + +// DarkColorSchemeEvent is sent when the operating system is using a dark color +// scheme. This is typically used to notify applications of the current or new +// system color scheme. +type DarkColorSchemeEvent struct{} + +// LightColorSchemeEvent is sent when the operating system is using a light color +// scheme. This is typically used to notify applications of the current or new +// system color scheme. +type LightColorSchemeEvent struct{} + +// PasteEvent is an message that is emitted when a terminal receives pasted text +// using bracketed-paste. +type PasteEvent struct { + // Content is the pasted text content. + Content string +} + +// String returns the pasted content as a string. +func (e PasteEvent) String() string { + return e.Content +} + +// PasteStartEvent is an message that is emitted when the terminal starts the +// bracketed-paste text. +type PasteStartEvent struct{} + +// PasteEndEvent is an message that is emitted when the terminal ends the +// bracketed-paste text. +type PasteEndEvent struct{} + +// TerminalVersionEvent is a message that represents the terminal version. +type TerminalVersionEvent struct { + Name string +} + +// String returns the terminal version as a string. +func (e TerminalVersionEvent) String() string { + return e.Name +} + +// ModifyOtherKeysEvent represents a modifyOtherKeys event. +// +// 0: disable +// 1: enable mode 1 +// 2: enable mode 2 +// +// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_ +// See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys +type ModifyOtherKeysEvent struct { + Mode int +} + +// KittyGraphicsEvent represents a Kitty Graphics response event. +// +// See https://sw.kovidgoyal.net/kitty/graphics-protocol/ +type KittyGraphicsEvent struct { + Options kitty.Options + Payload []byte +} + +// KeyboardEnhancementsEvent represents a keyboard enhancements report event. +type KeyboardEnhancementsEvent struct { + // Flags are the Kitty Keyboard Enhancement flags. + // + // Bit values: + // + // 00000001: Disambiguate escape codes + // 00000010: Report event types + // 00000100: Report alternate keys + // 00001000: Report all keys as escape codes + // 00010000: Report associated text + // + // See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#keyboard-enhancements + Flags int +} + +// Contains reports whether m contains the given enhancements. +func (e KeyboardEnhancementsEvent) Contains(enhancements int) bool { + return e.Flags&enhancements == enhancements +} + +// SupportsKeyDisambiguation returns whether the terminal supports reporting +// disambiguous keys as escape codes. +func (e KeyboardEnhancementsEvent) SupportsKeyDisambiguation() bool { + return e.Flags&ansi.KittyDisambiguateEscapeCodes != 0 +} + +// SupportsKeyReleases returns whether the terminal supports key release +// events. +func (e KeyboardEnhancementsEvent) SupportsKeyReleases() bool { + return e.Flags&ansi.KittyReportEventTypes != 0 +} + +// SupportsUniformKeyLayout returns whether the terminal supports reporting key +// events as though they were on a PC-101 layout. +func (e KeyboardEnhancementsEvent) SupportsUniformKeyLayout() bool { + return e.SupportsKeyDisambiguation() && + e.Flags&ansi.KittyReportAlternateKeys != 0 && + e.Flags&ansi.KittyReportAllKeysAsEscapeCodes != 0 +} + +// PrimaryDeviceAttributesEvent is an event that represents the terminal +// primary device attributes. +// +// Common attributes include: +// - 1 132 columns +// - 2 Printer port +// - 4 Sixel +// - 6 Selective erase +// - 7 Soft character set (DRCS) +// - 8 User-defined keys (UDKs) +// - 9 National replacement character sets (NRCS) (International terminal only) +// - 12 Yugoslavian (SCS) +// - 15 Technical character set +// - 18 Windowing capability +// - 21 Horizontal scrolling +// - 23 Greek +// - 24 Turkish +// - 42 ISO Latin-2 character set +// - 44 PCTerm +// - 45 Soft key map +// - 46 ASCII emulation +// +// See [ansi.PrimaryDeviceAttributes] for more details. +type PrimaryDeviceAttributesEvent []int + +// SecondaryDeviceAttributesEvent is an event that represents the terminal +// secondary device attributes. +// +// See [ansi.SecondaryDeviceAttributes] for more details. +type SecondaryDeviceAttributesEvent []int + +// TertiaryDeviceAttributesEvent is an event that represents the terminal +// tertiary device attributes. +// +// See [ansi.TertiaryDeviceAttributes] for more details. +type TertiaryDeviceAttributesEvent string + +// ModeReportEvent is a message that represents a mode report event (DECRPM). +// +// See: https://vt100.net/docs/vt510-rm/DECRPM.html +type ModeReportEvent struct { + // Mode is the mode number. + Mode ansi.Mode + + // Value is the mode value. + Value ansi.ModeSetting +} + +// ForegroundColorEvent represents a foreground color event. This event is +// emitted when the terminal requests the terminal foreground color using +// [ansi.RequestForegroundColor]. +type ForegroundColorEvent struct{ color.Color } + +// String returns the hex representation of the color. +func (e ForegroundColorEvent) String() string { + return colorToHex(e.Color) +} + +// IsDark returns whether the color is dark. +func (e ForegroundColorEvent) IsDark() bool { + return isDarkColor(e.Color) +} + +// BackgroundColorEvent represents a background color event. This event is +// emitted when the terminal requests the terminal background color using +// [ansi.RequestBackgroundColor]. +type BackgroundColorEvent struct{ color.Color } + +// String returns the hex representation of the color. +func (e BackgroundColorEvent) String() string { + return colorToHex(e) +} + +// IsDark returns whether the color is dark. +func (e BackgroundColorEvent) IsDark() bool { + return isDarkColor(e.Color) +} + +// CursorColorEvent represents a cursor color change event. This event is +// emitted when the program requests the terminal cursor color using +// [ansi.RequestCursorColor]. +type CursorColorEvent struct{ color.Color } + +// String returns the hex representation of the color. +func (e CursorColorEvent) String() string { + return colorToHex(e) +} + +// IsDark returns whether the color is dark. +func (e CursorColorEvent) IsDark() bool { + return isDarkColor(e) +} + +// WindowOpEvent is a window operation (XTWINOPS) report event. This is used to +// report various window operations such as reporting the window size or cell +// size. +type WindowOpEvent struct { + Op int + Args []int +} + +// CapabilityEvent represents a Termcap/Terminfo response event. Termcap +// responses are generated by the terminal in response to RequestTermcap +// (XTGETTCAP) requests. +// +// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands +type CapabilityEvent struct { + Content string +} + +// String returns the capability content. +func (e CapabilityEvent) String() string { + return e.Content +} + +// ClipboardSelection represents a clipboard selection. The most common +// clipboard selections are "system" and "primary" and selections. +type ClipboardSelection = byte + +// Clipboard selections. +const ( + SystemClipboard ClipboardSelection = ansi.SystemClipboard + PrimaryClipboard ClipboardSelection = ansi.PrimaryClipboard +) + +// ClipboardEvent is a clipboard read message event. This message is emitted when +// a terminal receives an OSC52 clipboard read message event. +type ClipboardEvent struct { + Content string + Selection ClipboardSelection +} + +// String returns the string representation of the clipboard message. +func (e ClipboardEvent) String() string { + return e.Content +} + +// Clipboard returns the clipboard selection. This can be either +// [SystemClipboard] 'c' or [PrimaryClipboard] 'p'. +func (e ClipboardEvent) Clipboard() ClipboardSelection { + return e.Selection +} + +// ignoredEvent represents a sequence event that is ignored by the terminal +// reader. This is used to ignore certain sequences that can be canceled. +type ignoredEvent string diff --git a/vendor/github.com/charmbracelet/ultraviolet/key.go b/vendor/github.com/charmbracelet/ultraviolet/key.go new file mode 100644 index 000000000..f3b5f6a57 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/key.go @@ -0,0 +1,762 @@ +package uv + +import ( + "strings" + "unicode" + "unicode/utf8" + + "github.com/charmbracelet/x/ansi" +) + +// KeyMod represents modifier keys. +type KeyMod int + +// Modifier keys. +const ( + ModShift KeyMod = 1 << iota + ModAlt + ModCtrl + ModMeta + + // These modifiers are used with the Kitty protocol. + // XXX: Meta and Super are swapped in the Kitty protocol, + // this is to preserve compatibility with XTerm modifiers. + + ModHyper + ModSuper // Windows/Command keys + + // These are key lock states. + + ModCapsLock + ModNumLock + ModScrollLock // Defined in Windows API only +) + +// Contains reports whether m contains the given modifiers. +// +// Example: +// +// m := ModAlt | ModCtrl +// m.Contains(ModCtrl) // true +// m.Contains(ModAlt | ModCtrl) // true +// m.Contains(ModAlt | ModCtrl | ModShift) // false +func (m KeyMod) Contains(mods KeyMod) bool { + return m&mods == mods +} + +const ( + // KeyExtended is a special key code used to signify that a key event + // contains multiple runes. + KeyExtended = unicode.MaxRune + 1 +) + +// Special key symbols. +const ( + + // Special keys. + + KeyUp rune = KeyExtended + iota + 1 + KeyDown + KeyRight + KeyLeft + KeyBegin + KeyFind + KeyInsert + KeyDelete + KeySelect + KeyPgUp + KeyPgDown + KeyHome + KeyEnd + + // Keypad keys. + + KeyKpEnter + KeyKpEqual + KeyKpMultiply + KeyKpPlus + KeyKpComma + KeyKpMinus + KeyKpDecimal + KeyKpDivide + KeyKp0 + KeyKp1 + KeyKp2 + KeyKp3 + KeyKp4 + KeyKp5 + KeyKp6 + KeyKp7 + KeyKp8 + KeyKp9 + + //nolint:godox + // The following are keys defined in the Kitty keyboard protocol. + // TODO: Investigate the names of these keys. + + KeyKpSep + KeyKpUp + KeyKpDown + KeyKpLeft + KeyKpRight + KeyKpPgUp + KeyKpPgDown + KeyKpHome + KeyKpEnd + KeyKpInsert + KeyKpDelete + KeyKpBegin + + // Function keys. + + KeyF1 + KeyF2 + KeyF3 + KeyF4 + KeyF5 + KeyF6 + KeyF7 + KeyF8 + KeyF9 + KeyF10 + KeyF11 + KeyF12 + KeyF13 + KeyF14 + KeyF15 + KeyF16 + KeyF17 + KeyF18 + KeyF19 + KeyF20 + KeyF21 + KeyF22 + KeyF23 + KeyF24 + KeyF25 + KeyF26 + KeyF27 + KeyF28 + KeyF29 + KeyF30 + KeyF31 + KeyF32 + KeyF33 + KeyF34 + KeyF35 + KeyF36 + KeyF37 + KeyF38 + KeyF39 + KeyF40 + KeyF41 + KeyF42 + KeyF43 + KeyF44 + KeyF45 + KeyF46 + KeyF47 + KeyF48 + KeyF49 + KeyF50 + KeyF51 + KeyF52 + KeyF53 + KeyF54 + KeyF55 + KeyF56 + KeyF57 + KeyF58 + KeyF59 + KeyF60 + KeyF61 + KeyF62 + KeyF63 + + //nolint:godox + // The following are keys defined in the Kitty keyboard protocol. + // TODO: Investigate the names of these keys. + + KeyCapsLock + KeyScrollLock + KeyNumLock + KeyPrintScreen + KeyPause + KeyMenu + + KeyMediaPlay + KeyMediaPause + KeyMediaPlayPause + KeyMediaReverse + KeyMediaStop + KeyMediaFastForward + KeyMediaRewind + KeyMediaNext + KeyMediaPrev + KeyMediaRecord + + KeyLowerVol + KeyRaiseVol + KeyMute + + KeyLeftShift + KeyLeftAlt + KeyLeftCtrl + KeyLeftSuper + KeyLeftHyper + KeyLeftMeta + KeyRightShift + KeyRightAlt + KeyRightCtrl + KeyRightSuper + KeyRightHyper + KeyRightMeta + KeyIsoLevel3Shift + KeyIsoLevel5Shift + + // Special names in C0. + + KeyBackspace = rune(ansi.DEL) + KeyTab = rune(ansi.HT) + KeyEnter = rune(ansi.CR) + KeyReturn = KeyEnter + KeyEscape = rune(ansi.ESC) + KeyEsc = KeyEscape + + // Special names in G0. + + KeySpace = rune(ansi.SP) +) + +// Key represents a Key press or release event. It contains information about +// the Key pressed, like the runes, the type of Key, and the modifiers pressed. +// There are a couple general patterns you could use to check for key presses +// or releases: +// +// // Switch on the string representation of the key (shorter) +// switch ev := ev.(type) { +// case KeyPressEvent: +// switch ev.String() { +// case "enter": +// fmt.Println("you pressed enter!") +// case "a": +// fmt.Println("you pressed a!") +// } +// } +// +// // Switch on the key type (more foolproof) +// switch ev := ev.(type) { +// case KeyEvent: +// // catch both KeyPressEvent and KeyReleaseEvent +// switch key := ev.Key(); key.Code { +// case KeyEnter: +// fmt.Println("you pressed enter!") +// default: +// switch key.Text { +// case "a": +// fmt.Println("you pressed a!") +// } +// } +// } +// +// Note that [Key.Text] will be empty for special keys like [KeyEnter], +// [KeyTab], and for keys that don't represent printable characters like key +// combos with modifier keys. In other words, [Key.Text] is populated only for +// keys that represent printable characters shifted or unshifted (like 'a', +// 'A', '1', '!', etc.). +type Key struct { + // Text contains the actual characters received. This usually the same as + // [Key.Code]. When [Key.Text] is non-empty, it indicates that the key + // pressed represents printable character(s). + Text string + + // Mod represents modifier keys, like [ModCtrl], [ModAlt], and so on. + Mod KeyMod + + // Code represents the key pressed. This is usually a special key like + // [KeyTab], [KeyEnter], [KeyF1], or a printable character like 'a'. + Code rune + + // ShiftedCode is the actual, shifted key pressed by the user. For example, + // if the user presses shift+a, or caps lock is on, [Key.ShiftedCode] will + // be 'A' and [Key.Code] will be 'a'. + // + // In the case of non-latin keyboards, like Arabic, [Key.ShiftedCode] is the + // unshifted key on the keyboard. + // + // This is only available with the Kitty Keyboard Protocol or the Windows + // Console API. + ShiftedCode rune + + // BaseCode is the key pressed according to the standard PC-101 key layout. + // On international keyboards, this is the key that would be pressed if the + // keyboard was set to US PC-101 layout. + // + // For example, if the user presses 'q' on a French AZERTY keyboard, + // [Key.BaseCode] will be 'q'. + // + // This is only available with the Kitty Keyboard Protocol or the Windows + // Console API. + BaseCode rune + + // IsRepeat indicates whether the key is being held down and sending events + // repeatedly. + // + // This is only available with the Kitty Keyboard Protocol or the Windows + // Console API. + IsRepeat bool +} + +// MatchString returns true if the [Key] matches one of the given strings. +// +// A string can be a key name like "enter", "tab", "a", or a printable +// character like "1" or " ". It can also have combinations of modifiers like +// "ctrl+a", "shift+enter", "alt+tab", "ctrl+shift+enter", etc. +func (k Key) MatchString(s ...string) bool { + for _, s := range s { + if keyMatchString(k, s) { + return true + } + } + return false +} + +func keyMatchString(k Key, s string) bool { + var ( + mod KeyMod + code rune + text string + ) + parts := strings.Split(s, "+") + for _, part := range parts { + switch part { + case "ctrl": + mod |= ModCtrl + case "alt": + mod |= ModAlt + case "shift": + mod |= ModShift + case "meta": + mod |= ModMeta + case "hyper": + mod |= ModHyper + case "super": + mod |= ModSuper + case "capslock": + mod |= ModCapsLock + case "scrolllock": + mod |= ModScrollLock + case "numlock": + mod |= ModNumLock + default: + // Check if the part is a key name. + if k, ok := stringKeyType[part]; ok { + code = k + } else { + // Check if the part is a printable character. + if utf8.RuneCountInString(part) == 1 { + code, _ = utf8.DecodeRuneInString(part) + } else { + // Multi-rune key. + code = KeyExtended + text = part + } + } + } + } + + // Check if we have a printable character. + smod := mod &^ (ModShift | ModCapsLock) + if smod == 0 && text == "" && unicode.IsPrint(code) { + if mod&ModShift != 0 || mod&ModCapsLock != 0 { + // Shifted code we need to use uppercase. + text = string(unicode.ToUpper(code)) + } else { + // Otherwise, use the code as is. + text = string(code) + } + } + + // Check if we have a match. + return (k.Mod == mod && k.Code == code) || + (k.Text != "" && k.Text == text) +} + +// String implements [fmt.Stringer] and is quite useful for matching key +// events. It will return the textual representation of the [Key] if there is +// one, otherwise, it will fallback to [Key.Keystroke]. +// +// For example, you'll always get "?" and instead of "shift+/" on a US ANSI +// keyboard. +func (k Key) String() string { + if len(k.Text) > 0 && k.Text != " " { + return k.Text + } + return k.Keystroke() +} + +// Keystroke returns the keystroke representation of the [Key]. While less type +// safe than looking at the individual fields, it will usually be more +// convenient and readable to use this method when matching against keys. +// +// Note that modifier keys are always printed in the following order: +// - ctrl +// - alt +// - shift +// - meta +// - hyper +// - super +// +// For example, you'll always see "ctrl+shift+alt+a" and never +// "shift+ctrl+alt+a". +func (k Key) Keystroke() string { + var sb strings.Builder + if k.Mod.Contains(ModCtrl) && k.Code != KeyLeftCtrl && k.Code != KeyRightCtrl { + sb.WriteString("ctrl+") + } + if k.Mod.Contains(ModAlt) && k.Code != KeyLeftAlt && k.Code != KeyRightAlt { + sb.WriteString("alt+") + } + if k.Mod.Contains(ModShift) && k.Code != KeyLeftShift && k.Code != KeyRightShift { + sb.WriteString("shift+") + } + if k.Mod.Contains(ModMeta) && k.Code != KeyLeftMeta && k.Code != KeyRightMeta { + sb.WriteString("meta+") + } + if k.Mod.Contains(ModHyper) && k.Code != KeyLeftHyper && k.Code != KeyRightHyper { + sb.WriteString("hyper+") + } + if k.Mod.Contains(ModSuper) && k.Code != KeyLeftSuper && k.Code != KeyRightSuper { + sb.WriteString("super+") + } + + if kt, ok := keyTypeString[k.Code]; ok { + sb.WriteString(kt) + } else { + code := k.Code + if k.BaseCode != 0 { + // If a [Key.BaseCode] is present, use it to represent a key using the standard + // PC-101 key layout. + code = k.BaseCode + } + + switch code { + case KeySpace: + // Space is the only invisible printable character. + sb.WriteString("space") + case KeyExtended: + // Write the actual text of the key when the key contains multiple + // runes. + sb.WriteString(k.Text) + default: + sb.WriteRune(code) + } + } + + return sb.String() +} + +var keyTypeString = map[rune]string{ + KeyEnter: "enter", + KeyTab: "tab", + KeyBackspace: "backspace", + KeyEscape: "esc", + KeySpace: "space", + KeyUp: "up", + KeyDown: "down", + KeyLeft: "left", + KeyRight: "right", + KeyBegin: "begin", + KeyFind: "find", + KeyInsert: "insert", + KeyDelete: "delete", + KeySelect: "select", + KeyPgUp: "pgup", + KeyPgDown: "pgdown", + KeyHome: "home", + KeyEnd: "end", + KeyKpEnter: "enter", + KeyKpEqual: "equal", + KeyKpMultiply: "mul", + KeyKpPlus: "plus", + KeyKpComma: "comma", + KeyKpMinus: "minus", + KeyKpDecimal: "period", + KeyKpDivide: "div", + KeyKp0: "0", + KeyKp1: "1", + KeyKp2: "2", + KeyKp3: "3", + KeyKp4: "4", + KeyKp5: "5", + KeyKp6: "6", + KeyKp7: "7", + KeyKp8: "8", + KeyKp9: "9", + + // Kitty keyboard extension + KeyKpSep: "sep", + KeyKpUp: "up", + KeyKpDown: "down", + KeyKpLeft: "left", + KeyKpRight: "right", + KeyKpPgUp: "pgup", + KeyKpPgDown: "pgdown", + KeyKpHome: "home", + KeyKpEnd: "end", + KeyKpInsert: "insert", + KeyKpDelete: "delete", + KeyKpBegin: "begin", + + KeyF1: "f1", + KeyF2: "f2", + KeyF3: "f3", + KeyF4: "f4", + KeyF5: "f5", + KeyF6: "f6", + KeyF7: "f7", + KeyF8: "f8", + KeyF9: "f9", + KeyF10: "f10", + KeyF11: "f11", + KeyF12: "f12", + KeyF13: "f13", + KeyF14: "f14", + KeyF15: "f15", + KeyF16: "f16", + KeyF17: "f17", + KeyF18: "f18", + KeyF19: "f19", + KeyF20: "f20", + KeyF21: "f21", + KeyF22: "f22", + KeyF23: "f23", + KeyF24: "f24", + KeyF25: "f25", + KeyF26: "f26", + KeyF27: "f27", + KeyF28: "f28", + KeyF29: "f29", + KeyF30: "f30", + KeyF31: "f31", + KeyF32: "f32", + KeyF33: "f33", + KeyF34: "f34", + KeyF35: "f35", + KeyF36: "f36", + KeyF37: "f37", + KeyF38: "f38", + KeyF39: "f39", + KeyF40: "f40", + KeyF41: "f41", + KeyF42: "f42", + KeyF43: "f43", + KeyF44: "f44", + KeyF45: "f45", + KeyF46: "f46", + KeyF47: "f47", + KeyF48: "f48", + KeyF49: "f49", + KeyF50: "f50", + KeyF51: "f51", + KeyF52: "f52", + KeyF53: "f53", + KeyF54: "f54", + KeyF55: "f55", + KeyF56: "f56", + KeyF57: "f57", + KeyF58: "f58", + KeyF59: "f59", + KeyF60: "f60", + KeyF61: "f61", + KeyF62: "f62", + KeyF63: "f63", + + // Kitty keyboard extension + KeyCapsLock: "capslock", + KeyScrollLock: "scrolllock", + KeyNumLock: "numlock", + KeyPrintScreen: "printscreen", + KeyPause: "pause", + KeyMenu: "menu", + KeyMediaPlay: "mediaplay", + KeyMediaPause: "mediapause", + KeyMediaPlayPause: "mediaplaypause", + KeyMediaReverse: "mediareverse", + KeyMediaStop: "mediastop", + KeyMediaFastForward: "mediafastforward", + KeyMediaRewind: "mediarewind", + KeyMediaNext: "medianext", + KeyMediaPrev: "mediaprev", + KeyMediaRecord: "mediarecord", + KeyLowerVol: "lowervol", + KeyRaiseVol: "raisevol", + KeyMute: "mute", + KeyLeftShift: "leftshift", + KeyLeftAlt: "leftalt", + KeyLeftCtrl: "leftctrl", + KeyLeftSuper: "leftsuper", + KeyLeftHyper: "lefthyper", + KeyLeftMeta: "leftmeta", + KeyRightShift: "rightshift", + KeyRightAlt: "rightalt", + KeyRightCtrl: "rightctrl", + KeyRightSuper: "rightsuper", + KeyRightHyper: "righthyper", + KeyRightMeta: "rightmeta", + KeyIsoLevel3Shift: "isolevel3shift", + KeyIsoLevel5Shift: "isolevel5shift", +} + +var stringKeyType = map[string]rune{ + "enter": KeyEnter, + "tab": KeyTab, + "backspace": KeyBackspace, + "escape": KeyEscape, + "esc": KeyEscape, + "space": KeySpace, + "up": KeyUp, + "down": KeyDown, + "left": KeyLeft, + "right": KeyRight, + "begin": KeyBegin, + "find": KeyFind, + "insert": KeyInsert, + "delete": KeyDelete, + "select": KeySelect, + "pgup": KeyPgUp, + "pgdown": KeyPgDown, + "home": KeyHome, + "end": KeyEnd, + "kpenter": KeyKpEnter, + "kpequal": KeyKpEqual, + "kpmul": KeyKpMultiply, + "kpplus": KeyKpPlus, + "kpcomma": KeyKpComma, + "kpminus": KeyKpMinus, + "kpperiod": KeyKpDecimal, + "kpdiv": KeyKpDivide, + "kp0": KeyKp0, + "kp1": KeyKp1, + "kp2": KeyKp2, + "kp3": KeyKp3, + "kp4": KeyKp4, + "kp5": KeyKp5, + "kp6": KeyKp6, + "kp7": KeyKp7, + "kp8": KeyKp8, + "kp9": KeyKp9, + + // Kitty keyboard extension + "kpsep": KeyKpSep, + "kpup": KeyKpUp, + "kpdown": KeyKpDown, + "kpleft": KeyKpLeft, + "kpright": KeyKpRight, + "kppgup": KeyKpPgUp, + "kppgdown": KeyKpPgDown, + "kphome": KeyKpHome, + "kpend": KeyKpEnd, + "kpinsert": KeyKpInsert, + "kpdelete": KeyKpDelete, + "kpbegin": KeyKpBegin, + + "f1": KeyF1, + "f2": KeyF2, + "f3": KeyF3, + "f4": KeyF4, + "f5": KeyF5, + "f6": KeyF6, + "f7": KeyF7, + "f8": KeyF8, + "f9": KeyF9, + "f10": KeyF10, + "f11": KeyF11, + "f12": KeyF12, + "f13": KeyF13, + "f14": KeyF14, + "f15": KeyF15, + "f16": KeyF16, + "f17": KeyF17, + "f18": KeyF18, + "f19": KeyF19, + "f20": KeyF20, + "f21": KeyF21, + "f22": KeyF22, + "f23": KeyF23, + "f24": KeyF24, + "f25": KeyF25, + "f26": KeyF26, + "f27": KeyF27, + "f28": KeyF28, + "f29": KeyF29, + "f30": KeyF30, + "f31": KeyF31, + "f32": KeyF32, + "f33": KeyF33, + "f34": KeyF34, + "f35": KeyF35, + "f36": KeyF36, + "f37": KeyF37, + "f38": KeyF38, + "f39": KeyF39, + "f40": KeyF40, + "f41": KeyF41, + "f42": KeyF42, + "f43": KeyF43, + "f44": KeyF44, + "f45": KeyF45, + "f46": KeyF46, + "f47": KeyF47, + "f48": KeyF48, + "f49": KeyF49, + "f50": KeyF50, + "f51": KeyF51, + "f52": KeyF52, + "f53": KeyF53, + "f54": KeyF54, + "f55": KeyF55, + "f56": KeyF56, + "f57": KeyF57, + "f58": KeyF58, + "f59": KeyF59, + "f60": KeyF60, + "f61": KeyF61, + "f62": KeyF62, + "f63": KeyF63, + + // Kitty keyboard extension + "capslock": KeyCapsLock, + "scrolllock": KeyScrollLock, + "numlock": KeyNumLock, + "printscreen": KeyPrintScreen, + "pause": KeyPause, + "menu": KeyMenu, + "mediaplay": KeyMediaPlay, + "mediapause": KeyMediaPause, + "mediaplaypause": KeyMediaPlayPause, + "mediareverse": KeyMediaReverse, + "mediastop": KeyMediaStop, + "mediafastforward": KeyMediaFastForward, + "mediarewind": KeyMediaRewind, + "medianext": KeyMediaNext, + "mediaprev": KeyMediaPrev, + "mediarecord": KeyMediaRecord, + "lowervol": KeyLowerVol, + "raisevol": KeyRaiseVol, + "mute": KeyMute, + "leftshift": KeyLeftShift, + "leftalt": KeyLeftAlt, + "leftctrl": KeyLeftCtrl, + "leftsuper": KeyLeftSuper, + "lefthyper": KeyLeftHyper, + "leftmeta": KeyLeftMeta, + "rightshift": KeyRightShift, + "rightalt": KeyRightAlt, + "rightctrl": KeyRightCtrl, + "rightsuper": KeyRightSuper, + "righthyper": KeyRightHyper, + "rightmeta": KeyRightMeta, + "isolevel3shift": KeyIsoLevel3Shift, + "isolevel5shift": KeyIsoLevel5Shift, +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/key_table.go b/vendor/github.com/charmbracelet/ultraviolet/key_table.go new file mode 100644 index 000000000..285ed70fa --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/key_table.go @@ -0,0 +1,664 @@ +package uv + +import ( + "strconv" + "strings" + + "github.com/charmbracelet/x/ansi" + "github.com/xo/terminfo" +) + +// buildKeysTable builds a table of key sequences and their corresponding key +// events based on the VT100/VT200, XTerm, and Urxvt terminal specs. +func buildKeysTable(flags LegacyKeyEncoding, term string, useTerminfo bool) map[string]Key { + nul := Key{Code: KeySpace, Mod: ModCtrl} // ctrl+@ or ctrl+space + if flags&flagCtrlAt != 0 { + nul = Key{Code: '@', Mod: ModCtrl} + } + + tab := Key{Code: KeyTab} // ctrl+i or tab + if flags&flagCtrlI != 0 { + tab = Key{Code: 'i', Mod: ModCtrl} + } + + enter := Key{Code: KeyEnter} // ctrl+m or enter + if flags&flagCtrlM != 0 { + enter = Key{Code: 'm', Mod: ModCtrl} + } + + esc := Key{Code: KeyEscape} // ctrl+[ or escape + if flags&flagCtrlOpenBracket != 0 { + esc = Key{Code: '[', Mod: ModCtrl} // ctrl+[ or escape + } + + del := Key{Code: KeyBackspace} + if flags&flagBackspace != 0 { + del.Code = KeyDelete + } + + find := Key{Code: KeyHome} + if flags&flagFind != 0 { + find.Code = KeyFind + } + + sel := Key{Code: KeyEnd} + if flags&flagSelect != 0 { + sel.Code = KeySelect + } + + // The following is a table of key sequences and their corresponding key + // events based on the VT100/VT200 terminal specs. + // + // See: https://vt100.net/docs/vt100-ug/chapter3.html#S3.2 + // See: https://vt100.net/docs/vt220-rm/chapter3.html + // + // XXX: These keys may be overwritten by other options like XTerm or + // Terminfo. + table := map[string]Key{ + // C0 control characters + string(byte(ansi.NUL)): nul, + string(byte(ansi.SOH)): {Code: 'a', Mod: ModCtrl}, + string(byte(ansi.STX)): {Code: 'b', Mod: ModCtrl}, + string(byte(ansi.ETX)): {Code: 'c', Mod: ModCtrl}, + string(byte(ansi.EOT)): {Code: 'd', Mod: ModCtrl}, + string(byte(ansi.ENQ)): {Code: 'e', Mod: ModCtrl}, + string(byte(ansi.ACK)): {Code: 'f', Mod: ModCtrl}, + string(byte(ansi.BEL)): {Code: 'g', Mod: ModCtrl}, + string(byte(ansi.BS)): {Code: 'h', Mod: ModCtrl}, + string(byte(ansi.HT)): tab, + string(byte(ansi.LF)): {Code: 'j', Mod: ModCtrl}, + string(byte(ansi.VT)): {Code: 'k', Mod: ModCtrl}, + string(byte(ansi.FF)): {Code: 'l', Mod: ModCtrl}, + string(byte(ansi.CR)): enter, + string(byte(ansi.SO)): {Code: 'n', Mod: ModCtrl}, + string(byte(ansi.SI)): {Code: 'o', Mod: ModCtrl}, + string(byte(ansi.DLE)): {Code: 'p', Mod: ModCtrl}, + string(byte(ansi.DC1)): {Code: 'q', Mod: ModCtrl}, + string(byte(ansi.DC2)): {Code: 'r', Mod: ModCtrl}, + string(byte(ansi.DC3)): {Code: 's', Mod: ModCtrl}, + string(byte(ansi.DC4)): {Code: 't', Mod: ModCtrl}, + string(byte(ansi.NAK)): {Code: 'u', Mod: ModCtrl}, + string(byte(ansi.SYN)): {Code: 'v', Mod: ModCtrl}, + string(byte(ansi.ETB)): {Code: 'w', Mod: ModCtrl}, + string(byte(ansi.CAN)): {Code: 'x', Mod: ModCtrl}, + string(byte(ansi.EM)): {Code: 'y', Mod: ModCtrl}, + string(byte(ansi.SUB)): {Code: 'z', Mod: ModCtrl}, + string(byte(ansi.ESC)): esc, + string(byte(ansi.FS)): {Code: '\\', Mod: ModCtrl}, + string(byte(ansi.GS)): {Code: ']', Mod: ModCtrl}, + string(byte(ansi.RS)): {Code: '^', Mod: ModCtrl}, + string(byte(ansi.US)): {Code: '_', Mod: ModCtrl}, + + // Special keys in G0 + string(byte(ansi.SP)): {Code: KeySpace, Text: " "}, + string(byte(ansi.DEL)): del, + + // Special keys + + "\x1b[Z": {Code: KeyTab, Mod: ModShift}, + + "\x1b[1~": find, + "\x1b[2~": {Code: KeyInsert}, + "\x1b[3~": {Code: KeyDelete}, + "\x1b[4~": sel, + "\x1b[5~": {Code: KeyPgUp}, + "\x1b[6~": {Code: KeyPgDown}, + "\x1b[7~": {Code: KeyHome}, + "\x1b[8~": {Code: KeyEnd}, + + // Normal mode + "\x1b[A": {Code: KeyUp}, + "\x1b[B": {Code: KeyDown}, + "\x1b[C": {Code: KeyRight}, + "\x1b[D": {Code: KeyLeft}, + "\x1b[E": {Code: KeyBegin}, + "\x1b[F": {Code: KeyEnd}, + "\x1b[H": {Code: KeyHome}, + "\x1b[P": {Code: KeyF1}, + "\x1b[Q": {Code: KeyF2}, + "\x1b[R": {Code: KeyF3}, + "\x1b[S": {Code: KeyF4}, + + // Application Cursor Key Mode (DECCKM) + "\x1bOA": {Code: KeyUp}, + "\x1bOB": {Code: KeyDown}, + "\x1bOC": {Code: KeyRight}, + "\x1bOD": {Code: KeyLeft}, + "\x1bOE": {Code: KeyBegin}, + "\x1bOF": {Code: KeyEnd}, + "\x1bOH": {Code: KeyHome}, + "\x1bOP": {Code: KeyF1}, + "\x1bOQ": {Code: KeyF2}, + "\x1bOR": {Code: KeyF3}, + "\x1bOS": {Code: KeyF4}, + + // Keypad Application Mode (DECKPAM) + + "\x1bOM": {Code: KeyKpEnter}, + "\x1bOX": {Code: KeyKpEqual}, + "\x1bOj": {Code: KeyKpMultiply}, + "\x1bOk": {Code: KeyKpPlus}, + "\x1bOl": {Code: KeyKpComma}, + "\x1bOm": {Code: KeyKpMinus}, + "\x1bOn": {Code: KeyKpDecimal}, + "\x1bOo": {Code: KeyKpDivide}, + "\x1bOp": {Code: KeyKp0}, + "\x1bOq": {Code: KeyKp1}, + "\x1bOr": {Code: KeyKp2}, + "\x1bOs": {Code: KeyKp3}, + "\x1bOt": {Code: KeyKp4}, + "\x1bOu": {Code: KeyKp5}, + "\x1bOv": {Code: KeyKp6}, + "\x1bOw": {Code: KeyKp7}, + "\x1bOx": {Code: KeyKp8}, + "\x1bOy": {Code: KeyKp9}, + + // Function keys + + "\x1b[11~": {Code: KeyF1}, + "\x1b[12~": {Code: KeyF2}, + "\x1b[13~": {Code: KeyF3}, + "\x1b[14~": {Code: KeyF4}, + "\x1b[15~": {Code: KeyF5}, + "\x1b[17~": {Code: KeyF6}, + "\x1b[18~": {Code: KeyF7}, + "\x1b[19~": {Code: KeyF8}, + "\x1b[20~": {Code: KeyF9}, + "\x1b[21~": {Code: KeyF10}, + "\x1b[23~": {Code: KeyF11}, + "\x1b[24~": {Code: KeyF12}, + "\x1b[25~": {Code: KeyF13}, + "\x1b[26~": {Code: KeyF14}, + "\x1b[28~": {Code: KeyF15}, + "\x1b[29~": {Code: KeyF16}, + "\x1b[31~": {Code: KeyF17}, + "\x1b[32~": {Code: KeyF18}, + "\x1b[33~": {Code: KeyF19}, + "\x1b[34~": {Code: KeyF20}, + } + + // CSI ~ sequence keys + csiTildeKeys := map[string]Key{ + "1": find, "2": {Code: KeyInsert}, + "3": {Code: KeyDelete}, "4": sel, + "5": {Code: KeyPgUp}, "6": {Code: KeyPgDown}, + "7": {Code: KeyHome}, "8": {Code: KeyEnd}, + // There are no 9 and 10 keys + "11": {Code: KeyF1}, "12": {Code: KeyF2}, + "13": {Code: KeyF3}, "14": {Code: KeyF4}, + "15": {Code: KeyF5}, "17": {Code: KeyF6}, + "18": {Code: KeyF7}, "19": {Code: KeyF8}, + "20": {Code: KeyF9}, "21": {Code: KeyF10}, + "23": {Code: KeyF11}, "24": {Code: KeyF12}, + "25": {Code: KeyF13}, "26": {Code: KeyF14}, + "28": {Code: KeyF15}, "29": {Code: KeyF16}, + "31": {Code: KeyF17}, "32": {Code: KeyF18}, + "33": {Code: KeyF19}, "34": {Code: KeyF20}, + } + + // URxvt keys + // See https://manpages.ubuntu.com/manpages/trusty/man7/urxvt.7.html#key%20codes + table["\x1b[a"] = Key{Code: KeyUp, Mod: ModShift} + table["\x1b[b"] = Key{Code: KeyDown, Mod: ModShift} + table["\x1b[c"] = Key{Code: KeyRight, Mod: ModShift} + table["\x1b[d"] = Key{Code: KeyLeft, Mod: ModShift} + table["\x1bOa"] = Key{Code: KeyUp, Mod: ModCtrl} + table["\x1bOb"] = Key{Code: KeyDown, Mod: ModCtrl} + table["\x1bOc"] = Key{Code: KeyRight, Mod: ModCtrl} + table["\x1bOd"] = Key{Code: KeyLeft, Mod: ModCtrl} + //nolint:godox + // TODO: invistigate if shift-ctrl arrow keys collide with DECCKM keys i.e. + // "\x1bOA", "\x1bOB", "\x1bOC", "\x1bOD" + + // URxvt modifier CSI ~ keys + for k, v := range csiTildeKeys { + key := v + // Normal (no modifier) already defined part of VT100/VT200 + // Shift modifier + key.Mod = ModShift + table["\x1b["+k+"$"] = key + // Ctrl modifier + key.Mod = ModCtrl + table["\x1b["+k+"^"] = key + // Shift-Ctrl modifier + key.Mod = ModShift | ModCtrl + table["\x1b["+k+"@"] = key + } + + // URxvt F keys + // Note: Shift + F1-F10 generates F11-F20. + // This means Shift + F1 and Shift + F2 will generate F11 and F12, the same + // applies to Ctrl + Shift F1 & F2. + // + // P.S. Don't like this? Blame URxvt, configure your terminal to use + // different escapes like XTerm, or switch to a better terminal ¯\_(ツ)_/¯ + // + // See https://manpages.ubuntu.com/manpages/trusty/man7/urxvt.7.html#key%20codes + table["\x1b[23$"] = Key{Code: KeyF11, Mod: ModShift} + table["\x1b[24$"] = Key{Code: KeyF12, Mod: ModShift} + table["\x1b[25$"] = Key{Code: KeyF13, Mod: ModShift} + table["\x1b[26$"] = Key{Code: KeyF14, Mod: ModShift} + table["\x1b[28$"] = Key{Code: KeyF15, Mod: ModShift} + table["\x1b[29$"] = Key{Code: KeyF16, Mod: ModShift} + table["\x1b[31$"] = Key{Code: KeyF17, Mod: ModShift} + table["\x1b[32$"] = Key{Code: KeyF18, Mod: ModShift} + table["\x1b[33$"] = Key{Code: KeyF19, Mod: ModShift} + table["\x1b[34$"] = Key{Code: KeyF20, Mod: ModShift} + table["\x1b[11^"] = Key{Code: KeyF1, Mod: ModCtrl} + table["\x1b[12^"] = Key{Code: KeyF2, Mod: ModCtrl} + table["\x1b[13^"] = Key{Code: KeyF3, Mod: ModCtrl} + table["\x1b[14^"] = Key{Code: KeyF4, Mod: ModCtrl} + table["\x1b[15^"] = Key{Code: KeyF5, Mod: ModCtrl} + table["\x1b[17^"] = Key{Code: KeyF6, Mod: ModCtrl} + table["\x1b[18^"] = Key{Code: KeyF7, Mod: ModCtrl} + table["\x1b[19^"] = Key{Code: KeyF8, Mod: ModCtrl} + table["\x1b[20^"] = Key{Code: KeyF9, Mod: ModCtrl} + table["\x1b[21^"] = Key{Code: KeyF10, Mod: ModCtrl} + table["\x1b[23^"] = Key{Code: KeyF11, Mod: ModCtrl} + table["\x1b[24^"] = Key{Code: KeyF12, Mod: ModCtrl} + table["\x1b[25^"] = Key{Code: KeyF13, Mod: ModCtrl} + table["\x1b[26^"] = Key{Code: KeyF14, Mod: ModCtrl} + table["\x1b[28^"] = Key{Code: KeyF15, Mod: ModCtrl} + table["\x1b[29^"] = Key{Code: KeyF16, Mod: ModCtrl} + table["\x1b[31^"] = Key{Code: KeyF17, Mod: ModCtrl} + table["\x1b[32^"] = Key{Code: KeyF18, Mod: ModCtrl} + table["\x1b[33^"] = Key{Code: KeyF19, Mod: ModCtrl} + table["\x1b[34^"] = Key{Code: KeyF20, Mod: ModCtrl} + table["\x1b[23@"] = Key{Code: KeyF11, Mod: ModShift | ModCtrl} + table["\x1b[24@"] = Key{Code: KeyF12, Mod: ModShift | ModCtrl} + table["\x1b[25@"] = Key{Code: KeyF13, Mod: ModShift | ModCtrl} + table["\x1b[26@"] = Key{Code: KeyF14, Mod: ModShift | ModCtrl} + table["\x1b[28@"] = Key{Code: KeyF15, Mod: ModShift | ModCtrl} + table["\x1b[29@"] = Key{Code: KeyF16, Mod: ModShift | ModCtrl} + table["\x1b[31@"] = Key{Code: KeyF17, Mod: ModShift | ModCtrl} + table["\x1b[32@"] = Key{Code: KeyF18, Mod: ModShift | ModCtrl} + table["\x1b[33@"] = Key{Code: KeyF19, Mod: ModShift | ModCtrl} + table["\x1b[34@"] = Key{Code: KeyF20, Mod: ModShift | ModCtrl} + + // Register Alt + combinations + // XXX: this must come after URxvt but before XTerm keys to register URxvt + // keys with alt modifier + tmap := map[string]Key{} + for seq, key := range table { + key := key + key.Mod |= ModAlt + key.Text = "" // Clear runes + tmap["\x1b"+seq] = key + } + for seq, key := range tmap { + table[seq] = key + } + + // XTerm modifiers + // These are offset by 1 to be compatible with our Mod type. + // See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-PC-Style-Function-Keys + modifiers := []KeyMod{ + ModShift, // 1 + ModAlt, // 2 + ModShift | ModAlt, // 3 + ModCtrl, // 4 + ModShift | ModCtrl, // 5 + ModAlt | ModCtrl, // 6 + ModShift | ModAlt | ModCtrl, // 7 + ModMeta, // 8 + ModMeta | ModShift, // 9 + ModMeta | ModAlt, // 10 + ModMeta | ModShift | ModAlt, // 11 + ModMeta | ModCtrl, // 12 + ModMeta | ModShift | ModCtrl, // 13 + ModMeta | ModAlt | ModCtrl, // 14 + ModMeta | ModShift | ModAlt | ModCtrl, // 15 + } + + // SS3 keypad function keys + ss3FuncKeys := map[string]Key{ + // These are defined in XTerm + // Taken from Foot keymap.h and XTerm modifyOtherKeys + // https://codeberg.org/dnkl/foot/src/branch/master/keymap.h + "M": {Code: KeyKpEnter}, "X": {Code: KeyKpEqual}, + "j": {Code: KeyKpMultiply}, "k": {Code: KeyKpPlus}, + "l": {Code: KeyKpComma}, "m": {Code: KeyKpMinus}, + "n": {Code: KeyKpDecimal}, "o": {Code: KeyKpDivide}, + "p": {Code: KeyKp0}, "q": {Code: KeyKp1}, + "r": {Code: KeyKp2}, "s": {Code: KeyKp3}, + "t": {Code: KeyKp4}, "u": {Code: KeyKp5}, + "v": {Code: KeyKp6}, "w": {Code: KeyKp7}, + "x": {Code: KeyKp8}, "y": {Code: KeyKp9}, + } + + // XTerm keys + csiFuncKeys := map[string]Key{ + "A": {Code: KeyUp}, "B": {Code: KeyDown}, + "C": {Code: KeyRight}, "D": {Code: KeyLeft}, + "E": {Code: KeyBegin}, "F": {Code: KeyEnd}, + "H": {Code: KeyHome}, "P": {Code: KeyF1}, + "Q": {Code: KeyF2}, "R": {Code: KeyF3}, + "S": {Code: KeyF4}, + } + + // CSI 27 ; ; ~ keys defined in XTerm modifyOtherKeys + modifyOtherKeys := map[int]Key{ + ansi.BS: {Code: KeyBackspace}, + ansi.HT: {Code: KeyTab}, + ansi.CR: {Code: KeyEnter}, + ansi.ESC: {Code: KeyEscape}, + ansi.DEL: {Code: KeyBackspace}, + } + + for _, m := range modifiers { + // XTerm modifier offset +1 + xtermMod := strconv.Itoa(int(m) + 1) + + // CSI 1 ; + for k, v := range csiFuncKeys { + // Functions always have a leading 1 param + seq := "\x1b[1;" + xtermMod + k + key := v + key.Mod = m + table[seq] = key + } + // SS3 + for k, v := range ss3FuncKeys { + seq := "\x1bO" + xtermMod + k + key := v + key.Mod = m + table[seq] = key + } + // CSI ; ~ + for k, v := range csiTildeKeys { + seq := "\x1b[" + k + ";" + xtermMod + "~" + key := v + key.Mod = m + table[seq] = key + } + // CSI 27 ; ; ~ + for k, v := range modifyOtherKeys { + code := strconv.Itoa(k) + seq := "\x1b[27;" + xtermMod + ";" + code + "~" + key := v + key.Mod = m + table[seq] = key + } + } + + // Register terminfo keys + // XXX: this might override keys already registered in table + if useTerminfo { + titable := buildTerminfoKeys(flags, term) + for seq, key := range titable { + table[seq] = key + } + } + + return table +} + +func buildTerminfoKeys(flags LegacyKeyEncoding, term string) map[string]Key { + table := make(map[string]Key) + ti, _ := terminfo.Load(term) + if ti == nil { + return table + } + + tiTable := defaultTerminfoKeys(flags) + + // Default keys + for name, seq := range ti.StringCapsShort() { + if !strings.HasPrefix(name, "k") || len(seq) == 0 { + continue + } + + if k, ok := tiTable[name]; ok { + table[string(seq)] = k + } + } + + // Extended keys + for name, seq := range ti.ExtStringCapsShort() { + if !strings.HasPrefix(name, "k") || len(seq) == 0 { + continue + } + + if k, ok := tiTable[name]; ok { + table[string(seq)] = k + } + } + + return table +} + +// This returns a map of terminfo keys to key events. It's a mix of ncurses +// terminfo default and user-defined key capabilities. +// Upper-case caps that are defined in the default terminfo database are +// - kNXT +// - kPRV +// - kHOM +// - kEND +// - kDC +// - kIC +// - kLFT +// - kRIT +// +// See https://man7.org/linux/man-pages/man5/terminfo.5.html +// See https://github.com/mirror/ncurses/blob/master/include/Caps-ncurses +func defaultTerminfoKeys(flags LegacyKeyEncoding) map[string]Key { + keys := map[string]Key{ + "kcuu1": {Code: KeyUp}, + "kUP": {Code: KeyUp, Mod: ModShift}, + "kUP3": {Code: KeyUp, Mod: ModAlt}, + "kUP4": {Code: KeyUp, Mod: ModShift | ModAlt}, + "kUP5": {Code: KeyUp, Mod: ModCtrl}, + "kUP6": {Code: KeyUp, Mod: ModShift | ModCtrl}, + "kUP7": {Code: KeyUp, Mod: ModAlt | ModCtrl}, + "kUP8": {Code: KeyUp, Mod: ModShift | ModAlt | ModCtrl}, + "kcud1": {Code: KeyDown}, + "kDN": {Code: KeyDown, Mod: ModShift}, + "kDN3": {Code: KeyDown, Mod: ModAlt}, + "kDN4": {Code: KeyDown, Mod: ModShift | ModAlt}, + "kDN5": {Code: KeyDown, Mod: ModCtrl}, + "kDN7": {Code: KeyDown, Mod: ModAlt | ModCtrl}, + "kDN6": {Code: KeyDown, Mod: ModShift | ModCtrl}, + "kDN8": {Code: KeyDown, Mod: ModShift | ModAlt | ModCtrl}, + "kcub1": {Code: KeyLeft}, + "kLFT": {Code: KeyLeft, Mod: ModShift}, + "kLFT3": {Code: KeyLeft, Mod: ModAlt}, + "kLFT4": {Code: KeyLeft, Mod: ModShift | ModAlt}, + "kLFT5": {Code: KeyLeft, Mod: ModCtrl}, + "kLFT6": {Code: KeyLeft, Mod: ModShift | ModCtrl}, + "kLFT7": {Code: KeyLeft, Mod: ModAlt | ModCtrl}, + "kLFT8": {Code: KeyLeft, Mod: ModShift | ModAlt | ModCtrl}, + "kcuf1": {Code: KeyRight}, + "kRIT": {Code: KeyRight, Mod: ModShift}, + "kRIT3": {Code: KeyRight, Mod: ModAlt}, + "kRIT4": {Code: KeyRight, Mod: ModShift | ModAlt}, + "kRIT5": {Code: KeyRight, Mod: ModCtrl}, + "kRIT6": {Code: KeyRight, Mod: ModShift | ModCtrl}, + "kRIT7": {Code: KeyRight, Mod: ModAlt | ModCtrl}, + "kRIT8": {Code: KeyRight, Mod: ModShift | ModAlt | ModCtrl}, + "kich1": {Code: KeyInsert}, + "kIC": {Code: KeyInsert, Mod: ModShift}, + "kIC3": {Code: KeyInsert, Mod: ModAlt}, + "kIC4": {Code: KeyInsert, Mod: ModShift | ModAlt}, + "kIC5": {Code: KeyInsert, Mod: ModCtrl}, + "kIC6": {Code: KeyInsert, Mod: ModShift | ModCtrl}, + "kIC7": {Code: KeyInsert, Mod: ModAlt | ModCtrl}, + "kIC8": {Code: KeyInsert, Mod: ModShift | ModAlt | ModCtrl}, + "kdch1": {Code: KeyDelete}, + "kDC": {Code: KeyDelete, Mod: ModShift}, + "kDC3": {Code: KeyDelete, Mod: ModAlt}, + "kDC4": {Code: KeyDelete, Mod: ModShift | ModAlt}, + "kDC5": {Code: KeyDelete, Mod: ModCtrl}, + "kDC6": {Code: KeyDelete, Mod: ModShift | ModCtrl}, + "kDC7": {Code: KeyDelete, Mod: ModAlt | ModCtrl}, + "kDC8": {Code: KeyDelete, Mod: ModShift | ModAlt | ModCtrl}, + "khome": {Code: KeyHome}, + "kHOM": {Code: KeyHome, Mod: ModShift}, + "kHOM3": {Code: KeyHome, Mod: ModAlt}, + "kHOM4": {Code: KeyHome, Mod: ModShift | ModAlt}, + "kHOM5": {Code: KeyHome, Mod: ModCtrl}, + "kHOM6": {Code: KeyHome, Mod: ModShift | ModCtrl}, + "kHOM7": {Code: KeyHome, Mod: ModAlt | ModCtrl}, + "kHOM8": {Code: KeyHome, Mod: ModShift | ModAlt | ModCtrl}, + "kend": {Code: KeyEnd}, + "kEND": {Code: KeyEnd, Mod: ModShift}, + "kEND3": {Code: KeyEnd, Mod: ModAlt}, + "kEND4": {Code: KeyEnd, Mod: ModShift | ModAlt}, + "kEND5": {Code: KeyEnd, Mod: ModCtrl}, + "kEND6": {Code: KeyEnd, Mod: ModShift | ModCtrl}, + "kEND7": {Code: KeyEnd, Mod: ModAlt | ModCtrl}, + "kEND8": {Code: KeyEnd, Mod: ModShift | ModAlt | ModCtrl}, + "kpp": {Code: KeyPgUp}, + "kprv": {Code: KeyPgUp}, + "kPRV": {Code: KeyPgUp, Mod: ModShift}, + "kPRV3": {Code: KeyPgUp, Mod: ModAlt}, + "kPRV4": {Code: KeyPgUp, Mod: ModShift | ModAlt}, + "kPRV5": {Code: KeyPgUp, Mod: ModCtrl}, + "kPRV6": {Code: KeyPgUp, Mod: ModShift | ModCtrl}, + "kPRV7": {Code: KeyPgUp, Mod: ModAlt | ModCtrl}, + "kPRV8": {Code: KeyPgUp, Mod: ModShift | ModAlt | ModCtrl}, + "knp": {Code: KeyPgDown}, + "knxt": {Code: KeyPgDown}, + "kNXT": {Code: KeyPgDown, Mod: ModShift}, + "kNXT3": {Code: KeyPgDown, Mod: ModAlt}, + "kNXT4": {Code: KeyPgDown, Mod: ModShift | ModAlt}, + "kNXT5": {Code: KeyPgDown, Mod: ModCtrl}, + "kNXT6": {Code: KeyPgDown, Mod: ModShift | ModCtrl}, + "kNXT7": {Code: KeyPgDown, Mod: ModAlt | ModCtrl}, + "kNXT8": {Code: KeyPgDown, Mod: ModShift | ModAlt | ModCtrl}, + + "kbs": {Code: KeyBackspace}, + "kcbt": {Code: KeyTab, Mod: ModShift}, + + // Function keys + // This only includes the first 12 function keys. The rest are treated + // as modifiers of the first 12. + // Take a look at XTerm modifyFunctionKeys + // + // XXX: To use unambiguous function keys, use fixterms or kitty clipboard. + // + // See https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyFunctionKeys + // See https://invisible-island.net/xterm/terminfo.html + + "kf1": {Code: KeyF1}, + "kf2": {Code: KeyF2}, + "kf3": {Code: KeyF3}, + "kf4": {Code: KeyF4}, + "kf5": {Code: KeyF5}, + "kf6": {Code: KeyF6}, + "kf7": {Code: KeyF7}, + "kf8": {Code: KeyF8}, + "kf9": {Code: KeyF9}, + "kf10": {Code: KeyF10}, + "kf11": {Code: KeyF11}, + "kf12": {Code: KeyF12}, + "kf13": {Code: KeyF1, Mod: ModShift}, + "kf14": {Code: KeyF2, Mod: ModShift}, + "kf15": {Code: KeyF3, Mod: ModShift}, + "kf16": {Code: KeyF4, Mod: ModShift}, + "kf17": {Code: KeyF5, Mod: ModShift}, + "kf18": {Code: KeyF6, Mod: ModShift}, + "kf19": {Code: KeyF7, Mod: ModShift}, + "kf20": {Code: KeyF8, Mod: ModShift}, + "kf21": {Code: KeyF9, Mod: ModShift}, + "kf22": {Code: KeyF10, Mod: ModShift}, + "kf23": {Code: KeyF11, Mod: ModShift}, + "kf24": {Code: KeyF12, Mod: ModShift}, + "kf25": {Code: KeyF1, Mod: ModCtrl}, + "kf26": {Code: KeyF2, Mod: ModCtrl}, + "kf27": {Code: KeyF3, Mod: ModCtrl}, + "kf28": {Code: KeyF4, Mod: ModCtrl}, + "kf29": {Code: KeyF5, Mod: ModCtrl}, + "kf30": {Code: KeyF6, Mod: ModCtrl}, + "kf31": {Code: KeyF7, Mod: ModCtrl}, + "kf32": {Code: KeyF8, Mod: ModCtrl}, + "kf33": {Code: KeyF9, Mod: ModCtrl}, + "kf34": {Code: KeyF10, Mod: ModCtrl}, + "kf35": {Code: KeyF11, Mod: ModCtrl}, + "kf36": {Code: KeyF12, Mod: ModCtrl}, + "kf37": {Code: KeyF1, Mod: ModShift | ModCtrl}, + "kf38": {Code: KeyF2, Mod: ModShift | ModCtrl}, + "kf39": {Code: KeyF3, Mod: ModShift | ModCtrl}, + "kf40": {Code: KeyF4, Mod: ModShift | ModCtrl}, + "kf41": {Code: KeyF5, Mod: ModShift | ModCtrl}, + "kf42": {Code: KeyF6, Mod: ModShift | ModCtrl}, + "kf43": {Code: KeyF7, Mod: ModShift | ModCtrl}, + "kf44": {Code: KeyF8, Mod: ModShift | ModCtrl}, + "kf45": {Code: KeyF9, Mod: ModShift | ModCtrl}, + "kf46": {Code: KeyF10, Mod: ModShift | ModCtrl}, + "kf47": {Code: KeyF11, Mod: ModShift | ModCtrl}, + "kf48": {Code: KeyF12, Mod: ModShift | ModCtrl}, + "kf49": {Code: KeyF1, Mod: ModAlt}, + "kf50": {Code: KeyF2, Mod: ModAlt}, + "kf51": {Code: KeyF3, Mod: ModAlt}, + "kf52": {Code: KeyF4, Mod: ModAlt}, + "kf53": {Code: KeyF5, Mod: ModAlt}, + "kf54": {Code: KeyF6, Mod: ModAlt}, + "kf55": {Code: KeyF7, Mod: ModAlt}, + "kf56": {Code: KeyF8, Mod: ModAlt}, + "kf57": {Code: KeyF9, Mod: ModAlt}, + "kf58": {Code: KeyF10, Mod: ModAlt}, + "kf59": {Code: KeyF11, Mod: ModAlt}, + "kf60": {Code: KeyF12, Mod: ModAlt}, + "kf61": {Code: KeyF1, Mod: ModShift | ModAlt}, + "kf62": {Code: KeyF2, Mod: ModShift | ModAlt}, + "kf63": {Code: KeyF3, Mod: ModShift | ModAlt}, + } + + // Preserve F keys from F13 to F63 instead of using them for F-keys + // modifiers. + if flags&flagFKeys != 0 { + keys["kf13"] = Key{Code: KeyF13} + keys["kf14"] = Key{Code: KeyF14} + keys["kf15"] = Key{Code: KeyF15} + keys["kf16"] = Key{Code: KeyF16} + keys["kf17"] = Key{Code: KeyF17} + keys["kf18"] = Key{Code: KeyF18} + keys["kf19"] = Key{Code: KeyF19} + keys["kf20"] = Key{Code: KeyF20} + keys["kf21"] = Key{Code: KeyF21} + keys["kf22"] = Key{Code: KeyF22} + keys["kf23"] = Key{Code: KeyF23} + keys["kf24"] = Key{Code: KeyF24} + keys["kf25"] = Key{Code: KeyF25} + keys["kf26"] = Key{Code: KeyF26} + keys["kf27"] = Key{Code: KeyF27} + keys["kf28"] = Key{Code: KeyF28} + keys["kf29"] = Key{Code: KeyF29} + keys["kf30"] = Key{Code: KeyF30} + keys["kf31"] = Key{Code: KeyF31} + keys["kf32"] = Key{Code: KeyF32} + keys["kf33"] = Key{Code: KeyF33} + keys["kf34"] = Key{Code: KeyF34} + keys["kf35"] = Key{Code: KeyF35} + keys["kf36"] = Key{Code: KeyF36} + keys["kf37"] = Key{Code: KeyF37} + keys["kf38"] = Key{Code: KeyF38} + keys["kf39"] = Key{Code: KeyF39} + keys["kf40"] = Key{Code: KeyF40} + keys["kf41"] = Key{Code: KeyF41} + keys["kf42"] = Key{Code: KeyF42} + keys["kf43"] = Key{Code: KeyF43} + keys["kf44"] = Key{Code: KeyF44} + keys["kf45"] = Key{Code: KeyF45} + keys["kf46"] = Key{Code: KeyF46} + keys["kf47"] = Key{Code: KeyF47} + keys["kf48"] = Key{Code: KeyF48} + keys["kf49"] = Key{Code: KeyF49} + keys["kf50"] = Key{Code: KeyF50} + keys["kf51"] = Key{Code: KeyF51} + keys["kf52"] = Key{Code: KeyF52} + keys["kf53"] = Key{Code: KeyF53} + keys["kf54"] = Key{Code: KeyF54} + keys["kf55"] = Key{Code: KeyF55} + keys["kf56"] = Key{Code: KeyF56} + keys["kf57"] = Key{Code: KeyF57} + keys["kf58"] = Key{Code: KeyF58} + keys["kf59"] = Key{Code: KeyF59} + keys["kf60"] = Key{Code: KeyF60} + keys["kf61"] = Key{Code: KeyF61} + keys["kf62"] = Key{Code: KeyF62} + keys["kf63"] = Key{Code: KeyF63} + } + + return keys +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/logger.go b/vendor/github.com/charmbracelet/ultraviolet/logger.go new file mode 100644 index 000000000..abfd86e7a --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/logger.go @@ -0,0 +1,6 @@ +package uv + +// Logger is a simple logger interface. +type Logger interface { + Printf(format string, v ...interface{}) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/mouse.go b/vendor/github.com/charmbracelet/ultraviolet/mouse.go new file mode 100644 index 000000000..e86d94b8d --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/mouse.go @@ -0,0 +1,98 @@ +package uv + +import ( + "github.com/charmbracelet/x/ansi" +) + +// MouseMode represents the mouse mode for the terminal. It is used to enable +// or disable mouse support on the terminal. +type MouseMode byte + +// Mouse modes. +const ( + MouseModeNone MouseMode = iota + MouseModeClick + MouseModeDrag + MouseModeMotion +) + +// MouseButton represents the button that was pressed during a mouse message. +type MouseButton = ansi.MouseButton + +// Mouse event buttons +// +// This is based on X11 mouse button codes. +// +// 1 = left button +// 2 = middle button (pressing the scroll wheel) +// 3 = right button +// 4 = turn scroll wheel up +// 5 = turn scroll wheel down +// 6 = push scroll wheel left +// 7 = push scroll wheel right +// 8 = 4th button (aka browser backward button) +// 9 = 5th button (aka browser forward button) +// 10 +// 11 +// +// Other buttons are not supported. +const ( + MouseNone = ansi.MouseNone + MouseLeft = ansi.MouseLeft + MouseMiddle = ansi.MouseMiddle + MouseRight = ansi.MouseRight + MouseWheelUp = ansi.MouseWheelUp + MouseWheelDown = ansi.MouseWheelDown + MouseWheelLeft = ansi.MouseWheelLeft + MouseWheelRight = ansi.MouseWheelRight + MouseBackward = ansi.MouseBackward + MouseForward = ansi.MouseForward + MouseButton10 = ansi.MouseButton10 + MouseButton11 = ansi.MouseButton11 +) + +// Mouse represents a Mouse message. Use [MouseEvent] to represent all mouse +// messages. +// +// The X and Y coordinates are zero-based, with (0,0) being the upper left +// corner of the terminal. +// +// // Catch all mouse events +// switch Event := Event.(type) { +// case MouseEvent: +// m := Event.Mouse() +// fmt.Println("Mouse event:", m.X, m.Y, m) +// } +// +// // Only catch mouse click events +// switch Event := Event.(type) { +// case MouseClickEvent: +// fmt.Println("Mouse click event:", Event.X, Event.Y, Event) +// } +type Mouse struct { + X, Y int + Button MouseButton + Mod KeyMod +} + +// String returns a string representation of the mouse message. +func (m Mouse) String() (s string) { + if m.Mod.Contains(ModCtrl) { + s += "ctrl+" + } + if m.Mod.Contains(ModAlt) { + s += "alt+" + } + if m.Mod.Contains(ModShift) { + s += "shift+" + } + + str := m.Button.String() + if str == "" { + s += "unknown" + } else if str != "none" { // motion events don't have a button + s += str + } + + return s +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/poll.go b/vendor/github.com/charmbracelet/ultraviolet/poll.go new file mode 100644 index 000000000..209211f58 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/poll.go @@ -0,0 +1,38 @@ +package uv + +import ( + "fmt" + "time" +) + +// pollReader reads data from an [io.Reader] using different native poll APIs +// depending on the operating system. +// +// On Linux, it uses the epoll API. +// On Windows, it uses The Windows I/O and Console APIs. +// On macOS and other BSD-based systems, it will try to use the kqueue API and +// fall back to Unix select if kqueue is not available (e.g., on TTY). +// On other Unix-like systems, it uses the select API. +// On all other systems, it falls back to a simple read loop with a timeout. +type pollReader interface { + // Read reads data from the underlying [io.Reader]. It blocks until data is + // available or an error occurs. + // + // Use [pollReader] to check for data availability before calling Read to + // avoid blocking. + Read(p []byte) (n int, err error) + + // Poll notifies when data is available to read with the given timeout. Use + // a negative duration to wait indefinitely. + Poll(timeout time.Duration) (ready bool, err error) + + // Cancel cancels any ongoing poll or read operations. It returns true if + // an operation was canceled, false otherwise. + Cancel() bool + + // Close closes the reader and releases any resources associated with it. + Close() error +} + +// ErrCanceled is returned when a poll or read operation is canceled. +var ErrCanceled = fmt.Errorf("poll canceled") diff --git a/vendor/github.com/charmbracelet/ultraviolet/poll_bsd.go b/vendor/github.com/charmbracelet/ultraviolet/poll_bsd.go new file mode 100644 index 000000000..947448750 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/poll_bsd.go @@ -0,0 +1,165 @@ +//go:build darwin || freebsd || netbsd || openbsd || dragonfly +// +build darwin freebsd netbsd openbsd dragonfly + +package uv + +import ( + "errors" + "fmt" + "io" + "os" + "strings" + "sync" + "time" + + "golang.org/x/sys/unix" +) + +// newPollReader creates a new pollReader for the given io.Reader. +func newPollReader(reader io.Reader) (pollReader, error) { + file, ok := reader.(File) + if !ok { + return newFallbackReader(reader) + } + + // kqueue returns instantly when polling /dev/tty so fallback to select + if file.Name() == "/dev/tty" { + return newSelectPollReader(reader) + } + + kQueue, err := unix.Kqueue() + if err != nil { + return nil, fmt.Errorf("create kqueue: %w", err) + } + + r := &kqueueReader{ + reader: reader, + file: file, + kQueue: kQueue, + } + + r.cancelSignalReader, r.cancelSignalWriter, err = os.Pipe() + if err != nil { + _ = unix.Close(kQueue) + return nil, err + } + + unix.SetKevent(&r.kQueueEvents[0], int(file.Fd()), unix.EVFILT_READ, unix.EV_ADD) + unix.SetKevent(&r.kQueueEvents[1], int(r.cancelSignalReader.Fd()), unix.EVFILT_READ, unix.EV_ADD) + + return r, nil +} + +// kqueueReader implements pollReader using the BSD kqueue API. +type kqueueReader struct { + reader io.Reader + file File + cancelSignalReader *os.File + cancelSignalWriter *os.File + kQueue int + kQueueEvents [2]unix.Kevent_t + mu sync.Mutex + canceled bool +} + +// Read reads data from the underlying reader. +func (r *kqueueReader) Read(p []byte) (int, error) { + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return 0, ErrCanceled + } + r.mu.Unlock() + + return r.reader.Read(p) +} + +// Poll waits for data to be available to read with the given timeout. +func (r *kqueueReader) Poll(timeout time.Duration) (bool, error) { + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return false, ErrCanceled + } + r.mu.Unlock() + + events := make([]unix.Kevent_t, 1) + + var ts *unix.Timespec + if timeout >= 0 { + t := unix.NsecToTimespec(timeout.Nanoseconds()) + ts = &t + } + + for { + n, err := unix.Kevent(r.kQueue, r.kQueueEvents[:], events, ts) + if errors.Is(err, unix.EINTR) { + continue // try again if the syscall was interrupted + } + + if err != nil { + return false, fmt.Errorf("kevent: %w", err) + } + + if n == 0 { + return false, nil // timeout + } + + break + } + + ident := uint64(events[0].Ident) + switch ident { + case uint64(r.file.Fd()): + return true, nil + case uint64(r.cancelSignalReader.Fd()): + // remove signal from pipe + var b [1]byte + _, errRead := r.cancelSignalReader.Read(b[:]) + if errRead != nil { + return false, fmt.Errorf("reading cancel signal: %w", errRead) + } + return false, ErrCanceled + } + + return false, fmt.Errorf("unknown error") +} + +// Cancel cancels any ongoing poll or read operations. +func (r *kqueueReader) Cancel() bool { + r.mu.Lock() + r.canceled = true + r.mu.Unlock() + + // send cancel signal + _, err := r.cancelSignalWriter.Write([]byte{'c'}) + return err == nil +} + +// Close closes the reader and releases any resources. +func (r *kqueueReader) Close() error { + var errMsgs []string + + // close kqueue + err := unix.Close(r.kQueue) + if err != nil { + errMsgs = append(errMsgs, fmt.Sprintf("closing kqueue: %v", err)) + } + + // close pipe + err = r.cancelSignalWriter.Close() + if err != nil { + errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal writer: %v", err)) + } + + err = r.cancelSignalReader.Close() + if err != nil { + errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal reader: %v", err)) + } + + if len(errMsgs) > 0 { + return fmt.Errorf("%s", strings.Join(errMsgs, ", ")) + } + + return nil +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/poll_default.go b/vendor/github.com/charmbracelet/ultraviolet/poll_default.go new file mode 100644 index 000000000..464c70fb6 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/poll_default.go @@ -0,0 +1,12 @@ +//go:build !linux && !windows && !darwin && !freebsd && !netbsd && !openbsd && !dragonfly && !solaris +// +build !linux,!windows,!darwin,!freebsd,!netbsd,!openbsd,!dragonfly,!solaris + +package uv + +import "io" + +// newPollReader creates a new pollReader for the given io.Reader. +// This is the default implementation for unsupported platforms. +func newPollReader(reader io.Reader) (pollReader, error) { + return newFallbackReader(reader) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/poll_fallback.go b/vendor/github.com/charmbracelet/ultraviolet/poll_fallback.go new file mode 100644 index 000000000..187a1ed85 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/poll_fallback.go @@ -0,0 +1,158 @@ +package uv + +import ( + "bufio" + "io" + "sync" + "time" +) + +// newFallbackReader creates a new fallbackReader for the given io.Reader. +func newFallbackReader(reader io.Reader) (pollReader, error) { + return &fallbackReader{ + reader: bufio.NewReader(reader), + cancelChan: make(chan struct{}), + dataChan: make(chan struct{}, 1), + }, nil +} + +// fallbackReader implements pollReader using goroutines and buffered I/O. +// This is a fallback implementation that works on all platforms. +type fallbackReader struct { + reader *bufio.Reader + cancelChan chan struct{} + dataChan chan struct{} + mu sync.Mutex + canceled bool + started bool +} + +// Read reads data from the underlying reader. +func (r *fallbackReader) Read(p []byte) (int, error) { + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return 0, ErrCanceled + } + r.mu.Unlock() + + n, err := r.reader.Read(p) + // If we get an error during a concurrent cancel, prefer ErrCanceled + if err != nil { + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return 0, ErrCanceled + } + r.mu.Unlock() + } + return n, err +} + +// Poll waits for data to be available to read with the given timeout. +// This implementation starts a background goroutine to check for buffered +// data availability. +func (r *fallbackReader) Poll(timeout time.Duration) (bool, error) { + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return false, ErrCanceled + } + + // Start the background reader goroutine if not already started + if !r.started { + r.started = true + go r.checkBuffered() + } + r.mu.Unlock() + + if timeout < 0 { + // Wait indefinitely + select { + case <-r.dataChan: + // Put it back for next poll/read + select { + case r.dataChan <- struct{}{}: + default: + } + return true, nil + case <-r.cancelChan: + return false, ErrCanceled + } + } + + // Wait with timeout + timer := time.NewTimer(timeout) + defer timer.Stop() + + select { + case <-r.dataChan: + // Put it back for next poll/read + select { + case r.dataChan <- struct{}{}: + default: + } + return true, nil + case <-timer.C: + return false, nil + case <-r.cancelChan: + return false, ErrCanceled + } +} + +// checkBuffered runs in a background goroutine to signal when data is available. +func (r *fallbackReader) checkBuffered() { + for { + select { + case <-r.cancelChan: + return + default: + } + + // Check if data is buffered + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return + } + r.mu.Unlock() + + // Peek at one byte to check if data is available + // This will block until data arrives + _, err := r.reader.Peek(1) + if err != nil { + // If error (including EOF), stop the goroutine + return + } + + // Signal that data is available + select { + case r.dataChan <- struct{}{}: + case <-r.cancelChan: + return + } + + // Wait a bit before checking again to avoid busy loop + time.Sleep(10 * time.Millisecond) + } +} + +// Cancel cancels any ongoing poll or read operations. +func (r *fallbackReader) Cancel() bool { + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return false + } + r.canceled = true + r.mu.Unlock() + + close(r.cancelChan) + return true +} + +// Close closes the reader and releases any resources. +func (r *fallbackReader) Close() error { + r.Cancel() + return nil +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/poll_linux.go b/vendor/github.com/charmbracelet/ultraviolet/poll_linux.go new file mode 100644 index 000000000..bbe2c9fa4 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/poll_linux.go @@ -0,0 +1,175 @@ +//go:build linux +// +build linux + +package uv + +import ( + "errors" + "fmt" + "io" + "os" + "sync" + "time" + + "golang.org/x/sys/unix" +) + +// newPollReader creates a new pollReader for the given io.Reader. +func newPollReader(reader io.Reader) (pollReader, error) { + file, ok := reader.(File) + if !ok { + return newFallbackReader(reader) + } + + epoll, err := unix.EpollCreate1(0) + if err != nil { + return nil, fmt.Errorf("create epoll: %w", err) + } + + r := &epollReader{ + reader: reader, + file: file, + epoll: epoll, + } + + r.cancelSignalReader, r.cancelSignalWriter, err = os.Pipe() + if err != nil { + _ = unix.Close(epoll) + return nil, err + } + + err = unix.EpollCtl(epoll, unix.EPOLL_CTL_ADD, int(file.Fd()), &unix.EpollEvent{ + Events: unix.EPOLLIN, + Fd: int32(file.Fd()), + }) + if err != nil { + _ = unix.Close(epoll) + _ = r.cancelSignalReader.Close() + _ = r.cancelSignalWriter.Close() + return nil, fmt.Errorf("add reader to epoll interest list: %w", err) + } + + err = unix.EpollCtl(epoll, unix.EPOLL_CTL_ADD, int(r.cancelSignalReader.Fd()), &unix.EpollEvent{ + Events: unix.EPOLLIN, + Fd: int32(r.cancelSignalReader.Fd()), + }) + if err != nil { + _ = unix.Close(epoll) + _ = r.cancelSignalReader.Close() + _ = r.cancelSignalWriter.Close() + return nil, fmt.Errorf("add cancel signal to epoll interest list: %w", err) + } + + return r, nil +} + +// epollReader implements pollReader using the Linux epoll API. +type epollReader struct { + reader io.Reader + file File + cancelSignalReader *os.File + cancelSignalWriter *os.File + epoll int + mu sync.Mutex + canceled bool +} + +// Read reads data from the underlying reader. +func (r *epollReader) Read(p []byte) (int, error) { + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return 0, ErrCanceled + } + r.mu.Unlock() + + return r.reader.Read(p) +} + +// Poll waits for data to be available to read with the given timeout. +func (r *epollReader) Poll(timeout time.Duration) (bool, error) { + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return false, ErrCanceled + } + r.mu.Unlock() + + events := make([]unix.EpollEvent, 1) + + timeoutMs := -1 + if timeout >= 0 { + timeoutMs = int(timeout.Milliseconds()) + } + + for { + n, err := unix.EpollWait(r.epoll, events, timeoutMs) + if errors.Is(err, unix.EINTR) { + continue // try again if the syscall was interrupted + } + + if err != nil { + return false, fmt.Errorf("epoll wait: %w", err) + } + + if n == 0 { + return false, nil // timeout + } + + break + } + + switch events[0].Fd { + case int32(r.file.Fd()): + return true, nil + case int32(r.cancelSignalReader.Fd()): + // remove signal from pipe + var b [1]byte + _, readErr := r.cancelSignalReader.Read(b[:]) + if readErr != nil { + return false, fmt.Errorf("reading cancel signal: %w", readErr) + } + return false, ErrCanceled + } + + return false, fmt.Errorf("unknown error") +} + +// Cancel cancels any ongoing poll or read operations. +func (r *epollReader) Cancel() bool { + r.mu.Lock() + r.canceled = true + r.mu.Unlock() + + // send cancel signal + _, err := r.cancelSignalWriter.Write([]byte{'c'}) + return err == nil +} + +// Close closes the reader and releases any resources. +func (r *epollReader) Close() error { + var errMsgs []error + + // close epoll + err := unix.Close(r.epoll) + if err != nil { + errMsgs = append(errMsgs, fmt.Errorf("closing epoll: %w", err)) + } + + // close pipe + err = r.cancelSignalWriter.Close() + if err != nil { + errMsgs = append(errMsgs, fmt.Errorf("closing cancel signal writer: %w", err)) + } + + err = r.cancelSignalReader.Close() + if err != nil { + errMsgs = append(errMsgs, fmt.Errorf("closing cancel signal reader: %w", err)) + } + + if len(errMsgs) > 0 { + return errors.Join(errMsgs...) + } + + return nil +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/poll_select.go b/vendor/github.com/charmbracelet/ultraviolet/poll_select.go new file mode 100644 index 000000000..ff7756a14 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/poll_select.go @@ -0,0 +1,156 @@ +//go:build solaris || darwin || freebsd || netbsd || openbsd || dragonfly +// +build solaris darwin freebsd netbsd openbsd dragonfly + +package uv + +import ( + "errors" + "fmt" + "io" + "os" + "strings" + "sync" + "time" + + "golang.org/x/sys/unix" +) + +// newSelectPollReader creates a new SelectReader for the given io.Reader. +func newSelectPollReader(reader io.Reader) (pollReader, error) { + file, ok := reader.(File) + if !ok || file.Fd() >= unix.FD_SETSIZE { + return newFallbackReader(reader) + } + + r := &selectReader{ + reader: reader, + file: file, + } + + var err error + r.cancelSignalReader, r.cancelSignalWriter, err = os.Pipe() + if err != nil { + return nil, err + } + + return r, nil +} + +// selectReader implements pollReader using the POSIX select API. +type selectReader struct { + reader io.Reader + file File + cancelSignalReader *os.File + cancelSignalWriter *os.File + mu sync.Mutex + canceled bool +} + +// Read reads data from the underlying reader. +func (r *selectReader) Read(p []byte) (int, error) { + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return 0, ErrCanceled + } + r.mu.Unlock() + + return r.reader.Read(p) +} + +// Poll waits for data to be available to read with the given timeout. +func (r *selectReader) Poll(timeout time.Duration) (bool, error) { + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return false, ErrCanceled + } + r.mu.Unlock() + + for { + readerFd := int(r.file.Fd()) + abortFd := int(r.cancelSignalReader.Fd()) + + maxFd := readerFd + if abortFd > maxFd { + maxFd = abortFd + } + + // this is a limitation of the select syscall + if maxFd >= unix.FD_SETSIZE { + return false, fmt.Errorf("cannot select on file descriptor %d which is larger than %d", maxFd, unix.FD_SETSIZE) + } + + fdSet := &unix.FdSet{} + fdSet.Set(readerFd) + fdSet.Set(abortFd) + + var tv *unix.Timeval + if timeout >= 0 { + t := unix.NsecToTimeval(timeout.Nanoseconds()) + tv = &t + } + + n, err := unix.Select(maxFd+1, fdSet, nil, nil, tv) + if errors.Is(err, unix.EINTR) { + continue // try again if the syscall was interrupted + } + + if err != nil { + return false, fmt.Errorf("select: %w", err) + } + + if n == 0 { + return false, nil // timeout + } + + if fdSet.IsSet(abortFd) { + // remove signal from pipe + var b [1]byte + _, readErr := r.cancelSignalReader.Read(b[:]) + if readErr != nil { + return false, fmt.Errorf("reading cancel signal: %w", readErr) + } + return false, ErrCanceled + } + + if fdSet.IsSet(readerFd) { + return true, nil + } + + return false, fmt.Errorf("select returned without setting a file descriptor") + } +} + +// Cancel cancels any ongoing poll or read operations. +func (r *selectReader) Cancel() bool { + r.mu.Lock() + r.canceled = true + r.mu.Unlock() + + // send cancel signal + _, err := r.cancelSignalWriter.Write([]byte{'c'}) + return err == nil +} + +// Close closes the reader and releases any resources. +func (r *selectReader) Close() error { + var errMsgs []string + + // close pipe + err := r.cancelSignalWriter.Close() + if err != nil { + errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal writer: %v", err)) + } + + err = r.cancelSignalReader.Close() + if err != nil { + errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal reader: %v", err)) + } + + if len(errMsgs) > 0 { + return fmt.Errorf("%s", strings.Join(errMsgs, ", ")) + } + + return nil +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/poll_solaris.go b/vendor/github.com/charmbracelet/ultraviolet/poll_solaris.go new file mode 100644 index 000000000..7a753da45 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/poll_solaris.go @@ -0,0 +1,12 @@ +//go:build solaris +// +build solaris + +package uv + +import "io" + +// newPollReader creates a new pollReader for the given io.Reader. +// On Solaris, we use the select API. +func newPollReader(reader io.Reader) (pollReader, error) { + return newSelectPollReader(reader) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/poll_windows.go b/vendor/github.com/charmbracelet/ultraviolet/poll_windows.go new file mode 100644 index 000000000..2e1922eb2 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/poll_windows.go @@ -0,0 +1,217 @@ +//go:build windows +// +build windows + +package uv + +import ( + "fmt" + "io" + "os" + "sync" + "time" + + "golang.org/x/sys/windows" +) + +var fileShareValidFlags uint32 = windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ + +// newPollReader creates a new pollReader for the given io.Reader. +func newPollReader(reader io.Reader) (pollReader, error) { + f, ok := reader.(File) + if !ok || f.Fd() != os.Stdin.Fd() { + return newFallbackReader(reader) + } + + // it is necessary to open CONIN$ (NOT windows.STD_INPUT_HANDLE) in + // overlapped mode to be able to use it with WaitForMultipleObjects. + coninPath, err := windows.UTF16PtrFromString("CONIN$") + if err != nil { + return nil, fmt.Errorf("convert CONIN$ to UTF16: %w", err) + } + + conin, err := windows.CreateFile( + coninPath, + windows.GENERIC_READ|windows.GENERIC_WRITE, + fileShareValidFlags, + nil, + windows.OPEN_EXISTING, + windows.FILE_FLAG_OVERLAPPED, + 0) + if err != nil { + return nil, fmt.Errorf("open CONIN$ in overlapping mode: %w", err) + } + + resetConsole, err := preparePollConsole(conin) + if err != nil { + _ = windows.CloseHandle(conin) + return nil, fmt.Errorf("prepare console: %w", err) + } + + // flush input, otherwise it can contain events which trigger + // WaitForMultipleObjects but which ReadFile cannot read, resulting in an + // un-cancelable read + err = windows.FlushConsoleInputBuffer(conin) + if err != nil { + _ = windows.CloseHandle(conin) + return nil, fmt.Errorf("flush console input buffer: %w", err) + } + + cancelEvent, err := windows.CreateEvent(nil, 0, 0, nil) + if err != nil { + _ = windows.CloseHandle(conin) + return nil, fmt.Errorf("create cancel event: %w", err) + } + + return &conReader{ + reader: reader, + conin: conin, + cancelEvent: cancelEvent, + resetConsole: resetConsole, + blockingReadSignal: make(chan struct{}, 1), + }, nil +} + +// conReader implements pollReader using Windows I/O and Console APIs. +type conReader struct { + reader io.Reader + conin windows.Handle + cancelEvent windows.Handle + resetConsole func() error + blockingReadSignal chan struct{} + mu sync.Mutex + canceled bool +} + +// Read reads data from the underlying reader. +func (r *conReader) Read(p []byte) (int, error) { + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return 0, ErrCanceled + } + r.mu.Unlock() + + return r.reader.Read(p) +} + +// Poll waits for data to be available to read with the given timeout. +func (r *conReader) Poll(timeout time.Duration) (bool, error) { + r.mu.Lock() + if r.canceled { + r.mu.Unlock() + return false, ErrCanceled + } + r.mu.Unlock() + + timeoutMs := uint32(windows.INFINITE) + if timeout >= 0 { + timeoutMs = uint32(timeout.Milliseconds()) + } + + event, err := windows.WaitForMultipleObjects([]windows.Handle{r.conin, r.cancelEvent}, false, timeoutMs) + switch { + case windows.WAIT_OBJECT_0 <= event && event < windows.WAIT_OBJECT_0+2: + if event == windows.WAIT_OBJECT_0+1 { + return false, ErrCanceled + } + + if event == windows.WAIT_OBJECT_0 { + return true, nil + } + + return false, fmt.Errorf("unexpected wait object is ready: %d", event-windows.WAIT_OBJECT_0) + case windows.WAIT_ABANDONED <= event && event < windows.WAIT_ABANDONED+2: + return false, fmt.Errorf("abandoned") + case event == uint32(windows.WAIT_TIMEOUT): + return false, nil + case event == windows.WAIT_FAILED: + return false, fmt.Errorf("failed") + default: + return false, fmt.Errorf("unexpected error: %w", error(err)) + } +} + +// Cancel cancels any ongoing poll or read operations. +// On Windows Terminal, WaitForMultipleObjects sometimes immediately returns +// without input being available. In this case, graceful cancelation is not +// possible and Cancel() returns false. +func (r *conReader) Cancel() bool { + r.mu.Lock() + r.canceled = true + r.mu.Unlock() + + select { + case r.blockingReadSignal <- struct{}{}: + err := windows.SetEvent(r.cancelEvent) + if err != nil { + return false + } + <-r.blockingReadSignal + case <-time.After(100 * time.Millisecond): + // Read() hangs in a GetOverlappedResult which is likely due to + // WaitForMultipleObjects returning without input being available + // so we cannot cancel this ongoing read. + return false + } + + return true +} + +// Close closes the reader and releases any resources. +func (r *conReader) Close() error { + err := windows.CloseHandle(r.cancelEvent) + if err != nil { + return fmt.Errorf("closing cancel event handle: %w", err) + } + + err = r.resetConsole() + if err != nil { + return err + } + + err = windows.CloseHandle(r.conin) + if err != nil { + return fmt.Errorf("closing CONIN$: %w", err) + } + + return nil +} + +func preparePollConsole(input windows.Handle) (reset func() error, err error) { + var originalMode uint32 + + err = windows.GetConsoleMode(input, &originalMode) + if err != nil { + return nil, fmt.Errorf("get console mode: %w", err) + } + + var newMode uint32 + newMode &^= windows.ENABLE_ECHO_INPUT + newMode &^= windows.ENABLE_LINE_INPUT + newMode &^= windows.ENABLE_MOUSE_INPUT + newMode &^= windows.ENABLE_PROCESSED_INPUT + + newMode |= windows.ENABLE_EXTENDED_FLAGS + newMode |= windows.ENABLE_INSERT_MODE + newMode |= windows.ENABLE_QUICK_EDIT_MODE + newMode |= windows.ENABLE_WINDOW_INPUT // Enable window input events + + // Enabling virtual terminal input is necessary for processing certain + // types of input like X10 mouse events and arrows keys with the current + // bytes-based input reader. + newMode |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT + + err = windows.SetConsoleMode(input, newMode) + if err != nil { + return nil, fmt.Errorf("set console mode: %w", err) + } + + return func() error { + err := windows.SetConsoleMode(input, originalMode) + if err != nil { + return fmt.Errorf("reset console mode: %w", err) + } + + return nil + }, nil +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/styled.go b/vendor/github.com/charmbracelet/ultraviolet/styled.go new file mode 100644 index 000000000..540f6463a --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/styled.go @@ -0,0 +1,337 @@ +package uv + +import ( + "bytes" + "image/color" + "strings" + + "github.com/charmbracelet/x/ansi" +) + +// StyledString is a string that can be decomposed into a series of styled +// lines and cells. It is used to disassemble a rendered string with ANSI +// escape codes into a series of cells that can be used in a [Buffer]. +// A StyledString supports reading [ansi.SGR] and [ansi.Hyperlink] escape +// codes. +type StyledString struct { + // Text is the original string that was used to create the styled string. + Text string + // Wrap determines whether the styled string should wrap to the next line. + Wrap bool + // Tail is the string that will be appended to the end of the line when the + // string is truncated i.e. when [StyledString.Wrap] is false. + Tail string +} + +var _ Drawable = (*StyledString)(nil) + +// NewStyledString creates a new [StyledString] for the given method and styled +// string. The method is used to calculate the width of each line. +func NewStyledString(str string) *StyledString { + ss := new(StyledString) + ss.Text = str + return ss +} + +// String returns the text of the styled string. +// +// It implements the [fmt.Stringer] interface. +func (s *StyledString) String() string { + return s.Text +} + +// Lines returns the styled string decomposed into a slice of [Line]s. +func (s *StyledString) Lines(m ansi.Method) []Line { + return printString(nil, m, 0, 0, Rectangle{}, s.Text, false, "") +} + +// Draw renders the styled string to the given buffer at the +// specified area. +func (s *StyledString) Draw(buf Screen, area Rectangle) { + // Clear the area before drawing. + for y := area.Min.Y; y < area.Max.Y; y++ { + for x := area.Min.X; x < area.Max.X; x++ { + buf.SetCell(x, y, nil) + } + } + str := s.Text + // We need to normalize newlines "\n" to "\r\n" to emulate a raw terminal + // output. + str = strings.ReplaceAll(str, "\r\n", "\n") + printString(buf, buf.WidthMethod(), area.Min.X, area.Min.Y, area, str, !s.Wrap, s.Tail) +} + +// Height returns the number of lines in the styled string. This is the number +// of lines that the styled string will occupy when rendered to the screen. +func (s *StyledString) Height() int { + return strings.Count(s.Text, "\n") + 1 +} + +// UnicodeWidth returns the cells width of the widest line in the styled string +// using the [ansi.GraphemeWidth] method. +func (s *StyledString) UnicodeWidth() int { + w, _ := s.widthHeight(ansi.GraphemeWidth) + return w +} + +// WcWidth returns the cells width of the widest line in the styled string +// using the [ansi.WcWidth] method. +func (s *StyledString) WcWidth() int { + w, _ := s.widthHeight(ansi.WcWidth) + return w +} + +func (s *StyledString) widthHeight(m ansi.Method) (w, h int) { + lines := strings.Split(s.Text, "\n") + h = len(lines) + for _, l := range lines { + w = max(w, m.StringWidth(l)) + } + return +} + +// Bounds returns the minimum area that can contain the whole styled string. +func (s *StyledString) Bounds() Rectangle { + w, h := s.widthHeight(ansi.GraphemeWidth) + return Rect(0, 0, w, h) +} + +// printString draws a string starting at the given position. If s is nil, it +// will build and return a slice of [Line]s instead (unwrapped, ignoring bounds). +func printString[T []byte | string]( + s Screen, + m WidthMethod, + x, y int, + bounds Rectangle, str T, + truncate bool, tail string, +) (lines []Line) { + p := ansi.GetParser() + defer ansi.PutParser(p) + + var tailc Cell + if truncate && len(tail) > 0 { + tailc = *NewCell(m, tail) + } + + decoder := ansi.DecodeSequenceWc[T] + if m == ansi.GraphemeWidth { + decoder = ansi.DecodeSequence[T] + } + + if s == nil { + lines = []Line{} + } + + var cell Cell + var style Style + var link Link + var state byte + for len(str) > 0 { + seq, width, n, newState := decoder(str, state, p) + switch width { + case 1, 2, 3, 4: // wide cells can go up to 4 cells wide + cell.Width = width + cell.Content = string(seq) + cell.Style = style + cell.Link = link + + if s == nil { + // Building lines: unwrapped, no bounds + if y >= len(lines) { + lines = append(lines, Line{}) + } + lines[y] = append(lines[y], cell) + x += width + } else { + // Drawing to screen: handle wrapping, truncation, and bounds + if !truncate && x+cell.Width > bounds.Max.X && y+1 < bounds.Max.Y { + // Wrap the string to the width of the window + x = bounds.Min.X + y++ + } + + pos := Pos(x, y) + if pos.In(bounds) { + if truncate && tailc.Width > 0 && x+cell.Width > bounds.Max.X-tailc.Width { + // Truncate the string and append the tail if any. + cell = tailc + cell.Style = style + cell.Link = link + s.SetCell(x, y, &cell) + x += tailc.Width + } else { + // Print the cell to the screen + s.SetCell(x, y, &cell) + x += width + } + } + } + + // Reset cell for next iteration + cell = Cell{} + default: + // Valid sequences always have a non-zero Cmd. + // TODO: Handle cursor movement and other sequences + switch { + case ansi.HasCsiPrefix(seq) && p.Command() == 'm': + // SGR - Select Graphic Rendition + ReadStyle(p.Params(), &style) + case ansi.HasOscPrefix(seq) && p.Command() == 8: + // Hyperlinks + ReadLink(p.Data(), &link) + case ansi.Equal(seq, T("\n")): + if s == nil { + // When building lines, we need to ensure empty lines are represented. + if y >= len(lines) { + lines = append(lines, Line{}) + } + } + y++ + // Always treat a NL as CR-LF similar to Termios ONLCR. + fallthrough + case ansi.Equal(seq, T("\r")): + if s == nil { + x = 0 + } else { + x = bounds.Min.X + } + default: + cell.Content += string(seq) + } + } + + // Advance the state and data + state = newState + str = str[n:] + + if y >= bounds.Max.Y { + // We've reached the bottom of the bounds, stop processing further + // lines. + break + } + } + + // Make sure to set the last cell if it's not empty. + if !cell.IsZero() && s != nil { + s.SetCell(x, y, &cell) + } + + return lines +} + +// ReadStyle reads a Select Graphic Rendition (SGR) escape sequences from a +// list of parameters into pen. +func ReadStyle(params ansi.Params, pen *Style) { + if len(params) == 0 { + *pen = Style{} + return + } + + for i := 0; i < len(params); i++ { + param, hasMore, _ := params.Param(i, 0) + switch param { + case 0: // Reset + *pen = Style{} + case 1: // Bold + pen.Attrs |= AttrBold + case 2: // Dim/Faint + pen.Attrs |= AttrFaint + case 3: // Italic + pen.Attrs |= AttrItalic + case 4: // Underline + nextParam, _, ok := params.Param(i+1, 0) + if hasMore && ok { // Only accept subparameters i.e. separated by ":" + switch nextParam { + case 0, 1, 2, 3, 4, 5: + i++ + switch nextParam { + case 0: // No Underline + pen.Underline = UnderlineStyleNone + case 1: // Single Underline + pen.Underline = UnderlineStyleSingle + case 2: // Double Underline + pen.Underline = UnderlineStyleDouble + case 3: // Curly Underline + pen.Underline = UnderlineStyleCurly + case 4: // Dotted Underline + pen.Underline = UnderlineStyleDotted + case 5: // Dashed Underline + pen.Underline = UnderlineStyleDashed + } + } + } else { + // Single Underline + pen.Underline = UnderlineStyleSingle + } + case 5: // Slow Blink + pen.Attrs |= AttrBlink + case 6: // Rapid Blink + pen.Attrs |= AttrRapidBlink + case 7: // Reverse + pen.Attrs |= AttrReverse + case 8: // Conceal + pen.Attrs |= AttrConceal + case 9: // Crossed-out/Strikethrough + pen.Attrs |= AttrStrikethrough + case 22: // Normal Intensity (not bold or faint) + pen.Attrs &^= (AttrBold | AttrFaint) + case 23: // Not italic, not Fraktur + pen.Attrs &^= AttrItalic + case 24: // Not underlined + pen.Underline = UnderlineStyleNone + case 25: // Blink off + pen.Attrs &^= (AttrBlink | AttrRapidBlink) + case 27: // Positive (not reverse) + pen.Attrs &^= AttrReverse + case 28: // Reveal + pen.Attrs &^= AttrConceal + case 29: // Not crossed out + pen.Attrs &^= AttrStrikethrough + case 30, 31, 32, 33, 34, 35, 36, 37: // Set foreground + pen.Fg = ansi.Black + ansi.BasicColor(param-30) //nolint:gosec + case 38: // Set foreground 256 or truecolor + var c color.Color + n := ansi.ReadStyleColor(params[i:], &c) + if n > 0 { + pen.Fg = c + i += n - 1 + } + case 39: // Default foreground + pen.Fg = nil + case 40, 41, 42, 43, 44, 45, 46, 47: // Set background + pen.Bg = ansi.Black + ansi.BasicColor(param-40) //nolint:gosec + case 48: // Set background 256 or truecolor + var c color.Color + n := ansi.ReadStyleColor(params[i:], &c) + if n > 0 { + pen.Bg = c + i += n - 1 + } + case 49: // Default Background + pen.Bg = nil + case 58: // Set underline color + var c color.Color + n := ansi.ReadStyleColor(params[i:], &c) + if n > 0 { + pen.UnderlineColor = c + i += n - 1 + } + case 59: // Default underline color + pen.UnderlineColor = nil + case 90, 91, 92, 93, 94, 95, 96, 97: // Set bright foreground + pen.Fg = ansi.BrightBlack + ansi.BasicColor(param-90) //nolint:gosec + case 100, 101, 102, 103, 104, 105, 106, 107: // Set bright background + pen.Bg = ansi.BrightBlack + ansi.BasicColor(param-100) //nolint:gosec + } + } +} + +// ReadLink reads a hyperlink escape sequence from a data buffer into link. +func ReadLink(p []byte, link *Link) { + params := bytes.Split(p, []byte{';'}) + if len(params) != 3 { + return + } + link.Params = string(params[1]) + link.URL = string(params[2]) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/tabstop.go b/vendor/github.com/charmbracelet/ultraviolet/tabstop.go new file mode 100644 index 000000000..ce13814bc --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/tabstop.go @@ -0,0 +1,142 @@ +package uv + +// DefaultTabInterval is the default tab interval. +const DefaultTabInterval = 8 + +// TabStops represents horizontal line tab stops. +type TabStops struct { + stops []int + interval int + width int +} + +// NewTabStops creates a new set of tab stops from a number of columns and an +// interval. +func NewTabStops(width, interval int) *TabStops { + ts := new(TabStops) + ts.interval = interval + ts.width = width + ts.stops = make([]int, (width+(interval-1))/interval) + ts.init(0, width) + return ts +} + +// DefaultTabStops creates a new set of tab stops with the default interval. +func DefaultTabStops(cols int) *TabStops { + return NewTabStops(cols, DefaultTabInterval) +} + +// Resize resizes the tab stops to the given width. +func (ts *TabStops) Resize(width int) { + if width == ts.width { + return + } + + if width < ts.width { + size := (width + (ts.interval - 1)) / ts.interval + ts.stops = ts.stops[:size] + } else { + size := (width - ts.width + (ts.interval - 1)) / ts.interval + ts.stops = append(ts.stops, make([]int, size)...) + } + + ts.init(ts.width, width) + ts.width = width +} + +// Width returns the width of the screen that the tab stops are set for. +func (ts *TabStops) Width() int { + return ts.width +} + +// IsStop returns true if the given column is a tab stop. +func (ts TabStops) IsStop(col int) bool { + mask := ts.mask(col) + i := col >> 3 + if i < 0 || i >= len(ts.stops) { + return false + } + return ts.stops[i]&mask != 0 +} + +// Next returns the next tab stop after the given column. +func (ts TabStops) Next(col int) int { + return ts.Find(col, 1) +} + +// Prev returns the previous tab stop before the given column. +func (ts TabStops) Prev(col int) int { + return ts.Find(col, -1) +} + +// Find returns the prev/next tab stop before/after the given column and delta. +// If delta is positive, it returns the next tab stop after the given column. +// If delta is negative, it returns the previous tab stop before the given column. +// If delta is zero, it returns the given column. +func (ts TabStops) Find(col, delta int) int { + if delta == 0 { + return col + } + + var prev bool + count := delta + if count < 0 { + count = -count + prev = true + } + + for count > 0 { + if !prev { + if col >= ts.width-1 { + return col + } + + col++ + } else { + if col < 1 { + return col + } + + col-- + } + + if ts.IsStop(col) { + count-- + } + } + + return col +} + +// Set adds a tab stop at the given column. +func (ts *TabStops) Set(col int) { + mask := ts.mask(col) + ts.stops[col>>3] |= mask +} + +// Reset removes the tab stop at the given column. +func (ts *TabStops) Reset(col int) { + mask := ts.mask(col) + ts.stops[col>>3] &= ^mask +} + +// Clear removes all tab stops. +func (ts *TabStops) Clear() { + ts.stops = make([]int, len(ts.stops)) +} + +// mask returns the mask for the given column. +func (ts *TabStops) mask(col int) int { + return 1 << (col & (ts.interval - 1)) +} + +// init initializes the tab stops starting from col until width. +func (ts *TabStops) init(col, width int) { + for x := col; x < width; x++ { + if x%ts.interval == 0 { + ts.Set(x) + } else { + ts.Reset(x) + } + } +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal.go b/vendor/github.com/charmbracelet/ultraviolet/terminal.go new file mode 100644 index 000000000..14c735a4d --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal.go @@ -0,0 +1,350 @@ +package uv + +import ( + "fmt" + "os" + "os/signal" + "time" + + "golang.org/x/sync/errgroup" +) + +// DefaultBufferSize is the default size of the input buffer used for reading +// terminal events. +const DefaultBufferSize = 4096 + +// DefaultEventTimeout is the default duration to wait for input events before +// timing out. +const DefaultEventTimeout = 100 * time.Millisecond + +// Options represents options for creating a new [Terminal]. +type Options struct { + // BufferSize is the size of the input buffer used for reading terminal + // events. If zero, [DefaultBufferSize] is used. + BufferSize int + + // EventTimeout is the duration to wait for input events before timing out. + // If zero, a default of 100 milliseconds is used. + EventTimeout time.Duration + + // LegacyKeyEncoding represents any legacy key encoding ambiguities. By + // default, the terminal will use its preferred key encoding settings. + LegacyKeyEncoding LegacyKeyEncoding + + // LookupKeys whether to use a lookup table for common key sequences. If + // true, the terminal will use a lookup table to quickly identify common + // key sequences, reducing the need for more complex decoding logic. This + // can improve performance for common key sequences at the cost of + // increased memory usage. + // + // This is enabled by default. + LookupKeys bool + + // UseTerminfoKeys whether to use terminfo databases key definitions to + // build up the keys lookup table. If true, the terminal will use terminfo + // databases key definitions to build up the keys lookup table, which can + // provide more accurate key mappings for legacy non-xterm like terminals. + // + // This won't take effect if [TerminalOptions.LookupKeys] is false, since + // the lookup table won't be used. + // + // This is disabled by default. + UseTerminfoKeys bool +} + +// DefaultOptions returns the default [Terminal] options. +func DefaultOptions() *Options { + return &Options{ + BufferSize: DefaultBufferSize, + EventTimeout: DefaultEventTimeout, + LookupKeys: true, + } +} + +// Terminal represents an interactive terminal application. +type Terminal struct { + con Console + opts *Options + scr *TerminalScreen + pr pollReader + buf []byte + evc chan Event + errg errgroup.Group + winch chan os.Signal + donec chan struct{} + logger Logger +} + +// DefaultTerminal creates a new [Terminal] instance using the default standard +// console and the given options. Options can be nil to use the default +// options. +// +// This is a convenience function for creating a terminal that uses the +// standard input and output file descriptors. +func DefaultTerminal() *Terminal { + return NewTerminal(nil, nil) +} + +// ControllingTerminal creates a new [Terminal] instance using the controlling +// terminal's input and output file descriptors. +// Options can be nil to use the default options. +// +// This is a convenience function for creating a terminal that uses the +// controlling TTY of the current process. +func ControllingTerminal() (*Terminal, error) { + con, err := ControllingConsole() + if err != nil { + return nil, err + } + return NewTerminal(con, nil), nil +} + +// NewTerminal creates a new [Terminal] instance with the given console and +// options. +// Options can be nil to use the default options. +func NewTerminal(con Console, opts *Options) *Terminal { + t := &Terminal{} + if con == nil { + con = DefaultConsole() + } + if opts == nil { + opts = DefaultOptions() + } + if opts.BufferSize <= 0 { + opts.BufferSize = DefaultBufferSize + } + if opts.EventTimeout <= 0 { + opts.EventTimeout = DefaultEventTimeout + } + t.con = con + t.opts = opts + t.scr = NewTerminalScreen(t.con.Writer(), t.con.Environ()) + t.buf = make([]byte, opts.BufferSize) + // These channels never close during the terminal's lifetime. + t.evc = make(chan Event) + t.winch = make(chan os.Signal, 1) // buffered to avoid missing signals + return t +} + +// SetLogger sets the terminal's logger for tracing I/O operations. +func (t *Terminal) SetLogger(logger Logger) { + t.logger = logger + t.scr.rend.SetLogger(logger) +} + +// Screen returns the terminal's screen. +func (t *Terminal) Screen() *TerminalScreen { + return t.scr +} + +// Events returns the terminal's event channel. +func (t *Terminal) Events() <-chan Event { + return t.evc +} + +// Start starts the terminal application event loop. This is a non-blocking +// call. Use [Terminal.Wait] to wait for the terminal to exit. +func (t *Terminal) Start() error { + _, err := t.con.MakeRaw() + if err != nil { + return fmt.Errorf("failed to set terminal to raw mode: %w", err) + } + + evs := newEventScanner() + evs.lookup = t.opts.LookupKeys + if evs.lookup { + evs.table = buildKeysTable(t.opts.LegacyKeyEncoding, t.con.Getenv("TERM"), t.opts.UseTerminfoKeys) + } + if t.logger != nil { + evs.setLogger(t.logger) + } + bufc := make(chan []byte) + t.donec = make(chan struct{}) + t.pr, err = newPollReader(t.con.Reader()) + if err != nil { + return fmt.Errorf("failed to create poll reader: %w", err) + } + + // input loop + t.errg.Go(func() error { + for { + n, err := t.pr.Read(t.buf) + if err != nil { + return fmt.Errorf("reading terminal input: %w", err) + } + select { + case bufc <- t.buf[:n]: + case <-t.donec: + return nil + } + } + }) + + // event loop + sendEvents := func(buf []byte, expired bool) int { + n, events := evs.scanEvents(buf, expired) + for _, ev := range events { + t.SendEvent(ev) + } + return n + } + t.errg.Go(func() error { + var buf []byte + timer := time.NewTimer(t.opts.EventTimeout) + timeout := time.Now().Add(t.opts.EventTimeout) + + for { + select { + case <-t.donec: + return nil + case <-timer.C: + expired := len(buf) > 0 && time.Now().After(timeout) + n := sendEvents(buf, expired) + if n > 0 { + buf = buf[min(n, len(buf)):] + } + if len(buf) > 0 { + timer.Reset(t.opts.EventTimeout) + } + case data := <-bufc: + buf = append(buf, data...) + n := sendEvents(buf, false) + timeout = time.Now().Add(t.opts.EventTimeout) + timer.Stop() + if n > 0 { + buf = buf[min(n, len(buf)):] + } + if len(buf) > 0 { + timer.Reset(t.opts.EventTimeout) + } + } + } + }) + + sendWinsize := func() error { + ws, err := t.con.GetWinsize() + if err != nil { + return fmt.Errorf("getting terminal size: %w", err) + } + if ws.Col > 0 && ws.Row > 0 { + t.SendEvent(WindowSizeEvent{ + Width: int(ws.Col), + Height: int(ws.Row), + }) + } + if ws.Xpixel > 0 && ws.Ypixel > 0 { + t.SendEvent(PixelSizeEvent{ + Width: int(ws.Xpixel), + Height: int(ws.Ypixel), + }) + } + return nil + } + + // winch handler + NotifyWinch(t.winch) + t.errg.Go(func() error { + for { + select { + case <-t.donec: + return nil + case <-t.winch: + if err := sendWinsize(); err != nil { + return err + } + } + } + }) + + // init window size + t.errg.Go(func() error { + if err := sendWinsize(); err != nil { + return err + } + return nil + }) + + // Restore any previous screen state. + if err := t.scr.Restore(); err != nil { + return fmt.Errorf("failed to restore terminal screen: %w", err) + } + if err := t.scr.Flush(); err != nil { + return fmt.Errorf("failed to flush terminal screen: %w", err) + } + + return nil +} + +// Wait waits for the terminal event loop to exit and returns any error that +// occurred. +func (t *Terminal) Wait() error { + if err := t.errg.Wait(); err != nil { + return fmt.Errorf("terminal event loop error: %w", err) + } + return nil +} + +// Stop stops the terminal event loop. It is safe to call Stop without a +// prior [Terminal.Start], and safe to call Stop multiple times in a row. +// After Stop returns, [Terminal.Start] may be called again to resume. +func (t *Terminal) Stop() error { + if t.donec != nil { + select { + case <-t.donec: + // Already closed. + default: + close(t.donec) + } + } + if t.winch != nil { + signal.Stop(t.winch) + } + if t.pr != nil { + t.pr.Cancel() + _ = t.pr.Close() + t.pr = nil + } + if err := t.scr.Reset(); err != nil { + _ = t.scr.Flush() + _ = t.con.Restore() + return fmt.Errorf("failed to reset terminal screen: %w", err) + } + if err := t.scr.Flush(); err != nil { + _ = t.con.Restore() + return fmt.Errorf("failed to flush terminal screen: %w", err) + } + if err := t.con.Restore(); err != nil { + return fmt.Errorf("failed to restore terminal state: %w", err) + } + return nil +} + +// SendEvent sends an event to the terminal's event channel. +// +// This can be used to inject custom events into the terminal's event loop, +// such as timer events, signals, or application-specific events. +func (t *Terminal) SendEvent(e Event) { + select { + case t.evc <- e: + case <-t.donec: + } +} + +// Write writes data directly to the terminal's console output. +// +// This is a low-level operation that bypasses the terminal screen buffering +// and writes directly to the console output handler, usually [os.Stdout] or +// the controlling TTY. +func (t *Terminal) Write(p []byte) (n int, err error) { + return t.con.Write(p) +} + +// Read reads data from the terminal's console input. +// +// This is a low-level operation that bypasses the terminal event processing +// and reads directly from the console input handler, usually [os.Stdin] or the +// controlling TTY. Use this method with caution, as it may interfere with the +// terminal's event loop and screen management. +func (t *Terminal) Read(p []byte) (n int, err error) { + return t.con.Read(p) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_bsdly.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_bsdly.go new file mode 100644 index 000000000..6f64e6566 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_bsdly.go @@ -0,0 +1,10 @@ +//go:build darwin || linux || aix +// +build darwin linux aix + +package uv + +import "golang.org/x/sys/unix" + +func supportsBackspace(lflag uint64) bool { + return lflag&unix.BSDLY == unix.BS0 +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_bsdly_other.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_bsdly_other.go new file mode 100644 index 000000000..24bb50d76 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_bsdly_other.go @@ -0,0 +1,8 @@ +//go:build !darwin && !linux && !aix && !windows +// +build !darwin,!linux,!aix,!windows + +package uv + +func supportsBackspace(uint64) bool { + return false +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_other.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_other.go new file mode 100644 index 000000000..9130fcc4c --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_other.go @@ -0,0 +1,22 @@ +//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !aix && !windows +// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!aix,!windows + +package uv + +import "github.com/charmbracelet/x/term" + +func makeRaw(_, _ term.File) (inTtyState, outTtyState *term.State, err error) { + return nil, nil, ErrPlatformNotSupported +} + +func getSize(_, _ term.File) (w, h int, err error) { + return 0, 0, ErrPlatformNotSupported +} + +func getWinsize(_, _ term.File) (ws Winsize, err error) { + return Winsize{}, ErrPlatformNotSupported +} + +func optimizeMovements(*term.State) (useTabs, useBspace bool) { + return false, false +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go new file mode 100644 index 000000000..b3b79cd00 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go @@ -0,0 +1,503 @@ +package uv + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "strconv" + "strings" + "sync" + "time" + "unicode" + "unicode/utf16" + "unicode/utf8" + + "github.com/charmbracelet/x/ansi" + "github.com/muesli/cancelreader" + "github.com/rivo/uniseg" +) + +// ErrReaderNotStarted is returned when the reader has not been started yet. +var ErrReaderNotStarted = fmt.Errorf("reader not started") + +// DefaultEscTimeout is the default timeout at which the [TerminalReader] will +// process ESC sequences. It is set to 50 milliseconds. +const DefaultEscTimeout = 50 * time.Millisecond + +// TerminalReader represents an input event loop that reads input events from +// a reader and parses them into human-readable events. It supports +// reading escape sequences, mouse events, and bracketed paste mode. +type TerminalReader struct { + EventDecoder + + // MouseMode determines whether mouse events are enabled or not. This is a + // platform-specific feature and is only available on Windows. When this is + // true, the reader will be initialized to read mouse events using the + // Windows Console API. + MouseMode *MouseMode + + // EscTimeout is the escape character timeout duration. Most escape + // sequences start with an escape character [ansi.ESC] and are followed by + // one or more characters. If the next character is not received within + // this timeout, the reader will assume that the escape sequence is + // complete and will process the received characters as a complete escape + // sequence. + // + // By default, this is set to [DefaultEscTimeout] (50 milliseconds). + EscTimeout time.Duration + + r io.Reader + table map[string]Key // table is a lookup table for key sequences. + + term string // term is the terminal name $TERM. + + // paste is the bracketed paste mode buffer. + // When nil, bracketed paste mode is disabled. + paste []byte + + lookup bool // lookup indicates whether to use the lookup table for key sequences. + + // vtInput indicates whether we're using Windows Console API VT input mode. + //nolint:unused,nolintlint + vtInput bool + + eventScanner *eventScanner + + // We use these buffers to decode UTF-16 sequences and graphemes from the + // Windows Console API and Win32-Input-Mode events. + utf16Half [2]bool // 0 key up, 1 key down + utf16Buf [2][2]rune // 0 key up, 1 key down + graphemeBuf [2][]rune // 0 key up, 1 key down + //nolint:unused,nolintlint + lastMouseBtns uint32 // the last mouse button state for the previous event + //nolint:unused,nolintlint + lastWinsizeX, lastWinsizeY int16 // the last window size for the previous event to prevent multiple size events from firing + + logger Logger // The logger to use for debugging. +} + +// NewTerminalReader returns a new input event reader. The reader streams input +// events from the terminal and parses escape sequences into human-readable +// events. It supports reading Terminfo databases. +// +// Use [TerminalReader.UseTerminfo] to use Terminfo defined key sequences. +// Use [TerminalReader.Legacy] to control legacy key encoding behavior. +// +// Example: +// +// ```go +// var cr cancelreader.CancelReader +// var evc chan Event +// sc := NewTerminalReader(cr, os.Getenv("TERM")) +// go sc.StreamEvents(ctx, evc) +// ``` +func NewTerminalReader(r io.Reader, termType string) *TerminalReader { + d := &TerminalReader{ + EscTimeout: DefaultEscTimeout, + r: r, + term: termType, + lookup: true, // Use lookup table by default. + } + d.r = r + if d.table == nil { + d.table = buildKeysTable(d.Legacy, d.term, d.UseTerminfo) + } + evs := newEventScanner() + evs.EventDecoder = d.EventDecoder + evs.table = d.table + evs.lookup = d.lookup + evs.setLogger(d.logger) + d.eventScanner = evs + return d +} + +// readBufSize is the size of the read buffer used to read input events at a time. +const readBufSize = 4096 + +// sendBytes reads data from the reader and sends it to the provided channel. +// It stops when an error occurs or when the context is closed. +func (d *TerminalReader) sendBytes(ctx context.Context, readc chan []byte) error { + for { + var readBuf [readBufSize]byte + n, err := d.r.Read(readBuf[:]) + if err != nil { + return err //nolint:wrapcheck + } + + select { + case <-ctx.Done(): + return nil + case readc <- readBuf[:n]: + } + } +} + +// StreamEvents sends events to the provided channel. It stops when the context +// is closed or when an error occurs. +func (d *TerminalReader) StreamEvents(ctx context.Context, eventc chan<- Event) error { + var buf bytes.Buffer + errc := make(chan error, 1) + readc := make(chan []byte) + timeout := time.NewTimer(d.EscTimeout) + ttimeout := time.Now().Add(d.EscTimeout) + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + if err := d.streamData(ctx, readc); err != nil { + if errors.Is(err, io.EOF) || errors.Is(err, cancelreader.ErrCanceled) { + errc <- nil + return + } + errc <- err + return + } + }() + + for { + select { + case <-ctx.Done(): + d.sendEvents(eventc, buf.Bytes(), true) + wg.Wait() + return nil + case err := <-errc: + d.sendEvents(eventc, buf.Bytes(), true) + wg.Wait() + return err // return the first error encountered + case <-timeout.C: + d.logf("timeout reached") + + // Timeout reached process the buffer including any incomplete sequences. + var n int // n is the number of bytes processed + timedout := time.Now().After(ttimeout) + if buf.Len() > 0 && timedout { + d.logf("timeout expired, processing buffer") + n = d.sendEvents(eventc, buf.Bytes(), true) + } + + if n > 0 { + buf.Next(n) + } + + if buf.Len() > 0 { + if !timeout.Stop() { + // drain the channel if it was already running + select { + case <-timeout.C: + default: + } + } + + d.logf("resetting timeout for remaining buffer") + timeout.Reset(d.EscTimeout) + } + + case read := <-readc: + d.logf("input: %q", read) + buf.Write(read) + ttimeout = time.Now().Add(d.EscTimeout) + n := d.sendEvents(eventc, buf.Bytes(), false) + if !timeout.Stop() { + // drain the channel if it was already running + select { + case <-timeout.C: + default: + } + } + + if n > 0 { + d.logf("processed %d bytes from buffer", n) + buf.Next(n) + } + + if buf.Len() > 0 { + d.logf("resetting timeout for remaining buffer after parse") + timeout.Reset(d.EscTimeout) + } + } + } +} + +// SetLogger sets the logger to use for debugging. If nil, no logging will be +// performed. +func (d *TerminalReader) SetLogger(logger Logger) { + d.logger = logger +} + +func (d *TerminalReader) sendEvents(eventc chan<- Event, buf []byte, expired bool) int { + n, events := d.eventScanner.scanEvents(buf, expired) + for _, event := range events { + eventc <- event + } + return n +} + +// eventScanner scans the buffer for events and sends them to the event channel. +type eventScanner struct { + EventDecoder + + utf16Half [2]bool // 0 key up, 1 key down + utf16Buf [2][2]rune // 0 key up, 1 key down + graphemeBuf [2][]rune // 0 key up, 1 key down + paste []byte + table map[string]Key + lookup bool + logger Logger +} + +// newEventScanner creates a new event scanner. +func newEventScanner() *eventScanner { + return &eventScanner{} +} + +// setLogger sets the logger to use for debugging. If nil, no logging will be +// performed. +func (d *eventScanner) setLogger(logger Logger) { + d.logger = logger +} + +func (d *eventScanner) logf(format string, v ...interface{}) { + logf(d.logger, format, v...) +} + +func (d *eventScanner) scanEvents(buf []byte, expired bool) (total int, events []Event) { + if len(buf) == 0 { + return 0, nil + } + + var dn int + d.logf("processing buf %d %q", len(buf), buf) + dn, buf = d.deserializeWin32Input(buf) + total += dn + + // Lookup table first + if d.lookup && len(buf) > 2 && buf[0] == ansi.ESC { + if k, ok := d.table[string(buf)]; ok { + return len(buf), []Event{KeyPressEvent(k)} + } + } + + // total is the total number of bytes processed + for len(buf) > 0 { + esc := buf[0] == ansi.ESC + n, event := d.Decode(buf) + + // Handle bracketed-paste + if d.paste != nil { //nolint:nestif + if _, ok := event.(PasteEndEvent); !ok { + switch event := event.(type) { + case KeyPressEvent: + if len(event.Text) > 0 { + d.paste = append(d.paste, event.Text...) + } else { + seq := string(buf[:n]) + isWin32 := strings.HasPrefix(seq, "\x1b[") && strings.HasSuffix(seq, "_") + switch { + case isWin32 && event.Code == KeyEnter && event.Code == event.BaseCode: + // This handles special cases where + // win32-input-mode encodes newlines and other keys + // as keypress events. We need to encode them as + // their respective values. + d.paste = append(d.paste, '\n') + case isWin32 && unicode.IsControl(event.Code) && event.Code == event.BaseCode: + // This handles other cases such as tabs, escapes, etc. + d.paste = append(d.paste, string(event.Code)...) + case !isWin32: + // We ignore all other non-text win32-input-mode events. + if esc && n <= 2 && !expired { + // If the event is an escape sequence and we + // are not expired, we need to wait for more + // input. + return total, events + } + d.paste = append(d.paste, seq...) + } + } + case UnknownEvent: + if !expired { + // If the event is unknown and we are not expired, we + // return need to try to decode the buffer again. + return total, events + } + default: + // Everything else is ignored... + } + buf = buf[n:] + total += n + continue + } + } + + var isUnknown bool + switch event.(type) { + case ignoredEvent: + // ignore this event + event = nil + case UnknownEvent: + isUnknown = true + // Try to look up the event in the table. + if !expired { + return total, events + } + + if k, ok := d.table[string(buf[:n])]; ok { + events = append(events, KeyPressEvent(k)) + return total + n, events + } + + events = append(events, event) + case PasteStartEvent: + d.paste = []byte{} // reset the paste buffer + case PasteEndEvent: + var paste []rune + for len(d.paste) > 0 { + r, w := utf8.DecodeRune(d.paste) + if r != utf8.RuneError { + paste = append(paste, r) + } + d.paste = d.paste[w:] + } + d.paste = nil // reset the paste buffer + events = append(events, PasteEvent{string(paste)}) + } + + if !isUnknown && event != nil { + if esc && n <= 2 && !expired { + // Wait for more input + return total, events + } + + if m, ok := event.(MultiEvent); ok { + // If the event is a MultiEvent, append all events to the queue. + events = append(events, m...) + } else { + // Otherwise, just append the event to the queue. + events = append(events, event) + } + } + + buf = buf[n:] + total += n + } + + return total, events +} + +func (d *eventScanner) encodeGraphemeBufs() []byte { + var b []byte + for kd := range d.graphemeBuf { + if len(d.graphemeBuf[kd]) > 0 { + switch kd { + case 1: + b = append(b, string(d.graphemeBuf[kd])...) + case 0: + // Encode the release grapheme as Kitty Keyboard to get the release event. + grs := uniseg.NewGraphemes(string(d.graphemeBuf[kd])) + for grs.Next() { + var codepoints string + gr := grs.Str() + for i, r := range gr { + if r == 0 { + continue + } + if i > 0 { + codepoints += ":" + } + codepoints += strconv.FormatInt(int64(r), 10) + } + // This is dark :) + // During serializing/deserializing of win32 input events, the + // API will split a grapheme into runes and send them as a + // keydown/keyup sequences. We collect the runes, decoded them + // from UTF-16 just fine. However, [EventDecoder.Decode] will + // always decode graphemes as [KeyPressEvent]s. Thus, to + // workaround that and the existing API, while we are + // intercepting win32 input events, we encode the grapheme + // release events as Kitty Keyboard sequences so that + // [EventDecoder.Decode] can properly decode them as + // [KeyReleaseEvent]s. + seq := fmt.Sprintf("\x1b[%d;1:3;%su", d.graphemeBuf[kd][0], codepoints) + b = append(b, seq...) + } + } + d.graphemeBuf[kd] = d.graphemeBuf[kd][:0] // reset the buffer + } + } + return b +} + +func (d *eventScanner) storeGraphemeRune(kd int, r rune) { + if d.utf16Half[kd] { + // We have a half pair that needs to be decoded. + d.utf16Half[kd] = false + d.utf16Buf[kd][1] = r + r := utf16.DecodeRune(d.utf16Buf[kd][0], d.utf16Buf[kd][1]) + d.graphemeBuf[kd] = append(d.graphemeBuf[kd], r) + } else if utf16.IsSurrogate(r) { + // This is the first half of a UTF-16 surrogate pair. + d.utf16Half[kd] = true + d.utf16Buf[kd][0] = r + } else { + // This should be a single UTF-16 that can be converted + // to UTF-8. + d.graphemeBuf[kd] = append(d.graphemeBuf[kd], r) + } +} + +// deserializeWin32Input deserializes the Win32 input events converting +// KeyEventRecrods to bytes. Before returning the bytes, it will also try to +// decode any UTF-16 pairs that might be present in the input buffer. +func (d *eventScanner) deserializeWin32Input(buf []byte) (int, []byte) { + p := ansi.GetParser() + defer ansi.PutParser(p) + + var processed int + var state byte + des := make([]byte, 0, len(buf)) + + for len(buf) > 0 { + seq, width, n, newState := ansi.DecodeSequence(buf, state, p) + switch width { + case 0: + if p.Command() == '_' { // Win32 Input Mode + vk, _ := p.Param(0, 0) + if vk == 0 { + // This is either a serialized KeyEventRecord or a UTF-16 + // pair. + uc, _ := p.Param(2, 0) + kd, _ := p.Param(3, 0) + kd = clamp(kd, 0, 1) // kd is the key down state (0 or 1) + d.storeGraphemeRune(kd, rune(uc)) + processed += n + break + } + } + fallthrough + default: + des = append(des, d.encodeGraphemeBufs()...) + des = append(des, seq...) + } + + state = newState + buf = buf[n:] + } + + des = append(des, d.encodeGraphemeBufs()...) + + return processed, des +} + +func (d *TerminalReader) logf(format string, v ...interface{}) { + logf(d.logger, format, v...) +} + +func logf(logger Logger, format string, v ...interface{}) { + if logger == nil { + return + } + logger.Printf(format, v...) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_other.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_other.go new file mode 100644 index 000000000..ca2e9e724 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_other.go @@ -0,0 +1,13 @@ +//go:build !windows +// +build !windows + +package uv + +import ( + "context" +) + +// streamData sends data from the input stream to the event channel. +func (d *TerminalReader) streamData(ctx context.Context, readc chan []byte) error { + return d.sendBytes(ctx, readc) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go new file mode 100644 index 000000000..119dae172 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go @@ -0,0 +1,336 @@ +//go:build windows +// +build windows + +package uv + +import ( + "bytes" + "context" + "fmt" + "strings" + "time" + "unicode/utf16" + + "github.com/charmbracelet/x/ansi" + xwindows "github.com/charmbracelet/x/windows" + "github.com/muesli/cancelreader" + "golang.org/x/sys/windows" +) + +// streamData sends data from the input stream to the event channel. +func (d *TerminalReader) streamData(ctx context.Context, readc chan []byte) error { + cc, ok := d.r.(*conInputReader) + if !ok { + d.logf("streamData: reader is not a conInputReader, falling back to default implementation") + return d.sendBytes(ctx, readc) + } + + // Store the value of VT Input Mode for later use. + d.vtInput = cc.newMode&windows.ENABLE_VIRTUAL_TERMINAL_INPUT != 0 + + var buf bytes.Buffer + var records []xwindows.InputRecord + var err error + for { + for { + records, err = peekNConsoleInputs(cc.conin, readBufSize) + if cc.isCanceled() { + return cancelreader.ErrCanceled + } + if err != nil { + return err + } + if len(records) > 0 { + break + } + + // Sleep for a bit to avoid busy waiting. + time.Sleep(10 * time.Millisecond) + } + + records, err = readNConsoleInputs(cc.conin, uint32(len(records))) //nolint:gosec + if cc.isCanceled() { + return cancelreader.ErrCanceled + } + if err != nil { + return err + } + + // We convert Windows Input Records to VT input sequences for easier + // processing especially when dealing with UTF-16 decoding and + // Win32-Input-Mode processing. + d.serializeWin32InputRecords(records, &buf) + + select { + case <-ctx.Done(): + return nil + case readc <- buf.Bytes(): + } + + buf.Reset() + } +} + +// serializeWin32InputRecords serializes the Win32 input events converting them +// to valid VT input sequences. It will also encode any UTF-16 pairs that might +// be present in the input buffer. The resulting byte slice can be sent to the +// terminal as input. +func (d *TerminalReader) serializeWin32InputRecords(records []xwindows.InputRecord, buf *bytes.Buffer) { + for _, record := range records { + switch record.EventType { + case xwindows.KEY_EVENT: + kevent := record.KeyEvent() + // d.logf("key event: %s", keyEventString(kevent.VirtualKeyCode, kevent.VirtualScanCode, kevent.Char, kevent.KeyDown, kevent.ControlKeyState, kevent.RepeatCount)) + + var kd int + if kevent.KeyDown { + kd = 1 + } + if d.vtInput { //nolint:nestif + // In VT Input Mode, we only capture the Unicode characters + // decoding them along the way. + // This is similar to [TerminalReader.storeGraphemeRune] except + // that we need to write the events directly to the buffer. + if d.utf16Half[kd] { + // We have a half pair that needs to be decoded. + d.utf16Half[kd] = false + d.utf16Buf[kd][1] = kevent.Char + r := utf16.DecodeRune(d.utf16Buf[kd][0], d.utf16Buf[kd][1]) + buf.WriteRune(r) + } else if utf16.IsSurrogate(kevent.Char) { + // This is the first half of a UTF-16 surrogate pair. + d.utf16Half[kd] = true + d.utf16Buf[kd][0] = kevent.Char + } else if kevent.KeyDown { + // Just a regular key press character encoded in VT. + buf.WriteRune(kevent.Char) + } + } else { + // We encode the key to Win32 Input Mode if it is a known key. + if kevent.VirtualKeyCode == 0 { + d.eventScanner.storeGraphemeRune(kd, kevent.Char) + } else { + buf.Write(d.eventScanner.encodeGraphemeBufs()) + fmt.Fprintf(buf, + "\x1b[%d;%d;%d;%d;%d;%d_", + kevent.VirtualKeyCode, + kevent.VirtualScanCode, + kevent.Char, + kd, + kevent.ControlKeyState, + kevent.RepeatCount) + } + } + + case xwindows.MOUSE_EVENT: + if d.MouseMode == nil || *d.MouseMode == 0 { + continue + } + mouseMode := *d.MouseMode + mevent := record.MouseEvent() + + var isRelease bool + var isMotion bool + var button MouseButton + alt := mevent.ControlKeyState&(xwindows.LEFT_ALT_PRESSED|xwindows.RIGHT_ALT_PRESSED) != 0 + ctrl := mevent.ControlKeyState&(xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_CTRL_PRESSED) != 0 + shift := mevent.ControlKeyState&(xwindows.SHIFT_PRESSED) != 0 + wheelDirection := int16(highWord(mevent.ButtonState)) //nolint:gosec + switch mevent.EventFlags { + case 0, xwindows.DOUBLE_CLICK: + button, isRelease = mouseEventButton(d.lastMouseBtns, mevent.ButtonState) + case xwindows.MOUSE_WHEELED: + if wheelDirection > 0 { + button = MouseWheelUp + } else { + button = MouseWheelDown + } + case xwindows.MOUSE_HWHEELED: + if wheelDirection > 0 { + button = MouseWheelRight + } else { + button = MouseWheelLeft + } + case xwindows.MOUSE_MOVED: + button, _ = mouseEventButton(d.lastMouseBtns, mevent.ButtonState) + isMotion = true + } + + // We emulate mouse mode levels on Windows. This is because Windows + // doesn't have a concept of different mouse modes. We use the mouse mode to determine + if button == MouseNone && mouseMode&MouseModeMotion == 0 || + (button != MouseNone && mouseMode&MouseModeDrag == 0) { + continue + } + + // Encode mouse events as SGR mouse sequences that can be read by [EventDecoder]. + buf.WriteString(ansi.MouseSgr( + ansi.EncodeMouseButton(button, isMotion, shift, alt, ctrl), + int(mevent.MousePositon.X), int(mevent.MousePositon.Y), isRelease, + )) + + d.lastMouseBtns = mevent.ButtonState + + case xwindows.WINDOW_BUFFER_SIZE_EVENT: + wevent := record.WindowBufferSizeEvent() + if wevent.Size.X != d.lastWinsizeX || wevent.Size.Y != d.lastWinsizeY { + d.lastWinsizeX, d.lastWinsizeY = wevent.Size.X, wevent.Size.Y + // We encode window resize events as CSI 4 ; height ; width t + // sequence which the [EventDecoder] understands. + buf.WriteString( + ansi.WindowOp( + 8, // Terminal window size in cells + int(wevent.Size.Y), // height + int(wevent.Size.X), // width + ), + ) + } + + case xwindows.FOCUS_EVENT: + fevent := record.FocusEvent() + if fevent.SetFocus { + buf.WriteString(ansi.Focus) + } else { + buf.WriteString(ansi.Blur) + } + + case xwindows.MENU_EVENT: + // ignore + } + } + + // Flush any remaining grapheme buffers. + buf.Write(d.eventScanner.encodeGraphemeBufs()) +} + +func mouseEventButton(p, s uint32) (MouseButton, bool) { + var isRelease bool + button := MouseNone + btn := p ^ s + if btn&s == 0 { + isRelease = true + } + + if btn == 0 { + switch { + case s&xwindows.FROM_LEFT_1ST_BUTTON_PRESSED > 0: + button = MouseLeft + case s&xwindows.FROM_LEFT_2ND_BUTTON_PRESSED > 0: + button = MouseMiddle + case s&xwindows.RIGHTMOST_BUTTON_PRESSED > 0: + button = MouseRight + case s&xwindows.FROM_LEFT_3RD_BUTTON_PRESSED > 0: + button = MouseBackward + case s&xwindows.FROM_LEFT_4TH_BUTTON_PRESSED > 0: + button = MouseForward + } + return button, isRelease + } + + switch btn { + case xwindows.FROM_LEFT_1ST_BUTTON_PRESSED: // left button + button = MouseLeft + case xwindows.RIGHTMOST_BUTTON_PRESSED: // right button + button = MouseRight + case xwindows.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button + button = MouseMiddle + case xwindows.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward) + button = MouseBackward + case xwindows.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward) + button = MouseForward + } + + return button, isRelease +} + +func highWord(data uint32) uint16 { + return uint16((data & 0xFFFF0000) >> 16) //nolint:gosec +} + +func readNConsoleInputs(console windows.Handle, maxEvents uint32) ([]xwindows.InputRecord, error) { + if maxEvents == 0 { + return nil, fmt.Errorf("maxEvents cannot be zero") + } + + records := make([]xwindows.InputRecord, maxEvents) + n, err := readConsoleInput(console, records) + return records[:n], err +} + +func readConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) { + if len(inputRecords) == 0 { + return 0, fmt.Errorf("size of input record buffer cannot be zero") + } + + var read uint32 + + err := xwindows.ReadConsoleInput(console, &inputRecords[0], uint32(len(inputRecords)), &read) //nolint:gosec + + return read, err //nolint:wrapcheck +} + +func peekConsoleInput(console windows.Handle, inputRecords []xwindows.InputRecord) (uint32, error) { + if len(inputRecords) == 0 { + return 0, fmt.Errorf("size of input record buffer cannot be zero") + } + + var read uint32 + + err := xwindows.PeekConsoleInput(console, &inputRecords[0], uint32(len(inputRecords)), &read) //nolint:gosec + + return read, err //nolint:wrapcheck +} + +func peekNConsoleInputs(console windows.Handle, maxEvents uint32) ([]xwindows.InputRecord, error) { + if maxEvents == 0 { + return nil, fmt.Errorf("maxEvents cannot be zero") + } + + records := make([]xwindows.InputRecord, maxEvents) + n, err := peekConsoleInput(console, records) + return records[:n], err +} + +//nolint:unused +func keyEventString(vkc, sc uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) string { + var s strings.Builder + s.WriteString("vkc: ") + s.WriteString(fmt.Sprintf("%d, 0x%02x", vkc, vkc)) + s.WriteString(", sc: ") + s.WriteString(fmt.Sprintf("%d, 0x%02x", sc, sc)) + s.WriteString(", r: ") + s.WriteString(fmt.Sprintf("%q 0x%x", r, r)) + s.WriteString(", down: ") + s.WriteString(fmt.Sprintf("%v", keyDown)) + s.WriteString(", cks: [") + if cks&xwindows.LEFT_ALT_PRESSED != 0 { + s.WriteString("left alt, ") + } + if cks&xwindows.RIGHT_ALT_PRESSED != 0 { + s.WriteString("right alt, ") + } + if cks&xwindows.LEFT_CTRL_PRESSED != 0 { + s.WriteString("left ctrl, ") + } + if cks&xwindows.RIGHT_CTRL_PRESSED != 0 { + s.WriteString("right ctrl, ") + } + if cks&xwindows.SHIFT_PRESSED != 0 { + s.WriteString("shift, ") + } + if cks&xwindows.CAPSLOCK_ON != 0 { + s.WriteString("caps lock, ") + } + if cks&xwindows.NUMLOCK_ON != 0 { + s.WriteString("num lock, ") + } + if cks&xwindows.SCROLLLOCK_ON != 0 { + s.WriteString("scroll lock, ") + } + if cks&xwindows.ENHANCED_KEY != 0 { + s.WriteString("enhanced key, ") + } + s.WriteString("], repeat count: ") + s.WriteString(fmt.Sprintf("%d", repeatCount)) + return s.String() +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer.go new file mode 100644 index 000000000..4b55cd25f --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer.go @@ -0,0 +1,1590 @@ +package uv + +import ( + "bytes" + "errors" + "hash/maphash" + "io" + "strings" + + "github.com/charmbracelet/colorprofile" + "github.com/charmbracelet/x/ansi" +) + +// ErrInvalidDimensions is returned when the dimensions of a window are invalid +// for the operation. +var ErrInvalidDimensions = errors.New("invalid dimensions") + +// capabilities represents a mask of supported ANSI escape sequences. +type capabilities uint + +const ( + // Vertical Position Absolute [ansi.VPA]. + capVPA capabilities = 1 << iota + // Horizontal Position Absolute [ansi.HPA]. + capHPA + // Cursor Horizontal Absolute [ansi.CHA]. + capCHA + // Cursor Horizontal Tab [ansi.CHT]. + capCHT + // Cursor Backward Tab [ansi.CBT]. + capCBT + // Repeat Previous Character [ansi.REP]. + capREP + // Erase Character [ansi.ECH]. + capECH + // Insert Character [ansi.ICH]. + capICH + // Scroll Down [ansi.SD]. + capSD + // Scroll Up [ansi.SU]. + capSU + // These capabilities depend on the tty termios settings and are not + // enabled by default. + // Tabulation [ansi.HT]. + capHT + // Backspace [ansi.BS]. + capBS + + noCaps capabilities = 0 + allCaps = capVPA | capHPA | capCHA | capCHT | capCBT | capREP | capECH | capICH | capSD | capSU +) + +// Set sets the given capabilities. +func (v *capabilities) Set(c capabilities) { + *v |= c +} + +// Reset resets the given capabilities. +func (v *capabilities) Reset(c capabilities) { + *v &^= c +} + +// Contains returns whether the capabilities contains the given capability. +func (v capabilities) Contains(c capabilities) bool { + return v&c == c +} + +// cursor represents a terminal cursor. +type cursor struct { + Cell + Position +} + +// LineData represents the metadata for a line. +type LineData struct { + // First and last changed cell indices. + FirstCell, LastCell int + // Old index used for scrolling + oldIndex int //nolint:unused +} + +// tFlag is a bitmask of terminal flags. +type tFlag uint + +// Terminal writer flags. +const ( + tRelativeCursor tFlag = 1 << iota + tFullscreen + tMapNewline + tScrollOptim +) + +// Set sets the given flags. +func (v *tFlag) Set(c tFlag) { + *v |= c +} + +// Reset resets the given flags. +func (v *tFlag) Reset(c tFlag) { + *v &^= c +} + +// Contains returns whether the terminal flags contains the given flags. +func (v tFlag) Contains(c tFlag) bool { + return v&c == c +} + +// TerminalRenderer is a terminal screen render and lazy writer that buffers +// the output until it is flushed. It handles rendering a screen from a +// [Buffer] to the terminal with the minimal necessary escape sequences to +// transition the terminal to the new buffer state. It uses various escape +// sequence optimizations to reduce the number of bytes sent to the terminal. +// It's designed to be lazy and only flush the output when necessary by calling +// the [TerminalRenderer.Flush] method. +// +// The renderer handles the terminal's alternate screen and cursor visibility +// via the [TerminalRenderer.EnterAltScreen], [TerminalRenderer.ExitAltScreen], +// [TerminalRenderer.ShowCursor] and [TerminalRenderer.HideCursor] methods. +// Using these methods will queue the appropriate escape sequences to enter or +// exit the alternate screen and show or hide the cursor respectively to be +// flushed to the terminal. +// +// Use the [io.Writer] and [io.StringWriter] interfaces to queue custom +// commands the renderer. +// +// The renderer is not thread-safe, the caller must protect the renderer when +// using it from multiple goroutines. +type TerminalRenderer struct { + w io.Writer + buf *bytes.Buffer // buffer for writing to the screen + curbuf *RenderBuffer // the current buffer + tabs *TabStops + hasher maphash.Hash + oldhash, newhash []uint64 // the old and new hash values for each line + hashtab []hashmap // the hashmap table + oldnum []int // old indices from previous hash + cur, saved cursor // the current and saved cursors + flags tFlag // terminal writer flags. + term string // the terminal type + clear bool // whether to force clear the screen + caps capabilities // terminal control sequence capabilities + atPhantom bool // whether the cursor is out of bounds and at a phantom cell + logger Logger // The logger used for debugging. + + // profile is the color profile to use when downsampling colors. This is + // used to determine the appropriate color the terminal can display. + profile colorprofile.Profile +} + +// NewTerminalRenderer returns a new [TerminalRenderer] that uses the given +// writer, terminal type, and initializes the width of the terminal. The +// terminal type is used to determine the capabilities of the terminal and +// should be set to the value of the TERM environment variable. +// +// The renderer will try to detect the color profile from the output and the +// given environment variables. Use [TerminalRenderer.SetColorProfile] method +// to set a specific color profile for downsampling. +// +// See [TerminalRenderer] for more information on how to use the renderer. +func NewTerminalRenderer(w io.Writer, env []string) (s *TerminalRenderer) { + s = new(TerminalRenderer) + s.w = w + s.profile = colorprofile.Detect(w, env) + s.buf = new(bytes.Buffer) + s.term = Environ(env).Getenv("TERM") + s.caps = xtermCaps(s.term) + s.cur = cursor{Cell: EmptyCell, Position: Pos(-1, -1)} // start at -1 to force a move + s.saved = s.cur + s.oldhash, s.newhash = nil, nil + return +} + +// SetLogger sets the logger to use for debugging. If nil, no logging will be +// performed. +func (s *TerminalRenderer) SetLogger(logger Logger) { + s.logger = logger +} + +// SetColorProfile sets the color profile to use for downsampling colors. This +// is used to determine the appropriate color the terminal can display. +func (s *TerminalRenderer) SetColorProfile(profile colorprofile.Profile) { + s.profile = profile +} + +// SetScrollOptim sets whether to use hard scroll optimizations. +func (s *TerminalRenderer) SetScrollOptim(v bool) { + if v { + s.flags.Set(tScrollOptim) + } else { + s.flags.Reset(tScrollOptim) + } +} + +// SetMapNewline sets whether the terminal is currently mapping newlines to +// CRLF or carriage return and line feed. This is used to correctly determine +// how to move the cursor when writing to the screen. +func (s *TerminalRenderer) SetMapNewline(v bool) { + if v { + s.flags.Set(tMapNewline) + } else { + s.flags.Reset(tMapNewline) + } +} + +// SetBackspace sets whether to use backspace as a movement optimization. +func (s *TerminalRenderer) SetBackspace(v bool) { + if v { + s.caps.Set(capBS) + } else { + s.caps.Reset(capBS) + } +} + +// SetTabStops sets the tab stops for the terminal and enables hard tabs +// movement optimizations. Use -1 to disable hard tabs. This option is ignored +// when the terminal type is "linux" as it does not support hard tabs. +func (s *TerminalRenderer) SetTabStops(width int) { + if width < 0 || strings.HasPrefix(s.term, "linux") { + // Linux terminal does not support hard tabs. + s.caps.Reset(capHT) + } else { + s.caps.Set(capHT) + s.tabs = DefaultTabStops(width) + } +} + +// SetFullscreen sets whether whole screen is being used. This is usually +// paired with the alternate screen mode, and used to control cursor movements +// and optimizations when the terminal is occupying the whole screen. +func (s *TerminalRenderer) SetFullscreen(v bool) { + if v { + s.flags.Set(tFullscreen) + } else { + s.flags.Reset(tFullscreen) + } +} + +// Fullscreen returns whether whole screen is being used. This is usually +// paired with the alternate screen mode, and used to control cursor movements +// and optimizations when the terminal is occupying the whole screen. +func (s *TerminalRenderer) Fullscreen() bool { + return s.flags.Contains(tFullscreen) +} + +// SetRelativeCursor sets whether to use relative cursor movements. +func (s *TerminalRenderer) SetRelativeCursor(v bool) { + if v { + s.flags.Set(tRelativeCursor) + } else { + s.flags.Reset(tRelativeCursor) + } +} + +// SaveCursor saves the current cursor position and styles. This can be used +// when a program enters the alternate screen buffer mode using DECSET 1049 +// [ansi.ModeAltScreenSaveCursor] and wants to restore the cursor position and +// styles later. +func (s *TerminalRenderer) SaveCursor() { + s.saved = s.cur +} + +// RestoreCursor restores the saved cursor position and styles. This can be +// used when a program exits the alternate screen buffer mode using DECRST 1049 +// [ansi.ModeAltScreenSaveCursor] and wants to restore the cursor position and +// styles saved earlier. +func (s *TerminalRenderer) RestoreCursor() { + s.cur = s.saved +} + +// EnterAltScreen is a helper that queues the [ansi.ModeAltScreenSaveCursor] +// escape sequence to enter the alternate screen buffer and save cursor mode. +// +// It saves the current cursor properties, enables +// [TerminalRenderer.SetFullscreen] flag, and disables +// [TerminalRenderer.SetRelativeCursor] flag. On the next call to +// [TerminalRenderer.Render], it will move the cursor home and clear the screen +// to prepare for rendering the new buffer. +// +// Note: you might want to reapply the cursor visibility state after calling +// this method, as some terminals reset the cursor visibility when switching to +// the alternate screen. +func (s *TerminalRenderer) EnterAltScreen() { + s.SaveCursor() + s.buf.WriteString(ansi.SetModeAltScreenSaveCursor) + s.SetFullscreen(true) + s.SetRelativeCursor(false) + s.Erase() +} + +// ExitAltScreen is a helper that queues the [ansi.ModeAltScreenSaveCursor] +// escape sequence to exit the alternate screen buffer and restore cursor mode. +// +// It restores the saved cursor properties, disables +// [TerminalRenderer.SetFullscreen] flag, and enables +// [TerminalRenderer.SetRelativeCursor] flag. On the next call to +// [TerminalRenderer.Render], it will move the cursor to the first line and +// clear everything below the cursor to prepare for rendering the new buffer. +// +// Note: you might want to reapply the cursor visibility state after calling +// this method, as some terminals reset the cursor visibility when switching to +// the alternate screen. +func (s *TerminalRenderer) ExitAltScreen() { + s.Erase() + s.SetRelativeCursor(true) + s.SetFullscreen(false) + s.buf.WriteString(ansi.ResetModeAltScreenSaveCursor) + s.RestoreCursor() +} + +// PrependString adds the lines of the given string to the top of the terminal +// screen. The lines prepended are not managed by the renderer and will not be +// cleared or updated by the renderer. +// +// Using this when the terminal is using the alternate screen or when occupying +// the whole screen may not produce any visible effects. This is because +// once the terminal writes the prepended lines, they will get overwritten +// by the next frame. +func (s *TerminalRenderer) PrependString(newbuf *RenderBuffer, str string) { + if len(str) == 0 { + return + } + + // TODO: Use scrolling region if available. + // TODO: Use [Screen.Write] [io.Writer] interface. + + // We need to scroll the screen up by the number of lines in the queue. + // We can't use [ansi.SU] because we want the cursor to move down until + // it reaches the bottom of the screen. + w, h := newbuf.Width(), newbuf.Height() + s.move(newbuf, 0, h-1) + lines := strings.Split(str, "\n") + offset := 0 + for _, line := range lines { + lineWidth := ansi.StringWidth(line) + if w > 0 && lineWidth > w { + offset += (lineWidth / w) + } + if lineWidth == 0 || lineWidth%w != 0 { + offset++ + } + } + + s.buf.WriteString(strings.Repeat("\n", offset)) + s.cur.Y += offset + // XXX: Now go to the top of the screen, insert new lines, and write + // the queued strings. It is important to use [Screen.moveCursor] + // instead of [Screen.move] because we don't want to perform any checks + // on the cursor position. + s.moveCursor(newbuf, 0, 0, false) + s.buf.WriteString(ansi.InsertLine(offset)) + for _, line := range lines { + s.buf.WriteString(line) + s.buf.WriteString("\r\n") + } +} + +// moveCursor moves the cursor to the specified position. +// +// It is safe to call this function with a nil [Buffer], in that case, it won't +// be using any optimizations that depend on the buffer. +func (s *TerminalRenderer) moveCursor(newbuf *RenderBuffer, x, y int, overwrite bool) { + if !s.flags.Contains(tFullscreen) && s.flags.Contains(tRelativeCursor) && + s.cur.X == -1 && s.cur.Y == -1 { + // First cursor movement in inline mode, move the cursor to the first + // column before moving to the target position. + _ = s.buf.WriteByte('\r') + s.cur.X, s.cur.Y = 0, 0 + } + seq := moveCursor(s, newbuf, x, y, overwrite) + _, _ = s.buf.WriteString(seq) + s.cur.X, s.cur.Y = x, y +} + +// move moves the cursor to the specified position in the buffer. +// +// It is safe to call this function with a nil [Buffer], in that case, it won't +// be using any optimizations that depend on the buffer. +func (s *TerminalRenderer) move(newbuf *RenderBuffer, x, y int) { + // XXX: Make sure we use the max height and width of the buffer in case + // we're in the middle of a resize operation. + var width, height int + if s.curbuf != nil { + width, height = s.curbuf.Width(), s.curbuf.Height() + } + if newbuf != nil { + width = max(newbuf.Width(), width) + height = max(newbuf.Height(), height) + } + + if width > 0 && x >= width { + // Handle autowrap + y += (x / width) + x %= width + } + + // XXX: Disable styles if there's any + // Some move operations such as [ansi.LF] can apply styles to the new + // cursor position, thus, we need to reset the styles before moving the + // cursor. + blank := s.clearBlank() + resetPen := y != s.cur.Y && !blank.Equal(&EmptyCell) + if resetPen { + s.updatePen(nil) + } + + // Reset wrap around (phantom cursor) state + if s.atPhantom { + s.cur.X = 0 + _ = s.buf.WriteByte('\r') + s.atPhantom = false // reset phantom cell state + } + + // TODO: Investigate if we need to handle this case and/or if we need the + // following code. + // + // if width > 0 && s.cur.X >= width { + // l := (s.cur.X + 1) / width + // + // s.cur.Y += l + // if height > 0 && s.cur.Y >= height { + // l -= s.cur.Y - height - 1 + // } + // + // if l > 0 { + // s.cur.X = 0 + // s.buf.WriteString("\r" + strings.Repeat("\n", l)) //nolint:errcheck + // } + // } + + if height > 0 { + if s.cur.Y > height-1 { + s.cur.Y = height - 1 + } + if y > height-1 { + y = height - 1 + } + } + + if x == s.cur.X && y == s.cur.Y { + // We give up later because we need to run checks for the phantom cell + // and others before we can determine if we can give up. + return + } + + // We set the new cursor in tscreen.moveCursor]. + s.moveCursor(newbuf, x, y, true) // Overwrite cells if possible +} + +// cellEqual returns whether the two cells are equal. A nil cell is considered +// a [EmptyCell]. +func cellEqual(a, b *Cell) bool { + if a == b { + return true + } + // TODO: This is needed to handle empty lines correctly when scroll + // optimizations are enabled. Instead, a nil check should be equivalent to + // checking for an [EmptyCell], should it? + // Investigate why when we assign the pointers to &[EmptyCell], this causes + // scroll optimization related artifacts where excess lines are left behind + // in empty lines after scrolling. + if a == nil || b == nil { + return false + } + return a.Equal(b) +} + +// putCell draws a cell at the current cursor position. +func (s *TerminalRenderer) putCell(newbuf *RenderBuffer, cell *Cell) { + width, height := newbuf.Width(), newbuf.Height() + if s.flags.Contains(tFullscreen) && s.cur.X == width-1 && s.cur.Y == height-1 { + s.putCellLR(newbuf, cell) + } else { + s.putAttrCell(newbuf, cell) + } +} + +// wrapCursor wraps the cursor to the next line. +func (s *TerminalRenderer) wrapCursor() { + const autoRightMargin = true + if autoRightMargin { + // Assume we have auto wrap mode enabled. + s.cur.X = 0 + s.cur.Y++ + } else { + s.cur.X-- + } +} + +func (s *TerminalRenderer) putAttrCell(newbuf *RenderBuffer, cell *Cell) { + if cell != nil && cell.IsZero() { + // XXX: Zero width cells are special and should not be written to the + // screen no matter what other attributes they have. + // Zero width cells are used for wide characters that are split into + // multiple cells. + return + } + + // We're at pending wrap state (phantom cell), incoming cell should + // wrap. + if s.atPhantom { + s.wrapCursor() + s.atPhantom = false + } + + s.updatePen(cell) + cellWidth := 1 + if cell == nil { + _ = s.buf.WriteByte(' ') + } else { + _, _ = s.buf.WriteString(cell.Content) + cellWidth = cell.Width + } + + s.cur.X += cellWidth + if s.cur.X >= newbuf.Width() { + s.atPhantom = true + } +} + +// putCellLR draws a cell at the lower right corner of the screen. +func (s *TerminalRenderer) putCellLR(newbuf *RenderBuffer, cell *Cell) { + // Optimize for the lower right corner cell. + curX := s.cur.X + if cell == nil || !cell.IsZero() { + _, _ = s.buf.WriteString(ansi.ResetModeAutoWrap) + s.putAttrCell(newbuf, cell) + // Writing to lower-right corner cell should not wrap. + s.atPhantom = false + s.cur.X = curX + _, _ = s.buf.WriteString(ansi.SetModeAutoWrap) + } +} + +// updatePen updates the cursor pen styles. +func (s *TerminalRenderer) updatePen(cell *Cell) { + if cell == nil { + if !s.cur.Style.IsZero() { + _, _ = s.buf.WriteString(ansi.ResetStyle) + s.cur.Style = Style{} // Reset style + } + if !s.cur.Link.IsZero() { + _, _ = s.buf.WriteString(ansi.ResetHyperlink()) + } + return + } + + // Downsample pen when we don't have a [colorprofile.TrueColor], + // otherwise, use the original style. + newStyle := ConvertStyle(cell.Style, s.profile) + newLink := ConvertLink(cell.Link, s.profile) + oldStyle := ConvertStyle(s.cur.Style, s.profile) + oldLink := ConvertLink(s.cur.Link, s.profile) + + if !newStyle.Equal(&oldStyle) { + seq := newStyle.Diff(&oldStyle) + if newStyle.IsZero() && len(seq) > len(ansi.ResetStyle) { + seq = ansi.ResetStyle + } + _, _ = s.buf.WriteString(seq) + s.cur.Style = cell.Style // Copy the original style + } + if !newLink.Equal(&oldLink) { + _, _ = s.buf.WriteString(ansi.SetHyperlink(newLink.URL, newLink.Params)) + s.cur.Link = cell.Link // Copy the original link + } +} + +// canClearWith checks whether the given cell can be used by clearing commands +// like [ansi.EL] to clear the screen. It tests if a cell is empty i.e. space +// or blank and doesn't include any bad style attributes such as [AttrReverse]. +func canClearWith(c *Cell) bool { + if c == nil { + return true + } + if c.Width != 1 || len(c.Content) != 1 || c.Content != " " { + return false + } + // NOTE: This assumes that the terminal supports bce terminfo capability + // which all xterm-compatible terminals and terminals that use xterm* + // terminal types do. + return c.Style.Underline == UnderlineNone && + c.Style.Attrs&^(AttrBold|AttrFaint|AttrItalic|AttrBlink|AttrRapidBlink) == 0 && + c.Link.IsZero() +} + +// emitRange emits a range of cells to the buffer. It it equivalent to calling +// tscreen.putCell] for each cell in the range. This is optimized to use +// [ansi.ECH] and [ansi.REP]. +// Returns whether the cursor is at the end of interval or somewhere in the +// middle. +func (s *TerminalRenderer) emitRange(newbuf *RenderBuffer, line Line, n int) (eoi bool) { + hasECH := s.caps.Contains(capECH) + hasREP := s.caps.Contains(capREP) + if hasECH || hasREP { //nolint:nestif + for n > 0 { + var count int + for n > 1 && !cellEqual(line.At(0), line.At(1)) { + s.putCell(newbuf, line.At(0)) + line = line[1:] + n-- + } + + cell0 := line[0] + if n == 1 { + s.putCell(newbuf, &cell0) + return false + } + + count = 2 + for count < n && cellEqual(line.At(count), &cell0) { + count++ + } + + ech := ansi.EraseCharacter(count) + cup := ansi.CursorPosition(s.cur.X+count, s.cur.Y) + rep := ansi.RepeatPreviousCharacter(count) + if hasECH && count > len(ech)+len(cup) && canClearWith(&cell0) { + s.updatePen(&cell0) + _, _ = s.buf.WriteString(ech) + + // If this is the last cell, we don't need to move the cursor. + if count < n { + s.move(newbuf, s.cur.X+count, s.cur.Y) + } else { + return true // cursor in the middle + } + } else if hasREP && count > len(rep) && + (len(cell0.Content) == 1 && cell0.Content[0] >= ansi.US && cell0.Content[0] < ansi.DEL) { + // We only support ASCII characters. Most terminals will handle + // non-ASCII characters correctly, but some might not, ahem xterm. + // + // NOTE: [ansi.REP] only repeats the last rune and won't work + // if the last cell contains multiple runes. + + wrapPossible := s.cur.X+count >= newbuf.Width() + repCount := count + if wrapPossible { + repCount-- + } + + s.updatePen(&cell0) + s.putCell(newbuf, &cell0) + repCount-- // cell0 is a single width cell ASCII character + + _, _ = s.buf.WriteString(ansi.RepeatPreviousCharacter(repCount)) + s.cur.X += repCount + if wrapPossible { + s.putCell(newbuf, &cell0) + } + } else { + for i := 0; i < count; i++ { + s.putCell(newbuf, line.At(i)) + } + } + + line = line[clamp(count, 0, len(line)):] + n -= count + } + + return false + } + + for i := 0; i < n; i++ { + s.putCell(newbuf, line.At(i)) + } + + return false +} + +// putRange puts a range of cells from the old line to the new line. +// Returns whether the cursor is at the end of interval or somewhere in the +// middle. +func (s *TerminalRenderer) putRange(newbuf *RenderBuffer, oldLine, newLine Line, y, start, end int) (eoi bool) { + inline := min(len(ansi.CursorPosition(start+1, y+1)), + min(len(ansi.HorizontalPositionAbsolute(start+1)), + len(ansi.CursorForward(start+1)))) + if (end - start + 1) > inline { //nolint:nestif + var j, same int + for j, same = start, 0; j <= end; j++ { + oldCell, newCell := oldLine.At(j), newLine.At(j) + if same == 0 && oldCell != nil && oldCell.IsZero() { + continue + } + if cellEqual(oldCell, newCell) { + same++ + } else { + if same > end-start { + s.emitRange(newbuf, newLine[start:], j-same-start) + s.move(newbuf, j, y) + start = j + } + same = 0 + } + } + + i := s.emitRange(newbuf, newLine[start:], j-same-start) + + // Always return 1 for the next [tScreen.move] after a + // [tScreen.putRange] if we found identical characters at end of + // interval. + if same == 0 { + return i + } + return true + } + + return s.emitRange(newbuf, newLine[start:], end-start+1) +} + +// clearToEnd clears the screen from the current cursor position to the end of +// line. +func (s *TerminalRenderer) clearToEnd(newbuf *RenderBuffer, blank *Cell, force bool) { + if s.cur.Y >= 0 { + curline := s.curbuf.Line(s.cur.Y) + // We use the newbuf width because the current buffer might be smaller + // than the new buffer during a resize operation and we want to detect + // that. + for j := s.cur.X; j < newbuf.Width(); j++ { + if j >= 0 { + c := curline.At(j) + if !cellEqual(c, blank) { + curline.Set(j, blank) + force = true + } + } + } + } + + if force { + s.updatePen(blank) + count := newbuf.Width() - s.cur.X + if s.el0Cost() <= count { + _, _ = s.buf.WriteString(ansi.EraseLineRight) + } else { + for i := 0; i < count; i++ { + s.putCell(newbuf, blank) + } + } + } +} + +// clearBlank returns a blank cell based on the current cursor background color. +func (s *TerminalRenderer) clearBlank() *Cell { + return &s.cur.Cell +} + +// insertCells inserts the count cells pointed by the given line at the current +// cursor position. +func (s *TerminalRenderer) insertCells(newbuf *RenderBuffer, line Line, count int) { + supportsICH := s.caps.Contains(capICH) + if supportsICH { + // Use [ansi.ICH] as an optimization. + _, _ = s.buf.WriteString(ansi.InsertCharacter(count)) + } else { + // Otherwise, use [ansi.IRM] mode. + _, _ = s.buf.WriteString(ansi.SetModeInsertReplace) + } + + for i := 0; count > 0; i++ { + s.putAttrCell(newbuf, line.At(i)) + count-- + } + + if !supportsICH { + _, _ = s.buf.WriteString(ansi.ResetModeInsertReplace) + } +} + +// el0Cost returns the cost of using [ansi.EL] 0 i.e. [ansi.EraseLineRight]. If +// this terminal supports background color erase, it can be cheaper to use +// [ansi.EL] 0 i.e. [ansi.EraseLineRight] to clear +// trailing spaces. +func (s *TerminalRenderer) el0Cost() int { + if s.caps != noCaps { + return 0 + } + return len(ansi.EraseLineRight) +} + +// transformLine transforms the given line in the current window to the +// corresponding line in the new window. It uses [ansi.ICH] and [ansi.DCH] to +// insert or delete characters. +func (s *TerminalRenderer) transformLine(newbuf *RenderBuffer, y int) { + var firstCell, oLastCell, nLastCell int // first, old last, new last index + oldLine := s.curbuf.Line(y) + newLine := newbuf.Line(y) + + // Find the first changed cell in the line + blank := newLine.At(0) + + // It might be cheaper to clear leading spaces with [ansi.EL] 1 i.e. + // [ansi.EraseLineLeft]. + if canClearWith(blank) { //nolint:nestif + var oFirstCell, nFirstCell int + for oFirstCell = 0; oFirstCell < s.curbuf.Width(); oFirstCell++ { + if !cellEqual(oldLine.At(oFirstCell), blank) { + break + } + } + for nFirstCell = 0; nFirstCell < newbuf.Width(); nFirstCell++ { + if !cellEqual(newLine.At(nFirstCell), blank) { + break + } + } + + if nFirstCell == oFirstCell { + firstCell = nFirstCell + + // Find the first differing cell + for firstCell < newbuf.Width() && + cellEqual(oldLine.At(firstCell), newLine.At(firstCell)) { + firstCell++ + } + } else if oFirstCell > nFirstCell { + firstCell = nFirstCell + } else if oFirstCell < nFirstCell { + firstCell = oFirstCell + el1Cost := len(ansi.EraseLineLeft) + if el1Cost < nFirstCell-oFirstCell { + if nFirstCell >= newbuf.Width() { + s.move(newbuf, 0, y) + s.updatePen(blank) + _, _ = s.buf.WriteString(ansi.EraseLineRight) + } else { + s.move(newbuf, nFirstCell-1, y) + s.updatePen(blank) + _, _ = s.buf.WriteString(ansi.EraseLineLeft) + } + + for firstCell < nFirstCell { + oldLine.Set(firstCell, blank) + firstCell++ + } + } + } + } else { + // Find the first differing cell + for firstCell < newbuf.Width() && cellEqual(newLine.At(firstCell), oldLine.At(firstCell)) { + firstCell++ + } + } + + // If we didn't find one, we're done + if firstCell >= newbuf.Width() { + return + } + + blank = newLine.At(newbuf.Width() - 1) + if blank != nil && !canClearWith(blank) { + // Find the last differing cell + nLastCell = newbuf.Width() - 1 + for nLastCell > firstCell && cellEqual(newLine.At(nLastCell), oldLine.At(nLastCell)) { + nLastCell-- + } + + if nLastCell >= firstCell { + s.move(newbuf, firstCell, y) + s.putRange(newbuf, oldLine, newLine, y, firstCell, nLastCell) + if firstCell < len(oldLine) && firstCell < len(newLine) { + copy(oldLine[firstCell:], newLine[firstCell:]) + } else { + copy(oldLine, newLine) + } + } + + return + } + + // Find last non-blank cell in the old line. + // We always use the newbuf width to detect new cell changes. + oLastCell = newbuf.Width() - 1 + for oLastCell > firstCell && cellEqual(oldLine.At(oLastCell), blank) { + oLastCell-- + } + + // Find last non-blank cell in the new line. + // We always use the newbuf width to detect new cell changes. + nLastCell = newbuf.Width() - 1 + for nLastCell > firstCell && cellEqual(newLine.At(nLastCell), blank) { + nLastCell-- + } + + if nLastCell == firstCell && s.el0Cost() < oLastCell-nLastCell { //nolint:nestif + s.move(newbuf, firstCell, y) + if !cellEqual(newLine.At(firstCell), blank) { + s.putCell(newbuf, newLine.At(firstCell)) + } + s.clearToEnd(newbuf, blank, false) + } else if nLastCell != oLastCell && + !cellEqual(newLine.At(nLastCell), oldLine.At(oLastCell)) { + s.move(newbuf, firstCell, y) + if oLastCell-nLastCell > s.el0Cost() { + if s.putRange(newbuf, oldLine, newLine, y, firstCell, nLastCell) { + s.move(newbuf, nLastCell+1, y) + } + s.clearToEnd(newbuf, blank, false) + } else { + n := max(nLastCell, oLastCell) + s.putRange(newbuf, oldLine, newLine, y, firstCell, n) + } + } else { + nLastNonBlank := nLastCell + oLastNonBlank := oLastCell + + // Find the last cells that really differ. + // Can be -1 if no cells differ. + for cellEqual(newLine.At(nLastCell), oldLine.At(oLastCell)) { + if !cellEqual(newLine.At(nLastCell-1), oldLine.At(oLastCell-1)) { + break + } + nLastCell-- + oLastCell-- + if nLastCell == -1 || oLastCell == -1 { + break + } + } + + n := min(oLastCell, nLastCell) + if n >= firstCell { + s.move(newbuf, firstCell, y) + s.putRange(newbuf, oldLine, newLine, y, firstCell, n) + } + + if oLastCell < nLastCell { + m := max(nLastNonBlank, oLastNonBlank) + if n != 0 { + for n > 0 { + wide := newLine.At(n + 1) + if wide == nil || !wide.IsZero() { + break + } + n-- + oLastCell-- + } + } else if n >= firstCell && newLine.At(n) != nil && newLine.At(n).Width > 1 { + next := newLine.At(n + 1) + for next != nil && next.IsZero() { + n++ + oLastCell++ + } + } + + // TODO: This can sometimes send unnecessary cursor movements with + // negative or zero ranges. This could happen on a screen resize + // where oLastCell < nLastCell and oLastCell is -1 or less. + // Investigate and fix. + s.move(newbuf, n+1, y) + ichCost := 3 + nLastCell - oLastCell + if s.caps.Contains(capICH) && (nLastCell < nLastNonBlank || ichCost > (m-n)) { + s.putRange(newbuf, oldLine, newLine, y, n+1, m) + } else { + s.insertCells(newbuf, newLine[n+1:], nLastCell-oLastCell) + } + } else if oLastCell > nLastCell { + s.move(newbuf, n+1, y) + dchCost := 3 + oLastCell - nLastCell + if dchCost > len(ansi.EraseLineRight)+nLastNonBlank-(n+1) { + if s.putRange(newbuf, oldLine, newLine, y, n+1, nLastNonBlank) { + s.move(newbuf, nLastNonBlank+1, y) + } + s.clearToEnd(newbuf, blank, false) + } else { + s.updatePen(blank) + s.deleteCells(oLastCell - nLastCell) + } + } + } + + // Update the old line with the new line + if firstCell < len(oldLine) && firstCell < len(newLine) { + copy(oldLine[firstCell:], newLine[firstCell:]) + } else { + copy(oldLine, newLine) + } +} + +// deleteCells deletes the count cells at the current cursor position and moves +// the rest of the line to the left. This is equivalent to [ansi.DCH]. +func (s *TerminalRenderer) deleteCells(count int) { + // [ansi.DCH] will shift in cells from the right margin so we need to + // ensure that they are the right style. + _, _ = s.buf.WriteString(ansi.DeleteCharacter(count)) +} + +// clearToBottom clears the screen from the current cursor position to the end +// of the screen. +func (s *TerminalRenderer) clearToBottom(blank *Cell) { + row, col := s.cur.Y, s.cur.X + if row < 0 { + row = 0 + } + + s.updatePen(blank) + _, _ = s.buf.WriteString(ansi.EraseScreenBelow) + // Clear the rest of the current line + s.curbuf.ClearArea(Rect(col, row, s.curbuf.Width()-col, 1)) + // Clear everything below the current line + s.curbuf.ClearArea(Rect(0, row+1, s.curbuf.Width(), s.curbuf.Height()-row-1)) +} + +// clearBottom tests if clearing the end of the screen would satisfy part of +// the screen update. Scan backwards through lines in the screen checking if +// each is blank and one or more are changed. +// It returns the top line. +func (s *TerminalRenderer) clearBottom(newbuf *RenderBuffer, total int) (top int) { + if total <= 0 { + return 0 + } + + top = total + last := min(s.curbuf.Width(), newbuf.Width()) + blank := s.clearBlank() + canClearWithBlank := canClearWith(blank) + + if canClearWithBlank { //nolint:nestif + var row int + for row = total - 1; row >= 0; row-- { + oldLine := s.curbuf.Line(row) + newLine := newbuf.Line(row) + + var col int + ok := true + for col = 0; ok && col < last; col++ { + ok = cellEqual(newLine.At(col), blank) + } + if !ok { + break + } + + for col = 0; ok && col < last; col++ { + ok = cellEqual(oldLine.At(col), blank) + } + if !ok { + top = row + } + } + + if top < total { + s.move(newbuf, 0, max(0, top-1)) // top is 1-based + s.clearToBottom(blank) + if s.oldhash != nil && s.newhash != nil && + row < len(s.oldhash) && row < len(s.newhash) { + for row := top; row < newbuf.Height(); row++ { + s.oldhash[row] = s.newhash[row] + } + } + } + } + + return top +} + +// clearScreen clears the screen and put cursor at home. +func (s *TerminalRenderer) clearScreen(blank *Cell) { + s.updatePen(blank) + _, _ = s.buf.WriteString(ansi.CursorHomePosition) + _, _ = s.buf.WriteString(ansi.EraseEntireScreen) + s.cur.X, s.cur.Y = 0, 0 + s.curbuf.Fill(blank) +} + +// clearBelow clears everything below and including the row. +func (s *TerminalRenderer) clearBelow(newbuf *RenderBuffer, blank *Cell, row int) { + s.move(newbuf, 0, row) + s.clearToBottom(blank) +} + +// clearUpdate forces a screen redraw. +func (s *TerminalRenderer) clearUpdate(newbuf *RenderBuffer) { + blank := s.clearBlank() + var nonEmpty int + if s.flags.Contains(tFullscreen) { + // XXX: We're using the maximum height of the two buffers to ensure we + // write newly added lines to the screen in + // [terminalWriter.transformLine]. + nonEmpty = max(s.curbuf.Height(), newbuf.Height()) + s.clearScreen(blank) + } else { + nonEmpty = newbuf.Height() + // FIXME: Investigate the double [ansi.ClearScreenBelow] call. + // Commenting the line below out seems to work but it might cause other + // bugs. + s.clearBelow(newbuf, blank, 0) + } + nonEmpty = s.clearBottom(newbuf, nonEmpty) + for i := 0; i < nonEmpty && i < newbuf.Height(); i++ { + s.transformLine(newbuf, i) + } +} + +func (s *TerminalRenderer) logf(format string, args ...any) { + if s.logger == nil { + return + } + s.logger.Printf(format, args...) +} + +// Buffered returns the number of bytes buffered for the next flush. +func (s *TerminalRenderer) Buffered() int { + return s.buf.Len() +} + +// Flush flushes the buffer to the screen. +func (s *TerminalRenderer) Flush() (err error) { + // Write the buffer + if n := s.buf.Len(); n > 0 { + bts := s.buf.Bytes() + if s.logger != nil { + s.logf("output: %q", bts) + } + _, err = s.w.Write(bts) + s.buf.Reset() + } + return +} + +// Redraw forces a full redraw of the screen. It's equivalent to calling +// [TerminalRenderer.Erase] and [TerminalRenderer.Render]. +func (s *TerminalRenderer) Redraw(newbuf *RenderBuffer) { + s.clear = true + s.Render(newbuf) +} + +// Render renders changes of the screen to the internal buffer. Call +// [terminalWriter.Flush] to flush pending changes to the screen. +func (s *TerminalRenderer) Render(newbuf *RenderBuffer) { + // Do we need to render anything? + touchedLines := newbuf.TouchedLines() + if !s.clear && touchedLines == 0 { + return + } + + if s.curbuf == nil || s.curbuf.Bounds().Empty() { + // Initialize the current buffer + s.curbuf = NewRenderBuffer(newbuf.Width(), newbuf.Height()) + } + + newWidth, newHeight := newbuf.Width(), newbuf.Height() + curWidth, curHeight := s.curbuf.Width(), s.curbuf.Height() + + if curWidth != newWidth || curHeight != newHeight { + s.oldhash, s.newhash = nil, nil + } + + // TODO: Investigate whether this is necessary. Theoretically, terminals + // can add/remove tab stops and we should be able to handle that. We could + // use [ansi.DECTABSR] to read the tab stops, but that's not implemented in + // most terminals :/ + // // Are we using hard tabs? If so, ensure tabs are using the + // // default interval using [ansi.DECST8C]. + // if s.opts.HardTabs && !s.initTabs { + // s.buf.WriteString(ansi.SetTabEvery8Columns) + // s.initTabs = true + // } + + var nonEmpty int + + // XXX: In inline mode, after a screen resize, we need to clear the extra + // lines at the bottom of the screen. This is because in inline mode, we + // don't use the full screen height and the current buffer size might be + // larger than the new buffer size. + partialClear := !s.flags.Contains(tFullscreen) && s.cur.X != -1 && s.cur.Y != -1 && + curWidth == newWidth && + curHeight > 0 && + curHeight > newHeight + + if !s.clear && partialClear { + s.clearBelow(newbuf, nil, newHeight-1) + } + + if s.clear { //nolint:nestif + s.clearUpdate(newbuf) + s.clear = false + } else if touchedLines > 0 { + // On Windows, there's a bug with Windows Terminal where [ansi.DECSTBM] + // misbehaves and moves the cursor outside of the scrolling region. For + // now, we disable the optimizations completely on Windows. + // See https://github.com/microsoft/terminal/issues/19016 + if s.flags.Contains(tScrollOptim) && s.flags.Contains(tFullscreen) { + // Optimize scrolling for the alternate screen buffer. + // TODO: Should we optimize for inline mode as well? If so, we need + // to know the actual cursor position to use [ansi.DECSTBM]. + s.scrollOptimize(newbuf) + } + + var changedLines int + var i int + + if s.flags.Contains(tFullscreen) { + nonEmpty = min(curHeight, newHeight) + } else { + nonEmpty = newHeight + } + + nonEmpty = s.clearBottom(newbuf, nonEmpty) + for i = 0; i < nonEmpty && i < newHeight; i++ { + if newbuf.Touched == nil || i >= len(newbuf.Touched) || (newbuf.Touched[i] != nil && + (newbuf.Touched[i].FirstCell != -1 || newbuf.Touched[i].LastCell != -1)) { + s.transformLine(newbuf, i) + changedLines++ + } + + // Mark line changed successfully. + if i < len(newbuf.Touched) && i <= newbuf.Height()-1 { + newbuf.Touched[i] = &LineData{ + FirstCell: -1, LastCell: -1, + } + } + if i < len(s.curbuf.Touched) && i < s.curbuf.Height()-1 { + s.curbuf.Touched[i] = &LineData{ + FirstCell: -1, LastCell: -1, + } + } + } + } + + if !s.flags.Contains(tFullscreen) && (curWidth != newWidth || curHeight != newHeight) { + s.move(newbuf, 0, newHeight-1) + } + + // Sync windows and screen + newbuf.Touched = make([]*LineData, newHeight) + for i := range newbuf.Touched { + newbuf.Touched[i] = &LineData{ + FirstCell: -1, LastCell: -1, + } + } + for i := range s.curbuf.Touched { + s.curbuf.Touched[i] = &LineData{ + FirstCell: -1, LastCell: -1, + } + } + + if curWidth != newWidth || curHeight != newHeight { + // Resize the old buffer to match the new buffer. + s.curbuf.Resize(newWidth, newHeight) + // Sync new lines to old lines + for i := curHeight - 1; i < newHeight; i++ { + copy(s.curbuf.Line(i), newbuf.Line(i)) + } + } + + s.updatePen(nil) // nil indicates a blank cell with no styles +} + +// Erase marks the screen to be fully erased on the next render. +func (s *TerminalRenderer) Erase() { + s.clear = true +} + +// Resize updates the terminal screen tab stops. This is used to calculate +// terminal tab stops for hard tab optimizations. +func (s *TerminalRenderer) Resize(width, _ int) { + if s.tabs != nil { + s.tabs.Resize(width) + } +} + +// Position returns the cursor position in the screen buffer after applying any +// pending transformations from the underlying buffer. +func (s *TerminalRenderer) Position() (x, y int) { + return s.cur.X, s.cur.Y +} + +// SetPosition changes the logical cursor position. This can be used when we +// change the cursor position outside of the screen and need to update the +// screen cursor position. +// This changes the cursor position for both normal and alternate screen +// buffers. +func (s *TerminalRenderer) SetPosition(x, y int) { + s.cur.X, s.cur.Y = x, y +} + +// WriteString writes the given string to the underlying buffer. +func (s *TerminalRenderer) WriteString(str string) (int, error) { + return s.buf.WriteString(str) //nolint:wrapcheck +} + +// Write writes the given bytes to the underlying buffer. +func (s *TerminalRenderer) Write(b []byte) (int, error) { + return s.buf.Write(b) //nolint:wrapcheck +} + +// MoveTo calculates and writes the shortest sequence to move the cursor to the +// given position. It uses the current cursor position and the new position to +// calculate the shortest amount of sequences to move the cursor. +func (s *TerminalRenderer) MoveTo(x, y int) { + s.move(nil, x, y) +} + +// notLocal returns whether the coordinates are not considered local movement +// using the defined thresholds. +// This takes the number of columns, and the coordinates of the current and +// target positions. +func notLocal(cols, fx, fy, tx, ty int) bool { + // The typical distance for a [ansi.CUP] sequence. Anything less than this + // is considered local movement. + const longDist = 8 - 1 + return (tx > longDist) && + (tx < cols-1-longDist) && + (abs(ty-fy)+abs(tx-fx) > longDist) +} + +// relativeCursorMove returns the relative cursor movement sequence using one or two +// of the following sequences [ansi.CUU], [ansi.CUD], [ansi.CUF], [ansi.CUB], +// [ansi.VPA], [ansi.HPA]. +// When overwrite is true, this will try to optimize the sequence by using the +// screen cells values to move the cursor instead of using escape sequences. +// +// It is safe to call this function with a nil [Buffer]. In that case, it won't +// use any optimizations that require the new buffer such as overwrite. +func relativeCursorMove(s *TerminalRenderer, newbuf *RenderBuffer, fx, fy, tx, ty int, overwrite, useTabs, useBackspace bool) string { + var seq strings.Builder + if newbuf == nil { + overwrite = false // We can't overwrite the current buffer. + } + + if ty != fy { //nolint:nestif + var yseq string + if s.caps.Contains(capVPA) && !s.flags.Contains(tRelativeCursor) { + yseq = ansi.VerticalPositionAbsolute(ty + 1) + } + + if ty > fy { + n := ty - fy + if cud := ansi.CursorDown(n); yseq == "" || len(cud) < len(yseq) { + yseq = cud + } + if !s.flags.Contains(tFullscreen) || n < len(yseq) { // n is the cost of using newline characters + yseq = strings.Repeat("\n", n) + if s.flags.Contains(tMapNewline) { + fx = 0 + } + } + } else if ty < fy { + n := fy - ty + if cuu := ansi.CursorUp(n); yseq == "" || len(cuu) < len(yseq) { + yseq = cuu + } + if n == 1 && fy-1 > 0 { + // TODO: Ensure we're not unintentionally scrolling the screen up. + yseq = ansi.ReverseIndex + } + } + + seq.WriteString(yseq) + } + + if tx != fx { //nolint:nestif + var xseq string + if !s.flags.Contains(tRelativeCursor) { + if s.caps.Contains(capHPA) { + xseq = ansi.HorizontalPositionAbsolute(tx + 1) + } else if s.caps.Contains(capCHA) { + xseq = ansi.CursorHorizontalAbsolute(tx + 1) + } + } + + if tx > fx { + n := tx - fx + if useTabs && s.tabs != nil { + var tabs int + var col int + for col = fx; s.tabs.Next(col) <= tx; col = s.tabs.Next(col) { + tabs++ + if col == s.tabs.Next(col) || col >= s.tabs.Width()-1 { + break + } + } + + if tabs > 0 { + cht := ansi.CursorHorizontalForwardTab(tabs) + tab := strings.Repeat("\t", tabs) + if false && s.caps.Contains(capCHT) && len(cht) < len(tab) { + // TODO: The linux console and some terminals such as + // Alacritty don't support [ansi.CHT]. Enable this when + // we have a way to detect this, or after 5 years when + // we're sure everyone has updated their terminals :P + seq.WriteString(cht) + } else { + seq.WriteString(tab) + } + + n = tx - col + fx = col + } + } + + if cuf := ansi.CursorForward(n); xseq == "" || len(cuf) < len(xseq) { + xseq = cuf + } + + // If we have no attribute and style changes, overwrite is cheaper. + var ovw string + if overwrite && ty >= 0 { + for i := 0; i < n; i++ { + cell := newbuf.CellAt(fx+i, ty) + if cell != nil && cell.Width > 0 { + i += cell.Width - 1 + if !cell.Style.Equal(&s.cur.Style) || !cell.Link.Equal(&s.cur.Link) { + overwrite = false + break + } + } + } + } + + if overwrite && ty >= 0 { + for i := 0; i < n; i++ { + cell := newbuf.CellAt(fx+i, ty) + if cell != nil && cell.Width > 0 { + ovw += cell.String() + i += cell.Width - 1 + } else { + ovw += " " + } + } + } + + if overwrite && len(ovw) < len(xseq) { + xseq = ovw + } + } else if tx < fx { + n := fx - tx + if useTabs && s.tabs != nil && s.caps.Contains(capCBT) { + // VT100 does not support backward tabs [ansi.CBT]. + + col := fx + + var cbt int // cursor backward tabs count + for s.tabs.Prev(col) >= tx { + col = s.tabs.Prev(col) + cbt++ + if col == s.tabs.Prev(col) || col <= 0 { + break + } + } + + if cbt > 0 { + seq.WriteString(ansi.CursorBackwardTab(cbt)) + n = col - tx + } + } + + if cub := ansi.CursorBackward(n); xseq == "" || len(cub) < len(xseq) { + xseq = cub + } + + if useBackspace && n < len(xseq) { + xseq = strings.Repeat("\b", n) + } + } + + seq.WriteString(xseq) + } + + return seq.String() +} + +// moveCursor moves and returns the cursor movement sequence to move the cursor +// to the specified position. +// When overwrite is true, this will try to optimize the sequence by using the +// screen cells values to move the cursor instead of using escape sequences. +// +// It is safe to call this function with a nil [Buffer]. In that case, it won't +// use any optimizations that require the new buffer such as overwrite. +func moveCursor(s *TerminalRenderer, newbuf *RenderBuffer, x, y int, overwrite bool) (seq string) { + fx, fy := s.cur.X, s.cur.Y + + if !s.flags.Contains(tRelativeCursor) { + width := -1 // Use -1 to indicate that we don't know the width of the screen. + if s.tabs != nil { + width = s.tabs.Width() + } + if newbuf != nil && width == -1 { + // Even though this might not be accurate, we can use the new + // buffer width as a fallback. Technically, if the new buffer + // didn't have the width of the terminal, this would give us a + // wrong result from [notLocal]. + width = newbuf.Width() + } + // Method #0: Use [ansi.CUP] if the distance is long. + seq = ansi.CursorPosition(x+1, y+1) + if fx == -1 || fy == -1 || width == -1 || notLocal(width, fx, fy, x, y) { + return seq + } + } + + // Optimize based on options. + trials := 0 + if s.caps.Contains(capHT) { + trials |= 2 // 0b10 in binary + } + if s.caps.Contains(capBS) { + trials |= 1 // 0b01 in binary + } + + // Try all possible combinations of hard tabs and backspace optimizations. + for i := 0; i <= trials; i++ { + // Skip combinations that are not enabled. + if i & ^trials != 0 { + continue + } + + useHardTabs := i&2 != 0 + useBackspace := i&1 != 0 + + // Method #1: Use local movement sequences. + nseq1 := relativeCursorMove(s, newbuf, fx, fy, x, y, overwrite, useHardTabs, useBackspace) + if (i == 0 && len(seq) == 0) || len(nseq1) < len(seq) { + seq = nseq1 + } + + // Method #2: Use [ansi.CR] and local movement sequences. + nseq2 := relativeCursorMove(s, newbuf, 0, fy, x, y, overwrite, useHardTabs, useBackspace) + nseq2 = "\r" + nseq2 + if len(nseq2) < len(seq) { + seq = nseq2 + } + + if !s.flags.Contains(tRelativeCursor) { + // Method #3: Use [ansi.CursorHomePosition] and local movement sequences. + nseq3 := relativeCursorMove(s, newbuf, 0, 0, x, y, overwrite, useHardTabs, useBackspace) + nseq3 = ansi.CursorHomePosition + nseq3 + if len(nseq3) < len(seq) { + seq = nseq3 + } + } + } + + return seq +} + +// xtermCaps returns whether the terminal is xterm-like. This means that the +// terminal supports ECMA-48 and ANSI X3.64 escape sequences. +// xtermCaps returns a list of control sequence capabilities for the given +// terminal type. This only supports a subset of sequences that can +// be different among terminals. +// NOTE: A hybrid approach would be to support Terminfo databases for a full +// set of capabilities. +func xtermCaps(termtype string) (v capabilities) { + parts := strings.Split(termtype, "-") + if len(parts) == 0 { + return v + } + + switch parts[0] { + case + "contour", + "foot", + "ghostty", + "kitty", + "rio", + "st", + "tmux", + "wezterm": + v = allCaps + case "xterm": + switch { + case len(parts) > 1 && parts[1] == "ghostty": + fallthrough + case len(parts) > 1 && parts[1] == "kitty": + fallthrough + case len(parts) > 1 && parts[1] == "rio": + // These terminals can be defined as xterm- variants for + // compatibility with applications that check for xterm. + v = allCaps + default: + // NOTE: We exclude capHPA from allCaps because terminals like + // Konsole don't support it. Xterm terminfo defines HPA as CHA + // which means we can use CHA instead of HPA. + v = allCaps + v &^= capHPA + v &^= capCHT + v &^= capREP + } + case "alacritty": + v = allCaps + v &^= capCHT // NOTE: alacritty added support for [ansi.CHT] in 2024-12-28 #62d5b13. + case "screen": + // See https://www.gnu.org/software/screen/manual/screen.html#Control-Sequences-1 + v = allCaps + v &^= capREP + case "linux": + // See https://man7.org/linux/man-pages/man4/console_codes.4.html + v = capVPA | capCHA | capHPA | capECH | capICH + } + + return v +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hardscroll.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hardscroll.go new file mode 100644 index 000000000..3b7eb00e4 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hardscroll.go @@ -0,0 +1,243 @@ +package uv + +import ( + "strings" + + "github.com/charmbracelet/x/ansi" +) + +// scrollOptimize optimizes the screen to transform the old buffer into the new +// buffer. +func (s *TerminalRenderer) scrollOptimize(newbuf *RenderBuffer) { + height := newbuf.Height() + if s.oldnum == nil || len(s.oldnum) < height { + s.oldnum = append(s.oldnum, make([]int, height-len(s.oldnum))...) + } + + // Calculate the indices + s.updateHashmap(newbuf) + if len(s.hashtab) < height { + return + } + + // Pass 1 - from top to bottom scrolling up + for i := 0; i < height; { + for i < height && (s.oldnum[i] == newIndex || s.oldnum[i] <= i) { + i++ + } + if i >= height { + break + } + + shift := s.oldnum[i] - i // shift > 0 + start := i + + i++ + for i < height && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift { + i++ + } + end := i - 1 + shift + + if !s.scrolln(newbuf, shift, start, end, height-1) { + continue + } + } + + // Pass 2 - from bottom to top scrolling down + for i := height - 1; i >= 0; { + for i >= 0 && (s.oldnum[i] == newIndex || s.oldnum[i] >= i) { + i-- + } + if i < 0 { + break + } + + shift := s.oldnum[i] - i // shift < 0 + end := i + + i-- + for i >= 0 && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift { + i-- + } + + start := i + 1 - (-shift) + if !s.scrolln(newbuf, shift, start, end, height-1) { + continue + } + } +} + +// scrolln scrolls the screen up by n lines. +func (s *TerminalRenderer) scrolln(newbuf *RenderBuffer, n, top, bot, maxY int) (v bool) { //nolint:unparam + blank := s.clearBlank() + if n > 0 { //nolint:nestif + // Scroll up (forward) + v = s.scrollUp(newbuf, n, top, bot, 0, maxY, blank) + if !v { + s.buf.WriteString(ansi.SetTopBottomMargins(top+1, bot+1)) + + // XXX: How should we handle this in inline mode when not using alternate screen? + s.cur.X, s.cur.Y = -1, -1 + v = s.scrollUp(newbuf, n, top, bot, top, bot, blank) + s.buf.WriteString(ansi.SetTopBottomMargins(1, maxY+1)) + s.cur.X, s.cur.Y = -1, -1 + } + + if !v { + v = s.scrollIdl(newbuf, n, top, bot-n+1, blank) + } + } else if n < 0 { + // Scroll down (backward) + v = s.scrollDown(newbuf, -n, top, bot, 0, maxY, blank) + if !v { + s.buf.WriteString(ansi.SetTopBottomMargins(top+1, bot+1)) + + // XXX: How should we handle this in inline mode when not using alternate screen? + s.cur.X, s.cur.Y = -1, -1 + v = s.scrollDown(newbuf, -n, top, bot, top, bot, blank) + s.buf.WriteString(ansi.SetTopBottomMargins(1, maxY+1)) + s.cur.X, s.cur.Y = -1, -1 + + if !v { + v = s.scrollIdl(newbuf, -n, bot+n+1, top, blank) + } + } + } + + if !v { + return false + } + + s.scrollBuffer(s.curbuf, n, top, bot, blank) + + // shift hash values too, they can be reused + s.scrollOldhash(n, top, bot) + + return true +} + +// scrollBuffer scrolls the buffer by n lines. +func (s *TerminalRenderer) scrollBuffer(b *RenderBuffer, n, top, bot int, blank *Cell) { + if top < 0 || bot < top || bot >= b.Height() { + // Nothing to scroll + return + } + + if n < 0 { + // shift n lines downwards + limit := top - n + for line := bot; line >= limit && line >= 0 && line >= top; line-- { + copy(b.Lines[line], b.Lines[line+n]) + } + for line := top; line < limit && line <= b.Height()-1 && line <= bot; line++ { + b.FillArea(blank, Rect(0, line, b.Width(), 1)) + } + } + + if n > 0 { + // shift n lines upwards + limit := bot - n + for line := top; line <= limit && line <= b.Height()-1 && line <= bot; line++ { + copy(b.Lines[line], b.Lines[line+n]) + } + for line := bot; line > limit && line >= 0 && line >= top; line-- { + b.FillArea(blank, Rect(0, line, b.Width(), 1)) + } + } + + s.touchLine(b, top, bot-top+1, true) +} + +// touchLine marks the line as touched. +func (s *TerminalRenderer) touchLine(newbuf *RenderBuffer, y, n int, changed bool) { + height := newbuf.Height() + if n < 0 || y < 0 || y >= height || newbuf.Touched == nil || len(newbuf.Touched) < height { + return // Nothing to touch + } + + width := newbuf.Width() + for i := y; i < y+n && i < height && i < len(newbuf.Touched); i++ { + if changed { + newbuf.TouchLine(0, i, width) + } else { + newbuf.Touched[i] = nil + } + } +} + +// scrollUp scrolls the screen up by n lines. +func (s *TerminalRenderer) scrollUp(newbuf *RenderBuffer, n, top, bot, minY, maxY int, blank *Cell) bool { + if n == 1 && top == minY && bot == maxY { //nolint:nestif + s.move(newbuf, 0, bot) + s.updatePen(blank) + s.buf.WriteByte('\n') + } else if n == 1 && bot == maxY { + s.move(newbuf, 0, top) + s.updatePen(blank) + s.buf.WriteString(ansi.DeleteLine(1)) + } else if top == minY && bot == maxY { + supportsSU := s.caps.Contains(capSU) + s.move(newbuf, 0, bot) + s.updatePen(blank) + if supportsSU { + s.buf.WriteString(ansi.ScrollUp(n)) + } else { + s.buf.WriteString(strings.Repeat("\n", n)) + } + } else if bot == maxY { + s.move(newbuf, 0, top) + s.updatePen(blank) + s.buf.WriteString(ansi.DeleteLine(n)) + } else { + return false + } + return true +} + +// scrollDown scrolls the screen down by n lines. +func (s *TerminalRenderer) scrollDown(newbuf *RenderBuffer, n, top, bot, minY, maxY int, blank *Cell) bool { + if n == 1 && top == minY && bot == maxY { //nolint:nestif + s.move(newbuf, 0, top) + s.updatePen(blank) + s.buf.WriteString(ansi.ReverseIndex) + } else if n == 1 && bot == maxY { + s.move(newbuf, 0, top) + s.updatePen(blank) + s.buf.WriteString(ansi.InsertLine(1)) + } else if top == minY && bot == maxY { + s.move(newbuf, 0, top) + s.updatePen(blank) + if s.caps.Contains(capSD) { + s.buf.WriteString(ansi.ScrollDown(n)) + } else { + s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n)) + } + } else if bot == maxY { + s.move(newbuf, 0, top) + s.updatePen(blank) + s.buf.WriteString(ansi.InsertLine(n)) + } else { + return false + } + return true +} + +// scrollIdl scrolls the screen n lines by using [ansi.DL] at del and using +// [ansi.IL] at ins. +func (s *TerminalRenderer) scrollIdl(newbuf *RenderBuffer, n, del, ins int, blank *Cell) bool { + if n < 0 { + return false + } + + // Delete lines + s.move(newbuf, 0, del) + s.updatePen(blank) + s.buf.WriteString(ansi.DeleteLine(n)) + + // Insert lines + s.move(newbuf, 0, ins) + s.updatePen(blank) + s.buf.WriteString(ansi.InsertLine(n)) + + return true +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hashmap.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hashmap.go new file mode 100644 index 000000000..ad776ff83 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hashmap.go @@ -0,0 +1,298 @@ +package uv + +import "hash/maphash" + +// hash returns the hash value of a [Line]. +func hash(h *maphash.Hash, l Line) uint64 { + h.Reset() + for _, c := range l { + // maphash writes can not fail + _, _ = h.WriteString(c.Content) + } + + return h.Sum64() +} + +// hashmap represents a single [Line] hash. +type hashmap struct { + value uint64 + oldcount, newcount int + oldindex, newindex int +} + +// The value used to indicate lines created by insertions and scrolls. +const newIndex = -1 + +// updateHashmap updates the hashmap with the new hash value. +func (s *TerminalRenderer) updateHashmap(newbuf *RenderBuffer) { + height := newbuf.Height() + + if len(s.oldhash) >= height && len(s.newhash) >= height { + // rehash changed lines + for i := range height { + if newbuf.Touched == nil || newbuf.Touched[i] != nil { + // TODO: Investigate why this is needed. If we remove this + // line, scroll optimization does not work correctly. This + // should happen else where. + s.oldhash[i] = hash(&s.hasher, s.curbuf.Line(i)) + s.newhash[i] = hash(&s.hasher, newbuf.Line(i)) + } + } + } else { + // rehash all + if len(s.oldhash) != height { + s.oldhash = make([]uint64, height) + } + if len(s.newhash) != height { + s.newhash = make([]uint64, height) + } + for i := range height { + s.oldhash[i] = hash(&s.hasher, s.curbuf.Line(i)) + s.newhash[i] = hash(&s.hasher, newbuf.Line(i)) + } + } + + s.hashtab = make([]hashmap, (height+1)*2) + for i := range height { + hashval := s.oldhash[i] + + // Find matching hash or empty slot + var idx int + for idx = 0; idx < len(s.hashtab) && s.hashtab[idx].value != 0; idx++ { + if s.hashtab[idx].value == hashval { + break + } + } + + s.hashtab[idx].value = hashval // in case this is a new hash + s.hashtab[idx].oldcount++ + s.hashtab[idx].oldindex = i + } + for i := range height { + hashval := s.newhash[i] + + // Find matching hash or empty slot + var idx int + for idx = 0; idx < len(s.hashtab) && s.hashtab[idx].value != 0; idx++ { + if s.hashtab[idx].value == hashval { + break + } + } + + s.hashtab[idx].value = hashval // in case this is a new hash + s.hashtab[idx].newcount++ + s.hashtab[idx].newindex = i + s.oldnum[i] = newIndex // init old indices slice + } + + // Mark line pair corresponding to unique hash pairs. + for i := 0; i < len(s.hashtab) && s.hashtab[i].value != 0; i++ { + hsp := &s.hashtab[i] + if hsp.oldcount == 1 && hsp.newcount == 1 && hsp.oldindex != hsp.newindex { + s.oldnum[hsp.newindex] = hsp.oldindex + } + } + + s.growHunks(newbuf) + + // Eliminate bad or impossible shifts. This includes removing those hunks + // which could not grow because of conflicts, as well those which are to be + // moved too far, they are likely to destroy more than carry. + for i := 0; i < height; { + var start, shift, size int + for i < height && s.oldnum[i] == newIndex { + i++ + } + if i >= height { + break + } + start = i + shift = s.oldnum[i] - i + i++ + for i < height && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift { + i++ + } + size = i - start + if size < 3 || size+min(size/8, 2) < abs(shift) { + for start < i { + s.oldnum[start] = newIndex + start++ + } + } + } + + // After clearing invalid hunks, try grow the rest. + s.growHunks(newbuf) +} + +// scrollOldhash scrolls the oldhash slice by 'n' lines between 'top' and 'bot'. +func (s *TerminalRenderer) scrollOldhash(n, top, bot int) { + if len(s.oldhash) == 0 { + return + } + + size := bot - top + 1 - abs(n) + if n > 0 { + // Move existing hashes up + copy(s.oldhash[top:], s.oldhash[top+n:top+n+size]) + // Recalculate hashes for newly shifted-in lines + for i := bot; i > bot-n; i-- { + s.oldhash[i] = hash(&s.hasher, s.curbuf.Line(i)) + } + } else { + // Move existing hashes down + copy(s.oldhash[top-n:], s.oldhash[top:top+size]) + // Recalculate hashes for newly shifted-in lines + for i := top; i < top-n; i++ { + s.oldhash[i] = hash(&s.hasher, s.curbuf.Line(i)) + } + } +} + +func (s *TerminalRenderer) growHunks(newbuf *RenderBuffer) { + var ( + backLimit int // limits for cells to fill + backRefLimit int // limit for references + i int + nextHunk int + ) + + height := newbuf.Height() + for i < height && s.oldnum[i] == newIndex { + i++ + } + for ; i < height; i = nextHunk { + var ( + forwardLimit int + forwardRefLimit int + end int + start = i + shift = s.oldnum[i] - i + ) + + // get forward limit + i = start + 1 + for i < height && + s.oldnum[i] != newIndex && + s.oldnum[i]-i == shift { + i++ + } + + end = i + for i < height && s.oldnum[i] == newIndex { + i++ + } + + nextHunk = i + forwardLimit = i + if i >= height || s.oldnum[i] >= i { + forwardRefLimit = i + } else { + forwardRefLimit = s.oldnum[i] + } + + i = start - 1 + + // grow back + if shift < 0 { + backLimit = backRefLimit + (-shift) + } + for i >= backLimit { + if s.newhash[i] == s.oldhash[i+shift] || + s.costEffective(newbuf, i+shift, i, shift < 0) { + s.oldnum[i] = i + shift + } else { + break + } + i-- + } + + i = end + // grow forward + if shift > 0 { + forwardLimit = forwardRefLimit - shift + } + for i < forwardLimit { + if s.newhash[i] == s.oldhash[i+shift] || + s.costEffective(newbuf, i+shift, i, shift > 0) { + s.oldnum[i] = i + shift + } else { + break + } + i++ + } + + backLimit = i + backRefLimit = backLimit + if shift > 0 { + backRefLimit += shift + } + } +} + +// costEffective returns true if the cost of moving line 'from' to line 'to' seems to be +// cost effective. 'blank' indicates whether the line 'to' would become blank. +func (s *TerminalRenderer) costEffective(newbuf *RenderBuffer, from, to int, blank bool) bool { + if from == to { + return false + } + + newFrom := s.oldnum[from] + if newFrom == newIndex { + newFrom = from + } + + // On the left side of >= is the cost before moving. On the right side -- + // cost after moving. + + // Calculate costs before moving. + var costBeforeMove int + if blank { + // Cost of updating blank line at destination. + costBeforeMove = s.updateCostBlank(newbuf, newbuf.Line(to)) + } else { + // Cost of updating exiting line at destination. + costBeforeMove = s.updateCost(newbuf, s.curbuf.Line(to), newbuf.Line(to)) + } + + // Add cost of updating source line + costBeforeMove += s.updateCost(newbuf, s.curbuf.Line(newFrom), newbuf.Line(from)) + + // Calculate costs after moving. + var costAfterMove int + if newFrom == from { + // Source becomes blank after move + costAfterMove = s.updateCostBlank(newbuf, newbuf.Line(from)) + } else { + // Source gets updated from another line + costAfterMove = s.updateCost(newbuf, s.curbuf.Line(newFrom), newbuf.Line(from)) + } + + // Add cost of moving source line to destination + costAfterMove += s.updateCost(newbuf, s.curbuf.Line(from), newbuf.Line(to)) + + // Return true if moving is cost effective (costs less or equal) + return costBeforeMove >= costAfterMove +} + +func (s *TerminalRenderer) updateCost(_ *RenderBuffer, from, to Line) (cost int) { + var fidx, tidx int + for i := s.curbuf.Width(); i > 0; i, fidx, tidx = i-1, fidx+1, tidx+1 { + if !cellEqual(from.At(fidx), to.At(tidx)) { + cost++ + } + } + return +} + +func (s *TerminalRenderer) updateCostBlank(_ *RenderBuffer, to Line) (cost int) { + // This assumes bce capability. + blank := s.clearBlank() + var tidx int + for i := s.curbuf.Width(); i > 0; i, tidx = i-1, tidx+1 { + if !cellEqual(blank, to.At(tidx)) { + cost++ + } + } + return +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_screen.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_screen.go new file mode 100644 index 000000000..ae79963f5 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_screen.go @@ -0,0 +1,825 @@ +package uv + +import ( + "bytes" + "image/color" + "io" + "log" + "os" + "strings" + + "github.com/charmbracelet/colorprofile" + "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/x/term" +) + +// TerminalScreen represents a terminal screen, providing methods for managing +// the screen state and rendering content. +type TerminalScreen struct { + win *Window + w io.Writer + buf *bytes.Buffer + rend *TerminalRenderer + rbuf *RenderBuffer + env Environ + profile colorprofile.Profile + + // Terminal state + altScreen bool + keyboardEnhancements *KeyboardEnhancements + bracketedPaste bool + mouseMode MouseMode + cursor *Cursor // initial state is cursor hidden + backgroundColor color.Color + foregroundColor color.Color + progressBar *ProgressBar + windowTitle string + syncUpdates bool // mode 2026 +} + +var _ Screen = (*TerminalScreen)(nil) + +// NewTerminalScreen creates a new [TerminalScreen] with the given writer and environment. +func NewTerminalScreen(w io.Writer, env Environ) *TerminalScreen { + s := &TerminalScreen{} + s.buf = &bytes.Buffer{} + s.win = NewScreen(0, 0) + s.w = w + s.profile = colorprofile.Detect(w, env) + s.rend = NewTerminalRenderer(s.buf, env) + s.rend.SetFullscreen(false) // by default, we start in inline mode + s.rend.SetRelativeCursor(true) // by default, we start in inline mode + s.rend.SetColorProfile(s.profile) + s.rbuf = NewRenderBuffer(0, 0) + s.env = env + + if debugFile := env.Getenv("UV_DEBUG"); debugFile != "" { + if f, err := os.OpenFile(debugFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644); err == nil { + log.SetOutput(f) + s.rend.SetLogger(log.Default()) + } + } + + // Configure renderer optimizations based on console settings. + f, ok := w.(term.File) + if ok { + state, err := term.GetState(f.Fd()) + if err == nil { + useTabs, useBspace := optimizeMovements(state) + if useTabs { + s.rend.SetTabStops(0) // the width will be set after calling [TerminalScreen.Resize] + } + s.rend.SetBackspace(useBspace) + } + } + // XXX: Do we still need map nl to crlf handling in the renderer? + s.rend.SetMapNewline(false) + return s +} + +// CellAt returns the cell at the specified x and y coordinates. +func (s *TerminalScreen) CellAt(x, y int) *Cell { + return s.win.CellAt(x, y) +} + +// SetCell sets the cell at the specified x and y coordinates. +func (s *TerminalScreen) SetCell(x, y int, cell *Cell) { + s.win.SetCell(x, y, cell) +} + +// Bounds returns the bounds of the terminal screen as a rectangle. +func (s *TerminalScreen) Bounds() Rectangle { + return s.win.Bounds() +} + +// WidthMethod returns the width method used by the terminal screen. +func (s *TerminalScreen) WidthMethod() WidthMethod { + return s.win.WidthMethod() +} + +// SetWidthMethod sets the width method for the terminal screen. +func (s *TerminalScreen) SetWidthMethod(method ansi.Method) { + s.win.SetWidthMethod(method) +} + +// SetColorProfile sets the color profile for the terminal screen. +// This is automatically detected when creating the terminal screen. However, +// you can override it using this method. +func (s *TerminalScreen) SetColorProfile(profile colorprofile.Profile) { + s.profile = profile + s.rend.SetColorProfile(profile) +} + +// Resize resizes the terminal screen to the specified width and height, +// updating the render buffer and renderer accordingly. +func (s *TerminalScreen) Resize(width, height int) error { + s.win.Resize(width, height) + s.rbuf.Resize(width, height) + s.rend.Resize(width, height) + s.rend.Erase() + s.rbuf.Touched = nil + return nil +} + +// Display clears the screen and draws the given [Drawable] onto the terminal +// screen and flushes the changes to the underlying writer. +// +// This is a convenience method that combines [TerminalScreen.Render] and +// [TerminalScreen.Flush]. +func (s *TerminalScreen) Display(d Drawable) error { + if d != nil { + s.win.Clear() + d.Draw(s, s.win.Bounds()) + } + if err := s.Render(); err != nil { + return err + } + return s.Flush() +} + +// Render renders changes that transform the screen from its current state to +// the state represented by the [TerminalScreen]. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) Render() error { + for y := 0; y < s.win.Height(); y++ { + for x := 0; x < s.win.Width(); { + cell := s.win.CellAt(x, y) + if cell == nil || cell.IsZero() { + x++ + continue + } + s.rbuf.SetCell(x, y, cell) + width := cell.Width + if width <= 0 { + width = 1 + } + x += width + } + } + s.rend.Render(s.rbuf) + return s.rend.Flush() +} + +// Flush writes any pending output to the underlying writer. +func (s *TerminalScreen) Flush() error { + if s.cursor != nil && !s.cursor.Hidden && s.cursor.X >= 0 && s.cursor.Y >= 0 { + s.rend.MoveTo(s.cursor.X, s.cursor.Y) + } else if !s.altScreen { + // We don't want the cursor to be dangling at the end of the line in + // inline mode because it can cause unwanted line wraps in some + // terminals. So we move it to the beginning of the next line if + // necessary. + // This is only needed when the cursor is hidden because when it's + // visible, we already set its position above. + x, y := s.rend.Position() + if x >= s.win.Width()-1 { + s.rend.MoveTo(0, y) + } + } + + var buf bytes.Buffer + buf.Grow(s.buf.Len()) + + if s.buf.Len() > 0 { + if s.syncUpdates { + buf.Grow(len(ansi.SetModeSynchronizedOutput) + len(ansi.ResetModeSynchronizedOutput)) + + // If synchronized updates are enabled, we need to wrap the output in + // the appropriate control sequences to ensure that the terminal treats + // it as a single atomic update. This is necessary to prevent flickering + // and other visual artifacts that can occur when multiple updates are sent + // separately. + buf.WriteString(ansi.SetModeSynchronizedOutput) + } else if s.cursor != nil && !s.cursor.Hidden { + buf.Grow(len(ansi.HideCursor) + len(ansi.ShowCursor)) + + // If synchronized updates are not enabled, we need to ensure that + // the cursor is hidden before writing any output to prevent + // unwanted cursor visual artifacts. + buf.WriteString(ansi.HideCursor) + } + + buf.Write(s.buf.Bytes()) + + if s.syncUpdates { + buf.WriteString(ansi.ResetModeSynchronizedOutput) + } else if s.cursor != nil && !s.cursor.Hidden { + buf.WriteString(ansi.ShowCursor) + } + } + + _, err := s.w.Write(buf.Bytes()) + if err != nil { + return err + } + s.buf.Reset() + return nil +} + +// EnterAltScreen switches the terminal to the alternate screen buffer, allowing +// applications to use a separate screen for their output without affecting the +// main screen. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) EnterAltScreen() error { + var sb strings.Builder + sb.WriteString(ansi.SetModeAltScreenSaveCursor) + if s.cursor == nil || s.cursor.Hidden { + sb.WriteString(ansi.HideCursor) + } else if s.cursor != nil && !s.cursor.Hidden { + sb.WriteString(ansi.ShowCursor) + } + if s.keyboardEnhancements != nil { + _ = EncodeKeyboardEnhancements(&sb, s.keyboardEnhancements) + } + _, err := s.buf.WriteString(sb.String()) + if err != nil { + return err + } + + if !s.altScreen { + s.rend.SaveCursor() + s.rend.Erase() + s.rend.SetFullscreen(true) + s.rend.SetRelativeCursor(false) + s.altScreen = true + } + + return nil +} + +// ExitAltScreen switches the terminal back to the main screen buffer, restoring +// the previous screen state. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) ExitAltScreen() error { + var sb strings.Builder + sb.WriteString(ansi.ResetModeAltScreenSaveCursor) + if s.cursor == nil || s.cursor.Hidden { + sb.WriteString(ansi.HideCursor) + } else if s.cursor != nil && !s.cursor.Hidden { + sb.WriteString(ansi.ShowCursor) + } + if s.keyboardEnhancements != nil { + _ = EncodeKeyboardEnhancements(&sb, s.keyboardEnhancements) + } + _, err := s.buf.WriteString(sb.String()) + if err != nil { + return err + } + + if s.altScreen { + s.rend.RestoreCursor() + s.rend.Erase() + s.rend.SetFullscreen(false) + s.rend.SetRelativeCursor(true) + s.altScreen = false + } + + return nil +} + +// AltScreen returns whether the terminal is currently in the alternate screen +// buffer. +func (s *TerminalScreen) AltScreen() bool { + return s.altScreen +} + +// HideCursor hides the terminal cursor. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) HideCursor() error { + _, err := s.buf.WriteString(ansi.HideCursor) + if err != nil { + return err + } + + if s.cursor != nil { + s.cursor.Hidden = true + } + + return nil +} + +// ShowCursor shows the terminal cursor. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) ShowCursor() error { + _, err := s.buf.WriteString(ansi.ShowCursor) + if err != nil { + return err + } + + if s.cursor != nil { + s.cursor.Hidden = false + } else { + s.cursor = NewCursor(-1, -1) + } + + return nil +} + +// CursorVisible returns whether the terminal cursor is currently visible. +func (s *TerminalScreen) CursorVisible() bool { + return s.cursor != nil && !s.cursor.Hidden +} + +// SetCursorPosition sets the position of the terminal cursor to the specified +// coordinates. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) SetCursorPosition(x, y int) error { + if s.cursor == nil { + s.cursor = NewCursor(x, y) + s.cursor.Hidden = true + } else { + s.cursor.X = x + s.cursor.Y = y + } + return nil +} + +// CursorPosition returns the last set cursor position of the terminal. If the +// cursor position is not set, it returns (-1, -1). +// +// This can be affected by [TerminalScreen.Render] and +// [TerminalScreen.SetCursorPosition] calls. +func (s *TerminalScreen) CursorPosition() (x, y int) { + if s.cursor != nil { + return s.cursor.X, s.cursor.Y + } + return -1, -1 +} + +// SetCursorStyle sets the style of the terminal cursor. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) SetCursorStyle(shape CursorShape, blink bool) error { + if err := EncodeCursorStyle(s.buf, shape, blink); err != nil { + return err + } + + if s.cursor == nil { + s.cursor = NewCursor(-1, -1) + } + s.cursor.Shape = shape + s.cursor.Blink = blink + + return nil +} + +// CursorStyle returns the current style of the terminal cursor. +func (s *TerminalScreen) CursorStyle() (shape CursorShape, blink bool) { + if s.cursor != nil { + return s.cursor.Shape, s.cursor.Blink + } + return CursorBlock, true +} + +// SetCursorColor sets the color of the terminal cursor. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) SetCursorColor(c color.Color) error { + if err := EncodeCursorColor(s.buf, c); err != nil { + return err + } + + if s.cursor == nil { + s.cursor = NewCursor(-1, -1) + } + s.cursor.Color = c + + return nil +} + +// CursorColor returns the current color of the terminal cursor. +// +// A nil color indicates that the cursor color is the default terminal cursor +// color. +func (s *TerminalScreen) CursorColor() color.Color { + if s.cursor != nil { + return s.cursor.Color + } + return nil +} + +// SetBackgroundColor sets the background color of the terminal. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) SetBackgroundColor(c color.Color) error { + if err := EncodeBackgroundColor(s.buf, c); err != nil { + return err + } + + s.backgroundColor = c + + return nil +} + +// BackgroundColor returns the current background color of the terminal. +// +// A nil color indicates that the background color is the default terminal +// background color. +func (s *TerminalScreen) BackgroundColor() color.Color { + return s.backgroundColor +} + +// SetForegroundColor sets the foreground color of the terminal. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) SetForegroundColor(c color.Color) error { + if err := EncodeForegroundColor(s.buf, c); err != nil { + return err + } + + s.foregroundColor = c + + return nil +} + +// ForegroundColor returns the current foreground color of the terminal. +// +// A nil color indicates that the foreground color is the default terminal +// foreground color. +func (s *TerminalScreen) ForegroundColor() color.Color { + return s.foregroundColor +} + +// EnableBracketedPaste enables bracketed paste mode, allowing the terminal to +// distinguish between pasted content and user input. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) EnableBracketedPaste() error { + _, err := s.buf.WriteString(ansi.SetModeBracketedPaste) + if err != nil { + return err + } + + s.bracketedPaste = true + + return nil +} + +// DisableBracketedPaste disables bracketed paste mode. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) DisableBracketedPaste() error { + _, err := s.buf.WriteString(ansi.ResetModeBracketedPaste) + if err != nil { + return err + } + + s.bracketedPaste = false + + return nil +} + +// BracketedPaste returns whether bracketed paste mode is currently enabled. +func (s *TerminalScreen) BracketedPaste() bool { + return s.bracketedPaste +} + +// SetSynchronizedUpdates sets whether to use synchronized updates (mode 2026), +// which allows applications to batch updates to the terminal screen and flush +// them all at once for improved performance. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) SetSynchronizedUpdates(enabled bool) error { + s.syncUpdates = enabled + return nil +} + +// SynchronizedUpdates returns whether synchronized updates (mode 2026) are +// currently enabled. +func (s *TerminalScreen) SynchronizedUpdates() bool { + return s.syncUpdates +} + +// SetMouseMode sets the mouse mode for the terminal, allowing applications to +// receive mouse events. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) SetMouseMode(mode MouseMode) error { + if err := EncodeMouseMode(s.buf, mode); err != nil { + return err + } + + s.mouseMode = mode + + return nil +} + +// MouseMode returns the current mouse mode of the terminal. +func (s *TerminalScreen) MouseMode() MouseMode { + return s.mouseMode +} + +// SetWindowTitle sets the title of the terminal window. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) SetWindowTitle(title string) error { + if err := EncodeWindowTitle(s.buf, title); err != nil { + return err + } + + s.windowTitle = title + + return nil +} + +// WindowTitle returns the current title of the terminal window. +func (s *TerminalScreen) WindowTitle() string { + return s.windowTitle +} + +// SetKeyboardEnhancements sets the keyboard enhancements for the terminal, +// allowing applications to receive enhanced keyboard input. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) SetKeyboardEnhancements(enh *KeyboardEnhancements) error { + if err := EncodeKeyboardEnhancements(s.buf, enh); err != nil { + return err + } + + s.keyboardEnhancements = enh + + return nil +} + +// KeyboardEnhancements returns the current keyboard enhancements of the terminal. +// +// A nil value indicates that no keyboard enhancements are currently enabled. +func (s *TerminalScreen) KeyboardEnhancements() *KeyboardEnhancements { + return s.keyboardEnhancements +} + +// SetProgressBar sets the progress bar for the terminal, allowing applications +// to display progress information. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) SetProgressBar(pb *ProgressBar) error { + if err := EncodeProgressBar(s.buf, pb); err != nil { + return err + } + + s.progressBar = pb + + return nil +} + +// ProgressBar returns the current progress bar of the terminal. +// +// A nil value indicates that no progress bar is currently set. +func (s *TerminalScreen) ProgressBar() *ProgressBar { + return s.progressBar +} + +// Reset resets the terminal screen to its default state, clearing the screen, +// switching back to the main screen buffer if necessary, and resetting all +// terminal settings to their defaults. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) Reset() error { + var sb strings.Builder + + hasKeyboardEnhancements := s.keyboardEnhancements != nil + + if s.altScreen { + if hasKeyboardEnhancements { + sb.WriteString(ansi.KittyKeyboard(0, 1)) + } + sb.WriteString(ansi.ResetModeAltScreenSaveCursor) + } + if hasKeyboardEnhancements { + sb.WriteString(ansi.KittyKeyboard(0, 1)) + } + if s.mouseMode != MouseModeNone { + _ = EncodeMouseMode(&sb, MouseModeNone) + } + + if s.cursor == nil || !s.cursor.Hidden { + sb.WriteString(ansi.ShowCursor) + } + if s.cursor != nil { + if s.cursor.Shape != CursorBlock || !s.cursor.Blink { + sb.WriteString(ansi.SetCursorStyle(0)) + } + if s.cursor.Color != nil { + sb.WriteString(ansi.ResetCursorColor) + } + } + if s.backgroundColor != nil { + sb.WriteString(ansi.ResetBackgroundColor) + } + if s.foregroundColor != nil { + sb.WriteString(ansi.ResetForegroundColor) + } + if s.bracketedPaste { + sb.WriteString(ansi.ResetModeBracketedPaste) + } + if s.windowTitle != "" { + sb.WriteString(ansi.SetWindowTitle("")) + } + if s.progressBar != nil && s.progressBar.State != ProgressBarNone { + sb.WriteString(ansi.ResetProgressBar) + } + + _, err := s.buf.WriteString(sb.String()) + if err != nil { + return err + } + + // Go to the bottom of the screen. + // We need to go to the bottom of the screen regardless of whether + // we're in alt screen mode or not to avoid leaving the cursor in the + // middle in terminals that don't support alt screen mode. + // + // This comes after resetting the screen state to ensure that moving the + // cursor is the last thing we do, preventing any unwanted cursor movements + // after resetting the screen. + // + // Note that both [TerminalScreen.rend] writes to [TerminalScreen.buf]. + s.rend.MoveTo(0, s.win.Height()-1) + + return nil +} + +// Restore restores the terminal screen to its previous state, applying any +// previous settings and state that were reset by the [TerminalScreen.Reset] method. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) Restore() error { + var sb strings.Builder + + if s.altScreen { + sb.WriteString(ansi.SetModeAltScreenSaveCursor) + } + if s.cursor != nil && !s.cursor.Hidden { + sb.WriteString(ansi.ShowCursor) + } else { + // Hide the cursor by default. + sb.WriteString(ansi.HideCursor) + } + if s.keyboardEnhancements != nil { + if err := EncodeKeyboardEnhancements(&sb, s.keyboardEnhancements); err != nil { + return err + } + } + if s.mouseMode != MouseModeNone { + if err := EncodeMouseMode(&sb, s.mouseMode); err != nil { + return err + } + } + if s.cursor != nil { + if s.cursor.Shape != CursorBlock || !s.cursor.Blink { + _ = EncodeCursorStyle(&sb, s.cursor.Shape, s.cursor.Blink) + } + if s.cursor.Color != nil { + if err := EncodeCursorColor(&sb, s.cursor.Color); err != nil { + return err + } + } + } + if s.backgroundColor != nil { + if err := EncodeBackgroundColor(&sb, s.backgroundColor); err != nil { + return err + } + } + if s.foregroundColor != nil { + if err := EncodeForegroundColor(&sb, s.foregroundColor); err != nil { + return err + } + } + if s.bracketedPaste { + sb.WriteString(ansi.SetModeBracketedPaste) + } + if s.windowTitle != "" { + if err := EncodeWindowTitle(&sb, s.windowTitle); err != nil { + return err + } + } + if s.progressBar != nil && s.progressBar.State != ProgressBarNone { + if err := EncodeProgressBar(&sb, s.progressBar); err != nil { + return err + } + } + + _, err := s.buf.WriteString(sb.String()) + if err != nil { + return err + } + + // This needs to be called after restoring the screen state and writing to + // the buffer. + // + // [TerminalScreen.Render] will write to [TerminalScreen.buf], so we need + // to call it after writing the restore commands to the buffer to ensure + // that the restore commands are included in the render output. This + // ensures that the screen is properly restored before rendering any + // changes. + if err := s.Render(); err != nil { + return err + } + + // Cursor position will be restored by the caller after calling + // [TerminalScreen.Flush]. + + return nil +} + +// Write writes data to the underlying buffer queuing it for output. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) Write(p []byte) (n int, err error) { + return s.buf.Write(p) +} + +// WriteString writes a string to the underlying buffer queuing it for output. +// +// The changes can be committed to the underlying writer by calling the +// [TerminalScreen.Flush] method. +func (s *TerminalScreen) WriteString(str string) (n int, err error) { + return s.buf.WriteString(str) +} + +// InsertAbove inserts content above the screen pushing the current content +// down. +// +// This is useful for inserting content above the current screen content +// without affecting the current cursor position or screen state. +// +// Note that this won't have any visible effect if the screen is in alt screen +// mode, as the content will be inserted above the alt screen buffer, which is +// not visible. However, if the screen is in inline mode, the content will be +// inserted above and will not be managed by the renderer. +// +// Unlike other methods that modify the screen state, this method writes +// directly to the underlying writer, so there is no need to call +// [TerminalScreen.Flush] after calling this method. +func (s *TerminalScreen) InsertAbove(content string) error { + if len(content) == 0 { + return nil + } + + var sb strings.Builder + w, h := s.win.Width(), s.win.Height() + _, y := s.rend.Position() + + // We need to scroll the screen up by the number of lines in the queue. + sb.WriteByte('\r') + down := h - y - 1 + if down > 0 { + sb.WriteString(ansi.CursorDown(down)) + } + + lines := strings.Split(content, "\n") + offset := len(lines) + for _, line := range lines { + lineWidth := s.win.WidthMethod().StringWidth(line) + if w > 0 && lineWidth > w { + offset += (lineWidth / w) + } + } + + // Scroll the screen up by the offset to make room for the new lines. + sb.WriteString(strings.Repeat("\n", offset)) + + // XXX: Now go to the top of the screen, insert new lines, and write + // the queued strings. It is important to use [Screen.moveCursor] + // instead of [Screen.move] because we don't want to perform any checks + // on the cursor position. + up := offset + h - 1 + sb.WriteString(ansi.CursorUp(up)) + sb.WriteString(ansi.InsertLine(offset)) + for _, line := range lines { + sb.WriteString(line) + sb.WriteString(ansi.EraseLineRight) + sb.WriteString("\r\n") + } + + s.rend.SetPosition(0, 0) + + _, err := io.WriteString(s.w, sb.String()) + return err +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_tabdly.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_tabdly.go new file mode 100644 index 000000000..52c47a225 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_tabdly.go @@ -0,0 +1,10 @@ +//go:build darwin || linux || freebsd || solaris || aix +// +build darwin linux freebsd solaris aix + +package uv + +import "golang.org/x/sys/unix" + +func supportsHardTabs(oflag uint64) bool { + return oflag&unix.TABDLY == unix.TAB0 +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_tabdly_other.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_tabdly_other.go new file mode 100644 index 000000000..30b1914c6 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_tabdly_other.go @@ -0,0 +1,8 @@ +//go:build !darwin && !linux && !freebsd && !solaris && !aix && !windows +// +build !darwin,!linux,!freebsd,!solaris,!aix,!windows + +package uv + +func supportsHardTabs(uint64) bool { + return false +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_unix.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_unix.go new file mode 100644 index 000000000..9caf0a74c --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_unix.go @@ -0,0 +1,78 @@ +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || aix +// +build darwin dragonfly freebsd linux netbsd openbsd solaris aix + +package uv + +import ( + "os" + "os/signal" + "syscall" + + "github.com/charmbracelet/x/term" + "github.com/charmbracelet/x/termios" +) + +func makeRaw(inTty, outTty term.File) (inTtyState, outTtyState *term.State, err error) { + if inTty == nil && outTty == nil { + return nil, nil, ErrNotTerminal + } + + // Check if we have a terminal. + for _, f := range []term.File{inTty, outTty} { + if f == nil { + continue + } + inTtyState, err = term.MakeRaw(f.Fd()) + if err == nil { + break + } + } + + if err != nil { + return nil, nil, err //nolint:wrapcheck + } + + return inTtyState, outTtyState, nil +} + +func getWinsize(inTty, outTty term.File) (ws Winsize, err error) { + // Try both inTty and outTty to get the size. + err = ErrNotTerminal + for _, f := range []term.File{inTty, outTty} { + if f == nil { + continue + } + size, err := termios.GetWinsize(int(f.Fd())) + if err == nil { + return Winsize(*size), nil + } + } + return +} + +func getSize(inTty, outTty term.File) (w, h int, err error) { + ws, err := getWinsize(inTty, outTty) + return int(ws.Col), int(ws.Row), err +} + +func optimizeMovements(state *term.State) (useTabs, useBspace bool) { + return supportsHardTabs(uint64(state.Oflag)), supportsBackspace(uint64(state.Lflag)) //nolint:unconvert,nolintlint +} + +func startWinch(inTty, outTty term.File) (c chan os.Signal, err error) { + for _, f := range []term.File{inTty, outTty} { + if f == nil { + continue + } + if term.IsTerminal(f.Fd()) { + c = make(chan os.Signal) + signal.Notify(c, syscall.SIGWINCH) + return c, nil + } + } + return nil, ErrNotTerminal +} + +func stopWinch(c chan os.Signal) { + signal.Stop(c) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/terminal_windows.go b/vendor/github.com/charmbracelet/ultraviolet/terminal_windows.go new file mode 100644 index 000000000..73a39d7bd --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/terminal_windows.go @@ -0,0 +1,85 @@ +//go:build windows +// +build windows + +package uv + +import ( + "fmt" + "os" + + "github.com/charmbracelet/x/term" + "golang.org/x/sys/windows" +) + +func makeRaw(inTty, outTty term.File) (inTtyState, outTtyState *term.State, err error) { + if inTty == nil || outTty == nil || !term.IsTerminal(inTty.Fd()) || !term.IsTerminal(outTty.Fd()) { + return nil, nil, ErrNotTerminal + } + + // Save stdin state and enable VT inpu + // We also need to enable VT input here. + inTtyState, err = term.MakeRaw(inTty.Fd()) + if err != nil { + return nil, nil, fmt.Errorf("error making terminal raw: %w", err) + } + + // Enable VT input + var imode uint32 + if err := windows.GetConsoleMode(windows.Handle(inTty.Fd()), &imode); err != nil { + return nil, nil, fmt.Errorf("error getting console mode: %w", err) + } + + if err := windows.SetConsoleMode(windows.Handle(inTty.Fd()), imode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err != nil { + return nil, nil, fmt.Errorf("error setting console mode: %w", err) + } + + // Save output screen buffer state and enable VT processing. + outTtyState, err = term.GetState(outTty.Fd()) + if err != nil { + return nil, nil, fmt.Errorf("error getting terminal state: %w", err) + } + + var omode uint32 + if err := windows.GetConsoleMode(windows.Handle(outTty.Fd()), &omode); err != nil { + return nil, nil, fmt.Errorf("error getting console mode: %w", err) + } + + if err := windows.SetConsoleMode(windows.Handle(outTty.Fd()), + omode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING| + windows.DISABLE_NEWLINE_AUTO_RETURN); err != nil { + return nil, nil, fmt.Errorf("error setting console mode: %w", err) + } + + return inTtyState, outTtyState, nil +} + +func getSize(_, outTty term.File) (w, h int, err error) { + if outTty != nil { + return term.GetSize(outTty.Fd()) //nolint:wrapcheck + } + return 0, 0, ErrNotTerminal +} + +func getWinsize(inTty, outTty term.File) (ws Winsize, err error) { + w, h, err := getSize(inTty, outTty) + return Winsize{Col: uint16(w), Row: uint16(h)}, err +} + +func optimizeMovements(*term.State) (useTabs, useBspace bool) { + return supportsBackspace(0), supportsHardTabs(0) +} + +func supportsBackspace(uint64) bool { + return true +} + +func supportsHardTabs(uint64) bool { + return true +} + +func startWinch(_, _ term.File) (chan os.Signal, error) { + return nil, ErrPlatformNotSupported +} + +func stopWinch(chan os.Signal) { +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/tty.go b/vendor/github.com/charmbracelet/ultraviolet/tty.go new file mode 100644 index 000000000..7e7bd9945 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/tty.go @@ -0,0 +1,42 @@ +package uv + +import ( + "context" + "os" +) + +// OpenTTY opens the terminal's input and output file descriptors. +// It returns the input and output files, or an error if the terminal is not +// available. +// +// This is useful for applications that need to interact with the terminal +// directly while piping or redirecting input/output. +func OpenTTY() (inTty, outTty *os.File, err error) { + return openTTY() +} + +// Suspend suspends the current process group. +func Suspend() error { + return suspend() +} + +// NotifyWinch sets up a channel to receive window size change signals and any +// other signals needed. This is a drop-in replacement for os/signal.Notify to +// ensure that SIGWINCH is included. +// +// On Windows, this will be a no-op for SIGWINCH, but other signals may still +// be handled. +func NotifyWinch(c chan os.Signal, sigs ...os.Signal) { + notifyWinch(c, sigs...) +} + +// NotifyWinchContext sets up a channel to receive window size change signals +// and any other signals needed, with context cancellation support. This is a +// drop-in replacement for os/signal.NotifyContext to ensure that SIGWINCH is +// included. +// +// On Windows, this will be a no-op for SIGWINCH, but other signals may still +// be handled. +func NotifyWinchContext(ctx context.Context, sigs ...os.Signal) (context.Context, context.CancelFunc) { + return notifyWinchContext(ctx, sigs...) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/tty_other.go b/vendor/github.com/charmbracelet/ultraviolet/tty_other.go new file mode 100644 index 000000000..c4c986ce9 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/tty_other.go @@ -0,0 +1,26 @@ +//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !aix && !windows +// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!aix,!windows + +package uv + +import ( + "context" + "os" + "os/signal" +) + +func openTTY() (*os.File, *os.File, error) { + return nil, nil, ErrPlatformNotSupported +} + +func suspend() error { + return ErrPlatformNotSupported +} + +func notifyWinch(c chan os.Signal, sigs ...os.Signal) { + signal.Notify(c, sigs...) +} + +func notifyWinchContext(ctx context.Context, sigs ...os.Signal) (context.Context, context.CancelFunc) { + return signal.NotifyContext(ctx, sigs...) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/tty_unix.go b/vendor/github.com/charmbracelet/ultraviolet/tty_unix.go new file mode 100644 index 000000000..328552cb1 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/tty_unix.go @@ -0,0 +1,39 @@ +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || aix +// +build darwin dragonfly freebsd linux netbsd openbsd solaris aix + +package uv + +import ( + "context" + "os" + "os/signal" + "syscall" + + "golang.org/x/sys/unix" +) + +func openTTY() (inTty, outTty *os.File, err error) { + f, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) + if err != nil { + return nil, nil, err //nolint:wrapcheck + } + return f, f, nil +} + +func suspend() (err error) { + // Send SIGTSTP to the entire process group. + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGCONT) + err = syscall.Kill(0, syscall.SIGTSTP) + // blocks until a CONT happens... + <-c + return +} + +func notifyWinch(c chan os.Signal, sigs ...os.Signal) { + signal.Notify(c, append(sigs, unix.SIGWINCH)...) +} + +func notifyWinchContext(ctx context.Context, sigs ...os.Signal) (context.Context, context.CancelFunc) { + return signal.NotifyContext(ctx, append(sigs, unix.SIGWINCH)...) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/tty_windows.go b/vendor/github.com/charmbracelet/ultraviolet/tty_windows.go new file mode 100644 index 000000000..4ad0ecb80 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/tty_windows.go @@ -0,0 +1,39 @@ +//go:build windows +// +build windows + +package uv + +import ( + "context" + "os" + "os/signal" +) + +func openTTY() (inTty, outTty *os.File, err error) { + // On Windows, when the input/output is redirected or piped, we need to + // open the console explicitly. + // See https://learn.microsoft.com/en-us/windows/console/getstdhandle#remarks + inTty, err = os.OpenFile("CONIN$", os.O_RDWR, 0o644) //nolint:gosec + if err != nil { + return nil, nil, err //nolint:wrapcheck + } + outTty, err = os.OpenFile("CONOUT$", os.O_RDWR, 0o644) //nolint:gosec + if err != nil { + return nil, nil, err //nolint:wrapcheck + } + return inTty, outTty, nil +} + +func suspend() (err error) { + // On Windows, suspending the process group is not supported in the same + // way as Unix-like systems. + return nil +} + +func notifyWinch(c chan os.Signal, sigs ...os.Signal) { + signal.Notify(c, sigs...) +} + +func notifyWinchContext(ctx context.Context, sigs ...os.Signal) (context.Context, context.CancelFunc) { + return signal.NotifyContext(ctx, sigs...) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/utils.go b/vendor/github.com/charmbracelet/ultraviolet/utils.go new file mode 100644 index 000000000..6616ebc97 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/utils.go @@ -0,0 +1,15 @@ +package uv + +func abs(x int) int { + if x < 0 { + return -x + } + return x +} + +func clamp(v, low, high int) int { + if high < low { + low, high = high, low + } + return min(high, max(low, v)) +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/uv.go b/vendor/github.com/charmbracelet/ultraviolet/uv.go new file mode 100644 index 000000000..546d6f332 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/uv.go @@ -0,0 +1,359 @@ +package uv + +import ( + "fmt" + "image/color" + "io" + "strings" + + "github.com/charmbracelet/x/ansi" + "github.com/lucasb-eyer/go-colorful" +) + +var ( + // ErrNotTerminal is an error that indicates that the file is not a terminal. + ErrNotTerminal = fmt.Errorf("not a terminal") + // ErrPlatformNotSupported is an error that indicates that the platform is not supported. + ErrPlatformNotSupported = fmt.Errorf("platform not supported") +) + +// Drawable represents a drawable component on a [Screen]. +type Drawable interface { + // Draw renders the component on the screen for the given area. + Draw(scr Screen, area Rectangle) +} + +// DrawableFunc is a function that implements the [Drawable] interface. +type DrawableFunc func(scr Screen, rect Rectangle) + +var _ Drawable = (DrawableFunc)(nil) + +// Draw implements the [Drawable] interface. +func (f DrawableFunc) Draw(scr Screen, rect Rectangle) { + f(scr, rect) +} + +// WidthMethod determines how many columns a grapheme occupies on the screen. +type WidthMethod interface { + StringWidth(s string) int +} + +// Screen represents a screen that can be drawn to. +type Screen interface { + // Bounds returns the bounds of the screen. This is the rectangle that + // includes the start and end points of the screen. + Bounds() Rectangle + + // CellAt returns the cell at the given position. If the position is out of + // bounds, it returns nil. Otherwise, it always returns a cell, even if it + // is empty (i.e., a cell with a space character and a width of 1). + CellAt(x, y int) *Cell + + // SetCell sets the cell at the given position. A nil cell is treated as an + // empty cell with a space character and a width of 1. + SetCell(x, y int, c *Cell) + + // WidthMethod returns the width method used by the screen. + WidthMethod() WidthMethod +} + +// Cursor represents a cursor on the terminal screen. +type Cursor struct { + // Position is a [Position] that determines the cursor's position on the + // screen relative to the top left corner of the frame. + Position + + // Color is a [color.Color] that determines the cursor's color. + Color color.Color + + // Shape is a [CursorShape] that determines the cursor's shape. + Shape CursorShape + + // Blink is a boolean that determines whether the cursor should blink. + Blink bool + + // Hidden is a boolean that determines whether the cursor is hidden. You + // can use this if you want to hide the cursor but still want to change its + // position. + Hidden bool +} + +// NewCursor returns a new cursor with the default settings and the given +// position. +func NewCursor(x, y int) *Cursor { + return &Cursor{ + Position: Position{X: x, Y: y}, + Color: nil, + Shape: CursorBlock, + Blink: true, + } +} + +// ProgressBarState represents the state of the progress bar. +type ProgressBarState int + +// Progress bar states. +const ( + ProgressBarNone ProgressBarState = iota + ProgressBarDefault + ProgressBarError + ProgressBarIndeterminate + ProgressBarWarning +) + +// String return a human-readable value for the given [ProgressBarState]. +func (s ProgressBarState) String() string { + return [...]string{ + "None", + "Default", + "Error", + "Indeterminate", + "Warning", + }[s] +} + +// ProgressBar represents the terminal progress bar. +// +// Support depends on the terminal. +// +// See https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences +type ProgressBar struct { + // State is the current state of the progress bar. It can be one of + // [ProgressBarNone], [ProgressBarDefault], [ProgressBarError], + // [ProgressBarIndeterminate], and [ProgressBarWarning]. + State ProgressBarState + // Value is the current value of the progress bar. It should be between + // 0 and 100. + Value int +} + +// NewProgressBar returns a new progress bar with the given state and value. +// The value is ignored if the state is [ProgressBarNone] or +// [ProgressBarIndeterminate]. +func NewProgressBar(state ProgressBarState, value int) *ProgressBar { + return &ProgressBar{ + State: state, + Value: min(max(value, 0), 100), + } +} + +// KeyboardEnhancements defines different keyboard enhancement features that +// can be requested from the terminal. +type KeyboardEnhancements struct { + // DisambiguateEscapeCodes requests the terminal to report ambiguous keys + // such as Ctrl+i and Tab, and Ctrl+m and Enter, and others as distinct key + // code sequences. + // If supported, your program will receive distinct [KeyPressEvent]s for + // these keys. + DisambiguateEscapeCodes bool + + // ReportEventTypes requests the terminal to report key repeat and release + // events. + // If supported, your program will receive [KeyReleaseEvent]s and + // [KeyPressEvent] with the [Key.IsRepeat] field set indicating that this + // is a it's part of a key repeat sequence. + ReportEventTypes bool +} + +// NewKeyboardEnhancements returns a new [KeyboardEnhancements] with the given +// options as flags. +// +// A zero, or negative, flags value is treated as no enhancements. +// +// See [ansi.KittyKeyboard] for more details on the supported keyboard enhancements. +func NewKeyboardEnhancements(flags int) *KeyboardEnhancements { + if flags <= 0 { + return &KeyboardEnhancements{} + } + + return &KeyboardEnhancements{ + DisambiguateEscapeCodes: flags&ansi.KittyDisambiguateEscapeCodes != 0, + ReportEventTypes: flags&ansi.KittyReportEventTypes != 0, + } +} + +// Flags returns the keyboard enhancements as bits that can be used to set the +// appropriate terminal modes. +func (ke KeyboardEnhancements) Flags() int { + bits := 0 + if ke.DisambiguateEscapeCodes { + bits |= ansi.KittyDisambiguateEscapeCodes + } + if ke.ReportEventTypes { + bits |= ansi.KittyReportEventTypes + } + + return bits +} + +// EncodeBackgroundColor encodes the background color to the given writer. Use +// nil to reset the background color to the default. +func EncodeBackgroundColor(w io.Writer, c color.Color) error { + var seq string + if c == nil { + seq = ansi.ResetBackgroundColor + } else if col, ok := colorful.MakeColor(c); ok { + seq = ansi.SetBackgroundColor(col.Hex()) + } else { + return fmt.Errorf("invalid color: %v", c) + } + + _, err := io.WriteString(w, seq) + if err != nil { + return fmt.Errorf("failed to set background color: %w", err) + } + + return nil +} + +// EncodeForegroundColor encodes the foreground color to the given writer. Use +// nil to reset the foreground color to the default. +func EncodeForegroundColor(w io.Writer, c color.Color) error { + var seq string + if c == nil { + seq = ansi.ResetForegroundColor + } else if col, ok := colorful.MakeColor(c); ok { + seq = ansi.SetForegroundColor(col.Hex()) + } else { + return fmt.Errorf("invalid color: %v", c) + } + + _, err := io.WriteString(w, seq) + if err != nil { + return fmt.Errorf("failed to set foreground color: %w", err) + } + + return nil +} + +// EncodeCursorColor encodes the cursor color to the given writer. Use nil to +// reset the cursor color to the default. +func EncodeCursorColor(w io.Writer, c color.Color) error { + var seq string + if c == nil { + seq = ansi.ResetCursorColor + } else if col, ok := colorful.MakeColor(c); ok { + seq = ansi.SetCursorColor(col.Hex()) + } else { + return fmt.Errorf("invalid color: %v", c) + } + + _, err := io.WriteString(w, seq) + if err != nil { + return fmt.Errorf("failed to set cursor color: %w", err) + } + + return nil +} + +// EncodeCursorStyle encodes the cursor style to the given writer. +func EncodeCursorStyle(w io.Writer, shape CursorShape, blink bool) error { + seq := ansi.SetCursorStyle(shape.Encode(blink)) + _, err := io.WriteString(w, seq) + if err != nil { + return fmt.Errorf("failed to set cursor style: %w", err) + } + + return nil +} + +// EncodeBracketedPaste encodes the bracketed paste mode to the given writer. +func EncodeBracketedPaste(w io.Writer, enable bool) error { + var seq string + if enable { + seq = ansi.SetModeBracketedPaste + } else { + seq = ansi.ResetModeBracketedPaste + } + + _, err := io.WriteString(w, seq) + if err != nil { + return fmt.Errorf("failed to set bracketed paste mode: %w", err) + } + + return nil +} + +// EncodeMouseMode encodes the mouse mode to the given writer. +func EncodeMouseMode(w io.Writer, mode MouseMode) error { + var sb strings.Builder + switch mode { + case MouseModeNone: + sb.WriteString(ansi.ResetModeMouseNormal) + sb.WriteString(ansi.ResetModeMouseButtonEvent) + sb.WriteString(ansi.ResetModeMouseAnyEvent) + sb.WriteString(ansi.ResetModeMouseExtSgr) + case MouseModeClick: + sb.WriteString(ansi.SetModeMouseNormal) + sb.WriteString(ansi.SetModeMouseExtSgr) + case MouseModeDrag: + sb.WriteString(ansi.SetModeMouseButtonEvent) + sb.WriteString(ansi.SetModeMouseExtSgr) + case MouseModeMotion: + sb.WriteString(ansi.SetModeMouseAnyEvent) + sb.WriteString(ansi.SetModeMouseExtSgr) + default: + return fmt.Errorf("invalid mouse mode: %d", mode) + } + + _, err := io.WriteString(w, sb.String()) + if err != nil { + return fmt.Errorf("failed to set mouse mode: %w", err) + } + + return nil +} + +// EncodeProgressBar encodes the progress bar to the given writer. +func EncodeProgressBar(w io.Writer, pb *ProgressBar) error { + if pb == nil { + pb = &ProgressBar{State: ProgressBarNone} + } + + var seq string + percent := clamp(pb.Value, 0, 100) + switch pb.State { + case ProgressBarNone: + seq = ansi.ResetProgressBar + case ProgressBarDefault: + seq = ansi.SetProgressBar(percent) + case ProgressBarError: + seq = ansi.SetErrorProgressBar(percent) + case ProgressBarIndeterminate: + seq = ansi.SetIndeterminateProgressBar + case ProgressBarWarning: + seq = ansi.SetWarningProgressBar(percent) + default: + return fmt.Errorf("invalid progress bar state: %d", pb.State) + } + + _, err := io.WriteString(w, seq) + if err != nil { + return fmt.Errorf("failed to set progress bar: %w", err) + } + + return nil +} + +// EncodeKeyboardEnhancements encodes the keyboard enhancements to the given +// writer. +func EncodeKeyboardEnhancements(w io.Writer, ke *KeyboardEnhancements) error { + var flags int + if ke != nil { + flags = ke.Flags() + } + _, err := io.WriteString(w, ansi.KittyKeyboard(flags, 1)) + if err != nil { + return fmt.Errorf("failed to set keyboard enhancements: %w", err) + } + return nil +} + +// EncodeWindowTitle encodes the window title to the given writer. +func EncodeWindowTitle(w io.Writer, title string) error { + _, err := io.WriteString(w, ansi.SetWindowTitle(title)) + if err != nil { + return fmt.Errorf("failed to set window title: %w", err) + } + return nil +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/winch.go b/vendor/github.com/charmbracelet/ultraviolet/winch.go new file mode 100644 index 000000000..77533f1cd --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/winch.go @@ -0,0 +1,62 @@ +package uv + +import ( + "os" + "sync" + + "github.com/charmbracelet/x/term" +) + +// SizeNotifier represents a notifier that listens for window size +// changes using the SIGWINCH signal and notifies the given channel. +type SizeNotifier struct { + // Channel that receives terminal size change notifications. + C <-chan os.Signal + + f term.File + sig chan os.Signal + m sync.Mutex +} + +// NewSizeNotifier creates a new [SizeNotifier] that listens for window size +// changes on the given TTY file through SIGWINCH signals. +func NewSizeNotifier(f term.File) *SizeNotifier { + if f == nil { + panic("no file set") + } + sig := make(chan os.Signal) + return &SizeNotifier{ + f: f, + sig: sig, + C: sig, + } +} + +// Start starts listening for window size changes and notifies [SizeNotifier.C] +// about any changes. +func (n *SizeNotifier) Start() error { + return n.start() +} + +// Stop stops the notifier and cleans up resources. +func (n *SizeNotifier) Stop() error { + return n.stop() +} + +// GetWindowSize returns the current size of the terminal window. +func (n *SizeNotifier) GetWindowSize() (cells Size, pixels Size, err error) { + return n.getWindowSize() +} + +// GetSize returns the current cell size of the terminal window. +func (n *SizeNotifier) GetSize() (width, height int, err error) { + n.m.Lock() + defer n.m.Unlock() + + width, height, err = term.GetSize(n.f.Fd()) + if err != nil { + return 0, 0, err //nolint:wrapcheck + } + + return width, height, nil +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/winch_other.go b/vendor/github.com/charmbracelet/ultraviolet/winch_other.go new file mode 100644 index 000000000..802268780 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/winch_other.go @@ -0,0 +1,17 @@ +//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris +// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris + +package uv + +func (n *SizeNotifier) start() error { + return ErrPlatformNotSupported +} + +func (n *SizeNotifier) stop() error { + return ErrPlatformNotSupported +} + +func (n *SizeNotifier) getWindowSize() (cells Size, pixels Size, err error) { + cells.Width, cells.Height, err = n.GetSize() + return cells, pixels, err +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/winch_unix.go b/vendor/github.com/charmbracelet/ultraviolet/winch_unix.go new file mode 100644 index 000000000..efe0ce509 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/winch_unix.go @@ -0,0 +1,50 @@ +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos +// +build darwin dragonfly freebsd linux netbsd openbsd solaris zos + +package uv + +import ( + "os/signal" + "syscall" + + "github.com/charmbracelet/x/term" + "github.com/charmbracelet/x/termios" +) + +func (n *SizeNotifier) start() error { + n.m.Lock() + defer n.m.Unlock() + if n.f == nil || !term.IsTerminal(n.f.Fd()) { + return ErrNotTerminal + } + + signal.Notify(n.sig, syscall.SIGWINCH) + return nil +} + +func (n *SizeNotifier) stop() error { + n.m.Lock() + signal.Stop(n.sig) + n.m.Unlock() + return nil +} + +func (n *SizeNotifier) getWindowSize() (cells Size, pixels Size, err error) { + n.m.Lock() + defer n.m.Unlock() + + winsize, err := termios.GetWinsize(int(n.f.Fd())) + if err != nil { + return Size{}, Size{}, err //nolint:wrapcheck + } + + cells = Size{ + Width: int(winsize.Col), + Height: int(winsize.Row), + } + pixels = Size{ + Width: int(winsize.Xpixel), + Height: int(winsize.Ypixel), + } + return cells, pixels, nil +} diff --git a/vendor/github.com/charmbracelet/ultraviolet/window.go b/vendor/github.com/charmbracelet/ultraviolet/window.go new file mode 100644 index 000000000..21c707a84 --- /dev/null +++ b/vendor/github.com/charmbracelet/ultraviolet/window.go @@ -0,0 +1,134 @@ +package uv + +import ( + "github.com/charmbracelet/x/ansi" +) + +// Window represents a rectangular area on the screen. It can be a root window +// with no parent, or a sub-window with a parent window. A window can have its +// own buffer or share the buffer of its parent window (view). +type Window struct { + *Buffer + + method *WidthMethod + parent *Window + bounds Rectangle +} + +var ( + _ Screen = (*Window)(nil) + _ Drawable = (*Window)(nil) +) + +// HasParent returns whether the window has a parent window. This can be used +// to determine if the window is a root window or a sub-window. +func (w *Window) HasParent() bool { + return w.parent != nil +} + +// Parent returns the parent window of the current window. +// If the window does not have a parent, it returns nil. +func (w *Window) Parent() *Window { + return w.parent +} + +// MoveTo moves the window to the specified x and y coordinates. +func (w *Window) MoveTo(x, y int) { + size := w.bounds.Size() + w.bounds.Min.X = x + w.bounds.Min.Y = y + w.bounds.Max.X = x + size.X + w.bounds.Max.Y = y + size.Y +} + +// MoveBy moves the window by the specified delta x and delta y. +func (w *Window) MoveBy(dx, dy int) { + w.bounds.Min.X += dx + w.bounds.Min.Y += dy + w.bounds.Max.X += dx + w.bounds.Max.Y += dy +} + +// Clone creates an exact copy of the window, including its buffer and values. +// The cloned window will have the same parent and method as the original +// window. +func (w *Window) Clone() *Window { + return w.CloneArea(w.Buffer.Bounds()) +} + +// CloneArea creates an exact copy of the window, including its buffer and +// values, but only within the specified area. The cloned window will have the +// same parent and method as the original window, but its bounds will be +// limited to the specified area. +func (w *Window) CloneArea(area Rectangle) *Window { + clone := new(Window) + clone.Buffer = w.Buffer.CloneArea(area) + clone.parent = w.parent + clone.method = w.method + clone.bounds = area + return clone +} + +// Resize resizes the window to the specified width and height. +func (w *Window) Resize(width, height int) { + // Only resize the buffer if this window owns its buffer. + if w.parent == nil || w.Buffer != w.parent.Buffer { + w.Buffer.Resize(width, height) + } + w.bounds.Max.X = w.bounds.Min.X + width + w.bounds.Max.Y = w.bounds.Min.Y + height +} + +// WidthMethod returns the method used to calculate the width of characters in +// the window. +func (w *Window) WidthMethod() WidthMethod { + return *w.method +} + +// Bounds returns the bounds of the window as a rectangle. +func (w *Window) Bounds() Rectangle { + return w.bounds +} + +// NewWindow creates a new window with its own buffer relative to the parent +// window at the specified position and size. +// +// This will panic if width or height is negative. +func (w *Window) NewWindow(x, y, width, height int) *Window { + return newWindow(w, x, y, width, height, w.method, false) +} + +// NewView creates a new view into the parent window at the specified position +// and size. Unlike [Window.NewWindow], this view shares the same buffer as the +// parent window. +func (w *Window) NewView(x, y, width, height int) *Window { + return newWindow(w, x, y, width, height, w.method, true) +} + +// NewScreen creates a new root [Window] with the given size and width method. +// +// This will panic if width or height is negative. +func NewScreen(width, height int) *Window { + var method WidthMethod = ansi.WcWidth + return newWindow(nil, 0, 0, width, height, &method, false) +} + +// SetWidthMethod sets the width method for the window. +func (w *Window) SetWidthMethod(method WidthMethod) { + w.method = &method +} + +// newWindow creates a new [Window] with the specified parent, position, +// method, and size. +func newWindow(parent *Window, x, y, width, height int, method *WidthMethod, view bool) *Window { + w := new(Window) + if view { + w.Buffer = parent.Buffer + } else { + w.Buffer = NewBuffer(width, height) + } + w.parent = parent + w.method = method + w.bounds = Rect(x, y, width, height) + return w +} diff --git a/vendor/github.com/charmbracelet/x/ansi/kitty/decoder.go b/vendor/github.com/charmbracelet/x/ansi/kitty/decoder.go new file mode 100644 index 000000000..7db15156e --- /dev/null +++ b/vendor/github.com/charmbracelet/x/ansi/kitty/decoder.go @@ -0,0 +1,86 @@ +// Package kitty provides Kitty terminal graphics protocol functionality. +package kitty + +import ( + "compress/zlib" + "fmt" + "image" + "image/color" + "image/png" + "io" +) + +// Decoder is a decoder for the Kitty graphics protocol. It supports decoding +// images in the 24-bit [RGB], 32-bit [RGBA], and [PNG] formats. It can also +// decompress data using zlib. +// The default format is 32-bit [RGBA]. +type Decoder struct { + // Uses zlib decompression. + Decompress bool + + // Can be one of [RGB], [RGBA], or [PNG]. + Format int + + // Width of the image in pixels. This can be omitted if the image is [PNG] + // formatted. + Width int + + // Height of the image in pixels. This can be omitted if the image is [PNG] + // formatted. + Height int +} + +// Decode decodes the image data from r in the specified format. +func (d *Decoder) Decode(r io.Reader) (image.Image, error) { + if d.Decompress { + zr, err := zlib.NewReader(r) + if err != nil { + return nil, fmt.Errorf("failed to create zlib reader: %w", err) + } + + defer zr.Close() //nolint:errcheck + r = zr + } + + if d.Format == 0 { + d.Format = RGBA + } + + switch d.Format { + case RGBA, RGB: + return d.decodeRGBA(r, d.Format == RGBA) + + case PNG: + return png.Decode(r) //nolint:wrapcheck + + default: + return nil, fmt.Errorf("unsupported format: %d", d.Format) + } +} + +// decodeRGBA decodes the image data in 32-bit RGBA or 24-bit RGB formats. +func (d *Decoder) decodeRGBA(r io.Reader, alpha bool) (image.Image, error) { + m := image.NewRGBA(image.Rect(0, 0, d.Width, d.Height)) + + var buf []byte + if alpha { + buf = make([]byte, 4) + } else { + buf = make([]byte, 3) + } + + for y := range d.Height { + for x := range d.Width { + if _, err := io.ReadFull(r, buf[:]); err != nil { + return nil, fmt.Errorf("failed to read pixel data: %w", err) + } + if alpha { + m.SetRGBA(x, y, color.RGBA{buf[0], buf[1], buf[2], buf[3]}) + } else { + m.SetRGBA(x, y, color.RGBA{buf[0], buf[1], buf[2], 0xff}) + } + } + } + + return m, nil +} diff --git a/vendor/github.com/charmbracelet/x/ansi/kitty/encoder.go b/vendor/github.com/charmbracelet/x/ansi/kitty/encoder.go new file mode 100644 index 000000000..7f7efe836 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/ansi/kitty/encoder.go @@ -0,0 +1,64 @@ +package kitty + +import ( + "compress/zlib" + "fmt" + "image" + "image/png" + "io" +) + +// Encoder is an encoder for the Kitty graphics protocol. It supports encoding +// images in the 24-bit [RGB], 32-bit [RGBA], and [PNG] formats, and +// compressing the data using zlib. +// The default format is 32-bit [RGBA]. +type Encoder struct { + // Uses zlib compression. + Compress bool + + // Can be one of [RGBA], [RGB], or [PNG]. + Format int +} + +// Encode encodes the image data in the specified format and writes it to w. +func (e *Encoder) Encode(w io.Writer, m image.Image) error { + if m == nil { + return nil + } + + if e.Compress { + zw := zlib.NewWriter(w) + defer zw.Close() //nolint:errcheck + w = zw + } + + if e.Format == 0 { + e.Format = RGBA + } + + switch e.Format { + case RGBA, RGB: + bounds := m.Bounds() + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + r, g, b, a := m.At(x, y).RGBA() + switch e.Format { + case RGBA: + w.Write([]byte{byte(r >> 8), byte(g >> 8), byte(b >> 8), byte(a >> 8)}) //nolint:errcheck,gosec + case RGB: + w.Write([]byte{byte(r >> 8), byte(g >> 8), byte(b >> 8)}) //nolint:errcheck,gosec + } + } + } + + case PNG: + if err := png.Encode(w, m); err != nil { + return fmt.Errorf("failed to encode PNG: %w", err) + } + + default: + return fmt.Errorf("unsupported format: %d", e.Format) + } + + return nil +} diff --git a/vendor/github.com/charmbracelet/x/ansi/kitty/graphics.go b/vendor/github.com/charmbracelet/x/ansi/kitty/graphics.go new file mode 100644 index 000000000..10862be87 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/ansi/kitty/graphics.go @@ -0,0 +1,414 @@ +package kitty + +import "errors" + +// ErrMissingFile is returned when the file path is missing. +var ErrMissingFile = errors.New("missing file path") + +// MaxChunkSize is the maximum chunk size for the image data. +const MaxChunkSize = 1024 * 4 + +// Placeholder is a special Unicode character that can be used as a placeholder +// for an image. +const Placeholder = '\U0010EEEE' + +// Graphics image format. +const ( + // 32-bit RGBA format. + RGBA = 32 + + // 24-bit RGB format. + RGB = 24 + + // PNG format. + PNG = 100 +) + +// Compression types. +const ( + Zlib = 'z' +) + +// Transmission types. +const ( + // The data transmitted directly in the escape sequence. + Direct = 'd' + + // The data transmitted in a regular file. + File = 'f' + + // A temporary file is used and deleted after transmission. + TempFile = 't' + + // A shared memory object. + // For POSIX see https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html + // For Windows see https://docs.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory + SharedMemory = 's' +) + +// Action types. +const ( + // Transmit image data. + Transmit = 't' + // TransmitAndPut transmit image data and display (put) it. + TransmitAndPut = 'T' + // Query terminal for image info. + Query = 'q' + // Put (display) previously transmitted image. + Put = 'p' + // Delete image. + Delete = 'd' + // Frame transmits data for animation frames. + Frame = 'f' + // Animate controls animation. + Animate = 'a' + // Compose composes animation frames. + Compose = 'c' +) + +// Delete types. +const ( + // Delete all placements visible on screen. + DeleteAll = 'a' + // Delete all images with the specified id, specified using the i key. If + // you specify a p key for the placement id as well, then only the + // placement with the specified image id and placement id will be deleted. + DeleteID = 'i' + // Delete newest image with the specified number, specified using the I + // key. If you specify a p key for the placement id as well, then only the + // placement with the specified number and placement id will be deleted. + DeleteNumber = 'n' + // Delete all placements that intersect with the current cursor position. + DeleteCursor = 'c' + // Delete animation frames. + DeleteFrames = 'f' + // Delete all placements that intersect a specific cell, the cell is + // specified using the x and y keys. + DeleteCell = 'p' + // Delete all placements that intersect a specific cell having a specific + // z-index. The cell and z-index is specified using the x, y and z keys. + DeleteCellZ = 'q' + // Delete all images whose id is greater than or equal to the value of the x + // key and less than or equal to the value of the y. + DeleteRange = 'r' + // Delete all placements that intersect the specified column, specified using + // the x key. + DeleteColumn = 'x' + // Delete all placements that intersect the specified row, specified using + // the y key. + DeleteRow = 'y' + // Delete all placements that have the specified z-index, specified using the + // z key. + DeleteZ = 'z' +) + +// Diacritic returns the diacritic rune at the specified index. If the index is +// out of bounds, the first diacritic rune is returned. +func Diacritic(i int) rune { + if i < 0 || i >= len(diacritics) { + return diacritics[0] + } + return diacritics[i] +} + +// From https://sw.kovidgoyal.net/kitty/_downloads/f0a0de9ec8d9ff4456206db8e0814937/rowcolumn-diacritics.txt +// See https://sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders for further explanation. +var diacritics = []rune{ + '\u0305', + '\u030D', + '\u030E', + '\u0310', + '\u0312', + '\u033D', + '\u033E', + '\u033F', + '\u0346', + '\u034A', + '\u034B', + '\u034C', + '\u0350', + '\u0351', + '\u0352', + '\u0357', + '\u035B', + '\u0363', + '\u0364', + '\u0365', + '\u0366', + '\u0367', + '\u0368', + '\u0369', + '\u036A', + '\u036B', + '\u036C', + '\u036D', + '\u036E', + '\u036F', + '\u0483', + '\u0484', + '\u0485', + '\u0486', + '\u0487', + '\u0592', + '\u0593', + '\u0594', + '\u0595', + '\u0597', + '\u0598', + '\u0599', + '\u059C', + '\u059D', + '\u059E', + '\u059F', + '\u05A0', + '\u05A1', + '\u05A8', + '\u05A9', + '\u05AB', + '\u05AC', + '\u05AF', + '\u05C4', + '\u0610', + '\u0611', + '\u0612', + '\u0613', + '\u0614', + '\u0615', + '\u0616', + '\u0617', + '\u0657', + '\u0658', + '\u0659', + '\u065A', + '\u065B', + '\u065D', + '\u065E', + '\u06D6', + '\u06D7', + '\u06D8', + '\u06D9', + '\u06DA', + '\u06DB', + '\u06DC', + '\u06DF', + '\u06E0', + '\u06E1', + '\u06E2', + '\u06E4', + '\u06E7', + '\u06E8', + '\u06EB', + '\u06EC', + '\u0730', + '\u0732', + '\u0733', + '\u0735', + '\u0736', + '\u073A', + '\u073D', + '\u073F', + '\u0740', + '\u0741', + '\u0743', + '\u0745', + '\u0747', + '\u0749', + '\u074A', + '\u07EB', + '\u07EC', + '\u07ED', + '\u07EE', + '\u07EF', + '\u07F0', + '\u07F1', + '\u07F3', + '\u0816', + '\u0817', + '\u0818', + '\u0819', + '\u081B', + '\u081C', + '\u081D', + '\u081E', + '\u081F', + '\u0820', + '\u0821', + '\u0822', + '\u0823', + '\u0825', + '\u0826', + '\u0827', + '\u0829', + '\u082A', + '\u082B', + '\u082C', + '\u082D', + '\u0951', + '\u0953', + '\u0954', + '\u0F82', + '\u0F83', + '\u0F86', + '\u0F87', + '\u135D', + '\u135E', + '\u135F', + '\u17DD', + '\u193A', + '\u1A17', + '\u1A75', + '\u1A76', + '\u1A77', + '\u1A78', + '\u1A79', + '\u1A7A', + '\u1A7B', + '\u1A7C', + '\u1B6B', + '\u1B6D', + '\u1B6E', + '\u1B6F', + '\u1B70', + '\u1B71', + '\u1B72', + '\u1B73', + '\u1CD0', + '\u1CD1', + '\u1CD2', + '\u1CDA', + '\u1CDB', + '\u1CE0', + '\u1DC0', + '\u1DC1', + '\u1DC3', + '\u1DC4', + '\u1DC5', + '\u1DC6', + '\u1DC7', + '\u1DC8', + '\u1DC9', + '\u1DCB', + '\u1DCC', + '\u1DD1', + '\u1DD2', + '\u1DD3', + '\u1DD4', + '\u1DD5', + '\u1DD6', + '\u1DD7', + '\u1DD8', + '\u1DD9', + '\u1DDA', + '\u1DDB', + '\u1DDC', + '\u1DDD', + '\u1DDE', + '\u1DDF', + '\u1DE0', + '\u1DE1', + '\u1DE2', + '\u1DE3', + '\u1DE4', + '\u1DE5', + '\u1DE6', + '\u1DFE', + '\u20D0', + '\u20D1', + '\u20D4', + '\u20D5', + '\u20D6', + '\u20D7', + '\u20DB', + '\u20DC', + '\u20E1', + '\u20E7', + '\u20E9', + '\u20F0', + '\u2CEF', + '\u2CF0', + '\u2CF1', + '\u2DE0', + '\u2DE1', + '\u2DE2', + '\u2DE3', + '\u2DE4', + '\u2DE5', + '\u2DE6', + '\u2DE7', + '\u2DE8', + '\u2DE9', + '\u2DEA', + '\u2DEB', + '\u2DEC', + '\u2DED', + '\u2DEE', + '\u2DEF', + '\u2DF0', + '\u2DF1', + '\u2DF2', + '\u2DF3', + '\u2DF4', + '\u2DF5', + '\u2DF6', + '\u2DF7', + '\u2DF8', + '\u2DF9', + '\u2DFA', + '\u2DFB', + '\u2DFC', + '\u2DFD', + '\u2DFE', + '\u2DFF', + '\uA66F', + '\uA67C', + '\uA67D', + '\uA6F0', + '\uA6F1', + '\uA8E0', + '\uA8E1', + '\uA8E2', + '\uA8E3', + '\uA8E4', + '\uA8E5', + '\uA8E6', + '\uA8E7', + '\uA8E8', + '\uA8E9', + '\uA8EA', + '\uA8EB', + '\uA8EC', + '\uA8ED', + '\uA8EE', + '\uA8EF', + '\uA8F0', + '\uA8F1', + '\uAAB0', + '\uAAB2', + '\uAAB3', + '\uAAB7', + '\uAAB8', + '\uAABE', + '\uAABF', + '\uAAC1', + '\uFE20', + '\uFE21', + '\uFE22', + '\uFE23', + '\uFE24', + '\uFE25', + '\uFE26', + '\U00010A0F', + '\U00010A38', + '\U0001D185', + '\U0001D186', + '\U0001D187', + '\U0001D188', + '\U0001D189', + '\U0001D1AA', + '\U0001D1AB', + '\U0001D1AC', + '\U0001D1AD', + '\U0001D242', + '\U0001D243', + '\U0001D244', +} diff --git a/vendor/github.com/charmbracelet/x/ansi/kitty/options.go b/vendor/github.com/charmbracelet/x/ansi/kitty/options.go new file mode 100644 index 000000000..bf3f8f066 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/ansi/kitty/options.go @@ -0,0 +1,386 @@ +package kitty + +import ( + "encoding" + "fmt" + "strconv" + "strings" +) + +var ( + _ encoding.TextMarshaler = Options{} + _ encoding.TextUnmarshaler = &Options{} +) + +// Stringish is a type constraint for types that are either a string or a +// byte slice. +type Stringish interface{ string | []byte } + +// Options represents a Kitty Graphics Protocol options. +type Options struct { + // Common options. + + // Action (a=t) is the action to be performed on the image. Can be one of + // [Transmit], [TransmitDisplay], [Query], [Put], [Delete], [Frame], + // [Animate], [Compose]. + Action byte + + // Quite is the kitty graphics quiet mode. + // + // Deprecated: misspelled field, use [Options.Quiet] instead. Any non-zero + // Quite overrides Quiet. + Quite byte + + // Quiet mode (q=0) is the quiet mode. Can be either zero, one, or two + // where zero is the default, 1 suppresses OK responses, and 2 suppresses + // both OK and error responses. + Quiet byte + + // Transmission options. + + // ID (i=) is the image ID. The ID is a unique identifier for the image. + // Must be a positive integer up to [math.MaxUint32]. + ID int + + // PlacementID (p=) is the placement ID. The placement ID is a unique + // identifier for the placement of the image. Must be a positive integer up + // to [math.MaxUint32]. + PlacementID int + + // Number (I=0) is the number of images to be transmitted. + Number int + + // Format (f=32) is the image format. One of [RGBA], [RGB], [PNG]. + Format int + + // ImageWidth (s=0) is the transmitted image width. + ImageWidth int + + // ImageHeight (v=0) is the transmitted image height. + ImageHeight int + + // Compression (o=) is the image compression type. Can be [Zlib] or zero. + Compression byte + + // Transmission (t=d) is the image transmission type. Can be [Direct], [File], + // [TempFile], or[SharedMemory]. + Transmission byte + + // File is the file path to be used when the transmission type is [File]. + // If [Options.Transmission] is omitted i.e. zero and this is non-empty, + // the transmission type is set to [File]. + File string + + // Size (S=0) is the size to be read from the transmission medium. + Size int + + // Offset (O=0) is the offset byte to start reading from the transmission + // medium. + Offset int + + // Chunk (m=) whether the image is transmitted in chunks. Can be either + // zero or one. When true, the image is transmitted in chunks. Each chunk + // must be a multiple of 4, and up to [MaxChunkSize] bytes. Each chunk must + // have the m=1 option except for the last chunk which must have m=0. + Chunk bool + + // ChunkFormatter is the function used to format each chunk when + // [Options.Chunk] is true. If nil, the chunks are sent as is. + ChunkFormatter func(chunk string) string + + // Display options. + + // X (x=0) is the pixel X coordinate of the image to start displaying. + X int + + // Y (y=0) is the pixel Y coordinate of the image to start displaying. + Y int + + // Z (z=0) is the Z coordinate of the image to display. + Z int + + // Width (w=0) is the width of the image to display. + Width int + + // Height (h=0) is the height of the image to display. + Height int + + // OffsetX (X=0) is the OffsetX coordinate of the cursor cell to start + // displaying the image. OffsetX=0 is the leftmost cell. This must be + // smaller than the terminal cell width. + OffsetX int + + // OffsetY (Y=0) is the OffsetY coordinate of the cursor cell to start + // displaying the image. OffsetY=0 is the topmost cell. This must be + // smaller than the terminal cell height. + OffsetY int + + // Columns (c=0) is the number of columns to display the image. The image + // will be scaled to fit the number of columns. + Columns int + + // Rows (r=0) is the number of rows to display the image. The image will be + // scaled to fit the number of rows. + Rows int + + // VirtualPlacement (U=0) whether to use virtual placement. This is used + // with Unicode [Placeholder] to display images. + VirtualPlacement bool + + // DoNotMoveCursor (C=0) whether to move the cursor after displaying the + // image. + DoNotMoveCursor bool + + // ParentID (P=0) is the parent image ID. The parent ID is the ID of the + // image that is the parent of the current image. This is used with Unicode + // [Placeholder] to display images relative to the parent image. + ParentID int + + // ParentPlacementID (Q=0) is the parent placement ID. The parent placement + // ID is the ID of the placement of the parent image. This is used with + // Unicode [Placeholder] to display images relative to the parent image. + ParentPlacementID int + + // Delete options. + + // Delete (d=a) is the delete action. Can be one of [DeleteAll], + // [DeleteID], [DeleteNumber], [DeleteCursor], [DeleteFrames], + // [DeleteCell], [DeleteCellZ], [DeleteRange], [DeleteColumn], [DeleteRow], + // [DeleteZ]. + Delete byte + + // DeleteResources indicates whether to delete the resources associated + // with the image. + DeleteResources bool +} + +// Options returns the options as a slice of a key-value pairs. +func (o *Options) Options() (opts []string) { + opts = []string{} + if o.Format == 0 { + o.Format = RGBA + } + + if o.Action == 0 { + o.Action = Transmit + } + + if o.Delete == 0 { + o.Delete = DeleteAll + } + + if o.Transmission == 0 { + if len(o.File) > 0 { + o.Transmission = File + } else { + o.Transmission = Direct + } + } + + if o.Format != RGBA { + opts = append(opts, fmt.Sprintf("f=%d", o.Format)) + } + + quiet := o.Quiet + if o.Quite > 0 { + // handle deprecated Quite field. + quiet = o.Quite + } + if quiet > 0 { + opts = append(opts, fmt.Sprintf("q=%d", quiet)) + } + + if o.ID > 0 { + opts = append(opts, fmt.Sprintf("i=%d", o.ID)) + } + + if o.PlacementID > 0 { + opts = append(opts, fmt.Sprintf("p=%d", o.PlacementID)) + } + + if o.Number > 0 { + opts = append(opts, fmt.Sprintf("I=%d", o.Number)) + } + + if o.ImageWidth > 0 { + opts = append(opts, fmt.Sprintf("s=%d", o.ImageWidth)) + } + + if o.ImageHeight > 0 { + opts = append(opts, fmt.Sprintf("v=%d", o.ImageHeight)) + } + + if o.Transmission != Direct { + opts = append(opts, fmt.Sprintf("t=%c", o.Transmission)) + } + + if o.Size > 0 { + opts = append(opts, fmt.Sprintf("S=%d", o.Size)) + } + + if o.Offset > 0 { + opts = append(opts, fmt.Sprintf("O=%d", o.Offset)) + } + + if o.Compression == Zlib { + opts = append(opts, fmt.Sprintf("o=%c", o.Compression)) + } + + if o.VirtualPlacement { + opts = append(opts, "U=1") + } + + if o.DoNotMoveCursor { + opts = append(opts, "C=1") + } + + if o.ParentID > 0 { + opts = append(opts, fmt.Sprintf("P=%d", o.ParentID)) + } + + if o.ParentPlacementID > 0 { + opts = append(opts, fmt.Sprintf("Q=%d", o.ParentPlacementID)) + } + + if o.X > 0 { + opts = append(opts, fmt.Sprintf("x=%d", o.X)) + } + + if o.Y > 0 { + opts = append(opts, fmt.Sprintf("y=%d", o.Y)) + } + + if o.Z > 0 { + opts = append(opts, fmt.Sprintf("z=%d", o.Z)) + } + + if o.Width > 0 { + opts = append(opts, fmt.Sprintf("w=%d", o.Width)) + } + + if o.Height > 0 { + opts = append(opts, fmt.Sprintf("h=%d", o.Height)) + } + + if o.OffsetX > 0 { + opts = append(opts, fmt.Sprintf("X=%d", o.OffsetX)) + } + + if o.OffsetY > 0 { + opts = append(opts, fmt.Sprintf("Y=%d", o.OffsetY)) + } + + if o.Columns > 0 { + opts = append(opts, fmt.Sprintf("c=%d", o.Columns)) + } + + if o.Rows > 0 { + opts = append(opts, fmt.Sprintf("r=%d", o.Rows)) + } + + if o.Delete != DeleteAll || o.DeleteResources { + da := o.Delete + if o.DeleteResources { + da = da - ' ' // to uppercase + } + + opts = append(opts, fmt.Sprintf("d=%c", da)) + } + + if o.Action != Transmit { + opts = append(opts, fmt.Sprintf("a=%c", o.Action)) + } + + return opts // complex function with multiple returns +} + +// String returns the string representation of the options. +func (o Options) String() string { + return strings.Join(o.Options(), ",") +} + +// MarshalText returns the string representation of the options. +func (o Options) MarshalText() ([]byte, error) { + return []byte(o.String()), nil +} + +// UnmarshalText parses the options from the given string. +func (o *Options) UnmarshalText(text []byte) error { + opts := strings.Split(string(text), ",") + for _, opt := range opts { + ps := strings.SplitN(opt, "=", 2) + if len(ps) != 2 || len(ps[1]) == 0 { + continue + } + + switch ps[0] { + case "a": + o.Action = ps[1][0] + case "o": + o.Compression = ps[1][0] + case "t": + o.Transmission = ps[1][0] + case "d": + d := ps[1][0] + if d >= 'A' && d <= 'Z' { + o.DeleteResources = true + d = d + ' ' // to lowercase + } + o.Delete = d + case "i", "q", "p", "I", "f", "s", "v", "S", "O", "m", "x", "y", "z", "w", "h", "X", "Y", "c", "r", "U", "P", "Q": + v, err := strconv.Atoi(ps[1]) + if err != nil { + continue + } + + switch ps[0] { + case "i": + o.ID = v + case "q": + o.Quiet = byte(v) //nolint:gosec + case "p": + o.PlacementID = v + case "I": + o.Number = v + case "f": + o.Format = v + case "s": + o.ImageWidth = v + case "v": + o.ImageHeight = v + case "S": + o.Size = v + case "O": + o.Offset = v + case "m": + o.Chunk = v == 0 || v == 1 + case "x": + o.X = v + case "y": + o.Y = v + case "z": + o.Z = v + case "w": + o.Width = v + case "h": + o.Height = v + case "X": + o.OffsetX = v + case "Y": + o.OffsetY = v + case "c": + o.Columns = v + case "r": + o.Rows = v + case "U": + o.VirtualPlacement = v == 1 + case "P": + o.ParentID = v + case "Q": + o.ParentPlacementID = v + } + } + } + + return nil +} diff --git a/vendor/github.com/charmbracelet/x/ansi/kitty/writer.go b/vendor/github.com/charmbracelet/x/ansi/kitty/writer.go new file mode 100644 index 000000000..6093b95b0 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/ansi/kitty/writer.go @@ -0,0 +1,192 @@ +package kitty + +import ( + "bytes" + "encoding/base64" + "errors" + "fmt" + "image" + "io" + "os" + + "github.com/charmbracelet/x/ansi" +) + +var ( + // GraphicsTempDir is the directory where temporary files are stored. + // This is used in [WriteKittyGraphics] along with [os.CreateTemp]. + GraphicsTempDir = "" + + // GraphicsTempPattern is the pattern used to create temporary files. + // This is used in [WriteKittyGraphics] along with [os.CreateTemp]. + // The Kitty Graphics protocol requires the file path to contain the + // substring "tty-graphics-protocol". + GraphicsTempPattern = "tty-graphics-protocol-*" +) + +// EncodeGraphics writes an image using the Kitty Graphics protocol with the +// given options to w. It chunks the written data if o.Chunk is true. +// +// You can omit m and use nil when rendering an image from a file. In this +// case, you must provide a file path in o.File and use o.Transmission = +// [File]. You can also use o.Transmission = [TempFile] to write +// the image to a temporary file. In that case, the file path is ignored, and +// the image is written to a temporary file that is automatically deleted by +// the terminal. +// +// See https://sw.kovidgoyal.net/kitty/graphics-protocol/ +func EncodeGraphics(w io.Writer, m image.Image, o *Options) error { + if o == nil { + o = &Options{} + } + + if o.Transmission == 0 && len(o.File) != 0 { + o.Transmission = File + } + + var data bytes.Buffer // the data to be encoded into base64 + e := &Encoder{ + Compress: o.Compression == Zlib, + Format: o.Format, + } + + switch o.Transmission { + case Direct: + if err := e.Encode(&data, m); err != nil { + return fmt.Errorf("failed to encode direct image: %w", err) + } + + case SharedMemory: + //nolint:godox + // TODO: Implement shared memory + return fmt.Errorf("shared memory transmission is not yet implemented") + + case File: + if len(o.File) == 0 { + return ErrMissingFile + } + + f, err := os.Open(o.File) + if err != nil { + return fmt.Errorf("failed to open file: %w", err) + } + + defer f.Close() //nolint:errcheck + + stat, err := f.Stat() + if err != nil { + return fmt.Errorf("failed to get file info: %w", err) + } + + mode := stat.Mode() + if !mode.IsRegular() { + return fmt.Errorf("file is not a regular file") + } + + // Write the file path to the buffer + if _, err := data.WriteString(f.Name()); err != nil { + return fmt.Errorf("failed to write file path to buffer: %w", err) + } + + case TempFile: + f, err := os.CreateTemp(GraphicsTempDir, GraphicsTempPattern) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + + defer f.Close() //nolint:errcheck + + if err := e.Encode(f, m); err != nil { + return fmt.Errorf("failed to encode image to file: %w", err) + } + + // Write the file path to the buffer + if _, err := data.WriteString(f.Name()); err != nil { + return fmt.Errorf("failed to write file path to buffer: %w", err) + } + } + + // Encode image to base64 + var payload bytes.Buffer // the base64 encoded image to be written to w + b64 := base64.NewEncoder(base64.StdEncoding, &payload) + if _, err := data.WriteTo(b64); err != nil { + return fmt.Errorf("failed to write base64 encoded image to payload: %w", err) + } + if err := b64.Close(); err != nil { + return err //nolint:wrapcheck + } + + // If not chunking, write all at once + if !o.Chunk { + _, err := io.WriteString(w, ansi.KittyGraphics(payload.Bytes(), o.Options()...)) + return err //nolint:wrapcheck + } + + // Write in chunks + var ( + err error + n int + ) + chunk := make([]byte, MaxChunkSize) + isFirstChunk := true + chunkFormatter := o.ChunkFormatter + if chunkFormatter == nil { + // Default to no formatting + chunkFormatter = func(s string) string { return s } + } + + for { + // Stop if we read less than the chunk size [MaxChunkSize]. + n, err = io.ReadFull(&payload, chunk) + if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) { + break + } + if err != nil { + return fmt.Errorf("failed to read chunk: %w", err) + } + + opts := buildChunkOptions(o, isFirstChunk, false) + if _, err := io.WriteString(w, + chunkFormatter(ansi.KittyGraphics(chunk[:n], opts...))); err != nil { + return err //nolint:wrapcheck + } + + isFirstChunk = false + } + + // Write the last chunk + opts := buildChunkOptions(o, isFirstChunk, true) + _, err = io.WriteString(w, chunkFormatter(ansi.KittyGraphics(chunk[:n], opts...))) + return err //nolint:wrapcheck +} + +// buildChunkOptions creates the options slice for a chunk. +func buildChunkOptions(o *Options, isFirstChunk, isLastChunk bool) []string { + var opts []string + if isFirstChunk { + opts = o.Options() + } else { + // These options are allowed in subsequent chunks + quiet := o.Quiet + if o.Quite > 0 { + // handle deprecated Quite field. + quiet = o.Quite + } + if quiet > 0 { + opts = append(opts, fmt.Sprintf("q=%d", quiet)) + } + if o.Action == Frame { + opts = append(opts, "a=f") + } + } + + if !isFirstChunk || !isLastChunk { + // We don't need to encode the (m=) option when we only have one chunk. + if isLastChunk { + opts = append(opts, "m=0") + } else { + opts = append(opts, "m=1") + } + } + return opts +} diff --git a/vendor/github.com/charmbracelet/x/termios/LICENSE b/vendor/github.com/charmbracelet/x/termios/LICENSE new file mode 100644 index 000000000..65a5654e2 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/termios/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Charmbracelet, Inc. + +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. diff --git a/vendor/github.com/charmbracelet/x/termios/bit_bsd.go b/vendor/github.com/charmbracelet/x/termios/bit_bsd.go new file mode 100644 index 000000000..8dc1eeddf --- /dev/null +++ b/vendor/github.com/charmbracelet/x/termios/bit_bsd.go @@ -0,0 +1,7 @@ +//go:build netbsd || openbsd +// +build netbsd openbsd + +package termios + +func speed(b uint32) int32 { return int32(b) } +func bit(b uint32) uint32 { return b } diff --git a/vendor/github.com/charmbracelet/x/termios/bit_darwin.go b/vendor/github.com/charmbracelet/x/termios/bit_darwin.go new file mode 100644 index 000000000..de5d5c4bb --- /dev/null +++ b/vendor/github.com/charmbracelet/x/termios/bit_darwin.go @@ -0,0 +1,7 @@ +//go:build darwin +// +build darwin + +package termios + +func speed(b uint32) uint64 { return uint64(b) } +func bit(b uint32) uint64 { return uint64(b) } diff --git a/vendor/github.com/charmbracelet/x/termios/bit_other.go b/vendor/github.com/charmbracelet/x/termios/bit_other.go new file mode 100644 index 000000000..afdf58fd9 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/termios/bit_other.go @@ -0,0 +1,7 @@ +//go:build !darwin && !netbsd && !openbsd +// +build !darwin,!netbsd,!openbsd + +package termios + +func speed(b uint32) uint32 { return b } +func bit(b uint32) uint32 { return b } diff --git a/vendor/github.com/charmbracelet/x/termios/syscalls_bsd.go b/vendor/github.com/charmbracelet/x/termios/syscalls_bsd.go new file mode 100644 index 000000000..8ee014bd6 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/termios/syscalls_bsd.go @@ -0,0 +1,11 @@ +//go:build darwin && netbsd && freebsd && netbsd +// +build darwin,netbsd,freebsd,netbsd + +package term + +import "syscall" + +func init() { + allCcOpts[STATUS] = syscall.VSTATUS + allCcOpts[DSUSP] = syscall.VDSUSP +} diff --git a/vendor/github.com/charmbracelet/x/termios/syscalls_darwin.go b/vendor/github.com/charmbracelet/x/termios/syscalls_darwin.go new file mode 100644 index 000000000..560c8a2b0 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/termios/syscalls_darwin.go @@ -0,0 +1,10 @@ +//go:build darwin +// +build darwin + +package termios + +import "syscall" + +func init() { + allLineOpts[IUTF8] = syscall.IUTF8 +} diff --git a/vendor/github.com/charmbracelet/x/termios/syscalls_linux.go b/vendor/github.com/charmbracelet/x/termios/syscalls_linux.go new file mode 100644 index 000000000..7aee0ae77 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/termios/syscalls_linux.go @@ -0,0 +1,14 @@ +//go:build linux +// +build linux + +package termios + +import "syscall" + +func init() { + allCcOpts[SWTCH] = syscall.VSWTC + allInputOpts[IUCLC] = syscall.IUCLC + allLineOpts[IUTF8] = syscall.IUTF8 + allLineOpts[XCASE] = syscall.XCASE + allOutputOpts[OLCUC] = syscall.OLCUC +} diff --git a/vendor/github.com/charmbracelet/x/termios/termios.go b/vendor/github.com/charmbracelet/x/termios/termios.go new file mode 100644 index 000000000..07cdacf22 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/termios/termios.go @@ -0,0 +1,249 @@ +//go:build darwin || netbsd || freebsd || openbsd || linux || dragonfly || solaris +// +build darwin netbsd freebsd openbsd linux dragonfly solaris + +package termios + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +// SetWinsize sets window size for an fd from a Winsize. +func SetWinsize(fd int, w *unix.Winsize) error { + return unix.IoctlSetWinsize(fd, ioctlSetWinSize, w) +} + +// GetWinsize gets window size for an fd. +func GetWinsize(fd int) (*unix.Winsize, error) { + return unix.IoctlGetWinsize(fd, ioctlGetWinSize) +} + +// GetTermios gets the termios of the given fd. +func GetTermios(fd int) (*unix.Termios, error) { + return unix.IoctlGetTermios(fd, ioctlGets) +} + +// SetTermios sets the given termios over the given fd's current termios. +func SetTermios( + fd int, + ispeed uint32, + ospeed uint32, + cc map[CC]uint8, + iflag map[I]bool, + oflag map[O]bool, + cflag map[C]bool, + lflag map[L]bool, +) error { + term, err := unix.IoctlGetTermios(fd, ioctlGets) + if err != nil { + return err + } + setSpeed(term, ispeed, ospeed) + + for key, value := range cc { + call, ok := allCcOpts[key] + if !ok { + continue + } + term.Cc[call] = value + } + + for key, value := range iflag { + mask, ok := allInputOpts[key] + if ok { + if value { + term.Iflag |= bit(mask) + } else { + term.Iflag &= ^bit(mask) + } + } + } + for key, value := range oflag { + mask, ok := allOutputOpts[key] + if ok { + if value { + term.Oflag |= bit(mask) + } else { + term.Oflag &= ^bit(mask) + } + } + } + for key, value := range cflag { + mask, ok := allControlOpts[key] + if ok { + if value { + term.Cflag |= bit(mask) + } else { + term.Cflag &= ^bit(mask) + } + } + } + for key, value := range lflag { + mask, ok := allLineOpts[key] + if ok { + if value { + term.Lflag |= bit(mask) + } else { + term.Lflag &= ^bit(mask) + } + } + } + return unix.IoctlSetTermios(fd, ioctlSets, term) +} + +// CC is the termios cc field. +// +// It stores an array of special characters related to terminal I/O. +type CC uint8 + +// CC possible values. +const ( + INTR CC = iota + QUIT + ERASE + KILL + EOF + EOL + EOL2 + START + STOP + SUSP + WERASE + RPRNT + LNEXT + DISCARD + STATUS + SWTCH + DSUSP + FLUSH +) + +// https://www.man7.org/linux/man-pages/man3/termios.3.html +var allCcOpts = map[CC]int{ + INTR: syscall.VINTR, + QUIT: syscall.VQUIT, + ERASE: syscall.VERASE, + KILL: syscall.VQUIT, + EOF: syscall.VEOF, + EOL: syscall.VEOL, + EOL2: syscall.VEOL2, + START: syscall.VSTART, + STOP: syscall.VSTOP, + SUSP: syscall.VSUSP, + WERASE: syscall.VWERASE, + RPRNT: syscall.VREPRINT, + LNEXT: syscall.VLNEXT, + DISCARD: syscall.VDISCARD, + + // XXX: these syscalls don't exist for any OS + // FLUSH: syscall.VFLUSH, +} + +// Input Controls +type I uint8 + +// Input possible values. +const ( + IGNPAR I = iota + PARMRK + INPCK + ISTRIP + INLCR + IGNCR + ICRNL + IXON + IXANY + IXOFF + IMAXBEL + IUCLC +) + +var allInputOpts = map[I]uint32{ + IGNPAR: syscall.IGNPAR, + PARMRK: syscall.PARMRK, + INPCK: syscall.INPCK, + ISTRIP: syscall.ISTRIP, + INLCR: syscall.INLCR, + IGNCR: syscall.IGNCR, + ICRNL: syscall.ICRNL, + IXON: syscall.IXON, + IXANY: syscall.IXANY, + IXOFF: syscall.IXOFF, + IMAXBEL: syscall.IMAXBEL, +} + +// Output Controls +type O uint8 + +// Output possible values. +const ( + OPOST O = iota + ONLCR + OCRNL + ONOCR + ONLRET + OLCUC +) + +var allOutputOpts = map[O]uint32{ + OPOST: syscall.OPOST, + ONLCR: syscall.ONLCR, + OCRNL: syscall.OCRNL, + ONOCR: syscall.ONOCR, + ONLRET: syscall.ONLRET, +} + +// Control +type C uint8 + +// Control possible values. +const ( + CS7 C = iota + CS8 + PARENB + PARODD +) + +var allControlOpts = map[C]uint32{ + CS7: syscall.CS7, + CS8: syscall.CS8, + PARENB: syscall.PARENB, + PARODD: syscall.PARODD, +} + +// Line Controls. +type L uint8 + +// Line possible values. +const ( + ISIG L = iota + ICANON + ECHO + ECHOE + ECHOK + ECHONL + NOFLSH + TOSTOP + IEXTEN + ECHOCTL + ECHOKE + PENDIN + IUTF8 + XCASE +) + +var allLineOpts = map[L]uint32{ + ISIG: syscall.ISIG, + ICANON: syscall.ICANON, + ECHO: syscall.ECHO, + ECHOE: syscall.ECHOE, + ECHOK: syscall.ECHOK, + ECHONL: syscall.ECHONL, + NOFLSH: syscall.NOFLSH, + TOSTOP: syscall.TOSTOP, + IEXTEN: syscall.IEXTEN, + ECHOCTL: syscall.ECHOCTL, + ECHOKE: syscall.ECHOKE, + PENDIN: syscall.PENDIN, +} diff --git a/vendor/github.com/charmbracelet/x/termios/termios_bsd.go b/vendor/github.com/charmbracelet/x/termios/termios_bsd.go new file mode 100644 index 000000000..ef62f1975 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/termios/termios_bsd.go @@ -0,0 +1,13 @@ +//go:build darwin || netbsd || freebsd || openbsd || dragonfly +// +build darwin netbsd freebsd openbsd dragonfly + +package termios + +import "golang.org/x/sys/unix" + +const ( + ioctlGets = unix.TIOCGETA + ioctlSets = unix.TIOCSETA + ioctlGetWinSize = unix.TIOCGWINSZ + ioctlSetWinSize = unix.TIOCSWINSZ +) diff --git a/vendor/github.com/charmbracelet/x/termios/termios_linux.go b/vendor/github.com/charmbracelet/x/termios/termios_linux.go new file mode 100644 index 000000000..c78557588 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/termios/termios_linux.go @@ -0,0 +1,13 @@ +//go:build linux +// +build linux + +package termios + +import "golang.org/x/sys/unix" + +const ( + ioctlGets = unix.TCGETS + ioctlSets = unix.TCSETS + ioctlGetWinSize = unix.TIOCGWINSZ + ioctlSetWinSize = unix.TIOCSWINSZ +) diff --git a/vendor/github.com/charmbracelet/x/termios/termios_other.go b/vendor/github.com/charmbracelet/x/termios/termios_other.go new file mode 100644 index 000000000..129dfcee9 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/termios/termios_other.go @@ -0,0 +1,15 @@ +//go:build darwin || netbsd || freebsd || openbsd || linux || dragonfly +// +build darwin netbsd freebsd openbsd linux dragonfly + +package termios + +import "golang.org/x/sys/unix" + +func setSpeed(term *unix.Termios, ispeed, ospeed uint32) { + term.Ispeed = speed(ispeed) + term.Ospeed = speed(ospeed) +} + +func getSpeed(term *unix.Termios) (uint32, uint32) { + return uint32(term.Ispeed), uint32(term.Ospeed) +} diff --git a/vendor/github.com/charmbracelet/x/termios/termios_solaris.go b/vendor/github.com/charmbracelet/x/termios/termios_solaris.go new file mode 100644 index 000000000..129a38190 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/termios/termios_solaris.go @@ -0,0 +1,26 @@ +//go:build solaris +// +build solaris + +package termios + +import "golang.org/x/sys/unix" + +// see https://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/isatty.c +// see https://github.com/omniti-labs/illumos-omnios/blob/master/usr/src/uts/common/sys/termios.h +const ( + ioctlSets = unix.TCSETA + ioctlGets = unix.TCGETA + ioctlSetWinSize = (int('T') << 8) | 103 + ioctlGetWinSize = (int('T') << 8) | 104 +) + +func setSpeed(*unix.Termios, uint32, uint32) { + // TODO: support setting speed on Solaris? + // see cfgetospeed(3C) and cfsetospeed(3C) + // see cfgetispeed(3C) and cfsetispeed(3C) + // https://github.com/omniti-labs/illumos-omnios/blob/master/usr/src/uts/common/sys/termios.h#L103 +} + +func getSpeed(*unix.Termios) (uint32, uint32) { + return 0, 0 +} diff --git a/vendor/github.com/charmbracelet/x/windows/LICENSE b/vendor/github.com/charmbracelet/x/windows/LICENSE new file mode 100644 index 000000000..65a5654e2 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/windows/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Charmbracelet, Inc. + +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. diff --git a/vendor/github.com/charmbracelet/x/windows/doc.go b/vendor/github.com/charmbracelet/x/windows/doc.go new file mode 100644 index 000000000..e99399084 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/windows/doc.go @@ -0,0 +1,4 @@ +// Package windows provides Windows-specific system utilities. +package windows + +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go diff --git a/vendor/github.com/charmbracelet/x/windows/syscall_windows.go b/vendor/github.com/charmbracelet/x/windows/syscall_windows.go new file mode 100644 index 000000000..48c9b307d --- /dev/null +++ b/vendor/github.com/charmbracelet/x/windows/syscall_windows.go @@ -0,0 +1,14 @@ +package windows + +import "golang.org/x/sys/windows" + +// NewLazySystemDLL is a type alias for windows.NewLazySystemDLL. +var NewLazySystemDLL = windows.NewLazySystemDLL + +// Handle is a type alias for windows.Handle. +type Handle = windows.Handle + +//sys ReadConsoleInput(console Handle, buf *InputRecord, toread uint32, read *uint32) (err error) = kernel32.ReadConsoleInputW +//sys PeekConsoleInput(console Handle, buf *InputRecord, toread uint32, read *uint32) (err error) = kernel32.PeekConsoleInputW +//sys GetNumberOfConsoleInputEvents(console Handle, numevents *uint32) (err error) = kernel32.GetNumberOfConsoleInputEvents +//sys FlushConsoleInputBuffer(console Handle) (err error) = kernel32.FlushConsoleInputBuffer diff --git a/vendor/github.com/charmbracelet/x/windows/types.go b/vendor/github.com/charmbracelet/x/windows/types.go new file mode 100644 index 000000000..0fd93c949 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/windows/types.go @@ -0,0 +1,221 @@ +package windows + +// Virtual Key codes +// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +// +//nolint:revive +const ( + VK_LBUTTON = 0x01 + VK_RBUTTON = 0x02 + VK_CANCEL = 0x03 + VK_MBUTTON = 0x04 + VK_XBUTTON1 = 0x05 + VK_XBUTTON2 = 0x06 + VK_BACK = 0x08 + VK_TAB = 0x09 + VK_CLEAR = 0x0C + VK_RETURN = 0x0D + VK_SHIFT = 0x10 + VK_CONTROL = 0x11 + VK_MENU = 0x12 + VK_PAUSE = 0x13 + VK_CAPITAL = 0x14 + VK_KANA = 0x15 + VK_HANGEUL = 0x15 + VK_HANGUL = 0x15 + VK_IME_ON = 0x16 + VK_JUNJA = 0x17 + VK_FINAL = 0x18 + VK_HANJA = 0x19 + VK_KANJI = 0x19 + VK_IME_OFF = 0x1A + VK_ESCAPE = 0x1B + VK_CONVERT = 0x1C + VK_NONCONVERT = 0x1D + VK_ACCEPT = 0x1E + VK_MODECHANGE = 0x1F + VK_SPACE = 0x20 + VK_PRIOR = 0x21 + VK_NEXT = 0x22 + VK_END = 0x23 + VK_HOME = 0x24 + VK_LEFT = 0x25 + VK_UP = 0x26 + VK_RIGHT = 0x27 + VK_DOWN = 0x28 + VK_SELECT = 0x29 + VK_PRINT = 0x2A + VK_EXECUTE = 0x2B + VK_SNAPSHOT = 0x2C + VK_INSERT = 0x2D + VK_DELETE = 0x2E + VK_HELP = 0x2F + VK_LWIN = 0x5B + VK_RWIN = 0x5C + VK_APPS = 0x5D + VK_SLEEP = 0x5F + VK_NUMPAD0 = 0x60 + VK_NUMPAD1 = 0x61 + VK_NUMPAD2 = 0x62 + VK_NUMPAD3 = 0x63 + VK_NUMPAD4 = 0x64 + VK_NUMPAD5 = 0x65 + VK_NUMPAD6 = 0x66 + VK_NUMPAD7 = 0x67 + VK_NUMPAD8 = 0x68 + VK_NUMPAD9 = 0x69 + VK_MULTIPLY = 0x6A + VK_ADD = 0x6B + VK_SEPARATOR = 0x6C + VK_SUBTRACT = 0x6D + VK_DECIMAL = 0x6E + VK_DIVIDE = 0x6F + VK_F1 = 0x70 + VK_F2 = 0x71 + VK_F3 = 0x72 + VK_F4 = 0x73 + VK_F5 = 0x74 + VK_F6 = 0x75 + VK_F7 = 0x76 + VK_F8 = 0x77 + VK_F9 = 0x78 + VK_F10 = 0x79 + VK_F11 = 0x7A + VK_F12 = 0x7B + VK_F13 = 0x7C + VK_F14 = 0x7D + VK_F15 = 0x7E + VK_F16 = 0x7F + VK_F17 = 0x80 + VK_F18 = 0x81 + VK_F19 = 0x82 + VK_F20 = 0x83 + VK_F21 = 0x84 + VK_F22 = 0x85 + VK_F23 = 0x86 + VK_F24 = 0x87 + VK_NUMLOCK = 0x90 + VK_SCROLL = 0x91 + VK_OEM_NEC_EQUAL = 0x92 + VK_OEM_FJ_JISHO = 0x92 + VK_OEM_FJ_MASSHOU = 0x93 + VK_OEM_FJ_TOUROKU = 0x94 + VK_OEM_FJ_LOYA = 0x95 + VK_OEM_FJ_ROYA = 0x96 + VK_LSHIFT = 0xA0 + VK_RSHIFT = 0xA1 + VK_LCONTROL = 0xA2 + VK_RCONTROL = 0xA3 + VK_LMENU = 0xA4 + VK_RMENU = 0xA5 + VK_BROWSER_BACK = 0xA6 + VK_BROWSER_FORWARD = 0xA7 + VK_BROWSER_REFRESH = 0xA8 + VK_BROWSER_STOP = 0xA9 + VK_BROWSER_SEARCH = 0xAA + VK_BROWSER_FAVORITES = 0xAB + VK_BROWSER_HOME = 0xAC + VK_VOLUME_MUTE = 0xAD + VK_VOLUME_DOWN = 0xAE + VK_VOLUME_UP = 0xAF + VK_MEDIA_NEXT_TRACK = 0xB0 + VK_MEDIA_PREV_TRACK = 0xB1 + VK_MEDIA_STOP = 0xB2 + VK_MEDIA_PLAY_PAUSE = 0xB3 + VK_LAUNCH_MAIL = 0xB4 + VK_LAUNCH_MEDIA_SELECT = 0xB5 + VK_LAUNCH_APP1 = 0xB6 + VK_LAUNCH_APP2 = 0xB7 + VK_OEM_1 = 0xBA + VK_OEM_PLUS = 0xBB + VK_OEM_COMMA = 0xBC + VK_OEM_MINUS = 0xBD + VK_OEM_PERIOD = 0xBE + VK_OEM_2 = 0xBF + VK_OEM_3 = 0xC0 + VK_OEM_4 = 0xDB + VK_OEM_5 = 0xDC + VK_OEM_6 = 0xDD + VK_OEM_7 = 0xDE + VK_OEM_8 = 0xDF + VK_OEM_AX = 0xE1 + VK_OEM_102 = 0xE2 + VK_ICO_HELP = 0xE3 + VK_ICO_00 = 0xE4 + VK_PROCESSKEY = 0xE5 + VK_ICO_CLEAR = 0xE6 + VK_OEM_RESET = 0xE9 + VK_OEM_JUMP = 0xEA + VK_OEM_PA1 = 0xEB + VK_OEM_PA2 = 0xEC + VK_OEM_PA3 = 0xED + VK_OEM_WSCTRL = 0xEE + VK_OEM_CUSEL = 0xEF + VK_OEM_ATTN = 0xF0 + VK_OEM_FINISH = 0xF1 + VK_OEM_COPY = 0xF2 + VK_OEM_AUTO = 0xF3 + VK_OEM_ENLW = 0xF4 + VK_OEM_BACKTAB = 0xF5 + VK_ATTN = 0xF6 + VK_CRSEL = 0xF7 + VK_EXSEL = 0xF8 + VK_EREOF = 0xF9 + VK_PLAY = 0xFA + VK_ZOOM = 0xFB + VK_NONAME = 0xFC + VK_PA1 = 0xFD + VK_OEM_CLEAR = 0xFE +) + +// Mouse button constants. +// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str +// +//nolint:revive +const ( + FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001 + RIGHTMOST_BUTTON_PRESSED = 0x0002 + FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004 + FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008 + FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010 +) + +// Control key state constraints. +// https://docs.microsoft.com/en-us/windows/console/key-event-record-str +// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str +// +//nolint:revive +const ( + CAPSLOCK_ON = 0x0080 + ENHANCED_KEY = 0x0100 + LEFT_ALT_PRESSED = 0x0002 + LEFT_CTRL_PRESSED = 0x0008 + NUMLOCK_ON = 0x0020 + RIGHT_ALT_PRESSED = 0x0001 + RIGHT_CTRL_PRESSED = 0x0004 + SCROLLLOCK_ON = 0x0040 + SHIFT_PRESSED = 0x0010 +) + +// Mouse event record event flags. +// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str +// +//nolint:revive +const ( + MOUSE_MOVED = 0x0001 + DOUBLE_CLICK = 0x0002 + MOUSE_WHEELED = 0x0004 + MOUSE_HWHEELED = 0x0008 +) + +// Input Record Event Types +// https://learn.microsoft.com/en-us/windows/console/input-record-str +// +//nolint:revive +const ( + FOCUS_EVENT = 0x0010 + KEY_EVENT = 0x0001 + MENU_EVENT = 0x0008 + MOUSE_EVENT = 0x0002 + WINDOW_BUFFER_SIZE_EVENT = 0x0004 +) diff --git a/vendor/github.com/charmbracelet/x/windows/types_windows.go b/vendor/github.com/charmbracelet/x/windows/types_windows.go new file mode 100644 index 000000000..272eeaab5 --- /dev/null +++ b/vendor/github.com/charmbracelet/x/windows/types_windows.go @@ -0,0 +1,142 @@ +//nolint:gosec +package windows + +import ( + "encoding/binary" + + "golang.org/x/sys/windows" +) + +// FocusEventRecord corresponds to the FocusEventRecord structure from the +// Windows console API. +// https://docs.microsoft.com/en-us/windows/console/focus-event-record-str +type FocusEventRecord struct { + // SetFocus is reserved and should not be used. + SetFocus bool +} + +// KeyEventRecord corresponds to the KeyEventRecord structure from the Windows +// console API. +// https://docs.microsoft.com/en-us/windows/console/key-event-record-str +type KeyEventRecord struct { + // KeyDown specified whether the key is pressed or released. + KeyDown bool + + // RepeatCount indicates that a key is being held down. For example, when a + // key is held down, five events with RepeatCount equal to 1 may be + // generated, one event with RepeatCount equal to 5, or multiple events + // with RepeatCount greater than or equal to 1. + RepeatCount uint16 + + // VirtualKeyCode identifies the given key in a device-independent manner + // (see + // https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes). + VirtualKeyCode uint16 + + // VirtualScanCode represents the device-dependent value generated by the + // keyboard hardware. + VirtualScanCode uint16 + + // Char is the character that corresponds to the pressed key. Char can be + // zero for some keys. + Char rune + + // ControlKeyState holds the state of the control keys. + ControlKeyState uint32 +} + +// MenuEventRecord corresponds to the MenuEventRecord structure from the +// Windows console API. +// https://docs.microsoft.com/en-us/windows/console/menu-event-record-str +type MenuEventRecord struct { + CommandID uint32 +} + +// MouseEventRecord corresponds to the MouseEventRecord structure from the +// Windows console API. +// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str +type MouseEventRecord struct { + // MousePosition contains the location of the cursor, in terms of the + // console screen buffer's character-cell coordinates. + MousePositon windows.Coord + + // ButtonState holds the status of the mouse buttons. + ButtonState uint32 + + // ControlKeyState holds the state of the control keys. + ControlKeyState uint32 + + // EventFlags specify the type of mouse event. + EventFlags uint32 +} + +// WindowBufferSizeRecord corresponds to the WindowBufferSizeRecord structure +// from the Windows console API. +// https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str +type WindowBufferSizeRecord struct { + // Size contains the size of the console screen buffer, in character cell + // columns and rows. + Size windows.Coord +} + +// InputRecord corresponds to the INPUT_RECORD structure from the Windows +// console API. +// +// https://docs.microsoft.com/en-us/windows/console/input-record-str +type InputRecord struct { + // EventType specifies the type of event that helt in Event. + EventType uint16 + + // Padding of the 16-bit EventType to a whole 32-bit dword. + _ [2]byte + + // Event holds the actual event data. + Event [16]byte +} + +// FocusEvent returns the event as a FOCUS_EVENT_RECORD. +func (ir InputRecord) FocusEvent() FocusEventRecord { + return FocusEventRecord{SetFocus: ir.Event[0] > 0} +} + +// KeyEvent returns the event as a KEY_EVENT_RECORD. +func (ir InputRecord) KeyEvent() KeyEventRecord { + return KeyEventRecord{ + KeyDown: binary.LittleEndian.Uint32(ir.Event[0:4]) > 0, + RepeatCount: binary.LittleEndian.Uint16(ir.Event[4:6]), + VirtualKeyCode: binary.LittleEndian.Uint16(ir.Event[6:8]), + VirtualScanCode: binary.LittleEndian.Uint16(ir.Event[8:10]), + Char: rune(binary.LittleEndian.Uint16(ir.Event[10:12])), + ControlKeyState: binary.LittleEndian.Uint32(ir.Event[12:16]), + } +} + +// MouseEvent returns the event as a MOUSE_EVENT_RECORD. +func (ir InputRecord) MouseEvent() MouseEventRecord { + return MouseEventRecord{ + MousePositon: windows.Coord{ + X: int16(binary.LittleEndian.Uint16(ir.Event[0:2])), + Y: int16(binary.LittleEndian.Uint16(ir.Event[2:4])), + }, + ButtonState: binary.LittleEndian.Uint32(ir.Event[4:8]), + ControlKeyState: binary.LittleEndian.Uint32(ir.Event[8:12]), + EventFlags: binary.LittleEndian.Uint32(ir.Event[12:16]), + } +} + +// WindowBufferSizeEvent returns the event as a WINDOW_BUFFER_SIZE_RECORD. +func (ir InputRecord) WindowBufferSizeEvent() WindowBufferSizeRecord { + return WindowBufferSizeRecord{ + Size: windows.Coord{ + X: int16(binary.LittleEndian.Uint16(ir.Event[0:2])), + Y: int16(binary.LittleEndian.Uint16(ir.Event[2:4])), + }, + } +} + +// MenuEvent returns the event as a MENU_EVENT_RECORD. +func (ir InputRecord) MenuEvent() MenuEventRecord { + return MenuEventRecord{ + CommandID: binary.LittleEndian.Uint32(ir.Event[0:4]), + } +} diff --git a/vendor/github.com/charmbracelet/x/windows/zsyscall_windows.go b/vendor/github.com/charmbracelet/x/windows/zsyscall_windows.go new file mode 100644 index 000000000..43412b84f --- /dev/null +++ b/vendor/github.com/charmbracelet/x/windows/zsyscall_windows.go @@ -0,0 +1,77 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package windows + +import ( + "syscall" + "unsafe" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = syscall.EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modkernel32 = NewLazySystemDLL("kernel32.dll") + + procFlushConsoleInputBuffer = modkernel32.NewProc("FlushConsoleInputBuffer") + procGetNumberOfConsoleInputEvents = modkernel32.NewProc("GetNumberOfConsoleInputEvents") + procPeekConsoleInputW = modkernel32.NewProc("PeekConsoleInputW") + procReadConsoleInputW = modkernel32.NewProc("ReadConsoleInputW") +) + +func FlushConsoleInputBuffer(console Handle) (err error) { + r1, _, e1 := syscall.Syscall(procFlushConsoleInputBuffer.Addr(), 1, uintptr(console), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func GetNumberOfConsoleInputEvents(console Handle, numevents *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetNumberOfConsoleInputEvents.Addr(), 2, uintptr(console), uintptr(unsafe.Pointer(numevents)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func PeekConsoleInput(console Handle, buf *InputRecord, toread uint32, read *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procPeekConsoleInputW.Addr(), 4, uintptr(console), uintptr(unsafe.Pointer(buf)), uintptr(toread), uintptr(unsafe.Pointer(read)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func ReadConsoleInput(console Handle, buf *InputRecord, toread uint32, read *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procReadConsoleInputW.Addr(), 4, uintptr(console), uintptr(unsafe.Pointer(buf)), uintptr(toread), uintptr(unsafe.Pointer(read)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} diff --git a/vendor/github.com/cheggaaa/pb/.travis.yml b/vendor/github.com/cheggaaa/pb/.travis.yml deleted file mode 100644 index 83a356415..000000000 --- a/vendor/github.com/cheggaaa/pb/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: go -go: - - 1.9.x - - 1.12.x -sudo: false -os: - - linux - - osx -before_install: - - go get github.com/mattn/goveralls -script: - - $GOPATH/bin/goveralls -package github.com/cheggaaa/pb/v3 -repotoken QT1y5Iujb8ete6JOiE0ytKFlBDv9vheWc diff --git a/vendor/github.com/cheggaaa/pb/LICENSE b/vendor/github.com/cheggaaa/pb/LICENSE deleted file mode 100644 index 511970333..000000000 --- a/vendor/github.com/cheggaaa/pb/LICENSE +++ /dev/null @@ -1,12 +0,0 @@ -Copyright (c) 2012-2015, Sergey Cherepanov -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -* Neither the name of the author 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 HOLDER 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. \ No newline at end of file diff --git a/vendor/github.com/cheggaaa/pb/README.md b/vendor/github.com/cheggaaa/pb/README.md deleted file mode 100644 index 07d52782d..000000000 --- a/vendor/github.com/cheggaaa/pb/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# Terminal progress bar for Go -[![Coverage Status](https://coveralls.io/repos/github/cheggaaa/pb/badge.svg)](https://coveralls.io/github/cheggaaa/pb) - -## Installation - -``` -go get github.com/cheggaaa/pb/v3 -``` - -Documentation for v1 bar available [here](README_V1.md) - -## Quick start - -```Go -package main - -import ( - "time" - - "github.com/cheggaaa/pb/v3" -) - -func main() { - count := 100000 - // create and start new bar - bar := pb.StartNew(count) - - // start bar from 'default' template - // bar := pb.Default.Start(count) - - // start bar from 'simple' template - // bar := pb.Simple.Start(count) - - // start bar from 'full' template - // bar := pb.Full.Start(count) - - for i := 0; i < count; i++ { - bar.Increment() - time.Sleep(time.Millisecond) - } - bar.Finish() -} - -``` - -Result will be like this: - -``` -> go run test.go -37158 / 100000 [================>_______________________________] 37.16% 1m11s -``` - -## Settings - -```Go -// create bar -bar := pb.New(count) - -// refresh info every second (default 200ms) -bar.SetRefreshRate(time.Second) - -// force set io.Writer, by default it's os.Stderr -bar.SetWriter(os.Stdout) - -// bar will format numbers as bytes (B, KiB, MiB, etc) -bar.Set(pb.Byte, true) - -// bar use SI bytes prefix names (B, kB) instead of IEC (B, KiB) -bar.Set(pb.SIBytesPrefix, true) - -// set custom bar template -bar.SetTemplateString(myTemplate) - -// check for error after template set -if err = bar.Err(); err != nil { - return -} - -// start bar -bar.Start() - -``` - -## Progress bar for IO Operations -```go -package main - -import ( - "crypto/rand" - "io" - "io/ioutil" - - "github.com/cheggaaa/pb/v3" -) - -func main() { - - var limit int64 = 1024 * 1024 * 500 - // we will copy 200 Mb from /dev/rand to /dev/null - reader := io.LimitReader(rand.Reader, limit) - writer := ioutil.Discard - - // start new bar - bar := pb.Full.Start64(limit) - // create proxy reader - barReader := bar.NewProxyReader(reader) - // copy from proxy reader - io.Copy(writer, barReader) - // finish bar - bar.Finish() -} - -``` - -## Custom Progress Bar templates - -Rendering based on builtin text/template package. You can use existing pb's elements or create you own. - -All available elements are described in element.go file. - -#### All in one example: -```go -tmpl := `{{ red "With funcs:" }} {{ bar . "<" "-" (cycle . "↖" "↗" "↘" "↙" ) "." ">"}} {{speed . | rndcolor }} {{percent .}} {{string . "my_green_string" | green}} {{string . "my_blue_string" | blue}}` -// start bar based on our template -bar := pb.ProgressBarTemplate(tmpl).Start64(limit) -// set values for string elements -bar.Set("my_green_string", "green"). - Set("my_blue_string", "blue") -``` \ No newline at end of file diff --git a/vendor/github.com/cheggaaa/pb/README_V1.md b/vendor/github.com/cheggaaa/pb/README_V1.md deleted file mode 100644 index f0689ac3b..000000000 --- a/vendor/github.com/cheggaaa/pb/README_V1.md +++ /dev/null @@ -1,175 +0,0 @@ -# Terminal progress bar for Go - -Simple progress bar for console programs. - -## Installation - -``` -go get github.com/cheggaaa/pb -``` - -## Usage - -```Go -package main - -import ( - "github.com/cheggaaa/pb" - "time" -) - -func main() { - count := 100000 - bar := pb.StartNew(count) - for i := 0; i < count; i++ { - bar.Increment() - time.Sleep(time.Millisecond) - } - bar.FinishPrint("The End!") -} - -``` - -Result will be like this: - -``` -> go run test.go -37158 / 100000 [================>_______________________________] 37.16% 1m11s -``` - -## Customization - -```Go -// create bar -bar := pb.New(count) - -// refresh info every second (default 200ms) -bar.SetRefreshRate(time.Second) - -// show percents (by default already true) -bar.ShowPercent = true - -// show bar (by default already true) -bar.ShowBar = true - -// no counters -bar.ShowCounters = false - -// show "time left" -bar.ShowTimeLeft = true - -// show average speed -bar.ShowSpeed = true - -// sets the width of the progress bar -bar.SetWidth(80) - -// sets the width of the progress bar, but if terminal size smaller will be ignored -bar.SetMaxWidth(80) - -// convert output to readable format (like KB, MB) -bar.SetUnits(pb.U_BYTES) - -// and start -bar.Start() -``` - -## Progress bar for IO Operations - -```go -// create and start bar -bar := pb.New(myDataLen).SetUnits(pb.U_BYTES) -bar.Start() - -// my io.Reader -r := myReader - -// my io.Writer -w := myWriter - -// create proxy reader -reader := bar.NewProxyReader(r) - -// and copy from pb reader -io.Copy(w, reader) - -``` - -```go -// create and start bar -bar := pb.New(myDataLen).SetUnits(pb.U_BYTES) -bar.Start() - -// my io.Reader -r := myReader - -// my io.Writer -w := myWriter - -// create multi writer -writer := io.MultiWriter(w, bar) - -// and copy -io.Copy(writer, r) - -bar.Finish() -``` - -## Custom Progress Bar Look-and-feel - -```go -bar.Format("<.- >") -``` - -## Multiple Progress Bars (experimental and unstable) - -Do not print to terminal while pool is active. - -```go -package main - -import ( - "math/rand" - "sync" - "time" - - "github.com/cheggaaa/pb" -) - -func main() { - // create bars - first := pb.New(200).Prefix("First ") - second := pb.New(200).Prefix("Second ") - third := pb.New(200).Prefix("Third ") - // start pool - pool, err := pb.StartPool(first, second, third) - if err != nil { - panic(err) - } - // update bars - wg := new(sync.WaitGroup) - for _, bar := range []*pb.ProgressBar{first, second, third} { - wg.Add(1) - go func(cb *pb.ProgressBar) { - for n := 0; n < 200; n++ { - cb.Increment() - time.Sleep(time.Millisecond * time.Duration(rand.Intn(100))) - } - cb.Finish() - wg.Done() - }(bar) - } - wg.Wait() - // close pool - pool.Stop() -} -``` - -The result will be as follows: - -``` -$ go run example/multiple.go -First 34 / 200 [=========>---------------------------------------------] 17.00% 00m08s -Second 42 / 200 [===========>------------------------------------------] 21.00% 00m06s -Third 36 / 200 [=========>---------------------------------------------] 18.00% 00m08s -``` diff --git a/vendor/github.com/cheggaaa/pb/format.go b/vendor/github.com/cheggaaa/pb/format.go deleted file mode 100644 index 8bb8a7a1d..000000000 --- a/vendor/github.com/cheggaaa/pb/format.go +++ /dev/null @@ -1,125 +0,0 @@ -package pb - -import ( - "fmt" - "time" -) - -type Units int - -const ( - // U_NO are default units, they represent a simple value and are not formatted at all. - U_NO Units = iota - // U_BYTES units are formatted in a human readable way (B, KiB, MiB, ...) - U_BYTES - // U_BYTES_DEC units are like U_BYTES, but base 10 (B, KB, MB, ...) - U_BYTES_DEC - // U_DURATION units are formatted in a human readable way (3h14m15s) - U_DURATION -) - -const ( - KiB = 1024 - MiB = 1048576 - GiB = 1073741824 - TiB = 1099511627776 - - KB = 1e3 - MB = 1e6 - GB = 1e9 - TB = 1e12 -) - -func Format(i int64) *formatter { - return &formatter{n: i} -} - -type formatter struct { - n int64 - unit Units - width int - perSec bool -} - -func (f *formatter) To(unit Units) *formatter { - f.unit = unit - return f -} - -func (f *formatter) Width(width int) *formatter { - f.width = width - return f -} - -func (f *formatter) PerSec() *formatter { - f.perSec = true - return f -} - -func (f *formatter) String() (out string) { - switch f.unit { - case U_BYTES: - out = formatBytes(f.n) - case U_BYTES_DEC: - out = formatBytesDec(f.n) - case U_DURATION: - out = formatDuration(f.n) - default: - out = fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n) - } - if f.perSec { - out += "/s" - } - return -} - -// Convert bytes to human readable string. Like 2 MiB, 64.2 KiB, 52 B -func formatBytes(i int64) (result string) { - switch { - case i >= TiB: - result = fmt.Sprintf("%.02f TiB", float64(i)/TiB) - case i >= GiB: - result = fmt.Sprintf("%.02f GiB", float64(i)/GiB) - case i >= MiB: - result = fmt.Sprintf("%.02f MiB", float64(i)/MiB) - case i >= KiB: - result = fmt.Sprintf("%.02f KiB", float64(i)/KiB) - default: - result = fmt.Sprintf("%d B", i) - } - return -} - -// Convert bytes to base-10 human readable string. Like 2 MB, 64.2 KB, 52 B -func formatBytesDec(i int64) (result string) { - switch { - case i >= TB: - result = fmt.Sprintf("%.02f TB", float64(i)/TB) - case i >= GB: - result = fmt.Sprintf("%.02f GB", float64(i)/GB) - case i >= MB: - result = fmt.Sprintf("%.02f MB", float64(i)/MB) - case i >= KB: - result = fmt.Sprintf("%.02f KB", float64(i)/KB) - default: - result = fmt.Sprintf("%d B", i) - } - return -} - -func formatDuration(n int64) (result string) { - d := time.Duration(n) - if d > time.Hour*24 { - result = fmt.Sprintf("%dd", d/24/time.Hour) - d -= (d / time.Hour / 24) * (time.Hour * 24) - } - if d > time.Hour { - result = fmt.Sprintf("%s%dh", result, d/time.Hour) - d -= d / time.Hour * time.Hour - } - m := d / time.Minute - d -= m * time.Minute - s := d / time.Second - result = fmt.Sprintf("%s%02dm%02ds", result, m, s) - return -} diff --git a/vendor/github.com/cheggaaa/pb/pb.go b/vendor/github.com/cheggaaa/pb/pb.go deleted file mode 100644 index 04dbce698..000000000 --- a/vendor/github.com/cheggaaa/pb/pb.go +++ /dev/null @@ -1,506 +0,0 @@ -// Simple console progress bars -package pb - -import ( - "fmt" - "io" - "math" - "strings" - "sync" - "sync/atomic" - "time" - "unicode/utf8" -) - -// Current version -const Version = "1.0.29" - -const ( - // Default refresh rate - 200ms - DEFAULT_REFRESH_RATE = time.Millisecond * 200 - FORMAT = "[=>-]" -) - -// DEPRECATED -// variables for backward compatibility, from now do not work -// use pb.Format and pb.SetRefreshRate -var ( - DefaultRefreshRate = DEFAULT_REFRESH_RATE - BarStart, BarEnd, Empty, Current, CurrentN string -) - -// Create new progress bar object -func New(total int) *ProgressBar { - return New64(int64(total)) -} - -// Create new progress bar object using int64 as total -func New64(total int64) *ProgressBar { - pb := &ProgressBar{ - Total: total, - RefreshRate: DEFAULT_REFRESH_RATE, - ShowPercent: true, - ShowCounters: true, - ShowBar: true, - ShowTimeLeft: true, - ShowElapsedTime: false, - ShowFinalTime: true, - Units: U_NO, - ManualUpdate: false, - finish: make(chan struct{}), - } - return pb.Format(FORMAT) -} - -// Create new object and start -func StartNew(total int) *ProgressBar { - return New(total).Start() -} - -// Callback for custom output -// For example: -// bar.Callback = func(s string) { -// mySuperPrint(s) -// } -// -type Callback func(out string) - -type ProgressBar struct { - current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278) - previous int64 - - Total int64 - RefreshRate time.Duration - ShowPercent, ShowCounters bool - ShowSpeed, ShowTimeLeft, ShowBar bool - ShowFinalTime, ShowElapsedTime bool - Output io.Writer - Callback Callback - NotPrint bool - Units Units - Width int - ForceWidth bool - ManualUpdate bool - AutoStat bool - - // Default width for the time box. - UnitsWidth int - TimeBoxWidth int - - finishOnce sync.Once //Guards isFinish - finish chan struct{} - isFinish bool - - startTime time.Time - startValue int64 - - changeTime time.Time - - prefix, postfix string - - mu sync.Mutex - lastPrint string - - BarStart string - BarEnd string - Empty string - Current string - CurrentN string - - AlwaysUpdate bool -} - -// Start print -func (pb *ProgressBar) Start() *ProgressBar { - pb.startTime = time.Now() - pb.startValue = atomic.LoadInt64(&pb.current) - if atomic.LoadInt64(&pb.Total) == 0 { - pb.ShowTimeLeft = false - pb.ShowPercent = false - pb.AutoStat = false - } - if !pb.ManualUpdate { - pb.Update() // Initial printing of the bar before running the bar refresher. - go pb.refresher() - } - return pb -} - -// Increment current value -func (pb *ProgressBar) Increment() int { - return pb.Add(1) -} - -// Get current value -func (pb *ProgressBar) Get() int64 { - c := atomic.LoadInt64(&pb.current) - return c -} - -// Set current value -func (pb *ProgressBar) Set(current int) *ProgressBar { - return pb.Set64(int64(current)) -} - -// Set64 sets the current value as int64 -func (pb *ProgressBar) Set64(current int64) *ProgressBar { - atomic.StoreInt64(&pb.current, current) - return pb -} - -// Add to current value -func (pb *ProgressBar) Add(add int) int { - return int(pb.Add64(int64(add))) -} - -func (pb *ProgressBar) Add64(add int64) int64 { - return atomic.AddInt64(&pb.current, add) -} - -// Set prefix string -func (pb *ProgressBar) Prefix(prefix string) *ProgressBar { - pb.mu.Lock() - defer pb.mu.Unlock() - pb.prefix = prefix - return pb -} - -// Set postfix string -func (pb *ProgressBar) Postfix(postfix string) *ProgressBar { - pb.mu.Lock() - defer pb.mu.Unlock() - pb.postfix = postfix - return pb -} - -// Set custom format for bar -// Example: bar.Format("[=>_]") -// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter -func (pb *ProgressBar) Format(format string) *ProgressBar { - var formatEntries []string - if utf8.RuneCountInString(format) == 5 { - formatEntries = strings.Split(format, "") - } else { - formatEntries = strings.Split(format, "\x00") - } - if len(formatEntries) == 5 { - pb.BarStart = formatEntries[0] - pb.BarEnd = formatEntries[4] - pb.Empty = formatEntries[3] - pb.Current = formatEntries[1] - pb.CurrentN = formatEntries[2] - } - return pb -} - -// Set bar refresh rate -func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar { - pb.RefreshRate = rate - return pb -} - -// Set units -// bar.SetUnits(U_NO) - by default -// bar.SetUnits(U_BYTES) - for Mb, Kb, etc -func (pb *ProgressBar) SetUnits(units Units) *ProgressBar { - pb.Units = units - return pb -} - -// Set max width, if width is bigger than terminal width, will be ignored -func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar { - pb.Width = width - pb.ForceWidth = false - return pb -} - -// Set bar width -func (pb *ProgressBar) SetWidth(width int) *ProgressBar { - pb.Width = width - pb.ForceWidth = true - return pb -} - -// End print -func (pb *ProgressBar) Finish() { - //Protect multiple calls - pb.finishOnce.Do(func() { - close(pb.finish) - pb.write(atomic.LoadInt64(&pb.Total), atomic.LoadInt64(&pb.current)) - pb.mu.Lock() - defer pb.mu.Unlock() - switch { - case pb.Output != nil: - fmt.Fprintln(pb.Output) - case !pb.NotPrint: - fmt.Println() - } - pb.isFinish = true - }) -} - -// IsFinished return boolean -func (pb *ProgressBar) IsFinished() bool { - pb.mu.Lock() - defer pb.mu.Unlock() - return pb.isFinish -} - -// End print and write string 'str' -func (pb *ProgressBar) FinishPrint(str string) { - pb.Finish() - if pb.Output != nil { - fmt.Fprintln(pb.Output, str) - } else { - fmt.Println(str) - } -} - -// implement io.Writer -func (pb *ProgressBar) Write(p []byte) (n int, err error) { - n = len(p) - pb.Add(n) - return -} - -// implement io.Reader -func (pb *ProgressBar) Read(p []byte) (n int, err error) { - n = len(p) - pb.Add(n) - return -} - -// Create new proxy reader over bar -// Takes io.Reader or io.ReadCloser -func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { - return &Reader{r, pb} -} - -// Create new proxy writer over bar -// Takes io.Writer or io.WriteCloser -func (pb *ProgressBar) NewProxyWriter(r io.Writer) *Writer { - return &Writer{r, pb} -} - -func (pb *ProgressBar) write(total, current int64) { - pb.mu.Lock() - defer pb.mu.Unlock() - width := pb.GetWidth() - - var percentBox, countersBox, timeLeftBox, timeSpentBox, speedBox, barBox, end, out string - - // percents - if pb.ShowPercent { - var percent float64 - if total > 0 { - percent = float64(current) / (float64(total) / float64(100)) - } else { - percent = float64(current) / float64(100) - } - percentBox = fmt.Sprintf(" %6.02f%%", percent) - } - - // counters - if pb.ShowCounters { - current := Format(current).To(pb.Units).Width(pb.UnitsWidth) - if total > 0 { - totalS := Format(total).To(pb.Units).Width(pb.UnitsWidth) - countersBox = fmt.Sprintf(" %s / %s ", current, totalS) - } else { - countersBox = fmt.Sprintf(" %s / ? ", current) - } - } - - // time left - currentFromStart := current - pb.startValue - fromStart := time.Now().Sub(pb.startTime) - lastChangeTime := pb.changeTime - fromChange := lastChangeTime.Sub(pb.startTime) - - if pb.ShowElapsedTime { - timeSpentBox = fmt.Sprintf(" %s ", (fromStart/time.Second)*time.Second) - } - - select { - case <-pb.finish: - if pb.ShowFinalTime { - var left time.Duration - left = (fromStart / time.Second) * time.Second - timeLeftBox = fmt.Sprintf(" %s", left.String()) - } - default: - if pb.ShowTimeLeft && currentFromStart > 0 { - perEntry := fromChange / time.Duration(currentFromStart) - var left time.Duration - if total > 0 { - left = time.Duration(total-current) * perEntry - left -= time.Since(lastChangeTime) - left = (left / time.Second) * time.Second - } - if left > 0 { - timeLeft := Format(int64(left)).To(U_DURATION).String() - timeLeftBox = fmt.Sprintf(" %s", timeLeft) - } - } - } - - if len(timeLeftBox) < pb.TimeBoxWidth { - timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox) - } - - // speed - if pb.ShowSpeed && currentFromStart > 0 { - fromStart := time.Now().Sub(pb.startTime) - speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second)) - speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String() - } - - barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeSpentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix) - // bar - if pb.ShowBar { - size := width - barWidth - if size > 0 { - if total > 0 { - curSize := int(math.Ceil((float64(current) / float64(total)) * float64(size))) - emptySize := size - curSize - barBox = pb.BarStart - if emptySize < 0 { - emptySize = 0 - } - if curSize > size { - curSize = size - } - - cursorLen := escapeAwareRuneCountInString(pb.Current) - if emptySize <= 0 { - barBox += strings.Repeat(pb.Current, curSize/cursorLen) - } else if curSize > 0 { - cursorEndLen := escapeAwareRuneCountInString(pb.CurrentN) - cursorRepetitions := (curSize - cursorEndLen) / cursorLen - barBox += strings.Repeat(pb.Current, cursorRepetitions) - barBox += pb.CurrentN - } - - emptyLen := escapeAwareRuneCountInString(pb.Empty) - barBox += strings.Repeat(pb.Empty, emptySize/emptyLen) - barBox += pb.BarEnd - } else { - pos := size - int(current)%int(size) - barBox = pb.BarStart - if pos-1 > 0 { - barBox += strings.Repeat(pb.Empty, pos-1) - } - barBox += pb.Current - if size-pos-1 > 0 { - barBox += strings.Repeat(pb.Empty, size-pos-1) - } - barBox += pb.BarEnd - } - } - } - - // check len - out = pb.prefix + timeSpentBox + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix - - if cl := escapeAwareRuneCountInString(out); cl < width { - end = strings.Repeat(" ", width-cl) - } - - // and print! - pb.lastPrint = out + end - isFinish := pb.isFinish - - switch { - case isFinish: - return - case pb.Output != nil: - fmt.Fprint(pb.Output, "\r"+out+end) - case pb.Callback != nil: - pb.Callback(out + end) - case !pb.NotPrint: - fmt.Print("\r" + out + end) - } -} - -// GetTerminalWidth - returns terminal width for all platforms. -func GetTerminalWidth() (int, error) { - return terminalWidth() -} - -func (pb *ProgressBar) GetWidth() int { - if pb.ForceWidth { - return pb.Width - } - - width := pb.Width - termWidth, _ := terminalWidth() - if width == 0 || termWidth <= width { - width = termWidth - } - - return width -} - -// Write the current state of the progressbar -func (pb *ProgressBar) Update() { - c := atomic.LoadInt64(&pb.current) - p := atomic.LoadInt64(&pb.previous) - t := atomic.LoadInt64(&pb.Total) - if p != c { - pb.mu.Lock() - pb.changeTime = time.Now() - pb.mu.Unlock() - atomic.StoreInt64(&pb.previous, c) - } - pb.write(t, c) - if pb.AutoStat { - if c == 0 { - pb.startTime = time.Now() - pb.startValue = 0 - } else if c >= t && pb.isFinish != true { - pb.Finish() - } - } -} - -// String return the last bar print -func (pb *ProgressBar) String() string { - pb.mu.Lock() - defer pb.mu.Unlock() - return pb.lastPrint -} - -// SetTotal atomically sets new total count -func (pb *ProgressBar) SetTotal(total int) *ProgressBar { - return pb.SetTotal64(int64(total)) -} - -// SetTotal64 atomically sets new total count -func (pb *ProgressBar) SetTotal64(total int64) *ProgressBar { - atomic.StoreInt64(&pb.Total, total) - return pb -} - -// Reset bar and set new total count -// Does effect only on finished bar -func (pb *ProgressBar) Reset(total int) *ProgressBar { - pb.mu.Lock() - defer pb.mu.Unlock() - if pb.isFinish { - pb.SetTotal(total).Set(0) - atomic.StoreInt64(&pb.previous, 0) - } - return pb -} - -// Internal loop for refreshing the progressbar -func (pb *ProgressBar) refresher() { - for { - select { - case <-pb.finish: - return - case <-time.After(pb.RefreshRate): - pb.Update() - } - } -} diff --git a/vendor/github.com/cheggaaa/pb/pb_appengine.go b/vendor/github.com/cheggaaa/pb/pb_appengine.go deleted file mode 100644 index 17168f39a..000000000 --- a/vendor/github.com/cheggaaa/pb/pb_appengine.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build appengine js - -package pb - -import "errors" - -// terminalWidth returns width of the terminal, which is not supported -// and should always failed on appengine classic which is a sandboxed PaaS. -func terminalWidth() (int, error) { - return 0, errors.New("Not supported") -} diff --git a/vendor/github.com/cheggaaa/pb/pb_plan9.go b/vendor/github.com/cheggaaa/pb/pb_plan9.go deleted file mode 100644 index 32e3b989d..000000000 --- a/vendor/github.com/cheggaaa/pb/pb_plan9.go +++ /dev/null @@ -1,70 +0,0 @@ -package pb - -import ( - "errors" - "os" - "os/signal" - "sync" - "syscall" -) - -var ErrPoolWasStarted = errors.New("Bar pool was started") - -var ( - echoLockMutex sync.Mutex - consctl *os.File -) - -// terminalWidth returns width of the terminal. -func terminalWidth() (int, error) { - return 0, errors.New("Not Supported") -} - -func lockEcho() (shutdownCh chan struct{}, err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - - if consctl != nil { - return nil, ErrPoolWasStarted - } - consctl, err = os.OpenFile("/dev/consctl", os.O_WRONLY, 0) - if err != nil { - return nil, err - } - _, err = consctl.WriteString("rawon") - if err != nil { - consctl.Close() - consctl = nil - return nil, err - } - shutdownCh = make(chan struct{}) - go catchTerminate(shutdownCh) - return -} - -func unlockEcho() error { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - - if consctl == nil { - return nil - } - if err := consctl.Close(); err != nil { - return err - } - consctl = nil - return nil -} - -// listen exit signals and restore terminal state -func catchTerminate(shutdownCh chan struct{}) { - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGKILL) - defer signal.Stop(sig) - select { - case <-shutdownCh: - unlockEcho() - case <-sig: - unlockEcho() - } -} diff --git a/vendor/github.com/cheggaaa/pb/pb_win.go b/vendor/github.com/cheggaaa/pb/pb_win.go deleted file mode 100644 index 9595e8236..000000000 --- a/vendor/github.com/cheggaaa/pb/pb_win.go +++ /dev/null @@ -1,143 +0,0 @@ -// +build windows - -package pb - -import ( - "errors" - "fmt" - "os" - "sync" - "syscall" - "unsafe" -) - -var tty = os.Stdin - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - - // GetConsoleScreenBufferInfo retrieves information about the - // specified console screen buffer. - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx - procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") - - // GetConsoleMode retrieves the current input mode of a console's - // input buffer or the current output mode of a console screen buffer. - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx - getConsoleMode = kernel32.NewProc("GetConsoleMode") - - // SetConsoleMode sets the input mode of a console's input buffer - // or the output mode of a console screen buffer. - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx - setConsoleMode = kernel32.NewProc("SetConsoleMode") - - // SetConsoleCursorPosition sets the cursor position in the - // specified console screen buffer. - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx - setConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") -) - -type ( - // Defines the coordinates of the upper left and lower right corners - // of a rectangle. - // See - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms686311(v=vs.85).aspx - smallRect struct { - Left, Top, Right, Bottom int16 - } - - // Defines the coordinates of a character cell in a console screen - // buffer. The origin of the coordinate system (0,0) is at the top, left cell - // of the buffer. - // See - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx - coordinates struct { - X, Y int16 - } - - word int16 - - // Contains information about a console screen buffer. - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx - consoleScreenBufferInfo struct { - dwSize coordinates - dwCursorPosition coordinates - wAttributes word - srWindow smallRect - dwMaximumWindowSize coordinates - } -) - -// terminalWidth returns width of the terminal. -func terminalWidth() (width int, err error) { - var info consoleScreenBufferInfo - _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0) - if e != 0 { - return 0, error(e) - } - return int(info.dwSize.X) - 1, nil -} - -func getCursorPos() (pos coordinates, err error) { - var info consoleScreenBufferInfo - _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0) - if e != 0 { - return info.dwCursorPosition, error(e) - } - return info.dwCursorPosition, nil -} - -func setCursorPos(pos coordinates) error { - _, _, e := syscall.Syscall(setConsoleCursorPosition.Addr(), 2, uintptr(syscall.Stdout), uintptr(uint32(uint16(pos.Y))<<16|uint32(uint16(pos.X))), 0) - if e != 0 { - return error(e) - } - return nil -} - -var ErrPoolWasStarted = errors.New("Bar pool was started") - -var echoLocked bool -var echoLockMutex sync.Mutex - -var oldState word - -func lockEcho() (shutdownCh chan struct{}, err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if echoLocked { - err = ErrPoolWasStarted - return - } - echoLocked = true - - if _, _, e := syscall.Syscall(getConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&oldState)), 0); e != 0 { - err = fmt.Errorf("Can't get terminal settings: %v", e) - return - } - - newState := oldState - const ENABLE_ECHO_INPUT = 0x0004 - const ENABLE_LINE_INPUT = 0x0002 - newState = newState & (^(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)) - if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(newState), 0); e != 0 { - err = fmt.Errorf("Can't set terminal settings: %v", e) - return - } - - shutdownCh = make(chan struct{}) - return -} - -func unlockEcho() (err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if !echoLocked { - return - } - echoLocked = false - if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(oldState), 0); e != 0 { - err = fmt.Errorf("Can't set terminal settings") - } - return -} diff --git a/vendor/github.com/cheggaaa/pb/pb_x.go b/vendor/github.com/cheggaaa/pb/pb_x.go deleted file mode 100644 index af4251760..000000000 --- a/vendor/github.com/cheggaaa/pb/pb_x.go +++ /dev/null @@ -1,118 +0,0 @@ -// +build linux darwin freebsd netbsd openbsd solaris dragonfly -// +build !appengine !js - -package pb - -import ( - "errors" - "fmt" - "os" - "os/signal" - "sync" - "syscall" - - "golang.org/x/sys/unix" -) - -var ErrPoolWasStarted = errors.New("Bar pool was started") - -var ( - echoLockMutex sync.Mutex - origTermStatePtr *unix.Termios - tty *os.File - istty bool -) - -func init() { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - - var err error - tty, err = os.Open("/dev/tty") - istty = true - if err != nil { - tty = os.Stdin - istty = false - } -} - -// terminalWidth returns width of the terminal. -func terminalWidth() (int, error) { - if !istty { - return 0, errors.New("Not Supported") - } - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - - fd := int(tty.Fd()) - - ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) - if err != nil { - return 0, err - } - - return int(ws.Col), nil -} - -func lockEcho() (shutdownCh chan struct{}, err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if istty { - if origTermStatePtr != nil { - return shutdownCh, ErrPoolWasStarted - } - - fd := int(tty.Fd()) - - origTermStatePtr, err = unix.IoctlGetTermios(fd, ioctlReadTermios) - if err != nil { - return nil, fmt.Errorf("Can't get terminal settings: %v", err) - } - - oldTermios := *origTermStatePtr - newTermios := oldTermios - newTermios.Lflag &^= syscall.ECHO - newTermios.Lflag |= syscall.ICANON | syscall.ISIG - newTermios.Iflag |= syscall.ICRNL - if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newTermios); err != nil { - return nil, fmt.Errorf("Can't set terminal settings: %v", err) - } - - } - shutdownCh = make(chan struct{}) - go catchTerminate(shutdownCh) - return -} - -func unlockEcho() error { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if istty { - if origTermStatePtr == nil { - return nil - } - - fd := int(tty.Fd()) - - if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, origTermStatePtr); err != nil { - return fmt.Errorf("Can't set terminal settings: %v", err) - } - - } - origTermStatePtr = nil - - return nil -} - -// listen exit signals and restore terminal state -func catchTerminate(shutdownCh chan struct{}) { - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL) - defer signal.Stop(sig) - select { - case <-shutdownCh: - unlockEcho() - case <-sig: - unlockEcho() - } -} diff --git a/vendor/github.com/cheggaaa/pb/pool.go b/vendor/github.com/cheggaaa/pb/pool.go deleted file mode 100644 index 861d787b0..000000000 --- a/vendor/github.com/cheggaaa/pb/pool.go +++ /dev/null @@ -1,104 +0,0 @@ -// +build linux darwin freebsd netbsd openbsd solaris dragonfly windows plan9 - -package pb - -import ( - "io" - "sync" - "time" -) - -// Create and start new pool with given bars -// You need call pool.Stop() after work -func StartPool(pbs ...*ProgressBar) (pool *Pool, err error) { - pool = new(Pool) - if err = pool.Start(); err != nil { - return - } - pool.Add(pbs...) - return -} - -// NewPool initialises a pool with progress bars, but -// doesn't start it. You need to call Start manually -func NewPool(pbs ...*ProgressBar) (pool *Pool) { - pool = new(Pool) - pool.Add(pbs...) - return -} - -type Pool struct { - Output io.Writer - RefreshRate time.Duration - bars []*ProgressBar - lastBarsCount int - shutdownCh chan struct{} - workerCh chan struct{} - m sync.Mutex - finishOnce sync.Once -} - -// Add progress bars. -func (p *Pool) Add(pbs ...*ProgressBar) { - p.m.Lock() - defer p.m.Unlock() - for _, bar := range pbs { - bar.ManualUpdate = true - bar.NotPrint = true - bar.Start() - p.bars = append(p.bars, bar) - } -} - -func (p *Pool) Start() (err error) { - p.RefreshRate = DefaultRefreshRate - p.shutdownCh, err = lockEcho() - if err != nil { - return - } - p.workerCh = make(chan struct{}) - go p.writer() - return -} - -func (p *Pool) writer() { - var first = true - defer func() { - if first == false { - p.print(false) - } else { - p.print(true) - p.print(false) - } - close(p.workerCh) - }() - - for { - select { - case <-time.After(p.RefreshRate): - if p.print(first) { - p.print(false) - return - } - first = false - case <-p.shutdownCh: - return - } - } -} - -// Restore terminal state and close pool -func (p *Pool) Stop() error { - p.finishOnce.Do(func() { - if p.shutdownCh != nil { - close(p.shutdownCh) - } - }) - - // Wait for the worker to complete - select { - case <-p.workerCh: - } - - return unlockEcho() -} diff --git a/vendor/github.com/cheggaaa/pb/pool_win.go b/vendor/github.com/cheggaaa/pb/pool_win.go deleted file mode 100644 index 63598d378..000000000 --- a/vendor/github.com/cheggaaa/pb/pool_win.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build windows - -package pb - -import ( - "fmt" - "log" -) - -func (p *Pool) print(first bool) bool { - p.m.Lock() - defer p.m.Unlock() - var out string - if !first { - coords, err := getCursorPos() - if err != nil { - log.Panic(err) - } - coords.Y -= int16(p.lastBarsCount) - if coords.Y < 0 { - coords.Y = 0 - } - coords.X = 0 - - err = setCursorPos(coords) - if err != nil { - log.Panic(err) - } - } - isFinished := true - for _, bar := range p.bars { - if !bar.IsFinished() { - isFinished = false - } - bar.Update() - out += fmt.Sprintf("\r%s\n", bar.String()) - } - if p.Output != nil { - fmt.Fprint(p.Output, out) - } else { - fmt.Print(out) - } - p.lastBarsCount = len(p.bars) - return isFinished -} diff --git a/vendor/github.com/cheggaaa/pb/pool_x.go b/vendor/github.com/cheggaaa/pb/pool_x.go deleted file mode 100644 index 3d4ed94c1..000000000 --- a/vendor/github.com/cheggaaa/pb/pool_x.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build linux darwin freebsd netbsd openbsd solaris dragonfly plan9 - -package pb - -import "fmt" - -func (p *Pool) print(first bool) bool { - p.m.Lock() - defer p.m.Unlock() - var out string - if !first { - out = fmt.Sprintf("\033[%dA", p.lastBarsCount) - } - isFinished := true - for _, bar := range p.bars { - if !bar.IsFinished() { - isFinished = false - } - bar.Update() - out += fmt.Sprintf("\r%s\n", bar.String()) - } - if p.Output != nil { - fmt.Fprint(p.Output, out) - } else { - fmt.Print(out) - } - p.lastBarsCount = len(p.bars) - return isFinished -} diff --git a/vendor/github.com/cheggaaa/pb/reader.go b/vendor/github.com/cheggaaa/pb/reader.go deleted file mode 100644 index 9562e949f..000000000 --- a/vendor/github.com/cheggaaa/pb/reader.go +++ /dev/null @@ -1,26 +0,0 @@ -package pb - -import ( - "io" -) - -// It's proxy reader, implement io.Reader -type Reader struct { - io.Reader - bar *ProgressBar -} - -func (r *Reader) Read(p []byte) (n int, err error) { - n, err = r.Reader.Read(p) - r.bar.Add(n) - return -} - -// Close the reader when it implements io.Closer -func (r *Reader) Close() (err error) { - r.bar.Finish() - if closer, ok := r.Reader.(io.Closer); ok { - return closer.Close() - } - return -} diff --git a/vendor/github.com/cheggaaa/pb/runecount.go b/vendor/github.com/cheggaaa/pb/runecount.go deleted file mode 100644 index c617c55ec..000000000 --- a/vendor/github.com/cheggaaa/pb/runecount.go +++ /dev/null @@ -1,17 +0,0 @@ -package pb - -import ( - "github.com/mattn/go-runewidth" - "regexp" -) - -// Finds the control character sequences (like colors) -var ctrlFinder = regexp.MustCompile("\x1b\x5b[0-9]+\x6d") - -func escapeAwareRuneCountInString(s string) int { - n := runewidth.StringWidth(s) - for _, sm := range ctrlFinder.FindAllString(s, -1) { - n -= runewidth.StringWidth(sm) - } - return n -} diff --git a/vendor/github.com/cheggaaa/pb/termios_bsd.go b/vendor/github.com/cheggaaa/pb/termios_bsd.go deleted file mode 100644 index 517ea8ed7..000000000 --- a/vendor/github.com/cheggaaa/pb/termios_bsd.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build darwin freebsd netbsd openbsd dragonfly -// +build !appengine - -package pb - -import "syscall" - -const ioctlReadTermios = syscall.TIOCGETA -const ioctlWriteTermios = syscall.TIOCSETA diff --git a/vendor/github.com/cheggaaa/pb/termios_sysv.go b/vendor/github.com/cheggaaa/pb/termios_sysv.go deleted file mode 100644 index b10f61859..000000000 --- a/vendor/github.com/cheggaaa/pb/termios_sysv.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build linux solaris -// +build !appengine - -package pb - -import "golang.org/x/sys/unix" - -const ioctlReadTermios = unix.TCGETS -const ioctlWriteTermios = unix.TCSETS diff --git a/vendor/github.com/cheggaaa/pb/writer.go b/vendor/github.com/cheggaaa/pb/writer.go deleted file mode 100644 index 9451ec5a3..000000000 --- a/vendor/github.com/cheggaaa/pb/writer.go +++ /dev/null @@ -1,26 +0,0 @@ -package pb - -import ( - "io" -) - -// It's proxy Writer, implement io.Writer -type Writer struct { - io.Writer - bar *ProgressBar -} - -func (r *Writer) Write(p []byte) (n int, err error) { - n, err = r.Writer.Write(p) - r.bar.Add(n) - return -} - -// Close the reader when it implements io.Closer -func (r *Writer) Close() (err error) { - r.bar.Finish() - if closer, ok := r.Writer.(io.Closer); ok { - return closer.Close() - } - return -} diff --git a/vendor/github.com/evertras/bubble-table/table/border.go b/vendor/github.com/evertras/bubble-table/table/border.go index 756d4c814..68feefd4f 100644 --- a/vendor/github.com/evertras/bubble-table/table/border.go +++ b/vendor/github.com/evertras/bubble-table/table/border.go @@ -1,6 +1,11 @@ package table -import "github.com/charmbracelet/lipgloss" +import ( + "image/color" + "strings" + + "charm.land/lipgloss/v2" +) // Border defines the borders in and around the table. type Border struct { @@ -48,6 +53,10 @@ type Border struct { // Style for the footer styleFooter lipgloss.Style + + // foreground is the colour applied to plain-string border lines (top, + // separator, bottom). nil means use the terminal default. + foreground color.Color } var ( @@ -105,21 +114,95 @@ func (b *Border) generateStyles() { b.generateSingleRowStyles() b.generateSingleCellStyle() - // The footer is a single cell with the top taken off... usually. We can - // re-enable the top if needed this way for certain format configurations. - b.styleFooter = b.styleSingleCell.Copy(). + // The footer is a full-width single cell with a bottom border. It needs + // the actual border characters for all edges because renderFooter may + // optionally add a top border (when there are no data rows above). + b.styleFooter = lipgloss.NewStyle(). + BorderStyle(lipgloss.Border{ + Left: b.Left, + Right: b.Right, + + Top: b.Top, + TopLeft: b.TopLeft, + TopRight: b.TopRight, + + Bottom: b.Bottom, + BottomLeft: b.BottomLeft, + BottomRight: b.BottomRight, + }). Align(lipgloss.Right). BorderBottom(true). BorderRight(true). BorderLeft(true) } +// buildBorderLine constructs a horizontal border line for the given column +// widths. leftChar is the leftmost character, midChar fills each column, +// junctionChar separates columns, and rightChar closes the line. +// If b.foreground is set the line is wrapped in a lipgloss Foreground style +// so that it renders in the same colour as the cell border characters. +func (b *Border) buildBorderLine(columnWidths []int, leftChar, midChar, junctionChar, rightChar string) string { + var buf strings.Builder + + buf.WriteString(leftChar) + + for i, w := range columnWidths { + buf.WriteString(strings.Repeat(midChar, w)) + + if i < len(columnWidths)-1 { + buf.WriteString(junctionChar) + } + } + + buf.WriteString(rightChar) + + line := buf.String() + + if b.foreground != nil { + line = lipgloss.NewStyle().Foreground(b.foreground).Render(line) + } + + return line +} + +// buildTopBorderLine builds the top border line for a multi-column table. +func (b *Border) buildTopBorderLine(columnWidths []int) string { + return b.buildBorderLine(columnWidths, b.TopLeft, b.Top, b.TopJunction, b.TopRight) +} + +// buildSeparatorLine builds the separator line between the header and first +// data row for a multi-column table. +func (b *Border) buildSeparatorLine(columnWidths []int) string { + return b.buildBorderLine(columnWidths, b.LeftJunction, b.Bottom, b.InnerJunction, b.RightJunction) +} + +// buildInnerSeparatorLine builds a separator line with no outer junction +// characters, used when the outer border is hidden. +func (b *Border) buildInnerSeparatorLine(columnWidths []int) string { + return b.buildBorderLine(columnWidths, "", b.Bottom, b.InnerJunction, "") +} + +// buildBottomBorderLine builds the bottom border line for a multi-column +// table. When hasFooter is true the corners are replaced with junctions so +// the footer can attach below. +func (b *Border) buildBottomBorderLine(columnWidths []int, hasFooter bool) string { + left := b.BottomLeft + right := b.BottomRight + + if hasFooter { + left = b.LeftJunction + right = b.RightJunction + } + + return b.buildBorderLine(columnWidths, left, b.Bottom, b.BottomJunction, right) +} + func (b *Border) styleLeftWithFooter(original lipgloss.Style) lipgloss.Style { border := original.GetBorderStyle() border.BottomLeft = b.LeftJunction - return original.Copy().BorderStyle(border) + return original.BorderStyle(border) } func (b *Border) styleRightWithFooter(original lipgloss.Style) lipgloss.Style { @@ -127,7 +210,7 @@ func (b *Border) styleRightWithFooter(original lipgloss.Style) lipgloss.Style { border.BottomRight = b.RightJunction - return original.Copy().BorderStyle(border) + return original.BorderStyle(border) } func (b *Border) styleBothWithFooter(original lipgloss.Style) lipgloss.Style { @@ -136,47 +219,32 @@ func (b *Border) styleBothWithFooter(original lipgloss.Style) lipgloss.Style { border.BottomLeft = b.LeftJunction border.BottomRight = b.RightJunction - return original.Copy().BorderStyle(border) + return original.BorderStyle(border) } -// This function is long, but it's just repetitive... -// -//nolint:funlen func (b *Border) generateMultiStyles() { + // All cell styles have ONLY left and/or right borders. + // Top, bottom, and separator lines are rendered as plain strings by + // the header/row rendering functions, not by these styles. + b.styleMultiTopLeft = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ - TopLeft: b.TopLeft, - Top: b.Top, - TopRight: b.TopJunction, - Right: b.InnerDivider, - BottomRight: b.InnerJunction, - Bottom: b.Bottom, - BottomLeft: b.LeftJunction, - Left: b.Left, + Left: b.Left, + Right: b.InnerDivider, }, - ) + ).BorderLeft(true).BorderRight(true) b.styleMultiTop = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ - Top: b.Top, - Right: b.InnerDivider, - Bottom: b.Bottom, - - TopRight: b.TopJunction, - BottomRight: b.InnerJunction, + Right: b.InnerDivider, }, - ).BorderTop(true).BorderBottom(true).BorderRight(true) + ).BorderRight(true) b.styleMultiTopRight = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ - Top: b.Top, - Right: b.Right, - Bottom: b.Bottom, - - TopRight: b.TopRight, - BottomRight: b.RightJunction, + Right: b.Right, }, - ).BorderTop(true).BorderBottom(true).BorderRight(true) + ).BorderRight(true) b.styleMultiLeft = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ @@ -199,48 +267,34 @@ func (b *Border) generateMultiStyles() { b.styleMultiBottomLeft = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ - Left: b.Left, - Right: b.InnerDivider, - Bottom: b.Bottom, - - BottomLeft: b.BottomLeft, - BottomRight: b.BottomJunction, + Left: b.Left, + Right: b.InnerDivider, }, - ).BorderLeft(true).BorderBottom(true).BorderRight(true) + ).BorderLeft(true).BorderRight(true) b.styleMultiBottom = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ - Right: b.InnerDivider, - Bottom: b.Bottom, - - BottomRight: b.BottomJunction, + Right: b.InnerDivider, }, - ).BorderBottom(true).BorderRight(true) + ).BorderRight(true) b.styleMultiBottomRight = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ - Right: b.Right, - Bottom: b.Bottom, - - BottomRight: b.BottomRight, + Right: b.Right, }, - ).BorderBottom(true).BorderRight(true) + ).BorderRight(true) } func (b *Border) generateSingleColumnStyles() { + // Cell styles have only left/right borders; top/bottom border lines are + // built as plain strings by the rendering functions. + b.styleSingleColumnTop = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ - Top: b.Top, - Left: b.Left, - Right: b.Right, - Bottom: b.Bottom, - - TopLeft: b.TopLeft, - TopRight: b.TopRight, - BottomLeft: b.LeftJunction, - BottomRight: b.RightJunction, + Left: b.Left, + Right: b.Right, }, - ) + ).BorderLeft(true).BorderRight(true) b.styleSingleColumnInner = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ @@ -251,83 +305,62 @@ func (b *Border) generateSingleColumnStyles() { b.styleSingleColumnBottom = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ - Left: b.Left, - Right: b.Right, - Bottom: b.Bottom, - - BottomLeft: b.BottomLeft, - BottomRight: b.BottomRight, + Left: b.Left, + Right: b.Right, }, - ).BorderRight(true).BorderLeft(true).BorderBottom(true) + ).BorderRight(true).BorderLeft(true) } func (b *Border) generateSingleRowStyles() { + // Cell styles have only left/right borders; top/bottom border lines are + // built as plain strings by the rendering functions. + b.styleSingleRowLeft = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ - Top: b.Top, - Left: b.Left, - Right: b.InnerDivider, - Bottom: b.Bottom, - - BottomLeft: b.BottomLeft, - BottomRight: b.BottomJunction, - TopRight: b.TopJunction, - TopLeft: b.TopLeft, + Left: b.Left, + Right: b.InnerDivider, }, - ) + ).BorderLeft(true).BorderRight(true) b.styleSingleRowInner = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ - Top: b.Top, - Right: b.InnerDivider, - Bottom: b.Bottom, - - BottomRight: b.BottomJunction, - TopRight: b.TopJunction, + Right: b.InnerDivider, }, - ).BorderTop(true).BorderBottom(true).BorderRight(true) + ).BorderRight(true) b.styleSingleRowRight = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ - Top: b.Top, - Right: b.Right, - Bottom: b.Bottom, - - BottomRight: b.BottomRight, - TopRight: b.TopRight, + Right: b.Right, }, - ).BorderTop(true).BorderBottom(true).BorderRight(true) + ).BorderRight(true) } func (b *Border) generateSingleCellStyle() { + // Cell style has only left/right borders; top/bottom border lines are + // built as plain strings by the rendering functions. b.styleSingleCell = lipgloss.NewStyle().BorderStyle( lipgloss.Border{ - Top: b.Top, - Left: b.Left, - Right: b.Right, - Bottom: b.Bottom, - - BottomLeft: b.BottomLeft, - BottomRight: b.BottomRight, - TopRight: b.TopRight, - TopLeft: b.TopLeft, + Left: b.Left, + Right: b.Right, }, - ) + ).BorderLeft(true).BorderRight(true) } // BorderDefault uses the basic square border, useful to reset the border if // it was changed somehow. func (m Model) BorderDefault() Model { - // Already generated styles + fg := m.border.foreground m.border = borderDefault + m.border.foreground = fg return m } // BorderRounded uses a thin, rounded border. func (m Model) BorderRounded() Model { - // Already generated styles + fg := m.border.foreground m.border = borderRounded + m.border.foreground = fg return m } @@ -348,9 +381,9 @@ type borderStyleRow struct { } func (b *borderStyleRow) inherit(s lipgloss.Style) { - b.left = b.left.Copy().Inherit(s) - b.inner = b.inner.Copy().Inherit(s) - b.right = b.right.Copy().Inherit(s) + b.left = b.left.Inherit(s) + b.inner = b.inner.Inherit(s) + b.right = b.right.Inherit(s) } // There's a lot of branches here, but splitting it up further would make it @@ -403,6 +436,19 @@ func (m Model) styleHeaders() borderStyleRow { styles.inherit(m.headerStyle) + // HeaderStyle is for text styling only. Strip any border-side flags that + // may have been inherited: top/bottom border lines are rendered as separate + // plain strings, and inner/right cells never carry a left border (each + // column's left divider is provided by the previous column's right border). + styles.left = styles.left.BorderTop(false).BorderBottom(false) + styles.inner = styles.inner.BorderTop(false).BorderBottom(false).BorderLeft(false) + styles.right = styles.right.BorderTop(false).BorderBottom(false).BorderLeft(false) + + if !m.outerBorder { + styles.left = styles.left.BorderLeft(false) + styles.right = styles.right.BorderRight(false) + } + return styles } @@ -435,5 +481,12 @@ func (m Model) styleRows() (inner borderStyleRow, last borderStyleRow) { } } + if !m.outerBorder { + inner.left = inner.left.BorderLeft(false) + inner.right = inner.right.BorderRight(false) + last.left = last.left.BorderLeft(false) + last.right = last.right.BorderRight(false) + } + return inner, last } diff --git a/vendor/github.com/evertras/bubble-table/table/calc.go b/vendor/github.com/evertras/bubble-table/table/calc.go index 3f0db046b..406763eb3 100644 --- a/vendor/github.com/evertras/bubble-table/table/calc.go +++ b/vendor/github.com/evertras/bubble-table/table/calc.go @@ -1,21 +1,5 @@ package table -func min(x, y int) int { - if x < y { - return x - } - - return y -} - -func max(x, y int) int { - if x > y { - return x - } - - return y -} - // These var names are fine for this little function // //nolint:varnamelen diff --git a/vendor/github.com/evertras/bubble-table/table/cell.go b/vendor/github.com/evertras/bubble-table/table/cell.go index 55156e76f..729acb2ed 100644 --- a/vendor/github.com/evertras/bubble-table/table/cell.go +++ b/vendor/github.com/evertras/bubble-table/table/cell.go @@ -1,6 +1,6 @@ package table -import "github.com/charmbracelet/lipgloss" +import "charm.land/lipgloss/v2" // StyledCell represents a cell in the table that has a particular style applied. // The cell style takes highest precedence and will overwrite more general styles @@ -8,12 +8,53 @@ import "github.com/charmbracelet/lipgloss" // limited to colors, font style, and alignments - spacing style such as margin // will break the table format. type StyledCell struct { - Data interface{} + // Data is the content of the cell. + Data any + + // Style is the specific style to apply. This is ignored if StyleFunc is not nil. Style lipgloss.Style + + // StyleFunc is a function that takes the row/column of the cell and + // returns a lipgloss.Style allowing for dynamic styling based on the cell's + // content or position. Overrides Style if set. + StyleFunc StyledCellFunc +} + +// StyledCellFuncInput is the input to the StyledCellFunc. Sent as a struct +// to allow for future additions without breaking changes. +type StyledCellFuncInput struct { + // Data is the data in the cell. + Data any + + // Column is the column that the cell belongs to. + Column Column + + // Row is the row that the cell belongs to. + Row Row + + // GlobalMetadata is the global table metadata that's been set by WithGlobalMetadata + GlobalMetadata map[string]any } +// StyledCellFunc is a function that takes various information about the cell and +// returns a lipgloss.Style allowing for easier dynamic styling based on the cell's +// content or position. +type StyledCellFunc = func(input StyledCellFuncInput) lipgloss.Style + // NewStyledCell creates an entry that can be set in the row data and show as // styled with the given style. -func NewStyledCell(data interface{}, style lipgloss.Style) StyledCell { - return StyledCell{data, style} +func NewStyledCell(data any, style lipgloss.Style) StyledCell { + return StyledCell{ + Data: data, + Style: style, + } +} + +// NewStyledCellWithStyleFunc creates an entry that can be set in the row data and show as +// styled with the given style function. +func NewStyledCellWithStyleFunc(data any, styleFunc StyledCellFunc) StyledCell { + return StyledCell{ + Data: data, + StyleFunc: styleFunc, + } } diff --git a/vendor/github.com/evertras/bubble-table/table/column.go b/vendor/github.com/evertras/bubble-table/table/column.go index 9a55fddb2..07f444419 100644 --- a/vendor/github.com/evertras/bubble-table/table/column.go +++ b/vendor/github.com/evertras/bubble-table/table/column.go @@ -1,7 +1,7 @@ package table import ( - "github.com/charmbracelet/lipgloss" + "charm.land/lipgloss/v2" ) // Column is a column in the table. @@ -47,7 +47,7 @@ func NewFlexColumn(key, title string, flexFactor int) Column { // WithStyle applies a style to the column as a whole. func (c Column) WithStyle(style lipgloss.Style) Column { - c.style = style.Copy().Width(c.width) + c.style = style.Width(c.width) return c } @@ -64,7 +64,7 @@ func (c Column) WithFiltered(filterable bool) Column { // If not set, the default is "%v" for all data types. Intended mainly for // numeric formatting. // -// Since data is of the interface{} type, make sure that all data in the column +// Since data is of the any type, make sure that all data in the column // is of the expected type or the format may fail. For example, hardcoding '3' // instead of '3.0' and using '%.2f' will fail because '3' is an integer. func (c Column) WithFormatString(fmtString string) Column { diff --git a/vendor/github.com/evertras/bubble-table/table/data.go b/vendor/github.com/evertras/bubble-table/table/data.go index 73d558a04..a0ed46952 100644 --- a/vendor/github.com/evertras/bubble-table/table/data.go +++ b/vendor/github.com/evertras/bubble-table/table/data.go @@ -2,10 +2,22 @@ package table import "time" +func asTime(data any) (time.Time, bool) { + switch val := data.(type) { + case time.Time: + return val, true + + case StyledCell: + return asTime(val.Data) + } + + return time.Time{}, false +} + // This is just a bunch of data type checks, so... no linting here // //nolint:cyclop -func asInt(data interface{}) (int64, bool) { +func asInt(data any) (int64, bool) { switch val := data.(type) { case int: return int64(val), true @@ -23,6 +35,7 @@ func asInt(data interface{}) (int64, bool) { return val, true case uint: + // #nosec: G115 return int64(val), true case uint8: @@ -35,6 +48,7 @@ func asInt(data interface{}) (int64, bool) { return int64(val), true case uint64: + // #nosec: G115 return int64(val), true case time.Duration: @@ -47,7 +61,7 @@ func asInt(data interface{}) (int64, bool) { return 0, false } -func asNumber(data interface{}) (float64, bool) { +func asNumber(data any) (float64, bool) { switch val := data.(type) { case float32: return float64(val), true diff --git a/vendor/github.com/evertras/bubble-table/table/dimensions.go b/vendor/github.com/evertras/bubble-table/table/dimensions.go index 380746160..d5e0623d2 100644 --- a/vendor/github.com/evertras/bubble-table/table/dimensions.go +++ b/vendor/github.com/evertras/bubble-table/table/dimensions.go @@ -1,7 +1,7 @@ package table import ( - "github.com/charmbracelet/lipgloss" + "charm.land/lipgloss/v2" ) func (m *Model) recalculateWidth() { @@ -29,10 +29,12 @@ func updateColumnWidths(cols []Column, totalWidth int) { totalFlexFactor := 0 flexGCD := 0 - for index, col := range cols { + for _, col := range cols { if !col.isFlex() { totalFlexWidth -= col.width - cols[index].style = col.style.Width(col.width) + // Do not set Width on col.style here; the rendering functions apply + // Width(column.width + borderOverhead) at render time to correctly + // account for lipgloss v2's total-outer-width semantics. } else { totalFlexFactor += col.flexFactor flexGCD = gcd(flexGCD, col.flexFactor) @@ -71,8 +73,8 @@ func updateColumnWidths(cols []Column, totalWidth int) { cols[index].width = width - // Take borders into account for the actual style - cols[index].style = cols[index].style.Width(width) + // Do not set Width on the style; rendering functions apply + // Width(column.width + borderOverhead) at render time. } } @@ -92,6 +94,71 @@ func (m *Model) recalculateHeight() { m.metaHeight = headerHeight + footerHeight } +// ensurePageMap rebuilds the page-start index cache if it is stale. +// For targetHeight mode it uses a two-pass approach: first calculate without a +// footer to determine whether multiple pages are needed, then if they are, +// recalculate with the footer included in the height budget and rebuild. +func (m *Model) ensurePageMap() { + if m.pageStartIndices == nil { + m.recalculateHeight() + m.buildPageStartIndices() + + if m.targetHeight != 0 && len(m.pageStartIndices) > 1 { + // Footer is now active (multi-page); redo with footer in budget. + m.recalculateHeight() + m.buildPageStartIndices() + } + } +} + +// buildPageStartIndices computes which row index each page starts on and stores +// the result in m.pageStartIndices. Must be called after recalculateHeight so +// that m.metaHeight is up to date. +func (m *Model) buildPageStartIndices() { + rows := m.GetVisibleRows() + + if len(rows) == 0 { + m.pageStartIndices = []int{} + + return + } + + // metaHeight covers the header (including top border) and footer. + // The bottom border is appended to the last data row by assembleRowOutput, + // so subtract 1 more to account for it. + availableLines := m.targetHeight - m.metaHeight - 1 + + if availableLines < 1 { + availableLines = 1 + } + + pageStarts := []int{0} + currentPageLines := 0 + + for rowIdx, row := range rows { + rowLines := m.rowLineCount(row) + + var linesNeeded int + if currentPageLines == 0 { + linesNeeded = rowLines + } else if m.rowSeparator { + linesNeeded = rowLines + 1 // separator between rows + } else { + linesNeeded = rowLines + } + + if currentPageLines+linesNeeded > availableLines && currentPageLines > 0 { + // Row doesn't fit on the current page; start a new one. + pageStarts = append(pageStarts, rowIdx) + currentPageLines = m.rowLineCount(row) + } else { + currentPageLines += linesNeeded + } + } + + m.pageStartIndices = pageStarts +} + func (m *Model) calculatePadding(numRows int) int { if m.minimumHeight == 0 { return 0 diff --git a/vendor/github.com/evertras/bubble-table/table/events.go b/vendor/github.com/evertras/bubble-table/table/events.go index 4b5e2b09d..b519623dc 100644 --- a/vendor/github.com/evertras/bubble-table/table/events.go +++ b/vendor/github.com/evertras/bubble-table/table/events.go @@ -3,7 +3,7 @@ package table // UserEvent is some state change that has occurred due to user input. These will // ONLY be generated when a user has interacted directly with the table. These // will NOT be generated when code programmatically changes values in the table. -type UserEvent interface{} +type UserEvent any func (m *Model) appendUserEvent(e UserEvent) { m.lastUpdateUserEvents = append(m.lastUpdateUserEvents, e) diff --git a/vendor/github.com/evertras/bubble-table/table/filter.go b/vendor/github.com/evertras/bubble-table/table/filter.go index 0ba233b86..adcb15481 100644 --- a/vendor/github.com/evertras/bubble-table/table/filter.go +++ b/vendor/github.com/evertras/bubble-table/table/filter.go @@ -5,6 +5,26 @@ import ( "strings" ) +// FilterFuncInput is the input to a FilterFunc. It's a struct so we can add more things later +// without breaking compatibility. +type FilterFuncInput struct { + // Columns is a list of the columns of the table + Columns []Column + + // Row is the row that's being considered for filtering + Row Row + + // GlobalMetadata is an arbitrary set of metadata from the table set by WithGlobalMetadata + GlobalMetadata map[string]any + + // Filter is the filter string input to consider + Filter string +} + +// FilterFunc takes a FilterFuncInput and returns true if the row should be visible, +// or false if the row should be hidden. +type FilterFunc func(FilterFuncInput) bool + func (m Model) getFilteredRows(rows []Row) []Row { filterInputValue := m.filterTextInput.Value() if !m.filtered || filterInputValue == "" { @@ -14,7 +34,20 @@ func (m Model) getFilteredRows(rows []Row) []Row { filteredRows := make([]Row, 0) for _, row := range rows { - if isRowMatched(m.columns, row, filterInputValue) { + var availableFilterFunc FilterFunc + + if m.filterFunc != nil { + availableFilterFunc = m.filterFunc + } else { + availableFilterFunc = filterFuncContains + } + + if availableFilterFunc(FilterFuncInput{ + Columns: m.columns, + Row: row, + Filter: filterInputValue, + GlobalMetadata: m.metadata, + }) { filteredRows = append(filteredRows, row) } } @@ -22,23 +55,25 @@ func (m Model) getFilteredRows(rows []Row) []Row { return filteredRows } -func isRowMatched(columns []Column, row Row, filter string) bool { - if filter == "" { +// filterFuncContains returns a filterFunc that performs case-insensitive +// "contains" matching over all filterable columns in a row. +func filterFuncContains(input FilterFuncInput) bool { + if input.Filter == "" { return true } checkedAny := false - filterLower := strings.ToLower(filter) + filterLower := strings.ToLower(input.Filter) - for _, column := range columns { + for _, column := range input.Columns { if !column.filterable { continue } checkedAny = true - data, ok := row.Data[column.key] + data, ok := input.Row.Data[column.key] if !ok { continue @@ -69,3 +104,79 @@ func isRowMatched(columns []Column, row Row, filter string) bool { return !checkedAny } + +// filterFuncFuzzy returns a filterFunc that performs case-insensitive fuzzy +// matching (subsequence) over the concatenation of all filterable column values. +// it supports multiple filter tokens separated by space which must all match. +// also, if a filter token starts with the quote character (') it has to match literally. +func filterFuncFuzzy(input FilterFuncInput) bool { + filter := strings.TrimSpace(input.Filter) + if filter == "" { + return true + } + + haystack := buildFuzzyHaystack(input) + if haystack == "" { + return false + } + + for _, token := range strings.Fields(strings.ToLower(filter)) { + if !fuzzyTokenMatches(haystack, token) { + return false + } + } + + return true +} + +func buildFuzzyHaystack(input FilterFuncInput) string { + var builder strings.Builder + + for _, col := range input.Columns { + if !col.filterable { + continue + } + + value, ok := input.Row.Data[col.key] + if !ok { + continue + } + + if sc, ok := value.(StyledCell); ok { + value = sc.Data + } + + fmt.Fprint(&builder, value) // uses Stringer if implemented + builder.WriteByte(' ') + } + + return strings.ToLower(builder.String()) +} + +func fuzzyTokenMatches(haystack, token string) bool { + if token[0] == '\'' && len(token) > 1 { + return strings.Contains(haystack, token[1:]) + } + + return fuzzySubsequenceMatch(haystack, token) +} + +// fuzzySubsequenceMatch returns true if all runes in needle appear in order +// within haystack (not necessarily contiguously). Case must be normalized by caller. +func fuzzySubsequenceMatch(haystack, needle string) bool { + if needle == "" { + return true + } + haystackIndex, needleIndex := 0, 0 + haystackRunes := []rune(haystack) + needleRunes := []rune(needle) + + for haystackIndex < len(haystackRunes) && needleIndex < len(needleRunes) { + if haystackRunes[haystackIndex] == needleRunes[needleIndex] { + needleIndex++ + } + haystackIndex++ + } + + return needleIndex == len(needleRunes) +} diff --git a/vendor/github.com/evertras/bubble-table/table/footer.go b/vendor/github.com/evertras/bubble-table/table/footer.go index c68709ce4..a452630f9 100644 --- a/vendor/github.com/evertras/bubble-table/table/footer.go +++ b/vendor/github.com/evertras/bubble-table/table/footer.go @@ -6,7 +6,46 @@ import ( ) func (m Model) hasFooter() bool { - return m.footerVisible && (m.staticFooter != "" || m.pageSize != 0 || m.filtered) + multiPageTargetHeight := m.targetHeight != 0 && m.pageStartIndices != nil && len(m.pageStartIndices) > 1 + + return m.footerVisible && (m.staticFooter != "" || m.pageSize != 0 || multiPageTargetHeight || m.filtered) +} + +func (m Model) footerFilterSection() string { + if !m.filtered { + return "" + } + + if m.filterTextInput.Focused() { + return m.filterTextInput.View() + } + + if m.filterTextInput.Value() != "" { + // When not focused, show only the prompt + value without the + // cursor character that bubbles v2 renders in its View(). + return m.filterTextInput.Prompt + m.filterTextInput.Value() + } + + return "" +} + +func (m Model) footerPageSection() string { + isPaged := m.pageSize != 0 || (m.targetHeight != 0 && m.pageStartIndices != nil && len(m.pageStartIndices) > 1) + if !isPaged { + return "" + } + + str := fmt.Sprintf("%d/%d", m.CurrentPage(), m.MaxPages()) + + if m.filtered && m.filterTextInput.Focused() { + // Need to apply inline style here in case of filter input cursor, because + // the input cursor resets the style after rendering. Note that Inline(true) + // creates a copy, so it's safe to use here without mutating the underlying + // base style. + str = m.baseStyle.Inline(true).Render(str) + } + + return str } func (m Model) renderFooter(width int, includeTop bool) string { @@ -14,9 +53,11 @@ func (m Model) renderFooter(width int, includeTop bool) string { return "" } - const borderAdjustment = 2 - - styleFooter := m.baseStyle.Copy().Inherit(m.border.styleFooter).Width(width - borderAdjustment) + // In lipgloss v2, Width(n) sets the *total* outer width including borders. + // The footer style has left and right borders (overhead = 2) plus a bottom + // border. We pass the full row width directly so that content gets + // width-2 chars, matching the inner width of the data rows. + styleFooter := m.baseStyle.Inherit(m.border.styleFooter).Width(width) if includeTop { styleFooter = styleFooter.BorderTop(true) @@ -28,24 +69,13 @@ func (m Model) renderFooter(width int, includeTop bool) string { sections := []string{} - if m.filtered && (m.filterTextInput.Focused() || m.filterTextInput.Value() != "") { - sections = append(sections, m.filterTextInput.View()) + if section := m.footerFilterSection(); section != "" { + sections = append(sections, section) } - // paged feature enabled - if m.pageSize != 0 { - str := fmt.Sprintf("%d/%d", m.CurrentPage(), m.MaxPages()) - if m.filtered && m.filterTextInput.Focused() { - // Need to apply inline style here in case of filter input cursor, because - // the input cursor resets the style after rendering. Note that Inline(true) - // creates a copy, so it's safe to use here without mutating the underlying - // base style. - str = m.baseStyle.Inline(true).Render(str) - } - sections = append(sections, str) + if section := m.footerPageSection(); section != "" { + sections = append(sections, section) } - footerText := strings.Join(sections, " ") - - return styleFooter.Render(footerText) + return styleFooter.Render(strings.Join(sections, " ")) } diff --git a/vendor/github.com/evertras/bubble-table/table/header.go b/vendor/github.com/evertras/bubble-table/table/header.go index fdd5ac0bd..010844abb 100644 --- a/vendor/github.com/evertras/bubble-table/table/header.go +++ b/vendor/github.com/evertras/bubble-table/table/header.go @@ -1,6 +1,10 @@ package table -import "github.com/charmbracelet/lipgloss" +import ( + "strings" + + "charm.land/lipgloss/v2" +) // This is long and could use some refactoring in the future, but unsure of how // to pick it apart right now. @@ -9,16 +13,33 @@ import "github.com/charmbracelet/lipgloss" func (m Model) renderHeaders() string { headerStrings := []string{} + // Track column widths (content-only, not including border chars) so we can + // build the top border line and separator line as plain strings. + renderedColWidths := []int{} + totalRenderedWidth := 0 headerStyles := m.styleHeaders() renderHeader := func(column Column, borderStyle lipgloss.Style) string { - borderStyle = borderStyle.Inherit(column.style).Inherit(m.baseStyle) + // In lipgloss v2, Width(n) sets the *total* outer width including borders. + // Compute the actual border char overhead using the border size accessors so + // that any Width already set on borderStyle (e.g. from genOverflowStyle) does + // not pollute the overhead calculation. + borderOverhead := borderStyle.GetBorderLeftSize() + borderStyle.GetBorderRightSize() + style := borderStyle.Inherit(column.style).Inherit(m.baseStyle).Width(column.width + borderOverhead) + + // lipgloss Inherit() explicitly skips padding/margin, so apply column + // padding directly to ensure it takes effect. + colPadTop, colPadRight, colPadBottom, colPadLeft := column.style.GetPadding() + if colPadTop != 0 || colPadRight != 0 || colPadBottom != 0 || colPadLeft != 0 { + style = style.Padding(colPadTop, colPadRight, colPadBottom, colPadLeft) + } - headerSection := limitStr(column.title, column.width) + contentWidth := max(column.width-colPadLeft-colPadRight, 0) + headerSection := limitStr(column.title, contentWidth) - return borderStyle.Render(headerSection) + return style.Render(headerSection) } for columnIndex, column := range m.columns { @@ -26,16 +47,18 @@ func (m Model) renderHeaders() string { if m.horizontalScrollOffsetCol > 0 && columnIndex == m.horizontalScrollFreezeColumnsCount { if columnIndex == 0 { - borderStyle = headerStyles.left.Copy() + borderStyle = headerStyles.left } else { - borderStyle = headerStyles.inner.Copy() + borderStyle = headerStyles.inner } - rendered := renderHeader(genOverflowColumnLeft(1), borderStyle) + overflowCol := genOverflowColumnLeft(1) + rendered := renderHeader(overflowCol, borderStyle) totalRenderedWidth += lipgloss.Width(rendered) headerStrings = append(headerStrings, rendered) + renderedColWidths = append(renderedColWidths, overflowCol.width) } if columnIndex >= m.horizontalScrollFreezeColumnsCount && @@ -44,11 +67,11 @@ func (m Model) renderHeaders() string { } if len(headerStrings) == 0 { - borderStyle = headerStyles.left.Copy() + borderStyle = headerStyles.left } else if columnIndex < len(m.columns)-1 { - borderStyle = headerStyles.inner.Copy() + borderStyle = headerStyles.inner } else { - borderStyle = headerStyles.right.Copy() + borderStyle = headerStyles.right } rendered := renderHeader(column, borderStyle) @@ -77,6 +100,7 @@ func (m Model) renderHeaders() string { overflowStr := renderHeader(overflowColumn, overflowStyle) headerStrings = append(headerStrings, overflowStr) + renderedColWidths = append(renderedColWidths, overflowColumn.width) break } @@ -85,9 +109,37 @@ func (m Model) renderHeaders() string { } headerStrings = append(headerStrings, rendered) + renderedColWidths = append(renderedColWidths, column.width) } headerBlock := lipgloss.JoinHorizontal(lipgloss.Bottom, headerStrings...) + hasRows := len(m.GetVisibleRows()) > 0 || m.calculatePadding(0) > 0 + + return m.assembleHeaderOutput(headerBlock, renderedColWidths, hasRows) +} + +func (m Model) assembleHeaderOutput(headerBlock string, renderedColWidths []int, hasRows bool) string { + if m.outerBorder { + var lastLine string + if hasRows { + lastLine = m.border.buildSeparatorLine(renderedColWidths) + } else { + lastLine = m.border.buildBottomBorderLine(renderedColWidths, m.hasFooter()) + } + topLine := m.border.buildTopBorderLine(renderedColWidths) + + return strings.Join([]string{topLine, headerBlock, lastLine}, "\n") + } + + var lastLine string + if hasRows { + lastLine = m.border.buildInnerSeparatorLine(renderedColWidths) + } + + if lastLine != "" { + return strings.Join([]string{headerBlock, lastLine}, "\n") + } + return headerBlock } diff --git a/vendor/github.com/evertras/bubble-table/table/keys.go b/vendor/github.com/evertras/bubble-table/table/keys.go index fe2eccc38..490ab72cf 100644 --- a/vendor/github.com/evertras/bubble-table/table/keys.go +++ b/vendor/github.com/evertras/bubble-table/table/keys.go @@ -1,6 +1,6 @@ package table -import "github.com/charmbracelet/bubbles/key" +import "charm.land/bubbles/v2/key" // KeyMap defines the keybindings for the table when it's focused. type KeyMap struct { @@ -42,7 +42,7 @@ func DefaultKeyMap() KeyMap { key.WithHelp("↑/k", "move up"), ), RowSelectToggle: key.NewBinding( - key.WithKeys(" ", "enter"), + key.WithKeys("space", "enter"), key.WithHelp("/enter", "select row"), ), PageDown: key.NewBinding( diff --git a/vendor/github.com/evertras/bubble-table/table/model.go b/vendor/github.com/evertras/bubble-table/table/model.go index d39b6604c..efdd90127 100644 --- a/vendor/github.com/evertras/bubble-table/table/model.go +++ b/vendor/github.com/evertras/bubble-table/table/model.go @@ -1,10 +1,10 @@ package table import ( - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/key" + "charm.land/bubbles/v2/textinput" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" ) const ( @@ -12,21 +12,26 @@ const ( ) var ( - defaultHighlightStyle = lipgloss.NewStyle().Background(lipgloss.Color("#334")) + // defaultHighlightStyle has no visual effect; users opt into highlight colors + // via WithHighlightStyle. This matches the historical rendering behavior + // where the table background color was not automatically applied to focused + // rows. + defaultHighlightStyle = lipgloss.NewStyle() ) // Model is the main table model. Create using New(). type Model struct { // Data - columns []Column - rows []Row + columns []Column + rows []Row + metadata map[string]any // Caches for optimizations visibleRowCacheUpdated bool visibleRowCache []Row // Shown when data is missing from a row - missingDataIndicator interface{} + missingDataIndicator any // Interaction focused bool @@ -78,6 +83,7 @@ type Model struct { // Filter filtered bool filterTextInput textinput.Model + filterFunc FilterFunc // For flex columns targetTotalWidth int @@ -101,22 +107,49 @@ type Model struct { // Minimum total height of the table minimumHeight int + // Target total height of the table in terminal lines, including borders, + // header, and footer. When set, the table fits as many rows as possible + // per page. Mutually exclusive with pageSize. + targetHeight int + + // Cached page boundary indices when targetHeight is set. + // pageStartIndices[i] is the index of the first visible row on page i. + // Nil means the cache needs rebuilding. + pageStartIndices []int + // Internal cached calculation, the height of the header and footer // including borders. Used to determine how many padding rows to add. metaHeight int // If true, the table will be multiline multiline bool + + // If true, draw a horizontal separator line between each data row + rowSeparator bool + + // If true, render the outer border (top line, bottom line, left/right cell borders) + outerBorder bool } // New creates a new table ready for further modifications. func New(columns []Column) Model { filterInput := textinput.New() filterInput.Prompt = "/" + // Use plain styles without foreground colors so that the filter text + // renders without ANSI color codes, keeping the output predictable. + plainStyles := textinput.DefaultDarkStyles() + plainStyles.Focused.Prompt = lipgloss.NewStyle() + plainStyles.Focused.Text = lipgloss.NewStyle() + plainStyles.Blurred.Prompt = lipgloss.NewStyle() + plainStyles.Blurred.Text = lipgloss.NewStyle() + plainStyles.Cursor.Color = lipgloss.NoColor{} + filterInput.SetStyles(plainStyles) model := Model{ columns: make([]Column, len(columns)), - highlightStyle: defaultHighlightStyle.Copy(), + metadata: make(map[string]any), + highlightStyle: defaultHighlightStyle, border: borderDefault, + outerBorder: true, headerVisible: true, footerVisible: true, keyMap: DefaultKeyMap(), @@ -125,6 +158,7 @@ func New(columns []Column) Model { unselectedText: "[ ]", filterTextInput: filterInput, + filterFunc: filterFuncContains, baseStyle: lipgloss.NewStyle().Align(lipgloss.Right), paginationWrapping: true, diff --git a/vendor/github.com/evertras/bubble-table/table/options.go b/vendor/github.com/evertras/bubble-table/table/options.go index 95119549f..1a44118bd 100644 --- a/vendor/github.com/evertras/bubble-table/table/options.go +++ b/vendor/github.com/evertras/bubble-table/table/options.go @@ -1,9 +1,12 @@ package table import ( - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textinput" - "github.com/charmbracelet/lipgloss" + "image/color" + + "charm.land/bubbles/v2/key" + "charm.land/bubbles/v2/textinput" + "charm.land/lipgloss/v2" + "github.com/charmbracelet/x/ansi" ) // RowStyleFuncInput is the input to the style function that can @@ -53,7 +56,7 @@ func (m Model) WithHighlightedRow(index int) Model { // HeaderStyle sets the style to apply to the header text, such as color or bold. func (m Model) HeaderStyle(style lipgloss.Style) Model { - m.headerStyle = style.Copy() + m.headerStyle = style return m } @@ -71,7 +74,16 @@ func (m Model) WithRows(rows []Row) Model { m.rowCursorIndex = 0 } - if m.pageSize != 0 { + if m.targetHeight != 0 { + m.pageStartIndices = nil + m.ensurePageMap() + + maxPage := m.MaxPages() + + if maxPage <= m.currentPage { + m.pageLast() + } + } else if m.pageSize != 0 { maxPage := m.MaxPages() // MaxPages is 1-index, currentPage is 0 index @@ -104,8 +116,9 @@ func (m Model) SelectableRows(selectable bool) Model { if hasSelectColumn != selectable { if selectable { + selectColWidth := max(ansi.StringWidth(m.selectedText), ansi.StringWidth(m.unselectedText)) m.columns = append([]Column{ - NewColumn(columnKeySelect, m.selectedText, len([]rune(m.selectedText))), + NewColumn(columnKeySelect, m.selectedText, selectColWidth), }, m.columns...) } else { m.columns = m.columns[1:] @@ -162,10 +175,14 @@ func (m Model) Filtered(filtered bool) Model { m.filtered = filtered m.visibleRowCacheUpdated = false - if m.minimumHeight > 0 { + if m.minimumHeight > 0 || m.targetHeight != 0 { m.recalculateHeight() } + if m.targetHeight != 0 { + m.pageStartIndices = nil + } + return m } @@ -180,10 +197,14 @@ func (m Model) StartFilterTyping() Model { func (m Model) WithStaticFooter(footer string) Model { m.staticFooter = footer - if m.minimumHeight > 0 { + if m.minimumHeight > 0 || m.targetHeight != 0 { m.recalculateHeight() } + if m.targetHeight != 0 { + m.pageStartIndices = nil + } + return m } @@ -231,7 +252,8 @@ func (m Model) WithSelectedText(unselected, selected string) Model { m.unselectedText = unselected if len(m.columns) > 0 && m.columns[0].key == columnKeySelect { - m.columns[0] = NewColumn(columnKeySelect, m.selectedText, len([]rune(m.selectedText))) + selectColWidth := max(ansi.StringWidth(m.selectedText), ansi.StringWidth(m.unselectedText)) + m.columns[0] = NewColumn(columnKeySelect, m.selectedText, selectColWidth) m.recalculateWidth() } @@ -246,6 +268,36 @@ func (m Model) WithBaseStyle(style lipgloss.Style) Model { return m } +// WithBorderForeground sets the foreground colour for the plain-string +// horizontal border lines (top border, header/data separator, bottom border). +// The left and right cell border characters are coloured via WithBaseStyle; +// this option covers the horizontal lines that are rendered as plain strings. +func (m Model) WithBorderForeground(c color.Color) Model { + m.border.foreground = c + + return m +} + +// WithOuterBorder controls whether the outer border of the table is rendered +// (top line, bottom line, and left/right cell borders). Defaults to true. +func (m Model) WithOuterBorder(show bool) Model { + m.outerBorder = show + + return m +} + +// WithRowBorder controls whether a horizontal separator line is drawn between +// each pair of data rows, creating a grid-like appearance. +func (m Model) WithRowBorder(show bool) Model { + m.rowSeparator = show + + if m.targetHeight != 0 { + m.pageStartIndices = nil + } + + return m +} + // WithTargetWidth sets the total target width of the table, including borders. // This only takes effect when using flex columns. When using flex columns, // columns will stretch to fill out to the total width given here. @@ -254,6 +306,10 @@ func (m Model) WithTargetWidth(totalWidth int) Model { m.recalculateWidth() + if m.targetHeight != 0 { + m.pageStartIndices = nil + } + return m } @@ -266,6 +322,20 @@ func (m Model) WithMinimumHeight(minimumHeight int) Model { return m } +// WithTargetHeight sets the total target height of the table in terminal lines, +// including borders, header, and footer. The table automatically fits as many +// rows as possible within that height per page, with proper pagination for the +// remainder. This is the correct way to constrain table height when +// WithMultiline is enabled. Mutually exclusive with WithPageSize. +func (m Model) WithTargetHeight(height int) Model { + m.targetHeight = height + m.pageSize = 0 + m.pageStartIndices = nil + m.ensurePageMap() + + return m +} + // PageDown goes to the next page of a paginated table, wrapping to the first // page if the table is already on the last page. func (m Model) PageDown() Model { @@ -300,6 +370,26 @@ func (m Model) PageFirst() Model { // table, bounded to the total number of pages. The current selected row will // be set to the top row of the page if the page changed. func (m Model) WithCurrentPage(currentPage int) Model { + if m.targetHeight != 0 { + m.ensurePageMap() + + maxPages := m.MaxPages() + + if currentPage < 1 { + currentPage = 1 + } else if currentPage > maxPages { + currentPage = maxPages + } + + m.currentPage = currentPage - 1 + + if m.currentPage < len(m.pageStartIndices) { + m.rowCursorIndex = m.pageStartIndices[m.currentPage] + } + + return m + } + if m.pageSize == 0 || currentPage == m.CurrentPage() { return m } @@ -332,6 +422,10 @@ func (m Model) WithColumns(columns []Column) Model { m = m.SelectableRows(true) } + if m.targetHeight != 0 { + m.pageStartIndices = nil + } + return m } @@ -346,6 +440,10 @@ func (m Model) WithFilterInput(input textinput.Model) Model { m.filterTextInput = input m.visibleRowCacheUpdated = false + if m.targetHeight != 0 { + m.pageStartIndices = nil + } + return m } @@ -361,17 +459,42 @@ func (m Model) WithFilterInputValue(value string) Model { m.filterTextInput.Blur() m.visibleRowCacheUpdated = false + if m.targetHeight != 0 { + m.pageStartIndices = nil + } + + return m +} + +// WithFilterFunc adds a filter function to the model. If the function returns +// true, the row will be included in the filtered results. If the function +// is nil, the function won't be used and instead the default filtering will be applied, +// if any. +func (m Model) WithFilterFunc(shouldInclude FilterFunc) Model { + m.filterFunc = shouldInclude + + m.visibleRowCacheUpdated = false + return m } +// WithFuzzyFilter enables fuzzy filtering for the table. +func (m Model) WithFuzzyFilter() Model { + return m.WithFilterFunc(filterFuncFuzzy) +} + // WithFooterVisibility sets the visibility of the footer. func (m Model) WithFooterVisibility(visibility bool) Model { m.footerVisible = visibility - if m.minimumHeight > 0 { + if m.minimumHeight > 0 || m.targetHeight != 0 { m.recalculateHeight() } + if m.targetHeight != 0 { + m.pageStartIndices = nil + } + return m } @@ -379,10 +502,14 @@ func (m Model) WithFooterVisibility(visibility bool) Model { func (m Model) WithHeaderVisibility(visibility bool) Model { m.headerVisible = visibility - if m.minimumHeight > 0 { + if m.minimumHeight > 0 || m.targetHeight != 0 { m.recalculateHeight() } + if m.targetHeight != 0 { + m.pageStartIndices = nil + } + return m } @@ -462,6 +589,10 @@ func (m Model) WithAllRowsDeselected() Model { func (m Model) WithMultiline(multiline bool) Model { m.multiline = multiline + if m.targetHeight != 0 { + m.pageStartIndices = nil + } + return m } @@ -482,3 +613,12 @@ func (m Model) WithAdditionalFullHelpKeys(keys []key.Binding) Model { return m } + +// WithGlobalMetadata applies the given metadata to the table. This metadata is passed to +// some functions in FilterFuncInput and StyleFuncInput to enable more advanced decisions, +// such as setting some global theme variable to reference, etc. Has no effect otherwise. +func (m Model) WithGlobalMetadata(metadata map[string]any) Model { + m.metadata = metadata + + return m +} diff --git a/vendor/github.com/evertras/bubble-table/table/overflow.go b/vendor/github.com/evertras/bubble-table/table/overflow.go index 19c5b0aa4..5776be1b8 100644 --- a/vendor/github.com/evertras/bubble-table/table/overflow.go +++ b/vendor/github.com/evertras/bubble-table/table/overflow.go @@ -1,6 +1,6 @@ package table -import "github.com/charmbracelet/lipgloss" +import "charm.land/lipgloss/v2" const columnKeyOverflowRight = "___overflow_r___" const columnKeyOverflowLeft = "___overflow_l__" diff --git a/vendor/github.com/evertras/bubble-table/table/pagination.go b/vendor/github.com/evertras/bubble-table/table/pagination.go index 6fce9b519..d2ce0f5ac 100644 --- a/vendor/github.com/evertras/bubble-table/table/pagination.go +++ b/vendor/github.com/evertras/bubble-table/table/pagination.go @@ -14,6 +14,16 @@ func (m *Model) CurrentPage() int { // MaxPages returns the maximum number of pages that are visible. func (m *Model) MaxPages() int { + if m.targetHeight != 0 { + m.ensurePageMap() + + if len(m.pageStartIndices) == 0 { + return 1 + } + + return len(m.pageStartIndices) + } + totalRows := len(m.GetVisibleRows()) if m.pageSize == 0 || totalRows == 0 { @@ -34,6 +44,24 @@ func (m *Model) TotalRows() int { func (m *Model) VisibleIndices() (start, end int) { totalRows := len(m.GetVisibleRows()) + if m.targetHeight != 0 { + m.ensurePageMap() + + if totalRows == 0 || len(m.pageStartIndices) == 0 { + return 0, -1 + } + + start = m.pageStartIndices[m.currentPage] + + if m.currentPage+1 < len(m.pageStartIndices) { + end = m.pageStartIndices[m.currentPage+1] - 1 + } else { + end = totalRows - 1 + } + + return start, end + } + if m.pageSize == 0 { start = 0 end = totalRows - 1 @@ -51,43 +79,75 @@ func (m *Model) VisibleIndices() (start, end int) { return start, end } -func (m *Model) pageDown() { - if m.pageSize == 0 || len(m.GetVisibleRows()) <= m.pageSize { +func (m *Model) wrappedOrClamped(wrappedValue, clampedValue int) int { + if m.paginationWrapping { + return wrappedValue + } + + return clampedValue +} + +func (m *Model) clampCurrentPage(maxPageIndex int) { + if m.currentPage > maxPageIndex { + m.currentPage = m.wrappedOrClamped(0, maxPageIndex) + } else if m.currentPage < 0 { + m.currentPage = m.wrappedOrClamped(maxPageIndex, 0) + } +} + +func (m *Model) pageDownTargetHeight() { + m.ensurePageMap() + + if len(m.pageStartIndices) <= 1 { return } m.currentPage++ + m.clampCurrentPage(len(m.pageStartIndices) - 1) + m.rowCursorIndex = m.pageStartIndices[m.currentPage] +} - maxPageIndex := m.MaxPages() - 1 +func (m *Model) pageUpTargetHeight() { + m.ensurePageMap() - if m.currentPage > maxPageIndex { - if m.paginationWrapping { - m.currentPage = 0 - } else { - m.currentPage = maxPageIndex - } + if len(m.pageStartIndices) <= 1 { + return } - m.rowCursorIndex = m.currentPage * m.pageSize + m.currentPage-- + m.clampCurrentPage(len(m.pageStartIndices) - 1) + m.rowCursorIndex = m.pageStartIndices[m.currentPage] } -func (m *Model) pageUp() { +func (m *Model) pageDown() { + if m.targetHeight != 0 { + m.pageDownTargetHeight() + + return + } + if m.pageSize == 0 || len(m.GetVisibleRows()) <= m.pageSize { return } - m.currentPage-- + m.currentPage++ + m.clampCurrentPage(m.MaxPages() - 1) + m.rowCursorIndex = m.currentPage * m.pageSize +} - maxPageIndex := m.MaxPages() - 1 +func (m *Model) pageUp() { + if m.targetHeight != 0 { + m.pageUpTargetHeight() - if m.currentPage < 0 { - if m.paginationWrapping { - m.currentPage = maxPageIndex - } else { - m.currentPage = 0 - } + return + } + + if m.pageSize == 0 || len(m.GetVisibleRows()) <= m.pageSize { + return } + m.currentPage-- + m.clampCurrentPage(m.MaxPages() - 1) m.rowCursorIndex = m.currentPage * m.pageSize } @@ -97,16 +157,45 @@ func (m *Model) pageFirst() { } func (m *Model) pageLast() { + if m.targetHeight != 0 { + m.ensurePageMap() + + if len(m.pageStartIndices) == 0 { + return + } + + m.currentPage = len(m.pageStartIndices) - 1 + m.rowCursorIndex = m.pageStartIndices[m.currentPage] + + return + } + m.currentPage = m.MaxPages() - 1 m.rowCursorIndex = m.currentPage * m.pageSize } func (m *Model) expectedPageForRowIndex(rowIndex int) int { + if m.targetHeight != 0 { + m.ensurePageMap() + + // Binary search: find the last page whose start index <= rowIndex. + low, high := 0, len(m.pageStartIndices)-1 + + for low < high { + mid := (low + high + 1) / 2 //nolint:mnd // standard ceiling-midpoint formula + if m.pageStartIndices[mid] <= rowIndex { + low = mid + } else { + high = mid - 1 + } + } + + return low + } + if m.pageSize == 0 { return 0 } - expectedPage := rowIndex / m.pageSize - - return expectedPage + return rowIndex / m.pageSize } diff --git a/vendor/github.com/evertras/bubble-table/table/row.go b/vendor/github.com/evertras/bubble-table/table/row.go index 2ba451973..f672abafb 100644 --- a/vendor/github.com/evertras/bubble-table/table/row.go +++ b/vendor/github.com/evertras/bubble-table/table/row.go @@ -4,16 +4,16 @@ import ( "fmt" "sync/atomic" - "github.com/charmbracelet/lipgloss" - "github.com/muesli/reflow/wordwrap" + "charm.land/lipgloss/v2" + "github.com/charmbracelet/x/ansi" ) -// RowData is a map of string column keys to interface{} data. Data with a key +// RowData is a map of string column keys to arbitrary data. Data with a key // that matches a column key will be displayed. Data with a key that does not // match a column key will not be displayed, but will remain attached to the Row. // This can be useful for attaching hidden metadata for future reference when // retrieving rows. -type RowData map[string]interface{} +type RowData map[string]any // Row represents a row in the table with some data keyed to the table columns> // Can have a style applied to it such as color/bold. Create using NewRow(). @@ -32,7 +32,7 @@ var lastRowID uint32 = 1 // NewRow creates a new row and copies the given row data. func NewRow(data RowData) Row { row := Row{ - Data: make(map[string]interface{}), + Data: make(map[string]any), id: lastRowID, } @@ -48,32 +48,57 @@ func NewRow(data RowData) Row { // WithStyle uses the given style for the text in the row. func (r Row) WithStyle(style lipgloss.Style) Row { - r.Style = style.Copy() + r.Style = style return r } -//nolint:nestif,cyclop // This has many ifs, but they're short +// rowLineCount returns the number of terminal lines the row occupies when rendered. +// For non-multiline tables this is always 1. +func (m *Model) rowLineCount(row Row) int { + if !m.multiline { + return 1 + } + + maxLines := 1 + + for _, column := range m.columns { + cellStr := m.renderRowColumnData(row, column, row.Style, lipgloss.NewStyle()) + maxLines = max(maxLines, lipgloss.Height(cellStr)) + } + + return maxLines +} + +//nolint:cyclop,funlen // Breaking this up will be more complicated than it's worth for now func (m Model) renderRowColumnData(row Row, column Column, rowStyle lipgloss.Style, borderStyle lipgloss.Style) string { - cellStyle := rowStyle.Copy().Inherit(column.style).Inherit(m.baseStyle) + cellStyle := rowStyle.Inherit(column.style).Inherit(m.baseStyle) + + // lipgloss Inherit() explicitly skips padding/margin, so apply column + // padding directly to ensure it takes effect. + colPadTop, colPadRight, colPadBottom, colPadLeft := column.style.GetPadding() + if colPadTop != 0 || colPadRight != 0 || colPadBottom != 0 || colPadLeft != 0 { + cellStyle = cellStyle.Padding(colPadTop, colPadRight, colPadBottom, colPadLeft) + } var str string - if column.key == columnKeySelect { + switch column.key { + case columnKeySelect: if row.selected { str = m.selectedText } else { str = m.unselectedText } - } else if column.key == columnKeyOverflowRight { + case columnKeyOverflowRight: cellStyle = cellStyle.Align(lipgloss.Right) str = ">" - } else if column.key == columnKeyOverflowLeft { + case columnKeyOverflowLeft: str = "<" - } else { + default: fmtString := "%v" - var data interface{} + var data any if entry, exists := row.Data[column.key]; exists { data = entry @@ -90,30 +115,52 @@ func (m Model) renderRowColumnData(row Row, column Column, rowStyle lipgloss.Sty switch entry := data.(type) { case StyledCell: str = fmt.Sprintf(fmtString, entry.Data) - cellStyle = entry.Style.Copy().Inherit(cellStyle) + + if entry.StyleFunc != nil { + cellStyle = entry.StyleFunc(StyledCellFuncInput{ + Column: column, + Data: entry.Data, + Row: row, + GlobalMetadata: m.metadata, + }).Inherit(cellStyle) + } else { + cellStyle = entry.Style.Inherit(cellStyle) + } default: str = fmt.Sprintf(fmtString, entry) } } + // Reduce the available text width by any horizontal column padding so that + // content is truncated/wrapped to the inner content area, not the full cell. + contentWidth := max(column.width-colPadLeft-colPadRight, 0) + if m.multiline { - str = wordwrap.String(str, column.width) + str = ansi.Wordwrap(str, contentWidth, "") + // a bug in Wordwrap means when hyphens are used as a breakpoint, the wrap is done AFTER the hyphen + // resulting in a line that exceeds the column width + // Hardwrap as a seconds step avoids this + str = ansi.Hardwrap(str, contentWidth, false) cellStyle = cellStyle.Align(lipgloss.Top) } else { - str = limitStr(str, column.width) + str = limitStr(str, contentWidth) } - cellStyle = cellStyle.Inherit(borderStyle) + // In lipgloss v2, Width() sets the *total* outer width (including borders). + // Use the border size accessors so that any Width already set on borderStyle + // (e.g. from genOverflowStyle) does not pollute the overhead calculation. + borderOverhead := borderStyle.GetBorderLeftSize() + borderStyle.GetBorderRightSize() + cellStyle = cellStyle.Inherit(borderStyle).Width(column.width + borderOverhead) cellStr := cellStyle.Render(str) return cellStr } -func (m Model) renderRow(rowIndex int, last bool) string { +func (m Model) renderRow(rowIndex int, last bool, separatorBelow bool) string { row := m.GetVisibleRows()[rowIndex] highlighted := rowIndex == m.rowCursorIndex - rowStyle := row.Style.Copy() + rowStyle := row.Style if m.rowStyleFunc != nil { styleResult := m.rowStyleFunc(RowStyleFuncInput{ @@ -127,21 +174,23 @@ func (m Model) renderRow(rowIndex int, last bool) string { rowStyle = rowStyle.Inherit(m.highlightStyle) } - return m.renderRowData(row, rowStyle, last) + return m.renderRowData(row, rowStyle, last, separatorBelow) } func (m Model) renderBlankRow(last bool) string { - return m.renderRowData(NewRow(nil), lipgloss.NewStyle(), last) + return m.renderRowData(NewRow(nil), lipgloss.NewStyle(), last, false) } // This is long and could use some refactoring in the future, but not quite sure // how to pick it apart yet. // -//nolint:funlen, cyclop, gocognit -func (m Model) renderRowData(row Row, rowStyle lipgloss.Style, last bool) string { +//nolint:funlen, cyclop +func (m Model) renderRowData(row Row, rowStyle lipgloss.Style, last bool, separatorBelow bool) string { numColumns := len(m.columns) columnStrings := []string{} + // Track content-only column widths for building the bottom border line. + renderedColWidths := []int{} totalRenderedWidth := 0 stylesInner, stylesLast := m.styleRows() @@ -154,31 +203,32 @@ func (m Model) renderRowData(row Row, rowStyle lipgloss.Style, last bool) string } } + // We always use the "inner" styles for cell borders; the bottom border line + // is rendered as a plain string below when last==true. + rowStyles := stylesInner + _ = stylesLast // kept for potential future use + for columnIndex, column := range m.columns { var borderStyle lipgloss.Style - var rowStyles borderStyleRow - if !last { - rowStyles = stylesInner - } else { - rowStyles = stylesLast - } - rowStyle = rowStyle.Copy().Height(maxCellHeight) + rowStyle = rowStyle.Height(maxCellHeight) if m.horizontalScrollOffsetCol > 0 && columnIndex == m.horizontalScrollFreezeColumnsCount { var borderStyle lipgloss.Style if columnIndex == 0 { - borderStyle = rowStyles.left.Copy() + borderStyle = rowStyles.left } else { - borderStyle = rowStyles.inner.Copy() + borderStyle = rowStyles.inner } - rendered := m.renderRowColumnData(row, genOverflowColumnLeft(1), rowStyle, borderStyle) + overflowCol := genOverflowColumnLeft(1) + rendered := m.renderRowColumnData(row, overflowCol, rowStyle, borderStyle) totalRenderedWidth += lipgloss.Width(rendered) columnStrings = append(columnStrings, rendered) + renderedColWidths = append(renderedColWidths, overflowCol.width) } if columnIndex >= m.horizontalScrollFreezeColumnsCount && @@ -219,6 +269,7 @@ func (m Model) renderRowData(row Row, rowStyle lipgloss.Style, last bool) string overflowStr := m.renderRowColumnData(row, overflowColumn, rowStyle, overflowStyle) columnStrings = append(columnStrings, overflowStr) + renderedColWidths = append(renderedColWidths, overflowColumn.width) break } @@ -227,9 +278,37 @@ func (m Model) renderRowData(row Row, rowStyle lipgloss.Style, last bool) string } columnStrings = append(columnStrings, cellStr) + renderedColWidths = append(renderedColWidths, column.width) + } + + rowLine := lipgloss.JoinHorizontal(lipgloss.Bottom, columnStrings...) + + return m.assembleRowOutput(rowLine, renderedColWidths, last, separatorBelow) +} + +func (m Model) assembleRowOutput(rowLine string, renderedColWidths []int, last, separatorBelow bool) string { + if last { + if m.outerBorder { + bottomLine := m.border.buildBottomBorderLine(renderedColWidths, m.hasFooter()) + + return rowLine + "\n" + bottomLine + } + + return rowLine + } + + if separatorBelow { + var sepLine string + if m.outerBorder { + sepLine = m.border.buildSeparatorLine(renderedColWidths) + } else { + sepLine = m.border.buildInnerSeparatorLine(renderedColWidths) + } + + return rowLine + "\n" + sepLine } - return lipgloss.JoinHorizontal(lipgloss.Bottom, columnStrings...) + return rowLine } // Selected returns a copy of the row that's set to be selected or deselected. diff --git a/vendor/github.com/evertras/bubble-table/table/sort.go b/vendor/github.com/evertras/bubble-table/table/sort.go index 9a282cbda..44a18ab2c 100644 --- a/vendor/github.com/evertras/bubble-table/table/sort.go +++ b/vendor/github.com/evertras/bubble-table/table/sort.go @@ -3,6 +3,7 @@ package table import ( "fmt" "sort" + "time" ) // SortDirection indicates whether a column should sort by ascending or descending. @@ -130,6 +131,16 @@ func (s *sortableTable) extractNumber(i int, column string) (float64, bool) { return asNumber(iData) } +func (s *sortableTable) extractTime(i int, column string) (time.Time, bool) { + iData, exists := s.rows[i].Data[column] + + if !exists { + return time.Time{}, false + } + + return asTime(iData) +} + func (s *sortableTable) Less(first, second int) bool { firstNum, firstNumIsValid := s.extractNumber(first, s.byColumn.ColumnKey) secondNum, secondNumIsValid := s.extractNumber(second, s.byColumn.ColumnKey) @@ -142,6 +153,17 @@ func (s *sortableTable) Less(first, second int) bool { return firstNum > secondNum } + firstTime, firstTimeIsValid := s.extractTime(first, s.byColumn.ColumnKey) + secondTime, secondTimeIsValid := s.extractTime(second, s.byColumn.ColumnKey) + + if firstTimeIsValid && secondTimeIsValid { + if s.byColumn.Direction == SortDirectionAsc { + return firstTime.Before(secondTime) + } + + return secondTime.Before(firstTime) + } + firstVal := s.extractString(first, s.byColumn.ColumnKey) secondVal := s.extractString(second, s.byColumn.ColumnKey) diff --git a/vendor/github.com/evertras/bubble-table/table/strlimit.go b/vendor/github.com/evertras/bubble-table/table/strlimit.go index 1b656625e..dc2ff9db0 100644 --- a/vendor/github.com/evertras/bubble-table/table/strlimit.go +++ b/vendor/github.com/evertras/bubble-table/table/strlimit.go @@ -3,8 +3,7 @@ package table import ( "strings" - "github.com/muesli/reflow/ansi" - "github.com/muesli/reflow/truncate" + "github.com/charmbracelet/x/ansi" ) func limitStr(str string, maxLen int) string { @@ -17,8 +16,8 @@ func limitStr(str string, maxLen int) string { str = str[:newLineIndex] + "…" } - if ansi.PrintableRuneWidth(str) > maxLen { - return truncate.StringWithTail(str, uint(maxLen), "…") + if ansi.StringWidth(str) > maxLen { + return ansi.Truncate(str, maxLen, "…") } return str diff --git a/vendor/github.com/evertras/bubble-table/table/update.go b/vendor/github.com/evertras/bubble-table/table/update.go index 198a57d2c..3ef8e0509 100644 --- a/vendor/github.com/evertras/bubble-table/table/update.go +++ b/vendor/github.com/evertras/bubble-table/table/update.go @@ -1,8 +1,8 @@ package table import ( - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" + "charm.land/bubbles/v2/key" + tea "charm.land/bubbletea/v2" ) func (m *Model) moveHighlightUp() { @@ -54,7 +54,7 @@ func (m *Model) toggleSelect() { func (m Model) updateFilterTextInput(msg tea.Msg) (Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { - case tea.KeyMsg: + case tea.KeyPressMsg: if key.Matches(msg, m.keyMap.FilterBlur) { m.filterTextInput.Blur() } @@ -63,13 +63,17 @@ func (m Model) updateFilterTextInput(msg tea.Msg) (Model, tea.Cmd) { m.pageFirst() m.visibleRowCacheUpdated = false + if m.targetHeight != 0 { + m.pageStartIndices = nil + } + return m, cmd } // This is a series of Matches tests with minimal logic // //nolint:cyclop -func (m *Model) handleKeypress(msg tea.KeyMsg) { +func (m *Model) handleKeypress(msg tea.KeyPressMsg) { previousRowIndex := m.rowCursorIndex if key.Matches(msg, m.keyMap.RowDown) { @@ -108,6 +112,10 @@ func (m *Model) handleKeypress(msg tea.KeyMsg) { if key.Matches(msg, m.keyMap.FilterClear) { m.visibleRowCacheUpdated = false m.filterTextInput.Reset() + + if m.targetHeight != 0 { + m.pageStartIndices = nil + } } if key.Matches(msg, m.keyMap.ScrollRight) { @@ -146,7 +154,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { } switch msg := msg.(type) { - case tea.KeyMsg: + case tea.KeyPressMsg: m.handleKeypress(msg) } diff --git a/vendor/github.com/evertras/bubble-table/table/view.go b/vendor/github.com/evertras/bubble-table/table/view.go index 5ef545cb9..45692973b 100644 --- a/vendor/github.com/evertras/bubble-table/table/view.go +++ b/vendor/github.com/evertras/bubble-table/table/view.go @@ -3,9 +3,42 @@ package table import ( "strings" - "github.com/charmbracelet/lipgloss" + "charm.land/lipgloss/v2" ) +// visibleDataLines returns the total terminal lines occupied by visible rows in +// the range [startRowIndex, endRowIndex], including any separator lines between +// rows when WithRowBorder is active. +func (m *Model) visibleDataLines(startRowIndex, endRowIndex int) int { + numRows := endRowIndex - startRowIndex + 1 + + if numRows <= 0 { + return numRows + } + + if !m.multiline { + if m.rowSeparator { + // Each pair of adjacent rows has one separator line between them. + return 2*numRows - 1 + } + + return numRows + } + + total := 0 + visibleRows := m.GetVisibleRows() + + for rowIdx := startRowIndex; rowIdx <= endRowIndex; rowIdx++ { + total += m.rowLineCount(visibleRows[rowIdx]) + + if m.rowSeparator && rowIdx > startRowIndex { + total++ + } + } + + return total +} + // View renders the table. It does not end in a newline, so that it can be // composed with other elements more consistently. // @@ -23,20 +56,23 @@ func (m Model) View() string { headers := m.renderHeaders() startRowIndex, endRowIndex := m.VisibleIndices() - numRows := endRowIndex - startRowIndex + 1 - padding := m.calculatePadding(numRows) + padding := m.calculatePadding(m.visibleDataLines(startRowIndex, endRowIndex)) + + numRows := endRowIndex - startRowIndex + 1 if m.headerVisible { rowStrs = append(rowStrs, headers) } else if numRows > 0 || padding > 0 { - //nolint: gomnd // This is just getting the first newlined substring + //nolint: mnd // This is just getting the first newlined substring split := strings.SplitN(headers, "\n", 2) rowStrs = append(rowStrs, split[0]) } for i := startRowIndex; i <= endRowIndex; i++ { - rowStrs = append(rowStrs, m.renderRow(i, padding == 0 && i == endRowIndex)) + isLast := padding == 0 && i == endRowIndex + separatorBelow := m.rowSeparator && i < endRowIndex + rowStrs = append(rowStrs, m.renderRow(i, isLast, separatorBelow)) } for i := 1; i <= padding; i++ { diff --git a/vendor/github.com/go-openapi/swag/.golangci.yml b/vendor/github.com/go-openapi/swag/.golangci.yml index 099c0a78c..c3ba0b6cd 100644 --- a/vendor/github.com/go-openapi/swag/.golangci.yml +++ b/vendor/github.com/go-openapi/swag/.golangci.yml @@ -48,7 +48,7 @@ linters: min-len: 2 min-occurrences: 3 gocyclo: - min-complexity: 45 + min-complexity: 25 exclusions: generated: lax presets: diff --git a/vendor/github.com/go-openapi/swag/CONTRIBUTORS.md b/vendor/github.com/go-openapi/swag/CONTRIBUTORS.md index 0f0711cd5..fb341cb10 100644 --- a/vendor/github.com/go-openapi/swag/CONTRIBUTORS.md +++ b/vendor/github.com/go-openapi/swag/CONTRIBUTORS.md @@ -4,11 +4,11 @@ | Total Contributors | Total Contributions | | --- | --- | -| 24 | 251 | +| 24 | 263 | | Username | All Time Contribution Count | All Commits | | --- | --- | --- | -| @fredbi | 121 | | +| @fredbi | 133 | | | @casualjim | 98 | | | @alexandear | 4 | | | @orisano | 3 | | diff --git a/vendor/github.com/go-openapi/swag/fileutils/doc.go b/vendor/github.com/go-openapi/swag/fileutils/doc.go index 859a200d8..d5ca57b96 100644 --- a/vendor/github.com/go-openapi/swag/fileutils/doc.go +++ b/vendor/github.com/go-openapi/swag/fileutils/doc.go @@ -3,8 +3,14 @@ // Package fileutils exposes utilities to deal with files and paths. // -// Currently, there is: -// - [File] to represent an abstraction of an uploaded file. -// For instance, this is used by [github.com/go-openapi/runtime.File]. -// - path search utilities (e.g. finding packages in the GO search path) +// It provides: +// +// - [File], an abstraction of an uploaded file. +// It is used by [github.com/go-openapi/runtime.File]. +// - Implementations of [fs.FS]: [OsFS] and [GlobOsFS] wrap the os package, +// [MapFS] serves files held in memory, [OverlayFS] stacks file systems on top of one another, +// [OpaqueFS] lets a layer claim a directory for itself, and [FileReaderFS] adds a ReadFile +// method to any [fs.FS]. +// - [MustSub], to re-root a file system inline when the directory is a constant of the program. +// - path search utilities, to locate a package in the go search path. package fileutils diff --git a/vendor/github.com/go-openapi/swag/fileutils/file.go b/vendor/github.com/go-openapi/swag/fileutils/file.go index 5ad4cfaea..a5ff03c75 100644 --- a/vendor/github.com/go-openapi/swag/fileutils/file.go +++ b/vendor/github.com/go-openapi/swag/fileutils/file.go @@ -6,17 +6,22 @@ package fileutils import "mime/multipart" // File represents an uploaded file. +// +// Data holds the payload, and Header the multipart metadata. +// File implements [io.ReadCloser] by delegating both methods to Data. +// +// The zero File is not usable: [File.Read] and [File.Close] both panic when Data is nil. type File struct { Data multipart.File Header *multipart.FileHeader } -// Read bytes from the file +// Read reads bytes from the payload. func (f *File) Read(p []byte) (n int, err error) { return f.Data.Read(p) } -// Close the file +// Close closes the payload. func (f *File) Close() error { return f.Data.Close() } diff --git a/vendor/github.com/go-openapi/swag/fileutils/fs.go b/vendor/github.com/go-openapi/swag/fileutils/fs.go new file mode 100644 index 000000000..56b0a1f53 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/fileutils/fs.go @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package fileutils + +import ( + "fmt" + "io/fs" + "os" +) + +// OsFS exposes package os features as an [fs.FS], without having to use [os.Root]. +// +// Existing alternatives from the standard library are [os.DirFS], which requires a base directory, +// and [os.Root.FS], which requires a root. +// [OsFS] is intended to be used when none of these alternatives are workable, +// that is when the caller does not know which root it should run in. +// +// Names are passed to the os package unchanged, +// so [OsFS] accepts absolute and relative paths, which a conforming [fs.FS] rejects. +// It offers no containment: every file that the process may read is reachable. +// +// [OsFS] implements [fs.FS], [fs.ReadFileFS] and [fs.ReadDirFS]. +type OsFS struct { +} + +// NewReadOnlyOsFS builds an [OsFS], a read-only view of the os file system. +func NewReadOnlyOsFS() *OsFS { + return &OsFS{} +} + +// Open opens the named file for reading. +func (f *OsFS) Open(name string) (fs.File, error) { + return os.Open(name) +} + +// ReadFile reads the named file and returns its content. +func (f *OsFS) ReadFile(name string) ([]byte, error) { + return os.ReadFile(name) +} + +// ReadDir reads the named directory and returns its entries sorted by file name. +func (f *OsFS) ReadDir(name string) ([]fs.DirEntry, error) { + return os.ReadDir(name) +} + +// FileReaderFS makes a [fs.FS] into a [fs.ReadFileFS], with a [FileReaderFS.ReadFile] method. +type FileReaderFS struct { + fs.FS +} + +// NewFileReaderFS transforms a [fs.FS] into a [fs.ReadFileFS]. +func NewFileReaderFS(base fs.FS) *FileReaderFS { + return &FileReaderFS{ + FS: base, + } +} + +// ReadFile reads the named file from the base file system and returns its content. +func (f *FileReaderFS) ReadFile(name string) ([]byte, error) { + return fs.ReadFile(f.FS, name) +} + +// GlobOsFS is an [OsFS] that also implements [fs.GlobFS], with a [GlobOsFS.Glob] method. +type GlobOsFS struct { + *OsFS +} + +// NewGlobOsFS is like [NewReadOnlyOsFS], augmented to match the [fs.GlobFS] interface. +func NewGlobOsFS() *GlobOsFS { + return &GlobOsFS{ + OsFS: NewReadOnlyOsFS(), + } +} + +// Glob returns the names matching pattern, sorted in lexical order. +// +// It returns a nil slice and no error when nothing matches, +// and [path.ErrBadPattern] when the pattern is malformed. +func (f *GlobOsFS) Glob(pattern string) ([]string, error) { + return fs.Glob(f.OsFS, pattern) +} + +// MustSub re-roots a file system at one of its directories, and panics when it cannot. +// +// It is [fs.Sub] for the cases where the directory is a constant of the program, such as a folder +// of an [embed.FS] assembled at initialization time: there, a failure means the program is wrong, +// not that its input is. +// +// Use [fs.Sub] itself whenever the directory comes from the outside, such as a flag, +// a configuration file or a request, so that an invalid one is reported rather than fatal. +// +// It is meant to be composed inline: +// +// assets := NewOverlayFS( +// MustSub(embedded, "templates"), +// MustSub(embedded, "templates/contrib/mine"), +// ) +func MustSub(fsys fs.FS, dir string) fs.FS { + subFS, err := fs.Sub(fsys, dir) + if err != nil { + panic(fmt.Errorf("fileutils.MustSub: cannot re-root at %q: %w", dir, err)) + } + + return subFS +} diff --git a/vendor/github.com/go-openapi/swag/fileutils/mapfs.go b/vendor/github.com/go-openapi/swag/fileutils/mapfs.go new file mode 100644 index 000000000..5a81879f6 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/fileutils/mapfs.go @@ -0,0 +1,366 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package fileutils + +import ( + "bytes" + "errors" + "io" + "io/fs" + "path" + "slices" + "strings" + "time" +) + +// Default modes of the entries of a [MapFS]. +// +// A [MapFS] is read-only, so its files and directories are readable and never writable. +const ( + // DefaultFileMode is the mode reported by a file with no [MapFile.Mode] of its own. + DefaultFileMode fs.FileMode = 0o444 + + // DefaultDirMode is the mode reported by the directories of a [MapFS]. + DefaultDirMode fs.FileMode = fs.ModeDir | 0o555 +) + +var ( + // errNameIsFileAndDir is reported when the same name is held as a file and as a parent directory. + errNameIsFileAndDir = errors.New("name is held both as a file and as a directory") + + // errNotDir is reported when a regular file is listed as a directory. + errNotDir = errors.New("not a directory") +) + +// MapFile is a file held by a [MapFS]. +// +// Only [MapFile.Data] is required. [NewMapFS] fills the remaining fields with presets when they +// are left to their zero value. +type MapFile struct { + // Data is the content of the file. + Data []byte + + // Mode is the file mode reported by [fs.FileInfo.Mode]. Zero means [DefaultFileMode]. + Mode fs.FileMode + + // ModTime is the modification time reported by [fs.FileInfo.ModTime]. + // + // The zero value is left as is, so that a [MapFS] built from the same input twice + // reports the same metadata. + ModTime time.Time + + // Sys is the opaque value reported by [fs.FileInfo.Sys]. + Sys any +} + +// MapFS is a read-only in-memory [fs.FS], built from a map of file names to content. +// +// It is intended for the cases where the files to serve are held in memory rather than on disk: +// an overlay assembled from raw bytes, assets that a configuration provides, or a fixture in a test. +// +// Names are slash-separated paths, as accepted by [fs.ValidPath]. [NewMapFS] cleans them, +// so a name may be given with a leading "./" or "/", or with redundant elements. +// A name that remains invalid once cleaned, such as one climbing above the root, is reported +// as an error rather than dropped. +// +// Separators are never translated, so that the same input yields the same file system on every +// platform: a caller holding os paths converts them with [path/filepath.ToSlash] beforehand. +// +// Directories are implied by the names of the files, and are indexed once, when the file system +// is built: a name holds a file, and every one of its parents holds a directory. +// The root "." always exists, even when the file system holds no file at all. +// +// [MapFS] implements [fs.FS], [fs.ReadFileFS], [fs.StatFS] and [fs.ReadDirFS], but not [fs.GlobFS]: +// [fs.Glob] resolves against it all the same, through [MapFS.ReadDir]. +type MapFS struct { + files map[string]MapFile + dirs map[string][]fs.DirEntry +} + +// NewMapFS builds an in-memory file system from a map of file names to content. +// +// Names are normalized, and reported as an error when they remain invalid, or when the same name +// is held both as a file and as the parent directory of another one. +// A [MapFile] left with a zero [MapFile.Mode] reports [DefaultFileMode]. +// +// The map is copied, so adding or removing an entry afterwards leaves the file system alone. +// The contents are not: a caller that writes to a [MapFile.Data] slice it still holds changes +// what the file system serves. Hand over a slice nothing else keeps, or copy it first. +func NewMapFS(files map[string]MapFile) (*MapFS, error) { + normalized := make(map[string]MapFile, len(files)) + + for name, file := range files { + clean, err := normalizeMapName(name) + if err != nil { + return nil, err + } + + if _, isDuplicate := normalized[clean]; isDuplicate { + return nil, &fs.PathError{Op: "newmapfs", Path: clean, Err: fs.ErrExist} + } + + if file.Mode == 0 { + file.Mode = DefaultFileMode + } + + normalized[clean] = file + } + + dirs, err := indexMapDirs(normalized) + if err != nil { + return nil, err + } + + return &MapFS{ + files: normalized, + dirs: dirs, + }, nil +} + +// FromRawMap builds the files of a [MapFS] from raw contents, leaving every metadata field to its preset. +// +// It is the shortest way to a [MapFS] when all the caller holds is bytes: +// +// mapFS, err := NewMapFS(FromRawMap(map[string][]byte{ +// "folder/file1": raw1, +// "folder/file2": raw2, +// })) +func FromRawMap(raw map[string][]byte) map[string]MapFile { + files := make(map[string]MapFile, len(raw)) + for name, data := range raw { + files[name] = MapFile{Data: data} + } + + return files +} + +// Open opens the named file or directory. +// +// Opening a directory yields a [fs.ReadDirFile] reporting the same entries as [MapFS.ReadDir]. +func (f *MapFS) Open(name string) (fs.File, error) { + if !fs.ValidPath(name) { + return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid} + } + + if file, isFile := f.files[name]; isFile { + return &openMapFile{ + info: mapFileInfo{name: path.Base(name), file: file}, + reader: bytes.NewReader(file.Data), + }, nil + } + + entries, isDir := f.dirs[name] + if !isDir { + return nil, notFound("open", name) + } + + return &openMapDir{info: mapDirInfo{name: path.Base(name)}, entries: entries}, nil +} + +// ReadFile reads the named file and returns a copy of its content. +func (f *MapFS) ReadFile(name string) ([]byte, error) { + if !fs.ValidPath(name) { + return nil, &fs.PathError{Op: "read", Path: name, Err: fs.ErrInvalid} + } + + file, isFile := f.files[name] + if !isFile { + if _, isDir := f.dirs[name]; isDir { + return nil, &fs.PathError{Op: "read", Path: name, Err: errIsDir} + } + + return nil, notFound("read", name) + } + + // a caller mutating the result would otherwise mutate what the file system serves + return slices.Clone(file.Data), nil +} + +// Stat returns the [fs.FileInfo] of the named file or directory. +func (f *MapFS) Stat(name string) (fs.FileInfo, error) { + if !fs.ValidPath(name) { + return nil, &fs.PathError{Op: "stat", Path: name, Err: fs.ErrInvalid} + } + + if file, isFile := f.files[name]; isFile { + return mapFileInfo{name: path.Base(name), file: file}, nil + } + + if _, isDir := f.dirs[name]; isDir { + return mapDirInfo{name: path.Base(name)}, nil + } + + return nil, notFound("stat", name) +} + +// ReadDir lists the named directory, with its entries sorted by file name. +func (f *MapFS) ReadDir(name string) ([]fs.DirEntry, error) { + if !fs.ValidPath(name) { + return nil, &fs.PathError{Op: "readdir", Path: name, Err: fs.ErrInvalid} + } + + entries, isDir := f.dirs[name] + if !isDir { + if _, isFile := f.files[name]; isFile { + return nil, &fs.PathError{Op: "readdir", Path: name, Err: errNotDir} + } + + return nil, notFound("readdir", name) + } + + // the index is shared by every caller, so it must not escape + return slices.Clone(entries), nil +} + +// normalizeMapName turns the name of a file into the cleaned form that [fs.ValidPath] accepts. +// +// Separators are left alone: an [fs.FS] name is slash-separated by definition, and translating +// them here would resolve the same input differently depending on the platform. +func normalizeMapName(name string) (string, error) { + clean := path.Clean(strings.TrimPrefix(name, "/")) + + if !fs.ValidPath(clean) || clean == "." { + return "", &fs.PathError{Op: "newmapfs", Path: name, Err: fs.ErrInvalid} + } + + return clean, nil +} + +// indexMapDirs builds the directory index of a [MapFS], once, from the names of its files. +// +// Every parent of a file name holds a directory, up to the root, which always exists. +func indexMapDirs(files map[string]MapFile) (map[string][]fs.DirEntry, error) { + children := map[string]map[string]bool{".": {}} // directory -> child base name -> is a directory + + for name := range files { + dir := path.Dir(name) + addMapChild(children, dir, path.Base(name), false) + + // every ancestor of the file holds a directory + for dir != "." { + parent := path.Dir(dir) + addMapChild(children, parent, path.Base(dir), true) + dir = parent + } + } + + dirs := make(map[string][]fs.DirEntry, len(children)) + for dir, names := range children { + if _, isFile := files[dir]; isFile { + return nil, &fs.PathError{Op: "newmapfs", Path: dir, Err: errNameIsFileAndDir} + } + + entries := make([]fs.DirEntry, 0, len(names)) + for base, isDir := range names { + if isDir { + entries = append(entries, mapDirInfo{name: base}) + + continue + } + + entries = append(entries, mapFileInfo{name: base, file: files[path.Join(dir, base)]}) + } + + slices.SortFunc(entries, func(a, b fs.DirEntry) int { + return strings.Compare(a.Name(), b.Name()) + }) + dirs[dir] = entries + } + + return dirs, nil +} + +// addMapChild records that a directory holds an entry. +func addMapChild(children map[string]map[string]bool, dir, base string, isDir bool) { + entries, exists := children[dir] + if !exists { + entries = make(map[string]bool) + children[dir] = entries + } + + entries[base] = isDir +} + +// mapFileInfo reports the metadata of a file of a [MapFS]. It is both a [fs.FileInfo] and a [fs.DirEntry]. +type mapFileInfo struct { + name string + file MapFile +} + +func (i mapFileInfo) Name() string { return i.name } +func (i mapFileInfo) Size() int64 { return int64(len(i.file.Data)) } +func (i mapFileInfo) Mode() fs.FileMode { return i.file.Mode } +func (i mapFileInfo) Type() fs.FileMode { return i.file.Mode.Type() } +func (i mapFileInfo) ModTime() time.Time { return i.file.ModTime } +func (i mapFileInfo) IsDir() bool { return false } +func (i mapFileInfo) Sys() any { return i.file.Sys } +func (i mapFileInfo) Info() (fs.FileInfo, error) { return i, nil } + +// mapDirInfo reports the metadata of a directory of a [MapFS]. It is both a [fs.FileInfo] and a [fs.DirEntry]. +// +// Directories are implied by the names of the files, so they carry no metadata of their own. +type mapDirInfo struct { + name string +} + +func (i mapDirInfo) Name() string { return i.name } +func (i mapDirInfo) Size() int64 { return 0 } +func (i mapDirInfo) Mode() fs.FileMode { return DefaultDirMode } +func (i mapDirInfo) Type() fs.FileMode { return fs.ModeDir } +func (i mapDirInfo) ModTime() time.Time { return time.Time{} } +func (i mapDirInfo) IsDir() bool { return true } +func (i mapDirInfo) Sys() any { return nil } +func (i mapDirInfo) Info() (fs.FileInfo, error) { return i, nil } + +// openMapFile is the [fs.File] returned when opening a file of a [MapFS]. +type openMapFile struct { + info mapFileInfo + reader *bytes.Reader +} + +func (f *openMapFile) Stat() (fs.FileInfo, error) { return f.info, nil } + +func (f *openMapFile) Close() error { return nil } + +func (f *openMapFile) Read(p []byte) (int, error) { return f.reader.Read(p) } + +func (f *openMapFile) Seek(offset int64, whence int) (int64, error) { + return f.reader.Seek(offset, whence) +} + +// openMapDir is the [fs.ReadDirFile] returned when opening a directory of a [MapFS]. +type openMapDir struct { + info mapDirInfo + entries []fs.DirEntry + offset int +} + +func (d *openMapDir) Stat() (fs.FileInfo, error) { return d.info, nil } + +func (d *openMapDir) Close() error { return nil } + +func (d *openMapDir) Read([]byte) (int, error) { + return 0, &fs.PathError{Op: "read", Path: d.info.name, Err: errIsDir} +} + +// ReadDir returns the next n entries, or all the remaining ones when n is not positive. +func (d *openMapDir) ReadDir(n int) ([]fs.DirEntry, error) { + remaining := len(d.entries) - d.offset + if n <= 0 { + entries := slices.Clone(d.entries[d.offset:]) + d.offset = len(d.entries) + + return entries, nil + } + + if remaining == 0 { + return nil, io.EOF + } + + n = min(n, remaining) + entries := slices.Clone(d.entries[d.offset : d.offset+n]) + d.offset += n + + return entries, nil +} diff --git a/vendor/github.com/go-openapi/swag/fileutils/opaque.go b/vendor/github.com/go-openapi/swag/fileutils/opaque.go new file mode 100644 index 000000000..526e86736 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/fileutils/opaque.go @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package fileutils + +import ( + "io/fs" + "path" +) + +// OpaqueDirFS is a file system that entirely owns some of its directories. +// +// [OverlayFS] neither merges an opaque directory with the layers below it, +// nor resolves any name under it against them: +// what the owning layer holds replaces what they hold under the same name. +// +// [OpaqueFS] is the implementation provided by this package. +type OpaqueDirFS interface { + fs.FS + + // IsOpaqueDir reports whether a directory was declared as entirely owned by this file system. + // + // It answers from the declarations given to [NewOpaqueFS], and does not check that the file + // system holds the directory: a name declared opaque reports true either way. + // [OverlayFS] consults it only for a layer that holds the directory. + IsOpaqueDir(name string) bool +} + +// OpaqueDirsAll declares that a file system owns every directory it holds, except its root. +// +// It is the only pattern recognized by [NewOpaqueFS]: directories are otherwise matched +// by their exact name. +const OpaqueDirsAll = "*" + +// OpaqueFS makes a [fs.FS] into an [OpaqueDirFS], by declaring the directories that it owns. +type OpaqueFS struct { + fs.FS + + opaqueDirs map[string]struct{} +} + +// NewOpaqueFS declares the directories that a file system entirely owns, +// so that they shadow the layers below when it is stacked in an [OverlayFS]. +// +// Directories are slash-separated paths, as accepted by [fs.ValidPath], and are cleaned. +// Declaring "." makes the whole file system opaque, down from its root. +// Declaring [OpaqueDirsAll] makes every directory opaque but the root, +// so that the root still merges and the layers below keep contributing what they hold beside it. +// +// Declaring a directory that this file system does not hold changes nothing once it is stacked. +// [OverlayFS] skips a layer that does not hold the directory before it consults opacity, +// so the layers below keep resolving that directory and everything under it. +func NewOpaqueFS(base fs.FS, dirs ...string) *OpaqueFS { + opaqueDirs := make(map[string]struct{}, len(dirs)) + for _, dir := range dirs { + opaqueDirs[path.Clean(dir)] = struct{}{} + } + + return &OpaqueFS{ + FS: base, + opaqueDirs: opaqueDirs, + } +} + +// IsOpaqueDir reports whether a directory was declared as entirely owned by this file system. +// +// It answers from the declarations given to [NewOpaqueFS], and does not check that the file +// system holds the directory: a name declared opaque reports true either way. +// [OverlayFS] consults it only for a layer that holds the directory. +func (f *OpaqueFS) IsOpaqueDir(name string) bool { + if _, isOpaque := f.opaqueDirs[name]; isOpaque { + return true + } + + if name == "." { + // the root is owned only when it is declared explicitly + return false + } + + _, ownsAll := f.opaqueDirs[OpaqueDirsAll] + + return ownsAll +} + +// ReadFile reads a file from the wrapped file system. +func (f *OpaqueFS) ReadFile(name string) ([]byte, error) { + return fs.ReadFile(f.FS, name) +} + +// Stat returns the [fs.FileInfo] of a name in the wrapped file system. +func (f *OpaqueFS) Stat(name string) (fs.FileInfo, error) { + return fs.Stat(f.FS, name) +} + +// ReadDir lists a directory of the wrapped file system. +func (f *OpaqueFS) ReadDir(name string) ([]fs.DirEntry, error) { + return fs.ReadDir(f.FS, name) +} diff --git a/vendor/github.com/go-openapi/swag/fileutils/overlay.go b/vendor/github.com/go-openapi/swag/fileutils/overlay.go new file mode 100644 index 000000000..d67ab6a5f --- /dev/null +++ b/vendor/github.com/go-openapi/swag/fileutils/overlay.go @@ -0,0 +1,345 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package fileutils + +import ( + "errors" + "io" + "io/fs" + "path" + "slices" + "strings" + "syscall" +) + +// OverlayFS is a read-only [fs.FS] that stacks overlays on top of a base file system. +// +// A name is resolved in the topmost layer that holds it, then down to the base. +// See [NewOverlayFS] for the order in which layers are stacked. +// When the name is absent from all layers, every method returns a [fs.PathError] that reports +// the name and matches [fs.ErrNotExist]. +// +// [OverlayFS] implements [fs.FS], [fs.ReadFileFS], [fs.StatFS] and [fs.ReadDirFS], but not [fs.GlobFS]. +// A directory returns an error when the resolved layer does not support reading directories. +// +// Directories are merged: a directory reports the union of the entries held by every layer, +// and the topmost layer wins whenever the same name is held by several of them. +// A layer may claim a directory for itself with [NewOpaqueFS], which stops the merge +// and hides everything the lower layers hold under that directory. +type OverlayFS struct { + layers []fs.FS +} + +// NewOverlayFS builds an overlay file system from a base file system and a list of overlays. +// +// Overlays are stacked in the order in which they are provided: +// the last one sits on top and is resolved first, then the preceding ones in reverse order. +// The base is always resolved last. +// +// An empty list of overlays yields a file system that resolves against the base alone. +func NewOverlayFS(base fs.FS, overlays ...fs.FS) *OverlayFS { + layers := make([]fs.FS, 0, len(overlays)+1) + for _, overlay := range slices.Backward(overlays) { + layers = append(layers, overlay) + } + layers = append(layers, base) + + return &OverlayFS{ + layers: layers, + } +} + +// Open opens a file, resolving layers from the topmost overlay down to the base. +// +// Opening a directory yields a [fs.ReadDirFile] that reports the same entries as +// [OverlayFS.ReadDir], so that a merged directory reads alike either way. +func (f *OverlayFS) Open(name string) (fs.File, error) { + file, err := f.openInLayers(name) + if err != nil { + return nil, err + } + + info, err := file.Stat() + if err != nil { + _ = file.Close() + + return nil, err + } + + if !info.IsDir() { + return file, nil + } + + // the entries of a directory come from every layer, not from the one that resolved it + _ = file.Close() + + entries, err := f.readMergedDir(name) + if err != nil { + return nil, err + } + + return &mergedDir{name: name, info: info, entries: entries}, nil +} + +// ReadFile reads a file, resolving layers from the topmost overlay down to the base. +func (f *OverlayFS) ReadFile(name string) ([]byte, error) { + layer, err := f.findInLayers("open", name) + if err != nil { + return nil, err + } + + return fs.ReadFile(layer, name) +} + +// Stat returns the [fs.FileInfo] of a file, resolving layers from the topmost overlay down to the base. +func (f *OverlayFS) Stat(name string) (fs.FileInfo, error) { + layer, err := f.findInLayers("stat", name) + if err != nil { + return nil, err + } + + return fs.Stat(layer, name) +} + +// ReadDir lists a directory, resolving layers from the topmost overlay down to the base. +// +// The entries of every layer holding the directory are merged, sorted by file name, +// and a name held by several layers is reported by the topmost of them. +// The merge stops at the topmost layer that owns the directory, as declared by [NewOpaqueFS]. +func (f *OverlayFS) ReadDir(name string) ([]fs.DirEntry, error) { + return f.readMergedDir(name) +} + +// readMergedDir collects the entries of every layer that holds a directory. +// +// A name that is not a directory in some layer shadows the layers below it, +// just like a regular file does. +func (f *OverlayFS) readMergedDir(name string) ([]fs.DirEntry, error) { + var ( + merged []fs.DirEntry + found bool + ) + seen := make(map[string]struct{}) + + for _, layer := range f.layers { + info, err := fs.Stat(layer, name) + if err != nil { + if !isNotFound(err) { + return nil, err + } + + if ownsSubtreeOf(layer, name) { + break + } + + continue + } + + if !info.IsDir() { + if found { + // a directory held by an upper layer shadows this entry + break + } + + // let the layer report why the name cannot be listed + return fs.ReadDir(layer, name) + } + + entries, err := fs.ReadDir(layer, name) + if err != nil { + return nil, err + } + + found = true + for _, entry := range entries { + if _, isShadowed := seen[entry.Name()]; isShadowed { + continue + } + + seen[entry.Name()] = struct{}{} + merged = append(merged, entry) + } + + if declaresOpaqueDir(layer, name) || ownsSubtreeOf(layer, name) { + // this layer owns the directory, or one of its parents: + // the layers below it contribute nothing + break + } + } + + if !found { + return nil, notFound("readdir", name) + } + + slices.SortFunc(merged, func(a, b fs.DirEntry) int { + return strings.Compare(a.Name(), b.Name()) + }) + + return merged, nil +} + +// openInLayers opens a name in the topmost layer that holds it. +func (f *OverlayFS) openInLayers(name string) (fs.File, error) { + for _, layer := range f.layers { + file, err := layer.Open(name) + if err == nil { + return file, nil + } + + if !isNotFound(err) { + return nil, err + } + + if ownsSubtreeOf(layer, name) { + break + } + } + + return nil, notFound("open", name) +} + +// findInLayers returns the topmost layer that holds a name. +// +// op names the operation reported by the [fs.PathError] raised when no layer holds the name. +func (f *OverlayFS) findInLayers(op, name string) (fs.FS, error) { + for _, layer := range f.layers { + err := probeInLayer(layer, name) + if err == nil { + return layer, nil + } + + if !isNotFound(err) { + return nil, err + } + + if ownsSubtreeOf(layer, name) { + break + } + } + + return nil, notFound(op, name) +} + +// probeInLayer reports the error raised by a layer when looking a name up. +// +// A layer implementing [fs.StatFS] is probed with Stat. +// Otherwise the name is opened, then closed again. +func probeInLayer(layer fs.FS, name string) error { + if statFS, supportsStat := layer.(fs.StatFS); supportsStat { + _, err := statFS.Stat(name) + + return err + } + + file, err := layer.Open(name) + if err == nil { + _ = file.Close() + } + + return err +} + +// declaresOpaqueDir tells whether a layer marks a directory as entirely owned. +func declaresOpaqueDir(layer fs.FS, name string) bool { + opaque, isOpaqueFS := layer.(OpaqueDirFS) + + return isOpaqueFS && opaque.IsOpaqueDir(name) +} + +// ownsDir tells whether a layer marks a directory as entirely owned, and holds it. +func ownsDir(layer fs.FS, name string) bool { + if !declaresOpaqueDir(layer, name) { + return false + } + + info, err := fs.Stat(layer, name) + + return err == nil && info.IsDir() +} + +// ownsSubtreeOf tells whether a layer owns one of the parent directories of a name. +// +// The layers below such a layer hold nothing that is reachable under that name. +func ownsSubtreeOf(layer fs.FS, name string) bool { + if _, isOpaqueFS := layer.(OpaqueDirFS); !isOpaqueFS { + return false + } + + for dir := path.Dir(name); ; { + if ownsDir(layer, dir) { + return true + } + + // "." and "/" are their own parent, so this is where the walk up ends + parent := path.Dir(dir) + if parent == dir { + return false + } + + dir = parent + } +} + +// errIsDir is reported when a merged directory is read as a regular file. +var errIsDir = errors.New("is a directory") + +// mergedDir is the [fs.ReadDirFile] returned when opening a directory with merged layers. +// +// Its [fs.FileInfo] is the one of the topmost layer holding the directory, +// while its entries come from all the layers holding it. +type mergedDir struct { + name string + info fs.FileInfo + entries []fs.DirEntry + offset int +} + +func (d *mergedDir) Stat() (fs.FileInfo, error) { return d.info, nil } + +func (d *mergedDir) Close() error { return nil } + +func (d *mergedDir) Read([]byte) (int, error) { + return 0, &fs.PathError{Op: "read", Path: d.name, Err: errIsDir} +} + +// ReadDir returns the next n entries, or all the remaining ones when n is not positive. +func (d *mergedDir) ReadDir(n int) ([]fs.DirEntry, error) { + remaining := len(d.entries) - d.offset + if n <= 0 { + entries := d.entries[d.offset:] + d.offset = len(d.entries) + + return entries, nil + } + + if remaining == 0 { + return nil, io.EOF + } + + n = min(n, remaining) + entries := d.entries[d.offset : d.offset+n] + d.offset += n + + return entries, nil +} + +// notFound builds the error reported when a name is absent from all layers. +// +// It mirrors what the os package raises for a missing file, +// so that a caller may retrieve the name from the error alone. +func notFound(op, name string) error { + return &fs.PathError{Op: op, Path: name, Err: fs.ErrNotExist} +} + +// isNotFound tells whether an error raised by a layer means that the name is absent from that layer. +// +// The whole error chain is inspected, so a layer that wraps its errors resolves like any other, +// and so does a layer that is itself an [OverlayFS]. +// +// [syscall.ENOTDIR] is matched explicitly, because a path traversing a regular file +// does not report [fs.ErrNotExist] on all platforms. +// Any other error stops the resolution and is reported to the caller. +func isNotFound(err error) bool { + return errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) +} diff --git a/vendor/github.com/go-openapi/swag/fileutils/path.go b/vendor/github.com/go-openapi/swag/fileutils/path.go index dd09f690b..647df7609 100644 --- a/vendor/github.com/go-openapi/swag/fileutils/path.go +++ b/vendor/github.com/go-openapi/swag/fileutils/path.go @@ -10,10 +10,17 @@ import ( "strings" ) -// GOPATHKey represents the env key for gopath +// GOPATHKey is the name of the environment variable that holds the go search path. const GOPATHKey = "GOPATH" -// FindInSearchPath finds a package in a provided lists of paths +// FindInSearchPath finds a package in a list of search paths. +// +// searchPath lists directories separated by the OS path separator, +// in the form accepted by [filepath.SplitList]. +// Each directory is probed for a src/pkg subdirectory. +// +// It returns the first match, with symlinks resolved, +// or an empty string when the package is not found in any of the directories. func FindInSearchPath(searchPath, pkg string) string { pathsList := filepath.SplitList(searchPath) for _, path := range pathsList { @@ -26,7 +33,9 @@ func FindInSearchPath(searchPath, pkg string) string { return "" } -// FindInGoSearchPath finds a package in the $GOPATH:$GOROOT +// FindInGoSearchPath finds a package in $GOPATH and $GOROOT. +// +// It returns an empty string when the package is not found. // // Deprecated: this function is no longer relevant with modern go. // It uses [runtime.GOROOT] under the hood, which is deprecated as of go1.24. @@ -34,7 +43,10 @@ func FindInGoSearchPath(pkg string) string { return FindInSearchPath(FullGoSearchPath(), pkg) } -// FullGoSearchPath gets the search paths for finding packages +// FullGoSearchPath returns the search paths in which a package may be found. +// +// It joins $GOPATH, which defaults to $HOME/go when unset, with [runtime.GOROOT]. +// The two are separated by a colon, so the result is not usable on windows. // // Deprecated: this function is no longer relevant with modern go. // It uses [runtime.GOROOT] under the hood, which is deprecated as of go1.24. diff --git a/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/options.go b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/options.go index f114f1f1a..cfcd42685 100644 --- a/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/options.go +++ b/vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/options.go @@ -10,6 +10,11 @@ package json // It mirrors the limit enforced by the standard library's [encoding/json] decoder // (see encoding/json's internal maxNestingDepth), which this adapter would otherwise // not benefit from since it drives [encoding/json.Decoder.Token] directly. +// +// With encoding/json v2 (GOEXPERIMENT=jsonv2, the default from go1.27), Token applies +// that same limit itself and reports "exceeded max depth" before this counter fires. +// Set a lower limit with [WithMaxNestingDepth] to be rejected earlier than the stdlib; +// a higher one does not lift the stdlib's own 10,000-level ceiling on the decode path. const defaultMaxNestingDepth = 10000 // Option selects options for the stdlib adapter. diff --git a/vendor/github.com/go-openapi/swag/revive.toml b/vendor/github.com/go-openapi/swag/revive.toml new file mode 100644 index 000000000..eef9c1712 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/revive.toml @@ -0,0 +1,18 @@ +# revive configuration used by codefactor.io. +# +# This is redundant with .golangci.yml, since revive is part of golangci-lint, +# but we need to configure it to remain consistent across reporting tools. + +ignore-generated-header = false +severity = "warning" +confidence = 0.8 +error-code = 0 +warning-code = 0 +enable-default-rules = true + +[rule.exported] +disabled = true +[rule.cyclomatic] +arguments = [25] +[rule.cognitive-complexity] +arguments = [25] diff --git a/vendor/github.com/muesli/reflow/ansi/ansi.go b/vendor/github.com/muesli/reflow/ansi/ansi.go deleted file mode 100644 index f3d0700af..000000000 --- a/vendor/github.com/muesli/reflow/ansi/ansi.go +++ /dev/null @@ -1,7 +0,0 @@ -package ansi - -const Marker = '\x1B' - -func IsTerminator(c rune) bool { - return (c >= 0x40 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) -} diff --git a/vendor/github.com/muesli/reflow/ansi/buffer.go b/vendor/github.com/muesli/reflow/ansi/buffer.go deleted file mode 100644 index 471bcaf78..000000000 --- a/vendor/github.com/muesli/reflow/ansi/buffer.go +++ /dev/null @@ -1,40 +0,0 @@ -package ansi - -import ( - "bytes" - - "github.com/mattn/go-runewidth" -) - -// Buffer is a buffer aware of ANSI escape sequences. -type Buffer struct { - bytes.Buffer -} - -// PrintableRuneWidth returns the cell width of all printable runes in the -// buffer. -func (w Buffer) PrintableRuneWidth() int { - return PrintableRuneWidth(w.String()) -} - -// PrintableRuneWidth returns the cell width of the given string. -func PrintableRuneWidth(s string) int { - var n int - var ansi bool - - for _, c := range s { - if c == Marker { - // ANSI escape sequence - ansi = true - } else if ansi { - if IsTerminator(c) { - // ANSI sequence terminated - ansi = false - } - } else { - n += runewidth.RuneWidth(c) - } - } - - return n -} diff --git a/vendor/github.com/muesli/reflow/ansi/writer.go b/vendor/github.com/muesli/reflow/ansi/writer.go deleted file mode 100644 index a6aaa1ec6..000000000 --- a/vendor/github.com/muesli/reflow/ansi/writer.go +++ /dev/null @@ -1,76 +0,0 @@ -package ansi - -import ( - "bytes" - "io" - "unicode/utf8" -) - -type Writer struct { - Forward io.Writer - - ansi bool - ansiseq bytes.Buffer - lastseq bytes.Buffer - seqchanged bool - runeBuf []byte -} - -// Write is used to write content to the ANSI buffer. -func (w *Writer) Write(b []byte) (int, error) { - for _, c := range string(b) { - if c == Marker { - // ANSI escape sequence - w.ansi = true - w.seqchanged = true - _, _ = w.ansiseq.WriteRune(c) - } else if w.ansi { - _, _ = w.ansiseq.WriteRune(c) - if IsTerminator(c) { - // ANSI sequence terminated - w.ansi = false - - if bytes.HasSuffix(w.ansiseq.Bytes(), []byte("[0m")) { - // reset sequence - w.lastseq.Reset() - w.seqchanged = false - } else if c == 'm' { - // color code - _, _ = w.lastseq.Write(w.ansiseq.Bytes()) - } - - _, _ = w.ansiseq.WriteTo(w.Forward) - } - } else { - _, err := w.writeRune(c) - if err != nil { - return 0, err - } - } - } - - return len(b), nil -} - -func (w *Writer) writeRune(r rune) (int, error) { - if w.runeBuf == nil { - w.runeBuf = make([]byte, utf8.UTFMax) - } - n := utf8.EncodeRune(w.runeBuf, r) - return w.Forward.Write(w.runeBuf[:n]) -} - -func (w *Writer) LastSequence() string { - return w.lastseq.String() -} - -func (w *Writer) ResetAnsi() { - if !w.seqchanged { - return - } - _, _ = w.Forward.Write([]byte("\x1b[0m")) -} - -func (w *Writer) RestoreAnsi() { - _, _ = w.Forward.Write(w.lastseq.Bytes()) -} diff --git a/vendor/github.com/muesli/reflow/truncate/truncate.go b/vendor/github.com/muesli/reflow/truncate/truncate.go deleted file mode 100644 index 5aab5f896..000000000 --- a/vendor/github.com/muesli/reflow/truncate/truncate.go +++ /dev/null @@ -1,120 +0,0 @@ -package truncate - -import ( - "bytes" - "io" - - "github.com/mattn/go-runewidth" - - "github.com/muesli/reflow/ansi" -) - -type Writer struct { - width uint - tail string - - ansiWriter *ansi.Writer - buf bytes.Buffer - ansi bool -} - -func NewWriter(width uint, tail string) *Writer { - w := &Writer{ - width: width, - tail: tail, - } - w.ansiWriter = &ansi.Writer{ - Forward: &w.buf, - } - return w -} - -func NewWriterPipe(forward io.Writer, width uint, tail string) *Writer { - return &Writer{ - width: width, - tail: tail, - ansiWriter: &ansi.Writer{ - Forward: forward, - }, - } -} - -// Bytes is shorthand for declaring a new default truncate-writer instance, -// used to immediately truncate a byte slice. -func Bytes(b []byte, width uint) []byte { - return BytesWithTail(b, width, []byte("")) -} - -// Bytes is shorthand for declaring a new default truncate-writer instance, -// used to immediately truncate a byte slice. A tail is then added to the -// end of the byte slice. -func BytesWithTail(b []byte, width uint, tail []byte) []byte { - f := NewWriter(width, string(tail)) - _, _ = f.Write(b) - - return f.Bytes() -} - -// String is shorthand for declaring a new default truncate-writer instance, -// used to immediately truncate a string. -func String(s string, width uint) string { - return StringWithTail(s, width, "") -} - -// StringWithTail is shorthand for declaring a new default truncate-writer instance, -// used to immediately truncate a string. A tail is then added to the end of the -// string. -func StringWithTail(s string, width uint, tail string) string { - return string(BytesWithTail([]byte(s), width, []byte(tail))) -} - -// Write truncates content at the given printable cell width, leaving any -// ansi sequences intact. -func (w *Writer) Write(b []byte) (int, error) { - tw := ansi.PrintableRuneWidth(w.tail) - if w.width < uint(tw) { - return w.buf.WriteString(w.tail) - } - - w.width -= uint(tw) - var curWidth uint - - for _, c := range string(b) { - if c == ansi.Marker { - // ANSI escape sequence - w.ansi = true - } else if w.ansi { - if ansi.IsTerminator(c) { - // ANSI sequence terminated - w.ansi = false - } - } else { - curWidth += uint(runewidth.RuneWidth(c)) - } - - if curWidth > w.width { - n, err := w.buf.WriteString(w.tail) - if w.ansiWriter.LastSequence() != "" { - w.ansiWriter.ResetAnsi() - } - return n, err - } - - _, err := w.ansiWriter.Write([]byte(string(c))) - if err != nil { - return 0, err - } - } - - return len(b), nil -} - -// Bytes returns the truncated result as a byte slice. -func (w *Writer) Bytes() []byte { - return w.buf.Bytes() -} - -// String returns the truncated result as a string. -func (w *Writer) String() string { - return w.buf.String() -} diff --git a/vendor/github.com/muesli/reflow/wordwrap/wordwrap.go b/vendor/github.com/muesli/reflow/wordwrap/wordwrap.go deleted file mode 100644 index 488fb2100..000000000 --- a/vendor/github.com/muesli/reflow/wordwrap/wordwrap.go +++ /dev/null @@ -1,167 +0,0 @@ -package wordwrap - -import ( - "bytes" - "strings" - "unicode" - - "github.com/muesli/reflow/ansi" -) - -var ( - defaultBreakpoints = []rune{'-'} - defaultNewline = []rune{'\n'} -) - -// WordWrap contains settings and state for customisable text reflowing with -// support for ANSI escape sequences. This means you can style your terminal -// output without affecting the word wrapping algorithm. -type WordWrap struct { - Limit int - Breakpoints []rune - Newline []rune - KeepNewlines bool - - buf bytes.Buffer - space bytes.Buffer - word ansi.Buffer - - lineLen int - ansi bool -} - -// NewWriter returns a new instance of a word-wrapping writer, initialized with -// default settings. -func NewWriter(limit int) *WordWrap { - return &WordWrap{ - Limit: limit, - Breakpoints: defaultBreakpoints, - Newline: defaultNewline, - KeepNewlines: true, - } -} - -// Bytes is shorthand for declaring a new default WordWrap instance, -// used to immediately word-wrap a byte slice. -func Bytes(b []byte, limit int) []byte { - f := NewWriter(limit) - _, _ = f.Write(b) - _ = f.Close() - - return f.Bytes() -} - -// String is shorthand for declaring a new default WordWrap instance, -// used to immediately word-wrap a string. -func String(s string, limit int) string { - return string(Bytes([]byte(s), limit)) -} - -func (w *WordWrap) addSpace() { - w.lineLen += w.space.Len() - _, _ = w.buf.Write(w.space.Bytes()) - w.space.Reset() -} - -func (w *WordWrap) addWord() { - if w.word.Len() > 0 { - w.addSpace() - w.lineLen += w.word.PrintableRuneWidth() - _, _ = w.buf.Write(w.word.Bytes()) - w.word.Reset() - } -} - -func (w *WordWrap) addNewLine() { - _, _ = w.buf.WriteRune('\n') - w.lineLen = 0 - w.space.Reset() -} - -func inGroup(a []rune, c rune) bool { - for _, v := range a { - if v == c { - return true - } - } - return false -} - -// Write is used to write more content to the word-wrap buffer. -func (w *WordWrap) Write(b []byte) (int, error) { - if w.Limit == 0 { - return w.buf.Write(b) - } - - s := string(b) - if !w.KeepNewlines { - s = strings.Replace(strings.TrimSpace(s), "\n", " ", -1) - } - - for _, c := range s { - if c == '\x1B' { - // ANSI escape sequence - _, _ = w.word.WriteRune(c) - w.ansi = true - } else if w.ansi { - _, _ = w.word.WriteRune(c) - if (c >= 0x40 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) { - // ANSI sequence terminated - w.ansi = false - } - } else if inGroup(w.Newline, c) { - // end of current line - // see if we can add the content of the space buffer to the current line - if w.word.Len() == 0 { - if w.lineLen+w.space.Len() > w.Limit { - w.lineLen = 0 - } else { - // preserve whitespace - _, _ = w.buf.Write(w.space.Bytes()) - } - w.space.Reset() - } - - w.addWord() - w.addNewLine() - } else if unicode.IsSpace(c) { - // end of current word - w.addWord() - _, _ = w.space.WriteRune(c) - } else if inGroup(w.Breakpoints, c) { - // valid breakpoint - w.addSpace() - w.addWord() - _, _ = w.buf.WriteRune(c) - } else { - // any other character - _, _ = w.word.WriteRune(c) - - // add a line break if the current word would exceed the line's - // character limit - if w.lineLen+w.space.Len()+w.word.PrintableRuneWidth() > w.Limit && - w.word.PrintableRuneWidth() < w.Limit { - w.addNewLine() - } - } - } - - return len(b), nil -} - -// Close will finish the word-wrap operation. Always call it before trying to -// retrieve the final result. -func (w *WordWrap) Close() error { - w.addWord() - return nil -} - -// Bytes returns the word-wrapped result as a byte slice. -func (w *WordWrap) Bytes() []byte { - return w.buf.Bytes() -} - -// String returns the word-wrapped result as a string. -func (w *WordWrap) String() string { - return w.buf.String() -} diff --git a/vendor/github.com/pulumi/pulumi-aws-native/sdk/go/aws/internal/pulumiUtilities.go b/vendor/github.com/pulumi/pulumi-aws-native/sdk/go/aws/internal/pulumiUtilities.go index 4e4c68b45..3f05e59fb 100644 --- a/vendor/github.com/pulumi/pulumi-aws-native/sdk/go/aws/internal/pulumiUtilities.go +++ b/vendor/github.com/pulumi/pulumi-aws-native/sdk/go/aws/internal/pulumiUtilities.go @@ -165,7 +165,7 @@ func callPlainInner( func PkgResourceDefaultOpts(opts []pulumi.ResourceOption) []pulumi.ResourceOption { defaults := []pulumi.ResourceOption{} - version := semver.MustParse("1.75.0") + version := semver.MustParse("1.76.0") if !version.Equals(semver.Version{}) { defaults = append(defaults, pulumi.Version(version.String())) } @@ -176,7 +176,7 @@ func PkgResourceDefaultOpts(opts []pulumi.ResourceOption) []pulumi.ResourceOptio func PkgInvokeDefaultOpts(opts []pulumi.InvokeOption) []pulumi.InvokeOption { defaults := []pulumi.InvokeOption{} - version := semver.MustParse("1.75.0") + version := semver.MustParse("1.76.0") if !version.Equals(semver.Version{}) { defaults = append(defaults, pulumi.Version(version.String())) } diff --git a/vendor/github.com/pulumi/pulumi-aws-native/sdk/go/aws/pulumi-plugin.json b/vendor/github.com/pulumi/pulumi-aws-native/sdk/go/aws/pulumi-plugin.json index 86ec630bf..647f80e24 100644 --- a/vendor/github.com/pulumi/pulumi-aws-native/sdk/go/aws/pulumi-plugin.json +++ b/vendor/github.com/pulumi/pulumi-aws-native/sdk/go/aws/pulumi-plugin.json @@ -1,5 +1,5 @@ { "resource": true, "name": "aws-native", - "version": "1.75.0" + "version": "1.76.0" } diff --git a/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2/amiLaunchPermission.go b/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2/amiLaunchPermission.go index 9ff934888..68bc17a97 100644 --- a/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2/amiLaunchPermission.go +++ b/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2/amiLaunchPermission.go @@ -104,6 +104,21 @@ import ( // // ## Import // +// ### Identity Schema +// +// #### Required +// +// * `imageId` (String) ID of the AMI. +// +// #### Optional +// +// * `accountId` (String) AWS Account where this resource is managed. +// * `group` (String) Name of the group for the launch permission. +// * `launchPermissionAccountId` (String) AWS account ID for the launch permission. +// * `organizationArn` (String) ARN of an organization for the launch permission. +// * `organizationalUnitArn` (String) ARN of an organizational unit for the launch permission. +// * `region` (String) Region where this resource is managed. +// // Using `pulumi import`, import AMI Launch Permissions using `[ACCOUNT-ID|GROUP-NAME|ORGANIZATION-ARN|ORGANIZATIONAL-UNIT-ARN]/IMAGE-ID`. For example: // // ```sh diff --git a/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/internal/pulumiUtilities.go b/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/internal/pulumiUtilities.go index 40bea8009..e0c8b09f2 100644 --- a/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/internal/pulumiUtilities.go +++ b/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/internal/pulumiUtilities.go @@ -165,7 +165,7 @@ func callPlainInner( func PkgResourceDefaultOpts(opts []pulumi.ResourceOption) []pulumi.ResourceOption { defaults := []pulumi.ResourceOption{} - version := semver.MustParse("7.42.0") + version := semver.MustParse("7.43.0") if !version.Equals(semver.Version{}) { defaults = append(defaults, pulumi.Version(version.String())) } @@ -176,7 +176,7 @@ func PkgResourceDefaultOpts(opts []pulumi.ResourceOption) []pulumi.ResourceOptio func PkgInvokeDefaultOpts(opts []pulumi.InvokeOption) []pulumi.InvokeOption { defaults := []pulumi.InvokeOption{} - version := semver.MustParse("7.42.0") + version := semver.MustParse("7.43.0") if !version.Equals(semver.Version{}) { defaults = append(defaults, pulumi.Version(version.String())) } diff --git a/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lb/pulumiTypes.go b/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lb/pulumiTypes.go index 74fa02ecc..191f492e4 100644 --- a/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lb/pulumiTypes.go +++ b/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lb/pulumiTypes.go @@ -4088,7 +4088,7 @@ type ListenerRuleCondition struct { PathPattern *ListenerRuleConditionPathPattern `pulumi:"pathPattern"` // Query strings to match. Query String block fields documented below. QueryStrings []ListenerRuleConditionQueryString `pulumi:"queryStrings"` - // Contains a single `values` item which is a list of source IP CIDR notations to match. You can use both IPv4 and IPv6 addresses. Wildcards are not supported. Condition is satisfied if the source IP address of the request matches one of the CIDR blocks. Condition is not satisfied by the addresses in the `X-Forwarded-For` header, use `httpHeader` condition instead. + // Source IP address to match. For ALB, use `values` to specify CIDR ranges. For NLB, use `ipAddressType` to match the IP address type (`ipv4` or `ipv6`). Source IP block fields documented below. // // > **NOTE::** Exactly one of `hostHeader`, `httpHeader`, `httpRequestMethod`, `pathPattern`, `queryString` or `sourceIp` must be set per condition. SourceIp *ListenerRuleConditionSourceIp `pulumi:"sourceIp"` @@ -4116,7 +4116,7 @@ type ListenerRuleConditionArgs struct { PathPattern ListenerRuleConditionPathPatternPtrInput `pulumi:"pathPattern"` // Query strings to match. Query String block fields documented below. QueryStrings ListenerRuleConditionQueryStringArrayInput `pulumi:"queryStrings"` - // Contains a single `values` item which is a list of source IP CIDR notations to match. You can use both IPv4 and IPv6 addresses. Wildcards are not supported. Condition is satisfied if the source IP address of the request matches one of the CIDR blocks. Condition is not satisfied by the addresses in the `X-Forwarded-For` header, use `httpHeader` condition instead. + // Source IP address to match. For ALB, use `values` to specify CIDR ranges. For NLB, use `ipAddressType` to match the IP address type (`ipv4` or `ipv6`). Source IP block fields documented below. // // > **NOTE::** Exactly one of `hostHeader`, `httpHeader`, `httpRequestMethod`, `pathPattern`, `queryString` or `sourceIp` must be set per condition. SourceIp ListenerRuleConditionSourceIpPtrInput `pulumi:"sourceIp"` @@ -4198,7 +4198,7 @@ func (o ListenerRuleConditionOutput) QueryStrings() ListenerRuleConditionQuerySt return o.ApplyT(func(v ListenerRuleCondition) []ListenerRuleConditionQueryString { return v.QueryStrings }).(ListenerRuleConditionQueryStringArrayOutput) } -// Contains a single `values` item which is a list of source IP CIDR notations to match. You can use both IPv4 and IPv6 addresses. Wildcards are not supported. Condition is satisfied if the source IP address of the request matches one of the CIDR blocks. Condition is not satisfied by the addresses in the `X-Forwarded-For` header, use `httpHeader` condition instead. +// Source IP address to match. For ALB, use `values` to specify CIDR ranges. For NLB, use `ipAddressType` to match the IP address type (`ipv4` or `ipv6`). Source IP block fields documented below. // // > **NOTE::** Exactly one of `hostHeader`, `httpHeader`, `httpRequestMethod`, `pathPattern`, `queryString` or `sourceIp` must be set per condition. func (o ListenerRuleConditionOutput) SourceIp() ListenerRuleConditionSourceIpPtrOutput { @@ -4952,6 +4952,9 @@ func (o ListenerRuleConditionQueryStringArrayOutput) Index(i pulumi.IntInput) Li } type ListenerRuleConditionSourceIp struct { + // IP address type for Network Load Balancers. Valid values are `ipv4` and `ipv6`. + IpAddressType *string `pulumi:"ipAddressType"` + // List of source IP addresses in CIDR format for Application Load Balancers. Both IPv4 and IPv6 addresses can be used. Wildcards are not supported. Condition is satisfied if the source IP address of the request matches one of the CIDR blocks. Condition is not satisfied by the addresses in the `X-Forwarded-For` header, use `httpHeader` condition instead. Values []string `pulumi:"values"` } @@ -4967,6 +4970,9 @@ type ListenerRuleConditionSourceIpInput interface { } type ListenerRuleConditionSourceIpArgs struct { + // IP address type for Network Load Balancers. Valid values are `ipv4` and `ipv6`. + IpAddressType pulumi.StringPtrInput `pulumi:"ipAddressType"` + // List of source IP addresses in CIDR format for Application Load Balancers. Both IPv4 and IPv6 addresses can be used. Wildcards are not supported. Condition is satisfied if the source IP address of the request matches one of the CIDR blocks. Condition is not satisfied by the addresses in the `X-Forwarded-For` header, use `httpHeader` condition instead. Values pulumi.StringArrayInput `pulumi:"values"` } @@ -5047,6 +5053,12 @@ func (o ListenerRuleConditionSourceIpOutput) ToListenerRuleConditionSourceIpPtrO }).(ListenerRuleConditionSourceIpPtrOutput) } +// IP address type for Network Load Balancers. Valid values are `ipv4` and `ipv6`. +func (o ListenerRuleConditionSourceIpOutput) IpAddressType() pulumi.StringPtrOutput { + return o.ApplyT(func(v ListenerRuleConditionSourceIp) *string { return v.IpAddressType }).(pulumi.StringPtrOutput) +} + +// List of source IP addresses in CIDR format for Application Load Balancers. Both IPv4 and IPv6 addresses can be used. Wildcards are not supported. Condition is satisfied if the source IP address of the request matches one of the CIDR blocks. Condition is not satisfied by the addresses in the `X-Forwarded-For` header, use `httpHeader` condition instead. func (o ListenerRuleConditionSourceIpOutput) Values() pulumi.StringArrayOutput { return o.ApplyT(func(v ListenerRuleConditionSourceIp) []string { return v.Values }).(pulumi.StringArrayOutput) } @@ -5075,6 +5087,17 @@ func (o ListenerRuleConditionSourceIpPtrOutput) Elem() ListenerRuleConditionSour }).(ListenerRuleConditionSourceIpOutput) } +// IP address type for Network Load Balancers. Valid values are `ipv4` and `ipv6`. +func (o ListenerRuleConditionSourceIpPtrOutput) IpAddressType() pulumi.StringPtrOutput { + return o.ApplyT(func(v *ListenerRuleConditionSourceIp) *string { + if v == nil { + return nil + } + return v.IpAddressType + }).(pulumi.StringPtrOutput) +} + +// List of source IP addresses in CIDR format for Application Load Balancers. Both IPv4 and IPv6 addresses can be used. Wildcards are not supported. Condition is satisfied if the source IP address of the request matches one of the CIDR blocks. Condition is not satisfied by the addresses in the `X-Forwarded-For` header, use `httpHeader` condition instead. func (o ListenerRuleConditionSourceIpPtrOutput) Values() pulumi.StringArrayOutput { return o.ApplyT(func(v *ListenerRuleConditionSourceIp) []string { if v == nil { @@ -10349,7 +10372,7 @@ type GetListenerRuleActionJwtValidationAdditionalClaim struct { Format string `pulumi:"format"` // Name of the claim to validate. Name string `pulumi:"name"` - // Set of `key`-`value` pairs indicating the query string parameters to match. + // Set of source IP addresses in CIDR format for Application Load Balancers Values []string `pulumi:"values"` } @@ -10369,7 +10392,7 @@ type GetListenerRuleActionJwtValidationAdditionalClaimArgs struct { Format pulumi.StringInput `pulumi:"format"` // Name of the claim to validate. Name pulumi.StringInput `pulumi:"name"` - // Set of `key`-`value` pairs indicating the query string parameters to match. + // Set of source IP addresses in CIDR format for Application Load Balancers Values pulumi.StringArrayInput `pulumi:"values"` } @@ -10434,7 +10457,7 @@ func (o GetListenerRuleActionJwtValidationAdditionalClaimOutput) Name() pulumi.S return o.ApplyT(func(v GetListenerRuleActionJwtValidationAdditionalClaim) string { return v.Name }).(pulumi.StringOutput) } -// Set of `key`-`value` pairs indicating the query string parameters to match. +// Set of source IP addresses in CIDR format for Application Load Balancers func (o GetListenerRuleActionJwtValidationAdditionalClaimOutput) Values() pulumi.StringArrayOutput { return o.ApplyT(func(v GetListenerRuleActionJwtValidationAdditionalClaim) []string { return v.Values }).(pulumi.StringArrayOutput) } @@ -10616,7 +10639,8 @@ type GetListenerRuleCondition struct { // Query string parameters to match. // Detailed below. QueryStrings []GetListenerRuleConditionQueryString `pulumi:"queryStrings"` - // Contains a single attribute `values`, which contains a set of source IPs in CIDR notation. + // Source IP address to match. + // Detailed below. SourceIps []GetListenerRuleConditionSourceIp `pulumi:"sourceIps"` } @@ -10646,7 +10670,8 @@ type GetListenerRuleConditionArgs struct { // Query string parameters to match. // Detailed below. QueryStrings GetListenerRuleConditionQueryStringArrayInput `pulumi:"queryStrings"` - // Contains a single attribute `values`, which contains a set of source IPs in CIDR notation. + // Source IP address to match. + // Detailed below. SourceIps GetListenerRuleConditionSourceIpArrayInput `pulumi:"sourceIps"` } @@ -10732,7 +10757,8 @@ func (o GetListenerRuleConditionOutput) QueryStrings() GetListenerRuleConditionQ return o.ApplyT(func(v GetListenerRuleCondition) []GetListenerRuleConditionQueryString { return v.QueryStrings }).(GetListenerRuleConditionQueryStringArrayOutput) } -// Contains a single attribute `values`, which contains a set of source IPs in CIDR notation. +// Source IP address to match. +// Detailed below. func (o GetListenerRuleConditionOutput) SourceIps() GetListenerRuleConditionSourceIpArrayOutput { return o.ApplyT(func(v GetListenerRuleCondition) []GetListenerRuleConditionSourceIp { return v.SourceIps }).(GetListenerRuleConditionSourceIpArrayOutput) } @@ -10760,7 +10786,7 @@ func (o GetListenerRuleConditionArrayOutput) Index(i pulumi.IntInput) GetListene type GetListenerRuleConditionHostHeader struct { // Set of regular expressions to compare against the request URL. RegexValues []string `pulumi:"regexValues"` - // Set of `key`-`value` pairs indicating the query string parameters to match. + // Set of source IP addresses in CIDR format for Application Load Balancers Values []string `pulumi:"values"` } @@ -10778,7 +10804,7 @@ type GetListenerRuleConditionHostHeaderInput interface { type GetListenerRuleConditionHostHeaderArgs struct { // Set of regular expressions to compare against the request URL. RegexValues pulumi.StringArrayInput `pulumi:"regexValues"` - // Set of `key`-`value` pairs indicating the query string parameters to match. + // Set of source IP addresses in CIDR format for Application Load Balancers Values pulumi.StringArrayInput `pulumi:"values"` } @@ -10838,7 +10864,7 @@ func (o GetListenerRuleConditionHostHeaderOutput) RegexValues() pulumi.StringArr return o.ApplyT(func(v GetListenerRuleConditionHostHeader) []string { return v.RegexValues }).(pulumi.StringArrayOutput) } -// Set of `key`-`value` pairs indicating the query string parameters to match. +// Set of source IP addresses in CIDR format for Application Load Balancers func (o GetListenerRuleConditionHostHeaderOutput) Values() pulumi.StringArrayOutput { return o.ApplyT(func(v GetListenerRuleConditionHostHeader) []string { return v.Values }).(pulumi.StringArrayOutput) } @@ -10868,7 +10894,7 @@ type GetListenerRuleConditionHttpHeader struct { HttpHeaderName string `pulumi:"httpHeaderName"` // Set of regular expressions to compare against the request URL. RegexValues []string `pulumi:"regexValues"` - // Set of `key`-`value` pairs indicating the query string parameters to match. + // Set of source IP addresses in CIDR format for Application Load Balancers Values []string `pulumi:"values"` } @@ -10888,7 +10914,7 @@ type GetListenerRuleConditionHttpHeaderArgs struct { HttpHeaderName pulumi.StringInput `pulumi:"httpHeaderName"` // Set of regular expressions to compare against the request URL. RegexValues pulumi.StringArrayInput `pulumi:"regexValues"` - // Set of `key`-`value` pairs indicating the query string parameters to match. + // Set of source IP addresses in CIDR format for Application Load Balancers Values pulumi.StringArrayInput `pulumi:"values"` } @@ -10953,7 +10979,7 @@ func (o GetListenerRuleConditionHttpHeaderOutput) RegexValues() pulumi.StringArr return o.ApplyT(func(v GetListenerRuleConditionHttpHeader) []string { return v.RegexValues }).(pulumi.StringArrayOutput) } -// Set of `key`-`value` pairs indicating the query string parameters to match. +// Set of source IP addresses in CIDR format for Application Load Balancers func (o GetListenerRuleConditionHttpHeaderOutput) Values() pulumi.StringArrayOutput { return o.ApplyT(func(v GetListenerRuleConditionHttpHeader) []string { return v.Values }).(pulumi.StringArrayOutput) } @@ -10979,7 +11005,7 @@ func (o GetListenerRuleConditionHttpHeaderArrayOutput) Index(i pulumi.IntInput) } type GetListenerRuleConditionHttpRequestMethod struct { - // Set of `key`-`value` pairs indicating the query string parameters to match. + // Set of source IP addresses in CIDR format for Application Load Balancers Values []string `pulumi:"values"` } @@ -10995,7 +11021,7 @@ type GetListenerRuleConditionHttpRequestMethodInput interface { } type GetListenerRuleConditionHttpRequestMethodArgs struct { - // Set of `key`-`value` pairs indicating the query string parameters to match. + // Set of source IP addresses in CIDR format for Application Load Balancers Values pulumi.StringArrayInput `pulumi:"values"` } @@ -11050,7 +11076,7 @@ func (o GetListenerRuleConditionHttpRequestMethodOutput) ToGetListenerRuleCondit return o } -// Set of `key`-`value` pairs indicating the query string parameters to match. +// Set of source IP addresses in CIDR format for Application Load Balancers func (o GetListenerRuleConditionHttpRequestMethodOutput) Values() pulumi.StringArrayOutput { return o.ApplyT(func(v GetListenerRuleConditionHttpRequestMethod) []string { return v.Values }).(pulumi.StringArrayOutput) } @@ -11078,7 +11104,7 @@ func (o GetListenerRuleConditionHttpRequestMethodArrayOutput) Index(i pulumi.Int type GetListenerRuleConditionPathPattern struct { // Set of regular expressions to compare against the request URL. RegexValues []string `pulumi:"regexValues"` - // Set of `key`-`value` pairs indicating the query string parameters to match. + // Set of source IP addresses in CIDR format for Application Load Balancers Values []string `pulumi:"values"` } @@ -11096,7 +11122,7 @@ type GetListenerRuleConditionPathPatternInput interface { type GetListenerRuleConditionPathPatternArgs struct { // Set of regular expressions to compare against the request URL. RegexValues pulumi.StringArrayInput `pulumi:"regexValues"` - // Set of `key`-`value` pairs indicating the query string parameters to match. + // Set of source IP addresses in CIDR format for Application Load Balancers Values pulumi.StringArrayInput `pulumi:"values"` } @@ -11156,7 +11182,7 @@ func (o GetListenerRuleConditionPathPatternOutput) RegexValues() pulumi.StringAr return o.ApplyT(func(v GetListenerRuleConditionPathPattern) []string { return v.RegexValues }).(pulumi.StringArrayOutput) } -// Set of `key`-`value` pairs indicating the query string parameters to match. +// Set of source IP addresses in CIDR format for Application Load Balancers func (o GetListenerRuleConditionPathPatternOutput) Values() pulumi.StringArrayOutput { return o.ApplyT(func(v GetListenerRuleConditionPathPattern) []string { return v.Values }).(pulumi.StringArrayOutput) } @@ -11182,7 +11208,7 @@ func (o GetListenerRuleConditionPathPatternArrayOutput) Index(i pulumi.IntInput) } type GetListenerRuleConditionQueryString struct { - // Set of `key`-`value` pairs indicating the query string parameters to match. + // Set of source IP addresses in CIDR format for Application Load Balancers Values []GetListenerRuleConditionQueryStringValue `pulumi:"values"` } @@ -11198,7 +11224,7 @@ type GetListenerRuleConditionQueryStringInput interface { } type GetListenerRuleConditionQueryStringArgs struct { - // Set of `key`-`value` pairs indicating the query string parameters to match. + // Set of source IP addresses in CIDR format for Application Load Balancers Values GetListenerRuleConditionQueryStringValueArrayInput `pulumi:"values"` } @@ -11253,7 +11279,7 @@ func (o GetListenerRuleConditionQueryStringOutput) ToGetListenerRuleConditionQue return o } -// Set of `key`-`value` pairs indicating the query string parameters to match. +// Set of source IP addresses in CIDR format for Application Load Balancers func (o GetListenerRuleConditionQueryStringOutput) Values() GetListenerRuleConditionQueryStringValueArrayOutput { return o.ApplyT(func(v GetListenerRuleConditionQueryString) []GetListenerRuleConditionQueryStringValue { return v.Values @@ -11387,7 +11413,9 @@ func (o GetListenerRuleConditionQueryStringValueArrayOutput) Index(i pulumi.IntI } type GetListenerRuleConditionSourceIp struct { - // Set of `key`-`value` pairs indicating the query string parameters to match. + // IP address type for Network Load Balancers. + IpAddressType string `pulumi:"ipAddressType"` + // Set of source IP addresses in CIDR format for Application Load Balancers Values []string `pulumi:"values"` } @@ -11403,7 +11431,9 @@ type GetListenerRuleConditionSourceIpInput interface { } type GetListenerRuleConditionSourceIpArgs struct { - // Set of `key`-`value` pairs indicating the query string parameters to match. + // IP address type for Network Load Balancers. + IpAddressType pulumi.StringInput `pulumi:"ipAddressType"` + // Set of source IP addresses in CIDR format for Application Load Balancers Values pulumi.StringArrayInput `pulumi:"values"` } @@ -11458,7 +11488,12 @@ func (o GetListenerRuleConditionSourceIpOutput) ToGetListenerRuleConditionSource return o } -// Set of `key`-`value` pairs indicating the query string parameters to match. +// IP address type for Network Load Balancers. +func (o GetListenerRuleConditionSourceIpOutput) IpAddressType() pulumi.StringOutput { + return o.ApplyT(func(v GetListenerRuleConditionSourceIp) string { return v.IpAddressType }).(pulumi.StringOutput) +} + +// Set of source IP addresses in CIDR format for Application Load Balancers func (o GetListenerRuleConditionSourceIpOutput) Values() pulumi.StringArrayOutput { return o.ApplyT(func(v GetListenerRuleConditionSourceIp) []string { return v.Values }).(pulumi.StringArrayOutput) } diff --git a/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/pulumi-plugin.json b/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/pulumi-plugin.json index 86b4c26ed..3ca2f1984 100644 --- a/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/pulumi-plugin.json +++ b/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/pulumi-plugin.json @@ -1,5 +1,5 @@ { "resource": true, "name": "aws", - "version": "7.42.0" + "version": "7.43.0" } diff --git a/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/pulumiEnums.go b/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/pulumiEnums.go index 56b3df49d..a154eccf4 100644 --- a/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/pulumiEnums.go +++ b/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/pulumiEnums.go @@ -27,6 +27,7 @@ const ( RegionAPSoutheast3 = Region("ap-southeast-3") RegionAPSoutheast4 = Region("ap-southeast-4") RegionAPSoutheast5 = Region("ap-southeast-5") + RegionAPSoutheast6 = Region("ap-southeast-6") RegionAPSoutheast7 = Region("ap-southeast-7") RegionCACentral = Region("ca-central-1") RegionCAWest1 = Region("ca-west-1") @@ -49,12 +50,16 @@ const ( RegionUSWest2 = Region("us-west-2") RegionCNNorth1 = Region("cn-north-1") RegionCNNorthwest1 = Region("cn-northwest-1") + RegionEUSCDEEast1 = Region("eusc-de-east-1") RegionUSGovEast1 = Region("us-gov-east-1") RegionUSGovWest1 = Region("us-gov-west-1") RegionUSISOEast1 = Region("us-iso-east-1") RegionUSISOWest1 = Region("us-iso-west-1") RegionUSISOBEast1 = Region("us-isob-east-1") + RegionUSISOBWest1 = Region("us-isob-west-1") RegionEUISOEWest1 = Region("eu-isoe-west-1") + RegionUSISOFEast1 = Region("us-isof-east-1") + RegionUSISOFSouth1 = Region("us-isof-south-1") ) func (Region) ElementType() reflect.Type { @@ -192,6 +197,7 @@ func (o RegionPtrOutput) ToStringPtrOutputWithContext(ctx context.Context) pulum // RegionAPSoutheast3 // RegionAPSoutheast4 // RegionAPSoutheast5 +// RegionAPSoutheast6 // RegionAPSoutheast7 // RegionCACentral // RegionCAWest1 @@ -214,12 +220,16 @@ func (o RegionPtrOutput) ToStringPtrOutputWithContext(ctx context.Context) pulum // RegionUSWest2 // RegionCNNorth1 // RegionCNNorthwest1 +// RegionEUSCDEEast1 // RegionUSGovEast1 // RegionUSGovWest1 // RegionUSISOEast1 // RegionUSISOWest1 // RegionUSISOBEast1 +// RegionUSISOBWest1 // RegionEUISOEWest1 +// RegionUSISOFEast1 +// RegionUSISOFSouth1 type RegionInput interface { pulumi.Input diff --git a/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/pulumiTypes.go b/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/pulumiTypes.go index 757a6932b..721933cf9 100644 --- a/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/pulumiTypes.go +++ b/vendor/github.com/pulumi/pulumi-aws/sdk/v7/go/aws/pulumiTypes.go @@ -568,10 +568,14 @@ type ProviderEndpoint struct { // Use this to override the default service endpoint URL Account *string `pulumi:"account"` // Use this to override the default service endpoint URL + Accountaccess *string `pulumi:"accountaccess"` + // Use this to override the default service endpoint URL Acm *string `pulumi:"acm"` // Use this to override the default service endpoint URL Acmpca *string `pulumi:"acmpca"` // Use this to override the default service endpoint URL + Agentregistry *string `pulumi:"agentregistry"` + // Use this to override the default service endpoint URL Amg *string `pulumi:"amg"` // Use this to override the default service endpoint URL Amp *string `pulumi:"amp"` @@ -768,6 +772,8 @@ type ProviderEndpoint struct { // Use this to override the default service endpoint URL Directoryservice *string `pulumi:"directoryservice"` // Use this to override the default service endpoint URL + Directoryservicedata *string `pulumi:"directoryservicedata"` + // Use this to override the default service endpoint URL Dlm *string `pulumi:"dlm"` // Use this to override the default service endpoint URL Dms *string `pulumi:"dms"` @@ -906,6 +912,8 @@ type ProviderEndpoint struct { // Use this to override the default service endpoint URL Lambda *string `pulumi:"lambda"` // Use this to override the default service endpoint URL + Lambdacore *string `pulumi:"lambdacore"` + // Use this to override the default service endpoint URL Lambdamicrovms *string `pulumi:"lambdamicrovms"` // Use this to override the default service endpoint URL Launchwizard *string `pulumi:"launchwizard"` @@ -1214,10 +1222,14 @@ type ProviderEndpointArgs struct { // Use this to override the default service endpoint URL Account pulumi.StringPtrInput `pulumi:"account"` // Use this to override the default service endpoint URL + Accountaccess pulumi.StringPtrInput `pulumi:"accountaccess"` + // Use this to override the default service endpoint URL Acm pulumi.StringPtrInput `pulumi:"acm"` // Use this to override the default service endpoint URL Acmpca pulumi.StringPtrInput `pulumi:"acmpca"` // Use this to override the default service endpoint URL + Agentregistry pulumi.StringPtrInput `pulumi:"agentregistry"` + // Use this to override the default service endpoint URL Amg pulumi.StringPtrInput `pulumi:"amg"` // Use this to override the default service endpoint URL Amp pulumi.StringPtrInput `pulumi:"amp"` @@ -1414,6 +1426,8 @@ type ProviderEndpointArgs struct { // Use this to override the default service endpoint URL Directoryservice pulumi.StringPtrInput `pulumi:"directoryservice"` // Use this to override the default service endpoint URL + Directoryservicedata pulumi.StringPtrInput `pulumi:"directoryservicedata"` + // Use this to override the default service endpoint URL Dlm pulumi.StringPtrInput `pulumi:"dlm"` // Use this to override the default service endpoint URL Dms pulumi.StringPtrInput `pulumi:"dms"` @@ -1552,6 +1566,8 @@ type ProviderEndpointArgs struct { // Use this to override the default service endpoint URL Lambda pulumi.StringPtrInput `pulumi:"lambda"` // Use this to override the default service endpoint URL + Lambdacore pulumi.StringPtrInput `pulumi:"lambdacore"` + // Use this to override the default service endpoint URL Lambdamicrovms pulumi.StringPtrInput `pulumi:"lambdamicrovms"` // Use this to override the default service endpoint URL Launchwizard pulumi.StringPtrInput `pulumi:"launchwizard"` @@ -1904,6 +1920,11 @@ func (o ProviderEndpointOutput) Account() pulumi.StringPtrOutput { return o.ApplyT(func(v ProviderEndpoint) *string { return v.Account }).(pulumi.StringPtrOutput) } +// Use this to override the default service endpoint URL +func (o ProviderEndpointOutput) Accountaccess() pulumi.StringPtrOutput { + return o.ApplyT(func(v ProviderEndpoint) *string { return v.Accountaccess }).(pulumi.StringPtrOutput) +} + // Use this to override the default service endpoint URL func (o ProviderEndpointOutput) Acm() pulumi.StringPtrOutput { return o.ApplyT(func(v ProviderEndpoint) *string { return v.Acm }).(pulumi.StringPtrOutput) @@ -1914,6 +1935,11 @@ func (o ProviderEndpointOutput) Acmpca() pulumi.StringPtrOutput { return o.ApplyT(func(v ProviderEndpoint) *string { return v.Acmpca }).(pulumi.StringPtrOutput) } +// Use this to override the default service endpoint URL +func (o ProviderEndpointOutput) Agentregistry() pulumi.StringPtrOutput { + return o.ApplyT(func(v ProviderEndpoint) *string { return v.Agentregistry }).(pulumi.StringPtrOutput) +} + // Use this to override the default service endpoint URL func (o ProviderEndpointOutput) Amg() pulumi.StringPtrOutput { return o.ApplyT(func(v ProviderEndpoint) *string { return v.Amg }).(pulumi.StringPtrOutput) @@ -2404,6 +2430,11 @@ func (o ProviderEndpointOutput) Directoryservice() pulumi.StringPtrOutput { return o.ApplyT(func(v ProviderEndpoint) *string { return v.Directoryservice }).(pulumi.StringPtrOutput) } +// Use this to override the default service endpoint URL +func (o ProviderEndpointOutput) Directoryservicedata() pulumi.StringPtrOutput { + return o.ApplyT(func(v ProviderEndpoint) *string { return v.Directoryservicedata }).(pulumi.StringPtrOutput) +} + // Use this to override the default service endpoint URL func (o ProviderEndpointOutput) Dlm() pulumi.StringPtrOutput { return o.ApplyT(func(v ProviderEndpoint) *string { return v.Dlm }).(pulumi.StringPtrOutput) @@ -2749,6 +2780,11 @@ func (o ProviderEndpointOutput) Lambda() pulumi.StringPtrOutput { return o.ApplyT(func(v ProviderEndpoint) *string { return v.Lambda }).(pulumi.StringPtrOutput) } +// Use this to override the default service endpoint URL +func (o ProviderEndpointOutput) Lambdacore() pulumi.StringPtrOutput { + return o.ApplyT(func(v ProviderEndpoint) *string { return v.Lambdacore }).(pulumi.StringPtrOutput) +} + // Use this to override the default service endpoint URL func (o ProviderEndpointOutput) Lambdamicrovms() pulumi.StringPtrOutput { return o.ApplyT(func(v ProviderEndpoint) *string { return v.Lambdamicrovms }).(pulumi.StringPtrOutput) diff --git a/vendor/github.com/pulumi/pulumi/sdk/v3/.version b/vendor/github.com/pulumi/pulumi/sdk/v3/.version index de84ca7c0..437c2b949 100644 --- a/vendor/github.com/pulumi/pulumi/sdk/v3/.version +++ b/vendor/github.com/pulumi/pulumi/sdk/v3/.version @@ -1 +1 @@ -3.258.0 +3.259.0 diff --git a/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/apitype/service.go b/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/apitype/service.go index f5388659e..47785352e 100644 --- a/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/apitype/service.go +++ b/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/apitype/service.go @@ -55,6 +55,9 @@ const ( // NeoCLIMode advertises minimum CLI requirements for `pulumi neo`; see NeoCLIModeConfig. NeoCLIMode APICapability = "neo-cli-mode" + + // Indicates that the service supports the combined begin-update endpoint. + BeginUpdate APICapability = "begin-update" ) type DeltaCheckpointUploadsConfigV2 struct { @@ -151,6 +154,9 @@ type Capabilities struct { // If non-nil, indicates that the service has advertised minimum CLI requirements // for `pulumi neo`. NeoCLIMode *NeoCLIModeConfig + + // Indicates whether the service supports the combined begin-update endpoint. + BeginUpdate bool } // Parse decodes the CapabilitiesResponse into a Capabilities struct for ease of use. @@ -213,6 +219,10 @@ func (r CapabilitiesResponse) Parse() (Capabilities, error) { } parsed.NeoCLIMode = &cfg } + case BeginUpdate: + if entry.Version == 1 { + parsed.BeginUpdate = true + } default: continue } diff --git a/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/env/env.go b/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/env/env.go index 6a15bc5ba..215e7cf12 100644 --- a/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/env/env.go +++ b/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/env/env.go @@ -100,6 +100,10 @@ var ContinueOnError = env.Bool("CONTINUE_ON_ERROR", var BackendURL = env.String("BACKEND_URL", "Set the backend that will be used instead of the currently logged in backend or the current project's backend.") +var DefaultOrg = env.String("DEFAULT_ORGANIZATION", + "Set the default organization to use when the organization is not otherwise specified. Takes precedence over "+ + "any default organization set with `pulumi org set-default`.") + // Neo environment variables var SuppressNeoLink = env.Bool("SUPPRESS_NEO_LINK", diff --git a/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/esc/expr.go b/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/esc/expr.go index e9e1befe2..95e870031 100644 --- a/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/esc/expr.go +++ b/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/esc/expr.go @@ -108,7 +108,7 @@ type BuiltinExpr struct { // A Range defines a range within an environment definition. type Range struct { // The name of the environment. - Environment string `json:"environment,omitempty"` + Environment string `json:"environment"` // The beginning of the range. Begin Pos `json:"begin"` diff --git a/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/esc/value.go b/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/esc/value.go index a5c68bd90..480ed813a 100644 --- a/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/esc/value.go +++ b/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/esc/value.go @@ -35,7 +35,7 @@ type ValueType interface { type Value struct { // Value holds the concrete representation of the value. May be nil, bool, json.Number, string, []Value, or // map[string]Value. - Value any `json:"value,omitempty"` + Value any `json:"value"` // Secret is true if this value is secret. Secret bool `json:"secret,omitempty"` diff --git a/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/workspace/paths.go b/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/workspace/paths.go index 3f1431dee..a4be678f0 100644 --- a/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/workspace/paths.go +++ b/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/workspace/paths.go @@ -22,7 +22,6 @@ import ( "path/filepath" "strings" - "github.com/pulumi/pulumi/sdk/v3/go/common/diag" "github.com/pulumi/pulumi/sdk/v3/go/common/encoding" "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" "github.com/pulumi/pulumi/sdk/v3/go/common/util/agentdetect" @@ -196,21 +195,6 @@ func DetectPolicyPackPathAt(path string) (string, error) { return "", nil } -// DetectProject loads the closest project from the current working directory, or an error if not found. -func DetectProject() (*Project, error) { - proj, _, err := detectProjectAndPath() - return proj, err -} - -func DetectProjectStack(diags diag.Sink, stackName tokens.QName) (*ProjectStack, error) { - project, path, err := DetectProjectStackPath(stackName) - if err != nil { - return nil, err - } - - return LoadProjectStack(diags, project, path) -} - // detectProjectAndPath loads the closest package from the current working directory, or an error if not found. It // also returns the path where the package was found. func detectProjectAndPath() (*Project, string, error) { diff --git a/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/workspace/plugins.go b/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/workspace/plugins.go index df8d10824..6d401497a 100644 --- a/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/workspace/plugins.go +++ b/vendor/github.com/pulumi/pulumi/sdk/v3/go/common/workspace/plugins.go @@ -40,15 +40,12 @@ import ( "time" "github.com/blang/semver" - "github.com/cheggaaa/pb" "github.com/djherbis/times" "github.com/go-git/go-git/v6/plumbing" "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" - "github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors" "github.com/pulumi/pulumi/sdk/v3/go/common/env" - "github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" "github.com/pulumi/pulumi/sdk/v3/go/common/util/gitutil" "github.com/pulumi/pulumi/sdk/v3/go/common/util/httputil" @@ -2463,32 +2460,6 @@ func SelectCompatiblePlugin( return &bestMatch } -// ReadCloserProgressBar displays a progress bar for the given closer and returns a wrapper closer to manipulate it. -func ReadCloserProgressBar( - closer io.ReadCloser, w io.Writer, size int64, message string, colorization colors.Colorization, -) io.ReadCloser { - if size == -1 || !cmdutil.Interactive() { - // We can't render a progress bar (unknown size, or non-interactive output), but still tell the - // user what's happening. - fmt.Fprintln(w, colorization.Colorize(colors.SpecUnimportant+message+colors.Reset)) - return closer - } - - // If we know the length of the download, show a progress bar. - bar := pb.New(int(size)) - bar.Output = w - bar.Prefix(colorization.Colorize(colors.SpecUnimportant + message + ":")) - bar.Postfix(colorization.Colorize(colors.Reset)) - bar.SetMaxWidth(80) - bar.SetUnits(pb.U_BYTES) - bar.Start() - - return &barCloser{ - bar: bar, - readCloser: bar.NewProxyReader(closer), - } -} - // getCandidateExtensions returns a set of file extensions (including the dot seprator) which should be used when // probing for an executable file. func getCandidateExtensions() []string { @@ -2605,17 +2576,3 @@ func getPluginSize(path string) (uint64, error) { } return size, nil } - -type barCloser struct { - bar *pb.ProgressBar - readCloser io.ReadCloser -} - -func (bc *barCloser) Read(dest []byte) (int, error) { - return bc.readCloser.Read(dest) -} - -func (bc *barCloser) Close() error { - bc.bar.Finish() - return bc.readCloser.Close() -} diff --git a/vendor/go.opentelemetry.io/otel/.golangci.yml b/vendor/go.opentelemetry.io/otel/.golangci.yml index 8a7f1ec5d..a0ba70805 100644 --- a/vendor/go.opentelemetry.io/otel/.golangci.yml +++ b/vendor/go.opentelemetry.io/otel/.golangci.yml @@ -143,8 +143,7 @@ linters: - name: constant-logical-expr - name: context-as-argument arguments: - - allow-types-before: '*testing.T' - disabled: true + - allow-types-before: '*testing.T,*testing.B' - name: context-keys-type - name: deep-exit - name: defer diff --git a/vendor/go.opentelemetry.io/otel/CHANGELOG.md b/vendor/go.opentelemetry.io/otel/CHANGELOG.md index 2db588ea2..4d473a1e8 100644 --- a/vendor/go.opentelemetry.io/otel/CHANGELOG.md +++ b/vendor/go.opentelemetry.io/otel/CHANGELOG.md @@ -11,6 +11,34 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm +## [1.46.0/0.68.0/0.22.0/0.0.19] - 2026-08-25 + +This release is the last to support [Go 1.25]. +The next release will require at least [Go 1.26]. + +### Added + +- Support testing of [Go 1.27]. (#8811) +- Support `http/json` protocol in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#8273, #8775, #8831) +- Add `Hasher` struct and methods in `go.opentelemetry.io/otel/attribute` to compute authoritative `Distinct` hashes incrementally for attribute filtering and deduplication. (#8598) + +### Changed + +- Lazily evaluate filtered and dropped attributes on measurement hot paths in `go.opentelemetry.io/otel/sdk/metric` to avoid unnecessary attribute set allocations. (#8598) +- Add `ErrExporterShutdown` to `go.opentelemetry.io/otel/sdk/log` and return it from the `go.opentelemetry.io/otel/exporters/stdout/stdoutlog`, `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`, and `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp` exporters when `Export` is called after `Shutdown`. (#8773) +- Clarify in `go.opentelemetry.io/otel/log` that calling `Logger.Enabled` is optional and that cached results can become stale. (#8764) + +### Fixed + +- Export dropped attribute counts in OTLP log records from `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8829) +- Name span events created from OpenTracing logs after the `event` log field, falling back to `log`, instead of always using an empty name in `go.opentelemetry.io/otel/bridge/opentracing`. (#8648) +- Count exception attributes omitted due to the attribute count limit as dropped in `go.opentelemetry.io/otel/sdk/log`. (#8796) +- Prevent log record and instrumentation scope attributes with empty keys from reaching processors and exporters in `go.opentelemetry.io/otel/sdk/log`. (#8797) +- Fix a data race when span attributes are read concurrently in `go.opentelemetry.io/otel/sdk/trace`. (#8706) +- Prevent a panic in `(*Set).Filter` when called on a nil receiver in `go.opentelemetry.io/otel/attribute`. (#8792) +- The simple span and log processors record `otel.sdk.processor.{span,log}.processed` when the record is submitted to the exporter instead of after the export completes, and no longer set `error.type` from the export outcome, in `go.opentelemetry.io/otel/sdk/trace` and `go.opentelemetry.io/otel/sdk/log`. (#8705) +- Prevent `Resource.MarshalLog` from panicking on nil resources in `go.opentelemetry.io/otel/sdk/resource`. (#8758) + ## [1.45.0/0.67.0/0.21.0/0.0.18] - 2026-08-03 ### Added @@ -3782,7 +3810,8 @@ It contains api and sdk for trace and meter. - CircleCI build CI manifest files. - CODEOWNERS file to track owners of this project. -[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.45.0...HEAD +[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.46.0...HEAD +[1.46.0/0.68.0/0.22.0/0.0.19]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.46.0 [1.45.0/0.67.0/0.21.0/0.0.18]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.45.0 [1.44.0/0.66.0/0.20.0/0.0.17]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.44.0 [1.43.0/0.65.0/0.19.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.43.0 @@ -3887,6 +3916,7 @@ It contains api and sdk for trace and meter. +[Go 1.27]: https://go.dev/doc/go1.27 [Go 1.26]: https://go.dev/doc/go1.26 [Go 1.25]: https://go.dev/doc/go1.25 [Go 1.24]: https://go.dev/doc/go1.24 diff --git a/vendor/go.opentelemetry.io/otel/Makefile b/vendor/go.opentelemetry.io/otel/Makefile index d4711257d..c674ef8a1 100644 --- a/vendor/go.opentelemetry.io/otel/Makefile +++ b/vendor/go.opentelemetry.io/otel/Makefile @@ -69,8 +69,13 @@ $(GORELEASE): PACKAGE=golang.org/x/exp/cmd/gorelease GOVULNCHECK = $(TOOLS)/govulncheck $(TOOLS)/govulncheck: PACKAGE=golang.org/x/vuln/cmd/govulncheck +AFFECTEDMODS = $(TOOLS)/affectedmods +AFFECTEDMODS_FILES := $(sort $(shell find $(TOOLS_MOD_DIR)/affectedmods -type f)) +$(TOOLS)/affectedmods: PACKAGE=go.opentelemetry.io/otel/$(TOOLS_MOD_DIR)/affectedmods +$(TOOLS)/affectedmods: $(AFFECTEDMODS_FILES) + .PHONY: tools -tools: $(CROSSLINK) $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(VERIFYREADMES) $(MULTIMOD) $(SEMCONVKIT) $(GOTMPL) $(GORELEASE) +tools: $(CROSSLINK) $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(VERIFYREADMES) $(MULTIMOD) $(SEMCONVKIT) $(GOTMPL) $(GORELEASE) $(AFFECTEDMODS) # Virtualized python tools via docker @@ -125,9 +130,11 @@ go-work: $(CROSSLINK) # Build -.PHONY: build +.PHONY: build cross-build build: $(OTEL_GO_MOD_DIRS:%=build/%) $(OTEL_GO_MOD_DIRS:%=build-tests/%) +# Cross-platform builds cannot execute the binaries produced by build-tests (go test), so use a compile-only target. +cross-build: $(OTEL_GO_MOD_DIRS:%=build/%) build/%: DIR=$* build/%: @echo "$(GO) build $(DIR)/..." \ @@ -163,6 +170,9 @@ test/%: | grep -v third_party \ | xargs $(GO) test -timeout $(TIMEOUT)s $(ARGS) +.PHONY: test-tools +test-tools: test/$(TOOLS_MOD_DIR) + COVERAGE_MODE = atomic COVERAGE_PROFILE = coverage.out .PHONY: test-coverage @@ -187,15 +197,23 @@ benchmark/%: # sdk/metric is split into two shards to work around CodSpeed limitations. # See https://github.com/CodSpeedHQ/codspeed-go/issues/56 -BENCHMARK_SHARDS := $(filter-out ./sdk/metric,$(OTEL_GO_MOD_DIRS)) ./sdk/metric/root ./sdk/metric/internal -benchmark/./sdk/metric/root: +BENCHMARK_SHARDS := $(filter-out ./sdk/metric,$(OTEL_GO_MOD_DIRS)) ./sdk/metric/. ./sdk/metric/internal +benchmark/./sdk/metric/.: cd ./sdk/metric && $(GO) test -run='^$$' -bench=. $(ARGS) . ./exemplar/... -benchmark/./sdk/metric/internal: - cd ./sdk/metric && $(GO) test -run='^$$' -bench=. $(ARGS) ./internal/... print-sharded-benchmarks: @echo $(BENCHMARK_SHARDS) | jq -cR 'split(" ")' +# Print the JSON list of benchmark shards whose code changed since +# BASE_REF (default: main). Filters print-sharded-benchmarks +# down to shards whose underlying module contains a changed *.go file. +# Non-Go changes (docs, workflows, Makefile, go.mod/go.sum, tooling) emit []. +# Override the diff base via BASE_REF, or pass ARGS=-all to emit the full list. +BASE_REF ?= main +.PHONY: print-affected-benchmarks +print-affected-benchmarks: + @$(MAKE) -s print-sharded-benchmarks | $(GO) -C $(TOOLS_MOD_DIR) run ./affectedmods $(if $(strip $(BASE_REF)),-base=$(BASE_REF),) $(ARGS) + .PHONY: golangci-lint golangci-lint-fix golangci-lint-fix: ARGS=--fix golangci-lint-fix: golangci-lint diff --git a/vendor/go.opentelemetry.io/otel/README.md b/vendor/go.opentelemetry.io/otel/README.md index ce97e9a25..9291d1470 100644 --- a/vendor/go.opentelemetry.io/otel/README.md +++ b/vendor/go.opentelemetry.io/otel/README.md @@ -53,18 +53,25 @@ Currently, this project supports the following environments. | OS | Go Version | Architecture | |----------|------------|--------------| +| Ubuntu | 1.27 | amd64 | | Ubuntu | 1.26 | amd64 | | Ubuntu | 1.25 | amd64 | +| Ubuntu | 1.27 | 386 | | Ubuntu | 1.26 | 386 | | Ubuntu | 1.25 | 386 | +| Ubuntu | 1.27 | arm64 | | Ubuntu | 1.26 | arm64 | | Ubuntu | 1.25 | arm64 | +| macOS | 1.27 | amd64 | | macOS | 1.26 | amd64 | | macOS | 1.25 | amd64 | +| macOS | 1.27 | arm64 | | macOS | 1.26 | arm64 | | macOS | 1.25 | arm64 | +| Windows | 1.27 | amd64 | | Windows | 1.26 | amd64 | | Windows | 1.25 | amd64 | +| Windows | 1.27 | 386 | | Windows | 1.26 | 386 | | Windows | 1.25 | 386 | diff --git a/vendor/go.opentelemetry.io/otel/VERSIONING.md b/vendor/go.opentelemetry.io/otel/VERSIONING.md index b27c9e84f..56eaa7d22 100644 --- a/vendor/go.opentelemetry.io/otel/VERSIONING.md +++ b/vendor/go.opentelemetry.io/otel/VERSIONING.md @@ -12,6 +12,12 @@ is designed so the following goals can be achieved. * [Semantic import versioning](https://github.com/golang/go/wiki/Modules#semantic-import-versioning) will be used. + * Stable module compatibility is understood in terms of the [Go 1 + compatibility guidelines](https://go.dev/doc/go1compat). Code that + compiled against an older version of a package should continue to compile + against newer versions of that package, subject to the exceptions in the + Go 1 compatibility guidelines and any additional exceptions documented + below. * Versions will comply with [semver 2.0](https://semver.org/spec/v2.0.0.html) with the following exceptions. * New methods may be added to exported API interfaces. All exported diff --git a/vendor/go.opentelemetry.io/otel/attribute/hash.go b/vendor/go.opentelemetry.io/otel/attribute/hash.go index f651eb13d..54c26e67a 100644 --- a/vendor/go.opentelemetry.io/otel/attribute/hash.go +++ b/vendor/go.opentelemetry.io/otel/attribute/hash.go @@ -33,19 +33,71 @@ const ( emptyID uint64 = 7305809155345288421 // "__empty_" (little endian) ) +// Hasher computes a Distinct value from KeyValue attributes supplied with +// Write. +// +// A Hasher must be obtained from [NewHasher]. The zero value is not usable and +// its methods will panic. +type Hasher struct { + h xxhash.Hash +} + +// NewHasher returns a new Hasher. +func NewHasher() *Hasher { + return &Hasher{h: xxhash.New()} +} + +// Reset resets h to its initial state so it can be reused. +func (h *Hasher) Reset() { + h.h.Reset() +} + +// Write adds kv to the hash. +// +// Write requires attributes to be supplied in ascending key order with no +// duplicate keys. To produce the same Distinct as Set.Equivalent, write +// attributes in ascending key order, with no more than one value for each key. +// If the source contains duplicate keys, retain the last value for each key +// before calling Write. +func (h *Hasher) Write(kv KeyValue) { + // hashKV mutates the digest h.h refers to in place and returns the same + // Hash value it was passed. Discarding the result keeps the digest pointer + // from flowing back into h, which would force the digest to be heap + // allocated for every Hasher. Keeping Write this small also keeps it within + // the inlining budget, which matters because hashKVs calls it per attribute. + _ = hashKV(h.h, kv) +} + +// Distinct returns the identifier for the attributes written to h. When Write +// is called as described above, it returns the same value as [Set.Equivalent]. +func (h *Hasher) Distinct() Distinct { + // No count of written attributes is needed to detect the empty case. The + // sum of a digest with nothing written to it is emptyHash, which is + // non-zero (0xef46db3751d8e999), so it passes through remapZeroHash + // unchanged and matches emptySet.Equivalent. + return Distinct{hash: remapZeroHash(h.h.Sum64())} +} + +// remapZeroHash remaps a 0 sum to a non-zero value, because hash == 0 is a +// reserved sentinel (treated as empty/invalid). +func remapZeroHash(sum uint64) uint64 { + if sum == 0 { + return 1 + } + return sum +} + // hashKVs returns a new xxHash64 hash of kvs. +// +// This routes through [Hasher] so that Set hashing and Hasher cannot disagree: +// there is exactly one implementation of how attributes are mixed and how the +// final sum is framed. func hashKVs(kvs []KeyValue) uint64 { - h := xxhash.New() + h := NewHasher() for _, kv := range kvs { - h = hashKV(h, kv) + h.Write(kv) } - sum := h.Sum64() - // Remap 0 to a non-zero value for non-empty input because hash == 0 is a reserved sentinel (treated as empty/invalid). - const remappedZeroHash uint64 = 1 - if sum == 0 && len(kvs) > 0 { - return remappedZeroHash - } - return sum + return h.Distinct().hash } // hashKV returns the xxHash64 hash of kv with h as the base. diff --git a/vendor/go.opentelemetry.io/otel/attribute/internal/xxhash/xxhash.go b/vendor/go.opentelemetry.io/otel/attribute/internal/xxhash/xxhash.go index c851179cc..4fcdd2dbe 100644 --- a/vendor/go.opentelemetry.io/otel/attribute/internal/xxhash/xxhash.go +++ b/vendor/go.opentelemetry.io/otel/attribute/internal/xxhash/xxhash.go @@ -62,3 +62,8 @@ func (h Hash) String(val string) Hash { func (h Hash) Sum64() uint64 { return h.d.Sum64() } + +// Reset resets the hash to its initial state. +func (h Hash) Reset() { + h.d.Reset() +} diff --git a/vendor/go.opentelemetry.io/otel/attribute/set.go b/vendor/go.opentelemetry.io/otel/attribute/set.go index 87d6f9620..56a70a9f6 100644 --- a/vendor/go.opentelemetry.io/otel/attribute/set.go +++ b/vendor/go.opentelemetry.io/otel/attribute/set.go @@ -313,6 +313,9 @@ func filteredToFront(slice []KeyValue, keep Filter) int { // Filter returns a filtered copy of this Set. See the documentation for // NewSetWithSortableFiltered for more details. func (l *Set) Filter(re Filter) (Set, []KeyValue) { + if l == nil { + return emptySet, nil + } if re == nil { return *l, nil } diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/client.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/client.go index 8a88b802e..558ec2256 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/client.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/client.go @@ -36,9 +36,9 @@ type client struct { requestFunc retry.RequestFunc // ourConn keeps track of where conn was created: true if created here in - // NewClient, or false if passed with an option. This is important on - // Shutdown as conn should only be closed if we created it. Otherwise, - // it is up to the processes that passed conn to close it. + // NewClient, or false if passed with an option. This is important during + // Shutdown because conn should only be closed if we created it. Otherwise, + // the caller that passed conn is responsible for closing it. ourConn bool conn *grpc.ClientConn lsc collogpb.LogsServiceClient @@ -133,13 +133,13 @@ func newGRPCDialOptions(cfg config) []grpc.DialOption { return dialOpts } -// UploadLogs sends proto logs to connected endpoint. +// UploadLogs sends proto logs to the connected endpoint. // // Retryable errors from the server will be handled according to any // RetryConfig the client was created with. // -// The otlplog.Exporter synchronizes access to client methods, and -// ensures this is not called after the Exporter is shutdown. Only thing +// The [Exporter] synchronizes access to client methods and +// ensures this is not called after the Exporter is shut down. The only thing // to do here is send data. func (c *client) UploadLogs(ctx context.Context, rl []*logpb.ResourceLogs) (uploadErr error) { select { @@ -196,7 +196,7 @@ func (c *client) UploadLogs(ctx context.Context, rl []*logpb.ResourceLogs) (uplo // WithGRPCConn will not be closed. It is the caller's responsibility to // handle cleanup of that resource. // -// The otlplog.Exporter synchronizes access to client methods and +// The [Exporter] synchronizes access to client methods and // ensures this is called only once. The only thing that needs to be done // here is to release any computational resources the client holds. func (c *client) Shutdown(ctx context.Context) error { @@ -218,9 +218,9 @@ func (c *client) Shutdown(ctx context.Context) error { } // exportContext returns a copy of parent with an appropriate deadline and -// cancellation function based on the clients configured export timeout. +// cancellation function based on the client's configured export timeout. // -// It is the callers responsibility to cancel the returned context once its +// It is the caller's responsibility to cancel the returned context once its // use is complete, via the parent or directly with the returned CancelFunc, to // ensure all resources are correctly released. func (c *client) exportContext(parent context.Context) (context.Context, context.CancelFunc) { @@ -257,8 +257,8 @@ func (*noopClient) UploadLogs(context.Context, []*logpb.ResourceLogs) error { re func (*noopClient) Shutdown(context.Context) error { return nil } -// retryable returns if err identifies a request that can be retried and a -// duration to wait for if an explicit throttle time is included in err. +// retryable reports whether err identifies a request that can be retried and +// returns a duration to wait if an explicit throttle time is included in err. func retryable(err error) (bool, time.Duration) { s := status.Convert(err) return retryableGRPCStatus(s) @@ -282,12 +282,12 @@ func retryableGRPCStatus(s *status.Status) (bool, time.Duration) { return throttleDelay(s) } - // Not a retry-able error. + // Not a retryable error. return false, 0 } -// throttleDelay returns if the status is RetryInfo -// and the duration to wait for if an explicit throttle time is included. +// throttleDelay reports whether the status contains RetryInfo and returns the +// duration to wait if an explicit throttle time is included. func throttleDelay(s *status.Status) (bool, time.Duration) { for _, detail := range s.Details() { if t, ok := detail.(*errdetails.RetryInfo); ok { diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/config.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/config.go index a872d7b35..6981a1dc0 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/config.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/config.go @@ -141,8 +141,7 @@ func newConfig(options []Option) config { return c } -// RetryConfig defines configuration for retrying the export of log data -// that failed. +// RetryConfig defines configuration for retrying failed exports of log data. // // This configuration does not define any network retry strategy. That is // entirely handled by the gRPC ClientConn. @@ -155,7 +154,7 @@ type RetryConfig retry.Config // If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_LOGS_ENDPOINT // environment variable is set, and this option is not passed, that variable // value will be used to determine client security. If the endpoint has a -// scheme of "http" or "unix" client security will be disabled. If both are +// scheme of "http" or "unix", client security will be disabled. If both are // set, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT will take precedence. // // By default, if an environment variable is not set, and this option is not @@ -219,7 +218,7 @@ func WithEndpointURL(rawURL string) Option { }) } -// WithReconnectionPeriod set the minimum amount of time between connection +// WithReconnectionPeriod sets the minimum amount of time between connection // attempts to the target endpoint. // // This option has no effect if WithGRPCConn is used. @@ -234,9 +233,9 @@ func WithReconnectionPeriod(rp time.Duration) Option { type Compression int const ( - // NoCompression represents that no compression should be used. + // NoCompression indicates that no compression is used. NoCompression Compression = iota - // GzipCompression represents that gzip compression should be used. + // GzipCompression indicates that gzip compression is used. GzipCompression ) @@ -260,13 +259,13 @@ func WithCompressor(compressor string) Option { }) } -// WithHeaders will send the provided headers with each gRPC requests. +// WithHeaders sends the provided headers with each gRPC request. // // If the OTEL_EXPORTER_OTLP_HEADERS or OTEL_EXPORTER_OTLP_LOGS_HEADERS // environment variable is set, and this option is not passed, that variable -// value will be used. The value will be parsed as a list of key value pairs. +// value will be used. The value will be parsed as a list of key-value pairs. // These pairs are expected to be in the W3C Correlation-Context format -// without additional semi-colon delimited metadata (i.e. "k1=v1,k2=v2"). If +// without additional semicolon-delimited metadata (i.e. "k1=v1,k2=v2"). If // both are set, OTEL_EXPORTER_OTLP_LOGS_HEADERS will take precedence. // // By default, if an environment variable is not set, and this option is not @@ -278,12 +277,12 @@ func WithHeaders(headers map[string]string) Option { }) } -// WithTLSCredentials sets the gRPC connection to use creds. +// WithTLSCredentials sets the gRPC connection to use the provided credentials. // // If the OTEL_EXPORTER_OTLP_CERTIFICATE or // OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE environment variable is set, and // this option is not passed, that variable value will be used. The value will -// be parsed the filepath of the TLS certificate chain to use. If both are +// be parsed as the filepath of the TLS certificate chain to use. If both are // set, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE will take precedence. // // By default, if an environment variable is not set, and this option is not @@ -308,9 +307,9 @@ func WithServiceConfig(serviceConfig string) Option { } // WithDialOption sets explicit grpc.DialOptions to use when establishing a -// gRPC connection. The options here are appended to the internal grpc.DialOptions -// used so they will take precedence over any other internal grpc.DialOptions -// they might conflict with. +// gRPC connection. The options here are appended to the internal +// grpc.DialOptions used, so they will take precedence over any other internal +// grpc.DialOptions with which they conflict. // The [grpc.WithBlock], [grpc.WithTimeout], and [grpc.WithReturnConnectionError] // grpc.DialOptions are ignored. // @@ -326,10 +325,10 @@ func WithDialOption(opts ...grpc.DialOption) Option { // // This option takes precedence over any other option that relates to // establishing or persisting a gRPC connection to a target endpoint. Any -// other option of those types passed will be ignored. +// other options of those types that are passed will be ignored. // -// It is the callers responsibility to close the passed conn. The Exporter -// Shutdown method will not close this connection. +// It is the caller's responsibility to close the passed conn. +// [Exporter.Shutdown] will not close this connection. func WithGRPCConn(conn *grpc.ClientConn) Option { return fnOpt(func(c config) config { c.gRPCConn = newSetting(conn) @@ -337,10 +336,11 @@ func WithGRPCConn(conn *grpc.ClientConn) Option { }) } -// WithTimeout sets the max amount of time an Exporter will attempt an export. +// WithTimeout sets the maximum amount of time an Exporter will attempt an +// export. // // This takes precedence over any retry settings defined by WithRetry. Once -// this time limit has been reached the export is abandoned and the log +// this time limit has been reached, the export is abandoned and the log // data is dropped. // // If the OTEL_EXPORTER_OTLP_TIMEOUT or OTEL_EXPORTER_OTLP_LOGS_TIMEOUT @@ -374,8 +374,8 @@ func WithMaxRequestSize(size int) Option { // WithRetry sets the retry policy for transient retryable errors that are // returned by the target endpoint. // -// If the target endpoint responds with not only a retryable error, but -// explicitly returns a backoff time in the response, that time will take +// If the target endpoint responds with a retryable error and explicitly +// returns a backoff time in the response, that time will take // precedence over these settings. // // These settings define the retry strategy implemented by the exporter. @@ -383,8 +383,8 @@ func WithMaxRequestSize(size int) Option { // That is handled by the gRPC ClientConn. // // If unset, the default retry policy will be used. It will retry the export -// 5 seconds after receiving a retryable error and increase exponentially -// after each error for no more than a total time of 1 minute. +// 5 seconds after receiving a retryable error, with the time between retries +// increasing exponentially after each error, for no more than 1 minute total. func WithRetry(rc RetryConfig) Option { return fnOpt(func(c config) config { c.retryCfg = newSetting(retry.Config(rc)) @@ -393,7 +393,7 @@ func WithRetry(rc RetryConfig) Option { } // convCompression returns the parsed compression encoded in s. NoCompression -// and an errors are returned if s is unknown. +// and an error are returned if s is unknown. func convCompression(s string) (Compression, error) { switch s { case "gzip": @@ -414,8 +414,8 @@ func convEndpoint(s string) (string, error) { return u.Host, nil } -// convInsecure converts s from string to bool without case sensitivity. -// If s is not valid returns error. +// convInsecure converts s from a string to a bool without case sensitivity. +// If s is invalid, it returns an error. func convInsecure(s string) (bool, error) { s = strings.ToLower(s) if s != "true" && s != "false" { @@ -425,8 +425,8 @@ func convInsecure(s string) (bool, error) { return s == "true", nil } -// loadInsecureFromEnvEndpoint returns a resolver that fetches -// insecure setting from envEndpoint is it possible. +// loadInsecureFromEnvEndpoint returns a resolver that fetches the insecure +// setting from envEndpoint, if possible. func loadInsecureFromEnvEndpoint(envEndpoint []string) resolver[bool] { return func(s setting[bool]) setting[bool] { if s.Set { @@ -450,7 +450,7 @@ func loadInsecureFromEnvEndpoint(envEndpoint []string) resolver[bool] { } // convHeaders converts the OTel environment variable header value s into a -// mapping of header key to value. If s is invalid a partial result and error +// mapping of header keys to values. If s is invalid, a partial result and an error // are returned. func convHeaders(s string) (map[string]string, error) { out := make(map[string]string) @@ -483,8 +483,9 @@ func convHeaders(s string) (map[string]string, error) { return out, err } -// convDuration converts s into a duration of milliseconds. If s does not -// contain an integer, 0 and an error are returned. +// convDuration interprets s as a number of milliseconds and returns the +// corresponding duration. If s does not contain an integer, 0 and an error are +// returned. func convDuration(s string) (time.Duration, error) { d, err := strconv.Atoi(s) if err != nil { @@ -495,10 +496,10 @@ func convDuration(s string) (time.Duration, error) { } // loadEnvTLS returns a resolver that loads a *tls.Config from files defined by -// the OTLP TLS environment variables. This will load both the rootCAs and +// the OTLP TLS environment variables. This will load both the root CAs and // certificates used for mTLS. // -// If the filepath defined is invalid or does not contain valid TLS files, an +// If a configured filepath is invalid or does not point to a valid TLS file, an // error is passed to the OTel ErrorHandler and no TLS configuration is // provided. func loadEnvTLS[T *tls.Config]() resolver[T] { @@ -544,7 +545,7 @@ func loadEnvTLS[T *tls.Config]() resolver[T] { var readFile = os.ReadFile // loadCertPool loads and returns the *x509.CertPool found at path if it exists -// and is valid. Otherwise, nil and an error is returned. +// and is valid. Otherwise, nil and an error are returned. func loadCertPool(path string) (*x509.CertPool, error) { b, err := readFile(path) if err != nil { @@ -558,7 +559,7 @@ func loadCertPool(path string) (*x509.CertPool, error) { } // loadCertificates loads and returns the tls.Certificate found at path if it -// exists and is valid. Otherwise, nil and an error is returned. +// exists and is valid. Otherwise, nil and an error are returned. func loadCertificates(certPath, keyPath string) ([]tls.Certificate, error) { cert, err := readFile(certPath) if err != nil { @@ -575,9 +576,8 @@ func loadCertificates(certPath, keyPath string) ([]tls.Certificate, error) { return []tls.Certificate{crt}, nil } -// insecureFromScheme return setting if the connection should -// use client transport security or not. -// Empty scheme doesn't force insecure setting. +// insecureFromScheme returns the insecure setting implied by scheme. +// An empty scheme does not force an insecure setting. func insecureFromScheme(prev setting[bool], scheme string) setting[bool] { if scheme == "https" { return newSetting(false) @@ -609,18 +609,18 @@ func newSetting[T any](value T) setting[T] { return setting[T]{Value: value, Set: true} } -// resolver returns an updated setting after applying an resolution operation. +// resolver returns an updated setting after applying a resolution operation. type resolver[T any] func(setting[T]) setting[T] // Resolve returns a resolved version of s. // -// It will apply all the passed fn in the order provided, chaining together the -// return setting to the next input. The setting s is used as the initial -// argument to the first fn. +// It applies all functions in fn in the order provided, passing each returned +// setting to the next function. The setting s is used as the initial argument +// to the first function. // -// Each fn needs to validate if it should apply given the Set state of the -// setting. This will not perform any checks on the set state when chaining -// function. +// Each function in fn needs to determine whether it should apply given the Set +// state of the setting. This will not perform any checks on the set state when +// chaining functions. func (s setting[T]) Resolve(fn ...resolver[T]) setting[T] { for _, f := range fn { s = f(s) @@ -659,7 +659,7 @@ func getEnv[T any](keys []string, conv func(string) (T, error)) resolver[T] { } } -// fallback returns a resolve that will set a setting value to val if it is not +// fallback returns a resolver that will set a setting value to val if it is not // already set. // // This is usually passed at the end of a resolver chain to ensure a default is diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/doc.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/doc.go index a159ad304..bbdbf9aea 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/doc.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/doc.go @@ -5,58 +5,58 @@ Package otlploggrpc provides an OTLP log exporter using gRPC. The exporter uses gRPC to transport OTLP protobuf payloads. -All Exporters must be created with [New]. +All Exporter values must be created with [New]. The environment variables described below can be used for configuration. OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT (default: "https://localhost:4317") - target to which the exporter sends telemetry. The target syntax is defined in https://github.com/grpc/grpc/blob/master/doc/naming.md. -The value must contain a scheme ("http" or "https") and host. -The value may additionally contain a port, and a path. +The value must contain a scheme ("http" or "https") and a host. +The value may additionally contain a port and a path. The value should not contain a query string or fragment. OTEL_EXPORTER_OTLP_LOGS_ENDPOINT takes precedence over OTEL_EXPORTER_OTLP_ENDPOINT. -The configuration can be overridden by [WithEndpoint], [WithEndpointURL], [WithInsecure], and [WithGRPCConn] options. +The configuration can be overridden by the [WithEndpoint], [WithEndpointURL], [WithInsecure], and [WithGRPCConn] options. OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_LOGS_INSECURE (default: "false") - setting "true" disables client transport security for the exporter's gRPC connection. -You can use this only when an endpoint is provided without scheme. +You can use this only when an endpoint is provided without a scheme. OTEL_EXPORTER_OTLP_LOGS_INSECURE takes precedence over OTEL_EXPORTER_OTLP_INSECURE. -The configuration can be overridden by [WithInsecure], [WithGRPCConn] options. +The configuration can be overridden by the [WithInsecure] and [WithGRPCConn] options. OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_LOGS_HEADERS (default: none) - key-value pairs used as gRPC metadata associated with gRPC requests. The value is expected to be represented in a format matching the [W3C Baggage HTTP Header Content Format], -except that additional semi-colon delimited metadata is not supported. +except that additional semicolon-delimited metadata is not supported. Example value: "key1=value1,key2=value2". OTEL_EXPORTER_OTLP_LOGS_HEADERS takes precedence over OTEL_EXPORTER_OTLP_HEADERS. -The configuration can be overridden by [WithHeaders] option. +The configuration can be overridden by the [WithHeaders] option. OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT (default: "10000") - maximum time in milliseconds the OTLP exporter waits for each batch export. OTEL_EXPORTER_OTLP_LOGS_TIMEOUT takes precedence over OTEL_EXPORTER_OTLP_TIMEOUT. -The configuration can be overridden by [WithTimeout] option. +The configuration can be overridden by the [WithTimeout] option. OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION (default: none) - the gRPC compressor the exporter uses. Supported value: "gzip". OTEL_EXPORTER_OTLP_LOGS_COMPRESSION takes precedence over OTEL_EXPORTER_OTLP_COMPRESSION. -The configuration can be overridden by [WithCompressor], [WithGRPCConn] options. +The configuration can be overridden by the [WithCompressor] and [WithGRPCConn] options. OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE (default: none) - the filepath to the trusted certificate to use when verifying a server's TLS credentials. OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE takes precedence over OTEL_EXPORTER_OTLP_CERTIFICATE. -The configuration can be overridden by [WithTLSCredentials], [WithGRPCConn] options. +The configuration can be overridden by the [WithTLSCredentials] and [WithGRPCConn] options. OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE (default: none) - -the filepath to the client certificate/chain trust for client's private key to use in mTLS communication in PEM format. +the filepath to the client's certificate chain to use with the client's private key for mTLS communication in PEM format. OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE takes precedence over OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE. -The configuration can be overridden by [WithTLSCredentials], [WithGRPCConn] options. +The configuration can be overridden by the [WithTLSCredentials] and [WithGRPCConn] options. OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY (default: none) - the filepath to the client's private key to use in mTLS communication in PEM format. OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY takes precedence over OTEL_EXPORTER_OTLP_CLIENT_KEY. -The configuration can be overridden by [WithTLSCredentials], [WithGRPCConn] option. +The configuration can be overridden by the [WithTLSCredentials] and [WithGRPCConn] options. [W3C Baggage HTTP Header Content Format]: https://www.w3.org/TR/baggage/#header-content */ diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/exporter.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/exporter.go index 63ef9252a..7a1276fdf 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/exporter.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/exporter.go @@ -19,9 +19,9 @@ type logClient interface { Shutdown(context.Context) error } -// Exporter is a OpenTelemetry log Exporter. It transports log data encoded as +// Exporter is an OpenTelemetry log exporter. It transports log data encoded as // OTLP protobufs using gRPC. -// All Exporters must be created with [New]. +// All Exporter values must be created with [New]. type Exporter struct { // Ensure synchronous access to the client across all functionality. clientMu sync.Mutex @@ -30,13 +30,13 @@ type Exporter struct { stopped atomic.Bool } -// Compile-time check Exporter implements [log.Exporter]. +// This is a compile-time check that Exporter implements [log.Exporter]. var _ log.Exporter = (*Exporter)(nil) // New returns a new [Exporter]. // -// It is recommended to use it with a [BatchProcessor] -// or other processor exporting records asynchronously. +// Use the Exporter with a [log.BatchProcessor] or another processor that +// exports records asynchronously. func New(_ context.Context, options ...Option) (*Exporter, error) { cfg := newConfig(options) c, err := newClient(cfg) @@ -56,11 +56,11 @@ var transformResourceLogs = transform.ResourceLogs // Export transforms and transmits log records to an OTLP receiver. // -// This method returns nil and drops records if called after Shutdown. +// This method returns [log.ErrExporterShutdown] if called after Shutdown. // This method returns an error if the method is canceled by the passed context. func (e *Exporter) Export(ctx context.Context, records []log.Record) error { if e.stopped.Load() { - return nil + return log.ErrExporterShutdown } otlp := transformResourceLogs(records) @@ -70,11 +70,15 @@ func (e *Exporter) Export(ctx context.Context, records []log.Record) error { e.clientMu.Lock() defer e.clientMu.Unlock() + + if e.stopped.Load() { + return log.ErrExporterShutdown + } return e.client.UploadLogs(ctx, otlp) } -// Shutdown shuts down the Exporter. Calls to Export or ForceFlush will perform -// no operation after this is called. +// Shutdown shuts down the Exporter. Calls to Export after Shutdown return +// [log.ErrExporterShutdown]. Calls to ForceFlush perform no operation. func (e *Exporter) Shutdown(ctx context.Context) error { if e.stopped.Swap(true) { return nil diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/instrumentation.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/instrumentation.go index 2058f51c4..df3655038 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/instrumentation.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/instrumentation.go @@ -73,7 +73,7 @@ func put[T any](p *sync.Pool, s *[]T) { p.Put(s) } -// GetComponentName returns the constant name for the exporter with the +// GetComponentName returns the component name for the exporter with the // provided id. func GetComponentName(id int64) string { return fmt.Sprintf("%s/%d", otelconv.ComponentTypeOtlpGRPCLogExporter, id) @@ -105,7 +105,7 @@ type Instrumentation struct { recOpt metric.RecordOption } -// NewInstrumentation returns instrumentation for otlplog grpc exporter. +// NewInstrumentation returns instrumentation for the otlplog gRPC exporter. func NewInstrumentation(id int64, target string) (*Instrumentation, error) { if !x.Observability.Enabled() { return nil, nil @@ -190,7 +190,7 @@ type ExportOp struct { // [Instrumentation.ExportLogs]. // Any error that is encountered is provided as err. // -// If err is not nil, all logs will be recorded as failures unless error is of +// If err is not nil, all logs will be recorded as failures unless the error is of // type [internal.PartialSuccess]. In the case of a PartialSuccess, the number // of successfully exported logs will be determined by inspecting the // RejectedItems field of the PartialSuccess. @@ -245,8 +245,8 @@ func (i *Instrumentation) recordOption(err error) metric.RecordOption { return metric.WithAttributeSet(attribute.NewSet(*attrs...)) } -// successful returns the number of successfully exported logs out of the n -// that were exported based on the provided error. +// successful returns the number of successfully exported log records from a +// batch of n records, as determined from err. // // If err is nil, n is returned. All logs were successfully exported. // @@ -271,8 +271,8 @@ var errPool = sync.Pool{ }, } -// rejectedCount returns how many out of the n logs exporter were rejected based on -// the provided non-nil err. +// rejectedCount returns the number of rejected log records from a batch of n +// records, as determined from the non-nil err. func rejectedCount(n int64, err error) int64 { ps := errPool.Get().(*internal.PartialSuccess) defer func() { @@ -284,7 +284,7 @@ func rejectedCount(n int64, err error) int64 { if errors.As(err, ps) { return min(max(ps.RejectedItems, 0), n) } - // all logs exporter + // All logs were rejected. return n } diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/target.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/target.go index 4d0665894..3bd379882 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/target.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/target.go @@ -21,13 +21,13 @@ const ( ) // ParseCanonicalTarget parses a target string and returns the extracted host -// (domain address or IP), the target port, or an error. +// (domain name or IP address), the target port, or an error. // // If no port is specified, -1 is returned. // // If no host is specified, an empty string is returned. // -// The target string is expected to always have the form +// The target string must have the form // "://[authority]/". For example: // - "dns:///example.com:42" // - "dns://8.8.8.8/example.com:42" @@ -35,8 +35,8 @@ const ( // - "unix-abstract:///socket-name" // - "passthrough:///192.34.2.1:42" // -// The target is expected to come from the CanonicalTarget method of a gRPC -// Client. +// The target is expected to be returned by gRPC's ClientConn.CanonicalTarget +// method. func ParseCanonicalTarget(target string) (string, int, error) { const sep = "://" @@ -60,7 +60,7 @@ func ParseCanonicalTarget(target string) (string, int, error) { return parseEndpoint(endpoint) } -// parseUnix parses unix socket targets. +// parseUnix parses Unix socket targets. func parseUnix(endpoint string) (string, int, error) { // Format: unix[-abstract]://path // @@ -70,8 +70,8 @@ func parseUnix(endpoint string) (string, int, error) { return endpoint, -1, nil } - // If there's no leading slash, it means there might be an authority - // Check for authority case (should error): "authority/path" + // If there is no leading slash, an authority might be present, which is + // invalid. Check for this case: "authority/path". if slashIdx := strings.Index(endpoint, "/"); slashIdx > 0 { return "", -1, fmt.Errorf("invalid (non-empty) authority: %s", endpoint[:slashIdx]) } @@ -97,8 +97,8 @@ func parseUnix(endpoint string) (string, int, error) { // - "[ipv6]:port" // - "[ipv6%zone]:port" // -// It returns the host or host%zone (domain address or IP), the port (or -1 if -// not specified), or an error if the input is not a valid. +// It returns the host or host%zone (domain name or IP address), the port (or +// -1 if not specified), or an error if the input is invalid. func parseEndpoint(endpoint string) (string, int, error) { // First check if the endpoint is just an IP address. if ip := parseIP(endpoint); ip != "" { diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/partialsuccess.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/partialsuccess.go index 455df0e0d..6ef9dfb73 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/partialsuccess.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/partialsuccess.go @@ -5,10 +5,9 @@ package internal import "fmt" -// PartialSuccess represents the underlying error for all handling -// OTLP partial success messages. Use `errors.Is(err, -// PartialSuccess{})` to test whether an error passed to the OTel -// error handler belongs to this category. +// PartialSuccess represents an OTLP partial-success response. Use +// `errors.Is(err, PartialSuccess{})` to test whether an error passed to the +// OTel error handler belongs to this category. type PartialSuccess struct { ErrorMessage string RejectedItems int64 @@ -26,13 +25,13 @@ func (ps PartialSuccess) Error() string { return fmt.Sprintf("OTLP partial success: %s (%d %s rejected)", msg, ps.RejectedItems, ps.RejectedKind) } -// Is supports the errors.Is() interface. +// Is supports matching with errors.Is. func (PartialSuccess) Is(err error) bool { _, ok := err.(PartialSuccess) return ok } -// LogPartialSuccessError returns an error describing a partial success +// LogPartialSuccessError returns an error describing a partial-success // response for the log signal. func LogPartialSuccessError(itemsRejected int64, errorMessage string) error { return PartialSuccess{ diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry/retry.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry/retry.go index 1883d0600..468c7c8eb 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry/retry.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry/retry.go @@ -17,7 +17,7 @@ import ( "github.com/cenkalti/backoff/v5" ) -// DefaultConfig are the recommended defaults to use. +// DefaultConfig is the recommended default configuration. var DefaultConfig = Config{ Enabled: true, InitialInterval: 5 * time.Second, @@ -25,42 +25,40 @@ var DefaultConfig = Config{ MaxElapsedTime: time.Minute, } -// Config defines configuration for retrying batches in case of export failure -// using an exponential backoff. +// Config defines configuration for retrying batches after an export failure +// using exponential backoff. type Config struct { - // Enabled indicates whether to not retry sending batches in case of - // export failure. + // Enabled indicates whether to retry sending batches after an export + // failure. Enabled bool - // InitialInterval the time to wait after the first failure before + // InitialInterval is the time to wait after the first failure before // retrying. InitialInterval time.Duration - // MaxInterval is the upper bound on backoff interval. Once this value is - // reached the delay between consecutive retries will always be - // `MaxInterval`. + // MaxInterval is the upper bound on the backoff interval before + // randomization. Once this value is reached, the base interval remains at + // MaxInterval. MaxInterval time.Duration // MaxElapsedTime is the maximum amount of time (including retries) spent - // trying to send a request/batch. Once this value is reached, the data - // is discarded. + // trying to send a request/batch. Once this value is reached, retrying stops. MaxElapsedTime time.Duration } // RequestFunc wraps a request with retry logic. type RequestFunc func(context.Context, func(context.Context) error) error -// EvaluateFunc returns if an error is retry-able and if an explicit throttle -// duration should be honored that was included in the error. +// EvaluateFunc reports whether an error is retryable and returns any explicit +// throttle duration to honor. // -// The function must return true if the error argument is retry-able, -// otherwise it must return false for the first return parameter. +// The function must return true as its first return value if the error +// argument is retryable; otherwise, it must return false. // -// The function must return a non-zero time.Duration if the error contains -// explicit throttle duration that should be honored, otherwise it must return -// a zero valued time.Duration. +// The function must return a nonzero time.Duration if the error contains an +// explicit throttle duration that should be honored; otherwise, it must return +// the zero value for time.Duration. type EvaluateFunc func(error) (bool, time.Duration) -// RequestFunc returns a RequestFunc using the evaluate function to determine -// if requests can be retried and based on the exponential backoff -// configuration of c. +// RequestFunc returns a RequestFunc that uses evaluate to determine whether +// requests can be retried and uses c to configure exponential backoff. func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc { if !c.Enabled { return func(ctx context.Context, fn func(context.Context) error) error { @@ -96,7 +94,7 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc { // Check if context is canceled before attempting to wait and retry. if ctx.Err() != nil { - return fmt.Errorf("%w: %w", ctx.Err(), err) + return fmt.Errorf("%w: %w", context.Cause(ctx), err) } if maxElapsedTime != 0 && time.Since(startTime) > maxElapsedTime { @@ -119,12 +117,12 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc { } } -// Allow override for testing. +// waitFunc can be overridden for testing. var waitFunc = wait -// wait takes the caller's context, and the amount of time to wait. It will -// return nil if the timer fires before or at the same time as the context's -// deadline. This indicates that the call can be retried. +// wait blocks until delay elapses or ctx is done. If both happen +// simultaneously, wait favors the elapsed delay and returns nil to indicate +// that the call can be retried. func wait(ctx context.Context, delay time.Duration) error { timer := time.NewTimer(delay) defer timer.Stop() diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go index 721e4eb3b..b147c3ab1 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go @@ -4,11 +4,12 @@ // DO NOT MODIFY. Generated by gotmpl. // source: internal/shared/otlp/otlplog/transform/log.go.tmpl -// Package transform provides transformation functionality from the -// sdk/log data-types into OTLP data-types. +// Package transform provides transformations from SDK log data types to OTLP +// data types. package transform import ( + "math" "time" cpb "go.opentelemetry.io/proto/otlp/common/v1" @@ -21,7 +22,7 @@ import ( "go.opentelemetry.io/otel/sdk/log" ) -// ResourceLogs returns an slice of OTLP ResourceLogs generated from records. +// ResourceLogs returns a slice of OTLP ResourceLogs generated from records. func ResourceLogs(records []log.Record) []*lpb.ResourceLogs { if len(records) == 0 { return nil @@ -89,15 +90,15 @@ func ResourceLogs(records []log.Record) []*lpb.ResourceLogs { // LogRecord returns an OTLP LogRecord generated from record. func LogRecord(record log.Record) *lpb.LogRecord { r := &lpb.LogRecord{ - TimeUnixNano: timeUnixNano(record.Timestamp()), - ObservedTimeUnixNano: timeUnixNano(record.ObservedTimestamp()), - EventName: record.EventName(), - SeverityNumber: SeverityNumber(record.Severity()), - SeverityText: record.SeverityText(), - Body: AttrValue(record.Body()), - Attributes: make([]*cpb.KeyValue, 0, record.AttributesLen()), - Flags: uint32(record.TraceFlags()), - // TODO: DroppedAttributesCount: /* ... */, + TimeUnixNano: timeUnixNano(record.Timestamp()), + ObservedTimeUnixNano: timeUnixNano(record.ObservedTimestamp()), + EventName: record.EventName(), + SeverityNumber: SeverityNumber(record.Severity()), + SeverityText: record.SeverityText(), + Body: AttrValue(record.Body()), + Attributes: make([]*cpb.KeyValue, 0, record.AttributesLen()), + DroppedAttributesCount: clampUint32(record.DroppedAttributes()), + Flags: uint32(record.TraceFlags()), } record.WalkAttributes(func(kv attribute.KeyValue) bool { r.Attributes = append(r.Attributes, Attr(kv)) @@ -112,11 +113,12 @@ func LogRecord(record log.Record) *lpb.LogRecord { return r } -// timeUnixNano returns t as a Unix time, the number of nanoseconds elapsed -// since January 1, 1970 UTC as uint64. The result is undefined if the Unix -// time in nanoseconds cannot be represented by an int64 (a date before the -// year 1678 or after 2262). timeUnixNano on the zero Time returns 0. The -// result does not depend on the location associated with t. +// timeUnixNano returns t as the number of nanoseconds elapsed since January 1, +// 1970 UTC, represented as a uint64. The result is undefined if the Unix time +// in nanoseconds cannot be represented by an int64 (a date before the year 1678 +// or after the year 2262). For representable times before the Unix epoch, and +// for the zero [time.Time] value, timeUnixNano returns 0. The result does not +// depend on the location associated with t. func timeUnixNano(t time.Time) uint64 { nano := t.UnixNano() if nano < 0 { @@ -125,6 +127,16 @@ func timeUnixNano(t time.Time) uint64 { return uint64(nano) // nolint:gosec // Overflow checked. } +func clampUint32(v int) uint32 { + if v < 0 { + return 0 + } + if int64(v) > math.MaxUint32 { + return math.MaxUint32 + } + return uint32(v) // nolint:gosec // Overflow/Underflow checked. +} + // AttrIter transforms an [attribute.Iterator] into OTLP key-values. func AttrIter(iter attribute.Iterator) []*cpb.KeyValue { l := iter.Len() diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/version.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/version.go index 8741594f3..dde000360 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/version.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/version.go @@ -5,4 +5,4 @@ package internal // Version is the current release version of the OpenTelemetry otlploggrpc // exporter in use. -const Version = "0.21.0" +const Version = "0.22.0" diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/features.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/features.go index 7b66b367c..255592d4b 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/features.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/features.go @@ -6,10 +6,10 @@ package x import "strings" -// Observability is an experimental feature flag that determines if exporter -// observability metrics are enabled. +// Observability is an experimental feature flag that determines whether +// exporter observability metrics are enabled. // -// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable +// To enable this feature, set the OTEL_GO_X_OBSERVABILITY environment variable // to the case-insensitive string value of "true" (i.e. "True" and "TRUE" // will also enable this). var Observability = newFeature( diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/x.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/x.go index 7b6363673..666b7f32d 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/x.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/x.go @@ -34,9 +34,9 @@ func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] // feature. func (f Feature[T]) Keys() []string { return f.keys } -// Lookup returns the user configured value for the feature and true if the +// Lookup returns the user-configured value for the feature and true if the // user has enabled the feature. Otherwise, if the feature is not enabled, a -// zero-value and false are returned. +// zero value and false are returned. func (f Feature[T]) Lookup() (v T, ok bool) { // https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value // diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ/target.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ/target.go index 4d0665894..3bd379882 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ/target.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ/target.go @@ -21,13 +21,13 @@ const ( ) // ParseCanonicalTarget parses a target string and returns the extracted host -// (domain address or IP), the target port, or an error. +// (domain name or IP address), the target port, or an error. // // If no port is specified, -1 is returned. // // If no host is specified, an empty string is returned. // -// The target string is expected to always have the form +// The target string must have the form // "://[authority]/". For example: // - "dns:///example.com:42" // - "dns://8.8.8.8/example.com:42" @@ -35,8 +35,8 @@ const ( // - "unix-abstract:///socket-name" // - "passthrough:///192.34.2.1:42" // -// The target is expected to come from the CanonicalTarget method of a gRPC -// Client. +// The target is expected to be returned by gRPC's ClientConn.CanonicalTarget +// method. func ParseCanonicalTarget(target string) (string, int, error) { const sep = "://" @@ -60,7 +60,7 @@ func ParseCanonicalTarget(target string) (string, int, error) { return parseEndpoint(endpoint) } -// parseUnix parses unix socket targets. +// parseUnix parses Unix socket targets. func parseUnix(endpoint string) (string, int, error) { // Format: unix[-abstract]://path // @@ -70,8 +70,8 @@ func parseUnix(endpoint string) (string, int, error) { return endpoint, -1, nil } - // If there's no leading slash, it means there might be an authority - // Check for authority case (should error): "authority/path" + // If there is no leading slash, an authority might be present, which is + // invalid. Check for this case: "authority/path". if slashIdx := strings.Index(endpoint, "/"); slashIdx > 0 { return "", -1, fmt.Errorf("invalid (non-empty) authority: %s", endpoint[:slashIdx]) } @@ -97,8 +97,8 @@ func parseUnix(endpoint string) (string, int, error) { // - "[ipv6]:port" // - "[ipv6%zone]:port" // -// It returns the host or host%zone (domain address or IP), the port (or -1 if -// not specified), or an error if the input is not a valid. +// It returns the host or host%zone (domain name or IP address), the port (or +// -1 if not specified), or an error if the input is invalid. func parseEndpoint(endpoint string) (string, int, error) { // First check if the endpoint is just an IP address. if ip := parseIP(endpoint); ip != "" { diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/envconfig.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/envconfig.go index 8b3f729fd..3bfc2a6f3 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/envconfig.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/envconfig.go @@ -100,6 +100,8 @@ func getOptionsFromEnv() []GenericOption { envconfig.WithHeaders("TRACES_HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }), WithEnvCompression("COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }), WithEnvCompression("TRACES_COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }), + WithEnvProtocol("PROTOCOL", func(p Protocol) { opts = append(opts, WithProtocol(p)) }), + WithEnvProtocol("TRACES_PROTOCOL", func(p Protocol) { opts = append(opts, WithProtocol(p)) }), envconfig.WithDuration("TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), envconfig.WithDuration("TRACES_TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), ) @@ -139,6 +141,25 @@ func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOpt } } +// WithEnvProtocol retrieves the specified config and passes it to ConfigFn as a Protocol. +func WithEnvProtocol(n string, fn func(Protocol)) func(e *envconfig.EnvOptionsReader) { + return func(e *envconfig.EnvOptionsReader) { + if v, ok := e.GetEnvValue(n); ok { + protocol := ProtocolHTTPProtobuf + switch v { + case "grpc": + protocol = ProtocolGRPC + case "http/protobuf": + protocol = ProtocolHTTPProtobuf + case "http/json": + protocol = ProtocolHTTPJSON + } + + fn(protocol) + } + } +} + // revive:disable-next-line:flag-parameter func withInsecure(b bool) GenericOption { if b { diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options.go index 36cf1e267..a56847a3b 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options.go @@ -50,6 +50,7 @@ type ( TLSCfg *tls.Config Headers map[string]string Compression Compression + Protocol Protocol MaxRequestSize int Timeout time.Duration URLPath string @@ -84,6 +85,7 @@ func NewHTTPConfig(opts ...HTTPOption) Config { Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort), URLPath: DefaultTracesPath, Compression: NoCompression, + Protocol: ProtocolHTTPProtobuf, MaxRequestSize: DefaultMaxRequestSize, Timeout: DefaultTimeout, }, @@ -119,6 +121,7 @@ func NewGRPCConfig(opts ...GRPCOption) Config { Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort), URLPath: DefaultTracesPath, Compression: NoCompression, + Protocol: ProtocolGRPC, MaxRequestSize: DefaultMaxRequestSize, Timeout: DefaultTimeout, }, @@ -376,3 +379,25 @@ func WithHTTPClient(c *http.Client) GenericOption { return cfg }) } + +func WithProtocol(protocol Protocol) GenericOption { + return newSplitOption( + // For OTLP/HTTP endpoints, this is the encoding format of the payloads sent to the collector. + func(cfg Config) Config { + if protocol == ProtocolGRPC { + global.Warn("grpc is not a valid protocol for OTLP/HTTP, defaulting to http/protobuf") + protocol = ProtocolHTTPProtobuf + } + cfg.Traces.Protocol = protocol + return cfg + }, + // For OTLP/gRPC endpoints, it's always "grpc". + func(cfg Config) Config { + if protocol != ProtocolGRPC { + global.Debug("protocol option is ignored for OTLP/gRPC and is set to grpc") + } + cfg.Traces.Protocol = ProtocolGRPC + return cfg + }, + ) +} diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/optiontypes.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/optiontypes.go index 213c3605f..4f2da7427 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/optiontypes.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/optiontypes.go @@ -38,3 +38,15 @@ const ( // MarshalJSON tells the driver to send using json format. MarshalJSON ) + +// Protocol describes the transport protocol used to send data to the collector. +type Protocol int + +const ( + // ProtocolGRPC describes the "grpc" protocol. + ProtocolGRPC Protocol = iota + // ProtocolHTTPProtobuf describes the "http/protobuf" protocol. + ProtocolHTTPProtobuf + // ProtocolHTTPJSON describes the "http/json" protocol. + ProtocolHTTPJSON +) diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry/retry.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry/retry.go index 1883d0600..468c7c8eb 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry/retry.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry/retry.go @@ -17,7 +17,7 @@ import ( "github.com/cenkalti/backoff/v5" ) -// DefaultConfig are the recommended defaults to use. +// DefaultConfig is the recommended default configuration. var DefaultConfig = Config{ Enabled: true, InitialInterval: 5 * time.Second, @@ -25,42 +25,40 @@ var DefaultConfig = Config{ MaxElapsedTime: time.Minute, } -// Config defines configuration for retrying batches in case of export failure -// using an exponential backoff. +// Config defines configuration for retrying batches after an export failure +// using exponential backoff. type Config struct { - // Enabled indicates whether to not retry sending batches in case of - // export failure. + // Enabled indicates whether to retry sending batches after an export + // failure. Enabled bool - // InitialInterval the time to wait after the first failure before + // InitialInterval is the time to wait after the first failure before // retrying. InitialInterval time.Duration - // MaxInterval is the upper bound on backoff interval. Once this value is - // reached the delay between consecutive retries will always be - // `MaxInterval`. + // MaxInterval is the upper bound on the backoff interval before + // randomization. Once this value is reached, the base interval remains at + // MaxInterval. MaxInterval time.Duration // MaxElapsedTime is the maximum amount of time (including retries) spent - // trying to send a request/batch. Once this value is reached, the data - // is discarded. + // trying to send a request/batch. Once this value is reached, retrying stops. MaxElapsedTime time.Duration } // RequestFunc wraps a request with retry logic. type RequestFunc func(context.Context, func(context.Context) error) error -// EvaluateFunc returns if an error is retry-able and if an explicit throttle -// duration should be honored that was included in the error. +// EvaluateFunc reports whether an error is retryable and returns any explicit +// throttle duration to honor. // -// The function must return true if the error argument is retry-able, -// otherwise it must return false for the first return parameter. +// The function must return true as its first return value if the error +// argument is retryable; otherwise, it must return false. // -// The function must return a non-zero time.Duration if the error contains -// explicit throttle duration that should be honored, otherwise it must return -// a zero valued time.Duration. +// The function must return a nonzero time.Duration if the error contains an +// explicit throttle duration that should be honored; otherwise, it must return +// the zero value for time.Duration. type EvaluateFunc func(error) (bool, time.Duration) -// RequestFunc returns a RequestFunc using the evaluate function to determine -// if requests can be retried and based on the exponential backoff -// configuration of c. +// RequestFunc returns a RequestFunc that uses evaluate to determine whether +// requests can be retried and uses c to configure exponential backoff. func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc { if !c.Enabled { return func(ctx context.Context, fn func(context.Context) error) error { @@ -96,7 +94,7 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc { // Check if context is canceled before attempting to wait and retry. if ctx.Err() != nil { - return fmt.Errorf("%w: %w", ctx.Err(), err) + return fmt.Errorf("%w: %w", context.Cause(ctx), err) } if maxElapsedTime != 0 && time.Since(startTime) > maxElapsedTime { @@ -119,12 +117,12 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc { } } -// Allow override for testing. +// waitFunc can be overridden for testing. var waitFunc = wait -// wait takes the caller's context, and the amount of time to wait. It will -// return nil if the timer fires before or at the same time as the context's -// deadline. This indicates that the call can be retried. +// wait blocks until delay elapses or ctx is done. If both happen +// simultaneously, wait favors the elapsed delay and returns nil to indicate +// that the call can be retried. func wait(ctx context.Context, delay time.Duration) error { timer := time.NewTimer(delay) defer timer.Stop() diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/version.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/version.go index 942d37af7..d26674e57 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/version.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/version.go @@ -5,4 +5,4 @@ package internal // Version is the current release version of the OpenTelemetry OTLP gRPC trace // exporter in use. -const Version = "1.45.0" +const Version = "1.46.0" diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x/x.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x/x.go index 69ce31eef..71011358c 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x/x.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x/x.go @@ -34,9 +34,9 @@ func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] // feature. func (f Feature[T]) Keys() []string { return f.keys } -// Lookup returns the user configured value for the feature and true if the +// Lookup returns the user-configured value for the feature and true if the // user has enabled the feature. Otherwise, if the feature is not enabled, a -// zero-value and false are returned. +// zero value and false are returned. func (f Feature[T]) Lookup() (v T, ok bool) { // https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value // diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/version.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/version.go index da70f3f54..fd8ee8102 100644 --- a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/version.go +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/version.go @@ -5,5 +5,5 @@ package otlptrace // Version is the current release version of the OpenTelemetry OTLP trace exporter in use. func Version() string { - return "1.45.0" + return "1.46.0" } diff --git a/vendor/go.opentelemetry.io/otel/log/doc.go b/vendor/go.opentelemetry.io/otel/log/doc.go index c6f956269..211de35f5 100644 --- a/vendor/go.opentelemetry.io/otel/log/doc.go +++ b/vendor/go.opentelemetry.io/otel/log/doc.go @@ -4,8 +4,8 @@ /* Package log provides the OpenTelemetry Logs API. -This API is separate from its implementation so the instrumentation built from -it is reusable. See [go.opentelemetry.io/otel/sdk/log] for the official +This API is separate from its implementation, so the instrumentation built +from it is reusable. See [go.opentelemetry.io/otel/sdk/log] for the official OpenTelemetry implementation of this API. The log package provides the OpenTelemetry Logs API, which serves as a standard @@ -15,16 +15,16 @@ that can be easily integrated with observability tools. It ensures that log data in a way that is consistent with OpenTelemetry's data model. This package can be used to create bridges between existing logging libraries and OpenTelemetry. -Log bridges allow integrating the existing logging setups with OpenTelemetry. +Log bridges allow existing logging setups to be integrated with OpenTelemetry. Log bridges can be found in the [registry]. # API Implementations -This package does not conform to the standard Go versioning policy, all of its +This package does not conform to the standard Go versioning policy; all of its interfaces may have methods added to them without a package major version bump. This non-standard API evolution could surprise an uninformed implementation author. They could unknowingly build their implementation in a way that would -result in a runtime panic for their users that update to the new API. +result in a runtime panic for their users who update to the new API. The API is designed to help inform an instrumentation author about this non-standard API evolution. It requires them to choose a default behavior for @@ -38,7 +38,7 @@ make: All interfaces in this API embed a corresponding interface from [go.opentelemetry.io/otel/log/embedded]. If an author wants the default behavior of their implementations to be a compilation failure, signaling to -their users they need to update to the latest version of that implementation, +their users that they need to update to the latest version of that implementation, they need to embed the corresponding interface from [go.opentelemetry.io/otel/log/embedded] in their implementation. For example, @@ -49,7 +49,7 @@ they need to embed the corresponding interface from // ... } -If an author wants the default behavior of their implementations to a panic, +If an author wants the default behavior of their implementations to panic, they need to embed the API interface directly. import "go.opentelemetry.io/otel/log" @@ -60,7 +60,7 @@ they need to embed the API interface directly. } This is not a recommended behavior as it could lead to publishing packages that -contain runtime panics when users update other package that use newer versions +contain runtime panics when users update other packages that use newer versions of [go.opentelemetry.io/otel/log]. Finally, an author can embed another implementation in theirs. The embedded diff --git a/vendor/go.opentelemetry.io/otel/log/embedded/embedded.go b/vendor/go.opentelemetry.io/otel/log/embedded/embedded.go index 70bab2312..3b97fcddc 100644 --- a/vendor/go.opentelemetry.io/otel/log/embedded/embedded.go +++ b/vendor/go.opentelemetry.io/otel/log/embedded/embedded.go @@ -16,21 +16,21 @@ package embedded // LoggerProvider is embedded in the [Logs API LoggerProvider]. // // Embed this interface in your implementation of the [Logs API -// LoggerProvider] if you want users to experience a compilation error, -// signaling they need to update to your latest implementation, when the [Logs -// Bridge API LoggerProvider] interface is extended (which is something that -// can happen without a major version bump of the API package). +// LoggerProvider] if you want users to experience a compilation error when the +// [Logs Bridge API LoggerProvider] interface is extended (which is something +// that can happen without a major version bump of the API package). This error +// signals that they need to update to your latest implementation. // // [Logs API LoggerProvider]: https://pkg.go.dev/go.opentelemetry.io/otel/log#LoggerProvider type LoggerProvider interface{ loggerProvider() } -// Logger is embedded in [Logs API Logger]. +// Logger is embedded in the [Logs API Logger]. // // Embed this interface in your implementation of the [Logs API Logger] -// if you want users to experience a compilation error, signaling they need to -// update to your latest implementation, when the [Logs API Logger] -// interface is extended (which is something that can happen without a major -// version bump of the API package). +// if you want users to experience a compilation error when the [Logs API +// Logger] interface is extended (which is something that can happen without a +// major version bump of the API package). This error signals that they need to +// update to your latest implementation. // // [Logs API Logger]: https://pkg.go.dev/go.opentelemetry.io/otel/log#Logger type Logger interface{ logger() } diff --git a/vendor/go.opentelemetry.io/otel/log/global/log.go b/vendor/go.opentelemetry.io/otel/log/global/log.go index ac9957c3f..c827de906 100644 --- a/vendor/go.opentelemetry.io/otel/log/global/log.go +++ b/vendor/go.opentelemetry.io/otel/log/global/log.go @@ -22,7 +22,7 @@ import ( // If this is called before a global LoggerProvider is configured, the returned // Logger will be a No-Op implementation of a Logger. When a global // LoggerProvider is registered for the first time, the returned Logger is -// updated in-place to report to this new LoggerProvider. There is no need to +// updated in place to report to this new LoggerProvider. There is no need to // call this function again for an updated instance. // // This is a convenience function. It is equivalent to: @@ -35,9 +35,9 @@ func Logger(name string, options ...log.LoggerOption) log.Logger { // GetLoggerProvider returns the globally configured [log.LoggerProvider]. // // If a global LoggerProvider has not been configured with [SetLoggerProvider], -// the returned Logger will be a No-Op implementation of a LoggerProvider. When +// the returned LoggerProvider will be a No-Op implementation. When // a global LoggerProvider is registered for the first time, the returned -// LoggerProvider and all of its created Loggers are updated in-place. There is +// LoggerProvider and all Loggers it has created are updated in place. There is // no need to call this function again for an updated instance. func GetLoggerProvider() log.LoggerProvider { return global.GetLoggerProvider() diff --git a/vendor/go.opentelemetry.io/otel/log/internal/global/log.go b/vendor/go.opentelemetry.io/otel/log/internal/global/log.go index 14eadf2e3..d2c5403ca 100644 --- a/vendor/go.opentelemetry.io/otel/log/internal/global/log.go +++ b/vendor/go.opentelemetry.io/otel/log/internal/global/log.go @@ -17,7 +17,7 @@ import ( // instLib defines the instrumentation library a logger is created for. // -// Do not use sdk/instrumentation (API cannot depend on the SDK). +// Do not use sdk/instrumentation (the API cannot depend on the SDK). type instLib struct { name string version string @@ -33,7 +33,7 @@ type loggerProvider struct { delegate log.LoggerProvider } -// Compile-time guarantee loggerProvider implements LoggerProvider. +// This is a compile-time guarantee that loggerProvider implements LoggerProvider. var _ log.LoggerProvider = (*loggerProvider)(nil) func (p *loggerProvider) Logger(name string, options ...log.LoggerOption) log.Logger { @@ -87,7 +87,7 @@ type logger struct { delegate atomic.Value // log.Logger } -// Compile-time guarantee logger implements Logger. +// This is a compile-time guarantee that logger implements Logger. var _ log.Logger = (*logger)(nil) func (l *logger) Emit(ctx context.Context, r log.Record) { diff --git a/vendor/go.opentelemetry.io/otel/log/logger.go b/vendor/go.opentelemetry.io/otel/log/logger.go index 178e6be26..c0e4ca7ae 100644 --- a/vendor/go.opentelemetry.io/otel/log/logger.go +++ b/vendor/go.opentelemetry.io/otel/log/logger.go @@ -15,7 +15,7 @@ import ( // // Warning: Methods may be added to this interface in minor releases. See // package documentation on API implementation for information on how to set -// default behavior for unimplemented methods. +// the default behavior for unimplemented methods. type Logger interface { // Users of the interface can ignore this. This embedded type is only used // by implementations of this interface. See the "API Implementations" @@ -25,7 +25,7 @@ type Logger interface { // Emit emits a log record. // // The record may be held by the implementation. Callers should not mutate - // the record after passed. + // the record after it is passed. // // Implementations of this method need to be safe for a user to call // concurrently. @@ -34,22 +34,28 @@ type Logger interface { // Enabled reports whether the Logger emits for the given context and // param. // - // This is useful for users that want to know if a [Record] - // will be processed or dropped before they perform complex operations to - // construct the [Record]. Callers should invoke Enabled before each call - // to [Logger.Emit] because the enabled state may change over time. + // Calling Enabled is optional. It is not required before calling + // [Logger.Emit]. // - // The passed param is likely to be a partial record information being - // provided (e.g a param with only the Severity set). + // Enabled is useful when constructing a [Record] is expensive. A caller can + // call Enabled first and skip record construction when it returns false. + // When constructing the Record is inexpensive, a caller can emit it + // directly. + // + // The returned value is not static and may change over time. A cached value + // can become stale. + // + // The passed param is likely to contain only partial information about a + // record (e.g., a param with only the Severity set). // If a Logger needs more information than is provided, it // is said to be in an indeterminate state (see below). // - // The returned value will be true when the Logger will emit for the - // provided context and param, and will be false if the Logger will not - // emit. The returned value may be true or false in an indeterminate state. + // The returned value is true if the Logger emits for the provided context + // and param, and false if the Logger does not emit. The returned value may be + // true or false in an indeterminate state. // An implementation should default to returning true for an indeterminate - // state, but may return false if valid reasons in particular circumstances - // exist (e.g. performance, correctness). + // state, but may return false if there are valid reasons to do so in + // particular circumstances (e.g., performance or correctness). // // The param should not be held by the implementation. A copy should be // made if the param needs to be held after the call returns. @@ -61,7 +67,7 @@ type Logger interface { // LoggerOption applies configuration options to a [Logger]. type LoggerOption interface { - // applyLogger is used to set a LoggerOption value of a LoggerConfig. + // applyLogger applies a LoggerOption to a LoggerConfig. applyLogger(LoggerConfig) LoggerConfig } @@ -123,8 +129,8 @@ func WithInstrumentationVersion(version string) LoggerOption { }) } -// mergeSets returns the union of keys between a and b. Any duplicate keys will -// use the value associated with b. +// mergeSets returns the union of the keys in a and b. For duplicate keys, the +// value associated with b is used. func mergeSets(a, b attribute.Set) attribute.Set { // NewMergeIterator uses the first value for any duplicates. iter := attribute.NewMergeIterator(&b, &a) @@ -144,18 +150,20 @@ func mergeSets(a, b attribute.Set) attribute.Set { // // If multiple [WithInstrumentationAttributes] or [WithInstrumentationAttributeSet] // options are passed, the attributes will be merged together in the order -// they are passed. Attributes with duplicate keys will use the last value passed. +// they are passed. For attributes with duplicate keys, the last value passed +// will be used. func WithInstrumentationAttributes(attr ...attribute.KeyValue) LoggerOption { set := attribute.NewSet(slices.Clone(attr)...) return WithInstrumentationAttributeSet(set) } -// WithInstrumentationAttributeSet returns a [LoggerOption] that adds the -// instrumentation attributes of a [Logger]. +// WithInstrumentationAttributeSet returns a [LoggerOption] that adds +// instrumentation attributes to a [Logger]. // // If multiple [WithInstrumentationAttributes] or [WithInstrumentationAttributeSet] // options are passed, the attributes will be merged together in the order -// they are passed. Attributes with duplicate keys will use the last value passed. +// they are passed. For attributes with duplicate keys, the last value passed +// will be used. func WithInstrumentationAttributeSet(set attribute.Set) LoggerOption { if set.Len() == 0 { return loggerOptionFunc(func(config LoggerConfig) LoggerConfig { @@ -182,7 +190,7 @@ func WithSchemaURL(schemaURL string) LoggerOption { }) } -// EnabledParameters represents payload for [Logger]'s Enabled method. +// EnabledParameters represents the payload for [Logger.Enabled]. type EnabledParameters struct { Severity Severity EventName string diff --git a/vendor/go.opentelemetry.io/otel/log/noop/noop.go b/vendor/go.opentelemetry.io/otel/log/noop/noop.go index 082eb887b..0bb1de4ab 100644 --- a/vendor/go.opentelemetry.io/otel/log/noop/noop.go +++ b/vendor/go.opentelemetry.io/otel/log/noop/noop.go @@ -2,14 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 // Package noop provides an implementation of the [OpenTelemetry Logs Bridge -// API] that produces no telemetry and minimizes used computation resources. +// API] that produces no telemetry and minimizes computational resource usage. // // Using this package to implement the [OpenTelemetry Logs API] will // effectively disable OpenTelemetry. // // This implementation can be embedded in other implementations of the -// [OpenTelemetry Logs API]. Doing so will mean the implementation -// defaults to no operation for methods it does not implement. +// [OpenTelemetry Logs API]. Doing so means that the implementation defaults to +// performing no operation for methods it does not implement. // // [OpenTelemetry Logs API]: https://pkg.go.dev/go.opentelemetry.io/otel/log package noop @@ -22,7 +22,8 @@ import ( ) var ( - // Compile-time check this implements the OpenTelemetry API. + // These compile-time checks ensure that the implementations satisfy the + // OpenTelemetry API. _ log.LoggerProvider = LoggerProvider{} _ log.Logger = Logger{} ) diff --git a/vendor/go.opentelemetry.io/otel/log/provider.go b/vendor/go.opentelemetry.io/otel/log/provider.go index 62b7d73fc..10969b415 100644 --- a/vendor/go.opentelemetry.io/otel/log/provider.go +++ b/vendor/go.opentelemetry.io/otel/log/provider.go @@ -9,7 +9,7 @@ import "go.opentelemetry.io/otel/log/embedded" // // Warning: Methods may be added to this interface in minor releases. See // package documentation on API implementation for information on how to set -// default behavior for unimplemented methods. +// the default behavior for unimplemented methods. type LoggerProvider interface { // Users of the interface can ignore this. This embedded type is only used // by implementations of this interface. See the "API Implementations" @@ -19,7 +19,7 @@ type LoggerProvider interface { // Logger returns a new [Logger] with the provided name and configuration. // // The name needs to uniquely identify the source of logged code. It is - // recommended that name is the Go package name of the library using a log + // recommended that name be the Go package name of the library using a log // bridge (note: this is not the name of the bridge package). Most // commonly, this means a bridge will need to accept this value from its // users. @@ -27,7 +27,7 @@ type LoggerProvider interface { // An empty name is invalid. Implementations should retain the empty value as the // instrumentation scope name, return a working Logger, and report the invalid value. // - // The version of the packages using a bridge can be critical information + // The version of the package using a bridge can be critical information // to include when logging. The bridge should accept this version // information and use the [WithInstrumentationVersion] option to configure // the Logger appropriately. diff --git a/vendor/go.opentelemetry.io/otel/log/record.go b/vendor/go.opentelemetry.io/otel/log/record.go index 9e4e4d80b..351657e95 100644 --- a/vendor/go.opentelemetry.io/otel/log/record.go +++ b/vendor/go.opentelemetry.io/otel/log/record.go @@ -11,13 +11,13 @@ import ( ) // attributesInlineCount is the number of attributes that are efficiently -// stored in an array within a Record. This value is borrowed from slog which -// performed a quantitative survey of log library use and found this value to -// cover 95% of all use-cases (https://go.dev/blog/slog#performance). +// stored in an array within a Record. This value is borrowed from slog, which +// performed a quantitative survey of log library use and found that this value +// covers 95% of all use cases (https://go.dev/blog/slog#performance). const attributesInlineCount = 5 // Record represents a log record. -// A log record with non-empty event name is interpreted as an event record. +// A log record with a non-empty event name is interpreted as an event record. type Record struct { // Ensure forward compatibility by explicitly making this not comparable. noCmp [0]func() //nolint: unused // This is indeed used. @@ -45,19 +45,20 @@ type Record struct { // The list of attributes except for those in front. // Invariants: // - len(back) > 0 if nFront == len(front) - // - Unused array elements are zero-ed. Used to detect mistakes. + // - Unused array elements are zeroed to detect mistakes. back []attribute.KeyValue } // EventName returns the event name. -// A log record with non-empty event name is interpreted as an event record. +// A log record with a non-empty event name is interpreted as an event record. func (r *Record) EventName() string { return r.eventName } // SetEventName sets the event name. -// A log record with non-empty event name is interpreted as an event record. -// Event names should uniquely identify the event's attribute and body structure. +// A log record with a non-empty event name is interpreted as an event record. +// Event names should uniquely identify the structure of the event's attributes +// and body. func (r *Record) SetEventName(s string) { r.eventName = s } @@ -92,14 +93,14 @@ func (r *Record) SetSeverity(level Severity) { r.severity = level } -// SeverityText returns severity (also known as log level) text. This is the -// original string representation of the severity as it is known at the source. +// SeverityText returns the text of the severity (also known as the log level) +// as originally known at the source. func (r *Record) SeverityText() string { return r.severityText } -// SetSeverityText sets severity (also known as log level) text. This is the -// original string representation of the severity as it is known at the source. +// SetSeverityText sets the text of the severity (also known as the log level) +// as originally known at the source. func (r *Record) SetSeverityText(text string) { r.severityText = text } @@ -124,8 +125,8 @@ func (r *Record) SetErr(err error) { r.err = err } -// WalkAttributes walks all attributes the log record holds by calling f for -// each on each [attribute.KeyValue] in the [Record]. Iteration stops if f returns false. +// WalkAttributes walks all attributes in the log record by calling f for each +// [attribute.KeyValue] in the [Record]. Iteration stops if f returns false. func (r *Record) WalkAttributes(f func(attribute.KeyValue) bool) { for i := 0; i < r.nFront; i++ { if !f(r.front[i]) { @@ -158,7 +159,8 @@ func (r *Record) AttributesLen() int { } // Clone returns a copy of the record with no shared state. -// The original record and the clone can both be modified without interfering with each other. +// The original record and the clone can both be modified without interfering +// with each other. func (r *Record) Clone() Record { res := *r res.back = slices.Clone(r.back) diff --git a/vendor/go.opentelemetry.io/otel/log/severity.go b/vendor/go.opentelemetry.io/otel/log/severity.go index 29aed675d..4f305be36 100644 --- a/vendor/go.opentelemetry.io/otel/log/severity.go +++ b/vendor/go.opentelemetry.io/otel/log/severity.go @@ -5,56 +5,61 @@ package log -// Severity represents a log record severity (also known as log level). Smaller -// numerical values correspond to less severe log records (such as debug -// events), larger numerical values correspond to more severe log records (such -// as errors and critical events). +// Severity represents a log record's severity (also known as its log level). +// Smaller numerical values correspond to less severe log records (such as +// debug events); larger numerical values correspond to more severe log records +// (such as errors and critical events). type Severity int -// Severity values defined by OpenTelemetry. +// The following Severity values are defined by OpenTelemetry. const ( // SeverityUndefined represents an unset Severity. SeverityUndefined Severity = 0 // UNDEFINED - // A fine-grained debugging log record. Typically disabled in default - // configurations. + // The following are severity values for fine-grained debugging log records. + // They are typically disabled in default configurations. SeverityTrace1 Severity = 1 // TRACE SeverityTrace2 Severity = 2 // TRACE2 SeverityTrace3 Severity = 3 // TRACE3 SeverityTrace4 Severity = 4 // TRACE4 - // A debugging log record. + // The following are severity values for debugging log records. SeverityDebug1 Severity = 5 // DEBUG SeverityDebug2 Severity = 6 // DEBUG2 SeverityDebug3 Severity = 7 // DEBUG3 SeverityDebug4 Severity = 8 // DEBUG4 - // An informational log record. Indicates that an event happened. + // The following are severity values for informational log records indicating + // that an event happened. SeverityInfo1 Severity = 9 // INFO SeverityInfo2 Severity = 10 // INFO2 SeverityInfo3 Severity = 11 // INFO3 SeverityInfo4 Severity = 12 // INFO4 - // A warning log record. Not an error but is likely more important than an - // informational event. + // The following are severity values for warning log records. These records + // are not errors, but they are likely more important than informational + // events. SeverityWarn1 Severity = 13 // WARN SeverityWarn2 Severity = 14 // WARN2 SeverityWarn3 Severity = 15 // WARN3 SeverityWarn4 Severity = 16 // WARN4 - // An error log record. Something went wrong. + // The following are severity values for error log records indicating that + // something went wrong. SeverityError1 Severity = 17 // ERROR SeverityError2 Severity = 18 // ERROR2 SeverityError3 Severity = 19 // ERROR3 SeverityError4 Severity = 20 // ERROR4 - // A fatal log record such as application or system crash. + // The following are severity values for fatal log records, such as those + // associated with an application or system crash. SeverityFatal1 Severity = 21 // FATAL SeverityFatal2 Severity = 22 // FATAL2 SeverityFatal3 Severity = 23 // FATAL3 SeverityFatal4 Severity = 24 // FATAL4 - // Convenience definitions for the base severity of each level. + // The following are convenience definitions for the base severity of each + // level. SeverityTrace = SeverityTrace1 SeverityDebug = SeverityDebug1 SeverityInfo = SeverityInfo1 diff --git a/vendor/go.opentelemetry.io/otel/renovate.json b/vendor/go.opentelemetry.io/otel/renovate.json index fa5acf2d3..5008ab2bf 100644 --- a/vendor/go.opentelemetry.io/otel/renovate.json +++ b/vendor/go.opentelemetry.io/otel/renovate.json @@ -15,6 +15,13 @@ "matchDepTypes": ["indirect"], "enabled": true }, + { + "description": "Disable Go module major updates to v2+", + "matchManagers": ["gomod"], + "matchUpdateTypes": ["major"], + "matchNewValue": "/^v?([2-9]|[1-9][0-9]+)\\./", + "enabled": false + }, { "matchPackageNames": ["go.opentelemetry.io/build-tools/**"], "groupName": "build-tools" diff --git a/vendor/go.opentelemetry.io/otel/sdk/internal/attrnorm/truncate.go b/vendor/go.opentelemetry.io/otel/sdk/internal/attrnorm/truncate.go index 9955e9072..c8341599b 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/internal/attrnorm/truncate.go +++ b/vendor/go.opentelemetry.io/otel/sdk/internal/attrnorm/truncate.go @@ -16,10 +16,10 @@ import ( // Truncate returns a truncated version of attr. Only string, string slice, // byte slice, slice, and map attribute values are truncated. String values are -// truncated to at most a length of limit. Each string slice value is truncated -// in this fashion (the slice length itself is unaffected), and byte slice -// values are truncated to at most limit bytes. For slice and map attribute -// values, the limit is applied recursively to contained values. +// truncated according to limit. Each string slice value is truncated in this +// fashion (the slice length itself is unaffected), and byte slice values are +// truncated to at most limit bytes. For slice and map attribute values, the +// limit is applied recursively to contained values. // // No truncation is performed for a negative limit. func Truncate(limit int, attr attribute.KeyValue) attribute.KeyValue { @@ -155,22 +155,20 @@ func needsTruncation(limit int, v attribute.Value) bool { return false } -// truncate returns a truncated version of s such that it contains less than -// the limit number of characters. Truncation is applied by returning the limit -// number of valid characters contained in s. +// truncate returns a version of s truncated according to limit. // // If limit is negative, it returns the original string. // -// UTF-8 is supported. When truncating, all invalid characters are dropped +// UTF-8 is supported. When truncating, all invalid UTF-8 bytes are dropped // before applying truncation. // -// If s already contains less than the limit number of bytes, it is returned -// unchanged. No invalid characters are removed. +// If s already contains at most limit bytes, it is returned unchanged. No +// invalid characters are removed. func truncate(limit int, s string) string { - // This prioritize performance in the following order based on the most - // common expected use-cases. + // This prioritizes performance in the following order based on the most + // common expected use cases. // - // - Short values less than the default limit (128). + // - Values shorter than the default limit (128). // - Strings with valid encodings that exceed the limit. // - No limit. // - Strings with invalid encodings that exceed the limit. diff --git a/vendor/go.opentelemetry.io/otel/sdk/internal/x/x.go b/vendor/go.opentelemetry.io/otel/sdk/internal/x/x.go index e829d1794..67eea20a7 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/internal/x/x.go +++ b/vendor/go.opentelemetry.io/otel/sdk/internal/x/x.go @@ -34,9 +34,9 @@ func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] // feature. func (f Feature[T]) Keys() []string { return f.keys } -// Lookup returns the user configured value for the feature and true if the +// Lookup returns the user-configured value for the feature and true if the // user has enabled the feature. Otherwise, if the feature is not enabled, a -// zero-value and false are returned. +// zero value and false are returned. func (f Feature[T]) Lookup() (v T, ok bool) { // https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value // diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/batch.go b/vendor/go.opentelemetry.io/otel/sdk/log/batch.go index c3c8237d8..8ff2b797f 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/batch.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/batch.go @@ -29,13 +29,13 @@ const ( envarExpMaxBatchSize = "OTEL_BLRP_MAX_EXPORT_BATCH_SIZE" ) -// Compile-time check BatchProcessor implements Processor. +// This is a compile-time check that BatchProcessor implements Processor. var _ Processor = (*BatchProcessor)(nil) // BatchProcessor is a processor that exports batches of log records. // // Use [NewBatchProcessor] to create a BatchProcessor. An empty BatchProcessor -// is shut down by default, no records will be batched or exported. +// is shut down by default, so no records will be batched or exported. type BatchProcessor struct { // A single goroutine owns dequeueing and all exporter calls. OnEmit only // writes to the bounded queue and signals that goroutine. Consequently, @@ -77,7 +77,7 @@ func (r batchProcessorRequest) respond(err error) { } // NewBatchProcessor decorates the provided exporter -// so that the log records are batched before exporting. +// so that the log records are batched before they are exported. // // Calls to the exporter's Export, ForceFlush, and Shutdown methods are // synchronized and never invoked concurrently. @@ -267,7 +267,7 @@ func (*BatchProcessor) Enabled(context.Context, EnabledParameters) bool { return true } -// OnEmit batches provided log record. +// OnEmit batches the provided log record. func (b *BatchProcessor) OnEmit(_ context.Context, r *Record) error { if b.stopped.Load() || b.q == nil { return nil @@ -366,8 +366,8 @@ func (q *queue) Dropped() uint64 { // Enqueue adds r to the queue. The queue size, including the addition of r, is // returned. // -// If enqueueing r will exceed the capacity of q, the oldest Record held in q -// will be dropped and r retained. +// If enqueueing r would exceed the capacity of q, the oldest Record held in q +// will be dropped and r will be retained. func (q *queue) Enqueue(r Record) (int, bool) { q.Lock() defer q.Unlock() @@ -405,8 +405,7 @@ func (q *queue) Dequeue(buf []Record) (int, int) { return n, q.len } -// Flush returns all the Records held in the queue and resets it to be -// empty. +// Flush returns all the Records held in the queue and resets it to empty. func (q *queue) Flush() []Record { q.Lock() defer q.Unlock() @@ -487,12 +486,12 @@ func (fn batchOptionFunc) apply(c batchConfig) batchConfig { } // WithMaxQueueSize sets the maximum queue size used by the Batcher. -// After the size is reached log records are dropped. +// After the size is reached, log records are dropped. // -// If the OTEL_BLRP_MAX_QUEUE_SIZE environment variable is set, -// and this option is not passed, that variable value will be used. +// If the OTEL_BLRP_MAX_QUEUE_SIZE environment variable is set and this option +// is not passed, the value of that variable will be used. // -// By default, if an environment variable is not set, and this option is not +// By default, if the environment variable is not set and this option is not // passed, 2048 will be used. // The default value is also used when the provided value is less than one. func WithMaxQueueSize(size int) BatchProcessorOption { @@ -504,10 +503,10 @@ func WithMaxQueueSize(size int) BatchProcessorOption { // WithExportInterval sets the maximum duration between batched exports. // -// If the OTEL_BLRP_SCHEDULE_DELAY environment variable is set, -// and this option is not passed, that variable value will be used. +// If the OTEL_BLRP_SCHEDULE_DELAY environment variable is set and this option +// is not passed, the value of that variable will be used. // -// By default, if an environment variable is not set, and this option is not +// By default, if the environment variable is not set and this option is not // passed, 1s will be used. // The default value is also used when the provided value is less than one. func WithExportInterval(d time.Duration) BatchProcessorOption { @@ -519,10 +518,10 @@ func WithExportInterval(d time.Duration) BatchProcessorOption { // WithExportTimeout sets the duration after which a batched export is canceled. // -// If the OTEL_BLRP_EXPORT_TIMEOUT environment variable is set, -// and this option is not passed, that variable value will be used. +// If the OTEL_BLRP_EXPORT_TIMEOUT environment variable is set and this option +// is not passed, the value of that variable will be used. // -// By default, if an environment variable is not set, and this option is not +// By default, if the environment variable is not set and this option is not // passed, 30s will be used. // The default value is also used when the provided value is less than one. func WithExportTimeout(d time.Duration) BatchProcessorOption { @@ -533,12 +532,12 @@ func WithExportTimeout(d time.Duration) BatchProcessorOption { } // WithExportMaxBatchSize sets the maximum batch size of every export. -// A batch will be split into multiple exports to not exceed this size. +// A batch will be split into multiple exports so that none exceed this size. // -// If the OTEL_BLRP_MAX_EXPORT_BATCH_SIZE environment variable is set, -// and this option is not passed, that variable value will be used. +// If the OTEL_BLRP_MAX_EXPORT_BATCH_SIZE environment variable is set and this +// option is not passed, the value of that variable will be used. // -// By default, if an environment variable is not set, and this option is not +// By default, if the environment variable is not set and this option is not // passed, 512 or the maximum queue size, if smaller, will be used. // The default value is also used when the provided value is less than one. // The effective batch size will not exceed the configured maximum queue size. diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/doc.go b/vendor/go.opentelemetry.io/otel/sdk/log/doc.go index 5153a0e55..1574f8ee2 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/doc.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/doc.go @@ -5,30 +5,29 @@ Package log provides the OpenTelemetry Logs SDK. See https://opentelemetry.io/docs/concepts/signals/logs/ for information -about the concept of OpenTelemetry Logs and +about OpenTelemetry Logs and https://opentelemetry.io/docs/concepts/components/ for more information about OpenTelemetry SDKs. The entry point for the log package is [NewLoggerProvider]. -[LoggerProvider] is the object that all Bridge API calls use to create -Loggers, and ultimately emit log records. -Also, it is an object that should be used to -control the life-cycle (start, flush, and shutdown) of the Logs SDK. +[LoggerProvider] is the object that all Bridge API calls use to create Loggers +and ultimately emit log records. It should also be used to control the +lifecycle (start, flush, and shutdown) of the Logs SDK. -A LoggerProvider needs to be configured to process the log records, this is -done by configuring it with a [Processor] implementation using [WithProcessor]. -The log package provides the [BatchProcessor] and [SimpleProcessor] -that are configured with an [Exporter] implementation which -exports the log records to given destination. See +A LoggerProvider needs to be configured to process log records. This is done +by configuring it with a [Processor] implementation using [WithProcessor]. +The log package provides [BatchProcessor] and [SimpleProcessor], which are +configured with an [Exporter] implementation that exports log records to a +given destination. See [go.opentelemetry.io/otel/exporters] for exporters that can be used with these Processors. -The data generated by a LoggerProvider needs to include information about its -origin. A LoggerProvider needs to be configured with a Resource, by using -[WithResource], to include this information. This Resource -should be used to describe the unique runtime environment instrumented code -is being run on. That way when multiple instances of the code are collected -at a single endpoint their origin is decipherable. +A LoggerProvider needs to include information about the origin of the data it +generates. It needs to be configured with a Resource by using [WithResource] +to include this information. This Resource should describe the unique runtime +environment in which the instrumented code runs. That way, when telemetry from +multiple instances of the code is collected at a single endpoint, the origin +of each instance is decipherable. See [go.opentelemetry.io/otel/sdk/log/internal/x] for information about the experimental features. diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/exporter.go b/vendor/go.opentelemetry.io/otel/sdk/log/exporter.go index 973ff91d7..35a8012d1 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/exporter.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/exporter.go @@ -11,8 +11,15 @@ import ( "go.opentelemetry.io/otel/sdk/log/internal/observ" ) +// ErrExporterShutdown is returned if Export is called after an +// Exporter has been Shutdown. +var ErrExporterShutdown = errors.New("exporter is shutdown") + // Exporter handles the delivery of log records to external receivers. type Exporter interface { + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. + // Export transmits log records to a receiver. // // The deadline or cancellation of the passed context must be honored. An @@ -28,9 +35,13 @@ type Exporter interface { // Before modifying a Record, the implementation must use Record.Clone // to create a copy that shares no state with the original. // + // Export should return [ErrExporterShutdown] if called after Shutdown. + // // Export should never be called concurrently with other Export calls. // However, it may be called concurrently with other methods. Export(ctx context.Context, records []Record) error + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. // Shutdown is called when the SDK shuts down. Any cleanup or release of // resources held by the exporter should be done in this call. @@ -38,20 +49,24 @@ type Exporter interface { // The deadline or cancellation of the passed context must be honored. An // appropriate error should be returned in these situations. // - // After Shutdown is called, calls to Export, Shutdown, or ForceFlush - // should perform no operation and return nil error. + // After Shutdown is called, calls to Shutdown or ForceFlush should perform + // no operation and return nil. Calls to Export should return + // [ErrExporterShutdown]. // // Shutdown may be called concurrently with itself or with other methods. Shutdown(ctx context.Context) error + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. - // ForceFlush exports log records to the configured Exporter that have not yet - // been exported. + // ForceFlush flushes any log records held by the Exporter. // // The deadline or cancellation of the passed context must be honored. An // appropriate error should be returned in these situations. // // ForceFlush may be called concurrently with itself or with other methods. ForceFlush(ctx context.Context) error + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. } var defaultNoopExporter = &noopExporter{} @@ -79,9 +94,9 @@ type chunkExporter struct { size int } -// newChunkExporter wraps exporter. Calls to the Export will have their records -// payload chunked so they do not exceed size. If size is less than or equal -// to 0, exporter is returned directly. +// newChunkExporter wraps exporter. Record payloads passed to Export are +// chunked so that they do not exceed size. If size is less than or equal to 0, +// exporter is returned directly. func newChunkExporter(exporter Exporter, size int) Exporter { if size <= 0 { return exporter @@ -107,8 +122,8 @@ func (c chunkExporter) Export(ctx context.Context, records []Record) error { return errors.Join(errs...) } -// timeoutExporter wraps an Exporter and ensures any call to Export will have a -// timeout for the context. +// timeoutExporter wraps an Exporter and adds a timeout to the context of any +// call to Export. type timeoutExporter struct { Exporter @@ -116,9 +131,9 @@ type timeoutExporter struct { timeout time.Duration } -// newTimeoutExporter wraps exporter with an Exporter that limits the context -// lifetime passed to Export to be timeout. If timeout is less than or equal to -// zero, exporter will be returned directly. +// newTimeoutExporter wraps exporter with an Exporter that limits the lifetime +// of the context passed to Export to the timeout value. If timeout is less than +// or equal to zero, exporter will be returned directly. func newTimeoutExporter(exp Exporter, timeout time.Duration) Exporter { if timeout <= 0 { return exp @@ -126,7 +141,7 @@ func newTimeoutExporter(exp Exporter, timeout time.Duration) Exporter { return &timeoutExporter{Exporter: exp, timeout: timeout} } -// Export sets the timeout of ctx before calling the Exporter e wraps. +// Export sets a timeout on ctx before calling the Exporter that e wraps. func (e *timeoutExporter) Export(ctx context.Context, records []Record) error { // This only used by the batch processor, and it takes processor timeout config. // Thus, the error message points to the processor. So users know they should adjust the processor timeout. @@ -150,9 +165,9 @@ func newMetricsExporter(exporter Exporter, inst *observ.BLP) Exporter { } } -// Export records the number of log records as a metric then forwards -// them to the wrapped Exporter. Error returned from wrapped exporter -// is not considered as per specification (to be measured by exporter). +// Export records the number of log records as a metric, then forwards them to +// the wrapped Exporter. As specified, the error returned by the wrapped +// exporter is not considered because it is to be measured by the exporter. func (e *metricsExporter) Export(ctx context.Context, records []Record) error { if e.inst != nil { e.inst.Processed(ctx, int64(len(records))) diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/internal/attrnorm/truncate.go b/vendor/go.opentelemetry.io/otel/sdk/log/internal/attrnorm/truncate.go index 9955e9072..c8341599b 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/internal/attrnorm/truncate.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/internal/attrnorm/truncate.go @@ -16,10 +16,10 @@ import ( // Truncate returns a truncated version of attr. Only string, string slice, // byte slice, slice, and map attribute values are truncated. String values are -// truncated to at most a length of limit. Each string slice value is truncated -// in this fashion (the slice length itself is unaffected), and byte slice -// values are truncated to at most limit bytes. For slice and map attribute -// values, the limit is applied recursively to contained values. +// truncated according to limit. Each string slice value is truncated in this +// fashion (the slice length itself is unaffected), and byte slice values are +// truncated to at most limit bytes. For slice and map attribute values, the +// limit is applied recursively to contained values. // // No truncation is performed for a negative limit. func Truncate(limit int, attr attribute.KeyValue) attribute.KeyValue { @@ -155,22 +155,20 @@ func needsTruncation(limit int, v attribute.Value) bool { return false } -// truncate returns a truncated version of s such that it contains less than -// the limit number of characters. Truncation is applied by returning the limit -// number of valid characters contained in s. +// truncate returns a version of s truncated according to limit. // // If limit is negative, it returns the original string. // -// UTF-8 is supported. When truncating, all invalid characters are dropped +// UTF-8 is supported. When truncating, all invalid UTF-8 bytes are dropped // before applying truncation. // -// If s already contains less than the limit number of bytes, it is returned -// unchanged. No invalid characters are removed. +// If s already contains at most limit bytes, it is returned unchanged. No +// invalid characters are removed. func truncate(limit int, s string) string { - // This prioritize performance in the following order based on the most - // common expected use-cases. + // This prioritizes performance in the following order based on the most + // common expected use cases. // - // - Short values less than the default limit (128). + // - Values shorter than the default limit (128). // - Strings with valid encodings that exceed the limit. // - No limit. // - Strings with invalid encodings that exceed the limit. diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/batch_log_processor.go b/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/batch_log_processor.go index e6c6d6074..2e1e7fe22 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/batch_log_processor.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/batch_log_processor.go @@ -42,8 +42,8 @@ type BLP struct { processedQueueFullOpts []metric.AddOption } -// NewBLP creates a new BatchLogProcessor instrumentation. -// Returns nil if observability is not enabled. +// NewBLP creates new instrumentation for a BatchLogProcessor. +// It returns nil if observability is not enabled. func NewBLP(id int64, qLen func() int64, qMax int64) (*BLP, error) { if !x.Observability.Enabled() { return nil, nil @@ -107,6 +107,7 @@ func NewBLP(id int64, qLen func() int64, qMax int64) (*BLP, error) { }, nil } +// Shutdown unregisters the callbacks registered by NewBLP. func (b *BLP) Shutdown() error { if b == nil || b.reg == nil { return nil @@ -114,12 +115,16 @@ func (b *BLP) Shutdown() error { return b.reg.Unregister() } +// Processed records n log records as having finished processing +// successfully. func (b *BLP) Processed(ctx context.Context, n int64) { if b.processed.Enabled(ctx) { b.processed.Add(ctx, n, b.processedOpts...) } } +// ProcessedQueueFull records n log records as having finished processing +// with a queue-full error. func (b *BLP) ProcessedQueueFull(ctx context.Context, n int64) { if b.processed.Enabled(ctx) { b.processed.Add(ctx, n, b.processedQueueFullOpts...) diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/doc.go b/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/doc.go index 1c52a93e3..53a3fe79f 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/doc.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/doc.go @@ -1,6 +1,5 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -// Package observ provides observability instrumentation for the OTel log SDK -// package. +// Package observ provides observability instrumentation for the OTel Logs SDK. package observ diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/simple_log_processor.go b/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/simple_log_processor.go index 9f19320ad..77161278a 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/simple_log_processor.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/simple_log_processor.go @@ -6,7 +6,6 @@ package observ import ( "context" "fmt" - "sync" "sync/atomic" "go.opentelemetry.io/otel" @@ -23,21 +22,11 @@ const ( ScopeName = "go.opentelemetry.io/otel/sdk/log/internal/observ" ) -var measureAttrsPool = sync.Pool{ - New: func() any { - // "component.name" + "component.type" + "error.type" - const n = 1 + 1 + 1 - s := make([]attribute.KeyValue, 0, n) - // Return a pointer to a slice instead of a slice itself - // to avoid allocations on every call. - return &s - }, -} - -// simpleProcessorN is a global 0-based count of the number of simple processor created. +// simpleProcessorN is a global zero-based count of the number of simple +// processors created. var simpleProcessorN atomic.Int64 -// NextSimpleProcessorID returns the next unique ID for a simpleProcessor. +// NextSimpleProcessorID returns the next unique ID for a simple processor. func NextSimpleProcessorID() int64 { const inc = 1 return simpleProcessorN.Add(inc) - inc @@ -63,7 +52,6 @@ func GetSLPComponentName(id int64) attribute.KeyValue { // SLP is the instrumentation for an OTel SDK SimpleLogProcessor. type SLP struct { processed metric.Int64Counter - attrs []attribute.KeyValue addOpts []metric.AddOption } @@ -96,34 +84,15 @@ func NewSLP(id int64) (*SLP, error) { return &SLP{ processed: p.Inst(), - attrs: attrs, addOpts: addOpts, }, nil } -// LogProcessed records that a log has been processed by the SimpleLogProcessor. -// If err is non-nil, it records the processing error as an attribute. -func (slp *SLP) LogProcessed(ctx context.Context, err error) { +// LogProcessed records that a log record has been submitted to the exporter by +// the SimpleLogProcessor. Per the semantic conventions, this count is recorded +// at submission time and MUST NOT be affected by the export outcome. +func (slp *SLP) LogProcessed(ctx context.Context) { if slp.processed.Enabled(ctx) { - slp.processed.Add(ctx, 1, slp.addOption(err)...) + slp.processed.Add(ctx, 1, slp.addOpts...) } } - -func (slp *SLP) addOption(err error) []metric.AddOption { - if err == nil { - return slp.addOpts - } - attrs := measureAttrsPool.Get().(*[]attribute.KeyValue) - defer func() { - clear(*attrs) - *attrs = (*attrs)[:0] // reset the slice - measureAttrsPool.Put(attrs) - }() - - *attrs = append(*attrs, slp.attrs...) - *attrs = append(*attrs, semconv.ErrorType(err)) - - // Do not inefficiently make a copy of attrs by using - // WithAttributes instead of WithAttributeSet. - return []metric.AddOption{metric.WithAttributeSet(attribute.NewSet(*attrs...))} -} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/features.go b/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/features.go index 76e1bf7e2..0de8bcace 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/features.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/features.go @@ -6,11 +6,11 @@ package x import "strings" -// Observability is an experimental feature flag that determines if SDK +// Observability is an experimental feature flag that determines whether SDK // observability metrics are enabled. // -// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable -// to the case-insensitive string value of "true" (i.e. "True" and "TRUE" +// To enable this feature, set the OTEL_GO_X_OBSERVABILITY environment variable +// to the case-insensitive string value of "true" (i.e., "True" and "TRUE" // will also enable this). var Observability = newFeature( []string{"OBSERVABILITY", "SELF_OBSERVABILITY"}, diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/x.go b/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/x.go index 90a1660bc..17c3c4a4c 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/x.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/x.go @@ -34,9 +34,9 @@ func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] // feature. func (f Feature[T]) Keys() []string { return f.keys } -// Lookup returns the user configured value for the feature and true if the +// Lookup returns the user-configured value for the feature and true if the // user has enabled the feature. Otherwise, if the feature is not enabled, a -// zero-value and false are returned. +// zero value and false are returned. func (f Feature[T]) Lookup() (v T, ok bool) { // https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value // diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/logger.go b/vendor/go.opentelemetry.io/otel/sdk/log/logger.go index 7f8adcf66..fd8487f17 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/logger.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/logger.go @@ -26,7 +26,7 @@ const ( exceptionMessageKey = semconv.ExceptionMessageKey ) -// Compile-time check logger implements log.Logger. +// This is a compile-time check that logger implements log.Logger. var _ log.Logger = (*logger)(nil) type logger struct { @@ -95,13 +95,15 @@ func (l *logger) recordCreated(ctx context.Context) { } // Enabled returns true if at least one Processor held by the LoggerProvider -// that created the logger will process for the provided context and param. +// that created the logger will process a record for the provided context and +// param. // -// Enabled returns false after the LoggerProvider that created l starts shutdown. +// Enabled returns false after the LoggerProvider that created l starts shutting +// down. // -// If it is not possible to definitively determine the record will be +// If it is not possible to definitively determine whether the record will be // processed, true will be returned by default. A value of false will only be -// returned if it can be positively verified that no Processor will process. +// returned if it can be positively verified that no Processor will process it. func (l *logger) Enabled(ctx context.Context, param log.EnabledParameters) bool { p := EnabledParameters{ InstrumentationScope: l.instrumentationScope, @@ -173,7 +175,7 @@ func (l *logger) newRecord(ctx context.Context, r log.Record) Record { if err := r.Err(); err != nil && (!hasExceptionMessage || !hasExceptionType) { // Derive missing exception attributes by default, as required by the // Logs SDK specification. Attribute limits may constrain generation, - // so stop once there is no capacity for another attribute. + // but attributes omitted due to those limits are still counted. var attrs [2]attribute.KeyValue n := 0 @@ -187,6 +189,13 @@ func (l *logger) newRecord(ctx context.Context, r log.Record) Record { if !hasExceptionMessage { if msg := err.Error(); msg != "" { if hasLimit && remaining <= n { + dropped := 1 + if !hasExceptionType { + // Every non-nil error has a concrete type, so avoid + // resolving it when it cannot be retained. + dropped++ + } + newRecord.addDropped(dropped) goto flush } attrs[n] = exceptionMessageKey.String(msg) @@ -194,10 +203,11 @@ func (l *logger) newRecord(ctx context.Context, r log.Record) Record { } } if !hasExceptionType { + if hasLimit && remaining <= n { + newRecord.addDropped(1) + goto flush + } if errType := errorType(err); errType != "" { - if hasLimit && remaining <= n { - goto flush - } attrs[n] = exceptionTypeKey.String(errType) n++ } diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/processor.go b/vendor/go.opentelemetry.io/otel/sdk/log/processor.go index 31cd72c20..ffb4bb0bd 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/processor.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/processor.go @@ -24,8 +24,11 @@ import ( // OnEmit, or ForceFlush when shutdown starts. Callers that use a Processor // directly are responsible for coordinating those calls with Shutdown. type Processor interface { - // Enabled reports whether the Processor will process for the given context - // and param. + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. + + // Enabled reports whether the Processor will process a Record for the given + // context and param. // // Enabled is called synchronously and should not block. // @@ -36,9 +39,8 @@ type Processor interface { // may be unable to populate all fields in EnabledParameters even though // they are present on the final Record. // - // The returned value will be true when the Processor will process for the - // provided context and param, and will be false if the Processor will not - // process. + // The returned value is true if the Processor would process a Record for the + // provided context and param, and false otherwise. // // Implementations that need additional information beyond what is provided // in param should treat the decision as indeterminate and default to @@ -46,19 +48,21 @@ type Processor interface { // (for example, to meet performance or correctness constraints). // // Processor implementations are expected to re-evaluate the [Record] passed - // to OnEmit. It is not expected that the caller to OnEmit will - // use the result from Enabled prior to calling OnEmit. + // to OnEmit. It is not expected that the caller of OnEmit will + // use the result of Enabled prior to calling OnEmit. // // The SDK's Logger.Enabled returns false if all the registered processors // return false. Otherwise, it returns true. // Enabled(ctx context.Context, param EnabledParameters) bool + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. // OnEmit is called when a Record is emitted. // // OnEmit is called synchronously and should not block. // - // OnEmit will be called independent of Enabled. Implementations need to + // OnEmit will be called independently of Enabled. Implementations need to // validate the arguments themselves before processing. // // Implementations should not stop processing a Record solely because the @@ -69,15 +73,17 @@ type Processor interface { // Errors returned by this function are treated as unrecoverable by the SDK // and will be reported to a configured error Handler. // - // The SDK invokes the processors sequentially in the same order as - // they were registered using WithProcessor. + // The SDK invokes the processors sequentially in the same order as they were + // registered with WithProcessor. // Implementations may synchronously modify the record so that the changes // are visible in the next registered processor. // - // Note that Record is not concurrent safe. Therefore, asynchronous + // Note that Record is not concurrent-safe. Therefore, asynchronous // processing may cause race conditions. Use Record.Clone // to create a copy that shares no state with the original. OnEmit(ctx context.Context, record *Record) error + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. // Shutdown is called when the SDK shuts down. Any cleanup or release of // resources held by the Processor (and any underlying Exporter) should be @@ -94,18 +100,22 @@ type Processor interface { // appropriate error should be returned in these situations. // // After Shutdown is called, calls to OnEmit, Shutdown, or ForceFlush - // should perform no operation and return nil error. + // should perform no operation and return nil. Shutdown(ctx context.Context) error + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. - // ForceFlush exports log records to the configured Exporter that have not yet - // been exported. + // ForceFlush exports any log records that have not yet been exported to the + // configured Exporter. // // The deadline or cancellation of the passed context must be honored. An // appropriate error should be returned in these situations. ForceFlush(ctx context.Context) error + // DO NOT CHANGE: any modification will not be backwards compatible and + // must never be done outside of a new major release. } -// EnabledParameters represents payload for [Processor]'s Enabled method. +// EnabledParameters represents the payload for [Processor.Enabled]. type EnabledParameters struct { InstrumentationScope instrumentation.Scope Severity log.Severity diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/provider.go b/vendor/go.opentelemetry.io/otel/sdk/log/provider.go index cf5472c0d..4711e6ab4 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/provider.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/provider.go @@ -10,6 +10,7 @@ import ( "sync/atomic" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/embedded" @@ -91,14 +92,15 @@ type LoggerProvider struct { noCmp [0]func() //nolint: unused // This is indeed used. } -// Compile-time check LoggerProvider implements log.LoggerProvider. +// This is a compile-time check that LoggerProvider implements +// log.LoggerProvider. var _ log.LoggerProvider = (*LoggerProvider)(nil) -// NewLoggerProvider returns a new and configured LoggerProvider. +// NewLoggerProvider returns a new, configured LoggerProvider. // // By default, the returned LoggerProvider is configured with the default -// Resource and no Processors. Processors cannot be added after a LoggerProvider is -// created. This means the returned LoggerProvider, one created with no +// Resource and no Processors. Processors cannot be added after a LoggerProvider +// is created. This means the returned LoggerProvider, one created with no // Processors, will perform no operations. func NewLoggerProvider(opts ...LoggerProviderOption) *LoggerProvider { cfg := newProviderConfig(opts) @@ -115,6 +117,8 @@ func NewLoggerProvider(opts ...LoggerProviderOption) *LoggerProvider { // // Calls made after [LoggerProvider.Shutdown] starts return a [noop.Logger]. // +// Instrumentation scope attributes with invalid keys are ignored. +// // This method can be called concurrently. func (p *LoggerProvider) Logger(name string, opts ...log.LoggerOption) log.Logger { if name == "" { @@ -127,6 +131,10 @@ func (p *LoggerProvider) Logger(name string, opts ...log.LoggerOption) log.Logge cfg := log.NewLoggerConfig(opts...) attrs := cfg.InstrumentationAttributes() + attrs, invalid := attrs.Filter(attribute.KeyValue.Valid) + if len(invalid) > 0 { + logInvalidAttribute() + } if !p.allowDupKeys { attrs, _ = attrnorm.Set(attrs) } @@ -158,14 +166,14 @@ func (p *LoggerProvider) Logger(name string, opts ...log.LoggerOption) log.Logge // Shutdown shuts down the provider and all processors in the order they were // registered. // -// The first call stops admitting new operations that invoke processor Enabled, -// OnEmit, or ForceFlush methods. It waits for operations already admitted to -// complete before synchronously invoking each processor's Shutdown method. If -// ctx is canceled before the admitted operations complete, Shutdown returns -// ctx.Err() without invoking processor Shutdown. +// The first call stops admitting new operations that invoke a processor's +// Enabled, OnEmit, or ForceFlush method. It waits for operations already +// admitted to complete before synchronously invoking each processor's Shutdown +// method. If ctx is canceled before the admitted operations complete, Shutdown +// returns ctx.Err() without invoking any processor's Shutdown method. // -// Concurrent or subsequent Shutdown calls return nil without invoking -// processor Shutdown. +// Concurrent or subsequent Shutdown calls return nil without invoking any +// processor's Shutdown method. // // Shutdown must not be called directly or indirectly from a Processor method. // @@ -284,7 +292,7 @@ func WithResource(res *resource.Resource) LoggerProviderOption { }) } -// WithProcessor associates Processor with a LoggerProvider. +// WithProcessor associates a Processor with a LoggerProvider. // // By default, if this option is not used, the LoggerProvider will perform no // operations; no data will be exported without a processor. @@ -292,8 +300,9 @@ func WithResource(res *resource.Resource) LoggerProviderOption { // The SDK invokes the processors sequentially in the same order as they were // registered. // -// For production, use [NewBatchProcessor] to batch log records before they are exported. -// For testing and debugging, use [NewSimpleProcessor] to synchronously export log records. +// For production, use [NewBatchProcessor] to batch log records before they are +// exported. For testing and debugging, use [NewSimpleProcessor] to +// synchronously export log records. func WithProcessor(processor Processor) LoggerProviderOption { return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig { cfg.processors = append(cfg.processors, processor) @@ -308,10 +317,10 @@ func WithProcessor(processor Processor) LoggerProviderOption { // // Setting this to a negative value means no limit is applied. // -// If the OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT environment variable is set, -// and this option is not passed, that variable value will be used. +// If the OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT environment variable is set and +// this option is not passed, the value of that variable will be used. // -// By default, if an environment variable is not set, and this option is not +// By default, if the environment variable is not set and this option is not // passed, 128 will be used. func WithAttributeCountLimit(limit int) LoggerProviderOption { return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig { @@ -327,10 +336,10 @@ func WithAttributeCountLimit(limit int) LoggerProviderOption { // // Setting this to a negative value means no limit is applied. // -// If the OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT environment variable is set, -// and this option is not passed, that variable value will be used. +// If the OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT environment variable is +// set and this option is not passed, the value of that variable will be used. // -// By default, if an environment variable is not set, and this option is not +// By default, if the environment variable is not set and this option is not // passed, no limit (-1) will be used. func WithAttributeValueLengthLimit(limit int) LoggerProviderOption { return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig { @@ -342,15 +351,16 @@ func WithAttributeValueLengthLimit(limit int) LoggerProviderOption { // WithAllowKeyDuplication sets whether deduplication is skipped for log record // and instrumentation scope key-value collections. // -// By default, the key-value collections within a log record and +// By default, the key-value collections within a log record and an // instrumentation scope are deduplicated to comply with the OpenTelemetry // Specification. // Deduplication means that if multiple key-value pairs with the same key are -// present, only a single pair is retained and others are discarded. Resource -// attributes are always deduplicated by go.opentelemetry.io/otel/sdk/resource. +// present, only a single pair is retained and the others are discarded. +// Resource attributes are always deduplicated by +// go.opentelemetry.io/otel/sdk/resource. // -// Disabling deduplication with this option can improve performance e.g. of -// adding attributes to the log record. +// Disabling deduplication with this option can improve performance when adding +// attributes to the log record. // // Receivers may handle duplicate keys unpredictably. If you disable // deduplication, you are responsible for ensuring that duplicate keys within a diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/record.go b/vendor/go.opentelemetry.io/otel/sdk/log/record.go index 6f64e04e3..5d5614e01 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/record.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/record.go @@ -19,9 +19,9 @@ import ( const ( // attributesInlineCount is the number of attributes that are efficiently - // stored in an array within a Record. This value is borrowed from slog which - // performed a quantitative survey of log library use and found this value to - // cover 95% of all use-cases (https://go.dev/blog/slog#performance). + // stored in an array within a Record. This value is borrowed from slog, which + // performed a quantitative survey of log library use and found that this + // value covers 95% of all use cases (https://go.dev/blog/slog#performance). attributesInlineCount = 5 // maxUniqueSize helps reduce peak allocation. maxUniqueSize = 1028 @@ -35,7 +35,11 @@ var logKeyValuePairDropped = sync.OnceFunc(func() { global.Warn("key duplication: dropping key-value pair") }) -// uniquePool is a pool of unique attributes used for attributes de-duplication. +var logInvalidAttribute = sync.OnceFunc(func() { + global.Warn("invalid attribute: dropping attribute with empty key") +}) + +// uniquePool is a pool of unique attributes used for attribute deduplication. var uniquePool = sync.Pool{ New: func() any { return new([]attribute.KeyValue) }, } @@ -52,7 +56,7 @@ func putUnique(v *[]attribute.KeyValue) { } } -// indexPool is a pool of index maps used for attributes de-duplication. +// indexPool is a pool of index maps used for attribute deduplication. var indexPool = sync.Pool{ New: func() any { return make(map[attribute.Key]int) }, } @@ -67,13 +71,13 @@ func putIndex(index map[attribute.Key]int) { } // Record is a log record emitted by the Logger. -// A log record with non-empty event name is interpreted as an event record. +// A log record with a non-empty event name is interpreted as an event record. // // Do not create instances of Record on your own in production code. // You can use [go.opentelemetry.io/otel/sdk/log/logtest.RecordFactory] // for testing purposes. type Record struct { - // Do not embed the log.Record. Attributes need to be overwrite-able and + // Do not embed the log.Record. Attributes need to be overwritable, and // deep-copying needs to be possible. eventName string @@ -98,7 +102,7 @@ type Record struct { // The list of attributes except for those in front. // Invariants: // - len(back) > 0 if nFront == len(front) - // - Unused array elements are zero-ed. Used to detect mistakes. + // - Unused array elements are zeroed to detect mistakes. back []attribute.KeyValue // dropped is the count of attributes that have been dropped when limits @@ -118,7 +122,8 @@ type Record struct { attributeValueLengthLimit int attributeCountLimit int - // specifies whether we should deduplicate any key value collections or not + // allowDupKeys specifies whether duplicate keys are allowed in key-value + // collections. allowDupKeys bool noCmp [0]func() //nolint: unused // This is indeed used. @@ -132,13 +137,13 @@ func (r *Record) addDropped(n int) { } // EventName returns the event name. -// A log record with non-empty event name is interpreted as an event record. +// A log record with a non-empty event name is interpreted as an event record. func (r *Record) EventName() string { return r.eventName } // SetEventName sets the event name. -// A log record with non-empty event name is interpreted as an event record. +// A log record with a non-empty event name is interpreted as an event record. func (r *Record) SetEventName(s string) { r.eventName = s } @@ -173,14 +178,16 @@ func (r *Record) SetSeverity(level log.Severity) { r.severity = level } -// SeverityText returns severity (also known as log level) text. This is the -// original string representation of the severity as it is known at the source. +// SeverityText returns the text of the severity (also known as the log level). +// This is the original string representation of the severity as it is known at +// the source. func (r *Record) SeverityText() string { return r.severityText } -// SetSeverityText sets severity (also known as log level) text. This is the -// original string representation of the severity as it is known at the source. +// SetSeverityText sets the text of the severity (also known as the log level). +// This is the original string representation of the severity as it is known at +// the source. func (r *Record) SetSeverityText(text string) { r.severityText = text } @@ -199,8 +206,9 @@ func (r *Record) SetBody(v attribute.Value) { } } -// WalkAttributes walks all attributes the log record holds by calling f for -// each on each [attribute.KeyValue] in the [Record]. Iteration stops if f returns false. +// WalkAttributes walks through all attributes held by the log record, calling +// f for each [attribute.KeyValue] in the [Record]. Iteration stops if f returns +// false. func (r *Record) WalkAttributes(f func(attribute.KeyValue) bool) { for i := 0; i < r.nFront; i++ { if !f(r.front[i]) { @@ -215,8 +223,11 @@ func (r *Record) WalkAttributes(f func(attribute.KeyValue) bool) { } // AddAttributes adds attributes to the log record. -// Attributes in attrs will overwrite any attribute already added to r with the same key. +// Unless key duplication is enabled with [WithAllowKeyDuplication], an +// attribute in attrs overwrites any attribute already added to r with the same +// key. Attributes with invalid keys are ignored. func (r *Record) AddAttributes(attrs ...attribute.KeyValue) { + attrs = filterInvalid(attrs) n := r.AttributesLen() if n == 0 { // Avoid the more complex duplicate map lookups below. @@ -300,11 +311,11 @@ func (r *Record) AddAttributes(attrs ...attribute.KeyValue) { } // attrIndex returns an index map for all attributes in the Record r. The index -// maps the attribute key to location the attribute is stored. If the value is -// < 0 then -(value + 1) (e.g. -1 -> 0, -2 -> 1, -3 -> 2) represents the index -// in r.nFront. Otherwise, the index is the exact index of r.back. +// maps the attribute key to the location where the attribute is stored. If the +// value is < 0, then -(value + 1) (e.g., -1 -> 0, -2 -> 1, -3 -> 2) represents +// the index in r.front. Otherwise, the value is the exact index in r.back. // -// The returned index is taken from the indexPool. It is the callers +// The returned index is taken from the indexPool. It is the caller's // responsibility to return the index to that pool (putIndex) when done. func (r *Record) attrIndex() map[attribute.Key]int { index := getIndex() @@ -320,8 +331,8 @@ func (r *Record) attrIndex() map[attribute.Key]int { } // addAttrs adds attrs to the Record r. This does not validate any limits or -// duplication of attributes, these tasks are left to the caller to handle -// prior to calling. +// attribute duplication; these tasks are left to the caller to handle before +// calling addAttrs. func (r *Record) addAttrs(attrs []attribute.KeyValue) { var i int for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ { @@ -339,8 +350,10 @@ func (r *Record) addAttrs(attrs []attribute.KeyValue) { } } -// SetAttributes sets (and overrides) attributes to the log record. +// SetAttributes sets (and overrides) attributes on the log record. +// Attributes with invalid keys are ignored. func (r *Record) SetAttributes(attrs ...attribute.KeyValue) { + attrs = filterInvalid(attrs) var drop int r.dropped = 0 if !r.allowDupKeys { @@ -367,6 +380,32 @@ func (r *Record) SetAttributes(attrs ...attribute.KeyValue) { } } +// filterInvalid returns attrs without invalid attributes. The original slice is +// returned when all attributes are valid. +func filterInvalid(attrs []attribute.KeyValue) []attribute.KeyValue { + valid := 0 + for _, attr := range attrs { + if attr.Valid() { + valid++ + } + } + if valid == len(attrs) { + return attrs + } + logInvalidAttribute() + if valid == 0 { + return nil + } + + filtered := make([]attribute.KeyValue, 0, valid) + for _, attr := range attrs { + if attr.Valid() { + filtered = append(filtered, attr) + } + } + return filtered +} + // head returns the attributes r can retain along with the number dropped. func (r *Record) head(kvs []attribute.KeyValue) (out []attribute.KeyValue, dropped int) { if r.attributeCountLimit < 0 || len(kvs) <= r.attributeCountLimit { @@ -418,12 +457,12 @@ func (r *Record) AttributesLen() int { } // DroppedAttributes returns the number of attributes dropped due to limits -// being reached. +// being reached. Invalid attributes are ignored and not counted. func (r *Record) DroppedAttributes() int { return r.dropped } -// TraceID returns the trace ID or empty array. +// TraceID returns the trace ID or an empty array. func (r *Record) TraceID() trace.TraceID { return r.traceID } @@ -433,7 +472,7 @@ func (r *Record) SetTraceID(id trace.TraceID) { r.traceID = id } -// SpanID returns the span ID or empty array. +// SpanID returns the span ID or an empty array. func (r *Record) SpanID() trace.SpanID { return r.spanID } diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/ring.go b/vendor/go.opentelemetry.io/otel/sdk/log/ring.go index 95da3816c..2b901a81e 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/ring.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/ring.go @@ -8,12 +8,12 @@ package log // A ring is an element of a circular list, or ring. Rings do not have a -// beginning or end; a pointer to any ring element serves as reference to the +// beginning or end; a pointer to any ring element serves as a reference to the // entire ring. Empty rings are represented as nil ring pointers. The zero // value for a ring is a one-element ring with a nil Value. // -// This is copied from the "container/ring" package. It uses a Record type for -// Value instead of any to avoid allocations. +// This is copied from the "container/ring" package. It uses Record as the type +// of Value instead of any to avoid allocations. type ring struct { next, prev *ring Value Record @@ -57,7 +57,7 @@ func newRing(n int) *ring { return r } -// Len computes the number of elements in ring r. It executes in time +// Len computes the number of elements in the ring r. It executes in time // proportional to the number of elements. func (r *ring) Len() int { n := 0 @@ -70,7 +70,7 @@ func (r *ring) Len() int { return n } -// Do calls function f on each element of the ring, in forward order. The +// Do calls f on each element of the ring, in forward order. The // behavior of Do is undefined if f changes *r. func (r *ring) Do(f func(Record)) { if r != nil { diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/setting.go b/vendor/go.opentelemetry.io/otel/sdk/log/setting.go index eb232bcb2..11a54552f 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/setting.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/setting.go @@ -23,18 +23,18 @@ func newSetting[T any](value T) setting[T] { return setting[T]{Value: value, Set: true} } -// resolver returns an updated setting after applying an resolution operation. +// resolver returns an updated setting after applying a resolution operation. type resolver[T any] func(setting[T]) setting[T] // Resolve returns a resolved version of s. // -// It will apply all the passed fn in the order provided, chaining together the -// return setting to the next input. The setting s is used as the initial -// argument to the first fn. +// It applies all functions passed to it in the order provided, passing the +// setting returned by each function as input to the next. The setting s is used +// as the initial argument to the first function. // -// Each fn needs to validate if it should apply given the Set state of the -// setting. This will not perform any checks on the set state when chaining -// function. +// Each function needs to determine whether it should apply to the setting based +// on the setting's Set state. Resolve does not perform any checks on the Set +// state when chaining functions. func (s setting[T]) Resolve(fn ...resolver[T]) setting[T] { for _, f := range fn { s = f(s) @@ -65,8 +65,8 @@ func clearLessThanOne[T ~int | ~int64]() resolver[T] { } } -// getenv returns a resolver that will apply an integer environment variable -// value associated with key to a setting value. +// getenv returns a resolver that applies the integer value of the environment +// variable associated with key to a setting. // // If the input setting to the resolver is set, the environment variable will // not be applied. @@ -75,8 +75,8 @@ func clearLessThanOne[T ~int | ~int64]() resolver[T] { // error will be sent to the OTel error handler and the setting will not be // updated. // -// If the setting value is a [time.Duration] type, the environment variable -// will be interpreted as a duration of milliseconds. +// If the setting value has type [time.Duration], the environment variable will +// be interpreted as a duration in milliseconds. func getenv[T ~int | ~int64](key string) resolver[T] { return func(s setting[T]) setting[T] { if s.Set { @@ -103,7 +103,7 @@ func getenv[T ~int | ~int64](key string) resolver[T] { } } -// fallback returns a resolve that will set a setting value to val if it is not +// fallback returns a resolver that will set a setting value to val if it is not // already set. // // This is usually passed at the end of a resolver chain to ensure a default is diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/simple.go b/vendor/go.opentelemetry.io/otel/sdk/log/simple.go index ab83f19a2..6b0c6017e 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/log/simple.go +++ b/vendor/go.opentelemetry.io/otel/sdk/log/simple.go @@ -11,10 +11,10 @@ import ( "go.opentelemetry.io/otel/sdk/log/internal/observ" ) -// Compile-time check SimpleProcessor implements Processor. +// This is a compile-time check that SimpleProcessor implements Processor. var _ Processor = (*SimpleProcessor)(nil) -// SimpleProcessor is an processor that synchronously exports log records. +// SimpleProcessor is a processor that synchronously exports log records. // // Use [NewSimpleProcessor] to create a SimpleProcessor. type SimpleProcessor struct { @@ -29,8 +29,8 @@ type SimpleProcessor struct { // This Processor is not recommended for production use due to its synchronous // nature, which makes it suitable for testing, debugging, or demonstrating // other features, but can lead to slow performance and high computational -// overhead. For production environments, it is recommended to use -// [NewBatchProcessor] instead. However, there may be exceptions where certain +// overhead. For production environments, use [NewBatchProcessor] instead. +// However, there may be exceptions in which certain // [Exporter] implementations perform better with this Processor. func NewSimpleProcessor(exporter Exporter, _ ...SimpleProcessorOption) *SimpleProcessor { slp := &SimpleProcessor{ @@ -56,8 +56,8 @@ func (*SimpleProcessor) Enabled(context.Context, EnabledParameters) bool { return true } -// OnEmit batches provided log record. -func (s *SimpleProcessor) OnEmit(ctx context.Context, r *Record) (err error) { +// OnEmit synchronously exports the provided log record. +func (s *SimpleProcessor) OnEmit(ctx context.Context, r *Record) error { if s.exporter == nil { return nil } @@ -73,9 +73,9 @@ func (s *SimpleProcessor) OnEmit(ctx context.Context, r *Record) (err error) { (*records)[0] = *r if s.inst != nil { - defer func() { - s.inst.LogProcessed(ctx, err) - }() + // Record the log record as processed at the point it is submitted to + // the exporter, independent of the export outcome. + s.inst.LogProcessed(ctx) } return s.exporter.Export(ctx, *records) } diff --git a/vendor/go.opentelemetry.io/otel/sdk/resource/resource.go b/vendor/go.opentelemetry.io/otel/sdk/resource/resource.go index 68ab274d2..dcd7280ac 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/resource/resource.go +++ b/vendor/go.opentelemetry.io/otel/sdk/resource/resource.go @@ -120,6 +120,9 @@ func (r *Resource) String() string { // MarshalLog is the marshaling function used by the logging system to represent this Resource. func (r *Resource) MarshalLog() any { + if r == nil { + r = Empty() + } return struct { Attributes attribute.Set SchemaURL string diff --git a/vendor/go.opentelemetry.io/otel/sdk/trace/internal/observ/simple_span_processor.go b/vendor/go.opentelemetry.io/otel/sdk/trace/internal/observ/simple_span_processor.go index 89922e4b8..e7ca1a6d9 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/trace/internal/observ/simple_span_processor.go +++ b/vendor/go.opentelemetry.io/otel/sdk/trace/internal/observ/simple_span_processor.go @@ -6,7 +6,6 @@ package observ import ( "context" "fmt" - "sync" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -17,22 +16,10 @@ import ( "go.opentelemetry.io/otel/semconv/v1.43.0/otelconv" ) -var measureAttrsPool = sync.Pool{ - New: func() any { - // "component.name" + "component.type" + "error.type" - const n = 1 + 1 + 1 - s := make([]attribute.KeyValue, 0, n) - // Return a pointer to a slice instead of a slice itself - // to avoid allocations on every call. - return &s - }, -} - // SSP is the instrumentation for an OTel SDK SimpleSpanProcessor. type SSP struct { spansProcessedCounter metric.Int64Counter addOpts []metric.AddOption - attrs []attribute.KeyValue } // SSPComponentName returns the component name attribute for a @@ -70,29 +57,12 @@ func NewSSP(id int64) (*SSP, error) { return &SSP{ spansProcessedCounter: spansProcessedCounter.Inst(), addOpts: addOpts, - attrs: attrs, }, err } -// SpanProcessed records that a span has been processed by the SimpleSpanProcessor. -// If err is non-nil, it records the processing error as an attribute. -func (ssp *SSP) SpanProcessed(ctx context.Context, err error) { - ssp.spansProcessedCounter.Add(ctx, 1, ssp.addOption(err)...) -} - -func (ssp *SSP) addOption(err error) []metric.AddOption { - if err == nil { - return ssp.addOpts - } - attrs := measureAttrsPool.Get().(*[]attribute.KeyValue) - defer func() { - clear(*attrs) - *attrs = (*attrs)[:0] // reset the slice for reuse - measureAttrsPool.Put(attrs) - }() - *attrs = append(*attrs, ssp.attrs...) - *attrs = append(*attrs, semconv.ErrorType(err)) - // Do not inefficiently make a copy of attrs by using - // WithAttributes instead of WithAttributeSet. - return []metric.AddOption{metric.WithAttributeSet(attribute.NewSet(*attrs...))} +// SpanProcessed records that a span has been submitted to the exporter by the +// SimpleSpanProcessor. Per the semantic conventions, this count is recorded at +// submission time and MUST NOT be affected by the export outcome. +func (ssp *SSP) SpanProcessed(ctx context.Context) { + ssp.spansProcessedCounter.Add(ctx, 1, ssp.addOpts...) } diff --git a/vendor/go.opentelemetry.io/otel/sdk/trace/simple_span_processor.go b/vendor/go.opentelemetry.io/otel/sdk/trace/simple_span_processor.go index e9bec4394..97985c498 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/trace/simple_span_processor.go +++ b/vendor/go.opentelemetry.io/otel/sdk/trace/simple_span_processor.go @@ -67,19 +67,19 @@ func (ssp *simpleSpanProcessor) OnEnd(s ReadOnlySpan) { ssp.exporterMu.Lock() defer ssp.exporterMu.Unlock() - var err error - if ssp.exporter != nil && s.SpanContext().TraceFlags().IsSampled() { - err = ssp.exporter.ExportSpans(context.Background(), []ReadOnlySpan{s}) - if err != nil { - otel.Handle(err) - } - } - if ssp.inst != nil { // Add the span to the context to ensure the metric is recorded - // with the correct span context. + // with the correct span context. Record the span as processed before + // invoking the exporter so the count is unaffected by the export + // outcome. ctx := trace.ContextWithSpanContext(context.Background(), s.SpanContext()) - ssp.inst.SpanProcessed(ctx, err) + ssp.inst.SpanProcessed(ctx) + } + + if ssp.exporter != nil && s.SpanContext().TraceFlags().IsSampled() { + if err := ssp.exporter.ExportSpans(context.Background(), []ReadOnlySpan{s}); err != nil { + otel.Handle(err) + } } } diff --git a/vendor/go.opentelemetry.io/otel/sdk/trace/span.go b/vendor/go.opentelemetry.io/otel/sdk/trace/span.go index 0aae5f5bb..aae977314 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/trace/span.go +++ b/vendor/go.opentelemetry.io/otel/sdk/trace/span.go @@ -138,6 +138,11 @@ type recordingSpan struct { attributes []attribute.KeyValue droppedAttributes int logDropAttrsOnce sync.Once + // attributesDirty indicates attributes may contain duplicate keys. + attributesDirty bool + // attributesShared indicates the attributes backing array has been exposed + // to a reader and must not be modified. + attributesShared bool // events are stored in FIFO queue capped by configured limit. events evictedQueue[Event] @@ -251,6 +256,11 @@ func (s *recordingSpan) SetAttributes(attributes ...attribute.KeyValue) { s.addDroppedAttr(len(attributes)) return } + maxCap := len(s.attributes) + len(attributes) + if limit > 0 { + maxCap = min(maxCap, limit) + } + s.ensureAttrsWritable(maxCap) // If adding these attributes could exceed the capacity of s perform a // de-duplication and truncation while adding to avoid over allocation. @@ -271,9 +281,23 @@ func (s *recordingSpan) SetAttributes(attributes ...attribute.KeyValue) { a = dedupAttr(a) a = attrnorm.Truncate(s.tracer.provider.spanLimits.AttributeValueLengthLimit, a) s.attributes = append(s.attributes, a) + s.attributesDirty = true } } +// ensureAttrsWritable copies shared attributes into a backing array with at +// least capacity. This method assumes s.mu.Lock is held by the caller. +func (s *recordingSpan) ensureAttrsWritable(capacity int) { + if !s.attributesShared { + return + } + capacity = max(capacity, len(s.attributes)) + attrs := make([]attribute.KeyValue, len(s.attributes), capacity) + copy(attrs, s.attributes) + s.attributes = attrs + s.attributesShared = false +} + // Declared as a var so tests can override. var logDropAttrs = func() { global.Warn("limit reached: dropping trace Span attributes") @@ -592,6 +616,7 @@ func (s *recordingSpan) Attributes() []attribute.KeyValue { s.mu.Lock() defer s.mu.Unlock() s.dedupeAttrs() + s.attributesShared = cap(s.attributes) > 0 return s.attributes } @@ -599,6 +624,10 @@ func (s *recordingSpan) Attributes() []attribute.KeyValue { // // This method assumes s.mu.Lock is held by the caller. func (s *recordingSpan) dedupeAttrs() { + if !s.attributesDirty { + return + } + s.ensureAttrsWritable(len(s.attributes)) // Do not set a capacity when creating this map. Benchmark testing has // showed this to only add unused memory allocations in general use. exists := make(map[attribute.Key]int, len(s.attributes)) @@ -622,6 +651,7 @@ func (s *recordingSpan) dedupeAttrsFromRecord(record map[attribute.Key]int) { } clear(s.attributes[len(unique):]) // Erase unneeded elements to let GC collect objects. s.attributes = unique + s.attributesDirty = false } // Links returns the links of this span. @@ -765,6 +795,7 @@ func (s *recordingSpan) snapshot() ReadOnlySpan { if len(s.attributes) > 0 { s.dedupeAttrs() sd.attributes = s.attributes + s.attributesShared = true } sd.droppedAttributeCount = s.droppedAttributes if len(s.events.queue) > 0 { diff --git a/vendor/go.opentelemetry.io/otel/sdk/version.go b/vendor/go.opentelemetry.io/otel/sdk/version.go index 1619fb3fa..621d6d7e6 100644 --- a/vendor/go.opentelemetry.io/otel/sdk/version.go +++ b/vendor/go.opentelemetry.io/otel/sdk/version.go @@ -6,5 +6,5 @@ package sdk // Version is the current release version of the OpenTelemetry SDK in use. func Version() string { - return "1.45.0" + return "1.46.0" } diff --git a/vendor/go.opentelemetry.io/otel/version.go b/vendor/go.opentelemetry.io/otel/version.go index ea7903096..f599bf285 100644 --- a/vendor/go.opentelemetry.io/otel/version.go +++ b/vendor/go.opentelemetry.io/otel/version.go @@ -5,5 +5,5 @@ package otel // Version is the current release version of OpenTelemetry in use. func Version() string { - return "1.45.0" + return "1.46.0" } diff --git a/vendor/go.opentelemetry.io/otel/versions.yaml b/vendor/go.opentelemetry.io/otel/versions.yaml index 3b1ad7301..f329d171d 100644 --- a/vendor/go.opentelemetry.io/otel/versions.yaml +++ b/vendor/go.opentelemetry.io/otel/versions.yaml @@ -3,7 +3,7 @@ module-sets: stable-v1: - version: v1.45.0 + version: v1.46.0 modules: - go.opentelemetry.io/otel - go.opentelemetry.io/otel/bridge/opencensus @@ -22,12 +22,12 @@ module-sets: - go.opentelemetry.io/otel/sdk/metric - go.opentelemetry.io/otel/trace experimental-metrics: - version: v0.67.0 + version: v0.68.0 modules: - go.opentelemetry.io/otel/exporters/prometheus - go.opentelemetry.io/otel/metric/x experimental-logs: - version: v0.21.0 + version: v0.22.0 modules: - go.opentelemetry.io/otel/log - go.opentelemetry.io/otel/log/logtest @@ -37,7 +37,7 @@ module-sets: - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp - go.opentelemetry.io/otel/exporters/stdout/stdoutlog experimental-schema: - version: v0.0.18 + version: v0.0.19 modules: - go.opentelemetry.io/otel/schema excluded-modules: diff --git a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go index 29d332e7b..333448127 100644 --- a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go +++ b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go @@ -150,8 +150,18 @@ var ( // throttling limit if unforeseen issues arise, and it will be removed in a // future release. // - // TODO: Remove this env var once v1.83.0 is release. + // TODO: Remove this env var once v1.83.0 is released. ControlBufferThrottleLimit = uint64FromEnv("GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT", 100, 1, 10000) + + // EnableReceiveBufferCompaction enables the compaction of data buffers + // to reduce the number of buffers in the receive buffer. + // + // This environment variable serves as an escape hatch to disable the + // feature if unforeseen issues arise, and it will be removed in a future + // release. + // + // TODO: Remove this env var once v1.85.0 is released. + EnableReceiveBufferCompaction = boolFromEnv("GRPC_GO_EXPERIMENTAL_ENABLE_RECEIVE_BUFFER_COMPACTION", true) ) func boolFromEnv(envVar string, def bool) bool { diff --git a/vendor/google.golang.org/grpc/internal/mem/buffer_pool.go b/vendor/google.golang.org/grpc/internal/mem/buffer_pool.go index 2d83b2ece..00aeca419 100644 --- a/vendor/google.golang.org/grpc/internal/mem/buffer_pool.go +++ b/vendor/google.golang.org/grpc/internal/mem/buffer_pool.go @@ -26,12 +26,26 @@ import ( "slices" "sort" "sync" + + "google.golang.org/grpc/internal" ) const ( goPageSize = 4 * 1024 // 4KiB. N.B. this must be a power of 2. ) +var ( + // BufferPoolingThreshold is the minimum size of a buffer that can be pooled. + // This is used to determine whether to pool buffers or allocate them directly. + BufferPoolingThreshold = 1 << 10 +) + +func init() { + internal.SetBufferPoolingThresholdForTesting = func(threshold int) { + BufferPoolingThreshold = threshold + } +} + var uintSize = bits.UintSize // use a variable for mocking during tests. // bufferPool is a copy of the public bufferPool interface used to avoid diff --git a/vendor/google.golang.org/grpc/internal/transport/handler_server.go b/vendor/google.golang.org/grpc/internal/transport/handler_server.go index a8356c9ad..9cd8d28d3 100644 --- a/vendor/google.golang.org/grpc/internal/transport/handler_server.go +++ b/vendor/google.golang.org/grpc/internal/transport/handler_server.go @@ -424,7 +424,7 @@ func (ht *serverHandlerTransport) HandleStreams(ctx context.Context, startStream st: ht, headerWireLength: 0, // won't have access to header wire length until golang/go#18997. } - s.Stream.buf.init() + s.Stream.buf.init(ht.bufferPool) s.readRequester = s s.trReader = transportReader{ reader: recvBufferReader{ctx: s.ctx, ctxDone: s.ctx.Done(), recv: &s.buf}, diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_client.go b/vendor/google.golang.org/grpc/internal/transport/http2_client.go index c19b45080..10d197741 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_client.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_client.go @@ -500,7 +500,7 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr, handler s headerChan: make(chan struct{}), statsHandler: handler, } - s.Stream.buf.init() + s.Stream.buf.init(t.bufferPool) s.Stream.wq.init(defaultWriteQuota, s.done) s.readRequester = s // The client side stream context should have exactly the same life cycle with the user provided context. diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_server.go b/vendor/google.golang.org/grpc/internal/transport/http2_server.go index be8ae9f9c..63c6539a3 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_server.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_server.go @@ -407,7 +407,7 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade st: t, headerWireLength: int(frame.Header().Length), } - s.Stream.buf.init() + s.Stream.buf.init(t.bufferPool) var ( // if false, content-type was missing or invalid isGRPC = false diff --git a/vendor/google.golang.org/grpc/internal/transport/transport.go b/vendor/google.golang.org/grpc/internal/transport/transport.go index d2e49538f..5fc901e5c 100644 --- a/vendor/google.golang.org/grpc/internal/transport/transport.go +++ b/vendor/google.golang.org/grpc/internal/transport/transport.go @@ -30,11 +30,14 @@ import ( "sync" "sync/atomic" "time" + "unsafe" "golang.org/x/net/http2" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/internal/channelz" + "google.golang.org/grpc/internal/envconfig" + imem "google.golang.org/grpc/internal/mem" "google.golang.org/grpc/internal/transport/internal" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/mem" @@ -45,7 +48,30 @@ import ( "google.golang.org/grpc/tap" ) -const logLevel = 2 +const ( + logLevel = 2 + // recvMsgSize estimates the memory overhead of a recvMsg in the backlog. + // It accounts for the recvMsg struct itself and the slice header of the + // underlying buffer's data. + recvMsgSize = int(unsafe.Sizeof(recvMsg{}) + unsafe.Sizeof([]byte{})) + + // utilizationFactor controls when we consider memory utilization acceptable. + // When backlogHeapSize / payloadSize <= utilizationFactor (meaning at least + // 50% of the heap memory is actual payload data), compaction is skipped. + utilizationFactor = 2 +) + +var ( + // compactionThreshold is approx 57KB (on 64-bit systems). It allows + // accumulating up to 1024 1-byte payloads before triggering compaction. + // + // Because individual payloads <= 1024 bytes are allocated on the heap + // outside mem.BufferPool, waiting for at least 1024 bytes to accumulate + // ensures that compaction coalesces those small heap allocations into a + // single large buffer from mem.BufferPool, enabling buffer reuse while + // avoiding frequent copying for small bursts of frames. + compactionThreshold = imem.BufferPoolingThreshold * (recvMsgSize + 1) +) func init() { internal.TimeNowFunc = func() int64 { return time.Now().UnixNano() } @@ -71,23 +97,31 @@ type recvBuffer struct { c chan recvMsg mu sync.Mutex backlog []recvMsg - err error + // uncompactedSuffixLen tracks the number of consecutive data messages at + // the tail of backlog that have not been compacted. + uncompactedSuffixLen int + // uncompactedBytes tracks the total payload bytes across the trailing + // uncompactedSuffixLen messages. + uncompactedBytes int + err error + bufPool mem.BufferPool } // init allows a recvBuffer to be initialized in-place, which is useful // for resetting a buffer or for avoiding a heap allocation when the buffer // is embedded in another struct. -func (b *recvBuffer) init() { +func (b *recvBuffer) init(pool mem.BufferPool) { b.c = make(chan recvMsg, 1) + b.bufPool = pool } func (b *recvBuffer) put(r recvMsg) { b.mu.Lock() + defer b.mu.Unlock() if b.err != nil { // drop the buffer on the floor. Since b.err is not nil, any subsequent reads // will always return an error, making this buffer inaccessible. r.buffer.Free() - b.mu.Unlock() // An error had occurred earlier, don't accept more // data or errors. return @@ -96,13 +130,70 @@ func (b *recvBuffer) put(r recvMsg) { if len(b.backlog) == 0 { select { case b.c <- r: - b.mu.Unlock() return default: } } b.backlog = append(b.backlog, r) - b.mu.Unlock() + b.compactBacklogLocked(r) +} + +func (b *recvBuffer) compactBacklogLocked(r recvMsg) { + if !envconfig.EnableReceiveBufferCompaction { + return + } + if r.buffer == nil { + b.uncompactedBytes = 0 + b.uncompactedSuffixLen = 0 + return + } + + b.uncompactedSuffixLen++ + b.uncompactedBytes += r.buffer.Len() + backlogHeapSize := b.uncompactedSuffixLen*recvMsgSize + b.uncompactedBytes + + // If the memory overhead is less than 50% of the heap usage (e.g., because + // a large DATA frame arrived), the average message size in the suffix is + // large enough that memory bloat is not a concern. Reset suffix tracking. + if backlogHeapSize <= utilizationFactor*b.uncompactedBytes { + b.uncompactedBytes = 0 + b.uncompactedSuffixLen = 0 + return + } + // Avoid compacting too frequently for short bursts of small frames. + // Wait until we have accumulated at least ~1024 small messages (~57 KB). + if backlogHeapSize <= compactionThreshold { + // Still can accumulate more payloads. + return + } + + // Since the memory utilization is less than 50%, the average payload size + // of each recvMsg must be less than recvMsgSize (approx 56 bytes). + // In the worst case for bytes copied (where the average payload is just + // below recvMsgSize), compaction will occur once every: + // compactionThreshold / (recvMsgSize + avg_payload) = ~520 messages, + // copying ~29KB of data. + + start := 0 + newBuf := b.bufPool.Get(b.uncompactedBytes) + startIdx := len(b.backlog) - b.uncompactedSuffixLen + + for i := startIdx; i < len(b.backlog); i++ { + m := b.backlog[i] + b.backlog[i] = recvMsg{} + start += copy((*newBuf)[start:], m.buffer.ReadOnlyData()) + m.buffer.Free() + } + b.backlog[startIdx] = recvMsg{ + buffer: mem.NewBuffer(newBuf, b.bufPool), + } + b.backlog = b.backlog[:startIdx+1] + // After compaction, the suffix is replaced with a single message containing + // the combined payload. The new utilization is close to 1.0 (overhead of + // one recvMsg relative to the large compacted payload), which is well + // below the utilization factor of 2. + b.uncompactedBytes = 0 + b.uncompactedSuffixLen = 0 } func (b *recvBuffer) load() { @@ -110,6 +201,13 @@ func (b *recvBuffer) load() { if len(b.backlog) > 0 { select { case b.c <- b.backlog[0]: + // backlog[0] is only part of the tracked uncompacted suffix if the + // entire backlog currently consists of the suffix. If an earlier + // compaction or reset occurred, backlog[0] is already compacted. + if envconfig.EnableReceiveBufferCompaction && b.uncompactedSuffixLen == len(b.backlog) { + b.uncompactedSuffixLen-- + b.uncompactedBytes -= b.backlog[0].buffer.Len() + } b.backlog[0] = recvMsg{} b.backlog = b.backlog[1:] default: diff --git a/vendor/google.golang.org/grpc/mem/buffer_pool.go b/vendor/google.golang.org/grpc/mem/buffer_pool.go index 3b02b9091..aa121379f 100644 --- a/vendor/google.golang.org/grpc/mem/buffer_pool.go +++ b/vendor/google.golang.org/grpc/mem/buffer_pool.go @@ -59,10 +59,6 @@ func init() { internal.SetDefaultBufferPool = func(pool BufferPool) { defaultBufferPool = pool } - - internal.SetBufferPoolingThresholdForTesting = func(threshold int) { - bufferPoolingThreshold = threshold - } } // DefaultBufferPool returns the current default buffer pool. It is a BufferPool diff --git a/vendor/google.golang.org/grpc/mem/buffers.go b/vendor/google.golang.org/grpc/mem/buffers.go index 2b410b16e..9b355d446 100644 --- a/vendor/google.golang.org/grpc/mem/buffers.go +++ b/vendor/google.golang.org/grpc/mem/buffers.go @@ -29,6 +29,8 @@ import ( "fmt" "sync" "sync/atomic" + + "google.golang.org/grpc/internal/mem" ) // A Buffer represents a reference counted piece of data (in bytes) that can be @@ -63,8 +65,6 @@ type Buffer interface { } var ( - bufferPoolingThreshold = 1 << 10 - bufferObjectPool = sync.Pool{New: func() any { return new(buffer) }} ) @@ -72,7 +72,7 @@ var ( // equal to the threshold for buffer pooling. This is used to determine whether // to pool buffers or allocate them directly. func IsBelowBufferPoolingThreshold(size int) bool { - return size <= bufferPoolingThreshold + return size <= mem.BufferPoolingThreshold } type buffer struct { diff --git a/vendor/google.golang.org/grpc/version.go b/vendor/google.golang.org/grpc/version.go index 4083c0390..64ec3b3cf 100644 --- a/vendor/google.golang.org/grpc/version.go +++ b/vendor/google.golang.org/grpc/version.go @@ -19,4 +19,4 @@ package grpc // Version is the current grpc version. -const Version = "1.83.0" +const Version = "1.83.1" diff --git a/vendor/modules.txt b/vendor/modules.txt index 7148953d2..ca4a0b49e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,3 +1,15 @@ +# charm.land/bubbles/v2 v2.1.0 +## explicit; go 1.25.0 +charm.land/bubbles/v2/cursor +charm.land/bubbles/v2/internal/runeutil +charm.land/bubbles/v2/key +charm.land/bubbles/v2/textinput +# charm.land/bubbletea/v2 v2.0.5 +## explicit; go 1.25.0 +charm.land/bubbletea/v2 +# charm.land/lipgloss/v2 v2.0.3 +## explicit; go 1.25.0 +charm.land/lipgloss/v2 # dario.cat/mergo v1.0.2 ## explicit; go 1.13 dario.cat/mergo @@ -68,7 +80,7 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/shared github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/pageblob github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service -# github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 +# github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0 ## explicit; go 1.18 github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential @@ -179,7 +191,7 @@ github.com/IBM/go-sdk-core/v5/core ## explicit; go 1.25.0 github.com/IBM/platform-services-go-sdk/common github.com/IBM/platform-services-go-sdk/resourcecontrollerv2 -# github.com/IBM/vpc-go-sdk v0.89.0 +# github.com/IBM/vpc-go-sdk v0.90.0 ## explicit; go 1.25.0 github.com/IBM/vpc-go-sdk/common github.com/IBM/vpc-go-sdk/vpcv1 @@ -237,7 +249,7 @@ github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector/outputs github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter -# github.com/aws/aws-sdk-go-v2 v1.43.6 +# github.com/aws/aws-sdk-go-v2 v1.43.8 ## explicit; go 1.24 github.com/aws/aws-sdk-go-v2/aws github.com/aws/aws-sdk-go-v2/aws/arn @@ -286,10 +298,10 @@ github.com/aws/aws-sdk-go-v2/credentials/stscreds ## explicit; go 1.24 github.com/aws/aws-sdk-go-v2/feature/ec2/imds github.com/aws/aws-sdk-go-v2/feature/ec2/imds/internal/config -# github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.37 +# github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.39 ## explicit; go 1.24 github.com/aws/aws-sdk-go-v2/internal/configsources -# github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.37 +# github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.39 ## explicit; go 1.24 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 # github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.38 @@ -297,7 +309,7 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 github.com/aws/aws-sdk-go-v2/internal/v4a github.com/aws/aws-sdk-go-v2/internal/v4a/internal/crypto github.com/aws/aws-sdk-go-v2/internal/v4a/internal/v4 -# github.com/aws/aws-sdk-go-v2/service/ec2 v1.321.3 +# github.com/aws/aws-sdk-go-v2/service/ec2 v1.323.0 ## explicit; go 1.24 github.com/aws/aws-sdk-go-v2/service/ec2 github.com/aws/aws-sdk-go-v2/service/ec2/internal/endpoints @@ -314,13 +326,13 @@ github.com/aws/aws-sdk-go-v2/service/ecs/types github.com/aws/aws-sdk-go-v2/service/iam github.com/aws/aws-sdk-go-v2/service/iam/internal/endpoints github.com/aws/aws-sdk-go-v2/service/iam/types -# github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.17 +# github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.18 ## explicit; go 1.24 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding # github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.30 ## explicit; go 1.24 github.com/aws/aws-sdk-go-v2/service/internal/checksum -# github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.37 +# github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.39 ## explicit; go 1.24 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url # github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.38 @@ -360,7 +372,7 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc/types github.com/aws/aws-sdk-go-v2/service/sts github.com/aws/aws-sdk-go-v2/service/sts/internal/endpoints github.com/aws/aws-sdk-go-v2/service/sts/types -# github.com/aws/smithy-go v1.27.8 +# github.com/aws/smithy-go v1.28.0 ## explicit; go 1.24 github.com/aws/smithy-go github.com/aws/smithy-go/auth @@ -429,21 +441,28 @@ github.com/charmbracelet/colorprofile # github.com/charmbracelet/lipgloss v1.1.0 ## explicit; go 1.18 github.com/charmbracelet/lipgloss +# github.com/charmbracelet/ultraviolet v0.0.0-20260413211237-bd52878bcec2 +## explicit; go 1.25.0 +github.com/charmbracelet/ultraviolet # github.com/charmbracelet/x/ansi v0.11.8 ## explicit; go 1.24.2 github.com/charmbracelet/x/ansi +github.com/charmbracelet/x/ansi/kitty github.com/charmbracelet/x/ansi/parser # github.com/charmbracelet/x/cellbuf v0.0.15 ## explicit; go 1.24.2 github.com/charmbracelet/x/cellbuf -# github.com/charmbracelet/x/exp/golden v0.0.0-20260816001655-68d539dca504 +# github.com/charmbracelet/x/exp/golden v0.0.0-20260823001701-96af6d2cb5f6 ## explicit; go 1.24.0 # github.com/charmbracelet/x/term v0.2.2 ## explicit; go 1.24.0 github.com/charmbracelet/x/term -# github.com/cheggaaa/pb v1.0.29 -## explicit; go 1.12 -github.com/cheggaaa/pb +# github.com/charmbracelet/x/termios v0.1.1 +## explicit; go 1.18 +github.com/charmbracelet/x/termios +# github.com/charmbracelet/x/windows v0.2.2 +## explicit; go 1.23.0 +github.com/charmbracelet/x/windows # github.com/clipperhouse/displaywidth v0.11.0 ## explicit; go 1.18 github.com/clipperhouse/displaywidth @@ -494,8 +513,8 @@ github.com/emirpasic/gods/utils # github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f ## explicit; go 1.16 github.com/erikgeiser/coninput -# github.com/evertras/bubble-table v0.17.1 -## explicit; go 1.18 +# github.com/evertras/bubble-table v0.22.3 +## explicit; go 1.25.0 github.com/evertras/bubble-table/table # github.com/fsnotify/fsnotify v1.10.1 ## explicit; go 1.23 @@ -648,43 +667,43 @@ github.com/go-openapi/spec github.com/go-openapi/strfmt github.com/go-openapi/strfmt/internal/bsonlite github.com/go-openapi/strfmt/internal/countries -# github.com/go-openapi/swag v0.28.0 +# github.com/go-openapi/swag v0.29.1 ## explicit; go 1.25.0 github.com/go-openapi/swag -# github.com/go-openapi/swag/cmdutils v0.28.0 +# github.com/go-openapi/swag/cmdutils v0.29.1 ## explicit; go 1.25.0 github.com/go-openapi/swag/cmdutils -# github.com/go-openapi/swag/conv v0.28.0 +# github.com/go-openapi/swag/conv v0.29.1 ## explicit; go 1.25.0 github.com/go-openapi/swag/conv -# github.com/go-openapi/swag/fileutils v0.28.0 +# github.com/go-openapi/swag/fileutils v0.29.1 ## explicit; go 1.25.0 github.com/go-openapi/swag/fileutils -# github.com/go-openapi/swag/jsonutils v0.28.0 +# github.com/go-openapi/swag/jsonutils v0.29.1 ## explicit; go 1.25.0 github.com/go-openapi/swag/jsonutils github.com/go-openapi/swag/jsonutils/adapters github.com/go-openapi/swag/jsonutils/adapters/ifaces github.com/go-openapi/swag/jsonutils/adapters/stdlib/json -# github.com/go-openapi/swag/loading v0.28.0 +# github.com/go-openapi/swag/loading v0.29.1 ## explicit; go 1.25.0 github.com/go-openapi/swag/loading -# github.com/go-openapi/swag/mangling v0.28.0 +# github.com/go-openapi/swag/mangling v0.29.1 ## explicit; go 1.25.0 github.com/go-openapi/swag/mangling -# github.com/go-openapi/swag/netutils v0.28.0 +# github.com/go-openapi/swag/netutils v0.29.1 ## explicit; go 1.25.0 github.com/go-openapi/swag/netutils -# github.com/go-openapi/swag/pools v0.28.0 +# github.com/go-openapi/swag/pools v0.29.1 ## explicit; go 1.25.0 github.com/go-openapi/swag/pools -# github.com/go-openapi/swag/stringutils v0.28.0 +# github.com/go-openapi/swag/stringutils v0.29.1 ## explicit; go 1.25.0 github.com/go-openapi/swag/stringutils -# github.com/go-openapi/swag/typeutils v0.28.0 +# github.com/go-openapi/swag/typeutils v0.29.1 ## explicit; go 1.25.0 github.com/go-openapi/swag/typeutils -# github.com/go-openapi/swag/yamlutils v0.28.0 +# github.com/go-openapi/swag/yamlutils v0.29.1 ## explicit; go 1.25.0 github.com/go-openapi/swag/yamlutils # github.com/go-openapi/validate v0.26.3 @@ -830,11 +849,6 @@ github.com/muesli/ansi/compressor # github.com/muesli/cancelreader v0.2.2 ## explicit; go 1.17 github.com/muesli/cancelreader -# github.com/muesli/reflow v0.3.0 -## explicit; go 1.13 -github.com/muesli/reflow/ansi -github.com/muesli/reflow/truncate -github.com/muesli/reflow/wordwrap # github.com/muesli/termenv v0.16.0 ## explicit; go 1.17 github.com/muesli/termenv @@ -897,13 +911,13 @@ github.com/pkg/term/termios github.com/pulumi/appdash github.com/pulumi/appdash/internal/wire github.com/pulumi/appdash/opentracing -# github.com/pulumi/pulumi-aws-native/sdk v1.75.0 +# github.com/pulumi/pulumi-aws-native/sdk v1.76.0 ## explicit; go 1.25.11 github.com/pulumi/pulumi-aws-native/sdk/go/aws github.com/pulumi/pulumi-aws-native/sdk/go/aws/internal github.com/pulumi/pulumi-aws-native/sdk/go/aws/scheduler github.com/pulumi/pulumi-aws-native/sdk/go/aws/ssm -# github.com/pulumi/pulumi-aws/sdk/v7 v7.42.0 +# github.com/pulumi/pulumi-aws/sdk/v7 v7.43.0 ## explicit; go 1.25.11 github.com/pulumi/pulumi-aws/sdk/v7/go/aws github.com/pulumi/pulumi-aws/sdk/v7/go/aws/autoscaling @@ -1043,7 +1057,7 @@ github.com/pulumi/pulumi-random/sdk/v4/go/random/internal ## explicit; go 1.25.11 github.com/pulumi/pulumi-tls/sdk/v5/go/tls github.com/pulumi/pulumi-tls/sdk/v5/go/tls/internal -# github.com/pulumi/pulumi/sdk/v3 v3.258.0 +# github.com/pulumi/pulumi/sdk/v3 v3.259.0 ## explicit; go 1.25.11 github.com/pulumi/pulumi/sdk/v3 github.com/pulumi/pulumi/sdk/v3/go/auto @@ -1246,7 +1260,7 @@ go.opentelemetry.io/collector/pdata/ptrace # go.opentelemetry.io/contrib/bridges/otelslog v0.20.0 ## explicit; go 1.25.0 go.opentelemetry.io/contrib/bridges/otelslog -# go.opentelemetry.io/otel v1.45.0 +# go.opentelemetry.io/otel v1.46.0 ## explicit; go 1.25.0 go.opentelemetry.io/otel go.opentelemetry.io/otel/attribute @@ -1264,7 +1278,7 @@ go.opentelemetry.io/otel/semconv/v1.26.0 go.opentelemetry.io/otel/semconv/v1.37.0 go.opentelemetry.io/otel/semconv/v1.43.0 go.opentelemetry.io/otel/semconv/v1.43.0/otelconv -# go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0 +# go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.22.0 ## explicit; go 1.25.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal @@ -1272,11 +1286,11 @@ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x -# go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 +# go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 ## explicit; go 1.25.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform -# go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 +# go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0 ## explicit; go 1.25.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal @@ -1286,19 +1300,19 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x -# go.opentelemetry.io/otel/log v0.21.0 +# go.opentelemetry.io/otel/log v0.22.0 ## explicit; go 1.25.0 go.opentelemetry.io/otel/log go.opentelemetry.io/otel/log/embedded go.opentelemetry.io/otel/log/global go.opentelemetry.io/otel/log/internal/global go.opentelemetry.io/otel/log/noop -# go.opentelemetry.io/otel/metric v1.45.0 +# go.opentelemetry.io/otel/metric v1.46.0 ## explicit; go 1.25.0 go.opentelemetry.io/otel/metric go.opentelemetry.io/otel/metric/embedded go.opentelemetry.io/otel/metric/noop -# go.opentelemetry.io/otel/sdk v1.45.0 +# go.opentelemetry.io/otel/sdk v1.46.0 ## explicit; go 1.25.0 go.opentelemetry.io/otel/sdk go.opentelemetry.io/otel/sdk/instrumentation @@ -1308,14 +1322,14 @@ go.opentelemetry.io/otel/sdk/resource go.opentelemetry.io/otel/sdk/trace go.opentelemetry.io/otel/sdk/trace/internal/env go.opentelemetry.io/otel/sdk/trace/internal/observ -# go.opentelemetry.io/otel/sdk/log v0.21.0 +# go.opentelemetry.io/otel/sdk/log v0.22.0 ## explicit; go 1.25.0 go.opentelemetry.io/otel/sdk/log go.opentelemetry.io/otel/sdk/log/internal/attrnorm go.opentelemetry.io/otel/sdk/log/internal/counter go.opentelemetry.io/otel/sdk/log/internal/observ go.opentelemetry.io/otel/sdk/log/internal/x -# go.opentelemetry.io/otel/trace v1.45.0 +# go.opentelemetry.io/otel/trace v1.46.0 ## explicit; go 1.25.0 go.opentelemetry.io/otel/trace go.opentelemetry.io/otel/trace/embedded @@ -1362,8 +1376,8 @@ golang.org/x/crypto/ssh golang.org/x/crypto/ssh/agent golang.org/x/crypto/ssh/internal/bcrypt_pbkdf golang.org/x/crypto/ssh/knownhosts -# golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 -## explicit; go 1.25.0 +# golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa +## explicit; go 1.26.0 golang.org/x/exp/maps golang.org/x/exp/slices # golang.org/x/mod v0.40.0 @@ -1444,14 +1458,14 @@ golang.org/x/tools/internal/pkgbits golang.org/x/tools/internal/stdlib golang.org/x/tools/internal/typesinternal golang.org/x/tools/internal/versions -# google.golang.org/genproto/googleapis/api v0.0.0-20260818201246-1b0934165a6f +# google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 ## explicit; go 1.25.0 google.golang.org/genproto/googleapis/api/httpbody -# google.golang.org/genproto/googleapis/rpc v0.0.0-20260818201246-1b0934165a6f +# google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 ## explicit; go 1.25.0 google.golang.org/genproto/googleapis/rpc/errdetails google.golang.org/genproto/googleapis/rpc/status -# google.golang.org/grpc v1.83.0 +# google.golang.org/grpc v1.83.1 ## explicit; go 1.25.0 google.golang.org/grpc google.golang.org/grpc/attributes @@ -1656,7 +1670,7 @@ k8s.io/klog/v2/internal/severity k8s.io/klog/v2/internal/sloghandler k8s.io/klog/v2/internal/verbosity k8s.io/klog/v2/textlogger -# k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad +# k8s.io/kube-openapi v0.0.0-20260821135717-be32def86098 ## explicit; go 1.25.0 k8s.io/kube-openapi/pkg/util # k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 @@ -1684,6 +1698,6 @@ sigs.k8s.io/structured-merge-diff/v6/value ## explicit; go 1.22 sigs.k8s.io/yaml sigs.k8s.io/yaml/goyaml.v2 -# github.com/charmbracelet/bubbles/v2 => charm.land/bubbles/v2 v2.1.1 +# github.com/charmbracelet/bubbles/v2 => charm.land/bubbles/v2 v2.2.1 # github.com/charmbracelet/bubbletea/v2 => charm.land/bubbletea/v2 v2.0.8 # github.com/charmbracelet/lipgloss/v2 => charm.land/lipgloss/v2 v2.0.6