Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ Current inference behavior:

TypeScript semantic runtime:
- native TypeScript and JavaScript built-ins use the TypeScript compiler API when `typescript.js` is available
- discovery order is `CODEGUARD_TYPESCRIPT_LIB_PATH`, then `node_modules/typescript/lib/typescript.js` from the target path upward, then the bundled VS Code TypeScript runtime
- discovery uses an explicitly trusted `CODEGUARD_TYPESCRIPT_LIB_PATH`, then the bundled VS Code TypeScript runtime; repository-local `node_modules` runtimes are never executed automatically
- if no runtime is available, codeguard falls back to the lightweight parser-based checks for TypeScript and JavaScript

Tree-sitter parsing (opt-in):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package support

import (
"os"
"path/filepath"
"strings"
)

Expand All @@ -12,46 +11,18 @@ var defaultTypeScriptLibCandidates = []string{
"/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/node_modules/typescript/lib/typescript.js",
}

func discoverTypeScriptLibPath(targetPath string) string {
func discoverTypeScriptLibPath(_ string) string {
if candidate := strings.TrimSpace(os.Getenv(codeguardTypeScriptLibEnv)); isTypeScriptLibPath(candidate) {
return candidate
}
for _, candidate := range typeScriptLibCandidates(targetPath) {
for _, candidate := range defaultTypeScriptLibCandidates {
if isTypeScriptLibPath(candidate) {
return candidate
}
}
return ""
}

func typeScriptLibCandidates(targetPath string) []string {
candidates := make([]string, 0, 8)
for _, dir := range ancestorPaths(targetPath) {
candidates = append(candidates, filepath.Join(dir, "node_modules", "typescript", "lib", "typescript.js"))
}
return append(candidates, defaultTypeScriptLibCandidates...)
}

func ancestorPaths(path string) []string {
path = strings.TrimSpace(path)
if path == "" {
return nil
}
current, err := filepath.Abs(path)
if err != nil {
current = path
}
paths := make([]string, 0, 6)
for {
paths = append(paths, current)
parent := filepath.Dir(current)
if parent == current {
return paths
}
current = parent
}
}

func isTypeScriptLibPath(path string) bool {
if strings.TrimSpace(path) == "" {
return false
Expand Down
32 changes: 32 additions & 0 deletions internal/codeguard/checks/support/typescript_semantic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,38 @@ import (
"github.com/devr-tools/codeguard/internal/codeguard/core"
)

func TestTypeScriptRuntimeDiscoveryIgnoresTargetDependencies(t *testing.T) {
t.Setenv(codeguardTypeScriptLibEnv, "")
previousDefaults := defaultTypeScriptLibCandidates
defaultTypeScriptLibCandidates = nil
t.Cleanup(func() { defaultTypeScriptLibCandidates = previousDefaults })

target := t.TempDir()
repositoryRuntime := filepath.Join(target, "node_modules", "typescript", "lib", "typescript.js")
if err := os.MkdirAll(filepath.Dir(repositoryRuntime), 0o750); err != nil {
t.Fatalf("create repository runtime directory: %v", err)
}
if err := os.WriteFile(repositoryRuntime, []byte("malicious JavaScript"), 0o600); err != nil {
t.Fatalf("write repository runtime: %v", err)
}

if got := discoverTypeScriptLibPath(target); got != "" {
t.Fatalf("discovered repository-controlled TypeScript runtime %q", got)
}
}

func TestTypeScriptRuntimeDiscoveryHonorsExplicitRuntime(t *testing.T) {
configuredRuntime := filepath.Join(t.TempDir(), "typescript.js")
if err := os.WriteFile(configuredRuntime, []byte("trusted JavaScript"), 0o600); err != nil {
t.Fatalf("write configured runtime: %v", err)
}
t.Setenv(codeguardTypeScriptLibEnv, configuredRuntime)

if got := discoverTypeScriptLibPath(t.TempDir()); got != configuredRuntime {
t.Fatalf("discovered runtime = %q, want explicitly configured runtime %q", got, configuredRuntime)
}
}

func TestTypeScriptSemanticRunnerIntersectsCorpusWithConfiguredFiles(t *testing.T) {
if _, err := exec.LookPath("node"); err != nil {
t.Skip("node is required for the embedded semantic runner test")
Expand Down
Loading