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

134 lines
3.8 KiB
Go

package contracts
import (
"fmt"
"strings"
"gopkg.in/yaml.v3"
"github.com/zzet/gortex/internal/graph"
"github.com/zzet/gortex/internal/parser"
)
// Drupal routing.yml route extraction. A Drupal module declares its routes in
// `<module>.routing.yml`, mapping a route name to a `path` and a `defaults`
// handler reference (`_controller: \Drupal\node\Controller\NodeController::view`,
// `_form: \Drupal\node\Form\NodeAddForm`, `_entity_*`). This pass parses the
// YAML and emits one route Contract per route, binding the handler same-file
// or stamping handler_ident/handler_class for the module-wide cross-file pass.
// drupalRoute is one route entry in a *.routing.yml file.
type drupalRoute struct {
Path string `yaml:"path"`
Methods []string `yaml:"methods"`
Defaults map[string]any `yaml:"defaults"`
}
// extractDrupalRoutes parses a *.routing.yml file into route Contracts.
func (h *HTTPExtractor) extractDrupalRoutes(filePath, text string, lines []string, fileNodes []*graph.Node, lang string, tree *parser.ParseTree) []Contract {
var doc map[string]drupalRoute
if err := yaml.Unmarshal([]byte(text), &doc); err != nil {
return nil
}
var out []Contract
for name, r := range doc {
if r.Path == "" {
continue
}
fqcn, hclass, hident := drupalRouteHandler(r.Defaults)
normPath, origNames := NormalizeHTTPPathWithParams(r.Path)
lineNum := drupalRouteLine(lines, name)
methods := []string{"ANY"}
if len(r.Methods) > 0 {
methods = methods[:0]
for _, m := range r.Methods {
methods = append(methods, strings.ToUpper(strings.TrimSpace(m)))
}
}
for _, method := range methods {
c := Contract{
ID: fmt.Sprintf("http::%s::%s", method, normPath),
Type: ContractHTTP,
Role: RoleProvider,
FilePath: filePath,
Line: lineNum,
Confidence: 0.85,
Meta: map[string]any{
"method": method,
"path": normPath,
"framework": "drupal",
"drupal_route": name,
},
}
if len(origNames) > 0 {
c.Meta["path_param_names"] = origNames
}
if fqcn != "" {
c.Meta["handler_fqcn"] = fqcn
}
if hclass != "" {
c.Meta["handler_class"] = hclass
}
if hident != "" {
c.Meta["handler_ident"] = hident
}
if hident != "" && hclass != "" {
if id := findMethodByNameAndReceiver(fileNodes, hident, hclass); id != "" {
c.SymbolID = id
}
}
EnrichHTTPContractWithTree(&c, lines, fileNodes, lang, tree)
out = append(out, c)
}
}
return out
}
// drupalRouteHandler reads the handler reference from a route's defaults:
// `_controller` carries `Class::method`; `_form` / `_entity_*` carry a bare
// class FQCN.
func drupalRouteHandler(defaults map[string]any) (fqcn, hclass, hident string) {
if defaults == nil {
return "", "", ""
}
if c, _ := defaults["_controller"].(string); c != "" {
cls, method := drupalSplitControllerRef(c)
return c, drupalSimpleClass(cls), method
}
for _, k := range []string{"_form", "_entity_form", "_entity_view", "_entity_list"} {
if v, _ := defaults[k].(string); v != "" {
return v, drupalSimpleClass(v), ""
}
}
return "", "", ""
}
// drupalSplitControllerRef splits `Class::method` into its class FQCN and
// method, tolerating a bare class.
func drupalSplitControllerRef(ref string) (cls, method string) {
if c, m, ok := strings.Cut(ref, "::"); ok {
return c, m
}
return ref, ""
}
// drupalSimpleClass strips the namespace from a PHP FQCN.
func drupalSimpleClass(fqcn string) string {
fqcn = strings.TrimPrefix(fqcn, "\\")
if i := strings.LastIndex(fqcn, "\\"); i >= 0 {
return fqcn[i+1:]
}
return fqcn
}
// drupalRouteLine returns the 1-based line of a route's top-level key.
func drupalRouteLine(lines []string, name string) int {
for i, l := range lines {
if strings.HasPrefix(strings.TrimSpace(l), name+":") {
return i + 1
}
}
return 1
}