fix: dep_scanner false positive — JS import inside Python f-string (#564)

* fix: skip JS ES module imports in Python string literals during dep scan

dep_scanner falsely matched JS `import mermaid from '...'` inside Python
f-strings as a pip dependency, causing functional skills to be archived.

Add jsFromStringRe check: if a line matching pyImportRe also contains
`from '...'`/`from "..."` (JS ES module pattern), skip it. Valid Python
`import X` never has `from '...'` on the same line.

Closes #544

* fix: add missing stub method + additional edge case tests

- Add MigrateUserDataOnMerge to stubAgentStore in context_file_interceptor_test.go
  (fixes CI build failure — stub was missing new AgentStore interface method)
- Add TestScanFile_MultipleJSImportsInsidePythonString for broader false positive coverage

---------

Co-authored-by: viettranx <viettranx@gmail.com>
This commit is contained in:
Thieu Nguyenandviettranx authored and GitHub committed 2026-03-30 08:37:30 +07:00
1 parent 7f897fe9f5
commit e7afa83245
3 files changed
+101 -1

No files matched your search

+7 -1
View File
@@ -120,6 +120,9 @@ var (
nodeRequireRe = regexp.MustCompile(`require\(['"]([\w@][^'"]*)['"]\)`)
nodeESImportRe = regexp.MustCompile(`from\s+['"]([^'"./][^'"]*?)['"]`)
shebangRe = regexp.MustCompile(`^#!\s*/usr/bin/env\s+(\S+)`)
// Detects JS ES module pattern: `import X from '...'` or `from '...'`.
// Used to skip false positives when JS imports appear inside Python string literals.
jsFromStringRe = regexp.MustCompile(`from\s+['"]`)
)
func scanFile(path string, pyImports, nodeImports map[string]bool, binaries map[string]bool) {
@@ -143,7 +146,10 @@ func scanFile(path string, pyImports, nodeImports map[string]bool, binaries map[
for _, line := range strings.Split(content, "\n") {
line = strings.TrimSpace(line)
if m := pyImportRe.FindStringSubmatch(line); len(m) > 1 {
pyImports[m[1]] = true
// Skip JS ES module imports inside string literals (e.g. `import mermaid from '...'`)
if !jsFromStringRe.MatchString(line) {
pyImports[m[1]] = true
}
}
if m := pyFromRe.FindStringSubmatch(line); len(m) > 1 {
pyImports[m[1]] = true
@@ -0,0 +1,91 @@
package skills
import (
"os"
"path/filepath"
"testing"
)
func TestScanFile_JSImportInsidePythonFString(t *testing.T) {
dir := t.TempDir()
pyFile := filepath.Join(dir, "render.py")
// Python file with JS ES module import inside f-string (issue #544)
content := `#!/usr/bin/env python3
import sys
import json
def render_html(text):
mermaid_init = f"""
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({{ startOnLoad: true }});
</script>
"""
return f"<html>{text}{mermaid_init}</html>"
`
if err := os.WriteFile(pyFile, []byte(content), 0644); err != nil {
t.Fatal(err)
}
pyImports := make(map[string]bool)
nodeImports := make(map[string]bool)
binaries := make(map[string]bool)
scanFile(pyFile, pyImports, nodeImports, binaries)
// sys and json are real Python imports — should be detected
if !pyImports["sys"] {
t.Error("expected sys to be detected as Python import")
}
if !pyImports["json"] {
t.Error("expected json to be detected as Python import")
}
// mermaid is a JS import inside f-string — should NOT be detected
if pyImports["mermaid"] {
t.Error("FALSE POSITIVE: mermaid detected as Python import — it's a JS import inside f-string")
}
}
func TestScanFile_MultipleJSImportsInsidePythonString(t *testing.T) {
dir := t.TempDir()
pyFile := filepath.Join(dir, "template.py")
// Multiple JS imports inside a Python string + real Python imports
content := `import os
import subprocess
TEMPLATE = """
<script type="module">
import React from 'https://cdn.example.com/react.js';
import lodash from 'https://cdn.example.com/lodash.js';
</script>
"""
`
if err := os.WriteFile(pyFile, []byte(content), 0644); err != nil {
t.Fatal(err)
}
pyImports := make(map[string]bool)
nodeImports := make(map[string]bool)
binaries := make(map[string]bool)
scanFile(pyFile, pyImports, nodeImports, binaries)
// Real Python imports
if !pyImports["os"] {
t.Error("expected os to be detected as Python import")
}
if !pyImports["subprocess"] {
t.Error("expected subprocess to be detected as Python import")
}
// JS imports inside string — should NOT be detected
if pyImports["React"] {
t.Error("FALSE POSITIVE: React detected as Python import")
}
if pyImports["lodash"] {
t.Error("FALSE POSITIVE: lodash detected as Python import")
}
}
@@ -90,6 +90,9 @@ func (s *stubAgentStore) EnsureUserProfile(_ context.Context, _ uuid.UUID, _ str
func (s *stubAgentStore) PropagateContextFile(_ context.Context, _ uuid.UUID, _ string) (int, error) {
return 0, nil
}
func (s *stubAgentStore) MigrateUserDataOnMerge(_ context.Context, _ []string, _ string) error {
return nil
}
// ---- Tests ----