Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

250 lines
9.7 KiB
Go

package parser
import (
"testing"
"github.com/stretchr/testify/assert"
)
// ambiguityRegistry builds a registry whose extension map exercises
// the ambiguous extensions (.h -> c, .m -> objc) and registers every
// language a content probe can resolve to.
func ambiguityRegistry() *Registry {
r := NewRegistry()
r.Register(&mockExtractor{lang: "c", exts: []string{".c", ".h"}})
r.Register(&mockExtractor{lang: "objc", exts: []string{".m", ".mm"}})
r.Register(&mockExtractor{lang: "cpp", exts: []string{".cpp", ".hpp"}})
r.Register(&mockExtractor{lang: "matlab", exts: []string{".mlx"}})
r.Register(&mockExtractor{lang: "mathematica", exts: []string{".wl"}})
r.Register(&mockExtractor{lang: "perl", exts: []string{".pl"}})
r.Register(&mockExtractor{lang: "python", exts: []string{".py"}})
r.Register(&mockExtractor{lang: "bash", exts: []string{".sh"}})
// .xml defaults to the generic xml extractor; a MyBatis mapper is
// content-routed to "mybatis" (which claims no extension here, so it
// never overrides the .xml default).
r.Register(&mockExtractor{lang: "mybatis"})
r.Register(&mockExtractor{lang: "spring"})
r.Register(&mockExtractor{lang: "xml", exts: []string{".xml"}})
// .json defaults to the generic json extractor; a Shopify OS 2.0 theme
// template is content+path-routed to "liquid_json" (which claims no
// extension here, so it never overrides the .json default).
r.Register(&mockExtractor{lang: "liquid_json"})
r.Register(&mockExtractor{lang: "json", exts: []string{".json"}})
return r
}
func TestSniffShebang(t *testing.T) {
cases := []struct {
name string
content string
want string
ok bool
}{
{"direct bash", "#!/bin/bash\necho hi\n", "bash", true},
{"direct sh", "#!/bin/sh\n", "bash", true},
{"env python3", "#!/usr/bin/env python3\n", "python", true},
{"env python", "#!/usr/bin/env python\n", "python", true},
{"direct perl", "#!/usr/bin/perl -w\n", "perl", true},
{"env -S flag", "#!/usr/bin/env -S python3 -u\n", "python", true},
{"env var assignment", "#!/usr/bin/env FOO=bar ruby\n", "ruby", true},
{"versioned interpreter", "#!/usr/bin/python3.11\n", "python", true},
{"node", "#!/usr/bin/env node\n", "javascript", true},
{"no shebang", "echo hi\n", "", false},
{"empty", "", "", false},
{"unknown interpreter", "#!/usr/bin/env cobol-run\n", "", false},
{"hash but no bang", "# a comment\n", "", false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
lang, ok := sniffShebang([]byte(tc.content))
assert.Equal(t, tc.ok, ok)
assert.Equal(t, tc.want, lang)
})
}
// A UTF-8 BOM ahead of the shebang must not defeat detection.
bom := append([]byte{0xEF, 0xBB, 0xBF}, []byte("#!/bin/bash\n")...)
lang, ok := sniffShebang(bom)
assert.True(t, ok)
assert.Equal(t, "bash", lang)
}
func TestDetectLanguageContent_Shebang(t *testing.T) {
r := ambiguityRegistry()
// A .cgi file has no extension mapping — the shebang resolves it.
lang, ok := r.DetectLanguageContent("cgi-bin/handler.cgi", []byte("#!/usr/bin/perl\nprint \"hi\";\n"))
assert.True(t, ok)
assert.Equal(t, "perl", lang)
// An extensionless script is resolved purely by its shebang.
lang, ok = r.DetectLanguageContent("bin/deploy", []byte("#!/usr/bin/env bash\nset -e\n"))
assert.True(t, ok)
assert.Equal(t, "bash", lang)
// An unknown extension with no shebang stays unresolved.
_, ok = r.DetectLanguageContent("data/notes.cgi", []byte("just some text\n"))
assert.False(t, ok)
// A shebang mapping to an unregistered language is ignored.
bare := NewRegistry()
bare.Register(&mockExtractor{lang: "go", exts: []string{".go"}})
_, ok = bare.DetectLanguageContent("x.cgi", []byte("#!/usr/bin/perl\n"))
assert.False(t, ok)
}
func TestDetectLanguageContent_AmbiguousHeader(t *testing.T) {
r := ambiguityRegistry()
// Plain C header — no probe signal, keeps the extension default.
lang, ok := r.DetectLanguageContent("inc/list.h", []byte("int list_len(struct list *l);\n"))
assert.True(t, ok)
assert.Equal(t, "c", lang)
// C++ header — a namespace / class gives it away.
lang, ok = r.DetectLanguageContent("inc/widget.h", []byte("namespace ui {\nclass Widget {};\n}\n"))
assert.True(t, ok)
assert.Equal(t, "cpp", lang)
// C++ header detected via the :: scope operator alone.
lang, ok = r.DetectLanguageContent("inc/util.h", []byte("inline int f() { return std::max(1, 2); }\n"))
assert.True(t, ok)
assert.Equal(t, "cpp", lang)
// Objective-C header.
lang, ok = r.DetectLanguageContent("inc/View.h", []byte("#import <Foundation/Foundation.h>\n@interface View\n@end\n"))
assert.True(t, ok)
assert.Equal(t, "objc", lang)
}
func TestDetectLanguageContent_CFragments(t *testing.T) {
r := NewRegistry()
r.Register(&mockExtractor{lang: "c", exts: []string{".c", ".h", ".def"}})
r.Register(&mockExtractor{lang: "php", exts: []string{".php", ".inc"}})
r.Register(&mockExtractor{lang: "pascal", exts: []string{".pas", ".inc"}})
r.Register(&mockExtractor{lang: "assembly", exts: []string{".asm", ".inc"}})
// A generated .def command table maps to C unconditionally (uncontested).
lang, ok := r.DetectLanguageContent("src/commands.def", []byte("{MAKE_CMD(\"get\", getCommand, 2)},\n"))
assert.True(t, ok)
assert.Equal(t, "c", lang)
// A clearly-C .inc fragment is content-routed to C despite the contested
// extension.
lang, ok = r.DetectLanguageContent("src/table.inc", []byte("#include \"server.h\"\n{MAKE_CMD(\"x\", xCommand)},\n"))
assert.True(t, ok)
assert.Equal(t, "c", lang)
// A PHP include is never stolen by the C reroute.
lang, _ = r.DetectLanguageContent("web/util.inc", []byte("<?php\nfunction f() {}\n"))
assert.NotEqual(t, "c", lang, "php include must not be detected as C")
// A Pascal include is never stolen by the C reroute.
lang, _ = r.DetectLanguageContent("src/types.inc", []byte("unit Types;\ninterface\nprocedure Foo;\n"))
assert.NotEqual(t, "c", lang, "pascal include must not be detected as C")
}
func TestDetectLanguageContent_AmbiguousDotM(t *testing.T) {
r := ambiguityRegistry()
// Objective-C implementation.
lang, ok := r.DetectLanguageContent("View.m", []byte("#import \"View.h\"\n@implementation View\n@end\n"))
assert.True(t, ok)
assert.Equal(t, "objc", lang)
// MATLAB function file.
lang, ok = r.DetectLanguageContent("solve.m", []byte("function y = solve(x)\n y = x .^ 2;\nend\n"))
assert.True(t, ok)
assert.Equal(t, "matlab", lang)
// MATLAB class file.
lang, ok = r.DetectLanguageContent("Point.m", []byte("classdef Point\n properties\n x\n end\nend\n"))
assert.True(t, ok)
assert.Equal(t, "matlab", lang)
// Mathematica package.
lang, ok = r.DetectLanguageContent("Pkg.m", []byte("BeginPackage[\"Pkg`\"]\nf[x_] := x^2\nEndPackage[]\n"))
assert.True(t, ok)
assert.Equal(t, "mathematica", lang)
// No probe signal — .m keeps its Objective-C default.
lang, ok = r.DetectLanguageContent("plain.m", []byte("int main() { return 0; }\n"))
assert.True(t, ok)
assert.Equal(t, "objc", lang)
}
func TestDetectLanguageContent_MyBatisMapper(t *testing.T) {
r := ambiguityRegistry()
mapper := []byte(`<?xml version="1.0"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "...">
<mapper namespace="com.app.UserMapper">
<select id="findUser" resultType="User">SELECT * FROM users WHERE id = #{id}</select>
</mapper>`)
lang, ok := r.DetectLanguageContent("UserMapper.xml", mapper)
assert.True(t, ok)
assert.Equal(t, "mybatis", lang, "a mapper document routes to the mybatis extractor")
// A Spring beans XML routes to the spring extractor.
beans := []byte(`<?xml version="1.0"?>
<beans xmlns="http://www.springframework.org/schema/beans">
<bean id="svc" class="com.app.Svc"/>
</beans>`)
lang, ok = r.DetectLanguageContent("applicationContext.xml", beans)
assert.True(t, ok)
assert.Equal(t, "spring", lang)
// A generic XML document keeps the xml default.
lang, ok = r.DetectLanguageContent("config.xml", []byte(`<?xml version="1.0"?><config><name>x</name></config>`))
assert.True(t, ok)
assert.Equal(t, "xml", lang)
}
func TestDetectLanguageContent_ShopifyJSON(t *testing.T) {
r := ambiguityRegistry()
tmpl := []byte(`{"sections":{"main":{"type":"main-product"}},"order":["main"]}`)
// A theme JSON template / section group routes to the liquid_json linker.
lang, ok := r.DetectLanguageContent("templates/product.json", tmpl)
assert.True(t, ok)
assert.Equal(t, "liquid_json", lang)
lang, ok = r.DetectLanguageContent("sections/header-group.json", tmpl)
assert.True(t, ok)
assert.Equal(t, "liquid_json", lang)
// A plain package.json keeps the generic JSON default (precise sniff).
lang, ok = r.DetectLanguageContent("package.json", []byte(`{"name":"x","dependencies":{}}`))
assert.True(t, ok)
assert.Equal(t, "json", lang)
// A theme-path JSON without section markers stays JSON.
lang, ok = r.DetectLanguageContent("templates/settings.json", []byte(`{"foo":"bar"}`))
assert.True(t, ok)
assert.Equal(t, "json", lang)
// Section-shaped content outside templates/ or sections/ stays JSON (the
// path gate keeps the sniff precise).
lang, ok = r.DetectLanguageContent("config/app.json", tmpl)
assert.True(t, ok)
assert.Equal(t, "json", lang)
}
func TestDetectLanguageContent_NilContentMatchesNameOnly(t *testing.T) {
r := ambiguityRegistry()
// With nil content, DetectLanguageContent must agree with the
// name-only DetectLanguage for every kind of path.
for _, p := range []string{"a.h", "b.m", "c.py", "d.unknown", "e.cgi"} {
nameLang, nameOK := r.DetectLanguage(p)
contentLang, contentOK := r.DetectLanguageContent(p, nil)
assert.Equal(t, nameOK, contentOK, p)
assert.Equal(t, nameLang, contentLang, p)
}
// A known extension still wins even when content would otherwise
// probe — a probe never fires for an unambiguous extension.
lang, ok := r.DetectLanguageContent("script.py", []byte("#!/usr/bin/env bash\n"))
assert.True(t, ok)
assert.Equal(t, "python", lang)
}