Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ linters:
- vendor
- dist
- site
- node_modules
rules:
# Test files use literal strings for clarity
- linters: [goconst]
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
google.golang.org/protobuf v1.36.12 // indirect
)

ignore node_modules
58 changes: 58 additions & 0 deletions internal/compliance/go_mod_ignore_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// / ctx: https://ctx.ist
// ,'`./ do you remember?
// `.,'\
// \ Copyright 2026-present Context contributors.
// SPDX-License-Identifier: Apache-2.0

package compliance

import (
"encoding/json"
"os/exec"
"testing"
)

// TestGoModIgnoresNodeModules verifies that the root go.mod
// carries an `ignore node_modules` directive.
//
// npm packages may ship Go sources (flatted does, under
// flatted/golang/). Without the directive, `./...` matches
// them as packages of this module, so `go build`, `go vet`,
// `go test`, and golangci-lint all compile and lint
// third-party code the moment anyone runs `npm install` in
// editors/vscode, ctx-desktop, or tools/typecheck/*. The
// bare form (no `./` prefix) matches a node_modules
// directory at any depth.
//
// The directive is read through `go mod edit -json` rather
// than by string search, so an ignore block or a reformat
// cannot fool the check.
//
// See specs/go-ignore-node-modules.md.
func TestGoModIgnoresNodeModules(t *testing.T) {
root := projectRoot(t)

cmd := exec.Command("go", "mod", "edit", "-json")
cmd.Dir = root
out, runErr := cmd.Output()
if runErr != nil {
t.Fatalf("go mod edit -json: %v", runErr)
}

var mod struct {
Ignore []struct {
Path string
}
}
if parseErr := json.Unmarshal(out, &mod); parseErr != nil {
t.Fatalf("parse go mod edit -json output: %v", parseErr)
}

for _, ig := range mod.Ignore {
if ig.Path == "node_modules" {
return
}
}
t.Errorf("go.mod is missing `ignore node_modules`; " +
"add it with: go mod edit -ignore=node_modules")
}
65 changes: 65 additions & 0 deletions specs/go-ignore-node-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Keep node_modules Out of the Go Module

The repo holds four npm packages (`editors/vscode`, `ctx-desktop`,
`tools/typecheck/opencode`, `tools/typecheck/pi`) inside the root
Go module. npm dependencies may ship Go sources, and nothing kept
Go tooling from treating them as part of ctx.

## Problem

`flatted` (pulled in by eslint through `flat-cache`) ships
`flatted/golang/pkg/flatted/flatted.go`. After `npm install` in
`editors/vscode`:

- `go list ./...` reports
`github.com/ActiveMemory/ctx/editors/vscode/node_modules/flatted/golang/pkg/flatted`
as a package of this module, so `go build`, `go vet`, and
`go test ./...` compile it.
- `make lint` fails with two `govet` findings inside that file.
- `make test` fails in `TestGolangciLint`, which shells out to
golangci-lint.

CI never saw it, because the Go jobs don't run `npm install`. Any
contributor who works on the extension and then runs `make lint`
does.

`./...` skips only directories starting with `.` or `_`,
`testdata`, and nested modules; `node_modules` is none of those.
golangci-lint's `exclusions.paths` only hides reported issues: the
package is still loaded and type-checked.

## Solution

1. **`go.mod`: `ignore node_modules`** (Go 1.25+). The bare form,
without a `./` prefix, matches a `node_modules` directory at
any depth (`./node_modules` would match only the root, which
was verified to leave the package in `go list ./...`). This
removes the directory from `./...` for every Go command,
golangci-lint included, since it loads packages through the
go command.
2. **`.golangci.yml`: `node_modules` in `exclusions.paths`**,
beside `vendor`, `dist`, and `site`. With (1) in place this is
belt-and-braces; it states in the linter's own config that
node code is not in scope, and holds if golangci-lint is ever
pointed at an explicit path.
3. **Regression guard:**
`internal/compliance/go_mod_ignore_test.go` reads go.mod via
`go mod edit -json` and fails if the directive is missing.

The compliance tests' own file walkers (`allGoFiles`,
`allSourceFiles`) already skip `node_modules`; they need no
change.

## Verification

With `editors/vscode/node_modules` installed:

- `go list ./... | grep node_modules` prints nothing.
- `make lint`: 0 issues. `make test`: passes.
- `TestGoModIgnoresNodeModules` passes, and fails after
`go mod edit -dropignore=node_modules`.

## Non-Goals

- `tools/ctxctl` is a separate module with no npm package inside
it; its go.mod is unchanged.
Loading