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
148 lines
4.7 KiB
Go
148 lines
4.7 KiB
Go
package languages
|
|
|
|
import (
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
"github.com/zzet/gortex/internal/parser"
|
|
)
|
|
|
|
// Expo Modules cross-language support. An Expo native module is declared
|
|
// in Swift or Kotlin with a DSL inside `definition() -> ModuleDefinition`:
|
|
// `Name("Foo")` sets the JS module name and `Function("bar") { ... }` /
|
|
// `AsyncFunction("baz") { ... }` declare the JS-callable methods, while
|
|
// `Property("p")`, `Events("e")`, and `Constants { ... }` declare the other
|
|
// member kinds. On the JS side they are consumed via
|
|
// `requireNativeModule('Foo').bar(...)` — which the JS/TS extractor already
|
|
// emits as an rn-native placeholder, so the Expo bridge synthesizer can land
|
|
// it on these synthetic nodes.
|
|
|
|
var (
|
|
expoNameRe = regexp.MustCompile(`\bName\s*\(\s*"([^"]+)"`)
|
|
expoMemberRe = regexp.MustCompile(`\b(Function|AsyncFunction|Property|Events)\s*(?:<[^(]*>)?\s*\(\s*"([^"]+)"`)
|
|
expoConstantsRe = regexp.MustCompile(`\bConstants\s*[({]`)
|
|
expoModuleClassRe = regexp.MustCompile(`\bclass\s+(\w+)\s*(?:\([^)]*\))?\s*:\s*Module\b`)
|
|
)
|
|
|
|
// expoExport is one Expo DSL member declaration (function, property, events,
|
|
// or constants) attributed to a module.
|
|
type expoExport struct {
|
|
module string
|
|
method string
|
|
async bool
|
|
off int
|
|
kind string
|
|
}
|
|
|
|
// extractExpoModules scans Swift/Kotlin source for the Expo module DSL and
|
|
// returns each member declaration attributed to the most recent preceding
|
|
// Name("..."). A definition() body with no Name() falls back to its enclosing
|
|
// `class XxxModule : Module` (the "Module" suffix stripped) rather than being
|
|
// dropped. Returns nil when the file is not an Expo module (no
|
|
// ModuleDefinition marker).
|
|
func extractExpoModules(src []byte) []expoExport {
|
|
s := string(src)
|
|
if !strings.Contains(s, "ModuleDefinition") {
|
|
return nil
|
|
}
|
|
type marker struct {
|
|
off int
|
|
kind string
|
|
name string
|
|
async bool
|
|
}
|
|
var markers []marker
|
|
for _, m := range expoNameRe.FindAllStringSubmatchIndex(s, -1) {
|
|
markers = append(markers, marker{off: m[0], kind: "name", name: s[m[2]:m[3]]})
|
|
}
|
|
for _, m := range expoMemberRe.FindAllStringSubmatchIndex(s, -1) {
|
|
kw := s[m[2]:m[3]]
|
|
kind := "function"
|
|
switch kw {
|
|
case "Property":
|
|
kind = "property"
|
|
case "Events":
|
|
kind = "events"
|
|
}
|
|
markers = append(markers, marker{off: m[0], kind: kind, name: s[m[4]:m[5]], async: kw == "AsyncFunction"})
|
|
}
|
|
for _, m := range expoConstantsRe.FindAllStringIndex(s, -1) {
|
|
markers = append(markers, marker{off: m[0], kind: "constants", name: "Constants"})
|
|
}
|
|
sort.Slice(markers, func(i, j int) bool { return markers[i].off < markers[j].off })
|
|
|
|
// Module classes back the no-Name() fallback.
|
|
type classDecl struct {
|
|
off int
|
|
name string
|
|
}
|
|
var classes []classDecl
|
|
for _, m := range expoModuleClassRe.FindAllStringSubmatchIndex(s, -1) {
|
|
classes = append(classes, classDecl{off: m[0], name: s[m[2]:m[3]]})
|
|
}
|
|
enclosingModule := func(off int) string {
|
|
name, best := "", -1
|
|
for _, cd := range classes {
|
|
if cd.off <= off && cd.off > best {
|
|
best, name = cd.off, cd.name
|
|
}
|
|
}
|
|
if name == "" {
|
|
return ""
|
|
}
|
|
if trimmed := strings.TrimSuffix(name, "Module"); trimmed != "" {
|
|
return trimmed
|
|
}
|
|
return name
|
|
}
|
|
|
|
curModule := ""
|
|
var out []expoExport
|
|
for _, mk := range markers {
|
|
if mk.kind == "name" {
|
|
curModule = mk.name
|
|
continue
|
|
}
|
|
module := curModule
|
|
if module == "" {
|
|
module = enclosingModule(mk.off)
|
|
}
|
|
if module == "" {
|
|
continue
|
|
}
|
|
out = append(out, expoExport{module: module, method: mk.name, async: mk.async, off: mk.off, kind: mk.kind})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// emitExpoModuleNodes materialises a synthetic node per Expo member
|
|
// declaration, carrying expo_module + expo_method + expo_kind so the Expo
|
|
// bridge synthesizer can pair a JS requireNativeModule('<module>').<member>
|
|
// access to it. A Property becomes a field node; every other member kind a
|
|
// method node.
|
|
func emitExpoModuleNodes(src []byte, filePath, language, fileID string, result *parser.ExtractionResult, seen map[string]bool) {
|
|
for _, ex := range extractExpoModules(src) {
|
|
id := filePath + "::expo:" + ex.module + ":" + ex.method
|
|
if seen[id] {
|
|
continue
|
|
}
|
|
seen[id] = true
|
|
line := lineAt(src, ex.off)
|
|
nodeKind := graph.KindMethod
|
|
if ex.kind == "property" {
|
|
nodeKind = graph.KindField
|
|
}
|
|
result.Nodes = append(result.Nodes, &graph.Node{
|
|
ID: id, Kind: nodeKind, Name: ex.method,
|
|
FilePath: filePath, StartLine: line, EndLine: line,
|
|
Language: language,
|
|
Meta: map[string]any{"expo_module": ex.module, "expo_method": ex.method, "expo_async": ex.async, "expo_kind": ex.kind},
|
|
})
|
|
result.Edges = append(result.Edges, &graph.Edge{
|
|
From: fileID, To: id, Kind: graph.EdgeDefines, FilePath: filePath, Line: line,
|
|
})
|
|
}
|
|
}
|