@smoothbricks/lmao-ttsc 0.0.0-bootstrap.0 → 0.1.7
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.
- package/dist/bun-register.d.ts +2 -0
- package/dist/bun-register.d.ts.map +1 -0
- package/dist/bun-register.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/package.json +113 -4
- package/plugin/go.mod +9 -0
- package/plugin/loginline.go +296 -0
- package/plugin/main.go +572 -0
- package/plugin/main_test.go +43 -0
- package/plugin/optimization.go +462 -0
- package/plugin/resultinline.go +211 -0
- package/plugin/taginline.go +453 -0
- package/plugin/template_test.go +264 -0
- package/plugin.cjs +19 -0
- package/plugin.d.cts +5 -0
- package/README.md +0 -3
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
// taginline.go — spec 01o §4 tag-chain inlining (smoo/lmao!n/transformer-tag-chain-inline)
|
|
2
|
+
//
|
|
3
|
+
// Checker-backed tag-chain inliner: rewrites statement-level
|
|
4
|
+
// `ctx.tag.a(x).b(y).with({...})` chains into a Block of direct columnar
|
|
5
|
+
// buffer writes (null-bitmap |= 1, values[0] = v), with enum indices folded
|
|
6
|
+
// to constants for literal arguments and switch-IIFEs for dynamic ones.
|
|
7
|
+
//
|
|
8
|
+
// Conservative by construction — the chain is transformed ONLY when the tsgo
|
|
9
|
+
// Checker proves the receiver of `.tag` is a TagWriter/GeneratedTagWriter;
|
|
10
|
+
// anything unprovable is left untouched (the runtime fluent path is always
|
|
11
|
+
// correct, just slower). Column names are the library-local (unprefixed)
|
|
12
|
+
// names per spec 01e — remapping is cold-path-only via RemappedBufferView.
|
|
13
|
+
//
|
|
14
|
+
// Output-shape parity with the TS inliner is the invariant (spec 01o: "the
|
|
15
|
+
// running invariant is the transformed output the tests assert"): all plain
|
|
16
|
+
// tag calls emit first in execution order, then .with() calls unrolled, each
|
|
17
|
+
// via the same boolean/enum/direct write shapes.
|
|
18
|
+
package main
|
|
19
|
+
|
|
20
|
+
import (
|
|
21
|
+
"sort"
|
|
22
|
+
"strings"
|
|
23
|
+
|
|
24
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
25
|
+
shimchecker "github.com/microsoft/typescript-go/shim/checker"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
var tagWriterTypeNames = []string{"TagWriter", "GeneratedTagWriter"}
|
|
29
|
+
|
|
30
|
+
type schemaFieldKind int
|
|
31
|
+
|
|
32
|
+
const (
|
|
33
|
+
fieldDirect schemaFieldKind = iota // category/text/number: direct value write
|
|
34
|
+
fieldBool
|
|
35
|
+
fieldEnum
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
type schemaField struct {
|
|
39
|
+
kind schemaFieldKind
|
|
40
|
+
enumValues []string // sorted, for fieldEnum
|
|
41
|
+
eager bool
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type tagWrite struct {
|
|
45
|
+
field string
|
|
46
|
+
arg *shimast.Node
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// --- chain detection ---------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
// findTagChain walks receiver-wards from the outermost call. Returns the ctx
|
|
52
|
+
// expression and the writes in execution order (plain tag calls first, then
|
|
53
|
+
// unrolled with() properties — matching the TS inliner), or ok=false.
|
|
54
|
+
func (t *fileTransformer) findTagChain(call *shimast.CallExpression) (ctxExpr *shimast.Node, writes []tagWrite, schema map[string]schemaField, ok bool) {
|
|
55
|
+
if t.checker == nil {
|
|
56
|
+
return nil, nil, nil, false
|
|
57
|
+
}
|
|
58
|
+
var tagCalls []tagWrite
|
|
59
|
+
var withProps [][]tagWrite
|
|
60
|
+
current := call
|
|
61
|
+
for {
|
|
62
|
+
expr := current.Expression
|
|
63
|
+
if expr.Kind != shimast.KindPropertyAccessExpression {
|
|
64
|
+
return nil, nil, nil, false
|
|
65
|
+
}
|
|
66
|
+
pa := expr.AsPropertyAccessExpression()
|
|
67
|
+
name := shimast.NodeText(pa.Name())
|
|
68
|
+
|
|
69
|
+
if name == "with" {
|
|
70
|
+
if len(current.Arguments.Nodes) != 1 || current.Arguments.Nodes[0].Kind != shimast.KindObjectLiteralExpression {
|
|
71
|
+
return nil, nil, nil, false // non-literal with(): can't unroll
|
|
72
|
+
}
|
|
73
|
+
var props []tagWrite
|
|
74
|
+
for _, prop := range current.Arguments.Nodes[0].AsObjectLiteralExpression().Properties.Nodes {
|
|
75
|
+
if prop.Kind != shimast.KindPropertyAssignment {
|
|
76
|
+
continue // spreads/shorthand/computed: skip (TS parity)
|
|
77
|
+
}
|
|
78
|
+
p := prop.AsPropertyAssignment()
|
|
79
|
+
nameNode := p.Name()
|
|
80
|
+
if nameNode.Kind != shimast.KindIdentifier && nameNode.Kind != shimast.KindStringLiteral {
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
props = append(props, tagWrite{field: shimast.NodeText(nameNode), arg: p.Initializer})
|
|
84
|
+
}
|
|
85
|
+
withProps = append([][]tagWrite{props}, withProps...)
|
|
86
|
+
} else {
|
|
87
|
+
if len(current.Arguments.Nodes) != 1 {
|
|
88
|
+
return nil, nil, nil, false
|
|
89
|
+
}
|
|
90
|
+
tagCalls = append([]tagWrite{{field: name, arg: current.Arguments.Nodes[0]}}, tagCalls...)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
next := pa.Expression
|
|
94
|
+
if next.Kind == shimast.KindCallExpression {
|
|
95
|
+
current = next.AsCallExpression()
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
// Chain root: must be `<ctx>.tag` and the Checker must prove TagWriter.
|
|
99
|
+
if next.Kind != shimast.KindPropertyAccessExpression {
|
|
100
|
+
return nil, nil, nil, false
|
|
101
|
+
}
|
|
102
|
+
rootPA := next.AsPropertyAccessExpression()
|
|
103
|
+
if shimast.NodeText(rootPA.Name()) != "tag" {
|
|
104
|
+
return nil, nil, nil, false
|
|
105
|
+
}
|
|
106
|
+
tagType := t.checker.GetTypeAtLocation(next)
|
|
107
|
+
if tagType == nil || !isTagWriterType(t.checker, tagType) {
|
|
108
|
+
return nil, nil, nil, false
|
|
109
|
+
}
|
|
110
|
+
schema = extractSchema(t.checker, tagType)
|
|
111
|
+
writes = tagCalls
|
|
112
|
+
for _, props := range withProps {
|
|
113
|
+
writes = append(writes, props...)
|
|
114
|
+
}
|
|
115
|
+
if len(writes) == 0 {
|
|
116
|
+
return nil, nil, nil, false
|
|
117
|
+
}
|
|
118
|
+
return rootPA.Expression, writes, schema, true
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func isTagWriterType(chk *shimchecker.Checker, t *shimchecker.Type) bool {
|
|
123
|
+
name := chk.TypeToString(t)
|
|
124
|
+
for _, w := range tagWriterTypeNames {
|
|
125
|
+
if name == w || strings.HasPrefix(name, w+"<") {
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if sym := shimchecker.Type_getTypeNameSymbol(t); sym != nil {
|
|
130
|
+
for _, w := range tagWriterTypeNames {
|
|
131
|
+
if sym.Name == w {
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// extractSchema mirrors extractSchemaFromTagType: per property, the first
|
|
140
|
+
// call-signature parameter's type decides the field kind; `_eager: true` on
|
|
141
|
+
// the property type marks eager columns.
|
|
142
|
+
func extractSchema(chk *shimchecker.Checker, tagType *shimchecker.Type) map[string]schemaField {
|
|
143
|
+
schema := map[string]schemaField{}
|
|
144
|
+
for _, sym := range shimchecker.Checker_getPropertiesOfType(chk, tagType) {
|
|
145
|
+
name := sym.Name
|
|
146
|
+
if name == "with" || strings.HasPrefix(name, "_") {
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
propType := shimchecker.Checker_getTypeOfSymbol(chk, sym)
|
|
150
|
+
sigs := shimchecker.Checker_getSignaturesOfType(chk, propType, shimchecker.SignatureKindCall)
|
|
151
|
+
if len(sigs) == 0 {
|
|
152
|
+
continue
|
|
153
|
+
}
|
|
154
|
+
params := shimchecker.Signature_parameters(sigs[0])
|
|
155
|
+
if len(params) == 0 {
|
|
156
|
+
continue
|
|
157
|
+
}
|
|
158
|
+
paramType := shimchecker.Checker_getTypeOfSymbol(chk, params[0])
|
|
159
|
+
eager := false
|
|
160
|
+
if et := shimchecker.Checker_getTypeOfPropertyOfType(chk, propType, "_eager"); et != nil {
|
|
161
|
+
eager = chk.TypeToString(et) == "true"
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if paramType.IsUnion() {
|
|
165
|
+
var values []string
|
|
166
|
+
allLiterals := true
|
|
167
|
+
for _, member := range paramType.Types() {
|
|
168
|
+
if member.IsStringLiteral() {
|
|
169
|
+
if v, isStr := member.AsLiteralType().Value().(string); isStr {
|
|
170
|
+
values = append(values, v)
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
allLiterals = false
|
|
175
|
+
break
|
|
176
|
+
}
|
|
177
|
+
if allLiterals && len(values) > 0 {
|
|
178
|
+
sort.Strings(values) // sorted-order indexing matches runtime dictionaries
|
|
179
|
+
schema[name] = schemaField{kind: fieldEnum, enumValues: values, eager: eager}
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
switch chk.TypeToString(paramType) {
|
|
184
|
+
case "string":
|
|
185
|
+
schema[name] = schemaField{kind: fieldDirect, eager: eager}
|
|
186
|
+
case "number":
|
|
187
|
+
schema[name] = schemaField{kind: fieldDirect, eager: eager}
|
|
188
|
+
case "boolean":
|
|
189
|
+
schema[name] = schemaField{kind: fieldBool, eager: eager}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return schema
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// --- code generation ----------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
func isCompileTimeLiteral(n *shimast.Node) bool {
|
|
198
|
+
switch n.Kind {
|
|
199
|
+
case shimast.KindStringLiteral, shimast.KindNumericLiteral, shimast.KindTrueKeyword, shimast.KindFalseKeyword:
|
|
200
|
+
return true
|
|
201
|
+
}
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
type inlineEmitter struct {
|
|
206
|
+
bufferExpr *shimast.Node // ctx._buffer
|
|
207
|
+
varCounter int
|
|
208
|
+
stmts []*shimast.Node
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func (e *inlineEmitter) freshVar() *shimast.Node {
|
|
212
|
+
name := "$$v" + itoa(e.varCounter)
|
|
213
|
+
e.varCounter++
|
|
214
|
+
return ident(name)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
func itoa(n int) string {
|
|
218
|
+
if n == 0 {
|
|
219
|
+
return "0"
|
|
220
|
+
}
|
|
221
|
+
var b [8]byte
|
|
222
|
+
i := len(b)
|
|
223
|
+
for n > 0 {
|
|
224
|
+
i--
|
|
225
|
+
b[i] = byte('0' + n%10)
|
|
226
|
+
n /= 10
|
|
227
|
+
}
|
|
228
|
+
return string(b[i:])
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// columnAccess builds `<buffer>.<field>_<suffix>[0]`.
|
|
232
|
+
func (e *inlineEmitter) columnAccess(field, suffix string) *shimast.Node {
|
|
233
|
+
return factory.NewElementAccessExpression(
|
|
234
|
+
propAccess(e.bufferExpr, field+"_"+suffix), nil, num(0), shimast.NodeFlagsNone)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
func binaryStmt(left *shimast.Node, op shimast.Kind, right *shimast.Node) *shimast.Node {
|
|
238
|
+
return factory.NewExpressionStatement(
|
|
239
|
+
factory.NewBinaryExpression(nil, left, nil, factory.NewToken(op), right))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
func (e *inlineEmitter) emitNullsSet(field string) {
|
|
243
|
+
e.stmts = append(e.stmts, binaryStmt(e.columnAccess(field, "nulls"), shimast.KindBarEqualsToken, num(1)))
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
func (e *inlineEmitter) emitField(w tagWrite, schema map[string]schemaField) {
|
|
247
|
+
info, known := schema[w.field]
|
|
248
|
+
literal := isCompileTimeLiteral(w.arg)
|
|
249
|
+
if known && info.kind == fieldBool {
|
|
250
|
+
e.emitBool(w, info.eager, literal)
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
if known && info.kind == fieldEnum {
|
|
254
|
+
e.emitEnum(w, info, literal)
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
eager := known && info.eager
|
|
258
|
+
e.emitDirect(w, eager, literal)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
func (e *inlineEmitter) emitBool(w tagWrite, eager, literal bool) {
|
|
262
|
+
values := func() *shimast.Node { return e.columnAccess(w.field, "values") }
|
|
263
|
+
setTrue := func() *shimast.Node { return binaryStmt(values(), shimast.KindBarEqualsToken, num(1)) }
|
|
264
|
+
setFalse := func() *shimast.Node {
|
|
265
|
+
return binaryStmt(values(), shimast.KindAmpersandEqualsToken,
|
|
266
|
+
factory.NewPrefixUnaryExpression(shimast.KindTildeToken, num(1)))
|
|
267
|
+
}
|
|
268
|
+
if literal {
|
|
269
|
+
if !eager {
|
|
270
|
+
e.emitNullsSet(w.field)
|
|
271
|
+
}
|
|
272
|
+
if w.arg.Kind == shimast.KindTrueKeyword {
|
|
273
|
+
e.stmts = append(e.stmts, setTrue())
|
|
274
|
+
} else {
|
|
275
|
+
e.stmts = append(e.stmts, setFalse())
|
|
276
|
+
}
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
v := e.freshVar()
|
|
280
|
+
e.stmts = append(e.stmts, constDecl(v, w.arg))
|
|
281
|
+
var ifBody []*shimast.Node
|
|
282
|
+
if !eager {
|
|
283
|
+
ifBody = append(ifBody, binaryStmt(e.columnAccess(w.field, "nulls"), shimast.KindBarEqualsToken, num(1)))
|
|
284
|
+
}
|
|
285
|
+
ifBody = append(ifBody, factory.NewIfStatement(ident(identText(v)),
|
|
286
|
+
factory.NewBlock(factory.NewNodeList([]*shimast.Node{setTrue()}), false),
|
|
287
|
+
factory.NewBlock(factory.NewNodeList([]*shimast.Node{setFalse()}), false)))
|
|
288
|
+
e.stmts = append(e.stmts, notNullGuard(v, ifBody))
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
func (e *inlineEmitter) emitEnum(w tagWrite, info schemaField, literal bool) {
|
|
292
|
+
if !info.eager {
|
|
293
|
+
e.emitNullsSet(w.field)
|
|
294
|
+
}
|
|
295
|
+
if literal && w.arg.Kind == shimast.KindStringLiteral {
|
|
296
|
+
idx := 0
|
|
297
|
+
for i, v := range info.enumValues {
|
|
298
|
+
if v == shimast.NodeText(w.arg) {
|
|
299
|
+
idx = i
|
|
300
|
+
break
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
e.stmts = append(e.stmts, binaryStmt(e.columnAccess(w.field, "values"), shimast.KindEqualsToken, num(idx)))
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
e.stmts = append(e.stmts, binaryStmt(e.columnAccess(w.field, "values"), shimast.KindEqualsToken,
|
|
307
|
+
enumSwitchIIFE(w.arg, info.enumValues)))
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
func (e *inlineEmitter) emitDirect(w tagWrite, eager, literal bool) {
|
|
311
|
+
if literal {
|
|
312
|
+
if !eager {
|
|
313
|
+
e.emitNullsSet(w.field)
|
|
314
|
+
}
|
|
315
|
+
e.stmts = append(e.stmts, binaryStmt(e.columnAccess(w.field, "values"), shimast.KindEqualsToken, w.arg))
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
v := e.freshVar()
|
|
319
|
+
e.stmts = append(e.stmts, constDecl(v, w.arg))
|
|
320
|
+
var ifBody []*shimast.Node
|
|
321
|
+
if !eager {
|
|
322
|
+
ifBody = append(ifBody, binaryStmt(e.columnAccess(w.field, "nulls"), shimast.KindBarEqualsToken, num(1)))
|
|
323
|
+
}
|
|
324
|
+
ifBody = append(ifBody, binaryStmt(e.columnAccess(w.field, "values"), shimast.KindEqualsToken, ident(identText(v))))
|
|
325
|
+
e.stmts = append(e.stmts, notNullGuard(v, ifBody))
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
func identText(n *shimast.Node) string { return shimast.NodeText(n) }
|
|
329
|
+
|
|
330
|
+
func constDecl(name *shimast.Node, init *shimast.Node) *shimast.Node {
|
|
331
|
+
decl := factory.NewVariableDeclaration(name, nil, nil, init)
|
|
332
|
+
return factory.NewVariableStatement(nil,
|
|
333
|
+
factory.NewVariableDeclarationList(factory.NewNodeList([]*shimast.Node{decl}), shimast.NodeFlagsConst))
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// notNullGuard builds `if (<v> != null) { <body> }`.
|
|
337
|
+
func notNullGuard(v *shimast.Node, body []*shimast.Node) *shimast.Node {
|
|
338
|
+
cond := factory.NewBinaryExpression(nil, ident(identText(v)), nil,
|
|
339
|
+
factory.NewToken(shimast.KindExclamationEqualsToken),
|
|
340
|
+
factory.NewKeywordExpression(shimast.KindNullKeyword))
|
|
341
|
+
return factory.NewIfStatement(cond, factory.NewBlock(factory.NewNodeList(body), true), nil)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// enumSwitchIIFE builds `(($$v) => { switch ($$v) { case 'A': return 0; ... default: return 0; } })(arg)`.
|
|
345
|
+
func enumSwitchIIFE(arg *shimast.Node, values []string) *shimast.Node {
|
|
346
|
+
param := ident("$$v")
|
|
347
|
+
var clauses []*shimast.Node
|
|
348
|
+
for i, v := range values {
|
|
349
|
+
clauses = append(clauses, factory.NewCaseOrDefaultClause(shimast.KindCaseClause, str(v),
|
|
350
|
+
factory.NewNodeList([]*shimast.Node{factory.NewReturnStatement(num(i))})))
|
|
351
|
+
}
|
|
352
|
+
clauses = append(clauses, factory.NewCaseOrDefaultClause(shimast.KindDefaultClause, nil,
|
|
353
|
+
factory.NewNodeList([]*shimast.Node{factory.NewReturnStatement(num(0))})))
|
|
354
|
+
sw := factory.NewSwitchStatement(ident("$$v"), factory.NewCaseBlock(factory.NewNodeList(clauses)))
|
|
355
|
+
arrow := factory.NewArrowFunction(nil, nil,
|
|
356
|
+
factory.NewNodeList([]*shimast.Node{factory.NewParameterDeclaration(nil, nil, param, nil, nil, nil)}),
|
|
357
|
+
nil, nil, factory.NewToken(shimast.KindEqualsGreaterThanToken),
|
|
358
|
+
factory.NewBlock(factory.NewNodeList([]*shimast.Node{sw}), true))
|
|
359
|
+
return factory.NewCallExpression(factory.NewParenthesizedExpression(arrow), nil, nil,
|
|
360
|
+
factory.NewNodeList([]*shimast.Node{arg}), shimast.NodeFlagsNone)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// --- two-phase statement-level entry ----------------------------------------------
|
|
364
|
+
//
|
|
365
|
+
// Phase A resolves chains with the Checker over the untouched tree; phase B
|
|
366
|
+
// splices synthesized blocks. Splitting matters: lazy type checking triggered
|
|
367
|
+
// by a phase-A query re-walks whole containing declarations, and would panic
|
|
368
|
+
// on any already-spliced synthesized node (pos -1 source reads).
|
|
369
|
+
|
|
370
|
+
type tagInline struct {
|
|
371
|
+
list *shimast.NodeList
|
|
372
|
+
index int
|
|
373
|
+
ctxExpr *shimast.Node
|
|
374
|
+
writes []tagWrite
|
|
375
|
+
schema map[string]schemaField
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// collectTagInlines finds every provable tag-chain and log-chain
|
|
379
|
+
// ExpressionStatement. Checker queries happen here and only here; the tree
|
|
380
|
+
// is not modified.
|
|
381
|
+
func (t *fileTransformer) collectTagInlines(root *shimast.Node) ([]tagInline, []logInline, []resultInline) {
|
|
382
|
+
var found []tagInline
|
|
383
|
+
var foundLogs []logInline
|
|
384
|
+
var foundResults []resultInline
|
|
385
|
+
var visit func(node *shimast.Node)
|
|
386
|
+
scanList := func(list *shimast.NodeList) {
|
|
387
|
+
if list == nil {
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
for i, stmt := range list.Nodes {
|
|
391
|
+
var inner *shimast.Node
|
|
392
|
+
isReturn := false
|
|
393
|
+
switch stmt.Kind {
|
|
394
|
+
case shimast.KindExpressionStatement:
|
|
395
|
+
inner = stmt.AsExpressionStatement().Expression
|
|
396
|
+
case shimast.KindReturnStatement:
|
|
397
|
+
inner = stmt.AsReturnStatement().Expression
|
|
398
|
+
isReturn = true
|
|
399
|
+
default:
|
|
400
|
+
continue
|
|
401
|
+
}
|
|
402
|
+
if inner == nil || inner.Kind != shimast.KindCallExpression {
|
|
403
|
+
continue
|
|
404
|
+
}
|
|
405
|
+
call := inner.AsCallExpression()
|
|
406
|
+
if !isReturn {
|
|
407
|
+
if ctxExpr, writes, schema, ok := t.findTagChain(call); ok {
|
|
408
|
+
t.processed[call] = true
|
|
409
|
+
found = append(found, tagInline{list: list, index: i, ctxExpr: ctxExpr, writes: writes, schema: schema})
|
|
410
|
+
continue
|
|
411
|
+
}
|
|
412
|
+
if in, ok := t.findLogInline(call); ok {
|
|
413
|
+
t.processed[call] = true
|
|
414
|
+
in.list, in.index = list, i
|
|
415
|
+
foundLogs = append(foundLogs, *in)
|
|
416
|
+
continue
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if in, ok := t.findResultInline(call); ok {
|
|
420
|
+
t.processed[call] = true
|
|
421
|
+
in.list, in.index, in.isReturn = list, i, isReturn
|
|
422
|
+
foundResults = append(foundResults, *in)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
visit = func(node *shimast.Node) {
|
|
427
|
+
if node == nil {
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
if node.Kind == shimast.KindSourceFile {
|
|
431
|
+
scanList(node.AsSourceFile().Statements)
|
|
432
|
+
} else if node.CanHaveStatements() {
|
|
433
|
+
scanList(node.StatementList())
|
|
434
|
+
}
|
|
435
|
+
node.ForEachChild(func(child *shimast.Node) bool {
|
|
436
|
+
visit(child)
|
|
437
|
+
return false
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
visit(root)
|
|
441
|
+
return found, foundLogs, foundResults
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// applyTagInlines splices the replacement blocks (phase B — no checker use).
|
|
445
|
+
func (t *fileTransformer) applyTagInlines(inlines []tagInline) {
|
|
446
|
+
for _, in := range inlines {
|
|
447
|
+
e := &inlineEmitter{bufferExpr: propAccess(in.ctxExpr, "_buffer")}
|
|
448
|
+
for _, w := range in.writes {
|
|
449
|
+
e.emitField(w, in.schema)
|
|
450
|
+
}
|
|
451
|
+
in.list.Nodes[in.index] = factory.NewBlock(factory.NewNodeList(e.stmts), true)
|
|
452
|
+
}
|
|
453
|
+
}
|