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

99 lines
3.2 KiB
Go

package contracts
import (
"regexp"
"strings"
"github.com/zzet/gortex/internal/graph"
)
// -----------------------------------------------------------------------------
// Dart — dio + package:http consumers
// -----------------------------------------------------------------------------
//
// Flutter apps are almost always the consumer side of HTTP contracts.
// Both dio and package:http accept a payload argument (`data:` or
// `body:`) and return a `Response`/`http.Response` that is then
// deserialised via `fromJson(...)`. We pick up the request type from
// the payload variable and the response type from the `fromJson`
// call's receiver.
func init() {
schemaEnrichers = append(schemaEnrichers,
schemaEnricher{
name: "dart-consumer",
languages: []string{"dart"},
roles: []Role{RoleConsumer},
detect: dartConsumerDetect,
},
)
}
var (
// dio.post(url, data: payload) — also handles keyword arg `data:`
// appearing anywhere in the call. `[^,)]*` bounds the identifier
// so we don't swallow an entire literal map.
dartDioDataRe = regexp.MustCompile(`data\s*:\s*([A-Za-z_$][\w$]*)`)
// http.post(url, body: jsonEncode(payload)) — common pattern for
// package:http. Same `body:` kwarg for custom wrappers.
dartBodyEncodeRe = regexp.MustCompile(`body\s*:\s*jsonEncode\(\s*([A-Za-z_$][\w$]*)\s*\)`)
// final parsed = FooResp.fromJson(resp.data) — the receiver of
// .fromJson is the response type.
dartFromJSONRe = regexp.MustCompile(`([A-Za-z_$][\w$]*)\.fromJson\(`)
// statusCode: 201 → present in dio request options but uncommon;
// response.statusCode reads aren't interesting here.
)
func dartConsumerDetect(body string, fileNodes []*graph.Node) schemaHints {
var h schemaHints
if m := dartDioDataRe.FindStringSubmatch(body); len(m) > 1 {
if rt := findDartVarType(body, m[1]); rt != "" {
h.RequestType = resolveTypeInFile(rt, fileNodes)
} else if h.RequestExpr == "" {
h.RequestExpr = "data: " + m[1]
}
} else if m := dartBodyEncodeRe.FindStringSubmatch(body); len(m) > 1 {
if rt := findDartVarType(body, m[1]); rt != "" {
h.RequestType = resolveTypeInFile(rt, fileNodes)
} else if h.RequestExpr == "" {
h.RequestExpr = "body: jsonEncode(" + m[1] + ")"
}
}
if m := dartFromJSONRe.FindStringSubmatch(body); len(m) > 1 {
h.ResponseType = resolveTypeInFile(m[1], fileNodes)
}
return h
}
// findDartVarType mirrors findVarType / findTSVarType for Dart
// declaration forms. Covers:
//
// final name = Type(...)
// var name = Type(...)
// Type name = ...
// (..., Type name, ...) method parameter
//
// Dart's optional type syntax means many call sites use `final name`
// without a type annotation — we can't resolve those here.
func findDartVarType(body, varName string) string {
if varName == "" {
return ""
}
v := regexp.QuoteMeta(varName)
// final|var|const name = Type(
if m := regexp.MustCompile(`\b(?:final|var|const)\s+` + v + `\s*=\s*([A-Za-z_$][\w$.]*)\(`).FindStringSubmatch(body); len(m) > 1 {
return m[1]
}
// Type name = ... — leading identifier that looks like a type.
if m := regexp.MustCompile(`\b([A-Z][\w$]*(?:<[^>]+>)?)\s+` + v + `\b`).FindStringSubmatch(body); len(m) > 1 {
return strings.TrimSpace(stripGenerics(m[1]))
}
return ""
}