@descryy/adapter-go 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,596 @@
1
+ // Command goshapes is descry-adapters' R3 evidence source for Go: real
2
+ // type-checker output, not a heuristic. It loads a target Go module with
3
+ // golang.org/x/tools/go/packages (which shells out to `go list`/the Go
4
+ // toolchain already on the analysing machine), and for every function whose
5
+ // signature matches a recognised HTTP-framework handler, walks the
6
+ // function's own body — through go/types' resolved type information, not
7
+ // syntax alone — to find which named struct type it decodes an inbound
8
+ // request into and which it encodes as the response.
9
+ //
10
+ // That join (handler -> request/response struct) is exactly the capability
11
+ // gap `descry-adapters`' prior investigation named: Go carries no written
12
+ // annotation the way Python's Pydantic or Java's JSON libraries do, so
13
+ // finding it requires tracing what a variable holds through a Decode/Bind or
14
+ // Encode/JSON call — a data-flow fact only a type checker can state
15
+ // honestly. Nothing here is invented when the evidence is ambiguous: more
16
+ // than one candidate shape for one handler's request (or response) is
17
+ // reported as unresolved rather than guessed, matching this project's
18
+ // precision-over-recall rule.
19
+ //
20
+ // Output is one JSON object on stdout, always exit 0 (including on
21
+ // failure — failure is a value in the JSON, `"ok": false`, not a process
22
+ // exit code the caller has to distinguish from "the helper itself crashed").
23
+ package main
24
+
25
+ import (
26
+ "encoding/json"
27
+ "fmt"
28
+ "go/ast"
29
+ "go/token"
30
+ "go/types"
31
+ "os"
32
+ "reflect"
33
+ "strings"
34
+
35
+ "golang.org/x/tools/go/packages"
36
+ )
37
+
38
+ // --- Output contract ---------------------------------------------------
39
+ //
40
+ // Mirrors what `packages/adapter-go/src/go-types.ts` decodes. Keep the two
41
+ // in lockstep by hand; there is no shared schema source between a Go program
42
+ // and a TypeScript one.
43
+
44
+ type shapeRef struct {
45
+ ImportPath string `json:"importPath"`
46
+ Name string `json:"name"`
47
+ }
48
+
49
+ type field struct {
50
+ Name string `json:"name"`
51
+ JSONName string `json:"jsonName,omitempty"`
52
+ Type string `json:"type"`
53
+ Embedded bool `json:"embedded,omitempty"`
54
+ Skip bool `json:"skip,omitempty"`
55
+ }
56
+
57
+ type structShape struct {
58
+ ImportPath string `json:"importPath"`
59
+ Name string `json:"name"`
60
+ Fields []field `json:"fields"`
61
+ }
62
+
63
+ type handler struct {
64
+ ImportPath string `json:"importPath"`
65
+ Receiver string `json:"receiver,omitempty"`
66
+ FuncName string `json:"funcName"`
67
+ File string `json:"file"`
68
+ Line int `json:"line"`
69
+ Framework string `json:"framework"`
70
+ Request *shapeRef `json:"request,omitempty"`
71
+ Response *shapeRef `json:"response,omitempty"`
72
+ }
73
+
74
+ type output struct {
75
+ OK bool `json:"ok"`
76
+ Error string `json:"error,omitempty"`
77
+ Structs []structShape `json:"structs"`
78
+ Handlers []handler `json:"handlers"`
79
+ }
80
+
81
+ func main() {
82
+ out := run()
83
+ enc := json.NewEncoder(os.Stdout)
84
+ enc.SetEscapeHTML(false)
85
+ if err := enc.Encode(out); err != nil {
86
+ // Nothing left to report through — the JSON contract itself
87
+ // could not be written. Fail loudly on stderr; the TypeScript
88
+ // side treats a stdout that fails to parse as a failure of its
89
+ // own, but this at least explains why to a human reading logs.
90
+ fmt.Fprintln(os.Stderr, "goshapes: failed to encode output:", err)
91
+ os.Exit(1)
92
+ }
93
+ }
94
+
95
+ // run never panics past this point — a bug in this file must degrade to a
96
+ // disclosed `ok: false`, never a hung or garbled process the caller cannot
97
+ // interpret (the project's "honest degradation" rule applies to this helper
98
+ // exactly as it applies to the TypeScript adapter that calls it).
99
+ func run() (result output) {
100
+ defer func() {
101
+ if r := recover(); r != nil {
102
+ result = output{OK: false, Error: fmt.Sprintf("panic: %v", r)}
103
+ }
104
+ }()
105
+
106
+ if len(os.Args) < 2 {
107
+ return output{OK: false, Error: "usage: goshapes <target-module-directory>"}
108
+ }
109
+ dir := os.Args[1]
110
+
111
+ cfg := &packages.Config{
112
+ Dir: dir,
113
+ Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles |
114
+ packages.NeedImports | packages.NeedTypes | packages.NeedTypesInfo |
115
+ packages.NeedSyntax | packages.NeedModule,
116
+ Tests: false,
117
+ }
118
+ pkgs, err := packages.Load(cfg, "./...")
119
+ if err != nil {
120
+ return output{OK: false, Error: "packages.Load: " + err.Error()}
121
+ }
122
+ if len(pkgs) == 0 {
123
+ return output{OK: false, Error: "no Go packages found under " + dir}
124
+ }
125
+
126
+ // Load errors are per-package (a broken sibling package must not blind
127
+ // this run to every package that DID load — the same "one file's gap,
128
+ // not the run's" standard `adapter-go`'s own TypeScript holds for a
129
+ // tree-walk throw). Collected for disclosure only; loading continues.
130
+ var loadErrors []string
131
+ packages.Visit(pkgs, nil, func(p *packages.Package) {
132
+ for _, e := range p.Errors {
133
+ loadErrors = append(loadErrors, p.PkgPath+": "+e.Error())
134
+ }
135
+ })
136
+
137
+ extractor := newExtractor()
138
+ for _, pkg := range pkgs {
139
+ if pkg.TypesInfo == nil || pkg.Types == nil {
140
+ continue
141
+ }
142
+ extractor.visitPackage(pkg)
143
+ }
144
+
145
+ structs, handlers := extractor.finish()
146
+ out := output{OK: true, Structs: structs, Handlers: handlers}
147
+ if len(loadErrors) > 0 && len(structs) == 0 && len(handlers) == 0 {
148
+ // Every package that mattered failed to load and nothing else
149
+ // stepped in — an honest failure, not a quiet empty result.
150
+ out = output{OK: false, Error: "no package loaded cleanly: " + loadErrors[0]}
151
+ }
152
+ return out
153
+ }
154
+
155
+ // --- Framework recognition ----------------------------------------------
156
+ //
157
+ // Deliberately the same closed set `packages/adapter-go/src/routes.ts`
158
+ // already recognises syntactically (its `FRAMEWORKS` map's header explains
159
+ // why a closed, auditable set is the right shape for this kind of table).
160
+ // chi and gorilla/mux both register ordinary `func(http.ResponseWriter,
161
+ // *http.Request)` handlers, so they need no entry of their own here — the
162
+ // "net/http" signature check already covers them, exactly as it does in
163
+ // `routes.ts`.
164
+
165
+ const (
166
+ frameworkNetHTTP = "net/http"
167
+ frameworkGin = "github.com/gin-gonic/gin"
168
+ frameworkEcho = "github.com/labstack/echo"
169
+ frameworkEchoV4 = "github.com/labstack/echo/v4"
170
+ frameworkFiberV1 = "github.com/gofiber/fiber"
171
+ frameworkFiberV2 = "github.com/gofiber/fiber/v2"
172
+ )
173
+
174
+ // ctxParamOf reports the single-context-parameter frameworks' own parameter
175
+ // object, when `sig` is shaped like that framework's handler. Returns nil
176
+ // for anything else, including a signature with the right type in the wrong
177
+ // position or arity — an exact match only, never a guess (the same "a
178
+ // verb-shaped call is not a route" discipline `routes.ts` applies to
179
+ // syntax, applied here to a checked signature).
180
+ func ctxParamOf(sig *types.Signature, wantType string) *types.Var {
181
+ if sig.Params().Len() != 1 {
182
+ return nil
183
+ }
184
+ p := sig.Params().At(0)
185
+ if typeString(p.Type()) != wantType {
186
+ return nil
187
+ }
188
+ return p
189
+ }
190
+
191
+ // handlerShapeOf classifies one function's signature against every
192
+ // recognised framework, returning the framework name and the request
193
+ // object(s) whose method calls are this handler's own (for gin/echo/fiber,
194
+ // the single context parameter; for net/http-shaped handlers, both the
195
+ // ResponseWriter and the *Request are candidate receivers, but neither
196
+ // carries bind/JSON methods this reader looks for beyond the request body
197
+ // itself, read directly).
198
+ func handlerShapeOf(sig *types.Signature) (framework string, ctx *types.Var, ok bool) {
199
+ if sig.Params().Len() == 2 {
200
+ w := sig.Params().At(0)
201
+ r := sig.Params().At(1)
202
+ if typeString(w.Type()) == "net/http.ResponseWriter" && typeString(r.Type()) == "*net/http.Request" {
203
+ return frameworkNetHTTP, nil, true
204
+ }
205
+ }
206
+ if v := ctxParamOf(sig, "*github.com/gin-gonic/gin.Context"); v != nil {
207
+ return frameworkGin, v, true
208
+ }
209
+ if v := ctxParamOf(sig, "github.com/labstack/echo.Context"); v != nil {
210
+ return frameworkEcho, v, true
211
+ }
212
+ if v := ctxParamOf(sig, "github.com/labstack/echo/v4.Context"); v != nil {
213
+ return frameworkEchoV4, v, true
214
+ }
215
+ if v := ctxParamOf(sig, "*github.com/gofiber/fiber.Ctx"); v != nil {
216
+ return frameworkFiberV1, v, true
217
+ }
218
+ if v := ctxParamOf(sig, "*github.com/gofiber/fiber/v2.Ctx"); v != nil {
219
+ return frameworkFiberV2, v, true
220
+ }
221
+ return "", nil, false
222
+ }
223
+
224
+ // typeString renders a type the same, stable way everywhere in this file —
225
+ // `types.TypeString` with no qualifier function, so every named type prints
226
+ // fully qualified by its import path. That is what makes it safe to compare
227
+ // against a literal like "*net/http.Request" instead of resolving the
228
+ // framework's own package object first.
229
+ func typeString(t types.Type) string {
230
+ return types.TypeString(t, nil)
231
+ }
232
+
233
+ // --- Struct shape extraction ---------------------------------------------
234
+
235
+ // namedStructOf reports the declared struct type's own field list, `nil`
236
+ // when `t` (after stripping at most one pointer) is not a named struct.
237
+ // Reading through `types.Struct` rather than the AST's field list is the
238
+ // entire reason this helper exists rather than a syntactic reader: it is
239
+ // go/types that flattens an anonymous embed and resolves a field's type
240
+ // across package boundaries, both of which `adapter-go`'s own tree-sitter
241
+ // reader (`GoType.fields`, `parse.ts`) discloses it cannot do.
242
+ func namedStructOf(t types.Type) (named *types.Named, st *types.Struct, ok bool) {
243
+ if ptr, isPtr := t.(*types.Pointer); isPtr {
244
+ t = ptr.Elem()
245
+ }
246
+ n, isNamed := t.(*types.Named)
247
+ if !isNamed {
248
+ return nil, nil, false
249
+ }
250
+ s, isStruct := n.Underlying().(*types.Struct)
251
+ if !isStruct {
252
+ return nil, nil, false
253
+ }
254
+ return n, s, true
255
+ }
256
+
257
+ func shapeRefOf(named *types.Named) shapeRef {
258
+ pkg := named.Obj().Pkg()
259
+ importPath := ""
260
+ if pkg != nil {
261
+ importPath = pkg.Path()
262
+ }
263
+ return shapeRef{ImportPath: importPath, Name: named.Obj().Name()}
264
+ }
265
+
266
+ func fieldsOf(st *types.Struct) []field {
267
+ out := make([]field, 0, st.NumFields())
268
+ for i := 0; i < st.NumFields(); i++ {
269
+ v := st.Field(i)
270
+ f := field{Name: v.Name(), Type: typeString(v.Type()), Embedded: v.Embedded()}
271
+ tag := parseJSONTag(st.Tag(i))
272
+ if tag == "-" {
273
+ f.Skip = true
274
+ } else if tag != "" {
275
+ f.JSONName = tag
276
+ }
277
+ out = append(out, f)
278
+ }
279
+ return out
280
+ }
281
+
282
+ // parseJSONTag reads only the `json:"..."` tag's name segment — omitempty
283
+ // and the other comma-separated options are encoding/json's own concern,
284
+ // not identity this adapter reports. `""` when there is no json tag at all,
285
+ // distinct from `"-"` (explicitly excluded from the wire shape).
286
+ // `reflect.StructTag` is the standard library's own tag parser — this file
287
+ // does not reimplement Go's tag grammar.
288
+ func parseJSONTag(tag string) string {
289
+ raw := reflect.StructTag(tag).Get("json")
290
+ if raw == "" {
291
+ return ""
292
+ }
293
+ if comma := strings.IndexByte(raw, ','); comma != -1 {
294
+ return raw[:comma]
295
+ }
296
+ return raw
297
+ }
298
+
299
+ // --- Handler body walk -----------------------------------------------------
300
+
301
+ // candidate is one shape this reader found written into a handler's body,
302
+ // with enough left to decide ambiguity — more than one DISTINCT candidate
303
+ // for one role (request or response) is refused, not guessed.
304
+ type candidate struct {
305
+ named *types.Named
306
+ st *types.Struct
307
+ }
308
+
309
+ type extractor struct {
310
+ // pkgPath::Name -> the struct, recorded only when a handler actually
311
+ // names it — matches this project's "promote, don't dump" precision
312
+ // stance the way `adapter-python`'s DTO promotion only ever emits a
313
+ // class that some route's own contract names.
314
+ wanted map[string]structShape
315
+ handlers []handler
316
+ }
317
+
318
+ func newExtractor() *extractor {
319
+ return &extractor{wanted: map[string]structShape{}}
320
+ }
321
+
322
+ func (e *extractor) finish() ([]structShape, []handler) {
323
+ structs := make([]structShape, 0, len(e.wanted))
324
+ for _, s := range e.wanted {
325
+ structs = append(structs, s)
326
+ }
327
+ return structs, e.handlers
328
+ }
329
+
330
+ func (e *extractor) remember(named *types.Named, st *types.Struct) shapeRef {
331
+ ref := shapeRefOf(named)
332
+ key := ref.ImportPath + "." + ref.Name
333
+ if _, ok := e.wanted[key]; !ok {
334
+ e.wanted[key] = structShape{ImportPath: ref.ImportPath, Name: ref.Name, Fields: fieldsOf(st)}
335
+ }
336
+ return ref
337
+ }
338
+
339
+ func (e *extractor) visitPackage(pkg *packages.Package) {
340
+ fset := pkg.Fset
341
+ info := pkg.TypesInfo
342
+ for _, file := range pkg.Syntax {
343
+ for _, decl := range file.Decls {
344
+ fn, isFunc := decl.(*ast.FuncDecl)
345
+ if !isFunc || fn.Body == nil {
346
+ continue
347
+ }
348
+ obj, isObj := info.Defs[fn.Name].(*types.Func)
349
+ if !isObj {
350
+ continue
351
+ }
352
+ sig, isSig := obj.Type().(*types.Signature)
353
+ if !isSig {
354
+ continue
355
+ }
356
+ framework, ctx, matched := handlerShapeOf(sig)
357
+ if !matched {
358
+ continue
359
+ }
360
+
361
+ receiver := ""
362
+ if fn.Recv != nil && len(fn.Recv.List) == 1 {
363
+ receiver = receiverTypeName(fn.Recv.List[0].Type)
364
+ }
365
+
366
+ reqCandidates, respCandidates := e.bodyShapesOf(fn.Body, info, ctx, framework)
367
+ req := e.resolveOne(reqCandidates)
368
+ resp := e.resolveOne(respCandidates)
369
+ if req == nil && resp == nil {
370
+ // A confirmed handler signature with no discernible
371
+ // request/response shape is real but out of this
372
+ // helper's scope — see the file header. Nothing to
373
+ // report; the TypeScript side gains nothing from a
374
+ // handler row that names no shape.
375
+ continue
376
+ }
377
+
378
+ pos := fset.Position(fn.Pos())
379
+ e.handlers = append(e.handlers, handler{
380
+ ImportPath: pkg.PkgPath,
381
+ Receiver: receiver,
382
+ FuncName: fn.Name.Name,
383
+ File: pos.Filename,
384
+ Line: pos.Line,
385
+ Framework: framework,
386
+ Request: req,
387
+ Response: resp,
388
+ })
389
+ }
390
+ }
391
+ }
392
+
393
+ func receiverTypeName(expr ast.Expr) string {
394
+ if star, isStar := expr.(*ast.StarExpr); isStar {
395
+ expr = star.X
396
+ }
397
+ if ident, isIdent := expr.(*ast.Ident); isIdent {
398
+ return ident.Name
399
+ }
400
+ return ""
401
+ }
402
+
403
+ // resolveOne applies the ambiguity rule: zero candidates is `nil` (nothing
404
+ // found, disclosed by simple absence); exactly one DISTINCT named type
405
+ // across every candidate is that type; more than one distinct type is
406
+ // `nil` too — refused, the same as zero, because this reader cannot say
407
+ // which of them the caller meant and a wrong shape claim is worse than a
408
+ // missing one (rule 2).
409
+ func (e *extractor) resolveOne(candidates []candidate) *shapeRef {
410
+ if len(candidates) == 0 {
411
+ return nil
412
+ }
413
+ first := candidates[0]
414
+ firstRef := shapeRefOf(first.named)
415
+ for _, c := range candidates[1:] {
416
+ ref := shapeRefOf(c.named)
417
+ if ref != firstRef {
418
+ return nil
419
+ }
420
+ }
421
+ ref := e.remember(first.named, first.st)
422
+ return &ref
423
+ }
424
+
425
+ // bodyShapesOf walks one handler's body for the framework's own
426
+ // decode/bind and encode/JSON idioms. `ctx` is the single context
427
+ // parameter for gin/echo/fiber, `nil` for net/http (whose two parameters
428
+ // are read straight off `info.Uses`, not through a shared receiver).
429
+ func (e *extractor) bodyShapesOf(
430
+ body *ast.BlockStmt,
431
+ info *types.Info,
432
+ ctx *types.Var,
433
+ framework string,
434
+ ) (requests []candidate, responses []candidate) {
435
+ ast.Inspect(body, func(n ast.Node) bool {
436
+ call, isCall := n.(*ast.CallExpr)
437
+ if !isCall {
438
+ return true
439
+ }
440
+ sel, isSel := call.Fun.(*ast.SelectorExpr)
441
+ if !isSel {
442
+ return true
443
+ }
444
+ method := sel.Sel.Name
445
+
446
+ // `json.NewDecoder(r.Body).Decode(&x)` / `json.NewEncoder(w).Encode(x)`
447
+ // — the receiver is itself a call, so the method is read off the
448
+ // call's own resolved type rather than an identifier.
449
+ if method == "Decode" || method == "Encode" {
450
+ if isJSONDecoderOrEncoder(sel.X, info, method) {
451
+ if method == "Decode" && len(call.Args) == 1 {
452
+ if c, ok := candidateFromPointerArg(call.Args[0], info); ok {
453
+ requests = append(requests, c)
454
+ }
455
+ }
456
+ if method == "Encode" && len(call.Args) == 1 {
457
+ if c, ok := candidateFromValueArg(call.Args[0], info); ok {
458
+ responses = append(responses, c)
459
+ }
460
+ }
461
+ }
462
+ return true
463
+ }
464
+
465
+ // `json.Unmarshal(data, &x)` — a package-level call, not a method
466
+ // on a receiver this reader tracks; `sel.X` is the `json`
467
+ // identifier itself.
468
+ if method == "Unmarshal" && isPackageIdent(sel.X, info, "encoding/json") && len(call.Args) == 2 {
469
+ if c, ok := candidateFromPointerArg(call.Args[1], info); ok {
470
+ requests = append(requests, c)
471
+ }
472
+ return true
473
+ }
474
+
475
+ // Every remaining idiom is a method called on the handler's own
476
+ // context parameter — confirm the receiver really is that
477
+ // parameter (`info.Uses[recv] == ctx`) before trusting the
478
+ // method name at all. A same-named method on an unrelated value
479
+ // is not this framework's bind/JSON call.
480
+ if ctx == nil {
481
+ return true
482
+ }
483
+ recvIdent, isIdent := sel.X.(*ast.Ident)
484
+ if !isIdent || info.Uses[recvIdent] != ctx {
485
+ return true
486
+ }
487
+
488
+ switch framework {
489
+ case frameworkGin:
490
+ switch method {
491
+ case "BindJSON", "ShouldBindJSON", "Bind", "ShouldBind":
492
+ if len(call.Args) == 1 {
493
+ if c, ok := candidateFromPointerArg(call.Args[0], info); ok {
494
+ requests = append(requests, c)
495
+ }
496
+ }
497
+ case "JSON":
498
+ if len(call.Args) == 2 {
499
+ if c, ok := candidateFromValueArg(call.Args[1], info); ok {
500
+ responses = append(responses, c)
501
+ }
502
+ }
503
+ }
504
+ case frameworkEcho, frameworkEchoV4:
505
+ switch method {
506
+ case "Bind":
507
+ if len(call.Args) == 1 {
508
+ if c, ok := candidateFromPointerArg(call.Args[0], info); ok {
509
+ requests = append(requests, c)
510
+ }
511
+ }
512
+ case "JSON":
513
+ if len(call.Args) == 2 {
514
+ if c, ok := candidateFromValueArg(call.Args[1], info); ok {
515
+ responses = append(responses, c)
516
+ }
517
+ }
518
+ }
519
+ case frameworkFiberV1, frameworkFiberV2:
520
+ switch method {
521
+ case "BodyParser":
522
+ if len(call.Args) == 1 {
523
+ if c, ok := candidateFromPointerArg(call.Args[0], info); ok {
524
+ requests = append(requests, c)
525
+ }
526
+ }
527
+ case "JSON":
528
+ if len(call.Args) == 1 {
529
+ if c, ok := candidateFromValueArg(call.Args[0], info); ok {
530
+ responses = append(responses, c)
531
+ }
532
+ }
533
+ }
534
+ }
535
+ return true
536
+ })
537
+ return requests, responses
538
+ }
539
+
540
+ // isJSONDecoderOrEncoder reports whether `expr` (the receiver of a
541
+ // `.Decode`/`.Encode` call) is itself a call to `json.NewDecoder`/
542
+ // `json.NewEncoder` — resolved off the call's own result type
543
+ // (`*encoding/json.Decoder` / `*encoding/json.Encoder`), which is a
544
+ // stronger check than matching the source text and is exactly what a type
545
+ // checker is for.
546
+ func isJSONDecoderOrEncoder(expr ast.Expr, info *types.Info, method string) bool {
547
+ call, isCall := expr.(*ast.CallExpr)
548
+ if !isCall {
549
+ return false
550
+ }
551
+ t := info.TypeOf(call)
552
+ if t == nil {
553
+ return false
554
+ }
555
+ want := "*encoding/json.Decoder"
556
+ if method == "Encode" {
557
+ want = "*encoding/json.Encoder"
558
+ }
559
+ return typeString(t) == want
560
+ }
561
+
562
+ func isPackageIdent(expr ast.Expr, info *types.Info, importPath string) bool {
563
+ ident, isIdent := expr.(*ast.Ident)
564
+ if !isIdent {
565
+ return false
566
+ }
567
+ pkgName, isPkgName := info.Uses[ident].(*types.PkgName)
568
+ return isPkgName && pkgName.Imported().Path() == importPath
569
+ }
570
+
571
+ // candidateFromPointerArg reads `&x` — the shape every Decode/Bind idiom
572
+ // this file recognises takes its target as. Anything else (a bare name
573
+ // with no `&`, an expression) names no addressable local this reader
574
+ // resolves a struct from, and is silently not a candidate rather than a
575
+ // refusal: the surrounding call may still be a real Decode of something
576
+ // this reader simply does not follow (a field of a struct, an interface
577
+ // value), which is a recall gap, never a precision one.
578
+ func candidateFromPointerArg(arg ast.Expr, info *types.Info) (candidate, bool) {
579
+ unary, isUnary := arg.(*ast.UnaryExpr)
580
+ if !isUnary || unary.Op != token.AND {
581
+ return candidate{}, false
582
+ }
583
+ return candidateFromValueArg(unary.X, info)
584
+ }
585
+
586
+ func candidateFromValueArg(arg ast.Expr, info *types.Info) (candidate, bool) {
587
+ t := info.TypeOf(arg)
588
+ if t == nil {
589
+ return candidate{}, false
590
+ }
591
+ named, st, ok := namedStructOf(t)
592
+ if !ok {
593
+ return candidate{}, false
594
+ }
595
+ return candidate{named: named, st: st}, true
596
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@descryy/adapter-go",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "description": "Go adapter, breadth tier. One grammar and one rule set over the shared tree-sitter harness — the second language to use it, and the measurement of whether that harness was worth building.",
6
6
  "license": "UNLICENSED",
@@ -23,7 +23,8 @@
23
23
  }
24
24
  },
25
25
  "files": [
26
- "dist"
26
+ "dist",
27
+ "goshapes"
27
28
  ],
28
29
  "publishConfig": {
29
30
  "registry": "https://registry.npmjs.org",
@@ -34,11 +35,11 @@
34
35
  "build:assets": "node -e \"require('fs').copyFileSync(require.resolve('tree-sitter-go/tree-sitter-go.wasm'),'dist/tree-sitter-go.wasm')\""
35
36
  },
36
37
  "dependencies": {
37
- "@descryy/adapter-common": "0.3.0",
38
- "@descryy/adapter-sql": "0.4.1",
39
- "@descryy/adapter-treesitter": "0.2.0",
40
- "@descryy/ir": "0.7.0",
38
+ "@descryy/adapter-common": "0.3.1",
39
+ "@descryy/adapter-sql": "0.5.1",
40
+ "@descryy/adapter-treesitter": "0.2.1",
41
+ "@descryy/ir": "0.10.0",
41
42
  "tree-sitter-go": "0.25.0",
42
- "@descryy/adapter-openapi": "0.4.1"
43
+ "@descryy/adapter-openapi": "0.5.1"
43
44
  }
44
45
  }