From 45dacf41327cb2056d0b5a017466c353e5152015 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Tue, 15 Jul 2025 10:46:52 +0200 Subject: [PATCH 1/9] feat: add Nix flake for Go 1.23 development environment --- flake.nix | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 flake.nix diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..7a187eb1 --- /dev/null +++ b/flake.nix @@ -0,0 +1,57 @@ +{ + description = "A Nix-flake-based Go 1.23 development environment"; + + inputs = { + nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1.*.tar.gz"; + + nur = { + url = "github:nix-community/NUR"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, nur }: + let + goVersion = 23; + + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + forEachSupportedSystem = f: + nixpkgs.lib.genAttrs supportedSystems (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ self.overlays.default nur.overlays.default ]; + config.allowUnfree = true; + }; + in + f { pkgs = pkgs; system = system; } + ); + in + { + overlays.default = final: prev: { + go = final."go_1_${toString goVersion}"; + }; + + devShells = forEachSupportedSystem ({ pkgs, system }: + { + default = pkgs.mkShell { + packages = with pkgs; [ + go + gotools + golangci-lint + ginkgo + pkgs.nur.repos.goreleaser.goreleaser-pro + just + goperf + ]; + }; + } + ); + }; +} \ No newline at end of file From a1c0f96e6c687cb4f12dd49b8b9e1d4d81de5c71 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Tue, 15 Jul 2025 10:48:33 +0200 Subject: [PATCH 2/9] feat: add .envrc and Justfile for environment setup; remove obsolete scripts --- .envrc | 2 ++ Justfile | 31 +++++++++++++++++++++ flake.lock | 68 ++++++++++++++++++++++++++++++++++++++++++++++ generate-cover.sh | 2 -- generate-parser.sh | 2 -- 5 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 .envrc create mode 100644 Justfile create mode 100644 flake.lock delete mode 100644 generate-cover.sh delete mode 100644 generate-parser.sh diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..17be98bd --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +use flake --impure +dotenv \ No newline at end of file diff --git a/Justfile b/Justfile new file mode 100644 index 00000000..4fe95592 --- /dev/null +++ b/Justfile @@ -0,0 +1,31 @@ +set dotenv-load + +default: + @just --list + +pre-commit: generate tidy lint +pc: pre-commit + +lint: + @golangci-lint run --fix --build-tags it --timeout 5m + +tidy: + @go mod tidy + +generate: + @antlr4 -Dlanguage=Go Lexer.g4 Numscript.g4 -o internal/parser/antlrParser -package antlrParser + @mv internal/parser/antlrParser/_lexer.go internal/parser/antlrParser/lexer.go + +tests: + @go test -race -covermode=atomic \ + -coverprofile coverage.txt \ + ./... + +release-local: + @goreleaser release --nightly --skip=publish --clean + +release-ci: + @goreleaser release --nightly --clean + +release: + @goreleaser release --clean diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..b283516d --- /dev/null +++ b/flake.lock @@ -0,0 +1,68 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nur", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1733312601, + "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1752480373, + "narHash": "sha256-JHQbm+OcGp32wAsXTE/FLYGNpb+4GLi5oTvCxwSoBOA=", + "rev": "62e0f05ede1da0d54515d4ea8ce9c733f12d9f08", + "revCount": 830073, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.830073%2Brev-62e0f05ede1da0d54515d4ea8ce9c733f12d9f08/01980c9b-b772-7845-b3a6-0d40f1f219ce/source.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/NixOS/nixpkgs/0.1.%2A.tar.gz" + } + }, + "nur": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1752568184, + "narHash": "sha256-wPcj4AcTSb1cYH5LRCd/cXQkIba3pgbAnBtwb1WuY+A=", + "owner": "nix-community", + "repo": "NUR", + "rev": "03d68216ee4db72b0813d8ffc5570473dca589ed", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "NUR", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "nur": "nur" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/generate-cover.sh b/generate-cover.sh deleted file mode 100644 index 1fb64181..00000000 --- a/generate-cover.sh +++ /dev/null @@ -1,2 +0,0 @@ -go test ./... -coverprofile=coverage.txt -go tool cover -html=coverage.txt diff --git a/generate-parser.sh b/generate-parser.sh deleted file mode 100644 index 9ec96fac..00000000 --- a/generate-parser.sh +++ /dev/null @@ -1,2 +0,0 @@ -antlr4 -Dlanguage=Go Lexer.g4 Numscript.g4 -o internal/parser/antlrParser -package antlrParser -mv internal/parser/antlrParser/_lexer.go internal/parser/antlrParser/lexer.go \ No newline at end of file From d9d5d1869a08fbfb520b8174debe382241937e26 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Tue, 15 Jul 2025 10:52:55 +0200 Subject: [PATCH 3/9] feat: add antlr to Nix flake dependencies --- flake.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flake.nix b/flake.nix index 7a187eb1..77b27858 100644 --- a/flake.nix +++ b/flake.nix @@ -49,6 +49,7 @@ pkgs.nur.repos.goreleaser.goreleaser-pro just goperf + antlr ]; }; } From 770c52cdc24cae6ca58cdbe5b9749c6276c78b77 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Tue, 15 Jul 2025 10:53:20 +0200 Subject: [PATCH 4/9] chore: update ANTLR version in generated files to 4.13.2 --- internal/parser/antlrParser/lexer.go | 2 +- internal/parser/antlrParser/numscript_base_listener.go | 2 +- internal/parser/antlrParser/numscript_listener.go | 2 +- internal/parser/antlrParser/numscript_parser.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/parser/antlrParser/lexer.go b/internal/parser/antlrParser/lexer.go index 9e7fd19a..e1524f68 100644 --- a/internal/parser/antlrParser/lexer.go +++ b/internal/parser/antlrParser/lexer.go @@ -1,4 +1,4 @@ -// Code generated from Lexer.g4 by ANTLR 4.13.1. DO NOT EDIT. +// Code generated from Lexer.g4 by ANTLR 4.13.2. DO NOT EDIT. package antlrParser diff --git a/internal/parser/antlrParser/numscript_base_listener.go b/internal/parser/antlrParser/numscript_base_listener.go index 5c4e1f6b..e786e701 100644 --- a/internal/parser/antlrParser/numscript_base_listener.go +++ b/internal/parser/antlrParser/numscript_base_listener.go @@ -1,4 +1,4 @@ -// Code generated from Numscript.g4 by ANTLR 4.13.1. DO NOT EDIT. +// Code generated from Numscript.g4 by ANTLR 4.13.2. DO NOT EDIT. package antlrParser // Numscript import "github.com/antlr4-go/antlr/v4" diff --git a/internal/parser/antlrParser/numscript_listener.go b/internal/parser/antlrParser/numscript_listener.go index 6dbcc8a0..2f7c5b67 100644 --- a/internal/parser/antlrParser/numscript_listener.go +++ b/internal/parser/antlrParser/numscript_listener.go @@ -1,4 +1,4 @@ -// Code generated from Numscript.g4 by ANTLR 4.13.1. DO NOT EDIT. +// Code generated from Numscript.g4 by ANTLR 4.13.2. DO NOT EDIT. package antlrParser // Numscript import "github.com/antlr4-go/antlr/v4" diff --git a/internal/parser/antlrParser/numscript_parser.go b/internal/parser/antlrParser/numscript_parser.go index d26c0454..838b47a0 100644 --- a/internal/parser/antlrParser/numscript_parser.go +++ b/internal/parser/antlrParser/numscript_parser.go @@ -1,4 +1,4 @@ -// Code generated from Numscript.g4 by ANTLR 4.13.1. DO NOT EDIT. +// Code generated from Numscript.g4 by ANTLR 4.13.2. DO NOT EDIT. package antlrParser // Numscript import ( From a85fda517d3262a064c661233026630c1cfa02e1 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Tue, 15 Jul 2025 11:01:34 +0200 Subject: [PATCH 5/9] feat: add GitHub Actions for environment setup and release management; remove obsolete workflows --- .envrc | 3 +- .github/actions/default/action.yml | 27 +++++++ .github/actions/env/action.yml | 24 +++++++ .github/workflows/checks.yml | 29 -------- .github/workflows/main.yml | 112 +++++++++++++++++++++++++++++ .github/workflows/release.yml | 31 -------- .github/workflows/releases.yml | 36 ++++++++++ 7 files changed, 200 insertions(+), 62 deletions(-) create mode 100644 .github/actions/default/action.yml create mode 100644 .github/actions/env/action.yml delete mode 100644 .github/workflows/checks.yml create mode 100644 .github/workflows/main.yml delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/releases.yml diff --git a/.envrc b/.envrc index 17be98bd..7d8b4bb5 100644 --- a/.envrc +++ b/.envrc @@ -1,2 +1 @@ -use flake --impure -dotenv \ No newline at end of file +use flake --impure \ No newline at end of file diff --git a/.github/actions/default/action.yml b/.github/actions/default/action.yml new file mode 100644 index 00000000..ff9b4ddc --- /dev/null +++ b/.github/actions/default/action.yml @@ -0,0 +1,27 @@ +name: Setup Env +description: Setup Env for Linux x64 +inputs: + token: + description: 'A Github PAT' + required: true +runs: + using: composite + steps: + - name: Install Nix + uses: cachix/install-nix-action@v31 + - name: Cache dependencies + uses: nix-community/cache-nix-action@v6 + with: + primary-key: nix-${{ runner.os }}-${{ hashFiles('**/flake.nix', '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}- + - name: Load dependencies + shell: bash + run: nix develop --install + - uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + /tmp/go/pkg/mod/ + key: ${{ runner.os }}-${{ github.job }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-${{ github.job }}-go- \ No newline at end of file diff --git a/.github/actions/env/action.yml b/.github/actions/env/action.yml new file mode 100644 index 00000000..e32a8d64 --- /dev/null +++ b/.github/actions/env/action.yml @@ -0,0 +1,24 @@ +name: Setup Env +description: Setup Env for Linux x64 +inputs: + token: + description: 'A Github PAT' + required: true +runs: + using: composite + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: "Put back the git branch into git (Earthly uses it for tagging)" + shell: bash + run: | + branch="" + if [ -n "$GITHUB_HEAD_REF" ]; then + branch="$GITHUB_HEAD_REF" + else + branch="${GITHUB_REF##*/}" + fi + git checkout -b "$branch" || true + diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml deleted file mode 100644 index a74dd386..00000000 --- a/.github/workflows/checks.yml +++ /dev/null @@ -1,29 +0,0 @@ -# This workflow will build a golang project -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go - -name: Go - -on: ["push", "pull_request"] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: "1.22" - - - name: Build - run: go build -v ./... - - - name: Test - run: go test -race -timeout 500ms -v ./... -coverprofile=coverage.txt - - - name: Upload results to Codecov - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - slug: formancehq/numscript diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..9e6313de --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,112 @@ +name: Default +on: + merge_group: + push: + branches: + - main + - release/* + pull_request: + types: [ assigned, opened, synchronize, reopened, labeled ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + PR: + if: github.event_name == 'pull_request' + name: Check PR Title + runs-on: ubuntu-latest + permissions: + statuses: write + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + Dirty: + runs-on: "shipfox-4vcpu-ubuntu-2404" + env: + GOPATH: /tmp/go + GOLANGCI_LINT_CACHE: /tmp/golangci-lint + steps: + - uses: 'actions/checkout@v4' + with: + fetch-depth: 0 + - name: Setup Env + uses: ./.github/actions/default + with: + token: ${{ secrets.NUMARY_GITHUB_TOKEN }} + - run: > + nix develop --impure --command just pre-commit + env: + SPEAKEASY_API_KEY: ${{ secrets.SPEAKEASY_API_KEY }} + - name: Get changed files + id: changed-files + shell: bash + run: | + hasChanged=$(git status --porcelain) + if (( $(echo ${#hasChanged}) != 0 )); then + git status + echo "There are changes in the repository" + git diff + exit 1 + fi + + Tests: + runs-on: "shipfox-4vcpu-ubuntu-2404" + env: + GOPATH: /tmp/go + steps: + - uses: 'actions/checkout@v4' + with: + fetch-depth: 0 + - name: Setup Env + uses: ./.github/actions/default + with: + token: ${{ secrets.NUMARY_GITHUB_TOKEN }} + - run: > + nix develop --impure --command just tests + env: + SPEAKEASY_API_KEY: ${{ secrets.SPEAKEASY_API_KEY }} + - name: Upload coverage reports to Codecov with GitHub Action + uses: codecov/codecov-action@v5.4.3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + GoReleaser: + runs-on: "shipfox-4vcpu-ubuntu-2404" + if: contains(github.event.pull_request.labels.*.name, 'build-images') || github.ref == 'refs/heads/main' || github.event_name == 'merge_group' + needs: + - Dirty + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - uses: earthly/actions-setup@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + version: "latest" + - uses: 'actions/checkout@v4' + with: + fetch-depth: 0 + - name: Setup Env + uses: ./.github/actions/default + with: + token: ${{ secrets.NUMARY_GITHUB_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: "NumaryBot" + password: ${{ secrets.NUMARY_GITHUB_TOKEN }} + - run: > + nix develop --impure --command just release-ci + env: + GITHUB_TOKEN: ${{ secrets.NUMARY_GITHUB_TOKEN }} + GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 1014420e..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: release -on: - release: - types: [created] -permissions: - contents: write -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/setup-go@v2 - with: - go-version: "1.22" - - uses: actions/checkout@v2 - - uses: actions/cache@v4 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - name: get deps - run: go mod download - - name: Run GoReleaser - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: goreleaser/goreleaser-action@v6 - with: - version: latest - args: release diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml new file mode 100644 index 00000000..dc288841 --- /dev/null +++ b/.github/workflows/releases.yml @@ -0,0 +1,36 @@ +name: Release +on: + push: + tags: + - 'v*.*.*' +permissions: + contents: write + +jobs: + GoReleaser: + runs-on: "shipfox-4vcpu-ubuntu-2404" + steps: + - uses: 'actions/checkout@v4' + with: + fetch-depth: 0 + - name: Setup Env + uses: ./.github/actions/default + with: + token: ${{ secrets.NUMARY_GITHUB_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: "NumaryBot" + password: ${{ secrets.NUMARY_GITHUB_TOKEN }} + - run: > + nix develop --impure --command just release + env: + GITHUB_TOKEN: ${{ secrets.NUMARY_GITHUB_TOKEN }} + SPEAKEASY_API_KEY: ${{ secrets.SPEAKEASY_API_KEY }} + FURY_TOKEN: ${{ secrets.FURY_TOKEN }} + GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} \ No newline at end of file From a1e40309102e9cf16e189e923d4484f15da88133 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Tue, 15 Jul 2025 11:16:24 +0200 Subject: [PATCH 6/9] chore: remove SPEAKEASY_API_KEY from workflows to streamline environment variables --- .github/workflows/main.yml | 4 ---- .github/workflows/releases.yml | 2 -- 2 files changed, 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e6313de..7603b6fa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,8 +39,6 @@ jobs: token: ${{ secrets.NUMARY_GITHUB_TOKEN }} - run: > nix develop --impure --command just pre-commit - env: - SPEAKEASY_API_KEY: ${{ secrets.SPEAKEASY_API_KEY }} - name: Get changed files id: changed-files shell: bash @@ -67,8 +65,6 @@ jobs: token: ${{ secrets.NUMARY_GITHUB_TOKEN }} - run: > nix develop --impure --command just tests - env: - SPEAKEASY_API_KEY: ${{ secrets.SPEAKEASY_API_KEY }} - name: Upload coverage reports to Codecov with GitHub Action uses: codecov/codecov-action@v5.4.3 env: diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index dc288841..4133ff33 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -31,6 +31,4 @@ jobs: nix develop --impure --command just release env: GITHUB_TOKEN: ${{ secrets.NUMARY_GITHUB_TOKEN }} - SPEAKEASY_API_KEY: ${{ secrets.SPEAKEASY_API_KEY }} - FURY_TOKEN: ${{ secrets.FURY_TOKEN }} GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} \ No newline at end of file From 97876784a31c254b24b3dc78099eff7842a6def8 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Tue, 15 Jul 2025 11:17:23 +0200 Subject: [PATCH 7/9] refactor: simplify range checks in hover functions and update variable end position --- internal/analysis/hover.go | 16 ++++++++-------- internal/lsp/code_actions.go | 2 +- internal/lsp/handlers.go | 2 +- internal/lsp/object_stream_test.go | 2 +- internal/parser/parser.go | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/analysis/hover.go b/internal/analysis/hover.go index ca7a79cf..c7448e0e 100644 --- a/internal/analysis/hover.go +++ b/internal/analysis/hover.go @@ -70,7 +70,7 @@ func HoverOn(program parser.Program, position parser.Position) Hover { } func hoverOnVar(varDecl parser.VarDeclaration, position parser.Position) Hover { - if !varDecl.Range.Contains(position) { + if !varDecl.Contains(position) { return nil } @@ -101,7 +101,7 @@ func hoverOnSentValue(sentValue parser.SentValue, position parser.Position) Hove } func hoverOnSaveStatement(saveStatement parser.SaveStatement, position parser.Position) Hover { - if !saveStatement.Range.Contains(position) { + if !saveStatement.Contains(position) { return nil } @@ -119,7 +119,7 @@ func hoverOnSaveStatement(saveStatement parser.SaveStatement, position parser.Po } func hoverOnSendStatement(sendStatement parser.SendStatement, position parser.Position) Hover { - if !sendStatement.Range.Contains(position) { + if !sendStatement.Contains(position) { return nil } @@ -238,7 +238,7 @@ func hoverOnSource(source parser.Source, position parser.Position) Hover { case *parser.SourceAllotment: for _, item := range source.Items { // TODO binary search - if !item.Range.Contains(position) { + if !item.Contains(position) { continue } @@ -288,7 +288,7 @@ func hoverOnDestination(destination parser.Destination, position parser.Position case *parser.DestinationInorder: for _, inorderClause := range source.Clauses { // TODO binary search - if !inorderClause.Range.Contains(position) { + if !inorderClause.Contains(position) { continue } @@ -311,7 +311,7 @@ func hoverOnDestination(destination parser.Destination, position parser.Position case *parser.DestinationAllotment: for _, item := range source.Items { // TODO binary search - if !item.Range.Contains(position) { + if !item.Contains(position) { continue } @@ -338,11 +338,11 @@ func hoverOnDestination(destination parser.Destination, position parser.Position } func hoverOnFnCall(callStatement parser.FnCall, position parser.Position) Hover { - if !callStatement.Range.Contains(position) { + if !callStatement.Contains(position) { return nil } - if callStatement.Caller.Range.Contains(position) { + if callStatement.Caller.Contains(position) { return &BuiltinFnHover{ Range: callStatement.Caller.Range, Node: &callStatement, diff --git a/internal/lsp/code_actions.go b/internal/lsp/code_actions.go index 4c043230..dfed82a9 100644 --- a/internal/lsp/code_actions.go +++ b/internal/lsp/code_actions.go @@ -29,7 +29,7 @@ func CreateVar(diagnostic analysis.UnboundVariable, program parser.Program) lsp_ lastVarEnd := program.Vars.Declarations[len(program.Vars.Declarations)-1].End - varsEndPosition := program.Vars.Range.End + varsEndPosition := program.Vars.End varsEndPosition.Character-- return lsp_types.TextEdit{ diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go index 75d2b41c..d7755e77 100644 --- a/internal/lsp/handlers.go +++ b/internal/lsp/handlers.go @@ -29,7 +29,7 @@ func (state *State) updateDocument(conn *jsonrpc2.Conn, uri lsp_types.DocumentUR CheckResult: checkResult, }) - var diagnostics []lsp_types.Diagnostic = make([]lsp_types.Diagnostic, 0) + var diagnostics = make([]lsp_types.Diagnostic, 0) for _, diagnostic := range checkResult.Diagnostics { diagnostics = append(diagnostics, toLspDiagnostic(diagnostic)) } diff --git a/internal/lsp/object_stream_test.go b/internal/lsp/object_stream_test.go index ba8c9274..01054c4e 100644 --- a/internal/lsp/object_stream_test.go +++ b/internal/lsp/object_stream_test.go @@ -45,7 +45,7 @@ func TestObjectStreamRead(t *testing.T) { } strMsg, _ := json.Marshal(sentMsg) - in.Write([]byte(fmt.Sprintf("Content-Length: %d\r\n\r\n%s", len(strMsg), strMsg))) + fmt.Fprintf(in, "Content-Length: %d\r\n\r\n%s", len(strMsg), strMsg) receivedMsg, err := stream.ReadMessage() require.Nil(t, err) diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 4e37d84a..aec04672 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -254,7 +254,7 @@ func parseSource(sourceCtx antlrParser.ISourceContext) Source { // TODO actually handle big int func ParsePercentageRatio(source string) (*big.Int, uint16, error) { str := strings.TrimSuffix(source, "%") - num, err := strconv.ParseUint(strings.Replace(str, ".", "", -1), 0, 64) + num, err := strconv.ParseUint(strings.ReplaceAll(str, ".", ""), 0, 64) if err != nil { return nil, 0, err } From 5638c3aa2cf5dc2901fb7397a15e81d6fe61fd23 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Tue, 15 Jul 2025 11:25:59 +0200 Subject: [PATCH 8/9] refactor: improve error handling and logging across multiple files --- internal/cmd/check.go | 4 +- internal/cmd/lsp.go | 4 +- internal/cmd/run.go | 97 +++++++++++--------- internal/interpreter/batch_balances_query.go | 2 +- internal/interpreter/interpreter.go | 2 +- internal/jsonrpc2/jsonrpc2.go | 44 ++++++--- internal/jsonrpc2/jsonrpc2_test.go | 3 +- internal/lsp/handlers.go | 11 ++- internal/lsp/handlers_test.go | 5 +- internal/lsp/object_stream_test.go | 3 +- internal/numscript/numscript.go | 8 +- 11 files changed, 109 insertions(+), 74 deletions(-) diff --git a/internal/cmd/check.go b/internal/cmd/check.go index 04256497..85957fe3 100644 --- a/internal/cmd/check.go +++ b/internal/cmd/check.go @@ -13,8 +13,8 @@ import ( func check(path string) { dat, err := os.ReadFile(path) if err != nil { - os.Stderr.Write([]byte(err.Error())) - return + fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err) + os.Exit(1) } res := analysis.CheckSource(string(dat)) diff --git a/internal/cmd/lsp.go b/internal/cmd/lsp.go index f1d7ba14..6c5c3389 100644 --- a/internal/cmd/lsp.go +++ b/internal/cmd/lsp.go @@ -11,7 +11,7 @@ var lspCmd = &cobra.Command{ Short: "Run the lsp server", Long: "Run the lsp server. This command is usually meant to be used for editors integration.", Hidden: true, - Run: func(cmd *cobra.Command, args []string) { - lsp.RunServer() + RunE: func(cmd *cobra.Command, args []string) error { + return lsp.RunServer() }, } diff --git a/internal/cmd/run.go b/internal/cmd/run.go index 0374723f..1c7434da 100644 --- a/internal/cmd/run.go +++ b/internal/cmd/run.go @@ -38,39 +38,40 @@ type inputOpts struct { Balances interpreter.Balances `json:"balances"` } -func (o *inputOpts) fromRaw(opts Args) { +func (o *inputOpts) fromRaw(opts Args) error { if opts.RawOpt == "" { - return + return nil } err := json.Unmarshal([]byte(opts.RawOpt), o) if err != nil { - panic(err) + return fmt.Errorf("invalid raw input JSON: %w", err) } + return nil } -func (o *inputOpts) fromStdin(opts Args) { +func (o *inputOpts) fromStdin(opts Args) error { if !opts.StdinFlag { - return + return nil } bytes, err := io.ReadAll(os.Stdin) if err != nil { - panic(err) + return fmt.Errorf("error reading from stdin: %w", err) } err = json.Unmarshal(bytes, o) if err != nil { - panic(err) + return fmt.Errorf("invalid stdin JSON: %w", err) } + return nil } -func (o *inputOpts) fromOptions(path string, opts Args) { +func (o *inputOpts) fromOptions(path string, opts Args) error { if path != "" { numscriptContent, err := os.ReadFile(path) if err != nil { - os.Stderr.Write([]byte(err.Error())) - return + return fmt.Errorf("error reading script file: %w", err) } o.Script = string(numscriptContent) } @@ -78,46 +79,56 @@ func (o *inputOpts) fromOptions(path string, opts Args) { if opts.BalancesOpt != "" { content, err := os.ReadFile(opts.BalancesOpt) if err != nil { - os.Stderr.Write([]byte(err.Error())) - return + return fmt.Errorf("error reading balances file: %w", err) + } + if err := json.Unmarshal(content, &o.Balances); err != nil { + return fmt.Errorf("invalid balances JSON: %w", err) } - json.Unmarshal(content, &o.Balances) } if opts.MetaOpt != "" { content, err := os.ReadFile(opts.MetaOpt) if err != nil { - os.Stderr.Write([]byte(err.Error())) - return + return fmt.Errorf("error reading metadata file: %w", err) + } + if err := json.Unmarshal(content, &o.Meta); err != nil { + return fmt.Errorf("invalid metadata JSON: %w", err) } - json.Unmarshal(content, &o.Meta) } if opts.VariablesOpt != "" { content, err := os.ReadFile(opts.VariablesOpt) if err != nil { - os.Stderr.Write([]byte(err.Error())) - return + return fmt.Errorf("error reading variables file: %w", err) + } + if err := json.Unmarshal(content, &o.Variables); err != nil { + return fmt.Errorf("invalid variables JSON: %w", err) } - json.Unmarshal(content, &o.Variables) } + return nil } -func run(path string, opts Args) { +func run(path string, opts Args) error { opt := inputOpts{ Variables: make(map[string]string), Meta: make(interpreter.AccountsMetadata), Balances: make(interpreter.Balances), } - opt.fromRaw(opts) - opt.fromOptions(path, opts) - opt.fromStdin(opts) + if err := opt.fromRaw(opts); err != nil { + return err + } + if err := opt.fromOptions(path, opts); err != nil { + return err + } + if err := opt.fromStdin(opts); err != nil { + return err + } parseResult := parser.Parse(opt.Script) if len(parseResult.Errors) != 0 { - os.Stderr.Write([]byte(parser.ParseErrorsToString(parseResult.Errors, opt.Script))) - os.Exit(1) + fmt.Fprint(os.Stderr, parser.ParseErrorsToString(parseResult.Errors, opt.Script)) + return fmt.Errorf("parsing failed") } featureFlags := map[string]struct{}{} @@ -132,42 +143,39 @@ func run(path string, opts Args) { if err != nil { rng := err.GetRange() - os.Stderr.Write([]byte(err.Error())) + fmt.Fprint(os.Stderr, err.Error()) if rng.Start != rng.End { - os.Stderr.Write([]byte("\n")) - os.Stderr.Write([]byte(err.GetRange().ShowOnSource(parseResult.Source))) + fmt.Fprint(os.Stderr, "\n") + fmt.Fprint(os.Stderr, err.GetRange().ShowOnSource(parseResult.Source)) } - os.Exit(1) - return + return fmt.Errorf("execution failed") } switch opts.OutFormatOpt { case OutputFormatJson: - showJson(result) + return showJson(result) case OutputFormatPretty: - showPretty(result) + return showPretty(result) default: - // TODO handle err - panic("Invalid option: " + opts.OutFormatOpt) + return fmt.Errorf("invalid output format: %s", opts.OutFormatOpt) } - } -func showJson(result *interpreter.ExecutionResult) { +func showJson(result *interpreter.ExecutionResult) error { out, err := json.Marshal(result) if err != nil { - // TODO handle err - panic(err) + return fmt.Errorf("error marshaling result to JSON: %w", err) } - os.Stdout.Write(out) + _, err = os.Stdout.Write(out) + return err } -func showPretty(result *interpreter.ExecutionResult) { +func showPretty(result *interpreter.ExecutionResult) error { fmt.Println(ansi.ColorCyan("Postings:")) postingsJson, err := json.MarshalIndent(result.Postings, "", " ") if err != nil { - panic(err) + return fmt.Errorf("error marshaling postings: %w", err) } fmt.Println(string(postingsJson)) @@ -176,9 +184,10 @@ func showPretty(result *interpreter.ExecutionResult) { fmt.Println(ansi.ColorCyan("Meta:")) txMetaJson, err := json.MarshalIndent(result.Metadata, "", " ") if err != nil { - panic(err) + return fmt.Errorf("error marshaling metadata: %w", err) } fmt.Println(string(txMetaJson)) + return nil } func getRunCmd() *cobra.Command { @@ -188,12 +197,12 @@ func getRunCmd() *cobra.Command { Use: "run", Short: "Evaluate a numscript file", Long: "Evaluate a numscript file, using the balances, the current metadata and the variables values as input.", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { var path string if len(args) > 0 { path = args[0] } - run(path, opts) + return run(path, opts) }, } diff --git a/internal/interpreter/batch_balances_query.go b/internal/interpreter/batch_balances_query.go index af1c58f3..d5a3b40c 100644 --- a/internal/interpreter/batch_balances_query.go +++ b/internal/interpreter/batch_balances_query.go @@ -148,7 +148,7 @@ func (st *programState) findBalancesQueries(source parser.Source) InterpreterErr return nil default: - utils.NonExhaustiveMatchPanic[error](source) + _ = utils.NonExhaustiveMatchPanic[error](source) return nil } } diff --git a/internal/interpreter/interpreter.go b/internal/interpreter/interpreter.go index 944b4487..f3670a18 100644 --- a/internal/interpreter/interpreter.go +++ b/internal/interpreter/interpreter.go @@ -537,7 +537,7 @@ func (s *programState) sendAll(source parser.Source) (*big.Int, InterpreterError return nil, InvalidAllotmentInSendAll{} default: - utils.NonExhaustiveMatchPanic[error](source) + _ = utils.NonExhaustiveMatchPanic[error](source) return nil, nil } } diff --git a/internal/jsonrpc2/jsonrpc2.go b/internal/jsonrpc2/jsonrpc2.go index bed9a2d0..899b1459 100644 --- a/internal/jsonrpc2/jsonrpc2.go +++ b/internal/jsonrpc2/jsonrpc2.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "log" "sync" "sync/atomic" @@ -53,10 +54,12 @@ func NewRequestHandler[Params any](method string, strategy HandlingStrategy, han if raw != nil { err := json.Unmarshal([]byte(raw), &payload) if err != nil { - conn.stream.WriteMessage(Response{ + if err := conn.stream.WriteMessage(Response{ ID: id, Error: &ErrInvalidParams, - }) + }); err != nil { + log.Printf("jsonrpc2: error writing invalid params response: %v", err) + } return } } @@ -68,10 +71,12 @@ func NewRequestHandler[Params any](method string, strategy HandlingStrategy, han panic(err) } - conn.stream.WriteMessage(Response{ + if err := conn.stream.WriteMessage(Response{ ID: id, Result: bytes, - }) + }); err != nil { + log.Printf("jsonrpc2: error writing response: %v", err) + } } switch strategy { @@ -180,11 +185,15 @@ func (s *Conn) SendRequest(method string, params any) (json.RawMessage, *Respons s.pendingRequests[freshId] = ch s.pendingRequestMu.Unlock() - go s.stream.WriteMessage(Request{ - ID: freshId, - Method: method, - Params: bytes, - }) + go func() { + if err := s.stream.WriteMessage(Request{ + ID: freshId, + Method: method, + Params: bytes, + }); err != nil { + log.Printf("jsonrpc2: error sending request %s: %v", method, err) + } + }() response := <-ch @@ -214,12 +223,13 @@ func (s *Conn) SendNotification(method string, params any) error { }) } -func (s *Conn) Close() { - s.stream.Close() +func (s *Conn) Close() error { + err := s.stream.Close() for _, ch := range s.pendingRequests { close(ch) } s.opened = false + return err } func (s *Conn) handleRequest(request Request) error { @@ -233,10 +243,14 @@ func (s *Conn) handleRequest(request Request) error { } else { handler, ok := s.requestsHandlers[request.Method] if !ok { - go s.stream.WriteMessage(Response{ - ID: request.ID, - Error: &ErrMethodNotFound, - }) + go func() { + if err := s.stream.WriteMessage(Response{ + ID: request.ID, + Error: &ErrMethodNotFound, + }); err != nil { + log.Printf("jsonrpc2: error writing method not found response: %v", err) + } + }() return nil } diff --git a/internal/jsonrpc2/jsonrpc2_test.go b/internal/jsonrpc2/jsonrpc2_test.go index 26c62a3c..7d57d3d7 100644 --- a/internal/jsonrpc2/jsonrpc2_test.go +++ b/internal/jsonrpc2/jsonrpc2_test.go @@ -42,9 +42,10 @@ func TestHandleNotification(t *testing.T) { }), ) - client.SendNotification("greet", NotifParams{ + err := client.SendNotification("greet", NotifParams{ Value: "Hello!", }) + require.NoError(t, err) require.Equal(t, "Hello!", <-ch) } diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go index d7755e77..eefa9a13 100644 --- a/internal/lsp/handlers.go +++ b/internal/lsp/handlers.go @@ -2,6 +2,7 @@ package lsp import ( "fmt" + "log" "os" "slices" @@ -34,10 +35,12 @@ func (state *State) updateDocument(conn *jsonrpc2.Conn, uri lsp_types.DocumentUR diagnostics = append(diagnostics, toLspDiagnostic(diagnostic)) } - conn.SendNotification("textDocument/publishDiagnostics", lsp_types.PublishDiagnosticsParams{ + if err := conn.SendNotification("textDocument/publishDiagnostics", lsp_types.PublishDiagnosticsParams{ URI: uri, Diagnostics: diagnostics, - }) + }); err != nil { + log.Printf("lsp: error publishing diagnostics: %v", err) + } } func (state *State) handleHover(params lsp_types.HoverParams) *lsp_types.Hover { @@ -280,7 +283,9 @@ func NewConn(objStream jsonrpc2.MessageStream) *jsonrpc2.Conn { }), jsonrpc2.NewRequestHandler("shutdown", jsonrpc2.SyncHandling, func(_ any, conn *jsonrpc2.Conn) any { - conn.SendNotification("exit", nil) + if err := conn.SendNotification("exit", nil); err != nil { + log.Printf("lsp: error sending exit notification: %v", err) + } return nil }), ) diff --git a/internal/lsp/handlers_test.go b/internal/lsp/handlers_test.go index 1611f112..f6059546 100644 --- a/internal/lsp/handlers_test.go +++ b/internal/lsp/handlers_test.go @@ -111,13 +111,16 @@ type TestClient struct { } func (c *TestClient) OpenFile(uri string, text string) (lsp_types.TextDocumentIdentifier, string, json.RawMessage) { - c.conn.SendNotification("textDocument/didOpen", lsp_types.DidOpenTextDocumentParams{ + err := c.conn.SendNotification("textDocument/didOpen", lsp_types.DidOpenTextDocumentParams{ TextDocument: lsp_types.TextDocumentItem{ URI: lsp_types.DocumentURI(uri), LanguageID: "numscript", Text: text, }, }) + if err != nil { + panic(err) + } docIdent := lsp_types.TextDocumentIdentifier{ URI: lsp_types.DocumentURI(uri), diff --git a/internal/lsp/object_stream_test.go b/internal/lsp/object_stream_test.go index 01054c4e..b783b4a1 100644 --- a/internal/lsp/object_stream_test.go +++ b/internal/lsp/object_stream_test.go @@ -45,7 +45,8 @@ func TestObjectStreamRead(t *testing.T) { } strMsg, _ := json.Marshal(sentMsg) - fmt.Fprintf(in, "Content-Length: %d\r\n\r\n%s", len(strMsg), strMsg) + _, err := fmt.Fprintf(in, "Content-Length: %d\r\n\r\n%s", len(strMsg), strMsg) + require.NoError(t, err) receivedMsg, err := stream.ReadMessage() require.Nil(t, err) diff --git a/internal/numscript/numscript.go b/internal/numscript/numscript.go index 52ef2b34..33bf0b21 100644 --- a/internal/numscript/numscript.go +++ b/internal/numscript/numscript.go @@ -26,16 +26,18 @@ func recoverPanic() { } errMsg := fmt.Sprintf("[uncaught panic]@%s: %s\n%s\n", Version, r, string(debug.Stack())) - os.Stderr.Write([]byte(errMsg)) + fmt.Fprint(os.Stderr, errMsg) sentry.CaptureMessage(errMsg) sentry.Flush(2 * time.Second) } func main() { - sentry.Init(sentry.ClientOptions{ + if err := sentry.Init(sentry.ClientOptions{ Dsn: "https://b8b6cfd5dab95e1258d80963c3db73bf@o4504394442539008.ingest.us.sentry.io/4507623538884608", AttachStacktrace: true, - }) + }); err != nil { + fmt.Fprintf(os.Stderr, "Failed to initialize Sentry: %v\n", err) + } defer recoverPanic() defer sentry.Flush(2 * time.Second) From 9234cac3671b0b46c8a7b2183a3bc522417d1b35 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Tue, 15 Jul 2025 11:29:58 +0200 Subject: [PATCH 9/9] feat: Update flake.nix Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 77b27858..3c3e73be 100644 --- a/flake.nix +++ b/flake.nix @@ -27,7 +27,7 @@ pkgs = import nixpkgs { inherit system; overlays = [ self.overlays.default nur.overlays.default ]; - config.allowUnfree = true; + config = { allowUnfree = true; }; }; in f { pkgs = pkgs; system = system; }