├── editors ├── vscode │ ├── .vscodeignore │ ├── pulumi-logo.png │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── extension.ts └── emacs │ └── pulumi-yaml.el ├── sdk ├── version │ └── version.go ├── yaml │ ├── find_test.go │ ├── util_test.go │ ├── bind │ │ ├── query.go │ │ ├── bind_test.go │ │ ├── diags.go │ │ ├── schema.go │ │ └── bind.go │ ├── util │ │ └── loader │ │ │ └── loader.go │ ├── describe.go │ ├── util.go │ ├── yaml.go │ ├── analysis.go │ └── find.go ├── lsp │ ├── text_test.go │ ├── lsp.go │ ├── client.go │ ├── text.go │ └── methods.go ├── util │ ├── schema_util.go │ └── util.go └── step │ └── step.go ├── .github └── workflows │ ├── pr-tests.yaml │ ├── auto-rebase.yml │ ├── publish-release.yaml │ ├── publish-snapshot.yaml │ ├── pr-comment.yaml │ ├── stage-test.yml │ ├── export-repo-secrets.yml │ ├── command-dispatch.yml │ ├── stage-lint.yml │ ├── pr-tests-command.yaml │ └── stage-publish.yml ├── .gitignore ├── scripts ├── next-release.sh └── release.sh ├── .golangci.yml ├── CHANGELOG_PENDING.md ├── .goreleaser.yml ├── .goreleaser.prerelease.yml ├── CHANGELOG.md ├── README.md ├── cmd └── pulumi-lsp │ └── main.go ├── Makefile ├── go.mod ├── LICENSE └── go.sum /editors/vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | !out/main.js 3 | **/*.ts 4 | -------------------------------------------------------------------------------- /editors/vscode/pulumi-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/pulumi-lsp/HEAD/editors/vscode/pulumi-logo.png -------------------------------------------------------------------------------- /sdk/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package version 4 | 5 | // Version is initialized by the Go linker to contain the semver of this build. 6 | var Version string 7 | -------------------------------------------------------------------------------- /editors/vscode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2020", 5 | "lib": ["es2020"], 6 | "outDir": "bin", 7 | "sourceMap": true, 8 | "rootDir": "src" 9 | }, 10 | "moduleResolution": "node" 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/pr-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Run Acceptance Tests from PR 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - "CHANGELOG.md" 7 | - "CHANGELOG_PENDING.md" 8 | 9 | jobs: 10 | lint: 11 | uses: ./.github/workflows/stage-lint.yml 12 | test: 13 | uses: ./.github/workflows/stage-test.yml 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/bin 3 | **/out 4 | .DS_Store 5 | yaml-mode.el 6 | sdk/yaml/testdata 7 | 8 | # Files copied for building the VS Code extension 9 | editors/vscode/LICENSE 10 | editors/vscode/README.md 11 | editors/vscode/pulumi-lsp 12 | 13 | # for goreleaser 14 | **/*.exe 15 | goreleaser 16 | 17 | # For emacs 18 | *.elc 19 | -------------------------------------------------------------------------------- /sdk/yaml/find_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | package yaml 3 | 4 | import ( 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestIndentation(t *testing.T) { 11 | s, blank := indentationLevel(" foo") 12 | assert.Equal(t, 4, s) 13 | assert.False(t, blank) 14 | } 15 | -------------------------------------------------------------------------------- /scripts/next-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Get the next minor release based on existing tags. 4 | 5 | set -euo pipefail 6 | 7 | git fetch --tags 8 | VERSION=$(git tag --list 'v*.*.*' | tail -1) 9 | MAJOR=$(echo $VERSION | sed 's/\..*//') 10 | MAJOR=${MAJOR#v} 11 | MINOR=$(echo ${VERSION#v$MAJOR.} | sed 's/\..*//') 12 | 13 | echo $MAJOR.$(($MINOR + 1)).0 14 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: standard 4 | settings: 5 | errcheck: 6 | exclude-functions: 7 | - (*github.com/pulumi/pulumi-lsp/sdk/lsp.Client).LogWarningf 8 | - (*github.com/pulumi/pulumi-lsp/sdk/lsp.Client).LogErrorf 9 | - (*github.com/pulumi/pulumi-lsp/sdk/lsp.Client).LogInfof 10 | - (*github.com/pulumi/pulumi-lsp/sdk/lsp.Client).LogDebugf 11 | -------------------------------------------------------------------------------- /CHANGELOG_PENDING.md: -------------------------------------------------------------------------------- 1 | ### Improvements 2 | 3 | - Depend on Pulumi YAML v0.5.10. 4 | [#75](https://github.com/pulumi/pulumi-lsp/pull/75) 5 | 6 | - [completion] Add completion for the `plugins` top level key. 7 | [#118](https://github.com/pulumi/pulumi-lsp/pull/118) 8 | 9 | ### Bug Fixes 10 | 11 | - [ci] Set tag correctly for full release. 12 | [#72](https://github.com/pulumi/pulumi-lsp/pull/72) 13 | 14 | - [completion] Add more top level items. 15 | [#73](https://github.com/pulumi/pulumi-lsp/pull/73) 16 | -------------------------------------------------------------------------------- /sdk/yaml/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package yaml 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/hashicorp/hcl/v2" 9 | "github.com/stretchr/testify/assert" 10 | "go.lsp.dev/protocol" 11 | ) 12 | 13 | func TestPosInRange(t *testing.T) { 14 | loc := &hcl.Range{ 15 | Start: hcl.Pos{Line: 3, Column: 17}, 16 | End: hcl.Pos{Line: 17, Column: 26}, 17 | } 18 | 19 | pos := protocol.Position{Line: 5, Character: 12} 20 | assert.True(t, posInRange(loc, pos)) 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/auto-rebase.yml: -------------------------------------------------------------------------------- 1 | name: Automatic Rebase 2 | on: 3 | repository_dispatch: 4 | types: [ auto-rebase-command ] 5 | jobs: 6 | rebase: 7 | name: Rebase 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout the latest code 11 | uses: actions/checkout@v2 12 | with: 13 | token: ${{ secrets.GITHUB_TOKEN }} 14 | fetch-depth: 0 15 | - name: Automatic Rebase 16 | uses: cirrus-actions/rebase@1.2 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v*.*.* 7 | - "!v*.*.*-**" 8 | 9 | env: 10 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 11 | 12 | jobs: 13 | lint: 14 | uses: ./.github/workflows/stage-lint.yml 15 | test: 16 | uses: ./.github/workflows/stage-test.yml 17 | publish: 18 | needs: [test, lint] 19 | uses: ./.github/workflows/stage-publish.yml 20 | secrets: inherit 21 | with: 22 | goreleaser-args: -p 10 -f .goreleaser.yml --clean --skip=validate --timeout 60m0s --release-notes=CHANGELOG_PENDING.md 23 | vsce-full-release: true 24 | -------------------------------------------------------------------------------- /.github/workflows/publish-snapshot.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Snapshot 2 | 3 | on: 4 | push: 5 | branches: ["main", "feature/**", "feature-**"] 6 | paths-ignore: 7 | - "CHANGELOG.md" 8 | - "CHANGELOG_PENDING.md" 9 | - "README.md" 10 | 11 | env: 12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | 14 | jobs: 15 | lint: 16 | uses: ./.github/workflows/stage-lint.yml 17 | test: 18 | uses: ./.github/workflows/stage-test.yml 19 | publish: 20 | needs: [test, lint] 21 | uses: ./.github/workflows/stage-publish.yml 22 | secrets: inherit 23 | with: 24 | goreleaser-args: -p 10 -f .goreleaser.prerelease.yml --clean --skip=validate --timeout 60m0s --release-notes=CHANGELOG_PENDING.md 25 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | dist: goreleaser 2 | project_name: pulumi-lsp 3 | version: 2 4 | snapshot: 5 | name_template: "{{ .Version }}-SNAPSHOT" 6 | checksum: 7 | name_template: "{{ .ProjectName }}-checksums.txt" 8 | archives: 9 | - id: archive 10 | name_template: "{{ .Binary }}-{{ .Os }}-{{ .Arch }}" 11 | format: gz 12 | files: [none*] 13 | builds: 14 | - id: pulumi-lsp 15 | binary: pulumi-lsp 16 | env: 17 | - CGO_ENABLED=0 18 | goarch: 19 | - amd64 20 | - arm64 21 | goos: 22 | - darwin 23 | - windows 24 | - linux 25 | ldflags: 26 | - -s 27 | - -w 28 | - -X github.com/pulumi/pulumi-lsp/sdk/version.Version={{.Tag}} 29 | main: ./cmd/pulumi-lsp/ 30 | -------------------------------------------------------------------------------- /.github/workflows/pr-comment.yaml: -------------------------------------------------------------------------------- 1 | name: New Pull Request 2 | on: 3 | pull_request_target: 4 | 5 | jobs: 6 | comment-on-pr: 7 | # We only care about commenting on a PR if the PR is from a fork 8 | if: github.event.pull_request.head.repo.full_name != github.repository 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Comment PR 12 | uses: thollander/actions-comment-pull-request@1.0.1 13 | with: 14 | message: | 15 | PR is now waiting for a maintainer to take action. 16 | 17 | **Note for the maintainer:** Commands available: 18 | 19 | * `/run-acceptance-tests` - used to test run the acceptance tests for the project 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /.goreleaser.prerelease.yml: -------------------------------------------------------------------------------- 1 | dist: goreleaser 2 | project_name: pulumi-lsp 3 | version: 2 4 | release: 5 | disable: true 6 | snapshot: 7 | name_template: "{{ .Tag }}-SNAPSHOT" 8 | checksum: 9 | name_template: "{{ .ProjectName }}-checksums.txt" 10 | archives: 11 | - id: archive 12 | name_template: "{{ .Binary }}-{{ .Os }}-{{ .Arch }}" 13 | format: gz 14 | files: [none*] 15 | builds: 16 | - id: pulumi-lsp 17 | binary: pulumi-lsp 18 | env: 19 | - CGO_ENABLED=0 20 | goarch: 21 | - amd64 22 | - arm64 23 | goos: 24 | - darwin 25 | - windows 26 | - linux 27 | ldflags: 28 | - -s 29 | - -w 30 | - -X github.com/pulumi/pulumi-lsp/sdk/version.Version={{.Tag}} 31 | main: ./cmd/pulumi-lsp/ 32 | -------------------------------------------------------------------------------- /.github/workflows/stage-test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | commit-ref: 7 | description: Commit ref to check out and run tests against. 8 | default: "" 9 | required: false 10 | type: string 11 | env: 12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | PULUMI_TEST_PARALLEL: false 14 | 15 | jobs: 16 | test: 17 | name: Test 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout Repo 21 | uses: actions/checkout@v2 22 | with: 23 | ref: ${{ inputs.commit-ref }} 24 | - name: Install Go 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: ${{ matrix.go-version }} 28 | stable: ${{ matrix.go-stable }} 29 | - name: Test 30 | run: make test 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | go-version: [1.24.x] 35 | go-stable: [true] 36 | -------------------------------------------------------------------------------- /sdk/lsp/text_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package lsp 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "go.lsp.dev/protocol" 10 | ) 11 | 12 | const uri = protocol.DocumentURI("") 13 | 14 | const text = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 15 | eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim 16 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 17 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum 18 | dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, 19 | sunt in culpa qui officia deserunt mollit anim id est laborum.` 20 | 21 | func TestDocumentString(t *testing.T) { 22 | doc := NewDocument(protocol.TextDocumentItem{ 23 | URI: uri, 24 | Text: text, 25 | }) 26 | 27 | assert.Equal(t, text, doc.String()) 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/export-repo-secrets.yml: -------------------------------------------------------------------------------- 1 | permissions: write-all # Equivalent to default permissions plus id-token: write 2 | name: Export secrets to ESC 3 | on: [ workflow_dispatch ] 4 | jobs: 5 | export-to-esc: 6 | runs-on: ubuntu-latest 7 | name: export GitHub secrets to ESC 8 | steps: 9 | - name: Generate a GitHub token 10 | id: generate-token 11 | uses: actions/create-github-app-token@v1 12 | with: 13 | app-id: 1256780 # Export Secrets GitHub App 14 | private-key: ${{ secrets.EXPORT_SECRETS_PRIVATE_KEY }} 15 | - name: Export secrets to ESC 16 | uses: pulumi/esc-export-secrets-action@v1 17 | with: 18 | organization: pulumi 19 | org-environment: github-secrets/pulumi-pulumi-lsp 20 | exclude-secrets: EXPORT_SECRETS_PRIVATE_KEY 21 | github-token: ${{ steps.generate-token.outputs.token }} 22 | oidc-auth: true 23 | oidc-requested-token-type: urn:pulumi:token-type:access_token:organization 24 | env: 25 | GITHUB_SECRETS: ${{ toJSON(secrets) }} 26 | -------------------------------------------------------------------------------- /.github/workflows/command-dispatch.yml: -------------------------------------------------------------------------------- 1 | name: Command Dispatch for PR events 2 | on: 3 | issue_comment: 4 | types: [created, edited] 5 | 6 | jobs: 7 | command-dispatch-for-testing: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Run Build 12 | uses: peter-evans/slash-command-dispatch@v2 13 | with: 14 | token: ${{ secrets.PULUMI_BOT_TOKEN }} 15 | reaction-token: ${{ secrets.GITHUB_TOKEN }} 16 | commands: run-acceptance-tests 17 | permission: write 18 | issue-type: pull-request 19 | repository: pulumi/pulumi-lsp 20 | auto-rebase-command: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Run Build 25 | uses: peter-evans/slash-command-dispatch@v2 26 | with: 27 | token: ${{ secrets.PULUMI_BOT_TOKEN }} 28 | reaction-token: ${{ secrets.GITHUB_TOKEN }} 29 | commands: auto-rebase 30 | permission: write 31 | issue-type: pull-request 32 | repository: pulumi/pulumi-lsp 33 | -------------------------------------------------------------------------------- /sdk/util/schema_util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package util 4 | 5 | import ( 6 | "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 7 | ) 8 | 9 | func ResourceProperties(resource *schema.Resource) []*schema.Property { 10 | if resource == nil { 11 | return nil 12 | } 13 | // id and urn props are special and not part of the schema 14 | properties := []*schema.Property{ 15 | { 16 | Name: "id", 17 | Type: schema.StringType, 18 | Plain: false, 19 | Comment: "ID is a unique identifier assigned by a resource provider to a resource", 20 | }, 21 | { 22 | Name: "urn", 23 | Type: schema.StringType, 24 | Plain: false, 25 | Comment: "URN is an automatically generated logical Uniform Resource Name, " + 26 | "used to stably identify resources. See " + 27 | "https://www.pulumi.com/docs/intro/concepts/resources/names/#urns", 28 | }, 29 | } 30 | 31 | if resource.Properties != nil { 32 | properties = append(properties, resource.Properties...) 33 | } 34 | 35 | if resource.InputProperties != nil { 36 | properties = append(properties, resource.InputProperties...) 37 | } 38 | 39 | return properties 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/stage-lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | workflow_call: 5 | 6 | permissions: read-all 7 | 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | 11 | jobs: 12 | lint: 13 | container: golangci/golangci-lint:latest 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Repo 17 | uses: actions/checkout@v4 18 | with: 19 | set-safe-directory: true 20 | - name: Make directory safe 21 | run: git config --global --add safe.directory /__w/pulumi-lsp/pulumi-lsp 22 | - name: Lint 23 | # TODO: Enable linting after a cleanup commit. 24 | # Avoiding in this commit so as to not mix code changes w/ CI changes. 25 | run: make lint-golang 26 | check-copyright: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout Repo 30 | uses: actions/checkout@v4 31 | - name: Install pulumictl 32 | uses: jaxxstorm/action-install-gh-release@v1.5.0 33 | with: 34 | repo: pulumi/pulumictl 35 | - name: Lint 36 | # TODO: Enable copyright checking after adding notices 37 | # Avoiding in this commit so as to not mix code changes w/ CI changes. 38 | run: make lint-copyright 39 | -------------------------------------------------------------------------------- /.github/workflows/pr-tests-command.yaml: -------------------------------------------------------------------------------- 1 | name: Acceptance Tests (Command) 2 | 3 | on: 4 | repository_dispatch: 5 | types: [run-acceptance-tests-command] 6 | 7 | jobs: 8 | comment-notification: 9 | # We only care about adding the result to the PR if it's a repository_dispatch event 10 | if: github.event_name == 'repository_dispatch' 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Create URL to the run output 14 | id: vars 15 | run: echo run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID >> "$GITHUB_OUTPUT" 16 | - name: Update with Result 17 | uses: peter-evans/create-or-update-comment@v1 18 | with: 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | repository: ${{ github.event.client_payload.github.payload.repository.full_name }} 21 | issue-number: ${{ github.event.client_payload.github.payload.issue.number }} 22 | body: | 23 | Please view the results of the PR Build + Acceptance Tests Run [Here][1] 24 | 25 | [1]: ${{ steps.vars.outputs.run-url }} 26 | test: 27 | uses: pulumi/pulumi-lsp/.github/workflows/stage-test.yml@main 28 | with: 29 | commit-ref: refs/pull/${{ github.event.client_payload.pull_request.number }}/merge 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v0.2.1 (2022-09-12) 4 | 5 | ### Improvements 6 | 7 | ### Bug Fixes 8 | 9 | - Fixed problems with the release process. 10 | 11 | ## v0.2.0 (2022-09-12) 12 | 13 | ### Improvements 14 | 15 | - [editors/emacs] Automatic downloads of the LSP Server binary. 16 | [#31](https://github.com/pulumi/pulumi-lsp/pull/31) 17 | 18 | - [editors/vscode] Publish VS Code Extension. 19 | [#32](https://github.com/pulumi/pulumi-lsp/pull/32) 20 | 21 | - [completion] Add support for the `defaultProvider` resource field. 22 | [#47](https://github.com/pulumi/pulumi-lsp/pull/47) 23 | 24 | - [completion] Add support for completing `Fn::*`. 25 | [#48](https://github.com/pulumi/pulumi-lsp/pull/48) 26 | 27 | - [editors/vscode] Warn when Red Hat YAML is also installed. 28 | [#52](https://github.com/pulumi/pulumi-lsp/pull/52) 29 | 30 | - [spec] Account for `Options.Version` when completing fields. 31 | [#51](https://github.com/pulumi/pulumi-lsp/pull/51) 32 | 33 | ### Bug Fixes 34 | 35 | - [completion] Only complete when the cursor is on the text bieng completed. 36 | [#51](https://github.com/pulumi/pulumi-lsp/pull/51) 37 | 38 | ## 0.1.3 (2022-06-10) 39 | 40 | - [mission] Pulumi has always strived to make authoring production ready _Infrastructure 41 | as Code_ as easy as possible. For our other languages, we have tapped into existing 42 | language tooling. We are excited to build best in class tooling for declarative 43 | infrastructure as code on top of Pulumi YAML, the first _declarative_ language supported 44 | by Pulumi. The Pulumi LSP Server will help ensure that, no matter what language you use, 45 | writing Pulumi Programs always comes with best in class tooling. 46 | -------------------------------------------------------------------------------- /sdk/lsp/lsp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | // The lsp package implements a convenience wrapper around the 4 | // go.lsp.dev/protocol package. It handles setting up a server that replies to 5 | // only some lsp requests, as well as providing other helpful LSP intrinsics. 6 | 7 | package lsp 8 | 9 | import ( 10 | "context" 11 | "io" 12 | 13 | "go.lsp.dev/jsonrpc2" 14 | "go.lsp.dev/protocol" 15 | "go.uber.org/zap" 16 | ) 17 | 18 | // A Server combines a set of LSP methods with the infrastructure needed to 19 | // fullfill the server side of the LSP contract. 20 | type Server struct { 21 | methods *Methods 22 | conn io.ReadWriteCloser 23 | cancel <-chan struct{} 24 | isInitialized bool 25 | client protocol.Client 26 | 27 | // The logger used by the server. 28 | Logger *zap.SugaredLogger 29 | } 30 | 31 | // Create a new server backed by `Methods`. The server reads requests and writes 32 | // responses via `conn`. 33 | func NewServer(methods *Methods, conn io.ReadWriteCloser) Server { 34 | return Server{ 35 | methods: methods, 36 | conn: conn, 37 | } 38 | } 39 | 40 | // Synchronously run the server. The server is rooted in the given context, 41 | // which can be used to cancel the server. 42 | func (s *Server) Run(ctx context.Context) error { 43 | if s.Logger == nil { 44 | logger, err := zap.NewDevelopment() 45 | if err != nil { 46 | return err 47 | } 48 | s.Logger = logger.Sugar() 49 | } 50 | ctx, cancel := context.WithCancel(ctx) 51 | defer cancel() 52 | s.run(ctx) 53 | <-s.cancel 54 | return nil 55 | } 56 | 57 | // Actually kick off the server 58 | func (s *Server) run(ctx context.Context) context.Context { 59 | 60 | closer := make(chan struct{}) 61 | 62 | s.cancel = closer 63 | s.methods.server = s 64 | s.methods.closer = closer 65 | 66 | stream := jsonrpc2.NewStream(s.conn) 67 | go func() { 68 | ctx, _, s.client = protocol.NewServer(ctx, s.methods.serve(), stream, s.Logger.Desugar()) 69 | }() 70 | return ctx 71 | } 72 | -------------------------------------------------------------------------------- /sdk/yaml/bind/query.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package bind 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/hashicorp/hcl/v2" 9 | 10 | "github.com/pulumi/pulumi-lsp/sdk/util" 11 | ) 12 | 13 | // Return a list of all resources whose token matches `tk`. 14 | func (d *Decl) GetResources(tk, version string) ([]Resource, error) { 15 | d.lock.RLock() 16 | defer d.lock.RUnlock() 17 | // First we load the token, so we can get the alias list 18 | pkgName, err := pkgNameFromToken(tk) 19 | if err != nil { 20 | return nil, fmt.Errorf("cannot get resources: %w", err) 21 | } 22 | pkg, ok := d.loadedPackages[pkgKey{pkgName, version}] 23 | // We didn't have access to that package 24 | if !ok { 25 | return nil, fmt.Errorf( 26 | "package '%s' is not loaded for query '%s', loaded packages are %s", 27 | pkgName, tk, util.MapKeys(d.loadedPackages)) 28 | } 29 | if pkg.p == nil { 30 | return nil, fmt.Errorf("failed to load pkg '%s'", pkgName) 31 | } 32 | r, ok := pkg.ResolveResource(tk) 33 | names := []string{tk} 34 | if ok { 35 | for _, a := range r.Aliases { 36 | names = append(names, *a.Type) 37 | } 38 | } 39 | var rs []Resource 40 | for _, v := range d.variables { 41 | if r, ok := v.definition.(*Resource); ok && util.SliceContains(names, r.token) { 42 | rs = append(rs, *r) 43 | } 44 | } 45 | return rs, nil 46 | } 47 | 48 | // Get all invokes used in the program. 49 | func (d *Decl) Invokes() []Invoke { 50 | return util.DerefList(util.MapKeys(d.invokes)) 51 | } 52 | 53 | // Retrieve the diagnostic list for the Decl. 54 | func (b *Decl) Diags() hcl.Diagnostics { 55 | if b == nil { 56 | return nil 57 | } 58 | return b.diags 59 | } 60 | 61 | // Return a list of all variable references bound in the Decl. 62 | func (d *Decl) References() []Reference { 63 | refs := []Reference{} 64 | for _, v := range d.variables { 65 | refs = append(refs, v.uses...) 66 | } 67 | return refs 68 | } 69 | 70 | // Return a list of all variables bound in the Decl. 71 | func (d *Decl) Variables() map[string]*Variable { 72 | return d.variables 73 | } 74 | -------------------------------------------------------------------------------- /editors/vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pulumi-lsp-client", 3 | "displayName": "Pulumi YAML", 4 | "description": "IntelliSense and Linting for Pulumi YAML in VSCode", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/pulumi/pulumi-lsp" 8 | }, 9 | "homepage": "https://github.com/pulumi/pulumi-lsp/blob/main/README.md", 10 | "publisher": "pulumi", 11 | "author": "iwahbe", 12 | "license": "MIT", 13 | "version": "0.0.1", 14 | "categories": [ 15 | "Programming Languages", 16 | "Snippets", 17 | "Linters" 18 | ], 19 | "engines": { 20 | "vscode": "^1.63.0" 21 | }, 22 | "dependencies": { 23 | "vscode-languageclient": "^7.0.0" 24 | }, 25 | "activationEvents": [ 26 | "workspaceContains:**/Pulumi.yaml", 27 | "workspaceContains:**/Main.yaml" 28 | ], 29 | "main": "out/main.js", 30 | "devDependencies": { 31 | "@types/node": "^14.18.12", 32 | "@types/vscode": "^1.63.0", 33 | "esbuild": "^0.25.0", 34 | "typescript": "^4.6.2" 35 | }, 36 | "scripts": { 37 | "vscode:prepublish": "npm run esbuild-base -- --minify", 38 | "esbuild-base": "npm exec esbuild -- ./src/extension.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node", 39 | "esbuild": "npm run esbuild-base -- --sourcemap", 40 | "test-compile": "tsc -p ./" 41 | }, 42 | "contributes": { 43 | "configuration": { 44 | "title": "Pulumi LSP", 45 | "properties": { 46 | "pulumi-lsp.server.path": { 47 | "type": [ 48 | "string", 49 | "null" 50 | ], 51 | "default": null, 52 | "markdownDescription": "Specifies the path to the `pulumi-lsp` binary to use. Leave as `null` to use the binary bundled with the downloaded extension." 53 | }, 54 | "pulumi-lsp.detectExtensionConflicts": { 55 | "type": [ 56 | "boolean" 57 | ], 58 | "default": true, 59 | "description": "Warn about conflicting extensions and suggest disabling them." 60 | } 61 | } 62 | } 63 | }, 64 | "icon": "pulumi-logo.png", 65 | "bugs": { 66 | "url": "https://github.com/pulumi/pulumi-lsp/issues" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | usage() 6 | { 7 | echo "Usage: ./scripts/release.sh ACTION VERSION" 8 | echo "" 9 | echo "Automates release-related chores" 10 | echo "" 11 | echo "Example releasing v3.35.1:" 12 | echo "" 13 | echo "./scripts/release.sh start v3.35.1" 14 | echo "./scripts/release.sh tag v3.35.1" 15 | } 16 | 17 | if [ "$#" -ne 2 ]; then 18 | usage 19 | exit 1 20 | fi 21 | 22 | VERSION="$2" 23 | 24 | if [[ "$VERSION" != v* ]]; then 25 | echo "VERSION must start with a v" 26 | exit 1 27 | fi 28 | 29 | merge_changelogs() 30 | { 31 | PREV=$(cat CHANGELOG.md) 32 | echo "# CHANGELOG" > CHANGELOG.md 33 | echo "" >> CHANGELOG.md 34 | echo "## $VERSION ($(date "+%Y-%m-%d"))" >> CHANGELOG.md 35 | echo "" >> CHANGELOG.md 36 | echo -n "$(cat CHANGELOG_PENDING.md)" >> CHANGELOG.md 37 | echo "${PREV#\# CHANGELOG}" >> CHANGELOG.md 38 | } 39 | 40 | restore_changelog_pending() 41 | { 42 | echo "### Improvements" > CHANGELOG_PENDING.md 43 | echo "" >> CHANGELOG_PENDING.md 44 | echo "### Bug Fixes" >> CHANGELOG_PENDING.md 45 | echo "" >> CHANGELOG_PENDING.md 46 | } 47 | 48 | case "$1" in 49 | start) 50 | git fetch origin main 51 | git checkout main -b release/$VERSION 52 | 53 | merge_changelogs 54 | git add CHANGELOG.md 55 | git commit -m "Release ${VERSION}" 56 | 57 | restore_changelog_pending 58 | git add CHANGELOG_PENDING.md 59 | git commit -m "Cleanup for ${VERSION} release" 60 | 61 | git push --set-upstream origin release/${VERSION} 62 | echo "" 63 | echo "Merge the newly created release/${VERSION} branch and run $0 tag ${VERSION}" 64 | echo "Make sure to use 'Rebase and merge' option to preserve the commit sequence." 65 | echo "" 66 | echo "After release/${VERSION} is merged, run" 67 | echo "$ $0 tag $VERSION" 68 | ;; 69 | 70 | tag) 71 | git fetch origin main 72 | 73 | git tag "$VERSION" origin/main~1 74 | git push origin "$VERSION" 75 | ;; 76 | 77 | *) 78 | echo "Invalid command: $1. Expecting one of: start, tag" 79 | usage 80 | exit 1 81 | ;; 82 | esac 83 | -------------------------------------------------------------------------------- /sdk/util/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package util 4 | 5 | type Set[T comparable] map[T]struct{} 6 | 7 | func NewSet[T comparable](elements ...T) Set[T] { 8 | s := make(map[T]struct{}, len(elements)) 9 | for _, e := range elements { 10 | s[e] = struct{}{} 11 | } 12 | return s 13 | } 14 | 15 | func (s Set[T]) Has(element T) bool { 16 | _, ok := s[element] 17 | return ok 18 | } 19 | 20 | // Map f over arr. 21 | func MapOver[T any, U any, F func(T) U](arr []T, f F) []U { 22 | l := make([]U, len(arr)) 23 | for i, t := range arr { 24 | l[i] = f(t) 25 | } 26 | return l 27 | } 28 | 29 | func FilterMap[T any, U any, F func(T) *U](arr []T, f F) []U { 30 | l := make([]U, 0, len(arr)) 31 | for _, t := range arr { 32 | if v := f(t); v != nil { 33 | l = append(l, *v) 34 | } 35 | } 36 | return l 37 | } 38 | 39 | type Tuple[A any, B any] struct { 40 | A A 41 | B B 42 | } 43 | 44 | // Check if slice contains the element. 45 | func SliceContains[T comparable](slice []T, element T) bool { 46 | for _, t := range slice { 47 | if t == element { 48 | return true 49 | } 50 | } 51 | return false 52 | } 53 | 54 | // Retrieve the keys of a map in any order. 55 | func MapKeys[K comparable, V any](m map[K]V) []K { 56 | arr := make([]K, len(m)) 57 | i := 0 58 | for k := range m { 59 | arr[i] = k 60 | i++ 61 | } 62 | return arr 63 | } 64 | 65 | // Retrieve the values of a map in any order. 66 | func MapValues[K comparable, V any](m map[K]V) []V { 67 | arr := make([]V, len(m)) 68 | i := 0 69 | for _, v := range m { 70 | arr[i] = v 71 | i++ 72 | } 73 | return arr 74 | } 75 | 76 | // Dereference each element in a list. 77 | func DerefList[T any](l []*T) []T { 78 | return MapOver(l, func(t *T) T { return *t }) 79 | } 80 | 81 | // ReverseList reverses a list. It runs in O(n) time and O(n) space. 82 | func ReverseList[T any](l []T) []T { 83 | rev := make([]T, 0, len(l)) 84 | for i := len(l) - 1; i >= 0; i-- { 85 | rev = append(rev, l[i]) 86 | } 87 | return rev 88 | } 89 | 90 | func StripNils[T any](l []*T) []*T { 91 | out := make([]*T, 0, len(l)) 92 | for _, v := range l { 93 | if v == nil { 94 | continue 95 | } 96 | out = append(out, v) 97 | } 98 | return out 99 | } 100 | -------------------------------------------------------------------------------- /sdk/yaml/util/loader/loader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package loader 4 | 5 | import ( 6 | "context" 7 | "reflect" 8 | "sync" 9 | 10 | "github.com/blang/semver" 11 | "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 12 | "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" 13 | ) 14 | 15 | type ReferenceLoader interface { 16 | schema.ReferenceLoader 17 | 18 | Loaded() []schema.PackageDescriptor 19 | } 20 | 21 | func New(host plugin.Host) ReferenceLoader { 22 | return &refLoader{inner: schema.NewPluginLoader(host)} 23 | } 24 | 25 | type refLoader struct { 26 | inner schema.ReferenceLoader 27 | 28 | m sync.Mutex 29 | loaded []schema.PackageDescriptor 30 | } 31 | 32 | func (r *refLoader) Loaded() []schema.PackageDescriptor { 33 | r.m.Lock() 34 | defer r.m.Unlock() 35 | 36 | out := make([]schema.PackageDescriptor, len(r.loaded)) 37 | copy(out, r.loaded) 38 | return out 39 | } 40 | 41 | // deprecated: use LoadPackageV2 42 | func (r *refLoader) LoadPackage(pkg string, version *semver.Version) (*schema.Package, error) { 43 | p, err := r.inner.LoadPackage(pkg, version) 44 | if err != nil { 45 | return p, err 46 | } 47 | r.push(&schema.PackageDescriptor{Name: pkg, Version: version}) 48 | return p, err 49 | } 50 | 51 | func (r *refLoader) LoadPackageV2(ctx context.Context, descriptor *schema.PackageDescriptor) (*schema.Package, error) { 52 | p, err := r.inner.LoadPackageV2(ctx, descriptor) 53 | if err != nil { 54 | return p, err 55 | } 56 | r.push(descriptor) 57 | return p, err 58 | } 59 | 60 | // deprecated: use LoadPackageReferenceV2 61 | func (r *refLoader) LoadPackageReference(pkg string, version *semver.Version) (schema.PackageReference, error) { 62 | p, err := r.inner.LoadPackageReference(pkg, version) 63 | if err != nil { 64 | return p, err 65 | } 66 | r.push(&schema.PackageDescriptor{Name: pkg, Version: version}) 67 | return p, err 68 | } 69 | 70 | func (r *refLoader) LoadPackageReferenceV2(ctx context.Context, descriptor *schema.PackageDescriptor) (schema.PackageReference, error) { 71 | p, err := r.inner.LoadPackageReferenceV2(ctx, descriptor) 72 | if err != nil { 73 | return p, err 74 | } 75 | r.push(descriptor) 76 | return p, err 77 | } 78 | 79 | func (r *refLoader) push(d *schema.PackageDescriptor) { 80 | if d == nil { 81 | return 82 | } 83 | r.m.Lock() 84 | defer r.m.Unlock() 85 | 86 | for _, o := range r.loaded { 87 | if reflect.DeepEqual(*d, o) { 88 | return 89 | } 90 | } 91 | 92 | r.loaded = append(r.loaded, *d) 93 | } 94 | -------------------------------------------------------------------------------- /sdk/step/step.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | // Generic Concurrency primitives. 4 | package step 5 | 6 | import ( 7 | "context" 8 | ) 9 | 10 | // Step represents a computation that may produce a value. This is equivalent to 11 | // a `Future` in other languages. 12 | type Step[T any] struct { 13 | // The returned data, once it is returned. 14 | data T 15 | // When this channel is closed, the computation has finished, we we can 16 | // return the data. 17 | done chan struct{} 18 | // If this context is canceled, we return. 19 | ctx context.Context 20 | } 21 | 22 | // A non-blocking attempt to retrieve the value produced by the Step. The bool 23 | // is true if a computed value is returned. False indicates that the attempt 24 | // failed. 25 | func (s *Step[T]) TryGetResult() (T, bool) { 26 | if s == nil { 27 | return Zero[T](), false 28 | } 29 | select { 30 | case <-s.done: 31 | return s.data, true 32 | case <-s.ctx.Done(): 33 | return Zero[T](), false 34 | default: 35 | return Zero[T](), false 36 | } 37 | } 38 | 39 | // Block on retrieving the computed result. If the computation is canceled, 40 | // Zero[T](), false is returned. 41 | func (s *Step[T]) GetResult() (T, bool) { 42 | if s == nil { 43 | return Zero[T](), false 44 | } 45 | select { 46 | case <-s.done: 47 | return s.data, true 48 | case <-s.ctx.Done(): 49 | return Zero[T](), false 50 | } 51 | } 52 | 53 | // Create a new Step not predicated on any other step. `f` is the computation 54 | // that the step represents. The second return value indicates if the 55 | // computation succeeded. 56 | func New[T any, F func() (T, bool)](ctx context.Context, f F) *Step[T] { 57 | ctx, cancel := context.WithCancel(ctx) 58 | s := &Step[T]{ 59 | ctx: ctx, 60 | done: make(chan struct{}), 61 | } 62 | go func() { 63 | data, ok := f() 64 | if !ok { 65 | cancel() 66 | } else { 67 | s.data = data 68 | close(s.done) 69 | } 70 | }() 71 | return s 72 | } 73 | 74 | // Chain a step (if it succeeded) into another step. 75 | func Then[T, U any, F func(T) (U, bool)](s *Step[T], f F) *Step[U] { 76 | return New(s.ctx, func() (U, bool) { 77 | var u U 78 | t, ok := s.GetResult() 79 | if !ok { 80 | return u, false 81 | } 82 | return f(t) 83 | }) 84 | } 85 | 86 | // Run a computation after a Step has succeeded. 87 | func After[T any, F func(T)](s *Step[T], f F) { 88 | Then(s, func(t T) (struct{}, bool) { 89 | f(t) 90 | return struct{}{}, true 91 | }) 92 | 93 | } 94 | 95 | // Zero returns the zero value for a type. 96 | func Zero[T any]() (zero T) { 97 | return 98 | } 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pulumi-lsp 2 | 3 | A [LSP server](https://microsoft.github.io/language-server-protocol/) for 4 | writing [Pulumi YAML](https://github.com/pulumi/pulumi-yaml). 5 | 6 | [![License](https://img.shields.io/github/license/pulumi/pulumi-lsp)](LICENSE) 7 | 8 | --- 9 | 10 | _Note_: The Pulumi YAML LSP Server is in a public beta. If you have suggestions 11 | for features or find bugs, please open an issue. 12 | 13 | ## Existing Capabilities 14 | 15 | ### Warnings and Errors 16 | 17 | The Pulumi LSP Server should give contextual warnings when: 18 | 19 | 1. There is a variable that is never referenced. 20 | 21 | The Pulumi LSP Server should give contextual errors when: 22 | 23 | 1. The file is not a valid YAML document. 24 | 2. A reference refers to a variable that does not exist. 25 | 3. More then one variable/resource share the same name. 26 | 27 | ### On Hover 28 | 29 | When you hover your mouse over a resources type token, you should observe a 30 | popup that describes the resource. Likewise for the type token of a function. 31 | 32 | ### Completion 33 | 34 | You should get semantic completion when: 35 | 36 | 1. Typing in a predefined key for Pulumi YAML such as "resources" or "properties". 37 | 2. Typing in the name of a resource property or function argument.. 38 | 3. Entering type tokens for resources or functions. 39 | 4. Referencing a structured variable. For example if "cluster" is a 40 | `eks:Cluster`, then "${cluster.awsPr}" will suggest `awsProvider`. 41 | 42 | ## Planned Capabilities 43 | 44 | ### Analysis 45 | 46 | - [ ] Duplicate key errors 47 | 48 | ### Hover 49 | 50 | - [ ] Highlight the variable at point across the file 51 | 52 | ### Completion 53 | 54 | - [ ] When entering Pulumi YAML builtin keys. 55 | - [ ] Functions 56 | - [x] Top level 57 | - [x] Resources 58 | - [ ] On the return value for invokes 59 | 60 | ### Actions 61 | 62 | - [ ] Rename variable 63 | - [ ] Fill in input properties 64 | 65 | ## Setting Up Pulumi LSP 66 | 67 | The server is theoretically deployable to any editor that supports LSP. 68 | 69 | ### VS Code 70 | 71 | Because [VS Code](https://code.visualstudio.com) is the most common editor, I used it for 72 | initial testing. Running `make install vscode-client` will install the server on your path 73 | and build a `.vsix` file in `./bin`. Running `code ${./bin/pulumi-lsp-client-*.vsix}` will 74 | install the extension. See [the docs](https://vscode-docs.readthedocs.io/en/stable/extensions/install-extension/) 75 | for details. 76 | 77 | ### Emacs 78 | 79 | `pulumi-yaml.el` provides a major mode for editing Pulumi YAML which should be 80 | auto-invoked on relevant documents. It also associates a LSP server 81 | [emacs-lsp](https://emacs-lsp.github.io/lsp-mode/) which can be launched the usual way: 82 | `M-x lsp`. Run `make install emacs-client` to install the server in `$GOPATH/bin`. A 83 | `pulumi-yaml.elc` fill will be generated in `./bin`. 84 | -------------------------------------------------------------------------------- /sdk/yaml/describe.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package yaml 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io" 9 | 10 | "go.lsp.dev/protocol" 11 | 12 | "github.com/pulumi/pulumi/pkg/v3/codegen" 13 | "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 14 | "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 15 | 16 | "github.com/pulumi/pulumi-lsp/sdk/yaml/bind" 17 | ) 18 | 19 | type object struct { 20 | rnge protocol.Range 21 | } 22 | 23 | func (object) isObject() {} 24 | func (o object) Range() *protocol.Range { 25 | return &o.rnge 26 | } 27 | 28 | // An object in the schema that can be acted upon. 29 | type Object interface { 30 | Describe() (protocol.MarkupContent, bool) 31 | Range() *protocol.Range 32 | isObject() 33 | } 34 | 35 | type Reference struct { 36 | object 37 | ref *bind.Reference 38 | } 39 | 40 | func (r *Reference) Describe() (protocol.MarkupContent, bool) { 41 | return protocol.MarkupContent{}, false 42 | } 43 | 44 | type Resource struct { 45 | object 46 | schema *schema.Resource 47 | } 48 | 49 | func (r Resource) Describe() (protocol.MarkupContent, bool) { 50 | if r.schema == nil { 51 | return protocol.MarkupContent{}, false 52 | } 53 | 54 | b := &bytes.Buffer{} 55 | writeResource(b, r.schema) 56 | return protocol.MarkupContent{ 57 | Kind: protocol.Markdown, 58 | Value: b.String(), 59 | }, true 60 | } 61 | 62 | type Invoke struct { 63 | object 64 | schema *schema.Function 65 | } 66 | 67 | func (f Invoke) Describe() (protocol.MarkupContent, bool) { 68 | if f.schema == nil { 69 | return protocol.MarkupContent{}, false 70 | } 71 | b := &bytes.Buffer{} 72 | writeFunction(b, f.schema) 73 | return protocol.MarkupContent{ 74 | Kind: protocol.Markdown, 75 | Value: b.String(), 76 | }, true 77 | } 78 | 79 | type Writer = func(msg string, args ...interface{}) 80 | 81 | func MakeIOWriter[T any](f func(Writer, T)) func(io.Writer, T) { 82 | return func(w io.Writer, t T) { 83 | f(func(format string, a ...interface{}) { 84 | _, err := fmt.Fprintf(w, format, a...) 85 | contract.IgnoreError(err) 86 | }, t) 87 | } 88 | } 89 | 90 | var writeFunction = MakeIOWriter(func(w Writer, f *schema.Function) { 91 | w("# Function: %s\n", f.Token) 92 | w("\n%s\n", f.Comment) 93 | if f.DeprecationMessage != "" { 94 | w("## Depreciated\n%s\n", f.DeprecationMessage) 95 | } 96 | 97 | if f.Inputs != nil { 98 | w("## Arguments\n") 99 | w("**Type:** `%s`\n", f.Inputs.Token) 100 | for _, input := range f.Inputs.Properties { 101 | writePropertyDescription(w, input) 102 | } 103 | } 104 | if f.Outputs != nil { 105 | w("## Return\n") 106 | w("**Type:** `%s`\n", f.Outputs.Token) 107 | for _, out := range f.Outputs.Properties { 108 | writePropertyDescription(w, out) 109 | } 110 | } 111 | 112 | }) 113 | 114 | var writeResource = MakeIOWriter(func(w Writer, r *schema.Resource) { 115 | w("# Resource: %s\n", r.Token) 116 | w("\n%s\n", r.Comment) 117 | if r.DeprecationMessage != "" { 118 | w("## Depreciated\n%s\n", r.DeprecationMessage) 119 | } 120 | w("## Inputs\n") 121 | for _, input := range r.InputProperties { 122 | writePropertyDescription(w, input) 123 | } 124 | w("## Outputs\n") 125 | for _, output := range r.Properties { 126 | writePropertyDescription(w, output) 127 | } 128 | }) 129 | 130 | func writePropertyDescription(w Writer, prop *schema.Property) { 131 | w("### %s\n", prop.Name) 132 | w("**Type:** `%s`\n\n", codegen.UnwrapType(prop.Type)) 133 | w("%s\n", prop.Comment) 134 | } 135 | -------------------------------------------------------------------------------- /sdk/lsp/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package lsp 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | "go.lsp.dev/protocol" 10 | ) 11 | 12 | // Client represents a LSP client to a Server. It is passed to all methods and 13 | // is used to post non-requested responses to the server. 14 | type Client struct { 15 | inner protocol.Client 16 | ctx context.Context 17 | } 18 | 19 | func (c *Client) Progress(params *protocol.ProgressParams) error { 20 | return c.inner.Progress(c.ctx, params) 21 | } 22 | func (c *Client) WorkDoneProgressCreate(params *protocol.WorkDoneProgressCreateParams) error { 23 | return c.inner.WorkDoneProgressCreate(c.ctx, params) 24 | } 25 | 26 | // Publish diagnostic messages to the user. This is how errors and warnings are 27 | // displayed. Every time diagnostics are published, the complete list of current 28 | // diagnostics must be published. Diagnostics persist until a new set of 29 | // diagnostics are published. 30 | // 31 | // To clear all diagnostics, publish an empty list of diagnostics. 32 | func (c *Client) PublishDiagnostics(params *protocol.PublishDiagnosticsParams) error { 33 | return c.inner.PublishDiagnostics(c.ctx, params) 34 | } 35 | func (c *Client) ShowMessage(params *protocol.ShowMessageParams) error { 36 | return c.inner.ShowMessage(c.ctx, params) 37 | } 38 | func (c *Client) ShowMessageRequest(params *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) { 39 | return c.inner.ShowMessageRequest(c.ctx, params) 40 | } 41 | func (c *Client) Telemetry(params interface{}) error { 42 | return c.inner.Telemetry(c.ctx, params) 43 | } 44 | func (c *Client) RegisterCapability(params *protocol.RegistrationParams) error { 45 | return c.inner.RegisterCapability(c.ctx, params) 46 | } 47 | func (c *Client) UnregisterCapability(params *protocol.UnregistrationParams) error { 48 | return c.inner.UnregisterCapability(c.ctx, params) 49 | } 50 | func (c *Client) ApplyEdit(params *protocol.ApplyWorkspaceEditParams) (bool, error) { 51 | return c.inner.ApplyEdit(c.ctx, params) 52 | } 53 | func (c *Client) Configuration(params *protocol.ConfigurationParams) ([]interface{}, error) { 54 | return c.inner.Configuration(c.ctx, params) 55 | } 56 | func (c *Client) WorkspaceFolders() ([]protocol.WorkspaceFolder, error) { 57 | return c.inner.WorkspaceFolders(c.ctx) 58 | } 59 | 60 | func (c *Client) logMessage(level protocol.MessageType, txt string) error { 61 | err := c.inner.LogMessage(c.ctx, &protocol.LogMessageParams{ 62 | Message: txt, 63 | Type: level, 64 | }) 65 | 66 | if err != nil { 67 | err = c.inner.LogMessage(c.ctx, &protocol.LogMessageParams{ 68 | Message: fmt.Sprintf(`Failed to send message "%s" at level %s: %s`, 69 | txt, level.String(), err.Error()), 70 | Type: protocol.MessageTypeError, 71 | }) 72 | } 73 | return err 74 | } 75 | 76 | func (c *Client) LogErrorf(msg string, args ...interface{}) error { 77 | return c.logMessage(protocol.MessageTypeError, fmt.Sprintf(msg, args...)) 78 | } 79 | 80 | func (c *Client) LogWarningf(msg string, args ...interface{}) error { 81 | return c.logMessage(protocol.MessageTypeWarning, fmt.Sprintf(msg, args...)) 82 | } 83 | 84 | func (c *Client) LogInfof(msg string, args ...interface{}) error { 85 | return c.logMessage(protocol.MessageTypeInfo, fmt.Sprintf(msg, args...)) 86 | } 87 | 88 | func (c *Client) LogDebugf(msg string, args ...interface{}) error { 89 | return c.logMessage(protocol.MessageTypeLog, fmt.Sprintf(msg, args...)) 90 | } 91 | 92 | // Retrieve the Context of method that Client was passed with. 93 | func (c *Client) Context() context.Context { 94 | return c.ctx 95 | } 96 | -------------------------------------------------------------------------------- /cmd/pulumi-lsp/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "io" 9 | "os" 10 | "runtime" 11 | "runtime/debug" 12 | 13 | "github.com/pulumi/pulumi/sdk/v3/go/common/diag" 14 | "github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors" 15 | "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" 16 | "github.com/spf13/cobra" 17 | 18 | "github.com/pulumi/pulumi-lsp/sdk/lsp" 19 | "github.com/pulumi/pulumi-lsp/sdk/version" 20 | "github.com/pulumi/pulumi-lsp/sdk/yaml" 21 | ) 22 | 23 | func main() { 24 | defer panicHandler() 25 | if err := newLSPCommand().Execute(); err != nil { 26 | _, _ = fmt.Fprintf(os.Stderr, "An error occurred: %v\n", err) 27 | // We ignore the error, since there is nothing to do with it 28 | os.Exit(1) 29 | } 30 | } 31 | 32 | func newLSPCommand() *cobra.Command { 33 | cmd := &cobra.Command{ 34 | Use: "pulumi-lsp", 35 | Short: "A LSP for Pulumi YAML", 36 | Args: cobra.NoArgs, 37 | Run: func(*cobra.Command, []string) { 38 | host, err := defaultPluginHost() 39 | if err != nil { 40 | panic(err) 41 | } 42 | defer func() { 43 | if err := host.Close(); err != nil { 44 | panic(err) 45 | } 46 | }() 47 | server := lsp.NewServer(yaml.Methods(host), &stdio{false}) 48 | err = server.Run(context.Background()) 49 | if err != nil { 50 | panic(err) 51 | } 52 | }, 53 | } 54 | 55 | cmd.AddCommand(newVersionCmd()) 56 | return cmd 57 | } 58 | 59 | func newVersionCmd() *cobra.Command { 60 | return &cobra.Command{ 61 | Use: "version", 62 | Short: "Print Pulumi's version number", 63 | Args: cobra.NoArgs, 64 | Run: func(*cobra.Command, []string) { 65 | fmt.Printf("%v\n", version.Version) 66 | }, 67 | } 68 | } 69 | 70 | func panicHandler() { 71 | if panicPayload := recover(); panicPayload != nil { 72 | stack := string(debug.Stack()) 73 | fmt.Fprintln(os.Stderr, "================================================================================") 74 | fmt.Fprintln(os.Stderr, "Pulumi LSP encountered a fatal error. This is a bug!") 75 | fmt.Fprintln(os.Stderr, "We would appreciate a report: https://github.com/pulumi/pulumi-lsp/issues/") 76 | fmt.Fprintln(os.Stderr, "Please provide all of the below text in your report.") 77 | fmt.Fprintln(os.Stderr, "================================================================================") 78 | fmt.Fprintf(os.Stderr, "pulumi-lsp Version: %s\n", version.Version) 79 | fmt.Fprintf(os.Stderr, "Go Version: %s\n", runtime.Version()) 80 | fmt.Fprintf(os.Stderr, "Go Compiler: %s\n", runtime.Compiler) 81 | fmt.Fprintf(os.Stderr, "Architecture: %s\n", runtime.GOARCH) 82 | fmt.Fprintf(os.Stderr, "Operating System: %s\n", runtime.GOOS) 83 | fmt.Fprintf(os.Stderr, "Panic: %s\n\n", panicPayload) 84 | fmt.Fprintln(os.Stderr, stack) 85 | os.Exit(1) 86 | } 87 | } 88 | 89 | func defaultPluginHost() (plugin.Host, error) { 90 | var cfg plugin.ConfigSource 91 | pwd, err := os.Getwd() 92 | if err != nil { 93 | return nil, err 94 | } 95 | sink := diag.DefaultSink(&stdio{false}, &stdio{false}, diag.FormatOptions{ 96 | Color: colors.Never, 97 | }) 98 | context, err := plugin.NewContext(sink, sink, nil, cfg, pwd, nil, false, nil) 99 | if err != nil { 100 | return nil, err 101 | } 102 | return plugin.NewDefaultHost(context, nil, false, nil, nil, nil) 103 | } 104 | 105 | // An io.ReadWriteCloser, whose value indicates if the closer is closed. 106 | type stdio struct{ bool } 107 | 108 | func (s *stdio) Read(p []byte) (n int, err error) { 109 | if s.bool { 110 | return 0, io.EOF 111 | } 112 | return os.Stdin.Read(p) 113 | } 114 | 115 | func (s *stdio) Write(p []byte) (n int, err error) { 116 | if s.bool { 117 | return 0, io.EOF 118 | } 119 | return os.Stdout.Write(p) 120 | } 121 | 122 | func (s *stdio) Close() error { 123 | s.bool = true 124 | return nil 125 | } 126 | -------------------------------------------------------------------------------- /.github/workflows/stage-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | goreleaser-args: 7 | required: true 8 | type: string 9 | vsce-full-release: 10 | required: false 11 | type: boolean 12 | default: false 13 | 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 17 | 18 | jobs: 19 | publish: 20 | name: Publish 21 | runs-on: macos-latest 22 | steps: 23 | - name: Checkout Repo 24 | uses: actions/checkout@v2 25 | - name: Unshallow clone for tags 26 | run: git fetch --prune --unshallow --tags || true 27 | - name: Install Go 28 | uses: actions/setup-go@v2 29 | with: 30 | go-version: 1.24.x 31 | - name: Run GoReleaser 32 | uses: goreleaser/goreleaser-action@v2 33 | with: 34 | args: ${{ inputs.goreleaser-args }} 35 | version: latest 36 | - name: Upload assets as artifacts 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: lsp-binaries 40 | if-no-files-found: error 41 | path: goreleaser/pulumi-lsp_* 42 | vscode: 43 | name: Publish VS Code Extension 44 | needs: [publish] 45 | strategy: 46 | matrix: 47 | include: 48 | - platform: win32 49 | arch: x64 50 | file: windows_amd64_v1 51 | - platform: win32 52 | arch: arm64 53 | file: windows_arm64_v8.0 54 | - platform: linux 55 | arch: x64 56 | file: linux_amd64_v1 57 | - platform: linux 58 | arch: arm64 59 | file: linux_arm64_v8.0 60 | - platform: linux 61 | arch: armhf 62 | file: linux_arm64_v8.0 63 | - platform: alpine 64 | arch: x64 65 | file: linux_amd64_v1 66 | - platform: darwin 67 | arch: x64 68 | file: darwin_amd64_v1 69 | - platform: darwin 70 | arch: arm64 71 | file: darwin_arm64_v8.0 72 | runs-on: ubuntu-latest 73 | steps: 74 | - uses: actions/checkout@v2 75 | - name: Install pulumictl 76 | uses: jaxxstorm/action-install-gh-release@v1.7.1 77 | with: 78 | repo: pulumi/pulumictl 79 | tag: v0.0.31 80 | cache: enable 81 | - uses: actions/setup-node@v2 82 | with: 83 | node-version: 18.x 84 | - uses: actions/download-artifact@v4 85 | id: download 86 | with: 87 | name: lsp-binaries 88 | - name: npm install 89 | run: | 90 | cd editors/vscode && npm install 91 | - name: Prepare to Package 92 | run: | 93 | mkdir bin 94 | cp LICENSE editors/vscode/LICENSE 95 | cp README.md editors/vscode/README.md 96 | git fetch --prune --unshallow --tags 97 | if [[ "${{inputs.vsce-full-release}}" == "false" ]]; then 98 | VERSION=$(./scripts/next-release.sh) 99 | echo -n "Setting unique version for pre-release: '${VERSION}' ->" 100 | VERSION="${VERSION%0}$(date '+%Y%m%d%S')" 101 | echo " '${VERSION}'" 102 | else 103 | VERSION=$(git tag --list 'v*.*.*' | tail -1) 104 | VERSION=${VERSION#v} 105 | echo "Setting release version: '${VERSION}'" 106 | fi 107 | jq ".version=\"${VERSION}\"" editors/vscode/package.json > tmp.json 108 | mv tmp.json editors/vscode/package.json 109 | echo ::group::package.json 110 | cat editors/vscode/package.json 111 | echo ::endgroup:: 112 | chmod u+x ${{steps.download.outputs.download-path}}/pulumi-lsp_${{matrix.file}}/* 113 | mv ${{steps.download.outputs.download-path}}/pulumi-lsp_${{matrix.file}}/* editors/vscode/ 114 | - name: Package (Pre-Release) 115 | if: ${{! inputs.vsce-full-release}} 116 | run: | 117 | cd editors/vscode 118 | npm exec vsce -- package --pre-release --target "${{ matrix.platform }}-${{ matrix.arch }}" --out ../../bin/ 119 | - name: Publish (Pre-Release) 120 | if: ${{! inputs.vsce-full-release}} 121 | run: | 122 | npm exec vsce -- publish --pre-release --packagePath bin/*.vsix 123 | - name: Package (Release) 124 | if: inputs.vsce-full-release 125 | run: | 126 | cd editors/vscode 127 | npm exec vsce -- package --target "${{ matrix.platform }}-${{ matrix.arch }}" --out ../../bin/ 128 | - name: Publish (Release) 129 | if: inputs.vsce-full-release 130 | run: | 131 | npm exec vsce -- publish --packagePath bin/*.vsix 132 | -------------------------------------------------------------------------------- /editors/emacs/pulumi-yaml.el: -------------------------------------------------------------------------------- 1 | ;;; package --- pulumi-yaml -*- lexical-binding: t; -*- 2 | ;; 3 | ;; Copyright 2022, Pulumi Corporation. All rights reserved. 4 | ;; Package-Requires: ((emacs "27.1") (yaml-mode)) 5 | 6 | ;;; Commentary: 7 | 8 | ;;; Code: 9 | 10 | (require 'yaml-mode) 11 | 12 | (defgroup pulumi-yaml () 13 | "Reading and writing Pulumi YAML files." 14 | :group 'languages) 15 | 16 | (defcustom pulumi-yaml-server-command "pulumi-lsp" 17 | "The command used to invoke the Pulumi YAML LSP server." 18 | :type 'string :group 'pulumi-yaml) 19 | 20 | (defcustom pulumi-yaml-server-command-args nil 21 | "The arg list to pass to `pulumi-yaml-server-command'." 22 | :type `(repeat string) :group 'pulumi-yaml) 23 | 24 | (defcustom pulumi-yaml-lsp-ensure (if (featurep 'lsp-mode) 25 | 'lsp-mode 26 | 'eglot) 27 | "If `pulumi-yaml-mode' should eagerly load a LSP host. 28 | 29 | If non-nil, `pulumi-yaml' will `require' the relevant LSP mode so 30 | it can inform it about the new server." 31 | :group 'pulumi-yaml :type '(choice 32 | (const nil) 33 | (const 'lsp-mode) 34 | (const 'eglot))) 35 | 36 | (defcustom pulumi-yaml-server-download-arch 37 | (cond 38 | ((string-search "arm32" system-configuration) "arm32") 39 | ((string-search "arm64" system-configuration) "arm64") 40 | ((string-search "x86_32" system-configuration) "amd32") 41 | (t "amd64")) 42 | "The system architecture to download the Pulumi LSP binary for. 43 | 44 | Note: automatic downloads are only supported when using `lsp-mode'." 45 | :group 'pulumi-yaml :type '(choice 46 | (const "arm32") 47 | (const "arm64") 48 | (const "amd32") 49 | (const "amd64"))) 50 | 51 | (defcustom pulumi-yaml-server-download-url 52 | (format "https://github.com/pulumi/pulumi-lsp/releases/latest/download/pulumi-lsp-%s-%s.gz" 53 | (pcase system-type 54 | ('gnu/linux "linux") 55 | ('darwin "darwin") 56 | ('windows-nt "windows")) 57 | pulumi-yaml-server-download-arch) 58 | "The download path to retrieve the server from. 59 | 60 | Note: automatic downloads are only supported when using `lsp-mode'." 61 | :group 'pulumi-yaml :type 'string) 62 | 63 | ;;;###autoload 64 | (define-derived-mode pulumi-yaml-mode yaml-mode "Pulumi YAML" 65 | "A YAML derivative specifically for writing Pulumi programs in YAML." 66 | :group 'pulumi-yaml 67 | (when pulumi-yaml-lsp-ensure 68 | (require pulumi-yaml-lsp-ensure))) 69 | 70 | (add-to-list 'auto-mode-alist (cons (regexp-quote "Pulumi.yaml") 'pulumi-yaml-mode)) 71 | (add-to-list 'auto-mode-alist (cons (regexp-quote "Pulumi.yml") 'pulumi-yaml-mode)) 72 | (add-to-list 'auto-mode-alist (cons (regexp-quote "Main.yaml") 'pulumi-yaml-mode)) 73 | 74 | (with-eval-after-load 'lsp-mode 75 | (require 'lsp-mode) 76 | 77 | (defcustom pulumi-yaml-store-path 78 | (expand-file-name 79 | "pulumi-lsp" 80 | (expand-file-name "pulumi-yaml" lsp-server-install-dir)) 81 | "The path where the server is installed to." 82 | :group 'pulumi-yaml :type 'string) 83 | 84 | (lsp-dependency 85 | 'pulumi-lsp 86 | '(:download :url pulumi-yaml-server-download-url 87 | :decompress :gzip 88 | :store-path pulumi-yaml-store-path) 89 | '(:system "pulumi-lsp")) 90 | 91 | (lsp-register-client 92 | (make-lsp-client 93 | :new-connection (lsp-stdio-connection 94 | (lambda () 95 | (cons (or (executable-find pulumi-yaml-server-command) 96 | (lsp-package-path 'pulumi-lsp)) 97 | pulumi-yaml-server-command-args))) 98 | :major-modes '(pulumi-yaml-mode) 99 | :server-id 'pulumi-lsp 100 | :add-on? t 101 | :download-server-fn (lambda (_client callback error-callback _update?) 102 | (lsp-package-ensure 103 | 'pulumi-lsp 104 | (lambda (&rest rest) 105 | (lsp-download-path 106 | :binary-path pulumi-yaml-store-path 107 | :set-executable? t) 108 | (apply callback rest)) 109 | error-callback)))) 110 | 111 | (add-to-list 'lsp-language-id-configuration '(pulumi-yaml-mode . "pulumi-lsp"))) 112 | 113 | (with-eval-after-load 'eglot 114 | (add-to-list 'eglot-server-programs 115 | `(pulumi-yaml-mode . ,(cons "pulumi-lsp" pulumi-yaml-server-command-args)))) 116 | 117 | (provide 'pulumi-yaml) 118 | 119 | ;;; pulumi-yaml.el ends here 120 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO := go 2 | EMACS := emacs 3 | NODE := node 4 | SHELL := bash 5 | 6 | default: install 7 | 8 | build: bin/pulumi-lsp client 9 | 10 | COMMIT := $(shell git rev-parse --short HEAD) 11 | VERSION := $(shell git describe --tags --match 'v*.*.*' --dirty=${COMMIT}) 12 | LINK_VERSION := -ldflags "-X github.com/pulumi/pulumi-lsp/sdk/version.Version=${VERSION}" 13 | 14 | _ := $(shell mkdir -p bin) 15 | _ := $(shell go build -o bin/helpmakego github.com/iwahbe/helpmakego) 16 | 17 | server: bin/pulumi-lsp 18 | 19 | bin/pulumi-lsp: $(shell bin/helpmakego cmd/pulumi-lsp) 20 | ${GO} build ${LINK_VERSION} -o $@ github.com/pulumi/pulumi-lsp/cmd/pulumi-lsp 21 | 22 | .PHONY: install 23 | install: bin/pulumi-lsp 24 | install $< $(or $(shell ${GO} env GOBIN),$(shell ${GO} env GOPATH)/bin) 25 | 26 | client: emacs-client vscode-client 27 | 28 | emacs-client: editors/emacs/pulumi-yaml.elc 29 | mkdir -p ./bin 30 | cp editors/emacs/pulumi-yaml.elc bin/ 31 | 32 | vscode-build: 33 | cd editors/vscode && npm install && npm run test-compile && npm run esbuild 34 | 35 | # Because vscode bundles embed the LSP server, we need to build the server first. 36 | vscode-client: vscode-build bin/pulumi-lsp 37 | cp LICENSE editors/vscode/LICENSE 38 | cp bin/pulumi-lsp editors/vscode/ 39 | cd editors/vscode && npm exec vsce -- package --out ../../bin/ 40 | 41 | clean: 42 | @rm -rf ./bin editors/node_modules 43 | @rm -f editors/emacs/{yaml-mode.el,*.elc} 44 | @rm -rf sdk/yaml/testdata 45 | @rm -f editors/vscode/LICENSE 46 | @rm -f editors/vscode/*.vsix 47 | @rm -f editors/vscode/pulumi-lsp 48 | @rm -rf editors/emacs/bin 49 | 50 | test: get_schemas 51 | go test ./... 52 | 53 | .PHONY: lint lint-copyright lint-golang 54 | lint:: lint-copyright lint-golang 55 | lint-golang: 56 | golangci-lint run --timeout 5m --config .golangci.yml 57 | lint-copyright: 58 | pulumictl copyright 59 | 60 | %.elc: %.el 61 | mkdir -p editors/emacs/bin 62 | cd editors/emacs && $(EMACS) -Q --batch --eval "(progn (setq package-user-dir \"$$(pwd)/bin\" \ 63 | package-archives '((\"melpa\" . \"https://melpa.org/packages/\") \ 64 | (\"gnu\" . \"https://elpa.gnu.org/packages/\"))) \ 65 | (package-initialize) \ 66 | (package-install 'yaml-mode) (package-install 'lsp-mode))" -f batch-byte-compile $(notdir $<) 67 | 68 | 69 | # Awsx has a different directory structure, so it needs to be special cased. 70 | schema-awsx!1.0.0-beta.5: url = "https://raw.githubusercontent.com/pulumi/pulumi-awsx/v${version}/awsx/schema.json" 71 | 72 | SCHEMA_PATH := sdk/yaml/testdata 73 | # We replace the '!' with a space, then take the first word 74 | # schema-pkg!x.y.z => schema-pkg 75 | # We then replace 'schema-' with nothing, giving only the package name. 76 | # schema-pkg => pkg 77 | # Recall that `$@` is the target make is trying to build, in our case schema-pkg!x.y.z 78 | name=$(subst schema-,,$(word 1,$(subst !, ,$@))) 79 | # Here we take the second word, just the version 80 | version=$(word 2,$(subst !, ,$@)) 81 | schema-%: url ?= "https://raw.githubusercontent.com/pulumi/pulumi-${name}/v${version}/provider/cmd/pulumi-resource-${name}/schema.json" 82 | schema-%: 83 | @mkdir -p ${SCHEMA_PATH} 84 | @echo "Ensuring schema ${name}, ${version}" 85 | # Download the package from github, then stamp in the correct version. 86 | @[ -f ${SCHEMA_PATH}/${name}-${version}.json ] || \ 87 | curl ${url} \ 88 | | jq '.version = "${version}"' > ${SCHEMA_PATH}/${name}-${version}.json 89 | # Confirm that the correct version is present. If not, error out. 90 | @FOUND="$$(jq -r '.version' ${SCHEMA_PATH}/${name}-${version}.json)" && \ 91 | if ! [ "$$FOUND" = "${version}" ]; then \ 92 | echo "${name} required version ${version} but found existing version $$FOUND"; \ 93 | exit 1; \ 94 | fi 95 | 96 | # This needs to mirror the list found in pulumi/pulumi 97 | get_schemas: \ 98 | schema-aws!4.26.0 \ 99 | schema-aws!4.36.0 \ 100 | schema-aws!4.37.1 \ 101 | schema-aws!5.4.0 \ 102 | schema-aws!5.16.2 \ 103 | schema-azure-native!1.28.0 \ 104 | schema-azure-native!1.29.0 \ 105 | schema-azure-native!1.56.0 \ 106 | schema-azure!4.18.0 \ 107 | schema-kubernetes!3.0.0 \ 108 | schema-kubernetes!3.7.0 \ 109 | schema-kubernetes!3.7.2 \ 110 | schema-random!4.2.0 \ 111 | schema-random!4.3.1 \ 112 | schema-eks!0.37.1 \ 113 | schema-eks!0.40.0 \ 114 | schema-docker!3.1.0 \ 115 | schema-awsx!1.0.0-beta.5 \ 116 | schema-aws-native!0.13.0 \ 117 | schema-google-native!0.18.2 118 | -------------------------------------------------------------------------------- /sdk/yaml/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package yaml 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/blang/semver" 10 | "github.com/hashicorp/hcl/v2" 11 | "go.lsp.dev/protocol" 12 | 13 | yaml "github.com/pulumi/pulumi-yaml/pkg/pulumiyaml" 14 | "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 15 | "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 16 | 17 | "github.com/pulumi/pulumi-lsp/sdk/lsp" 18 | ) 19 | 20 | func convertRange(r *hcl.Range) protocol.Range { 21 | contract.Assertf(r != nil, "Cannot convert an empty range") 22 | return protocol.Range{ 23 | Start: convertPosition(r.Start), 24 | End: convertPosition(r.End), 25 | } 26 | } 27 | 28 | func convertPosition(p hcl.Pos) protocol.Position { 29 | var defPos hcl.Pos 30 | var defProto protocol.Position 31 | if p == defPos { 32 | return defProto 33 | } 34 | contract.Assertf(p.Line != 0, "hcl.Pos line starts at 1") 35 | return protocol.Position{ 36 | Line: uint32(p.Line - 1), 37 | Character: uint32(p.Column - 1), 38 | } 39 | } 40 | 41 | func convertSeverity(s hcl.DiagnosticSeverity) protocol.DiagnosticSeverity { 42 | switch s { 43 | case hcl.DiagError: 44 | return protocol.DiagnosticSeverityError 45 | case hcl.DiagWarning: 46 | return protocol.DiagnosticSeverityWarning 47 | default: 48 | return protocol.DiagnosticSeverityInformation 49 | } 50 | 51 | } 52 | 53 | // Check wheither a lsp position is contained in a yaml range. 54 | func posInRange(r *hcl.Range, pos protocol.Position) bool { 55 | if r == nil { 56 | return false 57 | } 58 | rng := convertRange(r) 59 | s := rng.Start 60 | e := rng.End 61 | return (posLessThen(s, pos) && posGreaterThen(pos, e)) || pos == s || pos == e 62 | } 63 | 64 | // Returns true if p1 < p2 65 | func posGreaterThen(p1, p2 protocol.Position) bool { 66 | return (p1.Line < p2.Line) || 67 | (p1.Line == p2.Line && p1.Character < p2.Character) 68 | } 69 | 70 | // Returns true if p1 > p2 71 | func posLessThen(p1, p2 protocol.Position) bool { 72 | return p1 != p2 && !posGreaterThen(p2, p1) 73 | } 74 | 75 | func combineRange(lower, upper protocol.Range) protocol.Range { 76 | return protocol.Range{ 77 | Start: lower.Start, 78 | End: upper.End, 79 | } 80 | } 81 | 82 | // ResolveResource resolves an arbitrary resource token into an appropriate schema.Resource. 83 | func resolveResource(c lsp.Client, loader schema.ReferenceLoader, token, version string) (*schema.Resource, error) { 84 | tokens := strings.Split(token, ":") 85 | var pkg string 86 | if len(tokens) < 2 { 87 | return nil, fmt.Errorf("invalid token '%s': too few spans", token) 88 | } 89 | pkg = tokens[0] 90 | var isProvider bool 91 | if pkg == "pulumi" { 92 | isProvider = true 93 | if tokens[1] == "providers" && len(tokens) > 2 { 94 | pkg = tokens[2] 95 | } 96 | } 97 | 98 | var v *semver.Version 99 | if version != "" { 100 | version, err := semver.ParseTolerant(version) 101 | if err != nil { 102 | return nil, err 103 | } 104 | v = &version 105 | } 106 | schema, err := loader.LoadPackageReference(pkg, v) 107 | if err != nil { 108 | return nil, fmt.Errorf("could not resolve resource: %w", err) 109 | } 110 | if isProvider { 111 | return schema.Provider() 112 | } 113 | resolvedToken, err := yaml.NewResourcePackage(schema).ResolveResource(token) 114 | if err != nil { 115 | return nil, fmt.Errorf("could not resolve resource: %w", err) 116 | } 117 | resolvedResource, ok, err := schema.Resources().Get(string(resolvedToken)) 118 | if err != nil { 119 | return nil, fmt.Errorf("could not resolve resource: internal error: %w", err) 120 | } 121 | if !ok { 122 | return nil, fmt.Errorf("could not resolve resource: internal error: "+ 123 | "'%s' resolved to '%s' but the resolved token did not exist", 124 | token, resolvedToken) 125 | } 126 | return resolvedResource, nil 127 | } 128 | 129 | // ResolveFunction resolves an arbitrary function token into an appropriate schema.Resource. 130 | func resolveFunction(c lsp.Client, loader schema.ReferenceLoader, token, version string) (*schema.Function, error) { 131 | tokens := strings.Split(token, ":") 132 | if len(tokens) < 2 { 133 | return nil, fmt.Errorf("invalid token '%s': too few spans", token) 134 | } 135 | pkg := tokens[0] 136 | var v *semver.Version 137 | if version != "" { 138 | version, err := semver.ParseTolerant(version) 139 | if err != nil { 140 | return nil, err 141 | } 142 | v = &version 143 | } 144 | schema, err := loader.LoadPackageReference(pkg, v) 145 | if err != nil { 146 | return nil, fmt.Errorf("could not resolve function: %w", err) 147 | } 148 | resolvedToken, err := yaml.NewResourcePackage(schema).ResolveFunction(token) 149 | if err != nil { 150 | return nil, fmt.Errorf("could not resolve function: %w", err) 151 | } 152 | resolvedFunction, ok, err := schema.Functions().Get(string(resolvedToken)) 153 | if err != nil { 154 | return nil, fmt.Errorf("could not resolve function: internal error: %w", err) 155 | } 156 | if !ok { 157 | return nil, fmt.Errorf("could not resolve function: internal error: "+ 158 | "'%s' resolved to '%s' but the resolved token did not exist", 159 | token, resolvedToken) 160 | } 161 | return resolvedFunction, nil 162 | } 163 | -------------------------------------------------------------------------------- /sdk/yaml/bind/bind_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package bind 4 | 5 | import ( 6 | "path/filepath" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/hashicorp/hcl/v2" 11 | yaml "github.com/pulumi/pulumi-yaml/pkg/pulumiyaml" 12 | "github.com/pulumi/pulumi-yaml/pkg/pulumiyaml/ast" 13 | "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 14 | "github.com/pulumi/pulumi/pkg/v3/codegen/testing/utils" 15 | "github.com/stretchr/testify/assert" 16 | "github.com/stretchr/testify/require" 17 | "go.lsp.dev/protocol" 18 | 19 | "github.com/pulumi/pulumi-lsp/sdk/lsp" 20 | ) 21 | 22 | const awsEksExample = ` 23 | name: aws-eks 24 | runtime: yaml 25 | description: An EKS cluster 26 | variables: 27 | vpcId: 28 | fn::invoke: 29 | function: aws:ec2:getVpc 30 | arguments: 31 | default: true 32 | return: id 33 | subnetIds: 34 | fn::invoke: 35 | function: aws:ec2:getSubnetIds 36 | arguments: 37 | vpcId: ${vpcId} 38 | return: ids 39 | resources: 40 | cluster: 41 | type: eks:Cluster 42 | properties: 43 | vpcId: ${vpcId} 44 | subnetIds: ${subnetIds} 45 | instanceType: "t2.medium" 46 | desiredCapacity: 2 47 | minSize: 1 48 | maxSize: 2 49 | outputs: 50 | kubeconfig: ${cluster.kubeconfig} 51 | ` 52 | 53 | func newDocument(name, body string) lsp.Document { 54 | return lsp.NewDocument(protocol.TextDocumentItem{ 55 | URI: protocol.DocumentURI("file://" + name), 56 | LanguageID: protocol.YamlLanguage, 57 | Text: body, 58 | }) 59 | } 60 | 61 | func withLineAs(doc lsp.Document, lineNumber int, line string) { 62 | existing, err := doc.Line(lineNumber) 63 | if err != nil { 64 | panic(err) 65 | } 66 | err = doc.AcceptChanges([]protocol.TextDocumentContentChangeEvent{{ 67 | Range: protocol.Range{ 68 | Start: protocol.Position{ 69 | Line: uint32(lineNumber), 70 | Character: 0, 71 | }, 72 | End: protocol.Position{ 73 | Line: uint32(lineNumber), 74 | Character: uint32(len(existing)), 75 | }, 76 | }, 77 | RangeLength: uint32(len(line)), 78 | Text: line, 79 | }}) 80 | if err != nil { 81 | panic(err) 82 | } 83 | } 84 | 85 | func cleanParse(t *testing.T, doc lsp.Document) *ast.TemplateDecl { 86 | parsed, diags, err := yaml.LoadYAML(doc.URI().Filename(), strings.NewReader(doc.String())) 87 | require.NoError(t, err) 88 | assert.Len(t, diags, 0) 89 | return parsed 90 | } 91 | 92 | // Create a simple range on a single line of ASCI text. 93 | func rangeOnLine(line, startByte, start, end int) *hcl.Range { 94 | return &hcl.Range{ 95 | Start: hcl.Pos{ 96 | Line: line, 97 | Column: start, 98 | Byte: startByte, 99 | }, 100 | End: hcl.Pos{ 101 | Line: line, 102 | Column: end, 103 | Byte: startByte + end - start, 104 | }, 105 | } 106 | } 107 | 108 | func TestBind(t *testing.T) { 109 | doc := newDocument("invalid-property", awsEksExample) 110 | withLineAs(doc, 28, " kubeconfig: ${cluster.kubeconfigg}") 111 | parsed := cleanParse(t, doc) 112 | decl, err := NewDecl(parsed) 113 | require.NoError(t, err) 114 | diags := decl.Diags() 115 | require.Len(t, diags, 0) 116 | decl.LoadSchema(rootPluginLoader) 117 | diags = decl.Diags() 118 | require.Len(t, diags, 1) 119 | assert.Equal(t, &hcl.Diagnostic{ 120 | Severity: hcl.DiagError, 121 | Summary: "Property 'kubeconfigg' does not exist on eks:index:Cluster", 122 | Detail: "Existing properties are: kubeconfig, core, minSize, nodeAmiId, roleMappings, subnetIds, urn, userMappings, version, awsProvider, clusterTags, id, maxSize, name, provider, proxy, tags, vpcId, eksCluster, fargate, gpu, nodePublicKey, nodeSubnetIds, nodeUserData, publicSubnetIds, serviceRole, instanceRole, instanceRoles, instanceType, vpcCniOptions, desiredCapacity, nodeGroupOptions, publicAccessCidrs, storageClasses, defaultNodeGroup, privateSubnetIds, createOidcProvider, nodeRootVolumeSize, nodeSecurityGroup, useDefaultVpcCni, instanceProfileName, nodeRootVolumeIops, nodeRootVolumeType, clusterSecurityGroup, creationRoleProvider, eksClusterIngressRule, encryptionConfigKeyArn, endpointPublicAccess, nodeSecurityGroupTags, skipDefaultNodeGroup, clusterSecurityGroupTags, enabledClusterLogTypes, endpointPrivateAccess, providerCredentialOpts, encryptRootBlockDevice, nodeRootVolumeEncrypted, nodeRootVolumeThroughput, kubernetesServiceIpAddressRange, nodeAssociatePublicIpAddress, nodeRootVolumeDeleteOnTermination", 123 | Subject: rangeOnLine(29, 10, 25, 36), 124 | }, diags[0]) 125 | } 126 | 127 | func TestBindProperty(t *testing.T) { 128 | doc := newDocument("property-binding", ` 129 | variables: 130 | binding: ${pulumi.foo} 131 | `) 132 | parsed := cleanParse(t, doc) 133 | decl, err := NewDecl(parsed) 134 | require.NoError(t, err) 135 | uses := decl.variables["pulumi"].uses 136 | require.Len(t, uses, 1) 137 | use := uses[0] 138 | assert.Equal(t, &hcl.Range{ 139 | Start: hcl.Pos{ 140 | Line: 3, 141 | Column: 12, 142 | }, 143 | End: hcl.Pos{ 144 | Line: 3, 145 | Column: 25, 146 | }, 147 | }, use.Range()) 148 | assert.Equal(t, rangeOnLine(3, 9, 21, 24), use.access[0].rnge) 149 | } 150 | 151 | func newPluginLoader() schema.ReferenceLoader { 152 | schemaLoadPath := filepath.Join("..", "testdata") 153 | return schema.NewPluginLoader(utils.NewHost(schemaLoadPath)) 154 | } 155 | 156 | var rootPluginLoader schema.ReferenceLoader = newPluginLoader() 157 | -------------------------------------------------------------------------------- /sdk/yaml/bind/diags.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package bind 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/hashicorp/hcl/v2" 9 | "github.com/pulumi/pulumi-yaml/pkg/pulumiyaml/ast" 10 | "github.com/pulumi/pulumi-yaml/pkg/pulumiyaml/diags" 11 | "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 12 | ) 13 | 14 | func propertyStartsWithIndexDiag(p *ast.PropertyAccess, loc *hcl.Range) *hcl.Diagnostic { 15 | return &hcl.Diagnostic{ 16 | Severity: hcl.DiagWarning, 17 | Summary: "Property access starts with index", 18 | Detail: fmt.Sprintf("Property accesses should start with a bound name: %s", p.String()), 19 | Subject: loc, 20 | } 21 | } 22 | 23 | func duplicateSourceDiag(name string, subject *hcl.Range, prev *hcl.Range) *hcl.Diagnostic { 24 | return &hcl.Diagnostic{ 25 | Severity: hcl.DiagError, 26 | Summary: "Duplicate Binding", 27 | Detail: fmt.Sprintf("'%s' has already been bound", name), 28 | Subject: subject, 29 | Context: prev, 30 | } 31 | } 32 | 33 | func duplicateKeyDiag(key string, subject *hcl.Range) *hcl.Diagnostic { 34 | return &hcl.Diagnostic{ 35 | Severity: hcl.DiagWarning, 36 | Summary: "Duplicate key", 37 | Detail: fmt.Sprintf("'%s' has already been used as a key in this map", key), 38 | Subject: subject, 39 | } 40 | } 41 | 42 | func variableDoesNotExistDiag(name string, use Reference) *hcl.Diagnostic { 43 | return &hcl.Diagnostic{ 44 | Severity: hcl.DiagError, 45 | Summary: fmt.Sprintf("Missing variable '%s'", name), 46 | Detail: fmt.Sprintf("Reference to non-existant variable '%[1]s'. Consider adding a '%[1]s' to the variables section.", name), 47 | Subject: use.location, 48 | } 49 | } 50 | 51 | func propertyDoesNotExistDiag(prop, parent string, suggestedProps []string, loc *hcl.Range) *hcl.Diagnostic { 52 | f := diags.NonExistentFieldFormatter{ 53 | ParentLabel: parent, 54 | Fields: suggestedProps, 55 | MaxElements: 0, 56 | FieldsAreProperties: true, 57 | } 58 | msg, detail := f.MessageWithDetail(prop, fmt.Sprintf("Property '%s'", prop)) 59 | return &hcl.Diagnostic{ 60 | Severity: hcl.DiagError, 61 | Summary: msg, 62 | Detail: detail, 63 | Subject: loc, 64 | } 65 | } 66 | 67 | func noPropertyAccessDiag(typ string, loc *hcl.Range) *hcl.Diagnostic { 68 | return &hcl.Diagnostic{ 69 | Severity: hcl.DiagError, 70 | Summary: fmt.Sprintf("Property access not supported for %s", typ), 71 | Subject: loc, 72 | } 73 | } 74 | 75 | func noPropertyIndexDiag(typ string, loc *hcl.Range) *hcl.Diagnostic { 76 | return &hcl.Diagnostic{ 77 | Severity: hcl.DiagError, 78 | Summary: fmt.Sprintf("Indexing not supported for %s", typ), 79 | Subject: loc, 80 | } 81 | } 82 | 83 | func unusedVariableDiag(name string, loc *hcl.Range) *hcl.Diagnostic { 84 | return &hcl.Diagnostic{ 85 | Severity: hcl.DiagWarning, 86 | Summary: fmt.Sprintf("Variable '%s' is unused", name), 87 | Subject: loc, 88 | } 89 | } 90 | 91 | func unparsableTokenDiag(tk string, loc *hcl.Range, err error) *hcl.Diagnostic { 92 | return &hcl.Diagnostic{ 93 | Severity: hcl.DiagError, 94 | Summary: fmt.Sprintf("Could not parse '%s' as a schema type: %s", tk, err.Error()), 95 | Detail: "Valid schema tokens are of the form `${pkg}:${module}:${Type}`" + 96 | " or `${pkg}:${Type}`. Providers take the form `pulumi:providers:${pkg}`", 97 | Subject: loc, 98 | } 99 | } 100 | 101 | func failedToLoadPackageDiag(pkg string, loc *hcl.Range, err error) *hcl.Diagnostic { 102 | return &hcl.Diagnostic{ 103 | Severity: hcl.DiagWarning, 104 | Summary: fmt.Sprintf("Failed to load package '%s'", pkg), 105 | Detail: fmt.Sprintf("Error: %s", err.Error()), 106 | Subject: &hcl.Range{}, 107 | Context: &hcl.Range{}, 108 | Expression: nil, 109 | EvalContext: &hcl.EvalContext{}, 110 | } 111 | } 112 | 113 | func missingTokenDiag(pkg, tk string, loc *hcl.Range) *hcl.Diagnostic { 114 | return &hcl.Diagnostic{ 115 | Severity: hcl.DiagError, 116 | Summary: fmt.Sprintf("'%s' doesn't exist in '%s'", tk, pkg), 117 | Detail: "", 118 | Subject: loc, 119 | } 120 | } 121 | 122 | func depreciatedDiag(item, msg string, loc *hcl.Range) *hcl.Diagnostic { 123 | return &hcl.Diagnostic{ 124 | Severity: hcl.DiagWarning, 125 | Summary: fmt.Sprintf("'%s' is depreciated", item), 126 | Detail: msg, 127 | Subject: loc, 128 | } 129 | } 130 | 131 | func emptyPropertyAccessDiag(loc *hcl.Range) *hcl.Diagnostic { 132 | return &hcl.Diagnostic{ 133 | Severity: hcl.DiagError, 134 | Summary: "Empty interpolate expressions are not allowed", 135 | Subject: loc, 136 | } 137 | } 138 | 139 | func missingRequiredPropDiag(prop *schema.Property, loc *hcl.Range) *hcl.Diagnostic { 140 | return &hcl.Diagnostic{ 141 | Summary: fmt.Sprintf("Missing required property '%s'", prop.Name), 142 | Severity: hcl.DiagError, 143 | Subject: loc, 144 | } 145 | } 146 | 147 | func missingResourceBodyDiag(name string, loc *hcl.Range) *hcl.Diagnostic { 148 | return &hcl.Diagnostic{ 149 | Severity: hcl.DiagError, 150 | Summary: fmt.Sprintf("Resource %s is missing body statement", name), 151 | Subject: loc, 152 | } 153 | } 154 | 155 | func missingResourceTypeDiag(name string, loc *hcl.Range) *hcl.Diagnostic { 156 | return &hcl.Diagnostic{ 157 | Severity: hcl.DiagError, 158 | Summary: fmt.Sprintf("Resource %s is missing body a `type` key", name), 159 | Subject: loc, 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /sdk/yaml/yaml.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | // The logic specific to Pulumi YAML. 4 | package yaml 5 | 6 | import ( 7 | "fmt" 8 | 9 | "go.lsp.dev/protocol" 10 | 11 | "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" 12 | 13 | "github.com/pulumi/pulumi-lsp/sdk/lsp" 14 | "github.com/pulumi/pulumi-lsp/sdk/version" 15 | "github.com/pulumi/pulumi-lsp/sdk/yaml/util/loader" 16 | ) 17 | 18 | // The holder server level state. 19 | type server struct { 20 | docs map[protocol.DocumentURI]*document 21 | schemas loader.ReferenceLoader 22 | } 23 | 24 | // Create the set of methods necessary to implement a LSP server for Pulumi YAML. 25 | func Methods(host plugin.Host) *lsp.Methods { 26 | server := &server{ 27 | docs: map[protocol.DocumentURI]*document{}, 28 | schemas: loader.New(host), 29 | } 30 | return lsp.Methods{ 31 | DidOpenFunc: server.didOpen, 32 | DidCloseFunc: server.didClose, 33 | DidChangeFunc: server.didChange, 34 | HoverFunc: server.hover, 35 | CompletionFunc: server.completion, 36 | }.DefaultInitializer("pulumi-lsp", version.Version) 37 | } 38 | 39 | func (s *server) setDocument(text lsp.Document) *document { 40 | doc := &document{text: text, server: s} 41 | s.docs[text.URI()] = doc 42 | return doc 43 | } 44 | 45 | func (s *server) getDocument(uri protocol.DocumentURI) (*document, bool) { 46 | d, ok := s.docs[uri] 47 | return d, ok 48 | } 49 | 50 | // The representation of a document as used by the server. 51 | type document struct { 52 | // The actual text of the document. 53 | text lsp.Document 54 | 55 | // A back-link to the server 56 | server *server 57 | 58 | // A handle to the currently executing analysis pipeline. 59 | analysis *documentAnalysisPipeline 60 | } 61 | 62 | // Starts an analysis process for the document. 63 | func (d *document) process(c lsp.Client) { 64 | if d.analysis != nil { 65 | d.analysis.cancel() 66 | } 67 | d.analysis = NewDocumentAnalysisPipeline(c, d.text, d.server.schemas) 68 | } 69 | 70 | func (s *server) didOpen(client lsp.Client, params *protocol.DidOpenTextDocumentParams) error { 71 | fileName := params.TextDocument.URI.Filename() 72 | text := params.TextDocument.Text 73 | err := client.LogDebugf("Opened file %s:\n---\n%s---", fileName, text) 74 | s.setDocument(lsp.NewDocument(params.TextDocument)).process(client) 75 | return err 76 | } 77 | 78 | func (s *server) didClose(client lsp.Client, params *protocol.DidCloseTextDocumentParams) error { 79 | uri := params.TextDocument.URI 80 | client.LogDebugf("Closing file %s", uri.Filename()) 81 | _, ok := s.docs[uri] 82 | if !ok { 83 | client.LogWarningf("Attempted to close unopened file %s", uri.Filename()) 84 | } 85 | delete(s.docs, uri) 86 | return nil 87 | } 88 | 89 | func (s *server) didChange(client lsp.Client, params *protocol.DidChangeTextDocumentParams) error { 90 | uri := params.TextDocument.URI 91 | doc, ok := s.getDocument(params.TextDocument.URI) 92 | if !ok { 93 | return fmt.Errorf("could not find document %s(%s)", uri.Filename(), uri) 94 | } 95 | if err := doc.text.AcceptChanges(params.ContentChanges); err != nil { 96 | // Something has gone deeply wrong. We rely on having a reliable copy of 97 | // the document. 98 | return fmt.Errorf("document might be unknown: %w", err) 99 | } 100 | doc.process(client) 101 | return nil 102 | } 103 | 104 | func (s *server) hover(client lsp.Client, params *protocol.HoverParams) (*protocol.Hover, error) { 105 | uri := params.TextDocument.URI 106 | doc, ok := s.getDocument(uri) 107 | pos := params.Position 108 | if !ok { 109 | return nil, fmt.Errorf("could not find an opened document %s", uri.Filename()) 110 | } 111 | if doc.analysis == nil { 112 | // Do nothing. We can try again later. 113 | return nil, nil 114 | } 115 | typ, err := doc.objectAtPoint(pos) 116 | if err != nil { 117 | client.LogErrorf("%s", err.Error()) 118 | return nil, nil 119 | } 120 | client.LogInfof("Object found for hover: %v", typ) 121 | if typ != nil { 122 | if description, ok := typ.Describe(); ok { 123 | return &protocol.Hover{ 124 | Contents: description, 125 | Range: typ.Range(), 126 | }, nil 127 | } 128 | } 129 | return nil, nil 130 | } 131 | 132 | func (s *server) completion(client lsp.Client, params *protocol.CompletionParams) (*protocol.CompletionList, error) { 133 | client.LogWarningf("Completion called") 134 | uri := params.TextDocument.URI 135 | doc, ok := s.getDocument(uri) 136 | if !ok { 137 | return nil, fmt.Errorf("could not find an opened document %s", uri.Filename()) 138 | } 139 | 140 | // Complete for `type: ...` or `Function: ...`. 141 | typeFuncCompletion, err := s.completeType(client, doc, params) 142 | if err != nil || typeFuncCompletion != nil { 143 | return typeFuncCompletion, err 144 | } 145 | 146 | // Complete for new keys in the YAML 147 | keyCompletion, err := s.completeKey(client, doc, params) 148 | if err != nil || keyCompletion != nil { 149 | return keyCompletion, err 150 | } 151 | 152 | o, err := doc.objectAtPoint(params.Position) 153 | if err != nil { 154 | client.LogErrorf("%s", err.Error()) 155 | return nil, nil 156 | } 157 | client.LogInfof("Object found for completion: %v", o) 158 | // We handle completion when the schema is fully parsed 159 | if o, ok := o.(*Reference); ok { 160 | return s.completeReference(client, doc, o) 161 | } 162 | client.LogWarningf("No handler responded to completion call") 163 | return nil, nil 164 | } 165 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pulumi/pulumi-lsp 2 | 3 | go 1.24 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/blang/semver v3.5.1+incompatible 9 | github.com/hashicorp/hcl/v2 v2.17.0 10 | github.com/pulumi/pulumi-yaml v1.10.1 11 | github.com/pulumi/pulumi/pkg/v3 v3.132.0 12 | github.com/pulumi/pulumi/sdk/v3 v3.132.0 13 | github.com/spf13/cobra v1.8.1 14 | github.com/stretchr/testify v1.10.0 15 | go.lsp.dev/jsonrpc2 v0.10.0 16 | go.lsp.dev/protocol v0.12.0 17 | go.uber.org/zap v1.21.0 18 | ) 19 | 20 | require ( 21 | dario.cat/mergo v1.0.0 // indirect 22 | github.com/BurntSushi/toml v1.2.1 // indirect 23 | github.com/Microsoft/go-winio v0.6.1 // indirect 24 | github.com/ProtonMail/go-crypto v1.1.3 // indirect 25 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect 26 | github.com/agext/levenshtein v1.2.3 // indirect 27 | github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect 28 | github.com/atotto/clipboard v0.1.4 // indirect 29 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 30 | github.com/charmbracelet/bubbles v0.16.1 // indirect 31 | github.com/charmbracelet/bubbletea v0.25.0 // indirect 32 | github.com/charmbracelet/lipgloss v0.7.1 // indirect 33 | github.com/cheggaaa/pb v1.0.29 // indirect 34 | github.com/cloudflare/circl v1.3.7 // indirect 35 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect 36 | github.com/cyphar/filepath-securejoin v0.3.6 // indirect 37 | github.com/davecgh/go-spew v1.1.1 // indirect 38 | github.com/djherbis/times v1.5.0 // indirect 39 | github.com/edsrzf/mmap-go v1.1.0 // indirect 40 | github.com/emirpasic/gods v1.18.1 // indirect 41 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 42 | github.com/go-git/go-billy/v5 v5.6.1 // indirect 43 | github.com/go-git/go-git/v5 v5.13.1 // indirect 44 | github.com/gofrs/uuid v4.2.0+incompatible // indirect 45 | github.com/gogo/protobuf v1.3.2 // indirect 46 | github.com/golang/glog v1.2.0 // indirect 47 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 48 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 49 | github.com/google/uuid v1.6.0 // indirect 50 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect 51 | github.com/hashicorp/errwrap v1.1.0 // indirect 52 | github.com/hashicorp/go-multierror v1.1.1 // indirect 53 | github.com/iancoleman/strcase v0.2.0 // indirect 54 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 55 | github.com/iwahbe/helpmakego v0.2.0 // indirect 56 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 57 | github.com/kevinburke/ssh_config v1.2.0 // indirect 58 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 59 | github.com/mattn/go-isatty v0.0.20 // indirect 60 | github.com/mattn/go-localereader v0.0.1 // indirect 61 | github.com/mattn/go-runewidth v0.0.15 // indirect 62 | github.com/mitchellh/go-ps v1.0.0 // indirect 63 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 64 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 65 | github.com/muesli/cancelreader v0.2.2 // indirect 66 | github.com/muesli/reflow v0.3.0 // indirect 67 | github.com/muesli/termenv v0.15.2 // indirect 68 | github.com/natefinch/atomic v1.0.1 // indirect 69 | github.com/opentracing/basictracer-go v1.1.0 // indirect 70 | github.com/opentracing/opentracing-go v1.2.0 // indirect 71 | github.com/pgavlin/fx v0.1.6 // indirect 72 | github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386 // indirect 73 | github.com/pjbgf/sha1cd v0.3.0 // indirect 74 | github.com/pkg/errors v0.9.1 // indirect 75 | github.com/pkg/term v1.1.0 // indirect 76 | github.com/pmezard/go-difflib v1.0.0 // indirect 77 | github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 // indirect 78 | github.com/pulumi/esc v0.9.2-0.20240910221656-328d3204100f // indirect 79 | github.com/rivo/uniseg v0.4.4 // indirect 80 | github.com/rogpeppe/go-internal v1.12.0 // indirect 81 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect 82 | github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect 83 | github.com/segmentio/asm v1.1.3 // indirect 84 | github.com/segmentio/encoding v0.3.5 // indirect 85 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect 86 | github.com/skeema/knownhosts v1.3.0 // indirect 87 | github.com/spf13/cast v1.4.1 // indirect 88 | github.com/spf13/pflag v1.0.5 // indirect 89 | github.com/texttheater/golang-levenshtein v1.0.1 // indirect 90 | github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect 91 | github.com/uber/jaeger-lib v2.4.1+incompatible // indirect 92 | github.com/xanzy/ssh-agent v0.3.3 // indirect 93 | github.com/zclconf/go-cty v1.13.2 // indirect 94 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect 95 | go.lsp.dev/uri v0.3.0 // indirect 96 | go.uber.org/atomic v1.9.0 // indirect 97 | go.uber.org/multierr v1.8.0 // indirect 98 | golang.org/x/crypto v0.31.0 // indirect 99 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 100 | golang.org/x/mod v0.22.0 // indirect 101 | golang.org/x/net v0.33.0 // indirect 102 | golang.org/x/sync v0.10.0 // indirect 103 | golang.org/x/sys v0.28.0 // indirect 104 | golang.org/x/term v0.27.0 // indirect 105 | golang.org/x/text v0.21.0 // indirect 106 | golang.org/x/tools v0.23.0 // indirect 107 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240311173647-c811ad7063a7 // indirect 108 | google.golang.org/grpc v1.63.2 // indirect 109 | google.golang.org/protobuf v1.33.0 // indirect 110 | gopkg.in/warnings.v0 v0.1.2 // indirect 111 | gopkg.in/yaml.v3 v3.0.1 // indirect 112 | lukechampine.com/frand v1.4.2 // indirect 113 | ) 114 | 115 | tool github.com/iwahbe/helpmakego 116 | -------------------------------------------------------------------------------- /editors/vscode/src/extension.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | "use strict"; 4 | 5 | import * as process from "process"; 6 | import * as fs from "fs"; 7 | 8 | import * as vscode from "vscode"; 9 | import * as lc from "vscode-languageclient/node"; 10 | 11 | let client: PulumiLSPClient; 12 | 13 | class PulumiLSPClient extends lc.LanguageClient { 14 | constructor(serverOptions: lc.ServerOptions) { 15 | // Options to control the language client 16 | const clientOptions: lc.LanguageClientOptions = { 17 | documentSelector: [ 18 | { pattern: "**/Pulumi.yaml" }, 19 | { pattern: "**/Main.yaml" }, 20 | ], 21 | }; 22 | 23 | super("pulumi-lsp", "Pulumi LSP", serverOptions, clientOptions); 24 | } 25 | } 26 | 27 | class Config { 28 | readonly rootPath: string = "pulumi-lsp"; 29 | constructor() { 30 | vscode.workspace.onDidChangeConfiguration(this.onDidChangeConfiguration); 31 | } 32 | 33 | serverPath() { 34 | return vscode.workspace.getConfiguration(this.rootPath).get( 35 | "server.path", 36 | ); 37 | } 38 | 39 | onDidChangeConfiguration( 40 | event: vscode.ConfigurationChangeEvent, 41 | ) { 42 | if (event.affectsConfiguration(this.rootPath)) { 43 | outputChannel().replace( 44 | "Restart the Pulumi LSP extension for configuration changes to take effect.", 45 | ); 46 | } 47 | } 48 | } 49 | 50 | let OUTPUT_CHANNEL: vscode.OutputChannel | null = null; 51 | export function outputChannel() { 52 | if (!OUTPUT_CHANNEL) { 53 | OUTPUT_CHANNEL = vscode.window.createOutputChannel( 54 | "Pulumi LSP Server", 55 | ); 56 | } 57 | return OUTPUT_CHANNEL; 58 | } 59 | 60 | async function getServer( 61 | context: vscode.ExtensionContext, 62 | config: Config, 63 | ): Promise { 64 | const explicitPath = config.serverPath(); 65 | if (explicitPath) { 66 | if (fs.existsSync(explicitPath)) { 67 | outputChannel().replace( 68 | `Launching server from explicitly provided path: ${explicitPath}`, 69 | ); 70 | return Promise.resolve(explicitPath); 71 | } 72 | const msg = 73 | `${config.rootPath}.server.path specified a path, but the file ${explicitPath} does not exist.`; 74 | outputChannel().replace(msg); 75 | outputChannel().show(); 76 | return Promise.reject(msg); 77 | } 78 | 79 | const ext = process.platform === "win32" ? ".exe" : ""; 80 | const bundled = vscode.Uri.joinPath( 81 | context.extensionUri, 82 | `pulumi-lsp${ext}`, 83 | ); 84 | const bundledExists = await vscode.workspace.fs.stat(bundled).then( 85 | () => true, 86 | () => false, 87 | ); 88 | 89 | if (bundledExists) { 90 | const path = bundled.fsPath; 91 | outputChannel().replace(`Launching built-in Pulumi LSP Server`); 92 | return Promise.resolve(path); 93 | } 94 | 95 | outputChannel().replace(`Could not find a bundled Pulumi LSP Server. 96 | Please specify a pulumi-lsp binary via settings.json at the "${config.rootPath}.server.path" key. 97 | If you think this is an error, please report it at https://github.com/pulumi/pulumi-lsp/issues.`); 98 | outputChannel().show(); 99 | 100 | return Promise.reject("No binary found"); 101 | } 102 | 103 | export async function activate( 104 | context: vscode.ExtensionContext, 105 | ): Promise { 106 | const config = new Config(); 107 | const serverPath = await getServer(context, config); 108 | if (serverPath === undefined) { 109 | outputChannel().append("\nFailed to find LSP executable"); 110 | return Promise.reject(); 111 | } 112 | const serverOptions: lc.ServerOptions = { 113 | command: serverPath, 114 | }; 115 | 116 | // Create the language client and start the client. 117 | client = new PulumiLSPClient(serverOptions); 118 | client.start(); 119 | 120 | // Ensure that we are not running at the same time as 'Red Hat YAML' without warning the 121 | // user. 122 | const shouldCheck = vscode.workspace.getConfiguration("pulumi-lsp").get( 123 | "detectExtensionConflicts", 124 | ); 125 | if (shouldCheck) { 126 | let isDisplayed = false; 127 | const interval = setInterval(function () { 128 | const rhYaml = vscode.extensions.getExtension("redhat.vscode-yaml"); 129 | if (rhYaml && rhYaml.isActive && !isDisplayed) { 130 | isDisplayed = true; 131 | vscode.window 132 | .showWarningMessage( 133 | "You have both the Red Hat YAML extension and " + 134 | "Pulumi YAML extension enabled. Red Hat YAML " + 135 | "conflict with Pulumi YAML code completion.", 136 | "Disable Red Hat YAML", 137 | "Never show this warning", 138 | ) 139 | .then((selection) => { 140 | if (selection == "Disable Red Hat YAML") { 141 | const promise = vscode.commands.executeCommand( 142 | "workbench.extensions.uninstallExtension", 143 | "redhat.vscode-yaml", 144 | ); 145 | vscode.window.showInformationMessage( 146 | "Red Hat YAML has been uninstalled in this workspace. " + 147 | "You will need to reload VSCode for this to take effect.", 148 | "Restart Now", 149 | "Restart Later", 150 | ).then((selection) => { 151 | isDisplayed = false; 152 | if (selection == "Restart Now") { 153 | promise.then(() => 154 | vscode.commands.executeCommand( 155 | "workbench.action.reloadWindow", 156 | ) 157 | ); 158 | } 159 | }); 160 | } else if (selection == "Never show this warning") { 161 | vscode.workspace.getConfiguration("pulumi-lsp").update( 162 | "detectExtensionConflicts", 163 | false, 164 | vscode.ConfigurationTarget.Global, 165 | ); 166 | clearInterval(interval); 167 | isDisplayed = false; 168 | } else { 169 | isDisplayed = false; 170 | } 171 | }); 172 | } 173 | }, 5000); 174 | } 175 | 176 | return client; 177 | } 178 | 179 | export function deactivate(): Thenable | undefined { 180 | if (!client) { 181 | return undefined; 182 | } 183 | 184 | return client.stop(); 185 | } 186 | -------------------------------------------------------------------------------- /sdk/yaml/analysis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package yaml 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/hashicorp/hcl/v2" 12 | "go.lsp.dev/protocol" 13 | 14 | yaml "github.com/pulumi/pulumi-yaml/pkg/pulumiyaml" 15 | "github.com/pulumi/pulumi-yaml/pkg/pulumiyaml/ast" 16 | "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 17 | "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 18 | 19 | "github.com/pulumi/pulumi-lsp/sdk/lsp" 20 | "github.com/pulumi/pulumi-lsp/sdk/step" 21 | "github.com/pulumi/pulumi-lsp/sdk/util" 22 | "github.com/pulumi/pulumi-lsp/sdk/yaml/bind" 23 | ) 24 | 25 | // documentAnalysisPipeline represents a multi-stage analysis pipeline on a document. 26 | type documentAnalysisPipeline struct { 27 | ctx context.Context 28 | cancel context.CancelFunc 29 | 30 | // First stage, program is parsed 31 | parsed *step.Step[util.Tuple[*ast.TemplateDecl, hcl.Diagnostics]] 32 | 33 | // Then the program is analyzed 34 | bound *step.Step[util.Tuple[*bind.Decl, *hcl.Diagnostic]] 35 | } 36 | 37 | func inferParseErrorLine(err string) (int, bool) { 38 | e := strings.TrimPrefix(err, "yaml: ") 39 | if l := strings.Split(e, ":"); len(l) > 1 { 40 | e = l[0] 41 | } 42 | e = strings.TrimPrefix(e, "line ") 43 | if line, err := strconv.ParseInt(e, 10, 64); err == nil { 44 | return int(line), true 45 | } 46 | return 0, false 47 | } 48 | 49 | // Parse the document 50 | func (d *documentAnalysisPipeline) parse(text lsp.Document) { 51 | // This is the first step of analysis, so we don't check for previous errors 52 | d.parsed = step.New(d.ctx, func() (util.Tuple[*ast.TemplateDecl, hcl.Diagnostics], bool) { 53 | parsed, parseSyntaxDiags, err := yaml.LoadYAML(text.URI().Filename(), strings.NewReader(text.String())) 54 | parseDiags := parseSyntaxDiags.HCL() 55 | if err != nil { 56 | parseDiags = append(parseDiags, d.promoteError("Parse error", err)) 57 | } else if d.parsed == nil { 58 | parseDiags = append(parseDiags, d.promoteError("Parse error", fmt.Errorf("no template returned"))) 59 | } 60 | 61 | for _, d := range parseDiags { 62 | if line, ok := inferParseErrorLine(d.Summary); ok { 63 | d.Subject = &hcl.Range{ 64 | Filename: text.URI().Filename(), 65 | Start: hcl.Pos{ 66 | Line: line, 67 | Column: 1, 68 | }, 69 | End: hcl.Pos{ 70 | Line: line, 71 | Column: 0, // This indicates the end of the line 72 | }, 73 | } 74 | } 75 | } 76 | return util.Tuple[*ast.TemplateDecl, hcl.Diagnostics]{A: parsed, B: parseDiags}, true 77 | }) 78 | } 79 | 80 | // Bind the document, performing basic lexical analysis. 81 | func (d *documentAnalysisPipeline) bind(t util.Tuple[*ast.TemplateDecl, hcl.Diagnostics]) (util.Tuple[*bind.Decl, *hcl.Diagnostic], bool) { 82 | if t.A == nil { 83 | return util.Tuple[*bind.Decl, *hcl.Diagnostic]{}, false 84 | } 85 | bound, err := bind.NewDecl(t.A) 86 | var hclErr *hcl.Diagnostic 87 | if err != nil { 88 | hclErr = d.promoteError("Binding error", err) 89 | } 90 | return util.Tuple[*bind.Decl, *hcl.Diagnostic]{A: bound, B: hclErr}, true 91 | } 92 | 93 | // Creates a new asynchronous analysis pipeline, returning a handle to the 94 | // process. To avoid a memory leak, ${RESULT}.cancel must be called. 95 | func NewDocumentAnalysisPipeline(c lsp.Client, text lsp.Document, loader schema.ReferenceLoader) *documentAnalysisPipeline { 96 | ctx, cancel := context.WithCancel(c.Context()) 97 | d := &documentAnalysisPipeline{ 98 | ctx: ctx, 99 | cancel: cancel, 100 | parsed: nil, 101 | bound: nil, 102 | } 103 | go func(c lsp.Client, text lsp.Document, loader schema.ReferenceLoader) { 104 | // We need to ensure everything finished when we exit 105 | c.LogDebugf("Kicking off analysis for %s", text.URI().Filename()) 106 | 107 | d.parse(text) 108 | step.After(d.parsed, func(util.Tuple[*ast.TemplateDecl, hcl.Diagnostics]) { 109 | err := d.sendDiags(c, text.URI()) 110 | contract.IgnoreError(err) 111 | }) 112 | 113 | d.bound = step.Then(d.parsed, d.bind) 114 | step.After(d.bound, func(util.Tuple[*bind.Decl, *hcl.Diagnostic]) { 115 | err := d.sendDiags(c, text.URI()) 116 | contract.IgnoreError(err) 117 | }) 118 | 119 | schematize := step.Then(d.bound, func(t util.Tuple[*bind.Decl, *hcl.Diagnostic]) (struct{}, bool) { 120 | if t.A != nil { 121 | t.A.LoadSchema(loader) 122 | return struct{}{}, true 123 | } 124 | return struct{}{}, false 125 | }) 126 | step.After(schematize, func(struct{}) { 127 | err := d.sendDiags(c, text.URI()) 128 | contract.IgnoreError(err) 129 | }) 130 | }(c, text, loader) 131 | return d 132 | } 133 | 134 | // Retrieve all diagnostics generated by the analysis run. 135 | func (d *documentAnalysisPipeline) diags() hcl.Diagnostics { 136 | var arr hcl.Diagnostics 137 | parsed, ok := d.parsed.TryGetResult() 138 | if ok && parsed.B != nil { 139 | arr = append(arr, parsed.B...) 140 | } 141 | bound, ok := d.bound.TryGetResult() 142 | if ok { 143 | if bound.B != nil { 144 | arr = append(arr, bound.B) 145 | } 146 | if bound.A != nil { 147 | arr = append(arr, bound.A.Diags()...) 148 | } 149 | } 150 | return arr 151 | } 152 | 153 | // Actually send the report request to the lsp server 154 | func (d *documentAnalysisPipeline) sendDiags(c lsp.Client, uri protocol.DocumentURI) error { 155 | lspDiags := []protocol.Diagnostic{} 156 | for _, diag := range d.diags() { 157 | if diag == nil { 158 | continue 159 | } 160 | diagnostic := protocol.Diagnostic{ 161 | Severity: convertSeverity(diag.Severity), 162 | Source: "pulumi-yaml", 163 | Message: diag.Summary + "\n" + diag.Detail, 164 | } 165 | if diag.Subject != nil { 166 | diagnostic.Range = convertRange(diag.Subject) 167 | } 168 | 169 | lspDiags = append(lspDiags, diagnostic) 170 | c.LogDebugf("Preparing diagnostic %v", diagnostic) 171 | } 172 | 173 | // Diagnostics last until the next publish, so we need to publish even if we 174 | // have not found any diags. This will clear the diags for the user. 175 | return c.PublishDiagnostics(&protocol.PublishDiagnosticsParams{ 176 | URI: uri, 177 | Version: 0, 178 | Diagnostics: lspDiags, 179 | }) 180 | } 181 | 182 | func (d *documentAnalysisPipeline) promoteError(msg string, err error) *hcl.Diagnostic { 183 | return &hcl.Diagnostic{ 184 | Severity: hcl.DiagError, 185 | Summary: msg, 186 | Detail: err.Error(), 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /sdk/lsp/text.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package lsp 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | "sync" 9 | 10 | "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 11 | "go.lsp.dev/protocol" 12 | ) 13 | 14 | // A thread-safe text document designed to handle incremental updates. 15 | type Document struct { 16 | // Any method that reads `lines` needs to acquire a read lock of `m`. To 17 | // mutate `lines`, a write lock is required. 18 | // 19 | // TODO: find/implement a rope 20 | lines []string 21 | // NOTE: uri should be considered immutable. This allows us to fetch is 22 | // without a lock. 23 | uri protocol.DocumentURI 24 | m *sync.RWMutex 25 | 26 | version int32 27 | languageID protocol.LanguageIdentifier 28 | } 29 | 30 | // Create a new document from a TextDocumentItem. 31 | func NewDocument(item protocol.TextDocumentItem) Document { 32 | return Document{ 33 | lines: strings.Split(item.Text, lineDeliminator), 34 | uri: item.URI, 35 | version: item.Version, 36 | languageID: item.LanguageID, 37 | 38 | m: new(sync.RWMutex), 39 | } 40 | } 41 | 42 | // Update the document with the given changes. 43 | func (t *Document) AcceptChanges(changes []protocol.TextDocumentContentChangeEvent) error { 44 | t.m.Lock() 45 | defer t.m.Unlock() 46 | for _, change := range changes { 47 | err := t.acceptChange(change) 48 | if err != nil { 49 | return err 50 | } 51 | } 52 | return nil 53 | } 54 | 55 | const lineDeliminator = "\n" 56 | 57 | // Retrieve the URI of the Document. 58 | func (d *Document) URI() protocol.DocumentURI { 59 | return d.uri 60 | } 61 | 62 | // Returns the whole document as a string. 63 | func (d *Document) String() string { 64 | d.m.RLock() 65 | defer d.m.RUnlock() 66 | return strings.Join(d.lines, lineDeliminator) 67 | } 68 | 69 | // Window provides the text of the document that fits in the window. 70 | func (d *Document) Window(window protocol.Range) (string, error) { 71 | // This is only the range, which was passed by value 72 | if err := validateRange(window); err != nil { 73 | return "", err 74 | } 75 | d.m.RLock() 76 | defer d.m.RUnlock() 77 | if err := d.validateRange(window); err != nil { 78 | return "", err 79 | } 80 | sLine := int(window.Start.Line) 81 | sChar := int(window.Start.Character) 82 | eLine := int(window.End.Line) 83 | eChar := int(window.End.Character) 84 | if window.Start.Line == window.End.Line { 85 | return d.lines[sLine][sChar:eChar], nil 86 | } 87 | return d.lines[sLine][sChar:] + strings.Join(d.lines[sLine+1:eLine], lineDeliminator) + d.lines[eLine][:eChar], nil 88 | } 89 | 90 | // Retrieve a specific line in the document. If the index is out of range (or 91 | // negative), an error is returned. 92 | func (d *Document) Line(i int) (string, error) { 93 | d.m.Lock() 94 | defer d.m.Unlock() 95 | if i < 0 { 96 | return "", fmt.Errorf("cannot access negative line") 97 | } 98 | if i >= len(d.lines) { 99 | return "", fmt.Errorf("line index is %d but there are only %d lines", i, len(d.lines)) 100 | } 101 | return d.lines[i], nil 102 | } 103 | 104 | func (d *Document) LineLen() int { 105 | d.m.Lock() 106 | defer d.m.Unlock() 107 | return len(d.lines) 108 | } 109 | 110 | // Validate that the range is in the Text. Calling validateRange requires 111 | // holding any lock on the document. 112 | func (d *Document) validateRange(r protocol.Range) error { 113 | sLine := int(r.Start.Line) 114 | sChar := int(r.Start.Character) 115 | if sLine >= len(d.lines) { 116 | return newInvalidRange(r, "start line %d out of bounds for document with %d lines", sLine, len(d.lines)) 117 | } 118 | if sChar >= len(d.lines[sLine]) { 119 | return newInvalidRange(r, "start character %d out of bound on line %d", sChar, sLine) 120 | } 121 | eLine := int(r.End.Line) 122 | eChar := int(r.End.Character) 123 | if eLine >= len(d.lines) { 124 | return newInvalidRange(r, "end line %d out of bounds for document with %d lines", eLine, len(d.lines)) 125 | } 126 | if eChar > len(d.lines[eLine]) { 127 | return newInvalidRange(r, "end character %d out of bound on line %d (len = %d)", eChar, eLine, len(d.lines[eLine])) 128 | } 129 | return nil 130 | } 131 | 132 | // acceptChange implements the change on the document. Calling acceptChange 133 | // correctly requires holding a write lock on the document. 134 | func (d *Document) acceptChange(change protocol.TextDocumentContentChangeEvent) error { 135 | var defRange protocol.Range 136 | if change.Range == defRange && change.RangeLength == 0 { 137 | // This indicates that the whole document should be changed. 138 | d.lines = strings.Split(change.Text, lineDeliminator) 139 | return nil 140 | } 141 | // Note: RangeLength is depreciated 142 | lines := strings.Split(change.Text, lineDeliminator) 143 | s := change.Range.Start 144 | e := change.Range.End 145 | contract.Assertf(len(lines) != 0, "The change should have at least one line of content") 146 | 147 | if s.Line == e.Line { 148 | l := d.lines[s.Line] 149 | if len(lines) == 1 { 150 | // We are replacing withing a line 151 | if int(s.Character) > len(l) { 152 | panic(fmt.Sprintf("s.Char{%d} > len(l){%d}: %#v:\n\n%#v\n%#v\n%#v", 153 | int(s.Character), len(l), change, d.lines[s.Line-1], d.lines[s.Line], d.lines[s.Line+1])) 154 | 155 | } 156 | d.lines[s.Line] = l[:s.Character] + change.Text + l[e.Character:] 157 | return nil 158 | } 159 | // We need to add new lines 160 | start := l[:s.Character] + lines[0] 161 | end := l[e.Character:] 162 | lines[0] = start 163 | lines[len(lines)-1] += end 164 | newLines := []string{} 165 | newLines = append(newLines, d.lines[:s.Line]...) 166 | newLines = append(newLines, lines...) 167 | newLines = append(newLines, d.lines[e.Line+1:]...) 168 | d.lines = newLines 169 | return nil 170 | } 171 | // Range is across multiple lines 172 | if len(lines) == 1 { 173 | // Joining lines together 174 | join := d.lines[s.Line][:s.Character] + lines[0] + d.lines[e.Line][e.Character:] 175 | newLines := []string{} 176 | newLines = append(newLines, d.lines[:s.Line]...) 177 | newLines = append(newLines, join) 178 | newLines = append(newLines, d.lines[e.Line+1:]...) 179 | d.lines = newLines 180 | return nil 181 | } 182 | // multiple lines across a multi-line range 183 | d.lines[s.Line] = d.lines[s.Line][:s.Character] + lines[0] 184 | d.lines[e.Line] = lines[len(lines)-1] + d.lines[e.Line][e.Character:] 185 | start := d.lines[:s.Line+1] 186 | end := d.lines[e.Line:] 187 | d.lines = append(append(start, lines[1:len(lines)-1]...), end...) 188 | return nil 189 | } 190 | 191 | func validateRange(r protocol.Range) error { 192 | if r.Start.Line > r.End.Line { 193 | return newInvalidRange(r, "start line %d > end line %d", r.Start.Line, r.End.Line) 194 | } 195 | if r.Start.Line == r.End.Line && 196 | r.Start.Character > r.End.Character { 197 | return newInvalidRange(r, "start char %d > end char %d", r.Start.Character, r.End.Character) 198 | } 199 | return nil 200 | } 201 | 202 | func newInvalidRange(r protocol.Range, msg string, a ...interface{}) error { 203 | return invalidRange{r, fmt.Sprintf(msg, a...)} 204 | } 205 | 206 | type invalidRange struct { 207 | r protocol.Range 208 | reason string 209 | } 210 | 211 | func (ir invalidRange) Error() string { 212 | return "Invalid range: " + ir.reason 213 | } 214 | -------------------------------------------------------------------------------- /sdk/yaml/find.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package yaml 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/pulumi/pulumi-lsp/sdk/lsp" 10 | "github.com/pulumi/pulumi-lsp/sdk/util" 11 | "go.lsp.dev/protocol" 12 | ) 13 | 14 | type UnparsableError struct { 15 | msg string 16 | 17 | Bail bool // If an attempt should be made to continue without the parsed schema 18 | } 19 | 20 | func (e UnparsableError) Error() string { 21 | var post string 22 | if e.msg != "" { 23 | post = ": " + e.msg 24 | } 25 | return fmt.Sprintf("could not get parsed schema%s", post) 26 | } 27 | 28 | // Find the object at point, as well as it's location. An error indicates that 29 | // there was a problem getting the object at point. If no object is found, all 30 | // zero values are returned. 31 | func (doc *document) objectAtPoint(pos protocol.Position) (Object, error) { 32 | parsed, ok := doc.analysis.parsed.GetResult() 33 | canceledErr := UnparsableError{"canceled", true} 34 | nilError := UnparsableError{"failed", false} 35 | if !ok { 36 | return nil, canceledErr 37 | } 38 | if parsed.A == nil { 39 | return nil, nilError 40 | } 41 | for _, r := range parsed.A.Resources.Entries { 42 | keyRange := r.Key.Syntax().Syntax().Range() 43 | if r.Value != nil && r.Value.Type != nil && posInRange(r.Value.Type.Syntax().Syntax().Range(), pos) { 44 | tk := r.Value.Type.Value 45 | bound, ok := doc.analysis.bound.GetResult() 46 | if !ok { 47 | return nil, canceledErr 48 | } 49 | if bound.A == nil { 50 | return nil, nilError 51 | } 52 | version := "" 53 | if v := r.Value.Options.Version; v != nil { 54 | version = v.Value 55 | } 56 | res, err := bound.A.GetResources(tk, version) 57 | if err != nil { 58 | return nil, err 59 | } 60 | if len(res) == 0 { 61 | return nil, nil 62 | } 63 | valueRange := r.Value.Syntax().Syntax().Range() 64 | return Resource{ 65 | object: object{combineRange(convertRange(keyRange), convertRange(valueRange))}, 66 | schema: res[0].Schema(), 67 | }, nil 68 | } 69 | } 70 | bound, ok := doc.analysis.bound.GetResult() 71 | if !ok { 72 | return nil, canceledErr 73 | } 74 | if bound.A == nil { 75 | return nil, nilError 76 | } 77 | 78 | for _, f := range bound.A.Invokes() { 79 | tk := f.Expr().Token 80 | if posInRange(tk.Syntax().Syntax().Range(), pos) { 81 | return Invoke{ 82 | object: object{convertRange(f.Expr().Syntax().Syntax().Range())}, 83 | schema: f.Schema(), 84 | }, nil 85 | } 86 | } 87 | 88 | for _, r := range bound.A.References() { 89 | if posInRange(r.Range(), pos) { 90 | return &Reference{ 91 | object: object{convertRange(r.Range())}, 92 | ref: &r, 93 | }, nil 94 | } 95 | } 96 | return nil, nil 97 | } 98 | 99 | type KeyPos = util.Tuple[protocol.Position, string] 100 | 101 | // Return the place where the enclosing object starts 102 | func enclosingKey(text lsp.Document, pos protocol.Position) (protocol.Position, int, bool, error) { 103 | lineNum := int(pos.Line) 104 | line, err := text.Line(lineNum) 105 | if err != nil { 106 | return protocol.Position{}, 0, false, err 107 | } 108 | indentation, _ := indentationLevel(line) 109 | // Scan up until a non-blank line with less indentation is found. This 110 | // assumes that the YAML is valid and not in flow form. 111 | for lineNum > 1 { 112 | lineNum-- 113 | line, err := text.Line(lineNum) 114 | if err != nil { 115 | return protocol.Position{}, 0, false, err 116 | } 117 | ind, blank := indentationLevel(line) 118 | if !blank && ind < indentation && strings.HasSuffix(strings.TrimSpace(line), ":") { 119 | // Found the parent 120 | return protocol.Position{ 121 | Line: uint32(lineNum), 122 | Character: uint32(ind), 123 | }, indentation - ind, true, nil 124 | } 125 | } 126 | // We didn't find anything 127 | return protocol.Position{}, 0, false, nil 128 | } 129 | 130 | // Return the chain of parent keys from most senior to least senior. 131 | func parentKeys(text lsp.Document, pos protocol.Position) ([]KeyPos, int, bool, error) { 132 | parent, ind, ok, err := enclosingKey(text, pos) 133 | if err != nil || !ok { 134 | return nil, ind, ok, err 135 | } 136 | key, err := text.Line(int(parent.Line)) 137 | if err != nil { 138 | return nil, 0, false, err 139 | } 140 | key = strings.TrimSpace(key) 141 | key = strings.TrimSuffix(key, ":") 142 | 143 | grandparents, _, ok, err := parentKeys(text, parent) 144 | if err != nil { 145 | return nil, 0, false, err 146 | } 147 | tup := util.Tuple[protocol.Position, string]{A: parent, B: key} 148 | if !ok { 149 | return []KeyPos{tup}, 0, true, nil 150 | } 151 | return append(grandparents, tup), ind, true, nil 152 | } 153 | 154 | // Find the number of leading spaces in a line. 155 | func indentationLevel(line string) (spaces int, allBlank bool) { 156 | level := 0 157 | for _, c := range line { 158 | if c != ' ' { 159 | break 160 | } 161 | level += 1 162 | } 163 | return level, strings.TrimSpace(line) == "" 164 | } 165 | 166 | // childKeys returns a map of subsidiary keys to their positions. 167 | func childKeys(text lsp.Document, pos protocol.Position) (map[string]protocol.Position, error) { 168 | line, err := text.Line(int(pos.Line)) 169 | if err != nil { 170 | return nil, err 171 | } 172 | topLevel, blank := indentationLevel(line) 173 | if blank { 174 | return nil, fmt.Errorf("cannot call subsidiaryKeys on a blank line") 175 | } 176 | level := -1 177 | m := map[string]protocol.Position{} 178 | for i := int(pos.Line) + 1; i < text.LineLen(); i++ { 179 | line, err := text.Line(i) 180 | if err != nil { 181 | return nil, err 182 | } 183 | indLevel, blank := indentationLevel(line) 184 | if blank { 185 | continue 186 | } 187 | if indLevel <= topLevel { 188 | // Found a key at the same or greater level. We are done. 189 | break 190 | } 191 | if level == -1 { 192 | // Our first key indicates the indentation level of subsidiary keys 193 | level = indLevel 194 | } 195 | if indLevel == level { 196 | keyValue := strings.Split(line, ":") 197 | if len(keyValue) == 0 { 198 | continue 199 | } 200 | m[strings.ToLower(strings.TrimSpace(keyValue[0]))] = protocol.Position{ 201 | Line: uint32(i), 202 | Character: uint32(indLevel), 203 | } 204 | } 205 | // indLevel < level implies invalid yaml: 206 | // ``` 207 | // foo: 208 | // bar: valid, level = 2 209 | // buz: invalid, level = 1 210 | // ``` 211 | // We do nothing 212 | } 213 | return m, nil 214 | } 215 | 216 | // siblingKeys returns list of properties at the level of `pos`. 217 | func siblingKeys(text lsp.Document, pos protocol.Position) (map[string]protocol.Position, bool, error) { 218 | parent, _, ok, err := enclosingKey(text, pos) 219 | if err != nil || !ok { 220 | return nil, ok, err 221 | } 222 | siblings, err := childKeys(text, parent) 223 | if err != nil { 224 | return nil, false, err 225 | } 226 | return siblings, true, nil 227 | } 228 | 229 | // topLevelKeys returns the top level YAML keys in `text`. The parse is line by line. 230 | func topLevelKeys(text lsp.Document) (map[string]protocol.Position, error) { 231 | m := map[string]protocol.Position{} 232 | for i := 0; i < text.LineLen(); i++ { 233 | line, err := text.Line(i) 234 | if err != nil { 235 | return nil, err 236 | } 237 | if len(line) > 0 && line[0] != ' ' { 238 | part := line 239 | parts := strings.Split(line, ":") 240 | if len(parts) > 0 { 241 | part = parts[0] 242 | } 243 | m[part] = protocol.Position{Line: uint32(i), Character: 0} 244 | } 245 | } 246 | return m, nil 247 | } 248 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /sdk/yaml/bind/schema.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package bind 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/blang/semver" 10 | "github.com/hashicorp/hcl/v2" 11 | yaml "github.com/pulumi/pulumi-yaml/pkg/pulumiyaml" 12 | "github.com/pulumi/pulumi-yaml/pkg/pulumiyaml/ast" 13 | "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 14 | "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" 15 | "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" 16 | "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 17 | 18 | "github.com/pulumi/pulumi-lsp/sdk/util" 19 | ) 20 | 21 | // Loads schemas as necessary from the loader to attach to resources and invokes. 22 | // The schemas are cached internally to make searching faster. 23 | // New diagnostics are appended to the internal diag list. 24 | func (d *Decl) LoadSchema(loader schema.ReferenceLoader) { 25 | d.lock.Lock() 26 | defer d.lock.Unlock() 27 | for invoke := range d.invokes { 28 | typeLoc := invoke.defined.Token.Syntax().Syntax().Range() 29 | pkgName := d.loadPackage(invoke.token, invoke.version, loader, 30 | invoke.defined.Token.Syntax().Syntax().Range()) 31 | if pkgName != "" { 32 | pkg := d.loadedPackages[pkgKey{pkgName, invoke.version}] 33 | if pkg.diag != nil { 34 | // We don't have a valid package, so give an appropriate warning to the user and move on 35 | d.diags = d.diags.Extend(pkg.diag(typeLoc)) 36 | continue 37 | } 38 | if !pkg.isValid() { 39 | continue 40 | } 41 | if f, ok := pkg.ResolveFunction(invoke.token); ok { 42 | // There is something wrong with this definition 43 | if f.diag != nil { 44 | d.diags = d.diags.Extend(f.diag(typeLoc)) 45 | } 46 | invoke.definition = f.Function 47 | inputs := []*schema.Property{} 48 | if i := f.Inputs; i != nil && i.Properties != nil { 49 | inputs = i.Properties 50 | } 51 | args := ast.Object() 52 | if a := invoke.defined.CallArgs; a != nil { 53 | args = a 54 | } 55 | d.validateProperties(util.MapOver(args.Entries, func(o ast.ObjectProperty) MapKey { 56 | return MapKey{o.Key.(*ast.StringExpr).Value, o.Key.Syntax().Syntax().Range()} 57 | }), inputs, f.Token, args.Syntax().Syntax().Range()) 58 | if ret := invoke.defined.Return; ret != nil { 59 | if out := f.Outputs; out != nil { 60 | var valid bool 61 | existing := map[string]bool{} 62 | for _, prop := range out.Properties { 63 | if prop.Name == ret.Value { 64 | valid = true 65 | break 66 | } 67 | existing[prop.Name] = true 68 | } 69 | if !valid { 70 | d.diags = append(d.diags, 71 | propertyDoesNotExistDiag( 72 | ret.Value, 73 | out.String(), 74 | util.MapKeys(existing), 75 | ret.Syntax().Syntax().Range())) 76 | } 77 | } 78 | } 79 | } else { 80 | d.diags = append(d.diags, missingTokenDiag(pkgName, invoke.token, typeLoc)) 81 | } 82 | } 83 | } 84 | 85 | for _, v := range d.variables { 86 | if v, ok := v.definition.(*Resource); ok { 87 | if v.defined.Value == nil || v.defined.Value.Type == nil { 88 | // Type is not defined, so exit early 89 | continue 90 | } 91 | typeLoc := v.defined.Value.Type.Syntax().Syntax().Range() 92 | pkgName := d.loadPackage(v.token, v.version, loader, typeLoc) 93 | if pkgName != "" { 94 | pkg := d.loadedPackages[pkgKey{pkgName, v.version}] 95 | if pkg.diag != nil { 96 | // We don't have a valid package, so give an appropriate warning to the user and move on 97 | d.diags = d.diags.Extend(pkg.diag(typeLoc)) 98 | } 99 | if !pkg.isValid() { 100 | continue 101 | } 102 | if f, ok := pkg.ResolveResource(v.token); ok { 103 | // There is something wrong with this definition 104 | if f.diag != nil { 105 | d.diags = d.diags.Extend(f.diag(typeLoc)) 106 | } 107 | v.definition = f.Resource 108 | d.validateProperties(util.MapOver(v.defined.Value.Properties.Entries, func(m ast.PropertyMapEntry) MapKey { 109 | return MapKey{m.Key.Value, m.Key.Syntax().Syntax().Range()} 110 | }), 111 | f.InputProperties, (&schema.ResourceType{ 112 | Token: f.Resource.Token, 113 | Resource: f.Resource, 114 | }).String(), v.defined.Key.Syntax().Syntax().Range()) 115 | } else { 116 | d.diags = append(d.diags, missingTokenDiag(pkgName, v.token, typeLoc)) 117 | } 118 | } 119 | } 120 | } 121 | 122 | d.checkSchemaPropertyAccesses() 123 | } 124 | 125 | type MapKey struct { 126 | tag string 127 | rnge *hcl.Range 128 | } 129 | 130 | // Applied appropriate diagnostics to a property map given a backing schema. 131 | // Location is the default value for a diagnostic, and is only used if a more 132 | // specific location is not possible. 133 | func (d *Decl) validateProperties(existing []MapKey, typed []*schema.Property, parent string, loc *hcl.Range) { 134 | definedProps := map[string]bool{} 135 | for _, prop := range existing { 136 | definedProps[prop.tag] = true 137 | } 138 | resourceProps := map[string]bool{} 139 | for _, prop := range typed { 140 | resourceProps[prop.Name] = true 141 | if prop.IsRequired() && !definedProps[prop.Name] { 142 | // TODO: it would be good to put the error message on the 143 | // properties tag, but that is not available. 144 | d.diags = append(d.diags, missingRequiredPropDiag(prop, loc)) 145 | } 146 | } 147 | for _, prop := range existing { 148 | if !resourceProps[prop.tag] { 149 | d.diags = append(d.diags, propertyDoesNotExistDiag(prop.tag, 150 | parent, util.MapKeys(resourceProps), prop.rnge)) 151 | } 152 | } 153 | } 154 | 155 | func (d *Decl) typeExpr(e ast.Expr) schema.Type { 156 | switch e := e.(type) { 157 | // Primitive types: nothing to bind 158 | case *ast.NullExpr: 159 | return nil 160 | case *ast.BooleanExpr: 161 | return schema.BoolType 162 | case *ast.NumberExpr: 163 | return schema.NumberType 164 | case *ast.StringExpr, *ast.InterpolateExpr: 165 | return schema.StringType 166 | 167 | case *ast.SymbolExpr: 168 | var tag string 169 | if t, ok := e.Property.Accessors[0].(*ast.PropertyName); ok { 170 | tag = t.Name 171 | } 172 | if v, ok := d.variables[tag]; tag != "" && ok { 173 | t := v.definition.ResolveType(d) 174 | if len(e.Property.Accessors) == 0 { 175 | return t 176 | } 177 | for _, r := range v.uses { 178 | if r.location == e.Syntax().Syntax().Range() { 179 | types, _ := r.access.TypeFromRoot(t) 180 | return types[len(types)-1] 181 | } 182 | } 183 | } 184 | return nil 185 | 186 | // Container types: 187 | case *ast.ListExpr: 188 | t := schema.AnyType 189 | if len(e.Elements) != 0 { 190 | t = d.typeExpr(e.Elements[0]) 191 | } 192 | return &schema.ArrayType{ElementType: t} 193 | 194 | case *ast.ObjectExpr: 195 | // TODO handle object types 196 | return nil 197 | 198 | case *ast.InvokeExpr: 199 | t := e.Token 200 | if t == nil { 201 | return nil 202 | } 203 | for invoke := range d.invokes { 204 | if invoke.token == t.Value { 205 | if invoke.definition != nil { 206 | outputs := invoke.definition.Outputs 207 | if e.Return != nil { 208 | p, ok := outputs.Property(e.Return.Value) 209 | if ok { 210 | return p.Type 211 | } 212 | return nil 213 | } 214 | return outputs 215 | } 216 | break 217 | } 218 | } 219 | return nil 220 | 221 | case *ast.AssetArchiveExpr, *ast.FileArchiveExpr, *ast.RemoteArchiveExpr: 222 | return schema.ArchiveType 223 | case *ast.FileAssetExpr, *ast.RemoteAssetExpr, *ast.StringAssetExpr: 224 | return schema.AssetType 225 | 226 | // Stack reference 227 | case *ast.StackReferenceExpr: 228 | return nil 229 | 230 | // Functions 231 | case *ast.JoinExpr: 232 | return schema.StringType 233 | case *ast.SelectExpr: 234 | el := d.typeExpr(e.Values) 235 | if el == nil { 236 | return nil 237 | } 238 | if e, ok := el.(*schema.ArrayType); ok { 239 | return e.ElementType 240 | } 241 | return nil 242 | case *ast.SplitExpr: 243 | return schema.StringType 244 | case *ast.ToBase64Expr: 245 | return schema.StringType 246 | case *ast.ToJSONExpr: 247 | return schema.StringType 248 | default: 249 | return nil 250 | } 251 | } 252 | 253 | // We have resolved variables and invokes to their schema equivalents. We can 254 | // now resolve property access, posting diagnostics as necessary. 255 | func (d *Decl) checkSchemaPropertyAccesses() { 256 | for _, v := range d.variables { 257 | for _, use := range v.uses { 258 | d.checkSchemaPropertyAccess(v.definition, use) 259 | } 260 | } 261 | } 262 | 263 | // Check that a property access is valid against the schema. If no schema can be 264 | // found, say nothing. 265 | func (d *Decl) checkSchemaPropertyAccess(def Definition, use Reference) { 266 | // No point checking if there is no access 267 | if len(use.access) == 0 { 268 | return 269 | } 270 | switch def := def.(type) { 271 | case *Resource: 272 | if def.definition == nil { 273 | return 274 | } 275 | d.verifyPropertyAccess(use.access, &schema.ResourceType{ 276 | Token: def.definition.Token, 277 | Resource: def.definition, 278 | }) 279 | case ResolvableType: 280 | typ := def.ResolveType(d) 281 | if typ == nil { 282 | return 283 | } 284 | d.verifyPropertyAccess(use.access, typ) 285 | case nil: 286 | return 287 | default: 288 | contract.Failf("Unknown definition type: %[1]T: %[1]v", def) 289 | } 290 | } 291 | 292 | func (d *Decl) verifyPropertyAccess(expr PropertyAccessorList, typ schema.Type) { 293 | if len(expr) == 0 { 294 | return 295 | } 296 | _, diag := expr.TypeFromRoot(typ) 297 | if diag != nil { 298 | d.diags = append(d.diags, diag) 299 | } 300 | } 301 | 302 | // Load a package into the cache if necessary. errRange is the location that 303 | // motivated loading the package (a type token in a invoke or a resource). 304 | func (d *Decl) loadPackage(tk, version string, loader schema.ReferenceLoader, errRange *hcl.Range) string { 305 | pkgName, err := pkgNameFromToken(tk) 306 | if err != nil { 307 | d.diags = append(d.diags, unparsableTokenDiag(tk, errRange, err)) 308 | return "" 309 | } 310 | 311 | key := pkgKey{pkgName, version} 312 | _, ok := d.loadedPackages[key] 313 | if !ok { 314 | var v *semver.Version 315 | if version != "" { 316 | version, err := semver.ParseTolerant(version) 317 | if err != nil { 318 | return "" 319 | } 320 | v = &version 321 | } 322 | p, err := loader.LoadPackageReference(pkgName, v) 323 | var pkg pkgCache 324 | if err != nil { 325 | pkg = pkgCache{ 326 | diag: NewDiagsFromLocation(func(r *hcl.Range) *hcl.Diagnostic { 327 | return failedToLoadPackageDiag(pkgName, r, err) 328 | }), 329 | } 330 | } else { 331 | pkg = newPkgCache(p) 332 | } 333 | d.loadedPackages[key] = pkg 334 | } 335 | return pkgName 336 | } 337 | 338 | func pkgNameFromToken(tk string) (string, error) { 339 | components := strings.Split(tk, ":") 340 | if len(components) == 2 { 341 | if tk == "pulumi:providers" { 342 | return "", fmt.Errorf("package missing from provider type") 343 | } 344 | return components[0], nil 345 | } 346 | if len(components) == 3 { 347 | if strings.HasPrefix(tk, "pulumi:providers:") { 348 | return components[2], nil 349 | } 350 | return components[0], nil 351 | } 352 | return "", fmt.Errorf("wrong number of components") 353 | } 354 | 355 | type diagsFromLocation func(*hcl.Range) hcl.Diagnostics 356 | 357 | func NewDiagsFromLocation(f func(*hcl.Range) *hcl.Diagnostic) diagsFromLocation { 358 | return func(r *hcl.Range) hcl.Diagnostics { 359 | return hcl.Diagnostics{f(r)} 360 | } 361 | } 362 | 363 | // Maintain a cached map from tokens to specs. 364 | type pkgCache struct { 365 | p schema.PackageReference 366 | 367 | // A package level warning, applied to every resource loaded with the 368 | // package. 369 | diag diagsFromLocation 370 | } 371 | 372 | func (p *pkgCache) ResolveResource(token string) (ResourceSpec, bool) { 373 | var r *schema.Resource 374 | var err error 375 | ok := true 376 | if providers.IsProviderType(tokens.Type(token)) { 377 | r, err = p.p.Provider() 378 | } else { 379 | var tk yaml.ResourceTypeToken 380 | tk, err = yaml.NewResourcePackage(p.p).ResolveResource(token) 381 | if err != nil { 382 | return ResourceSpec{}, false 383 | } 384 | r, ok, err = p.p.Resources().Get(tk.String()) 385 | } 386 | if !ok || err != nil { 387 | return ResourceSpec{}, false 388 | } 389 | spec := ResourceSpec{Resource: r} 390 | if r.DeprecationMessage != "" { 391 | spec.diag = NewDiagsFromLocation(func(rng *hcl.Range) *hcl.Diagnostic { 392 | return depreciatedDiag(r.Token, r.DeprecationMessage, rng) 393 | }) 394 | } 395 | return spec, ok 396 | } 397 | 398 | func (p *pkgCache) ResolveFunction(token string) (FunctionSpec, bool) { 399 | tk, err := yaml.NewResourcePackage(p.p).ResolveFunction(token) 400 | if err != nil { 401 | return FunctionSpec{}, false 402 | } 403 | f, ok, err := p.p.Functions().Get(tk.String()) 404 | if !ok || err != nil { 405 | return FunctionSpec{}, false 406 | } 407 | spec := FunctionSpec{Function: f} 408 | if f.DeprecationMessage != "" { 409 | spec.diag = NewDiagsFromLocation(func(rng *hcl.Range) *hcl.Diagnostic { 410 | return depreciatedDiag(f.Token, f.DeprecationMessage, rng) 411 | }) 412 | 413 | } 414 | return spec, ok 415 | } 416 | 417 | func (p pkgCache) isValid() bool { 418 | return p.p != nil 419 | } 420 | 421 | type ResourceSpec struct { 422 | *schema.Resource 423 | 424 | diag diagsFromLocation 425 | } 426 | 427 | type FunctionSpec struct { 428 | *schema.Function 429 | 430 | diag diagsFromLocation 431 | } 432 | 433 | func newPkgCache(p schema.PackageReference) pkgCache { 434 | return pkgCache{p, nil} 435 | } 436 | -------------------------------------------------------------------------------- /sdk/yaml/bind/bind.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | // Bind performs static analysis on a YAML document. The entry point for the package is `NewDecl`. 4 | // 5 | // bind.go contains logic for the initial non-schema binding of an `ast.TemplateDecl` into a `Decl`. 6 | // query.go contains logic to get information out of a `Decl`. 7 | // schema.go handles loading appropriate schemas and binding them to an existing `Decl`. 8 | // diags.go contains the diagnostic error messages used. 9 | package bind 10 | 11 | import ( 12 | "fmt" 13 | "sync" 14 | 15 | "github.com/hashicorp/hcl/v2" 16 | 17 | "github.com/pulumi/pulumi-yaml/pkg/pulumiyaml" 18 | "github.com/pulumi/pulumi-yaml/pkg/pulumiyaml/ast" 19 | "github.com/pulumi/pulumi/pkg/v3/codegen" 20 | "github.com/pulumi/pulumi/pkg/v3/codegen/schema" 21 | 22 | "github.com/pulumi/pulumi-lsp/sdk/util" 23 | ) 24 | 25 | // A bound template. 26 | // 27 | // NOTE: the binding need not be complete, and the template need not be valid. 28 | type Decl struct { 29 | // All names in the global namespace. 30 | variables map[string]*Variable 31 | 32 | // The output namespace 33 | outputs map[string]ast.PropertyMapEntry 34 | 35 | // The set of all invokes. 36 | invokes map[*Invoke]struct{} 37 | 38 | diags hcl.Diagnostics 39 | 40 | loadedPackages map[pkgKey]pkgCache 41 | 42 | lock *sync.RWMutex 43 | } 44 | 45 | type pkgKey struct { 46 | name string 47 | version string 48 | } 49 | 50 | // A value that a reference can bind to. This includes the variables, config and 51 | // resources section of the yaml template. 52 | type Variable struct { 53 | // definition is either a Resource, *ast.ConfigParamDecl 54 | definition Definition 55 | uses []Reference 56 | 57 | // The variable name in the global namespace 58 | name string 59 | } 60 | 61 | func (v *Variable) Name() string { 62 | return v.name 63 | } 64 | 65 | func (v *Variable) Source() Definition { 66 | return v.definition 67 | } 68 | 69 | type Definition interface { 70 | ResolvableType 71 | DefinitionRange() *hcl.Range 72 | isDefinition() 73 | } 74 | 75 | type Reference struct { 76 | location *hcl.Range 77 | access PropertyAccessorList 78 | 79 | variable *Variable 80 | // A cache of how the variable is represented in text 81 | s string 82 | } 83 | 84 | // Returns the property accessors that hang on the variable referenced. 85 | // Examples: 86 | // ${foo.bar} => ["bar"] 87 | // ${foo.bar.baz} => ["bar" "baz"] 88 | // ${foo.} => [] 89 | // ${foo} => [] 90 | func (r *Reference) Accessors() PropertyAccessorList { 91 | return r.access 92 | } 93 | 94 | func (r *Reference) Var() *Variable { 95 | return r.variable 96 | } 97 | 98 | func (r *Reference) Range() *hcl.Range { 99 | return r.location 100 | } 101 | 102 | func (r *Reference) String() string { 103 | return r.s 104 | } 105 | 106 | type PropertyAccessorList []PropertyAccessor 107 | 108 | // Compute out the type chain as long as possible. The completion chain always 109 | // has 1 element since it includes the root. 110 | func (l PropertyAccessorList) TypeFromRoot(root schema.Type) ([]schema.Type, *hcl.Diagnostic) { 111 | types := []schema.Type{root} 112 | handleProperties := func(tag string, props []*schema.Property, parent schema.Type, rnge *hcl.Range) (*schema.Property, *hcl.Diagnostic) { 113 | existing := map[string]struct{}{} 114 | for _, p := range props { 115 | if p.Name == tag { 116 | return p, nil 117 | } 118 | existing[p.Name] = struct{}{} 119 | } 120 | return nil, propertyDoesNotExistDiag(tag, parent.String(), util.MapKeys(existing), rnge) 121 | } 122 | 123 | getStringTag := func(p ast.PropertyAccessor) string { 124 | switch a := p.(type) { 125 | case *ast.PropertyName: 126 | return a.Name 127 | case *ast.PropertySubscript: 128 | switch a := a.Index.(type) { 129 | case string: 130 | return a 131 | } 132 | } 133 | return "" 134 | } 135 | exit := func(diag *hcl.Diagnostic) ([]schema.Type, *hcl.Diagnostic) { 136 | for len(types)+1 < len(l) { 137 | // Indicate the a type exists but could not be found 138 | types = append(types, nil) 139 | } 140 | return types, diag 141 | } 142 | for _, prop := range l { 143 | next := func(t schema.Type) { 144 | types = append(types, t) 145 | root = t 146 | } 147 | switch typ := codegen.UnwrapType(root).(type) { 148 | case *schema.ArrayType: 149 | if s, ok := prop.PropertyAccessor.(*ast.PropertySubscript); ok { 150 | if _, ok := s.Index.(int); ok { 151 | next(typ.ElementType) 152 | continue 153 | } 154 | // TODO: specialize for map index 155 | return exit(noPropertyAccessDiag(typ.String(), prop.rnge)) 156 | } 157 | return exit(noPropertyAccessDiag(typ.String(), prop.rnge)) 158 | 159 | case *schema.MapType: 160 | switch a := prop.PropertyAccessor.(type) { 161 | case *ast.PropertySubscript: 162 | _, name := a.Index.(string) 163 | if name { 164 | next(typ.ElementType) 165 | continue 166 | } 167 | return exit(noPropertyIndexDiag(typ.String(), prop.rnge)) 168 | case *ast.PropertyName: 169 | next(typ.ElementType) 170 | continue 171 | } 172 | 173 | case *schema.ResourceType: 174 | r := typ.Resource 175 | tag := getStringTag(prop.PropertyAccessor) 176 | if tag == "" { 177 | return exit(noPropertyIndexDiag(typ.String(), prop.rnge)) 178 | } 179 | if r == nil { 180 | return exit(nil) 181 | } 182 | 183 | prop, diag := handleProperties(tag, util.ResourceProperties(r), typ, prop.rnge) 184 | if diag != nil { 185 | return exit(diag) 186 | } 187 | next(prop.Type) 188 | 189 | case *schema.ObjectType: 190 | tag := getStringTag(prop.PropertyAccessor) 191 | if tag == "" { 192 | return exit(noPropertyIndexDiag(typ.String(), prop.rnge)) 193 | } 194 | prop, diag := handleProperties(tag, typ.Properties, typ, prop.rnge) 195 | if diag != nil { 196 | return exit(diag) 197 | } 198 | next(prop.Type) 199 | } 200 | } 201 | return exit(nil) 202 | } 203 | 204 | type PropertyAccessor struct { 205 | ast.PropertyAccessor 206 | 207 | rnge *hcl.Range 208 | } 209 | 210 | func (b *Decl) newRefernce(variable string, loc *hcl.Range, accessor []ast.PropertyAccessor, repr string) { 211 | v, ok := b.variables[variable] 212 | // Name is used for the initial offset 213 | var l []PropertyAccessor 214 | offsetByte := len(variable) + loc.Start.Byte 215 | offsetChar := len(variable) + loc.Start.Column 216 | incrOffset := func(i int) { 217 | offsetByte += i 218 | offsetChar += i 219 | } 220 | // 2 for the leading ${ 221 | incrOffset(2) 222 | for _, a := range accessor { 223 | length := 0 224 | postOffset := 0 225 | switch a := a.(type) { 226 | case *ast.PropertyName: 227 | incrOffset(1) // 1 for the . 228 | length = len(a.Name) 229 | case *ast.PropertySubscript: 230 | switch a := a.Index.(type) { 231 | case string: 232 | incrOffset(2) 233 | postOffset = 2 234 | length = len(a) 235 | case int: 236 | incrOffset(1) 237 | postOffset = 1 238 | length = len(fmt.Sprintf("%d", a)) + 2 // 2 brackets 239 | } 240 | } 241 | aRange := loc 242 | if line := loc.Start.Line; line == loc.End.Line { 243 | start := hcl.Pos{ 244 | Line: line, 245 | Column: offsetChar, 246 | Byte: offsetByte, 247 | } 248 | aRange = &hcl.Range{ 249 | Filename: loc.Filename, 250 | Start: start, 251 | End: hcl.Pos{ 252 | Line: line, 253 | Column: offsetChar + length, 254 | Byte: offsetByte + length, 255 | }, 256 | } 257 | incrOffset(length + postOffset) 258 | } 259 | l = append(l, PropertyAccessor{ 260 | PropertyAccessor: a, 261 | rnge: aRange, 262 | }) 263 | } 264 | ref := Reference{location: loc, access: l} 265 | if !ok { 266 | v = &Variable{name: variable, uses: []Reference{}} 267 | b.variables[variable] = v 268 | } 269 | ref.variable = v 270 | ref.s = repr 271 | v.uses = append(v.uses, ref) 272 | 273 | } 274 | 275 | type Resource struct { 276 | token string 277 | version string 278 | defined *ast.ResourcesMapEntry 279 | definition *schema.Resource 280 | } 281 | 282 | func (r *Resource) isDefinition() {} 283 | func (r *Resource) DefinitionRange() *hcl.Range { 284 | return r.defined.Key.Syntax().Syntax().Range() 285 | } 286 | func (r *Resource) ResolveType(*Decl) schema.Type { 287 | return &schema.ResourceType{ 288 | Token: r.token, 289 | Resource: r.definition, 290 | } 291 | } 292 | 293 | func (r *Resource) Schema() *schema.Resource { 294 | return r.definition 295 | } 296 | 297 | type VariableMapEntry struct{ ast.VariablesMapEntry } 298 | 299 | func (r *VariableMapEntry) isDefinition() {} 300 | func (v *VariableMapEntry) DefinitionRange() *hcl.Range { 301 | s := v.Key.Syntax() 302 | if s == nil { 303 | return nil 304 | } 305 | ds := s.Syntax() 306 | if ds == nil { 307 | return nil 308 | } 309 | return ds.Range() 310 | } 311 | 312 | type ConfigMapEntry struct{ ast.ConfigMapEntry } 313 | 314 | func (c *ConfigMapEntry) isDefinition() {} 315 | func (c *ConfigMapEntry) DefinitionRange() *hcl.Range { 316 | return c.Key.Syntax().Syntax().Range() 317 | } 318 | 319 | type ResolvableType interface { 320 | ResolveType(*Decl) schema.Type 321 | } 322 | 323 | func (c *ConfigMapEntry) ResolveType(*Decl) schema.Type { 324 | switch c.Value.Type.Value { 325 | case "string": 326 | return schema.StringType 327 | case "int": 328 | return schema.IntType 329 | default: 330 | return nil 331 | } 332 | } 333 | 334 | func (v *VariableMapEntry) ResolveType(d *Decl) schema.Type { 335 | return d.typeExpr(v.Value) 336 | } 337 | 338 | type Invoke struct { 339 | token string 340 | version string 341 | defined *ast.InvokeExpr 342 | definition *schema.Function 343 | } 344 | 345 | func (f *Invoke) Expr() *ast.InvokeExpr { 346 | return f.defined 347 | } 348 | 349 | func (f *Invoke) Schema() *schema.Function { 350 | return f.definition 351 | } 352 | 353 | func NewDecl(decl *ast.TemplateDecl) (*Decl, error) { 354 | bound := &Decl{ 355 | variables: map[string]*Variable{ 356 | pulumiyaml.PulumiVarName: { 357 | definition: &VariableMapEntry{ 358 | ast.VariablesMapEntry{ 359 | Key: &ast.StringExpr{}, 360 | Value: ast.Object( 361 | ast.ObjectProperty{Key: ast.String("cwd"), Value: ast.String("cwd")}, 362 | ast.ObjectProperty{Key: ast.String("stack"), Value: ast.String("stack")}, 363 | ast.ObjectProperty{Key: ast.String("project"), Value: ast.String("project")}, 364 | ), 365 | }, 366 | }, 367 | uses: nil, 368 | name: pulumiyaml.PulumiVarName, 369 | }, 370 | }, 371 | outputs: map[string]ast.PropertyMapEntry{}, 372 | invokes: map[*Invoke]struct{}{}, 373 | diags: hcl.Diagnostics{}, 374 | loadedPackages: map[pkgKey]pkgCache{}, 375 | lock: &sync.RWMutex{}, 376 | } 377 | 378 | for _, c := range decl.Configuration.Entries { 379 | other, alreadyReferenced := bound.variables[c.Key.Value] 380 | if alreadyReferenced && other.definition != nil { 381 | bound.diags = bound.diags.Append( 382 | duplicateSourceDiag(c.Key.Value, 383 | c.Key.Syntax().Syntax().Range(), 384 | other.definition.DefinitionRange(), 385 | ), 386 | ) 387 | } else { 388 | bound.variables[c.Key.Value] = &Variable{ 389 | name: c.Key.Value, 390 | definition: &ConfigMapEntry{c}, 391 | } 392 | } 393 | } 394 | for _, v := range decl.Variables.Entries { 395 | other, alreadyReferenced := bound.variables[v.Key.Value] 396 | if alreadyReferenced && other.definition != nil { 397 | bound.diags = bound.diags.Append( 398 | duplicateSourceDiag(v.Key.Value, 399 | v.Key.Syntax().Syntax().Range(), 400 | other.definition.DefinitionRange(), 401 | ), 402 | ) 403 | } else { 404 | bound.variables[v.Key.Value] = &Variable{ 405 | name: v.Key.Value, 406 | definition: &VariableMapEntry{v}, 407 | } 408 | err := bound.bind(v.Value) 409 | if err != nil { 410 | return nil, err 411 | } 412 | } 413 | } 414 | for _, r := range decl.Resources.Entries { 415 | other, alreadyReferenced := bound.variables[r.Key.Value] 416 | if alreadyReferenced && other.definition != nil { 417 | var subject *hcl.Range 418 | if s := r.Key.Syntax(); s != nil && s.Syntax() != nil { 419 | subject = s.Syntax().Range() 420 | } 421 | var previous *hcl.Range 422 | if other != nil && other.definition != nil { 423 | previous = other.definition.DefinitionRange() 424 | } 425 | bound.diags = bound.diags.Append( 426 | duplicateSourceDiag(r.Key.Value, 427 | subject, previous, 428 | ), 429 | ) 430 | } else { 431 | if err := bound.bindResource(r); err != nil { 432 | return nil, err 433 | } 434 | } 435 | } 436 | for _, o := range decl.Outputs.Entries { 437 | // Because outputs cannot be referenced, we don't do a referenced check 438 | other, alreadyDefined := bound.outputs[o.Key.Value] 439 | if alreadyDefined { 440 | bound.diags = bound.diags.Append(duplicateSourceDiag(o.Key.Value, 441 | o.Key.Syntax().Syntax().Range(), 442 | other.Key.Syntax().Syntax().Range())) 443 | } else { 444 | bound.outputs[o.Key.Value] = o 445 | err := bound.bind(o.Value) 446 | if err != nil { 447 | return nil, err 448 | } 449 | } 450 | } 451 | 452 | err := bound.analyzeBindings() 453 | return bound, err 454 | } 455 | 456 | // Performs analysis on bindings without a schema. This results in missing 457 | // variable errors and unused variable warnings. 458 | func (b *Decl) analyzeBindings() error { 459 | for name, v := range b.variables { 460 | if v.definition == nil { 461 | for _, use := range v.uses { 462 | b.diags = append(b.diags, variableDoesNotExistDiag(name, use)) 463 | } 464 | } 465 | switch v.definition.(type) { 466 | case *VariableMapEntry: 467 | if len(v.uses) == 0 && name != pulumiyaml.PulumiVarName { 468 | b.diags = append(b.diags, unusedVariableDiag(name, v.definition.DefinitionRange())) 469 | } 470 | } 471 | } 472 | return nil 473 | } 474 | 475 | func (b *Decl) bind(e ast.Expr) error { 476 | switch e := e.(type) { 477 | // Primitive types: nothing to bind 478 | case *ast.NullExpr, *ast.BooleanExpr, *ast.NumberExpr, *ast.StringExpr: 479 | 480 | // Reference types 481 | case *ast.InterpolateExpr: 482 | for _, part := range e.Parts { 483 | if v := part.Value; v != nil { 484 | err := b.bindPropertyAccess(v, e.Syntax().Syntax().Range()) 485 | if err != nil { 486 | return err 487 | } 488 | } 489 | } 490 | case *ast.SymbolExpr: 491 | return b.bindPropertyAccess(e.Property, e.Syntax().Syntax().Range()) 492 | 493 | // Container types: 494 | case *ast.ListExpr: 495 | for _, el := range e.Elements { 496 | err := b.bind(el) 497 | if err != nil { 498 | return err 499 | } 500 | } 501 | case *ast.ObjectExpr: 502 | for _, el := range e.Entries { 503 | keys := map[string]bool{} 504 | keyStr, ok := el.Key.(*ast.StringExpr) 505 | if ok && keys[keyStr.Value] { 506 | b.diags = append(b.diags, duplicateKeyDiag(keyStr.Value, keyStr.Syntax().Syntax().Range())) 507 | } 508 | err := b.bind(el.Key) 509 | if err != nil { 510 | return err 511 | } 512 | err = b.bind(el.Value) 513 | if err != nil { 514 | return err 515 | } 516 | } 517 | 518 | // Invoke is special, because it requires loading a schema 519 | case *ast.InvokeExpr: 520 | return b.bindInvoke(e) 521 | 522 | // Assets and Archives: 523 | // 524 | // These take only string expressions. 525 | // TODO: on a second pass, we could give warnings when they point towards 526 | // files that don't exist, provide validation for urls provided, ect. 527 | case *ast.AssetArchiveExpr: 528 | case *ast.FileArchiveExpr: 529 | case *ast.FileAssetExpr: 530 | case *ast.RemoteArchiveExpr: 531 | case *ast.RemoteAssetExpr: 532 | case *ast.StringAssetExpr: 533 | 534 | case *ast.ReadFileExpr: 535 | return b.bind(e.Path) 536 | case *ast.SecretExpr: 537 | return b.bind(e.Value) 538 | 539 | // Stack reference 540 | case *ast.StackReferenceExpr: 541 | 542 | // Functions 543 | case *ast.JoinExpr: 544 | err := b.bind(e.Delimiter) 545 | if err != nil { 546 | return err 547 | } 548 | return b.bind(e.Values) 549 | case *ast.SelectExpr: 550 | err := b.bind(e.Index) 551 | if err != nil { 552 | return err 553 | } 554 | return b.bind(e.Values) 555 | case *ast.SplitExpr: 556 | err := b.bind(e.Delimiter) 557 | if err != nil { 558 | return err 559 | } 560 | return b.bind(e.Source) 561 | case *ast.ToBase64Expr: 562 | return b.bind(e.Value) 563 | case *ast.ToJSONExpr: 564 | return b.bind(e.Value) 565 | 566 | case nil: 567 | // The result of some non-fatal parse errors 568 | 569 | default: 570 | panic(fmt.Sprintf("Unexpected expr type: '%T'", e)) 571 | } 572 | return nil 573 | } 574 | 575 | func (d *Decl) bindInvoke(invoke *ast.InvokeExpr) error { 576 | d.invokes[&Invoke{ 577 | token: invoke.Token.Value, 578 | defined: invoke, 579 | version: invoke.CallOpts.Version.GetValue(), 580 | }] = struct{}{} 581 | return d.bind(invoke.Args()) 582 | } 583 | 584 | func (d *Decl) bindResource(r ast.ResourcesMapEntry) error { 585 | if r.Value == nil { 586 | d.variables[r.Key.Value] = &Variable{definition: &Resource{defined: &r}, name: r.Key.Value} 587 | d.diags = append(d.diags, missingResourceBodyDiag(r.Key.Value, r.Key.Syntax().Syntax().Range())) 588 | return nil 589 | } 590 | if r.Value.Type == nil { 591 | d.variables[r.Key.Value] = &Variable{definition: &Resource{defined: &r}, name: r.Key.Value} 592 | d.diags = append(d.diags, missingResourceTypeDiag(r.Key.Value, r.Key.Syntax().Syntax().Range())) 593 | return nil 594 | } 595 | res := Resource{ 596 | token: r.Value.Type.Value, 597 | version: r.Value.Options.Version.GetValue(), 598 | defined: &r, 599 | } 600 | entries := map[string]bool{} 601 | for _, entry := range r.Value.Properties.Entries { 602 | k := entry.Key.Value 603 | if entries[k] { 604 | d.diags = append(d.diags, duplicateKeyDiag(k, entry.Key.Syntax().Syntax().Range())) 605 | } 606 | if err := d.bind(entry.Key); err != nil { 607 | return err 608 | } 609 | if err := d.bind(entry.Value); err != nil { 610 | return err 611 | } 612 | } 613 | if err := d.bindResourceOptions(r.Value.Options); err != nil { 614 | return err 615 | } 616 | d.variables[r.Key.Value] = &Variable{definition: &res, name: r.Key.Value} 617 | return nil 618 | } 619 | 620 | func (b *Decl) bindResourceOptions(opts ast.ResourceOptionsDecl) error { 621 | // We only need to bind types that are backed by expressions that could 622 | // contain variables. 623 | for _, e := range []ast.Expr{opts.DependsOn, opts.Parent, opts.Provider, opts.Providers} { 624 | if err := b.bind(e); err != nil { 625 | return err 626 | } 627 | } 628 | return nil 629 | } 630 | 631 | func (b *Decl) bindPropertyAccess(p *ast.PropertyAccess, loc *hcl.Range) error { 632 | l := p.Accessors 633 | if len(l) == 0 { 634 | b.diags = append(b.diags, emptyPropertyAccessDiag(loc)) 635 | // We still take the reference so we can lookup this interpolation later. 636 | b.newRefernce("", loc, nil, p.String()) 637 | return nil 638 | } 639 | if v, ok := p.Accessors[0].(*ast.PropertyName); ok { 640 | b.newRefernce(v.Name, loc, l[1:], p.String()) 641 | } else { 642 | b.diags = append(b.diags, propertyStartsWithIndexDiag(p, loc)) 643 | // We still take the reference so we can lookup this interpolation later. 644 | b.newRefernce("", loc, nil, p.String()) 645 | } 646 | return nil 647 | } 648 | -------------------------------------------------------------------------------- /sdk/lsp/methods.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Pulumi Corporation. All rights reserved. 2 | 3 | package lsp 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 9 | "go.lsp.dev/protocol" 10 | ) 11 | 12 | // Methods provides the interface to define methods for the LSP server. When the 13 | // lsp server calls a method that is not provided, it will log a warning and 14 | // return the zero value for the method's return type. 15 | type Methods struct { 16 | // A pointer back to the server 17 | server *Server 18 | // And a channel to indicate that the server has exited 19 | closer chan<- struct{} 20 | 21 | InitializeFunc func(client Client, params *protocol.InitializeParams) (result *protocol.InitializeResult, err error) 22 | InitializedFunc func(client Client, params *protocol.InitializedParams) (err error) 23 | ShutdownFunc func(client Client) (err error) 24 | ExitFunc func(client Client) (err error) 25 | WorkDoneProgressCancelFunc func(client Client, params *protocol.WorkDoneProgressCancelParams) (err error) 26 | LogTraceFunc func(client Client, params *protocol.LogTraceParams) (err error) 27 | SetTraceFunc func(client Client, params *protocol.SetTraceParams) (err error) 28 | CodeActionFunc func(client Client, params *protocol.CodeActionParams) (result []protocol.CodeAction, err error) 29 | CodeLensFunc func(client Client, params *protocol.CodeLensParams) (result []protocol.CodeLens, err error) 30 | CodeLensResolveFunc func(client Client, params *protocol.CodeLens) (result *protocol.CodeLens, err error) 31 | ColorPresentationFunc func(client Client, params *protocol.ColorPresentationParams) (result []protocol.ColorPresentation, err error) 32 | CompletionFunc func(client Client, params *protocol.CompletionParams) (result *protocol.CompletionList, err error) 33 | CompletionResolveFunc func(client Client, params *protocol.CompletionItem) (result *protocol.CompletionItem, err error) 34 | DeclarationFunc func(client Client, params *protocol.DeclarationParams) (result []protocol.Location, err error) 35 | DefinitionFunc func(client Client, params *protocol.DefinitionParams) (result []protocol.Location, err error) 36 | DidChangeFunc func(client Client, params *protocol.DidChangeTextDocumentParams) (err error) 37 | DidChangeConfigurationFunc func(client Client, params *protocol.DidChangeConfigurationParams) (err error) 38 | DidChangeWatchedFilesFunc func(client Client, params *protocol.DidChangeWatchedFilesParams) (err error) 39 | DidChangeWorkspaceFoldersFunc func(client Client, params *protocol.DidChangeWorkspaceFoldersParams) (err error) 40 | DidCloseFunc func(client Client, params *protocol.DidCloseTextDocumentParams) (err error) 41 | DidOpenFunc func(client Client, params *protocol.DidOpenTextDocumentParams) (err error) 42 | DidSaveFunc func(client Client, params *protocol.DidSaveTextDocumentParams) (err error) 43 | DocumentColorFunc func(client Client, params *protocol.DocumentColorParams) (result []protocol.ColorInformation, err error) 44 | DocumentHighlightFunc func(client Client, params *protocol.DocumentHighlightParams) (result []protocol.DocumentHighlight, err error) 45 | DocumentLinkFunc func(client Client, params *protocol.DocumentLinkParams) (result []protocol.DocumentLink, err error) 46 | DocumentLinkResolveFunc func(client Client, params *protocol.DocumentLink) (result *protocol.DocumentLink, err error) 47 | DocumentSymbolFunc func(client Client, params *protocol.DocumentSymbolParams) (result []interface{}, err error) 48 | ExecuteCommandFunc func(client Client, params *protocol.ExecuteCommandParams) (result interface{}, err error) 49 | FoldingRangesFunc func(client Client, params *protocol.FoldingRangeParams) (result []protocol.FoldingRange, err error) 50 | FormattingFunc func(client Client, params *protocol.DocumentFormattingParams) (result []protocol.TextEdit, err error) 51 | HoverFunc func(client Client, params *protocol.HoverParams) (result *protocol.Hover, err error) 52 | ImplementationFunc func(client Client, params *protocol.ImplementationParams) (result []protocol.Location, err error) 53 | OnTypeFormattingFunc func(client Client, params *protocol.DocumentOnTypeFormattingParams) (result []protocol.TextEdit, err error) 54 | PrepareRenameFunc func(client Client, params *protocol.PrepareRenameParams) (result *protocol.Range, err error) 55 | RangeFormattingFunc func(client Client, params *protocol.DocumentRangeFormattingParams) (result []protocol.TextEdit, err error) 56 | ReferencesFunc func(client Client, params *protocol.ReferenceParams) (result []protocol.Location, err error) 57 | RenameFunc func(client Client, params *protocol.RenameParams) (result *protocol.WorkspaceEdit, err error) 58 | SignatureHelpFunc func(client Client, params *protocol.SignatureHelpParams) (result *protocol.SignatureHelp, err error) 59 | SymbolsFunc func(client Client, params *protocol.WorkspaceSymbolParams) (result []protocol.SymbolInformation, err error) 60 | TypeDefinitionFunc func(client Client, params *protocol.TypeDefinitionParams) (result []protocol.Location, err error) 61 | WillSaveFunc func(client Client, params *protocol.WillSaveTextDocumentParams) (err error) 62 | WillSaveWaitUntilFunc func(client Client, params *protocol.WillSaveTextDocumentParams) (result []protocol.TextEdit, err error) 63 | ShowDocumentFunc func(client Client, params *protocol.ShowDocumentParams) (result *protocol.ShowDocumentResult, err error) 64 | WillCreateFilesFunc func(client Client, params *protocol.CreateFilesParams) (result *protocol.WorkspaceEdit, err error) 65 | DidCreateFilesFunc func(client Client, params *protocol.CreateFilesParams) (err error) 66 | WillRenameFilesFunc func(client Client, params *protocol.RenameFilesParams) (result *protocol.WorkspaceEdit, err error) 67 | DidRenameFilesFunc func(client Client, params *protocol.RenameFilesParams) (err error) 68 | WillDeleteFilesFunc func(client Client, params *protocol.DeleteFilesParams) (result *protocol.WorkspaceEdit, err error) 69 | DidDeleteFilesFunc func(client Client, params *protocol.DeleteFilesParams) (err error) 70 | CodeLensRefreshFunc func(client Client) (err error) 71 | PrepareCallHierarchyFunc func(client Client, params *protocol.CallHierarchyPrepareParams) (result []protocol.CallHierarchyItem, err error) 72 | IncomingCallsFunc func(client Client, params *protocol.CallHierarchyIncomingCallsParams) (result []protocol.CallHierarchyIncomingCall, err error) 73 | OutgoingCallsFunc func(client Client, params *protocol.CallHierarchyOutgoingCallsParams) (result []protocol.CallHierarchyOutgoingCall, err error) 74 | SemanticTokensFullFunc func(client Client, params *protocol.SemanticTokensParams) (result *protocol.SemanticTokens, err error) 75 | SemanticTokensFullDeltaFunc func(client Client, params *protocol.SemanticTokensDeltaParams) (result interface{}, err error) 76 | SemanticTokensRangeFunc func(client Client, params *protocol.SemanticTokensRangeParams) (result *protocol.SemanticTokens, err error) 77 | SemanticTokensRefreshFunc func(client Client) (err error) 78 | LinkedEditingRangeFunc func(client Client, params *protocol.LinkedEditingRangeParams) (result *protocol.LinkedEditingRanges, err error) 79 | MonikerFunc func(client Client, params *protocol.MonikerParams) (result []protocol.Moniker, err error) 80 | RequestFunc func(client Client, method string, params interface{}) (result interface{}, err error) 81 | } 82 | 83 | // Guess what capabilities should be enabled from what functions are registered. 84 | // 85 | // NOTE: To be accurate, this method should be called on an otherwise fully initialized *Methods. 86 | // NOTE: This function will panic if a `InitializeFunc` is already set. 87 | func (m Methods) DefaultInitializer(name, version string) *Methods { 88 | contract.Assertf(m.InitializeFunc == nil, "Won't override an already set initializer") 89 | m.InitializeFunc = func(client Client, params *protocol.InitializeParams) (*protocol.InitializeResult, error) { 90 | var completion *protocol.CompletionOptions 91 | if m.CompletionFunc != nil || m.CompletionResolveFunc != nil { 92 | completion = &protocol.CompletionOptions{ 93 | ResolveProvider: m.CompletionResolveFunc != nil, 94 | TriggerCharacters: []string{":"}, // TODO: How should this be provided 95 | } 96 | } 97 | var hover *protocol.HoverOptions 98 | if m.HoverFunc != nil { 99 | hover = &protocol.HoverOptions{ 100 | WorkDoneProgressOptions: protocol.WorkDoneProgressOptions{ 101 | WorkDoneProgress: m.WorkDoneProgressCancelFunc != nil, 102 | }, 103 | } 104 | } 105 | var codeAction *protocol.CodeActionOptions 106 | if m.CodeActionFunc != nil { 107 | codeAction = &protocol.CodeActionOptions{ 108 | CodeActionKinds: []protocol.CodeActionKind{ 109 | // TODO: how do we let the user communicate this. 110 | protocol.RefactorRewrite, 111 | }, 112 | ResolveProvider: false, 113 | } 114 | } 115 | return &protocol.InitializeResult{ 116 | Capabilities: protocol.ServerCapabilities{ 117 | TextDocumentSync: &protocol.TextDocumentSyncOptions{ 118 | OpenClose: m.DidOpenFunc != nil || m.DidCloseFunc != nil, 119 | Change: protocol.TextDocumentSyncKindIncremental, // TODO: Can we figure out how do derive this 120 | WillSave: m.WillSaveFunc != nil, 121 | WillSaveWaitUntil: m.WillSaveWaitUntilFunc != nil, 122 | Save: &protocol.SaveOptions{ 123 | IncludeText: false, // TODO: how should this information be passed 124 | }, 125 | }, 126 | CompletionProvider: completion, 127 | HoverProvider: hover, 128 | // SignatureHelpProvider: &protocol.SignatureHelpOptions{}, 129 | // DeclarationProvider: nil, 130 | // DefinitionProvider: nil, 131 | // TypeDefinitionProvider: nil, 132 | // ImplementationProvider: nil, 133 | // ReferencesProvider: nil, 134 | // DocumentHighlightProvider: nil, 135 | // DocumentSymbolProvider: nil, 136 | CodeActionProvider: codeAction, 137 | // CodeLensProvider: &protocol.CodeLensOptions{}, 138 | // DocumentLinkProvider: &protocol.DocumentLinkOptions{}, 139 | // ColorProvider: nil, 140 | // WorkspaceSymbolProvider: nil, 141 | // DocumentFormattingProvider: nil, 142 | // DocumentRangeFormattingProvider: nil, 143 | // DocumentOnTypeFormattingProvider: &protocol.DocumentOnTypeFormattingOptions{}, 144 | // RenameProvider: name, 145 | // FoldingRangeProvider: nil, 146 | // SelectionRangeProvider: nil, 147 | // ExecuteCommandProvider: &protocol.ExecuteCommandOptions{}, 148 | // CallHierarchyProvider: nil, 149 | // LinkedEditingRangeProvider: nil, 150 | // SemanticTokensProvider: nil, 151 | // Workspace: &protocol.ServerCapabilitiesWorkspace{}, 152 | // MonikerProvider: m, 153 | // Experimental: nil, 154 | }, 155 | ServerInfo: &protocol.ServerInfo{ 156 | Name: name, 157 | Version: version, 158 | }, 159 | }, nil 160 | } 161 | return &m 162 | } 163 | 164 | func (m *methods) client(ctx context.Context) Client { 165 | return Client{ 166 | inner: m.server.client, 167 | ctx: ctx, 168 | } 169 | } 170 | 171 | func (m *Methods) serve() *methods { 172 | return &methods{m} 173 | } 174 | 175 | // The actual implementer of the protocol.Server trait. We do this to prevent 176 | // calling a method on `Methods`, and to keep auto-complete uncluttered. 177 | type methods struct { 178 | *Methods 179 | } 180 | 181 | func (m *methods) warnUninitialized(name string) { 182 | m.server.Logger.Debugf("'%s' was called but no handler was provided", name) 183 | } 184 | 185 | func (m *methods) Initialize(ctx context.Context, params *protocol.InitializeParams) (result *protocol.InitializeResult, err error) { 186 | if m.InitializeFunc != nil { 187 | result, err = m.InitializeFunc(m.client(ctx), params) 188 | } else { 189 | m.warnUninitialized("initialize") 190 | } 191 | m.server.isInitialized = true 192 | return 193 | } 194 | func (m *methods) Initialized(ctx context.Context, params *protocol.InitializedParams) (err error) { 195 | if m.InitializedFunc != nil { 196 | err = m.InitializedFunc(m.client(ctx), params) 197 | } else { 198 | m.warnUninitialized("initialized") 199 | } 200 | return 201 | } 202 | func (m *methods) Shutdown(ctx context.Context) (err error) { 203 | if m.ShutdownFunc != nil { 204 | err = m.ShutdownFunc(m.client(ctx)) 205 | } else { 206 | m.warnUninitialized("shutdown") 207 | } 208 | return 209 | } 210 | func (m *methods) Exit(ctx context.Context) (err error) { 211 | if m.ExitFunc != nil { 212 | err = m.ExitFunc(m.client(ctx)) 213 | } else { 214 | m.warnUninitialized("exit") 215 | } 216 | m.closer <- struct{}{} 217 | return 218 | } 219 | func (m *methods) WorkDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) (err error) { 220 | if m.WorkDoneProgressCancelFunc != nil { 221 | err = m.WorkDoneProgressCancelFunc(m.client(ctx), params) 222 | } else { 223 | m.warnUninitialized("*methods) workDoneProgressCancel") 224 | } 225 | return 226 | } 227 | func (m *methods) LogTrace(ctx context.Context, params *protocol.LogTraceParams) (err error) { 228 | if m.LogTraceFunc != nil { 229 | err = m.LogTraceFunc(m.client(ctx), params) 230 | } else { 231 | m.warnUninitialized("logTrace") 232 | } 233 | return 234 | } 235 | func (m *methods) SetTrace(ctx context.Context, params *protocol.SetTraceParams) (err error) { 236 | if m.SetTraceFunc != nil { 237 | err = m.SetTraceFunc(m.client(ctx), params) 238 | } else { 239 | m.warnUninitialized("setTrace") 240 | } 241 | return 242 | } 243 | func (m *methods) CodeAction(ctx context.Context, params *protocol.CodeActionParams) (result []protocol.CodeAction, err error) { 244 | if m.CodeActionFunc != nil { 245 | result, err = m.CodeActionFunc(m.client(ctx), params) 246 | } else { 247 | m.warnUninitialized("codeAction") 248 | } 249 | return 250 | } 251 | func (m *methods) CodeLens(ctx context.Context, params *protocol.CodeLensParams) (result []protocol.CodeLens, err error) { 252 | if m.CodeLensFunc != nil { 253 | result, err = m.CodeLensFunc(m.client(ctx), params) 254 | } else { 255 | m.warnUninitialized("codeLens") 256 | } 257 | return 258 | } 259 | func (m *methods) CodeLensResolve(ctx context.Context, params *protocol.CodeLens) (result *protocol.CodeLens, err error) { 260 | if m.CodeLensResolveFunc != nil { 261 | result, err = m.CodeLensResolveFunc(m.client(ctx), params) 262 | } else { 263 | m.warnUninitialized("codeLensResolve") 264 | } 265 | return 266 | } 267 | func (m *methods) ColorPresentation(ctx context.Context, params *protocol.ColorPresentationParams) (result []protocol.ColorPresentation, err error) { 268 | if m.ColorPresentationFunc != nil { 269 | result, err = m.ColorPresentationFunc(m.client(ctx), params) 270 | } else { 271 | m.warnUninitialized("colorPresentation") 272 | } 273 | return 274 | } 275 | func (m *methods) Completion(ctx context.Context, params *protocol.CompletionParams) (result *protocol.CompletionList, err error) { 276 | if m.CompletionFunc != nil { 277 | result, err = m.CompletionFunc(m.client(ctx), params) 278 | } else { 279 | m.warnUninitialized("completion") 280 | } 281 | return 282 | } 283 | func (m *methods) CompletionResolve(ctx context.Context, params *protocol.CompletionItem) (result *protocol.CompletionItem, err error) { 284 | if m.CompletionResolveFunc != nil { 285 | result, err = m.CompletionResolveFunc(m.client(ctx), params) 286 | } else { 287 | m.warnUninitialized("completionResolve") 288 | } 289 | return 290 | } 291 | func (m *methods) Declaration(ctx context.Context, params *protocol.DeclarationParams) (result []protocol.Location, err error) { 292 | if m.DeclarationFunc != nil { 293 | result, err = m.DeclarationFunc(m.client(ctx), params) 294 | } else { 295 | m.warnUninitialized("declaration") 296 | } 297 | return 298 | } 299 | func (m *methods) Definition(ctx context.Context, params *protocol.DefinitionParams) (result []protocol.Location, err error) { 300 | if m.DefinitionFunc != nil { 301 | result, err = m.DefinitionFunc(m.client(ctx), params) 302 | } else { 303 | m.warnUninitialized("definition") 304 | } 305 | return 306 | } 307 | func (m *methods) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) (err error) { 308 | if m.DidChangeFunc != nil { 309 | err = m.DidChangeFunc(m.client(ctx), params) 310 | } else { 311 | m.warnUninitialized("didChange") 312 | } 313 | return 314 | } 315 | func (m *methods) DidChangeConfiguration(ctx context.Context, params *protocol.DidChangeConfigurationParams) (err error) { 316 | if m.DidChangeConfigurationFunc != nil { 317 | err = m.DidChangeConfigurationFunc(m.client(ctx), params) 318 | } else { 319 | m.warnUninitialized("didChangeConfiguration") 320 | } 321 | return 322 | } 323 | func (m *methods) DidChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) (err error) { 324 | if m.DidChangeWatchedFilesFunc != nil { 325 | err = m.DidChangeWatchedFilesFunc(m.client(ctx), params) 326 | } else { 327 | m.warnUninitialized("didChangeWatchedFiles") 328 | } 329 | return 330 | } 331 | func (m *methods) DidChangeWorkspaceFolders(ctx context.Context, params *protocol.DidChangeWorkspaceFoldersParams) (err error) { 332 | if m.DidChangeWorkspaceFoldersFunc != nil { 333 | err = m.DidChangeWorkspaceFoldersFunc(m.client(ctx), params) 334 | } else { 335 | m.warnUninitialized("didChangeWorkspaceFolders") 336 | } 337 | return 338 | } 339 | func (m *methods) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) (err error) { 340 | if m.DidCloseFunc != nil { 341 | err = m.DidCloseFunc(m.client(ctx), params) 342 | } else { 343 | m.warnUninitialized("didClose") 344 | } 345 | return 346 | } 347 | func (m *methods) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) (err error) { 348 | if m.DidOpenFunc != nil { 349 | err = m.DidOpenFunc(m.client(ctx), params) 350 | } else { 351 | m.warnUninitialized("didOpen") 352 | } 353 | return 354 | } 355 | func (m *methods) DidSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) (err error) { 356 | if m.DidSaveFunc != nil { 357 | err = m.DidSaveFunc(m.client(ctx), params) 358 | } else { 359 | m.warnUninitialized("didSave") 360 | } 361 | return 362 | } 363 | func (m *methods) DocumentColor(ctx context.Context, params *protocol.DocumentColorParams) (result []protocol.ColorInformation, err error) { 364 | if m.DocumentColorFunc != nil { 365 | result, err = m.DocumentColorFunc(m.client(ctx), params) 366 | } else { 367 | m.warnUninitialized("documentColor") 368 | } 369 | return 370 | } 371 | func (m *methods) DocumentHighlight(ctx context.Context, params *protocol.DocumentHighlightParams) (result []protocol.DocumentHighlight, err error) { 372 | if m.DocumentHighlightFunc != nil { 373 | result, err = m.DocumentHighlightFunc(m.client(ctx), params) 374 | } else { 375 | m.warnUninitialized("documentHighlight") 376 | } 377 | return 378 | } 379 | func (m *methods) DocumentLink(ctx context.Context, params *protocol.DocumentLinkParams) (result []protocol.DocumentLink, err error) { 380 | if m.DocumentLinkFunc != nil { 381 | result, err = m.DocumentLinkFunc(m.client(ctx), params) 382 | } else { 383 | m.warnUninitialized("documentLink") 384 | } 385 | return 386 | } 387 | func (m *methods) DocumentLinkResolve(ctx context.Context, params *protocol.DocumentLink) (result *protocol.DocumentLink, err error) { 388 | if m.DocumentLinkResolveFunc != nil { 389 | result, err = m.DocumentLinkResolveFunc(m.client(ctx), params) 390 | } else { 391 | m.warnUninitialized("documentLinkResolve") 392 | } 393 | return 394 | } 395 | func (m *methods) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) (result []interface{}, err error) { 396 | if m.DocumentSymbolFunc != nil { 397 | result, err = m.DocumentSymbolFunc(m.client(ctx), params) 398 | } else { 399 | m.warnUninitialized("documentSymbol") 400 | } 401 | return 402 | } 403 | func (m *methods) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (result interface{}, err error) { 404 | if m.ExecuteCommandFunc != nil { 405 | result, err = m.ExecuteCommandFunc(m.client(ctx), params) 406 | } else { 407 | m.warnUninitialized("executeCommand") 408 | } 409 | return 410 | } 411 | func (m *methods) FoldingRanges(ctx context.Context, params *protocol.FoldingRangeParams) (result []protocol.FoldingRange, err error) { 412 | if m.FoldingRangesFunc != nil { 413 | result, err = m.FoldingRangesFunc(m.client(ctx), params) 414 | } else { 415 | m.warnUninitialized("foldingRanges") 416 | } 417 | return 418 | } 419 | func (m *methods) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) (result []protocol.TextEdit, err error) { 420 | if m.FormattingFunc != nil { 421 | result, err = m.FormattingFunc(m.client(ctx), params) 422 | } else { 423 | m.warnUninitialized("formatting") 424 | } 425 | return 426 | } 427 | func (m *methods) Hover(ctx context.Context, params *protocol.HoverParams) (result *protocol.Hover, err error) { 428 | if m.HoverFunc != nil { 429 | result, err = m.HoverFunc(m.client(ctx), params) 430 | } else { 431 | m.warnUninitialized("hover") 432 | } 433 | return 434 | } 435 | func (m *methods) Implementation(ctx context.Context, params *protocol.ImplementationParams) (result []protocol.Location, err error) { 436 | if m.ImplementationFunc != nil { 437 | result, err = m.ImplementationFunc(m.client(ctx), params) 438 | } else { 439 | m.warnUninitialized("implementation") 440 | } 441 | return 442 | } 443 | func (m *methods) OnTypeFormatting(ctx context.Context, params *protocol.DocumentOnTypeFormattingParams) (result []protocol.TextEdit, err error) { 444 | if m.OnTypeFormattingFunc != nil { 445 | result, err = m.OnTypeFormattingFunc(m.client(ctx), params) 446 | } else { 447 | m.warnUninitialized("onTypeFormatting") 448 | } 449 | return 450 | } 451 | func (m *methods) PrepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (result *protocol.Range, err error) { 452 | if m.PrepareRenameFunc != nil { 453 | result, err = m.PrepareRenameFunc(m.client(ctx), params) 454 | } else { 455 | m.warnUninitialized("prepareRename") 456 | } 457 | return 458 | } 459 | func (m *methods) RangeFormatting(ctx context.Context, params *protocol.DocumentRangeFormattingParams) (result []protocol.TextEdit, err error) { 460 | if m.RangeFormattingFunc != nil { 461 | result, err = m.RangeFormattingFunc(m.client(ctx), params) 462 | } else { 463 | m.warnUninitialized("rangeFormatting") 464 | } 465 | return 466 | } 467 | func (m *methods) References(ctx context.Context, params *protocol.ReferenceParams) (result []protocol.Location, err error) { 468 | if m.ReferencesFunc != nil { 469 | result, err = m.ReferencesFunc(m.client(ctx), params) 470 | } else { 471 | m.warnUninitialized("references") 472 | } 473 | return 474 | } 475 | func (m *methods) Rename(ctx context.Context, params *protocol.RenameParams) (result *protocol.WorkspaceEdit, err error) { 476 | if m.RenameFunc != nil { 477 | result, err = m.RenameFunc(m.client(ctx), params) 478 | } else { 479 | m.warnUninitialized("rename") 480 | } 481 | return 482 | } 483 | func (m *methods) SignatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (result *protocol.SignatureHelp, err error) { 484 | if m.SignatureHelpFunc != nil { 485 | result, err = m.SignatureHelpFunc(m.client(ctx), params) 486 | } else { 487 | m.warnUninitialized("signatureHelp") 488 | } 489 | return 490 | } 491 | func (m *methods) Symbols(ctx context.Context, params *protocol.WorkspaceSymbolParams) (result []protocol.SymbolInformation, err error) { 492 | if m.SymbolsFunc != nil { 493 | result, err = m.SymbolsFunc(m.client(ctx), params) 494 | } else { 495 | m.warnUninitialized("symbols") 496 | } 497 | return 498 | } 499 | func (m *methods) TypeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) (result []protocol.Location, err error) { 500 | if m.TypeDefinitionFunc != nil { 501 | result, err = m.TypeDefinitionFunc(m.client(ctx), params) 502 | } else { 503 | m.warnUninitialized("typeDefinition") 504 | } 505 | return 506 | } 507 | func (m *methods) WillSave(ctx context.Context, params *protocol.WillSaveTextDocumentParams) (err error) { 508 | if m.WillSaveFunc != nil { 509 | err = m.WillSaveFunc(m.client(ctx), params) 510 | } else { 511 | m.warnUninitialized("willSave") 512 | } 513 | return 514 | } 515 | func (m *methods) WillSaveWaitUntil(ctx context.Context, params *protocol.WillSaveTextDocumentParams) (result []protocol.TextEdit, err error) { 516 | if m.WillSaveWaitUntilFunc != nil { 517 | result, err = m.WillSaveWaitUntilFunc(m.client(ctx), params) 518 | } else { 519 | m.warnUninitialized("willSaveWaitUntil") 520 | } 521 | return 522 | } 523 | func (m *methods) ShowDocument(ctx context.Context, params *protocol.ShowDocumentParams) (result *protocol.ShowDocumentResult, err error) { 524 | if m.ShowDocumentFunc != nil { 525 | result, err = m.ShowDocumentFunc(m.client(ctx), params) 526 | } else { 527 | m.warnUninitialized("showDocument") 528 | } 529 | return 530 | } 531 | func (m *methods) WillCreateFiles(ctx context.Context, params *protocol.CreateFilesParams) (result *protocol.WorkspaceEdit, err error) { 532 | if m.WillCreateFilesFunc != nil { 533 | result, err = m.WillCreateFilesFunc(m.client(ctx), params) 534 | } else { 535 | m.warnUninitialized("willCreateFiles") 536 | } 537 | return 538 | } 539 | func (m *methods) DidCreateFiles(ctx context.Context, params *protocol.CreateFilesParams) (err error) { 540 | if m.DidCreateFilesFunc != nil { 541 | err = m.DidCreateFilesFunc(m.client(ctx), params) 542 | } else { 543 | m.warnUninitialized("didCreateFiles") 544 | } 545 | return 546 | } 547 | func (m *methods) WillRenameFiles(ctx context.Context, params *protocol.RenameFilesParams) (result *protocol.WorkspaceEdit, err error) { 548 | if m.WillRenameFilesFunc != nil { 549 | result, err = m.WillRenameFilesFunc(m.client(ctx), params) 550 | } else { 551 | m.warnUninitialized("willRenameFiles") 552 | } 553 | return 554 | } 555 | func (m *methods) DidRenameFiles(ctx context.Context, params *protocol.RenameFilesParams) (err error) { 556 | if m.DidRenameFilesFunc != nil { 557 | err = m.DidRenameFilesFunc(m.client(ctx), params) 558 | } else { 559 | m.warnUninitialized("didRenameFiles") 560 | } 561 | return 562 | } 563 | func (m *methods) WillDeleteFiles(ctx context.Context, params *protocol.DeleteFilesParams) (result *protocol.WorkspaceEdit, err error) { 564 | if m.WillDeleteFilesFunc != nil { 565 | result, err = m.WillDeleteFilesFunc(m.client(ctx), params) 566 | } else { 567 | m.warnUninitialized("willDeleteFiles") 568 | } 569 | return 570 | } 571 | func (m *methods) DidDeleteFiles(ctx context.Context, params *protocol.DeleteFilesParams) (err error) { 572 | if m.DidDeleteFilesFunc != nil { 573 | err = m.DidDeleteFilesFunc(m.client(ctx), params) 574 | } else { 575 | m.warnUninitialized("didDeleteFiles") 576 | } 577 | return 578 | } 579 | func (m *methods) CodeLensRefresh(ctx context.Context) (err error) { 580 | if m.CodeLensRefreshFunc != nil { 581 | err = m.CodeLensRefreshFunc(m.client(ctx)) 582 | } else { 583 | m.warnUninitialized("codeLensRefresh") 584 | } 585 | return 586 | } 587 | func (m *methods) PrepareCallHierarchy(ctx context.Context, params *protocol.CallHierarchyPrepareParams) (result []protocol.CallHierarchyItem, err error) { 588 | if m.PrepareCallHierarchyFunc != nil { 589 | result, err = m.PrepareCallHierarchyFunc(m.client(ctx), params) 590 | } else { 591 | m.warnUninitialized("prepareCallHierarchy") 592 | } 593 | return 594 | } 595 | func (m *methods) IncomingCalls(ctx context.Context, params *protocol.CallHierarchyIncomingCallsParams) (result []protocol.CallHierarchyIncomingCall, err error) { 596 | if m.IncomingCallsFunc != nil { 597 | result, err = m.IncomingCallsFunc(m.client(ctx), params) 598 | } else { 599 | m.warnUninitialized("incomingCalls") 600 | } 601 | return 602 | } 603 | func (m *methods) OutgoingCalls(ctx context.Context, params *protocol.CallHierarchyOutgoingCallsParams) (result []protocol.CallHierarchyOutgoingCall, err error) { 604 | if m.OutgoingCallsFunc != nil { 605 | result, err = m.OutgoingCallsFunc(m.client(ctx), params) 606 | } else { 607 | m.warnUninitialized("outgoingCalls") 608 | } 609 | return 610 | } 611 | func (m *methods) SemanticTokensFull(ctx context.Context, params *protocol.SemanticTokensParams) (result *protocol.SemanticTokens, err error) { 612 | if m.SemanticTokensFullFunc != nil { 613 | result, err = m.SemanticTokensFullFunc(m.client(ctx), params) 614 | } else { 615 | m.warnUninitialized("semanticTokensFull") 616 | } 617 | return 618 | } 619 | func (m *methods) SemanticTokensFullDelta(ctx context.Context, params *protocol.SemanticTokensDeltaParams) (result interface{}, err error) { 620 | if m.SemanticTokensFullDeltaFunc != nil { 621 | result, err = m.SemanticTokensFullDeltaFunc(m.client(ctx), params) 622 | } else { 623 | m.warnUninitialized("semanticTokensFullDelta") 624 | } 625 | return 626 | } 627 | func (m *methods) SemanticTokensRange(ctx context.Context, params *protocol.SemanticTokensRangeParams) (result *protocol.SemanticTokens, err error) { 628 | if m.SemanticTokensRangeFunc != nil { 629 | result, err = m.SemanticTokensRangeFunc(m.client(ctx), params) 630 | } else { 631 | m.warnUninitialized("semanticTokensRange") 632 | } 633 | return 634 | } 635 | func (m *methods) SemanticTokensRefresh(ctx context.Context) (err error) { 636 | if m.SemanticTokensRefreshFunc != nil { 637 | err = m.SemanticTokensRefreshFunc(m.client(ctx)) 638 | } else { 639 | m.warnUninitialized("semanticTokensRefresh") 640 | } 641 | return 642 | } 643 | func (m *methods) LinkedEditingRange(ctx context.Context, params *protocol.LinkedEditingRangeParams) (result *protocol.LinkedEditingRanges, err error) { 644 | if m.LinkedEditingRangeFunc != nil { 645 | result, err = m.LinkedEditingRangeFunc(m.client(ctx), params) 646 | } else { 647 | m.warnUninitialized("linkedEditingRange") 648 | } 649 | return 650 | } 651 | func (m *methods) Moniker(ctx context.Context, params *protocol.MonikerParams) (result []protocol.Moniker, err error) { 652 | if m.MonikerFunc != nil { 653 | result, err = m.MonikerFunc(m.client(ctx), params) 654 | } else { 655 | m.warnUninitialized("moniker") 656 | } 657 | return 658 | } 659 | func (m *methods) Request(ctx context.Context, method string, params interface{}) (result interface{}, err error) { 660 | if m.RequestFunc != nil { 661 | result, err = m.RequestFunc(m.client(ctx), method, params) 662 | } else { 663 | m.warnUninitialized("request") 664 | } 665 | return 666 | } 667 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= 2 | dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 4 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 5 | github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= 6 | github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= 7 | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 8 | github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= 9 | github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= 10 | github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= 11 | github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= 12 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= 13 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= 14 | github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= 15 | github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 16 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= 17 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 18 | github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= 19 | github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= 20 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 21 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 22 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= 23 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 24 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 25 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 26 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 27 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 28 | github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= 29 | github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 30 | github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= 31 | github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= 32 | github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= 33 | github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= 34 | github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= 35 | github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= 36 | github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= 37 | github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= 38 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 39 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 40 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= 41 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 42 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 43 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 44 | github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= 45 | github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 46 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 47 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 48 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 49 | github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU= 50 | github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0= 51 | github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= 52 | github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= 53 | github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= 54 | github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= 55 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 56 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 57 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 58 | github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= 59 | github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 60 | github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= 61 | github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= 62 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 63 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 64 | github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= 65 | github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= 66 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= 67 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= 68 | github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= 69 | github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= 70 | github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= 71 | github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 72 | github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= 73 | github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 74 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 75 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 76 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 77 | github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= 78 | github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= 79 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 80 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 81 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 82 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 83 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 84 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 85 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 86 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 87 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 88 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 89 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 90 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= 91 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= 92 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 93 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 94 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 95 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 96 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 97 | github.com/hashicorp/hcl/v2 v2.17.0 h1:z1XvSUyXd1HP10U4lrLg5e0JMVz6CPaJvAgxM0KNZVY= 98 | github.com/hashicorp/hcl/v2 v2.17.0/go.mod h1:gJyW2PTShkJqQBKpAmPO3yxMxIuoXkOF2TpqXzrQyx4= 99 | github.com/hexops/autogold v1.3.0 h1:IEtGNPxBeBu8RMn8eKWh/Ll9dVNgSnJ7bp/qHgMQ14o= 100 | github.com/hexops/autogold v1.3.0/go.mod h1:d4hwi2rid66Sag+BVuHgwakW/EmaFr8vdTSbWDbrDRI= 101 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 102 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 103 | github.com/hexops/valast v1.4.4 h1:rETyycw+/L2ZVJHHNxEBgh8KUn+87WugH9MxcEv9PGs= 104 | github.com/hexops/valast v1.4.4/go.mod h1:Jcy1pNH7LNraVaAZDLyv21hHg2WBv9Nf9FL6fGxU7o4= 105 | github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= 106 | github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 107 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 108 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 109 | github.com/iwahbe/helpmakego v0.2.0 h1:ZpqfUhfw1Y5qn/08LT4OLYRWHWqHzag4Ang0glszFQk= 110 | github.com/iwahbe/helpmakego v0.2.0/go.mod h1:SNrBTLB/hEwr4EzMfcoMFVBGP9wQfJzSDF6PWZn4qac= 111 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 112 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 113 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 114 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 115 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 116 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 117 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 118 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 119 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 120 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 121 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 122 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 123 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 124 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 125 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 126 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 127 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 128 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 129 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 130 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 131 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 132 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 133 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 134 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 135 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 136 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 137 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 138 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 139 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 140 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 141 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 142 | github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= 143 | github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= 144 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 145 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 146 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 147 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 148 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 149 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 150 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 151 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 152 | github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= 153 | github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= 154 | github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= 155 | github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= 156 | github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= 157 | github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= 158 | github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= 159 | github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= 160 | github.com/opentracing/basictracer-go v1.1.0 h1:Oa1fTSBvAl8pa3U+IJYqrKm0NALwH9OsgwOqDv4xJW0= 161 | github.com/opentracing/basictracer-go v1.1.0/go.mod h1:V2HZueSJEp879yv285Aap1BS69fQMD+MNP1mRs6mBQc= 162 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 163 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 164 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 165 | github.com/pgavlin/fx v0.1.6 h1:r9jEg69DhNoCd3Xh0+5mIbdbS3PqWrVWujkY76MFRTU= 166 | github.com/pgavlin/fx v0.1.6/go.mod h1:KWZJ6fqBBSh8GxHYqwYCf3rYE7Gp2p0N8tJp8xv9u9M= 167 | github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386 h1:LoCV5cscNVWyK5ChN/uCoIFJz8jZD63VQiGJIRgr6uo= 168 | github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386/go.mod h1:MRxHTJrf9FhdfNQ8Hdeh9gmHevC9RJE/fu8M3JIGjoE= 169 | github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= 170 | github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= 171 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 172 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 173 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 174 | github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= 175 | github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= 176 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 177 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 178 | github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 h1:vkHw5I/plNdTr435cARxCW6q9gc0S/Yxz7Mkd38pOb0= 179 | github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231/go.mod h1:murToZ2N9hNJzewjHBgfFdXhZKjY3z5cYC1VXk+lbFE= 180 | github.com/pulumi/esc v0.9.2-0.20240910221656-328d3204100f h1:0ZdVkYWt8zqyfDhjy7Q6V8ydWVvmmyCTRFTFOrvYPaM= 181 | github.com/pulumi/esc v0.9.2-0.20240910221656-328d3204100f/go.mod h1:2Bfa+FWj/xl8CKqRTWbWgDX0SOD4opdQgvYSURTGK2c= 182 | github.com/pulumi/pulumi-yaml v1.10.1 h1:BV/Rm57FiBPzb3PjbK4k1mky0vke1IIiaT2s117Ejng= 183 | github.com/pulumi/pulumi-yaml v1.10.1/go.mod h1:MFMQXkaUP5YQUKVJ6Z/aagZDl2f8hdU9oGaJfTcMf1Y= 184 | github.com/pulumi/pulumi/pkg/v3 v3.132.0 h1:FbIvkiFMOYwAixQZ330w42b0khMeAYj+vlH3k4lxwA8= 185 | github.com/pulumi/pulumi/pkg/v3 v3.132.0/go.mod h1:EnftmBaN59wQI//Q4a6XN3QUlHA4SiGBup8TXC8iT3s= 186 | github.com/pulumi/pulumi/sdk/v3 v3.132.0 h1:UDZHa+WpIEVxWJp+PCbsMxnTUeKMX3xBCcqIHxeAdzI= 187 | github.com/pulumi/pulumi/sdk/v3 v3.132.0/go.mod h1:J5kQEX8v87aeUhk6NdQXnjCo1DbiOnOiL3Sf2DuDda8= 188 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 189 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 190 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= 191 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 192 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 193 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 194 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 195 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= 196 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= 197 | github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE= 198 | github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= 199 | github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= 200 | github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= 201 | github.com/segmentio/encoding v0.3.5 h1:UZEiaZ55nlXGDL92scoVuw00RmiRCazIEmvPSbSvt8Y= 202 | github.com/segmentio/encoding v0.3.5/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM= 203 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= 204 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 205 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 206 | github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= 207 | github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= 208 | github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= 209 | github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 210 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 211 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 212 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 213 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 214 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 215 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 216 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 217 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 218 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 219 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 220 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 221 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 222 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 223 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 224 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 225 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 226 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 227 | github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= 228 | github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= 229 | github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= 230 | github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= 231 | github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= 232 | github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= 233 | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 234 | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 235 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 236 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 237 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 238 | github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0= 239 | github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= 240 | go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI= 241 | go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac= 242 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE= 243 | go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2/go.mod h1:gtSHRuYfbCT0qnbLnovpie/WEmqyJ7T4n6VXiFMBtcw= 244 | go.lsp.dev/protocol v0.12.0 h1:tNprUI9klQW5FAFVM4Sa+AbPFuVQByWhP1ttNUAjIWg= 245 | go.lsp.dev/protocol v0.12.0/go.mod h1:Qb11/HgZQ72qQbeyPfJbu3hZBH23s1sr4st8czGeDMQ= 246 | go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= 247 | go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= 248 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 249 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 250 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 251 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 252 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 253 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 254 | go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= 255 | go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 256 | go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= 257 | go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= 258 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 259 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 260 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 261 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 262 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 263 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 264 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= 265 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 266 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 267 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 268 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 269 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 270 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 271 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 272 | golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= 273 | golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 274 | golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= 275 | golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 276 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 277 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 278 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 279 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 280 | golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 281 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 282 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 283 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 284 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 285 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 286 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 287 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 288 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 289 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 290 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 291 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 292 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 293 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 294 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 295 | golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 296 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 297 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 298 | golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 299 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 300 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 305 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 306 | golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 307 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 308 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 309 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 310 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 311 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 312 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 313 | golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= 314 | golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 315 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 316 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 317 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 318 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 319 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 320 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 321 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 322 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 323 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 324 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 325 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 326 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 327 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 328 | golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= 329 | golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= 330 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 331 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 332 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 333 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 334 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240311173647-c811ad7063a7 h1:8EeVk1VKMD+GD/neyEHGmz7pFblqPjHoi+PGQIlLx2s= 335 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240311173647-c811ad7063a7/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= 336 | google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= 337 | google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= 338 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 339 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 340 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 341 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 342 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 343 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 344 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 345 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 346 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 347 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 348 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 349 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 350 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 351 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 352 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 353 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 354 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 355 | lukechampine.com/frand v1.4.2 h1:RzFIpOvkMXuPMBb9maa4ND4wjBn71E1Jpf8BzJHMaVw= 356 | lukechampine.com/frand v1.4.2/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s= 357 | mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= 358 | mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= 359 | pgregory.net/rapid v0.6.1 h1:4eyrDxyht86tT4Ztm+kvlyNBLIk071gR+ZQdhphc9dQ= 360 | pgregory.net/rapid v0.6.1/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= 361 | --------------------------------------------------------------------------------