ttsc 0.18.4 → 0.19.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.
- package/README.md +5 -6
- package/cmd/graphbench/main.go +1 -1
- package/cmd/platform/main.go +1 -48
- package/cmd/ttsc/build_branches_test.go +5 -1
- package/cmd/ttsc/main.go +0 -50
- package/cmd/ttsc/main_alias_branches_test.go +6 -12
- package/cmd/ttscgraph/dump.go +13 -3
- package/cmd/ttscserver/main.go +5 -3
- package/driver/lsp.go +13 -1
- package/driver/plugins.go +35 -0
- package/driver/program.go +7 -8
- package/driver/rewrite.go +381 -21
- package/go.mod +1 -0
- package/internal/graph/build.go +297 -3
- package/internal/graph/build_records_a_node_per_top_level_declaration_test.go +16 -2
- package/internal/graph/diagnostics.go +5 -4
- package/internal/graph/dump.go +60 -4
- package/internal/graph/dump_is_a_function_of_the_source_test.go +71 -0
- package/internal/graph/dump_serializes_the_full_graph_test.go +16 -4
- package/internal/graph/edges.go +122 -11
- package/internal/graph/exports.go +74 -17
- package/internal/graph/graph.go +33 -1
- package/internal/graph/node_identity_is_position_invariant_test.go +4 -2
- package/internal/graph/resolve.go +24 -0
- package/internal/graphsymbols/provider.go +50 -14
- package/internal/lspserver/lsp_native_plugin_source.go +77 -23
- package/internal/lspserver/lsp_plugin.go +19 -2
- package/internal/lspserver/lsp_proxy.go +244 -52
- package/internal/lspserver/lsp_server.go +35 -27
- package/internal/lspserver/lsp_symbols.go +25 -16
- package/lib/TtscCompiler.d.ts +10 -0
- package/lib/TtscCompiler.js +24 -6
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/TtscService.js +9 -7
- package/lib/TtscService.js.map +1 -1
- package/lib/compiler/internal/compileProjectInMemory.js +1 -0
- package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/project/createNativeProjectContextArgs.d.ts +3 -0
- package/lib/compiler/internal/project/createNativeProjectContextArgs.js +14 -0
- package/lib/compiler/internal/project/createNativeProjectContextArgs.js.map +1 -0
- package/lib/compiler/internal/project/readProjectConfig.js +53 -4
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/project/resolveProjectConfig.d.ts +9 -2
- package/lib/compiler/internal/project/resolveProjectConfig.js +45 -16
- package/lib/compiler/internal/project/resolveProjectConfig.js.map +1 -1
- package/lib/compiler/internal/residentTransformProcess.d.ts +16 -5
- package/lib/compiler/internal/residentTransformProcess.js +68 -20
- package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +11 -0
- package/lib/compiler/internal/runBuild.js +44 -8
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.js +7 -4
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +16 -0
- package/lib/compiler/internal/sharedHostHelpers.js +23 -0
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.js +18 -1
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +34 -8
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/parser.d.ts +17 -0
- package/lib/flags/parser.js +11 -1
- package/lib/flags/parser.js.map +1 -1
- package/lib/flags/schema.js +7 -0
- package/lib/flags/schema.js.map +1 -1
- package/lib/launcher/internal/prepareExecution.js +21 -2
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +13 -53
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +11 -0
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +27 -11
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +81 -0
- package/lib/launcher/internal/runtimeHooks.js +632 -43
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/launcher/internal/servedSourceMap.d.ts +25 -0
- package/lib/launcher/internal/servedSourceMap.js +164 -0
- package/lib/launcher/internal/servedSourceMap.js.map +1 -0
- package/lib/plugin/internal/buildSourcePlugin.d.ts +146 -2
- package/lib/plugin/internal/buildSourcePlugin.js +651 -113
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +7 -0
- package/lib/plugin/internal/loadProjectPlugins.js +33 -10
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscCompilerContext.d.ts +16 -2
- package/lib/structures/ITtscPluginCapabilities.d.ts +8 -0
- package/lib/structures/ITtscPluginFactoryContext.d.ts +14 -0
- package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +3 -0
- package/lib/structures/internal/ITtscProjectIdentity.d.ts +17 -0
- package/lib/structures/internal/ITtscProjectIdentity.js +3 -0
- package/lib/structures/internal/ITtscProjectIdentity.js.map +1 -0
- package/lib/structures/internal/TtscCommonOptions.d.ts +8 -0
- package/package.json +11 -8
- package/shim/ast/lint.go +10 -0
- package/shim/ast/shim.go +44 -0
- package/shim/checker/enums_gen.go +19 -0
- package/shim/checker/shim.go +130 -0
- package/shim/diagnosticwriter/lint.go +30 -3
- package/shim/scanner/regexp.go +27 -0
- package/src/TtscCompiler.ts +25 -6
- package/src/TtscService.ts +15 -7
- package/src/compiler/internal/compileProjectInMemory.ts +1 -0
- package/src/compiler/internal/project/createNativeProjectContextArgs.ts +15 -0
- package/src/compiler/internal/project/readProjectConfig.ts +61 -5
- package/src/compiler/internal/project/resolveProjectConfig.ts +51 -21
- package/src/compiler/internal/residentTransformProcess.ts +95 -21
- package/src/compiler/internal/runBuild.ts +76 -9
- package/src/compiler/internal/runSingleFileEmit.ts +7 -4
- package/src/compiler/internal/sharedHostHelpers.ts +24 -0
- package/src/compiler/internal/startResidentTransform.ts +21 -1
- package/src/compiler/internal/transformProjectInMemory.ts +49 -6
- package/src/flags/parser.ts +28 -1
- package/src/flags/schema.ts +8 -0
- package/src/launcher/internal/prepareExecution.ts +22 -2
- package/src/launcher/internal/runTtsc.ts +13 -61
- package/src/launcher/internal/runTtscserver.ts +14 -0
- package/src/launcher/internal/runTtsx.ts +28 -13
- package/src/launcher/internal/runtimeHooks.ts +771 -53
- package/src/launcher/internal/servedSourceMap.ts +183 -0
- package/src/plugin/internal/buildSourcePlugin.ts +859 -122
- package/src/plugin/internal/loadProjectPlugins.ts +36 -6
- package/src/structures/ITtscCompilerContext.ts +17 -2
- package/src/structures/ITtscPluginCapabilities.ts +9 -0
- package/src/structures/ITtscPluginFactoryContext.ts +15 -0
- package/src/structures/internal/ITtscParsedProjectConfig.ts +3 -0
- package/src/structures/internal/ITtscProjectIdentity.ts +17 -0
- package/src/structures/internal/TtscCommonOptions.ts +8 -0
- package/tools/shim_audit/baseline.json +16 -7
- package/tools/shim_audit/main.go +777 -71
- package/tools/shim_audit/main_test.go +28 -1
- package/tools/shim_audit/producer_exemptions_require_rationale_test.go +42 -0
- package/tools/shim_audit/producer_surface_includes_exposed_methods_test.go +154 -0
- package/tools/shim_audit/producer_surface_rejects_unproducible_reference_test.go +142 -0
- package/utility/host.go +46 -20
- package/utility/serve.go +1 -1
- package/shim/checker/signature_api_completeness.go +0 -58
package/internal/graph/build.go
CHANGED
|
@@ -17,9 +17,11 @@ func Build(prog *driver.Program) *Graph {
|
|
|
17
17
|
g := &Graph{
|
|
18
18
|
Nodes: map[string]*Node{},
|
|
19
19
|
bodyNodes: map[string]bool{},
|
|
20
|
-
seen: map[
|
|
20
|
+
seen: map[edgeKey]struct{}{},
|
|
21
|
+
resolved: map[*shimast.Node]*Target{},
|
|
21
22
|
}
|
|
22
23
|
for _, file := range prog.SourceFiles() {
|
|
24
|
+
g.putModuleNode(file)
|
|
23
25
|
collectDeclarations(g, file)
|
|
24
26
|
}
|
|
25
27
|
g.addEdges(prog)
|
|
@@ -59,6 +61,24 @@ func collectStatements(g *Graph, path string, statements []*shimast.Node) {
|
|
|
59
61
|
switch statement.Kind {
|
|
60
62
|
case shimast.KindFunctionDeclaration:
|
|
61
63
|
addNode(g, path, statement, NodeFunction)
|
|
64
|
+
// A function declared inside a function is still a name the runtime calls.
|
|
65
|
+
// This was off for a while on the theory that a closure is implementation,
|
|
66
|
+
// and implementation is read from the file — but Vue's renderer chain lives
|
|
67
|
+
// inside `baseCreateRenderer`, so `patch`, `mountElement` and
|
|
68
|
+
// `setupRenderEffect` were not implementation detail the index could skip;
|
|
69
|
+
// they were the flow the index exists to describe, and the graph had a blank
|
|
70
|
+
// where they belong. Asked how a state change reaches the DOM, a model spent
|
|
71
|
+
// three calls hunting for a name the graph did not hold.
|
|
72
|
+
//
|
|
73
|
+
// The cost is small and the answer is not bigger: the graph grows 3% in
|
|
74
|
+
// nodes on Vue and 5% on VS Code, and the tour payload does not change by a
|
|
75
|
+
// byte, because a closure ranks below the surface it hangs under and never
|
|
76
|
+
// takes a seed. It answers when asked for by name. Measured on the
|
|
77
|
+
// specific-flow lane: 59% of baseline tokens saved to 82%, and the calls
|
|
78
|
+
// halve — TypeORM 5 to 1, VS Code 8 to 2, Vue 6 to 2.
|
|
79
|
+
//
|
|
80
|
+
// The bodies stay out. A closure is a node with edges, not source text.
|
|
81
|
+
collectClosures(g, path, statement)
|
|
62
82
|
case shimast.KindClassDeclaration:
|
|
63
83
|
addNode(g, path, statement, NodeClass)
|
|
64
84
|
collectMembers(g, path, statement)
|
|
@@ -81,7 +101,8 @@ func collectStatements(g *Graph, path string, statements []*shimast.Node) {
|
|
|
81
101
|
}
|
|
82
102
|
|
|
83
103
|
// collectVariables records a variable node for each binding in a top-level
|
|
84
|
-
// variable statement (both bindings of `const a = 1, b = 2`)
|
|
104
|
+
// variable statement (both bindings of `const a = 1, b = 2`), then the functions
|
|
105
|
+
// declared inside a binding that holds one.
|
|
85
106
|
func collectVariables(g *Graph, path string, statement *shimast.Node) {
|
|
86
107
|
variables := statement.AsVariableStatement()
|
|
87
108
|
if variables == nil || variables.DeclarationList == nil {
|
|
@@ -93,9 +114,248 @@ func collectVariables(g *Graph, path string, statement *shimast.Node) {
|
|
|
93
114
|
}
|
|
94
115
|
for _, binding := range list.Declarations.Nodes {
|
|
95
116
|
addNode(g, path, binding, NodeVariable)
|
|
117
|
+
collectClosures(g, path, binding)
|
|
96
118
|
}
|
|
97
119
|
}
|
|
98
120
|
|
|
121
|
+
// collectClosures records a node for each function a declaration's body declares,
|
|
122
|
+
// and for the functions those declare in turn.
|
|
123
|
+
//
|
|
124
|
+
// A factory that closes over its state and returns local functions is how much of
|
|
125
|
+
// the ecosystem writes its engine: Vue's `patch`, `mountElement`, and
|
|
126
|
+
// `setupRenderEffect` are locals of `baseCreateRenderer`; a curried validator's
|
|
127
|
+
// real parse is a local of the function that binds its error class. Recording only
|
|
128
|
+
// what a file declares at its top level left that code out of the graph entirely —
|
|
129
|
+
// a model asking how a state change reaches the DOM found the factory, nothing
|
|
130
|
+
// under it, and went to read the files.
|
|
131
|
+
//
|
|
132
|
+
// Only functions are recorded. A local `const i = 0` is a value, not a place code
|
|
133
|
+
// runs, and the graph would drown in them.
|
|
134
|
+
func collectClosures(g *Graph, path string, declaration *shimast.Node) {
|
|
135
|
+
for _, closure := range ClosuresIn(declaration) {
|
|
136
|
+
name, ok := ClosureName(closure)
|
|
137
|
+
if !ok {
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
kind := NodeFunction
|
|
141
|
+
if closure.Kind == shimast.KindVariableDeclaration {
|
|
142
|
+
kind = NodeVariable
|
|
143
|
+
}
|
|
144
|
+
putDeclaredNode(g, path, name, kind, closure)
|
|
145
|
+
if node, ok := g.Nodes[nodeID(path, name, kind)]; ok {
|
|
146
|
+
node.Closure = true
|
|
147
|
+
}
|
|
148
|
+
collectClosures(g, path, closure)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ClosureName returns the name a closure is recorded under — its own name behind
|
|
153
|
+
// the names of the functions it is nested in, so `baseCreateRenderer.patch` and
|
|
154
|
+
// another file-mate's `patch` are two nodes rather than one merged phantom.
|
|
155
|
+
//
|
|
156
|
+
// It reports false when any enclosing function is anonymous. An `inner` declared
|
|
157
|
+
// inside two different callbacks of one file would otherwise key the same id and
|
|
158
|
+
// merge into a node that is neither, fabricating edges between unrelated scopes.
|
|
159
|
+
// Such a closure stays out of the graph, exactly as every body-scoped declaration
|
|
160
|
+
// did before.
|
|
161
|
+
func ClosureName(closure *shimast.Node) (string, bool) {
|
|
162
|
+
symbol := closure.Symbol()
|
|
163
|
+
if symbol == nil {
|
|
164
|
+
return "", false
|
|
165
|
+
}
|
|
166
|
+
name := qualifiedName(symbol)
|
|
167
|
+
if name == "" {
|
|
168
|
+
return "", false
|
|
169
|
+
}
|
|
170
|
+
for parent := closure.Parent; parent != nil; parent = parent.Parent {
|
|
171
|
+
if parent.Kind == shimast.KindSourceFile {
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
if !isFunctionLike(parent) {
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
owner, ok := ownerName(parent)
|
|
178
|
+
if !ok {
|
|
179
|
+
return "", false
|
|
180
|
+
}
|
|
181
|
+
name = owner + "." + name
|
|
182
|
+
}
|
|
183
|
+
return name, true
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// isFunctionLike reports whether a node is a function whose body is a scope. A
|
|
187
|
+
// variable that binds one is not: the function it holds is a node of this chain
|
|
188
|
+
// already, and counting both would name a closure after its owner twice.
|
|
189
|
+
func isFunctionLike(node *shimast.Node) bool {
|
|
190
|
+
switch node.Kind {
|
|
191
|
+
case shimast.KindFunctionDeclaration,
|
|
192
|
+
shimast.KindFunctionExpression,
|
|
193
|
+
shimast.KindArrowFunction,
|
|
194
|
+
shimast.KindMethodDeclaration,
|
|
195
|
+
shimast.KindConstructor,
|
|
196
|
+
shimast.KindGetAccessor,
|
|
197
|
+
shimast.KindSetAccessor:
|
|
198
|
+
return true
|
|
199
|
+
}
|
|
200
|
+
return false
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ownerName returns the name of a function-like declaration a closure sits in: a
|
|
204
|
+
// function or method by its own (class-qualified) name, a function expression or
|
|
205
|
+
// arrow by the variable that binds it. It reports false for an anonymous one.
|
|
206
|
+
func ownerName(declaration *shimast.Node) (string, bool) {
|
|
207
|
+
switch declaration.Kind {
|
|
208
|
+
case shimast.KindFunctionExpression, shimast.KindArrowFunction:
|
|
209
|
+
binding := bindingOf(declaration)
|
|
210
|
+
if binding == nil {
|
|
211
|
+
return "", false
|
|
212
|
+
}
|
|
213
|
+
declaration = binding
|
|
214
|
+
}
|
|
215
|
+
symbol := declaration.Symbol()
|
|
216
|
+
if symbol == nil {
|
|
217
|
+
return "", false
|
|
218
|
+
}
|
|
219
|
+
name := qualifiedName(symbol)
|
|
220
|
+
if name == "" {
|
|
221
|
+
return "", false
|
|
222
|
+
}
|
|
223
|
+
return name, true
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// bindingOf returns the variable declaration a function expression is bound to,
|
|
227
|
+
// seeing through the wrappers a codebase writes around one, or nil when the
|
|
228
|
+
// function is anonymous.
|
|
229
|
+
func bindingOf(fn *shimast.Node) *shimast.Node {
|
|
230
|
+
for parent := fn.Parent; parent != nil; parent = parent.Parent {
|
|
231
|
+
switch parent.Kind {
|
|
232
|
+
case shimast.KindVariableDeclaration:
|
|
233
|
+
return parent
|
|
234
|
+
case shimast.KindAsExpression,
|
|
235
|
+
shimast.KindSatisfiesExpression,
|
|
236
|
+
shimast.KindParenthesizedExpression:
|
|
237
|
+
continue
|
|
238
|
+
default:
|
|
239
|
+
return nil
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return nil
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ClosuresIn returns the functions a declaration's body declares — a nested
|
|
246
|
+
// function declaration, or a binding that holds a function — found however deep
|
|
247
|
+
// in the body's statements they sit (inside an `if`, a `try`, a loop), but not
|
|
248
|
+
// past one: a closure's own closures belong to it, and the caller recurses.
|
|
249
|
+
//
|
|
250
|
+
// A binding that holds no function is not one. A local `const i = 0` is a value,
|
|
251
|
+
// not a place code runs.
|
|
252
|
+
func ClosuresIn(declaration *shimast.Node) []*shimast.Node {
|
|
253
|
+
body := functionBody(declaration)
|
|
254
|
+
if body == nil {
|
|
255
|
+
return nil
|
|
256
|
+
}
|
|
257
|
+
var closures []*shimast.Node
|
|
258
|
+
var walk func(node *shimast.Node)
|
|
259
|
+
walk = func(node *shimast.Node) {
|
|
260
|
+
node.ForEachChild(func(child *shimast.Node) bool {
|
|
261
|
+
if IsClosure(child) {
|
|
262
|
+
closures = append(closures, child)
|
|
263
|
+
return false
|
|
264
|
+
}
|
|
265
|
+
walk(child)
|
|
266
|
+
return false
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
walk(body)
|
|
270
|
+
return closures
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// IsClosure reports whether a node inside a function body is a function the graph
|
|
274
|
+
// records: a nested function declaration, or a binding that holds a function.
|
|
275
|
+
func IsClosure(node *shimast.Node) bool {
|
|
276
|
+
switch node.Kind {
|
|
277
|
+
case shimast.KindFunctionDeclaration:
|
|
278
|
+
return true
|
|
279
|
+
case shimast.KindVariableDeclaration:
|
|
280
|
+
return functionBody(node) != nil
|
|
281
|
+
}
|
|
282
|
+
return false
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// functionBody returns the block body of any function-like declaration — a
|
|
286
|
+
// function, a method, an accessor, a constructor, or a function/arrow expression
|
|
287
|
+
// a variable binds.
|
|
288
|
+
func functionBody(declaration *shimast.Node) *shimast.Node {
|
|
289
|
+
switch declaration.Kind {
|
|
290
|
+
case shimast.KindFunctionDeclaration:
|
|
291
|
+
if fn := declaration.AsFunctionDeclaration(); fn != nil {
|
|
292
|
+
return fn.Body
|
|
293
|
+
}
|
|
294
|
+
case shimast.KindFunctionExpression:
|
|
295
|
+
if fn := declaration.AsFunctionExpression(); fn != nil {
|
|
296
|
+
return fn.Body
|
|
297
|
+
}
|
|
298
|
+
case shimast.KindArrowFunction:
|
|
299
|
+
if fn := declaration.AsArrowFunction(); fn != nil {
|
|
300
|
+
return fn.Body
|
|
301
|
+
}
|
|
302
|
+
case shimast.KindMethodDeclaration:
|
|
303
|
+
if fn := declaration.AsMethodDeclaration(); fn != nil {
|
|
304
|
+
return fn.Body
|
|
305
|
+
}
|
|
306
|
+
case shimast.KindConstructor:
|
|
307
|
+
if fn := declaration.AsConstructorDeclaration(); fn != nil {
|
|
308
|
+
return fn.Body
|
|
309
|
+
}
|
|
310
|
+
case shimast.KindGetAccessor:
|
|
311
|
+
if fn := declaration.AsGetAccessorDeclaration(); fn != nil {
|
|
312
|
+
return fn.Body
|
|
313
|
+
}
|
|
314
|
+
case shimast.KindSetAccessor:
|
|
315
|
+
if fn := declaration.AsSetAccessorDeclaration(); fn != nil {
|
|
316
|
+
return fn.Body
|
|
317
|
+
}
|
|
318
|
+
case shimast.KindVariableDeclaration:
|
|
319
|
+
if binding := declaration.AsVariableDeclaration(); binding != nil {
|
|
320
|
+
return functionBodyOfInitializer(binding.Initializer)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return nil
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// functionBodyOfInitializer unwraps a binding's initializer to the body of the
|
|
327
|
+
// function it holds, seeing through the `as const` / satisfies wrappers a
|
|
328
|
+
// codebase writes around one.
|
|
329
|
+
func functionBodyOfInitializer(initializer *shimast.Node) *shimast.Node {
|
|
330
|
+
for initializer != nil {
|
|
331
|
+
switch initializer.Kind {
|
|
332
|
+
case shimast.KindFunctionExpression, shimast.KindArrowFunction:
|
|
333
|
+
return functionBody(initializer)
|
|
334
|
+
case shimast.KindAsExpression:
|
|
335
|
+
as := initializer.AsAsExpression()
|
|
336
|
+
if as == nil {
|
|
337
|
+
return nil
|
|
338
|
+
}
|
|
339
|
+
initializer = as.Expression
|
|
340
|
+
case shimast.KindSatisfiesExpression:
|
|
341
|
+
satisfies := initializer.AsSatisfiesExpression()
|
|
342
|
+
if satisfies == nil {
|
|
343
|
+
return nil
|
|
344
|
+
}
|
|
345
|
+
initializer = satisfies.Expression
|
|
346
|
+
case shimast.KindParenthesizedExpression:
|
|
347
|
+
parenthesized := initializer.AsParenthesizedExpression()
|
|
348
|
+
if parenthesized == nil {
|
|
349
|
+
return nil
|
|
350
|
+
}
|
|
351
|
+
initializer = parenthesized.Expression
|
|
352
|
+
default:
|
|
353
|
+
return nil
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return nil
|
|
357
|
+
}
|
|
358
|
+
|
|
99
359
|
// addNode records a node for the symbol declared by node under its
|
|
100
360
|
// position-invariant id. A declaration the checker did not bind to a single
|
|
101
361
|
// named symbol (a destructuring pattern) is skipped. Redeclarations keep the
|
|
@@ -125,6 +385,7 @@ func collectMembers(g *Graph, path string, statement *shimast.Node) {
|
|
|
125
385
|
case isPropertyMember(member.Kind):
|
|
126
386
|
putDeclaredNode(g, path, name, NodeVariable, member)
|
|
127
387
|
}
|
|
388
|
+
collectClosures(g, path, member)
|
|
128
389
|
}
|
|
129
390
|
}
|
|
130
391
|
|
|
@@ -253,7 +514,40 @@ func simpleName(symbol *shimast.Symbol) string {
|
|
|
253
514
|
if symbol == nil || symbol.Name == "" {
|
|
254
515
|
return ""
|
|
255
516
|
}
|
|
256
|
-
return strings.ReplaceAll(symbol.Name, "\xFE", "__")
|
|
517
|
+
return stripPrivateMangling(strings.ReplaceAll(symbol.Name, "\xFE", "__"))
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// stripPrivateMangling removes the checker's per-run counter from the name of a
|
|
521
|
+
// private class member.
|
|
522
|
+
//
|
|
523
|
+
// A `#field` is bound under a mangled name — `__#41@#field` — whose number comes
|
|
524
|
+
// from a counter that advances as the program is bound, so the same field is
|
|
525
|
+
// `__#41@#field` in one run and `__#38@#field` in the next. That counter reached
|
|
526
|
+
// the node id, and with it the wire: on VS Code, 661 nodes and 661 edges changed
|
|
527
|
+
// identity between two dumps of the *same unedited source*. A handle the model
|
|
528
|
+
// was given could name nothing after a restart, and no dump could be compared to
|
|
529
|
+
// another to prove a change had left the facts alone.
|
|
530
|
+
//
|
|
531
|
+
// The number identifies nothing a reader can use — the class already
|
|
532
|
+
// distinguishes the field, and `#field` is what the source calls it. Dropping it
|
|
533
|
+
// makes the id a function of the code again.
|
|
534
|
+
func stripPrivateMangling(name string) string {
|
|
535
|
+
const prefix = "__#"
|
|
536
|
+
start := strings.Index(name, prefix)
|
|
537
|
+
if start == -1 {
|
|
538
|
+
return name
|
|
539
|
+
}
|
|
540
|
+
rest := name[start+len(prefix):]
|
|
541
|
+
at := strings.IndexByte(rest, '@')
|
|
542
|
+
if at <= 0 {
|
|
543
|
+
return name
|
|
544
|
+
}
|
|
545
|
+
for _, char := range rest[:at] {
|
|
546
|
+
if char < '0' || char > '9' {
|
|
547
|
+
return name
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return name[:start] + rest[at+1:]
|
|
257
551
|
}
|
|
258
552
|
|
|
259
553
|
// qualifiedName is the identity name of a symbol: its own name, prefixed by the
|
|
@@ -68,8 +68,10 @@ export const value = 1;
|
|
|
68
68
|
"En": NodeEnum,
|
|
69
69
|
"value": NodeVariable,
|
|
70
70
|
}
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
// The module node is the file's export surface, not a declaration in it, so
|
|
72
|
+
// the declaration count excludes it.
|
|
73
|
+
if declared := declaredNodeCount(graph); declared != len(want) {
|
|
74
|
+
t.Fatalf("expected %d nodes, got %d: %v", len(want), declared, nodeIDSet(graph))
|
|
73
75
|
}
|
|
74
76
|
for name, kind := range want {
|
|
75
77
|
id := nodeID(path, name, kind)
|
|
@@ -86,6 +88,18 @@ export const value = 1;
|
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
// declaredNodeCount counts the nodes that stand for a declaration, leaving out
|
|
92
|
+
// the module node a file's export table adds.
|
|
93
|
+
func declaredNodeCount(graph *Graph) int {
|
|
94
|
+
count := 0
|
|
95
|
+
for _, node := range graph.Nodes {
|
|
96
|
+
if node.Kind != NodeModule {
|
|
97
|
+
count++
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return count
|
|
101
|
+
}
|
|
102
|
+
|
|
89
103
|
// nodeIDSet returns the graph's node ids as a slice for failure messages.
|
|
90
104
|
func nodeIDSet(graph *Graph) []string {
|
|
91
105
|
ids := make([]string, 0, len(graph.Nodes))
|
|
@@ -8,10 +8,11 @@ import "github.com/samchon/ttsc/packages/ttsc/driver"
|
|
|
8
8
|
// over the warm checker, not a second compile.
|
|
9
9
|
//
|
|
10
10
|
// This is the compiler-only slice: it carries the TypeScript semantic
|
|
11
|
-
// diagnostics, not @ttsc/lint or transform-plugin findings.
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
11
|
+
// diagnostics, not @ttsc/lint or transform-plugin findings. Nothing in the
|
|
12
|
+
// graph pipeline calls it yet; the dump and MCP results carry no diagnostics.
|
|
13
|
+
// This helper and its tests pin the compiler-diagnostic conversion (code,
|
|
14
|
+
// column, message) so a future diagnostics integration starts from a proven
|
|
15
|
+
// producer.
|
|
15
16
|
func FileDiagnostics(prog *driver.Program, path string) []driver.Diagnostic {
|
|
16
17
|
out := make([]driver.Diagnostic, 0)
|
|
17
18
|
for _, diagnostic := range prog.Diagnostics() {
|
package/internal/graph/dump.go
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
package graph
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"bufio"
|
|
4
5
|
"encoding/json"
|
|
6
|
+
"io"
|
|
5
7
|
"path/filepath"
|
|
6
8
|
"sort"
|
|
7
9
|
"strings"
|
|
@@ -27,8 +29,15 @@ import (
|
|
|
27
29
|
|
|
28
30
|
// DumpEvidence is a 1-based source span grounding a node declaration or an edge
|
|
29
31
|
// expression. It is display/expansion only, never identity.
|
|
32
|
+
//
|
|
33
|
+
// File is omitted when the reader reconstructs it exactly: a node's span is in
|
|
34
|
+
// the node's file, and an edge's span is in the file its `from` id names. The
|
|
35
|
+
// path is long, it rode the wire once per node and once per edge, and on VS Code
|
|
36
|
+
// those two copies are 55 MB of a 323 MB document that then has to be encoded,
|
|
37
|
+
// piped, parsed and validated. An `implementation` span keeps its file — that one
|
|
38
|
+
// can genuinely live in another file from the declaration that owns it.
|
|
30
39
|
type DumpEvidence struct {
|
|
31
|
-
File string `json:"file"`
|
|
40
|
+
File string `json:"file,omitempty"`
|
|
32
41
|
StartLine int `json:"startLine"`
|
|
33
42
|
StartCol int `json:"startCol,omitempty"`
|
|
34
43
|
EndLine int `json:"endLine,omitempty"`
|
|
@@ -59,6 +68,7 @@ type DumpNode struct {
|
|
|
59
68
|
External bool `json:"external"`
|
|
60
69
|
Ignored bool `json:"ignored,omitempty"`
|
|
61
70
|
Exported bool `json:"exported,omitempty"`
|
|
71
|
+
Closure bool `json:"closure,omitempty"`
|
|
62
72
|
Modifiers []string `json:"modifiers,omitempty"`
|
|
63
73
|
Evidence *DumpEvidence `json:"evidence,omitempty"`
|
|
64
74
|
Implementation *DumpEvidence `json:"implementation,omitempty"`
|
|
@@ -108,6 +118,11 @@ func NewDump(g *Graph, project, tsconfig string, ignored map[string]bool, source
|
|
|
108
118
|
nodes := make([]DumpNode, 0, len(g.Nodes))
|
|
109
119
|
for _, n := range g.Nodes {
|
|
110
120
|
name, qualified := nodeNames(n)
|
|
121
|
+
// A module is named by its file, so its name relativizes like a path; every
|
|
122
|
+
// other node is named by a symbol.
|
|
123
|
+
if n.Kind == NodeModule {
|
|
124
|
+
name, qualified = ctx.rel(name), ""
|
|
125
|
+
}
|
|
111
126
|
nodes = append(nodes, DumpNode{
|
|
112
127
|
ID: ctx.relID(n.ID),
|
|
113
128
|
Kind: string(n.Kind),
|
|
@@ -117,8 +132,9 @@ func NewDump(g *Graph, project, tsconfig string, ignored map[string]bool, source
|
|
|
117
132
|
External: n.External,
|
|
118
133
|
Ignored: ignored[n.File],
|
|
119
134
|
Exported: n.Exported,
|
|
135
|
+
Closure: n.Closure,
|
|
120
136
|
Modifiers: n.Modifiers,
|
|
121
|
-
Evidence: ctx.evidence(n.File, n.Pos, n.End),
|
|
137
|
+
Evidence: withoutFile(ctx.evidence(n.File, n.Pos, n.End)),
|
|
122
138
|
Implementation: ctx.evidence(n.ImplementationFile, n.ImplementationPos, n.ImplementationEnd),
|
|
123
139
|
Decorators: decByNode[n.ID],
|
|
124
140
|
})
|
|
@@ -131,7 +147,7 @@ func NewDump(g *Graph, project, tsconfig string, ignored map[string]bool, source
|
|
|
131
147
|
From: ctx.relID(e.From),
|
|
132
148
|
To: ctx.relID(e.To),
|
|
133
149
|
Kind: dumpEdgeKind(e),
|
|
134
|
-
Evidence: ctx.edgeEvidence(e),
|
|
150
|
+
Evidence: withoutFile(ctx.edgeEvidence(e)),
|
|
135
151
|
})
|
|
136
152
|
}
|
|
137
153
|
sort.Slice(edges, func(i, j int) bool {
|
|
@@ -162,6 +178,26 @@ func MarshalDump(g *Graph, project, tsconfig string, ignored map[string]bool, so
|
|
|
162
178
|
return json.Marshal(d)
|
|
163
179
|
}
|
|
164
180
|
|
|
181
|
+
// EncodeDump writes the export JSON straight to w, one buffered pass, ending it
|
|
182
|
+
// with the newline the one-shot protocol expects.
|
|
183
|
+
//
|
|
184
|
+
// The alternative is what the dump command used to do: marshal the whole
|
|
185
|
+
// document into a byte slice, convert that slice into a string, and print the
|
|
186
|
+
// string. On VS Code the document is 323 MB, so the conversion was a second full
|
|
187
|
+
// copy of it held live beside the first — half a gigabyte of peak heap that
|
|
188
|
+
// bought nothing, because the bytes were already exactly what stdout wanted.
|
|
189
|
+
func EncodeDump(w io.Writer, g *Graph, project, tsconfig string, ignored map[string]bool, sources map[string]string, pretty bool) error {
|
|
190
|
+
buffered := bufio.NewWriterSize(w, 1<<20)
|
|
191
|
+
encoder := json.NewEncoder(buffered)
|
|
192
|
+
if pretty {
|
|
193
|
+
encoder.SetIndent("", " ")
|
|
194
|
+
}
|
|
195
|
+
if err := encoder.Encode(NewDump(g, project, tsconfig, ignored, sources)); err != nil {
|
|
196
|
+
return err
|
|
197
|
+
}
|
|
198
|
+
return buffered.Flush()
|
|
199
|
+
}
|
|
200
|
+
|
|
165
201
|
// dumpEdgeKind maps an internal edge kind, refined by Edge.Origin, onto the
|
|
166
202
|
// schema's finer relationship kind.
|
|
167
203
|
func dumpEdgeKind(e *Edge) string {
|
|
@@ -256,12 +292,22 @@ func (c *dumpContext) rel(file string) string {
|
|
|
256
292
|
|
|
257
293
|
// relID relativizes the path portion of a node id ("path#qualifiedName:kind").
|
|
258
294
|
// An id with no path (a future virtual node) is returned unchanged.
|
|
295
|
+
//
|
|
296
|
+
// A module is named by its own file path, so both halves of its id are paths and
|
|
297
|
+
// both relativize. Every other node is named by a symbol, which passes through
|
|
298
|
+
// untouched.
|
|
259
299
|
func (c *dumpContext) relID(id string) string {
|
|
260
300
|
hash := strings.Index(id, "#")
|
|
261
301
|
if hash < 0 {
|
|
262
302
|
return id
|
|
263
303
|
}
|
|
264
|
-
|
|
304
|
+
head, tail := c.rel(id[:hash]), id[hash+1:]
|
|
305
|
+
suffix := ":" + string(NodeModule)
|
|
306
|
+
if strings.HasSuffix(tail, suffix) {
|
|
307
|
+
name := strings.TrimSuffix(tail, suffix)
|
|
308
|
+
return head + "#" + c.rel(name) + suffix
|
|
309
|
+
}
|
|
310
|
+
return head + "#" + tail
|
|
265
311
|
}
|
|
266
312
|
|
|
267
313
|
// evidence builds the line/col span for a byte range in file, or nil when the
|
|
@@ -337,6 +383,16 @@ func (c *dumpContext) edgeEvidence(e *Edge) *DumpEvidence {
|
|
|
337
383
|
return c.evidence(file, e.Pos, e.End)
|
|
338
384
|
}
|
|
339
385
|
|
|
386
|
+
// withoutFile drops a span file the reader reconstructs from the node or edge
|
|
387
|
+
// that carries the span. It never touches an implementation span.
|
|
388
|
+
func withoutFile(ev *DumpEvidence) *DumpEvidence {
|
|
389
|
+
if ev == nil {
|
|
390
|
+
return nil
|
|
391
|
+
}
|
|
392
|
+
ev.File = ""
|
|
393
|
+
return ev
|
|
394
|
+
}
|
|
395
|
+
|
|
340
396
|
// nodeFile recovers the source file path embedded in a node id
|
|
341
397
|
// ("path#qualifiedName:kind"); "" for an id without a path.
|
|
342
398
|
func nodeFile(id string) string {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
package graph
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"testing"
|
|
6
|
+
|
|
7
|
+
"github.com/samchon/ttsc/packages/ttsc/driver"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestDumpIsAFunctionOfTheSource pins the graph to the code it describes: two
|
|
11
|
+
// builds of one unedited snapshot must produce one byte-identical document.
|
|
12
|
+
//
|
|
13
|
+
// It did not. A private class member — `#count` — is bound under a mangled name
|
|
14
|
+
// carrying a counter that advances as the program is bound: `__#41@#count` in one
|
|
15
|
+
// run, `__#38@#count` in the next. That counter reached the node id, and with it
|
|
16
|
+
// the wire. On VS Code, 661 nodes and 661 edges changed identity between two
|
|
17
|
+
// dumps of the *same source*: a handle the model was given could name nothing
|
|
18
|
+
// after a restart, and no dump could be compared with another to prove that a
|
|
19
|
+
// change had left the facts alone — the byte test every graph optimisation is
|
|
20
|
+
// supposed to answer to was itself meaningless.
|
|
21
|
+
//
|
|
22
|
+
// Two classes each declare a `#count`, so the mangling counter is exercised more
|
|
23
|
+
// than once and a per-class collision would surface here rather than in a
|
|
24
|
+
// three-million-line repository.
|
|
25
|
+
func TestDumpIsAFunctionOfTheSource(t *testing.T) {
|
|
26
|
+
root := t.TempDir()
|
|
27
|
+
writeFile(t, filepath.Join(root, "tsconfig.json"), fixtureTSConfig)
|
|
28
|
+
writeFile(t, filepath.Join(root, "src", "main.ts"), `export class Counter {
|
|
29
|
+
#count = 0;
|
|
30
|
+
#step: number;
|
|
31
|
+
constructor(step: number) {
|
|
32
|
+
this.#step = step;
|
|
33
|
+
}
|
|
34
|
+
bump(): number {
|
|
35
|
+
this.#count += this.#step;
|
|
36
|
+
return this.#count;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class Other {
|
|
41
|
+
#count = 0;
|
|
42
|
+
read(): number {
|
|
43
|
+
return this.#count;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`)
|
|
47
|
+
|
|
48
|
+
first := dumpBytes(t, root)
|
|
49
|
+
second := dumpBytes(t, root)
|
|
50
|
+
if string(first) != string(second) {
|
|
51
|
+
t.Fatalf("two dumps of one unedited snapshot differ: the graph is not a function of the source")
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func dumpBytes(t *testing.T, root string) []byte {
|
|
56
|
+
t.Helper()
|
|
57
|
+
prog, diags, err := driver.LoadProgram(root, "tsconfig.json", driver.LoadProgramOptions{})
|
|
58
|
+
if err != nil {
|
|
59
|
+
t.Fatal(err)
|
|
60
|
+
}
|
|
61
|
+
if len(diags) != 0 {
|
|
62
|
+
t.Fatalf("unexpected diagnostics: %v", diags)
|
|
63
|
+
}
|
|
64
|
+
defer func() { _ = prog.Close() }()
|
|
65
|
+
|
|
66
|
+
data, err := MarshalDump(Build(prog), root, "tsconfig.json", nil, SourceTexts(prog), false)
|
|
67
|
+
if err != nil {
|
|
68
|
+
t.Fatalf("MarshalDump: %v", err)
|
|
69
|
+
}
|
|
70
|
+
return data
|
|
71
|
+
}
|
|
@@ -102,8 +102,18 @@ export function main(): void {
|
|
|
102
102
|
if call == nil {
|
|
103
103
|
t.Fatalf("no calls edge main -> helper in dump:\n%s", data)
|
|
104
104
|
}
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
// The edge's span carries no file: it is the file its `from` id names, which
|
|
106
|
+
// the loader reconstructs (see ITtscGraphDump.IEdge). Sending the path a
|
|
107
|
+
// second time on every edge is 17% of the document for a value the reader
|
|
108
|
+
// already holds.
|
|
109
|
+
if call.Evidence == nil || call.Evidence.StartLine == 0 {
|
|
110
|
+
t.Fatalf("call edge missing line/col evidence: %+v", call.Evidence)
|
|
111
|
+
}
|
|
112
|
+
if call.Evidence.File != "" {
|
|
113
|
+
t.Fatalf("call edge repeats the file its `from` id already names: %+v", call.Evidence)
|
|
114
|
+
}
|
|
115
|
+
if nodeFile(call.From) != "src/main.ts" {
|
|
116
|
+
t.Fatalf("the edge's file is not recoverable from its from id: %q", call.From)
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
// Every edge endpoint resolves to a dumped node.
|
|
@@ -129,8 +139,10 @@ export function main(): void {
|
|
|
129
139
|
}
|
|
130
140
|
}
|
|
131
141
|
// The dump carries no ceremony keys: it is wholly checker-resolved, so it has
|
|
132
|
-
// no per-edge trust flags and no schema version to negotiate.
|
|
133
|
-
|
|
142
|
+
// no per-edge trust flags and no schema version to negotiate. It also carries
|
|
143
|
+
// no keys the schema stopped promising: no inlined evidence text and no
|
|
144
|
+
// diagnostics array.
|
|
145
|
+
for _, gone := range []string{`"schemaVersion"`, `"provenance":`, `"confidence":`, `"text":`, `"diagnostics":`} {
|
|
134
146
|
if strings.Contains(s, gone) {
|
|
135
147
|
t.Fatalf("dump still emits removed key %s:\n%s", gone, s)
|
|
136
148
|
}
|