Full Code of sonatard/noctx for AI

master 00a60094e864 cached
21 files
34.1 KB
12.1k tokens
12 symbols
1 requests
Download .txt
Repository: sonatard/noctx
Branch: master
Commit: 00a60094e864
Files: 21
Total size: 34.1 KB

Directory structure:
gitextract_qb9kju5i/

├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── LICENSE
├── Makefile
├── README.md
├── cmd/
│   └── noctx/
│       └── main.go
├── go.mod
├── go.sum
├── noctx.go
├── noctx_test.go
├── testdata/
│   └── src/
│       ├── crypto_tls/
│       │   └── tls.go
│       ├── exec_cmd/
│       │   └── exec.go
│       ├── http_client/
│       │   └── http_client.go
│       ├── http_request/
│       │   └── http_request.go
│       ├── httptest_request/
│       │   └── httptest_request.go
│       ├── network/
│       │   └── net.go
│       └── sql/
│           └── sql.go
└── types.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches:
      - master
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        go: [ stable, oldstable ]
    name: Go ${{ matrix.go }} test
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: ${{ matrix.go }}
      - name: Install GolangCI-Lint
        run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.5
      - run: make lint
      - run: make test_coverage
      - name: Upload code coverage to codecov
        if: matrix.go == 'stable'
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.out


================================================
FILE: .github/workflows/release.yml
================================================
name: release

on:
  push:
    tags:
      - "v[0-9]+.[0-9]+.[0-9]+"

jobs:
  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Unshallow
        run: git fetch --prune --unshallow
      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: stable
      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v6
        with:
          version: latest
          args: release --clean
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
coverage.out
/noctx


================================================
FILE: .golangci.yml
================================================
version: "2"

formatters:
  enable:
    - gofumpt
    - goimports


linters:
  default: all
  disable:
    - exhaustruct
    - gochecknoglobals
    - gocognit
    - lll
    - mnd
    - nestif
    - nilnil
    - paralleltest
    - varnamelen

  settings:
    depguard:
      rules:
        main:
          deny:
            - pkg: github.com/instana/testify
              desc: not allowed
            - pkg: github.com/pkg/errors
              desc: Should be replaced by standard lib errors package
    govet:
      enable-all: true
    perfsprint:
      err-error: true
      errorf: true
      sprintf1: true
      strconcat: false

  exclusions:
    presets:
      - comments
      - common-false-positives
      - std-error-handling


================================================
FILE: .goreleaser.yml
================================================
version: 2
project_name: noctx

builds:
  - binary: noctx

    main: ./cmd/noctx/main.go
    env:
      - CGO_ENABLED=0
    flags:
      - -trimpath
    goos:
      - windows
      - darwin
      - linux
    goarch:
      - amd64
      - 386
      - arm
      - arm64
    goarm:
      - 7
      - 6
      - 5
    ignore:
      - goos: darwin
        goarch: 386
      - goos: windows
        goarch: arm

archives:
  - id: noctx
    name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}'
    formats: [ 'tar.gz' ]
    format_overrides:
      - goos: windows
        formats: [ 'zip' ]
    files:
      - LICENSE


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 sonatard

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
.PHONY: all fmt test lint build

build:
	go build -ldflags "-s -w" -trimpath ./cmd/noctx/

fmt:
	golangci-lint fmt ./...

lint:
	golangci-lint run ./...

test:
	go test -race ./...

test_coverage:
	go test -race -coverprofile=coverage.out -covermode=atomic ./...


================================================
FILE: README.md
================================================
# noctx

![](https://github.com/sonatard/noctx/workflows/CI/badge.svg)

`noctx` finds function calls without context.Context.

Passing `context.Context` enables library user to cancel request, getting trace information and so on.

`noctx` helps you to identify code that could be rewritten to use the context.Context.

## Usage

### noctx with go vet

go vet is a Go standard tool for analyzing source code.

1. Install noctx.
```sh
$ go install github.com/sonatard/noctx/cmd/noctx@latest
```

2. Execute noctx
```sh
$ go vet -vettool=`which noctx` main.go
./main.go:6:11: net/http.Get must not be called
```

### noctx with golangci-lint

golangci-lint is a fast Go linters runner.

1. Install golangci-lint.
[golangci-lint - Install](https://golangci-lint.run/usage/install/)

2. Setup .golangci.yml
```yaml:
# Add noctx to enable linters.
linters:
  enable:
    - noctx

# Or enable-all is true.
linters:
  default: all
  disable:
   - xxx # Add unused linter to disable linters.
```

3. Execute noctx
```sh
# Use .golangci.yml
$ golangci-lint run

# Only execute noctx
golangci-lint run --enable-only noctx
```

## net/http package
### Rules
https://github.com/sonatard/noctx/blob/b768dab1764733f7f69c5075b7497eff4c58f260/noctx.go#L41-L50

### Sample
https://github.com/sonatard/noctx/blob/b768dab1764733f7f69c5075b7497eff4c58f260/testdata/src/http_client/http_client.go#L11
https://github.com/sonatard/noctx/blob/b768dab1764733f7f69c5075b7497eff4c58f260/testdata/src/http_request/http_request.go#L17

### Reference
- [net/http - NewRequest](https://pkg.go.dev/net/http#NewRequest)
- [net/http - NewRequestWithContext](https://pkg.go.dev/net/http#NewRequestWithContext)
- [net/http - Request.WithContext](https://pkg.go.dev/net/http#Request.WithContext)
- [net/http/httptest - NewRequest](https://pkg.go.dev/net/http/httptest#NewRequest)

## net package

### Rules
https://github.com/sonatard/noctx/blob/b768dab1764733f7f69c5075b7497eff4c58f260/noctx.go#L26-L39

### Sample
https://github.com/sonatard/noctx/blob/b768dab1764733f7f69c5075b7497eff4c58f260/testdata/src/network/net.go#L17

### References
- [net - ListenConfig](https://pkg.go.dev/net#ListenConfig)
- [net - Dialer.DialContext](https://pkg.go.dev/net#Dialer.DialContext)
- [net - Resolver](https://pkg.go.dev/net#Resolver)
- [net - DefaultResolver](https://pkg.go.dev/net#DefaultResolver)

## database/sql package
### Rules
https://github.com/sonatard/noctx/blob/b768dab1764733f7f69c5075b7497eff4c58f260/noctx.go#L52-L66

### Sample
https://github.com/sonatard/noctx/blob/b768dab1764733f7f69c5075b7497eff4c58f260/testdata/src/sql/sql.go#L18

### Reference
- [database/sql](https://pkg.go.dev/database/sql)

## crypt/tls package
### Rules
https://github.com/sonatard/noctx/blob/b768dab1764733f7f69c5075b7497eff4c58f260/noctx.go#L71-L74

### Sample
https://github.com/sonatard/noctx/blob/b768dab1764733f7f69c5075b7497eff4c58f260/testdata/src/crypto_tls/tls.go#L17

### Reference
- [crypto/tls - Dialer.DialContext](https://pkg.go.dev/crypto/tls#Dialer.DialContext)
- [crypto/tls - Conn.HandshakeContext](https://pkg.go.dev/crypto/tls#Conn.HandshakeContext)

## exec package
### Rules
https://github.com/sonatard/noctx/blob/b768dab1764733f7f69c5075b7497eff4c58f260/noctx.go#L68-L69

### Sample
https://github.com/sonatard/noctx/blob/b768dab1764733f7f69c5075b7497eff4c58f260/testdata/src/exec_cmd/exec.go#L11

### Reference
- [exec - exec.CommandContext](https://pkg.go.dev/exec#CommandContext)



================================================
FILE: cmd/noctx/main.go
================================================
package main

import (
	"github.com/sonatard/noctx"
	"golang.org/x/tools/go/analysis/unitchecker"
)

func main() { unitchecker.Main(noctx.Analyzer) }


================================================
FILE: go.mod
================================================
module github.com/sonatard/noctx

go 1.23.0

require (
	github.com/gostaticanalysis/analysisutil v0.7.1
	golang.org/x/tools v0.32.0
)

require (
	github.com/gostaticanalysis/comment v1.5.0 // indirect
	golang.org/x/mod v0.24.0 // indirect
	golang.org/x/sync v0.13.0 // indirect
)


================================================
FILE: go.sum
================================================
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM=
github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8=
github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc=
github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4 h1:d2/eIbH9XjD1fFwD5SHv8x168fjbQ9PB8hvs8DSEC08=
github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k=
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA=
github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag=
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=


================================================
FILE: noctx.go
================================================
package noctx

import (
	"fmt"
	"maps"
	"slices"

	"github.com/gostaticanalysis/analysisutil"
	"golang.org/x/tools/go/analysis"
	"golang.org/x/tools/go/analysis/passes/buildssa"
)

var Analyzer = &analysis.Analyzer{
	Name:             "noctx",
	Doc:              "noctx finds function calls without context.Context",
	Run:              Run,
	RunDespiteErrors: false,
	Requires: []*analysis.Analyzer{
		buildssa.Analyzer,
	},
	ResultType: nil,
	FactTypes:  nil,
}

var ngFuncMessages = map[string]string{
	// net
	"net.Listen":       "must not be called. use (*net.ListenConfig).Listen",
	"net.ListenPacket": "must not be called. use (*net.ListenConfig).ListenPacket",
	"net.Dial":         "must not be called. use (*net.Dialer).DialContext",
	"net.DialTimeout":  "must not be called. use (*net.Dialer).DialContext with (*net.Dialer).Timeout",
	"net.LookupCNAME":  "must not be called. use (*net.Resolver).LookupCNAME with a context",
	"net.LookupHost":   "must not be called. use (*net.Resolver).LookupHost with a context",
	"net.LookupIP":     "must not be called. use (*net.Resolver).LookupIPAddr with a context",
	"net.LookupPort":   "must not be called. use (*net.Resolver).LookupPort with a context",
	"net.LookupSRV":    "must not be called. use (*net.Resolver).LookupSRV with a context",
	"net.LookupMX":     "must not be called. use (*net.Resolver).LookupMX with a context",
	"net.LookupNS":     "must not be called. use (*net.Resolver).LookupNS with a context",
	"net.LookupTXT":    "must not be called. use (*net.Resolver).LookupTXT with a context",
	"net.LookupAddr":   "must not be called. use (*net.Resolver).LookupAddr with a context",

	// net/http
	"net/http.Get":                 "must not be called. use net/http.NewRequestWithContext and (*net/http.Client).Do(*http.Request)",
	"net/http.Head":                "must not be called. use net/http.NewRequestWithContext and (*net/http.Client).Do(*http.Request)",
	"net/http.Post":                "must not be called. use net/http.NewRequestWithContext and (*net/http.Client).Do(*http.Request)",
	"net/http.PostForm":            "must not be called. use net/http.NewRequestWithContext and (*net/http.Client).Do(*http.Request)",
	"(*net/http.Client).Get":       "must not be called. use (*net/http.Client).Do(*http.Request)",
	"(*net/http.Client).Head":      "must not be called. use (*net/http.Client).Do(*http.Request)",
	"(*net/http.Client).Post":      "must not be called. use (*net/http.Client).Do(*http.Request)",
	"(*net/http.Client).PostForm":  "must not be called. use (*net/http.Client).Do(*http.Request)",
	"net/http.NewRequest":          "must not be called. use net/http.NewRequestWithContext",
	"net/http/httptest.NewRequest": "must not be called. use net/http/httptest.NewRequestWithContext",

	// database/sql
	"(*database/sql.DB).Begin":      "must not be called. use (*database/sql.DB).BeginTx",
	"(*database/sql.DB).Exec":       "must not be called. use (*database/sql.DB).ExecContext",
	"(*database/sql.DB).Ping":       "must not be called. use (*database/sql.DB).PingContext",
	"(*database/sql.DB).Prepare":    "must not be called. use (*database/sql.DB).PrepareContext",
	"(*database/sql.DB).Query":      "must not be called. use (*database/sql.DB).QueryContext",
	"(*database/sql.DB).QueryRow":   "must not be called. use (*database/sql.DB).QueryRowContext",
	"(*database/sql.Tx).Exec":       "must not be called. use (*database/sql.Tx).ExecContext",
	"(*database/sql.Tx).Prepare":    "must not be called. use (*database/sql.Tx).PrepareContext",
	"(*database/sql.Tx).Query":      "must not be called. use (*database/sql.Tx).QueryContext",
	"(*database/sql.Tx).QueryRow":   "must not be called. use (*database/sql.Tx).QueryRowContext",
	"(*database/sql.Tx).Stmt":       "must not be called. use (*database/sql.Tx).StmtContext",
	"(*database/sql.Stmt).Exec":     "must not be called. use (*database/sql.Conn).ExecContext",
	"(*database/sql.Stmt).Query":    "must not be called. use (*database/sql.Conn).QueryContext",
	"(*database/sql.Stmt).QueryRow": "must not be called. use (*database/sql.Conn).QueryRowContext",

	// exec
	"os/exec.Command": "must not be called. use os/exec.CommandContext",

	// crypto/tls dialer
	"crypto/tls.Dial":              "must not be called. use (*crypto/tls.Dialer).DialContext",
	"crypto/tls.DialWithDialer":    "must not be called. use (*crypto/tls.Dialer).DialContext with NetDialer",
	"(*crypto/tls.Conn).Handshake": "must not be called. use (*crypto/tls.Conn).HandshakeContext",

	// log/slog is out of scope of this analyzer, as slog doesn't use the [context.Context]
	// for context cancellation, but for key-value logging via the data stored in it.
	//
	// Related discussion: https://github.com/sonatard/noctx/issues/47
}

func Run(pass *analysis.Pass) (interface{}, error) {
	ngFuncs := typeFuncs(pass, slices.Collect(maps.Keys(ngFuncMessages)))
	if len(ngFuncs) == 0 {
		return nil, nil
	}

	ssa, ok := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
	if !ok {
		panic(fmt.Sprintf("%T is not *buildssa.SSA", pass.ResultOf[buildssa.Analyzer]))
	}

	for _, sf := range ssa.SrcFuncs {
		for _, b := range sf.Blocks {
			for _, instr := range b.Instrs {
				for _, ngFunc := range ngFuncs {
					if analysisutil.Called(instr, nil, ngFunc) {
						pass.Reportf(instr.Pos(), "%s %s", ngFunc.FullName(), ngFuncMessages[ngFunc.FullName()])

						break
					}
				}
			}
		}
	}

	return nil, nil
}


================================================
FILE: noctx_test.go
================================================
package noctx_test

import (
	"testing"

	"github.com/sonatard/noctx"
	"golang.org/x/tools/go/analysis/analysistest"
)

func TestAnalyzer(t *testing.T) {
	testCases := []struct {
		desc string
	}{
		{desc: "crypto_tls"},
		{desc: "exec_cmd"},
		{desc: "http_client"},
		{desc: "http_request"},
		{desc: "httptest_request"},
		{desc: "network"},
		{desc: "sql"},
	}

	for _, test := range testCases {
		t.Run(test.desc, func(t *testing.T) {
			analysistest.Run(t, analysistest.TestData(), noctx.Analyzer, test.desc)
		})
	}
}


================================================
FILE: testdata/src/crypto_tls/tls.go
================================================
package crypto

import (
	"context"
	"crypto/tls"
	"net"
)

func _() {
	ctx := context.Background()

	netDialer := &net.Dialer{}

	tlsConfig := &tls.Config{}

	// dialers
	tls.Dial("tcp", "localhost:8080", tlsConfig)                      // want `crypto/tls\.Dial must not be called. use \(\*crypto/tls\.Dialer\)\.DialContext`
	tls.DialWithDialer(netDialer, "tcp", "localhost:8080", tlsConfig) // want `crypto/tls\.DialWithDialer must not be called. use \(\*crypto/tls\.Dialer\)\.DialContext with NetDialer`

	tlsDialer := &tls.Dialer{
		Config:    tlsConfig,
		NetDialer: netDialer,
	}
	tlsDialer.DialContext(ctx, "tcp", "localhost:8080")

	// connection
	tlsConn := &tls.Conn{}
	tlsConn.Handshake() // want `\(\*crypto/tls\.Conn\)\.Handshake must not be called. use \(\*crypto/tls\.Conn\).HandshakeContext`

	tlsConn.HandshakeContext(ctx)
}


================================================
FILE: testdata/src/exec_cmd/exec.go
================================================
package exec

import (
	"context"
	"os/exec"
)

func _() {
	ctx := context.Background()

	exec.Command("ls", "-l") // want `os/exec.Command must not be called. use os/exec.CommandContext`

	exec.CommandContext(ctx, "ls", "-l")
}


================================================
FILE: testdata/src/http_client/http_client.go
================================================
package http_client

import (
	"net/http"
)

func _() {
	const url = "http://example.com"
	cli := &http.Client{}

	http.Get(url) // want `net/http\.Get must not be called. use net/http\.NewRequestWithContext and \(\*net/http.Client\)\.Do\(\*http.Request\)`
	_ = http.Get  // OK
	f := http.Get // OK
	f(url)        // want `net/http\.Get must not be called. use net/http\.NewRequestWithContext and \(\*net/http.Client\)\.Do\(\*http.Request\)`

	http.Head(url)          // want `net/http\.Head must not be called. use net/http\.NewRequestWithContext and \(\*net/http.Client\)\.Do\(\*http.Request\)`
	http.Post(url, "", nil) // want `net/http\.Post must not be called. use net/http\.NewRequestWithContext and \(\*net/http.Client\)\.Do\(\*http.Request\)`
	http.PostForm(url, nil) // want `net/http\.PostForm must not be called. use net/http\.NewRequestWithContext and \(\*net/http.Client\)\.Do\(\*http.Request\)`

	cli.Get(url) // want `\(\*net/http\.Client\)\.Get must not be called. use \(\*net/http.Client\)\.Do\(\*http.Request\)`
	_ = cli.Get  // OK
	m := cli.Get // OK
	m(url)       // want `\(\*net/http\.Client\)\.Get must not be called. use \(\*net/http.Client\)\.Do\(\*http.Request\)`

	cli.Head(url)          // want `\(\*net/http\.Client\)\.Head must not be called. use \(\*net/http.Client\)\.Do\(\*http.Request\)`
	cli.Post(url, "", nil) // want `\(\*net/http\.Client\)\.Post must not be called. use \(\*net/http.Client\)\.Do\(\*http.Request\)`
	cli.PostForm(url, nil) // want `\(\*net/http\.Client\)\.PostForm must not be called. use \(\*net/http.Client\)\.Do\(\*http.Request\)`
}


================================================
FILE: testdata/src/http_request/http_request.go
================================================
package http_request

import (
	"context"
	"net/http"
)

var newRequestPkg = http.NewRequest

func _() {
	const url = "https://example.com"

	cli := &http.Client{}

	ctx := context.Background()

	req, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	cli.Do(req)

	req2, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, nil) // OK
	cli.Do(req2)

	req3, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req3 = req3.WithContext(ctx)
	cli.Do(req3)

	f2 := func(req *http.Request, ctx context.Context) *http.Request {
		return req
	}
	req4, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req4 = f2(req4, ctx)

	req41, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req41 = req41.WithContext(ctx)
	req41 = f2(req41, ctx)

	newRequest := http.NewRequest
	req5, _ := newRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	cli.Do(req5)

	req51, _ := newRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req51 = req51.WithContext(ctx)
	cli.Do(req51)

	req52, _ := newRequestPkg(http.MethodPost, url, nil) // TODO: false negative `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	cli.Do(req52)

	type MyRequest = http.Request
	f3 := func(req *MyRequest, ctx context.Context) *MyRequest {
		return req
	}
	req6, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req6 = f3(req6, ctx)

	req61, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req61 = req61.WithContext(ctx)
	req61 = f3(req61, ctx)

	type MyRequest2 http.Request
	f4 := func(req *MyRequest2, ctx context.Context) *MyRequest2 {
		return req
	}
	req7, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req71 := MyRequest2(*req7)
	f4(&req71, ctx)

	req72, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req72 = req72.WithContext(ctx)
	req73 := MyRequest2(*req7)
	f4(&req73, ctx)

	req8, _ := func() (*http.Request, error) {
		return http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	}()
	cli.Do(req8)

	req82, _ := func() (*http.Request, error) {
		req82, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
		req82 = req82.WithContext(ctx)
		return req82, nil
	}()
	cli.Do(req82)

	f5 := func(req, req2 *http.Request, ctx context.Context) (*http.Request, *http.Request) {
		return req, req2
	}
	req9, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req9, _ = f5(req9, req9, ctx)

	req91, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req91 = req91.WithContext(ctx)
	req9, _ = f5(req91, req91, ctx)

	req10, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req11, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req10, req11 = f5(req10, req11, ctx)

	req101, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req111, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req111 = req111.WithContext(ctx)
	req101, req111 = f5(req101, req111, ctx)

	func() (*http.Request, *http.Request) {
		req12, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
		req13, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
		return req12, req13
	}()

	func() (*http.Request, *http.Request) {
		req14, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
		req15, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
		req15 = req15.WithContext(ctx)

		return req14, req15
	}()

	req121, _ := http.NewRequest(http.MethodPost, url, nil) // want `net/http\.NewRequest must not be called. use net/http\.NewRequestWithContext`
	req121.AddCookie(&http.Cookie{Name: "k", Value: "v"})
	req121 = req121.WithContext(context.WithValue(req121.Context(), struct{}{}, 0))
	cli.Do(req121)
}


================================================
FILE: testdata/src/httptest_request/httptest_request.go
================================================
package httptest_request

import (
	"context"
	"net/http"
	"net/http/httptest"
)

func _() {
	const url = "https://example.com"

	cli := &http.Client{}

	ctx := context.Background()

	req := httptest.NewRequest(http.MethodPost, url, nil) // want `net/http/httptest\.NewRequest must not be called. use net/http/httptest\.NewRequestWithContext`
	cli.Do(req)

	req2 := httptest.NewRequestWithContext(ctx, http.MethodPost, url, nil) // OK
	cli.Do(req2)

	newRequest := httptest.NewRequest
	req3 := newRequest(http.MethodPost, url, nil) // want `net/http/httptest\.NewRequest must not be called. use net/http/httptest\.NewRequestWithContext`
	cli.Do(req3)
}


================================================
FILE: testdata/src/network/net.go
================================================
package network

import (
	"context"
	"net"
	"time"
)

var timeout = 10 * time.Second

func _() {
	ctx := context.Background()

	// listenConfig
	listenConfig := &net.ListenConfig{}

	net.Listen("tcp", "localhost:8080") // want `net\.Listen must not be called. use \(\*net\.ListenConfig\)\.Listen`
	listenConfig.Listen(ctx, "tcp", "localhost:8080")

	net.ListenPacket("udp", "localhost:8080") // want `net\.ListenPacket must not be called. use \(\*net\.ListenConfig\)\.ListenPacket`
	listenConfig.ListenPacket(ctx, "udp", "localhost:8080")

	// dialer
	net.Dial("tcp", "localhost:8080")                 // want `net\.Dial must not be called. use \(\*net\.Dialer\)\.DialContext`
	net.DialTimeout("tcp", "localhost:8080", timeout) // want `net\.DialTimeout must not be called. use \(\*net\.Dialer\)\.DialContext with \(\*net\.Dialer\)\.Timeout`

	dialer := &net.Dialer{}
	dialer.DialContext(ctx, "tcp", "localhost:8080")

	dialerTimeout := &net.Dialer{Timeout: timeout}
	dialerTimeout.DialContext(ctx, "tcp", "localhost:8080")

	// resolver
	net.LookupCNAME("example.com")              // want `net\.LookupCNAME must not be called. use \(\*net\.Resolver\)\.LookupCNAME with a context`
	net.LookupHost("example.com")               // want `net\.LookupHost must not be called. use \(\*net\.Resolver\)\.LookupHost with a context`
	net.LookupIP("example.com")                 // want `net\.LookupIP must not be called. use \(\*net\.Resolver\)\.LookupIPAddr with a context`
	net.LookupPort("tcp", "http")               // want `net\.LookupPort must not be called. use \(\*net\.Resolver\)\.LookupPort with a context`
	net.LookupSRV("http", "tcp", "example.com") // want `net\.LookupSRV must not be called. use \(\*net\.Resolver\)\.LookupSRV with a context`
	net.LookupMX("example.com")                 // want `net\.LookupMX must not be called. use \(\*net\.Resolver\)\.LookupMX with a context`
	net.LookupNS("example.com")                 // want `net\.LookupNS must not be called. use \(\*net\.Resolver\)\.LookupNS with a context`
	net.LookupTXT("example.com")                // want `net\.LookupTXT must not be called. use \(\*net\.Resolver\)\.LookupTXT with a context`
	net.LookupAddr("example.com")               // want `net\.LookupAddr must not be called. use \(\*net\.Resolver\)\.LookupAddr with a context`

	resolver := net.DefaultResolver
	resolver.LookupCNAME(ctx, "example.com")
	resolver.LookupHost(ctx, "example.com")
	resolver.LookupIPAddr(ctx, "example.com")
	resolver.LookupPort(ctx, "tcp", "http")
	resolver.LookupSRV(ctx, "http", "tcp", "example.com")
	resolver.LookupMX(ctx, "example.com")
	resolver.LookupNS(ctx, "example.com")
	resolver.LookupTXT(ctx, "example.com")
	resolver.LookupAddr(ctx, "example.com")
}


================================================
FILE: testdata/src/sql/sql.go
================================================
package http_request

import (
	"context"
	"database/sql"
)

func _() {
	ctx := context.Background()

	db, _ := sql.Open("noctx", "noctx://")

	// database/sql.DB methods

	db.Begin() // want `\(\*database/sql\.DB\)\.Begin must not be called. use \(\*database/sql\.DB\)\.BeginTx`
	db.BeginTx(ctx, nil)

	db.Exec("select * from testdata") // want `\(\*database/sql\.DB\)\.Exec must not be called. use \(\*database/sql\.DB\)\.ExecContext`
	db.ExecContext(ctx, "select * from testdata")

	db.Ping() // want `\(\*database/sql\.DB\)\.Ping must not be called. use \(\*database/sql\.DB\)\.PingContext`
	db.PingContext(ctx)

	db.Prepare("select * from testdata") // want `\(\*database/sql\.DB\)\.Prepare must not be called. use \(\*database/sql\.DB\)\.PrepareContext`
	db.PrepareContext(ctx, "select * from testdata")

	db.Query("select * from testdata") // want `\(\*database/sql\.DB\)\.Query must not be called. use \(\*database/sql\.DB\)\.QueryContext`
	db.QueryContext(ctx, "select * from testdata")

	db.QueryRow("select * from testdata") // want `\(\*database/sql\.DB\)\.QueryRow must not be called. use \(\*database/sql\.DB\)\.QueryRowContext`
	db.QueryRowContext(ctx, "select * from testdata")

	// database/sql.Stmt methods
	stmt, _ := db.PrepareContext(context.Background(), "select * from testdata where id = ?")

	stmt.Query("1") // want `\(\*database/sql\.Stmt\)\.Query must not be called. use \(\*database/sql\.Conn\)\.QueryContext`
	stmt.QueryContext(ctx, "1")

	stmt.QueryRow("1") // want `\(\*database/sql\.Stmt\)\.QueryRow must not be called. use \(\*database/sql\.Conn\)\.QueryRowContext`
	stmt.QueryRowContext(ctx, "1")

	stmt.Exec("1") // want `\(\*database/sql\.Stmt\)\.Exec must not be called. use \(\*database/sql\.Conn\)\.ExecContext`
	stmt.ExecContext(ctx, "1")

	// database/sql.Tx methods
	tx, _ := db.BeginTx(ctx, nil)
	tx.Exec("select * from testdata") // want `\(\*database/sql\.Tx\)\.Exec must not be called. use \(\*database/sql\.Tx\)\.ExecContext`
	tx.ExecContext(ctx, "select * from testdata")

	tx.Prepare("select * from testdata") // want `\(\*database/sql\.Tx\)\.Prepare must not be called. use \(\*database/sql\.Tx\)\.PrepareContext`
	tx.PrepareContext(ctx, "select * from testdata")

	tx.Query("select * from testdata") // want `\(\*database/sql\.Tx\)\.Query must not be called. use \(\*database/sql\.Tx\)\.QueryContext`
	tx.QueryContext(ctx, "select * from testdata")

	tx.QueryRow("select * from testdata") // want `\(\*database/sql\.Tx\)\.QueryRow must not be called. use \(\*database/sql\.Tx\)\.QueryRowContext`
	tx.QueryRowContext(ctx, "select * from testdata")

	tx.Stmt(stmt) // want `\(\*database/sql\.Tx\)\.Stmt must not be called. use \(\*database/sql\.Tx\)\.StmtContext`
	tx.StmtContext(ctx, stmt)

	_ = tx.Commit()

	// database/sql.Conn are safe, they only have context-aware methods
	// these lines are just to show that they are not flagged
	conn, _ := db.Conn(ctx)
	conn.ExecContext(ctx, "select * from testdata")
	conn.QueryContext(ctx, "select * from testdata")
	conn.QueryRowContext(ctx, "select * from testdata")
}


================================================
FILE: types.go
================================================
package noctx

import (
	"errors"
	"go/types"
	"strings"

	"github.com/gostaticanalysis/analysisutil"
	"golang.org/x/tools/go/analysis"
)

var errNotFound = errors.New("function not found")

func typeFuncs(pass *analysis.Pass, funcs []string) []*types.Func {
	fs := make([]*types.Func, 0, len(funcs))

	for _, fn := range funcs {
		f, err := typeFunc(pass, fn)
		if err != nil {
			continue
		}

		fs = append(fs, f)
	}

	return fs
}

func typeFunc(pass *analysis.Pass, funcName string) (*types.Func, error) {
	nameParts := strings.Split(strings.TrimSpace(funcName), ".")

	switch len(nameParts) {
	case 2:
		// package function: pkgname.Func
		f, ok := analysisutil.ObjectOf(pass, nameParts[0], nameParts[1]).(*types.Func)
		if !ok || f == nil {
			return nil, errNotFound
		}

		return f, nil
	case 3:
		// method: (*pkgname.Type).Method
		pkgName := strings.TrimLeft(nameParts[0], "(")
		typeName := strings.TrimRight(nameParts[1], ")")

		if pkgName != "" && pkgName[0] == '*' {
			pkgName = pkgName[1:]
			typeName = "*" + typeName
		}

		typ := analysisutil.TypeOf(pass, pkgName, typeName)
		if typ == nil {
			return nil, errNotFound
		}

		m := analysisutil.MethodOf(typ, nameParts[2])
		if m == nil {
			return nil, errNotFound
		}

		return m, nil
	}

	return nil, errNotFound
}
Download .txt
gitextract_qb9kju5i/

├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── LICENSE
├── Makefile
├── README.md
├── cmd/
│   └── noctx/
│       └── main.go
├── go.mod
├── go.sum
├── noctx.go
├── noctx_test.go
├── testdata/
│   └── src/
│       ├── crypto_tls/
│       │   └── tls.go
│       ├── exec_cmd/
│       │   └── exec.go
│       ├── http_client/
│       │   └── http_client.go
│       ├── http_request/
│       │   └── http_request.go
│       ├── httptest_request/
│       │   └── httptest_request.go
│       ├── network/
│       │   └── net.go
│       └── sql/
│           └── sql.go
└── types.go
Download .txt
SYMBOL INDEX (12 symbols across 11 files)

FILE: cmd/noctx/main.go
  function main (line 8) | func main() { unitchecker.Main(noctx.Analyzer) }

FILE: noctx.go
  function Run (line 83) | func Run(pass *analysis.Pass) (interface{}, error) {

FILE: noctx_test.go
  function TestAnalyzer (line 10) | func TestAnalyzer(t *testing.T) {

FILE: testdata/src/crypto_tls/tls.go
  function _ (line 9) | func _() {

FILE: testdata/src/exec_cmd/exec.go
  function _ (line 8) | func _() {

FILE: testdata/src/http_client/http_client.go
  function _ (line 7) | func _() {

FILE: testdata/src/http_request/http_request.go
  function _ (line 10) | func _() {

FILE: testdata/src/httptest_request/httptest_request.go
  function _ (line 9) | func _() {

FILE: testdata/src/network/net.go
  function _ (line 11) | func _() {

FILE: testdata/src/sql/sql.go
  function _ (line 8) | func _() {

FILE: types.go
  function typeFuncs (line 14) | func typeFuncs(pass *analysis.Pass, funcs []string) []*types.Func {
  function typeFunc (line 29) | func typeFunc(pass *analysis.Pass, funcName string) (*types.Func, error) {
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (38K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 734,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strate"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 565,
    "preview": "name: release\n\non:\n  push:\n    tags:\n      - \"v[0-9]+.[0-9]+.[0-9]+\"\n\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n   "
  },
  {
    "path": ".gitignore",
    "chars": 20,
    "preview": "coverage.out\n/noctx\n"
  },
  {
    "path": ".golangci.yml",
    "chars": 738,
    "preview": "version: \"2\"\n\nformatters:\n  enable:\n    - gofumpt\n    - goimports\n\n\nlinters:\n  default: all\n  disable:\n    - exhaustruct"
  },
  {
    "path": ".goreleaser.yml",
    "chars": 699,
    "preview": "version: 2\nproject_name: noctx\n\nbuilds:\n  - binary: noctx\n\n    main: ./cmd/noctx/main.go\n    env:\n      - CGO_ENABLED=0\n"
  },
  {
    "path": "LICENSE",
    "chars": 1065,
    "preview": "MIT License\n\nCopyright (c) 2020 sonatard\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
  },
  {
    "path": "Makefile",
    "chars": 263,
    "preview": ".PHONY: all fmt test lint build\n\nbuild:\n\tgo build -ldflags \"-s -w\" -trimpath ./cmd/noctx/\n\nfmt:\n\tgolangci-lint fmt ./..."
  },
  {
    "path": "README.md",
    "chars": 3459,
    "preview": "# noctx\n\n![](https://github.com/sonatard/noctx/workflows/CI/badge.svg)\n\n`noctx` finds function calls without context.Con"
  },
  {
    "path": "cmd/noctx/main.go",
    "chars": 150,
    "preview": "package main\n\nimport (\n\t\"github.com/sonatard/noctx\"\n\t\"golang.org/x/tools/go/analysis/unitchecker\"\n)\n\nfunc main() { unitc"
  },
  {
    "path": "go.mod",
    "chars": 280,
    "preview": "module github.com/sonatard/noctx\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/gostaticanalysis/analysisutil v0.7.1\n\tgolang.org/x/to"
  },
  {
    "path": "go.sum",
    "chars": 5333,
    "preview": "github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/g"
  },
  {
    "path": "noctx.go",
    "chars": 5418,
    "preview": "package noctx\n\nimport (\n\t\"fmt\"\n\t\"maps\"\n\t\"slices\"\n\n\t\"github.com/gostaticanalysis/analysisutil\"\n\t\"golang.org/x/tools/go/an"
  },
  {
    "path": "noctx_test.go",
    "chars": 525,
    "preview": "package noctx_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/sonatard/noctx\"\n\t\"golang.org/x/tools/go/analysis/analysistest\"\n)\n\n"
  },
  {
    "path": "testdata/src/crypto_tls/tls.go",
    "chars": 843,
    "preview": "package crypto\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net\"\n)\n\nfunc _() {\n\tctx := context.Background()\n\n\tnetDialer := &net."
  },
  {
    "path": "testdata/src/exec_cmd/exec.go",
    "chars": 229,
    "preview": "package exec\n\nimport (\n\t\"context\"\n\t\"os/exec\"\n)\n\nfunc _() {\n\tctx := context.Background()\n\n\texec.Command(\"ls\", \"-l\") // wa"
  },
  {
    "path": "testdata/src/http_client/http_client.go",
    "chars": 1590,
    "preview": "package http_client\n\nimport (\n\t\"net/http\"\n)\n\nfunc _() {\n\tconst url = \"http://example.com\"\n\tcli := &http.Client{}\n\n\thttp."
  },
  {
    "path": "testdata/src/http_request/http_request.go",
    "chars": 5311,
    "preview": "package http_request\n\nimport (\n\t\"context\"\n\t\"net/http\"\n)\n\nvar newRequestPkg = http.NewRequest\n\nfunc _() {\n\tconst url = \"h"
  },
  {
    "path": "testdata/src/httptest_request/httptest_request.go",
    "chars": 653,
    "preview": "package httptest_request\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n)\n\nfunc _() {\n\tconst url = \"https://examp"
  },
  {
    "path": "testdata/src/network/net.go",
    "chars": 2724,
    "preview": "package network\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n)\n\nvar timeout = 10 * time.Second\n\nfunc _() {\n\tctx := context.Backgr"
  },
  {
    "path": "testdata/src/sql/sql.go",
    "chars": 3067,
    "preview": "package http_request\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n)\n\nfunc _() {\n\tctx := context.Background()\n\n\tdb, _ := sql.Open"
  },
  {
    "path": "types.go",
    "chars": 1289,
    "preview": "package noctx\n\nimport (\n\t\"errors\"\n\t\"go/types\"\n\t\"strings\"\n\n\t\"github.com/gostaticanalysis/analysisutil\"\n\t\"golang.org/x/too"
  }
]

About this extraction

This page contains the full source code of the sonatard/noctx GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (34.1 KB), approximately 12.1k tokens, and a symbol index with 12 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!