ttsc 0.19.2 → 0.20.0
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 +7 -5
- package/cmd/graphdump/main.go +2 -4
- package/cmd/ttsc/filter.go +12 -4
- package/cmd/ttsc/filter_resolves_flag_names_case_insensitively_test.go +53 -0
- package/cmd/ttsc/flags_gen.go +3 -3
- package/cmd/ttscgraph/dump.go +5 -7
- package/cmd/ttscgraph/run_dump_emits_only_checker_verified_member_relations_test.go +118 -0
- package/cmd/ttscgraph/serve.go +58 -308
- package/cmd/ttscgraph/serve_session_keeps_base_url_fallback_unchanged_test.go +49 -0
- package/cmd/ttscgraph/serve_session_keeps_esm_directory_candidate_unchanged_test.go +61 -0
- package/cmd/ttscgraph/serve_session_keeps_exports_blocked_file_candidates_unchanged_test.go +64 -0
- package/cmd/ttscgraph/serve_session_keeps_inactive_conditional_export_unchanged_test.go +66 -0
- package/cmd/ttscgraph/serve_session_keeps_lower_priority_module_candidate_unchanged_test.go +67 -0
- package/cmd/ttscgraph/serve_session_keeps_lower_priority_paths_pattern_unchanged_test.go +58 -0
- package/cmd/ttscgraph/serve_session_keeps_nonlongest_root_dirs_candidate_unchanged_test.go +54 -0
- package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +50 -0
- package/cmd/ttscgraph/serve_session_reloads_module_suffix_candidate_test.go +41 -0
- package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +216 -0
- package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +103 -0
- package/driver/graph.go +12 -6
- package/driver/lsp.go +30 -0
- package/driver/program.go +37 -1
- package/driver/resolution_candidates.go +819 -0
- package/driver/windowsjunction/windowsjunction.go +40 -0
- package/go.mod +2 -0
- package/internal/graph/build.go +45 -11
- package/internal/graph/dump.go +312 -108
- package/internal/graph/dump_checkout_paths_are_stable_test.go +210 -0
- package/internal/graph/dump_diagnostics_ride_the_generation_that_built_it_test.go +9 -3
- package/internal/graph/dump_paths.go +172 -0
- package/internal/graph/dump_paths_are_portable_and_injective_test.go +93 -0
- package/internal/graph/dump_schema_version_matches_the_typescript_client_test.go +1 -1
- package/internal/graph/dump_uses_ecma_line_terminators_test.go +87 -0
- package/internal/graph/duplicate_variable_object_members_keep_first_declaration_test.go +61 -0
- package/internal/graph/edges.go +220 -5
- package/internal/graph/enum_members_carry_their_names_on_the_enum_node_test.go +131 -0
- package/internal/graph/graph.go +177 -5
- package/internal/graph/line_model.go +122 -0
- package/internal/graph/literals.go +175 -0
- package/internal/graph/literals_are_absent_when_the_type_admits_more_than_it_can_name_test.go +74 -0
- package/internal/graph/literals_cover_enum_members_the_graph_has_no_nodes_for_test.go +79 -0
- package/internal/graph/literals_do_not_depend_on_declaration_line_wrapping_test.go +84 -0
- package/internal/graph/literals_render_every_value_in_typescript_source_form_test.go +77 -0
- package/internal/graph/literals_resolve_through_alias_indirection_test.go +64 -0
- package/internal/graph/member_relation_edges_follow_checker_validity_test.go +85 -0
- package/internal/graph/member_relation_edges_preserve_checker_boundaries_test.go +191 -0
- package/internal/graph/member_relation_edges_reject_checker_invalid_pairs_test.go +119 -0
- package/internal/graph/node_ids_preserve_hash_bearing_components_test.go +41 -0
- package/internal/graph/node_modifiers_emit_union_strings_test.go +4 -1
- package/internal/graph/object_literal_members_ride_on_the_snapshot_test.go +192 -0
- package/internal/graph/object_member_signatures_preserve_literal_whitespace_test.go +67 -0
- package/internal/graph/object_members.go +229 -0
- package/internal/graph/provenance.go +88 -30
- package/internal/graph/source_texts_cover_every_resident_program_source_test.go +73 -0
- package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +66 -0
- package/internal/graphsymbols/provider.go +26 -91
- package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +144 -0
- package/internal/lspserver/lsp_completion.go +109 -0
- package/internal/lspserver/lsp_completion_gate_never_refuses_a_matching_line_test.go +50 -0
- package/internal/lspserver/lsp_completion_hints_longest_trigger_wins_test.go +149 -0
- package/internal/lspserver/lsp_completion_hints_nearest_trigger_wins_test.go +37 -0
- package/internal/lspserver/lsp_completion_merges_into_upstream_response_test.go +149 -0
- package/internal/lspserver/lsp_completion_nearest_hint_replaces_nearest_filter_test.go +43 -0
- package/internal/lspserver/lsp_completion_preserves_completion_list_extensions_test.go +99 -0
- package/internal/lspserver/lsp_completion_refuses_hints_inside_a_string_literal_test.go +69 -0
- package/internal/lspserver/lsp_completion_replaces_full_filter_in_rank_order_test.go +85 -0
- package/internal/lspserver/lsp_completion_request.go +455 -0
- package/internal/lspserver/lsp_completion_scope.go +530 -0
- package/internal/lspserver/lsp_completion_scope_bench_test.go +41 -0
- package/internal/lspserver/lsp_completion_scope_classifies_regex_statement_and_operator_boundaries_test.go +39 -0
- package/internal/lspserver/lsp_completion_scope_ignores_jsdoc_bytes_in_literals_test.go +108 -0
- package/internal/lspserver/lsp_completion_scope_refuses_regex_hints_test.go +52 -0
- package/internal/lspserver/lsp_completion_trigger_uses_last_rune_test.go +24 -0
- package/internal/lspserver/lsp_deleted_watched_file_withdraws_plugin_diagnostics_test.go +110 -0
- package/internal/lspserver/lsp_didsave_keeps_the_incremental_changed_uri_path_test.go +78 -0
- package/internal/lspserver/lsp_hints_absence_is_not_an_error_test.go +46 -0
- package/internal/lspserver/lsp_hints_refresh_coalesces_concurrent_requests_test.go +82 -0
- package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +40 -0
- package/internal/lspserver/lsp_hints_refresh_keeps_each_producer_isolated_test.go +64 -0
- package/internal/lspserver/lsp_hints_refresh_replaces_the_session_snapshot_test.go +66 -0
- package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +90 -0
- package/internal/lspserver/lsp_hints_refresh_serves_completion_while_running_test.go +91 -0
- package/internal/lspserver/lsp_index_byte_from_bounds_its_search_start_test.go +38 -0
- package/internal/lspserver/lsp_initialize_constrains_position_encoding_to_utf16_test.go +205 -0
- package/internal/lspserver/lsp_late_completion_trigger_is_reported_once_test.go +105 -0
- package/internal/lspserver/lsp_native_completion_hints_decode_flat_lint_wire_test.go +66 -0
- package/internal/lspserver/lsp_native_completion_hints_grouped_compatibility_boundary_test.go +60 -0
- package/internal/lspserver/lsp_native_plugin_source.go +315 -2
- package/internal/lspserver/lsp_native_resident.go +304 -0
- package/internal/lspserver/lsp_plugin.go +85 -8
- package/internal/lspserver/lsp_plugin_completion_resolve_stays_local_test.go +82 -0
- package/internal/lspserver/lsp_position_conversions_span_astral_and_combining_runes_test.go +119 -0
- package/internal/lspserver/lsp_proxy.go +601 -9
- package/internal/lspserver/lsp_refresh.go +70 -0
- package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +43 -0
- package/internal/lspserver/lsp_resident_maps_verb_args_test.go +34 -0
- package/internal/lspserver/lsp_server.go +13 -1
- package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +172 -0
- package/lib/TtscCompiler.d.ts +3 -1
- package/lib/TtscCompiler.js +5 -1
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/TtscService.d.ts +13 -3
- package/lib/TtscService.js +6 -6
- package/lib/TtscService.js.map +1 -1
- package/lib/compiler/internal/project/readConfigJson.d.ts +30 -0
- package/lib/compiler/internal/project/readConfigJson.js +208 -0
- package/lib/compiler/internal/project/readConfigJson.js.map +1 -0
- package/lib/compiler/internal/project/readProjectConfig.js +53 -149
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/residentTransformProcess.d.ts +15 -2
- package/lib/compiler/internal/residentTransformProcess.js +159 -39
- package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +6 -0
- package/lib/compiler/internal/runBuild.js +143 -44
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.d.ts +5 -1
- package/lib/compiler/internal/startResidentTransform.js +3 -1
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +12 -2
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/parser.d.ts +19 -0
- package/lib/flags/parser.js +78 -36
- package/lib/flags/parser.js.map +1 -1
- package/lib/flags/schema.d.ts +49 -11
- package/lib/flags/schema.js +94 -22
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +5 -0
- package/lib/internal/assertSafeExplicitCacheDirectory.js +26 -0
- package/lib/internal/assertSafeExplicitCacheDirectory.js.map +1 -0
- package/lib/launcher/internal/runTtsc.js +107 -69
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +28 -41
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +15 -0
- package/lib/launcher/internal/runtimeHooks.js +18 -1
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/launcher/internal/watchTopology.d.ts +43 -0
- package/lib/launcher/internal/watchTopology.js +360 -0
- package/lib/launcher/internal/watchTopology.js.map +1 -0
- package/lib/plugin/internal/buildSourcePlugin.d.ts +14 -3
- package/lib/plugin/internal/buildSourcePlugin.js +247 -61
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.js +63 -2
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscCompilerTransformation.d.ts +16 -0
- package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +2 -0
- package/package.json +8 -8
- package/shim/ast/shim.go +48 -3
- package/shim/astnav/go.mod +14 -0
- package/shim/astnav/go.sum +20 -0
- package/shim/astnav/shim.go +15 -0
- package/shim/checker/shim.go +113 -0
- package/shim/core/shim.go +47 -0
- package/shim/diagnosticwriter/lint.go +50 -0
- package/src/TtscCompiler.ts +5 -1
- package/src/TtscService.ts +37 -10
- package/src/compiler/internal/project/readConfigJson.ts +209 -0
- package/src/compiler/internal/project/readProjectConfig.ts +62 -158
- package/src/compiler/internal/residentTransformProcess.ts +197 -39
- package/src/compiler/internal/runBuild.ts +169 -47
- package/src/compiler/internal/startResidentTransform.ts +12 -1
- package/src/compiler/internal/transformProjectInMemory.ts +12 -1
- package/src/flags/parser.ts +113 -42
- package/src/flags/schema.ts +113 -31
- package/src/internal/assertSafeExplicitCacheDirectory.ts +29 -0
- package/src/launcher/internal/runTtsc.ts +117 -68
- package/src/launcher/internal/runTtsx.ts +29 -44
- package/src/launcher/internal/runtimeHooks.ts +30 -2
- package/src/launcher/internal/watchTopology.ts +466 -0
- package/src/plugin/internal/buildSourcePlugin.ts +326 -64
- package/src/plugin/internal/loadProjectPlugins.ts +80 -2
- package/src/structures/ITtscCompilerTransformation.ts +17 -0
- package/src/structures/internal/ITtscParsedProjectConfig.ts +2 -0
- package/tools/shim_audit/baseline.json +0 -3
- package/tools/shim_audit/main.go +42 -14
- package/tools/shim_audit/shim_dir_coverage_test.go +25 -0
- package/utility/flags_gen.go +3 -3
- package/utility/host.go +9 -3
package/internal/graph/dump.go
CHANGED
|
@@ -4,7 +4,6 @@ import (
|
|
|
4
4
|
"bufio"
|
|
5
5
|
"encoding/json"
|
|
6
6
|
"io"
|
|
7
|
-
"path/filepath"
|
|
8
7
|
"sort"
|
|
9
8
|
"strings"
|
|
10
9
|
)
|
|
@@ -16,11 +15,12 @@ import (
|
|
|
16
15
|
//
|
|
17
16
|
// - internal node kinds map straight through (NodeTypeAlias is already "type");
|
|
18
17
|
// - one EdgeValueCall splits by Origin into "calls" / "instantiates" /
|
|
19
|
-
// "renders",
|
|
18
|
+
// "renders", EdgeHeritage into "extends" / "implements", and
|
|
19
|
+
// EdgeMemberRelation into "implements" / "overrides";
|
|
20
20
|
// - byte spans become 1-based line/col Evidence ranges;
|
|
21
21
|
// - decorator facts ride on their target node;
|
|
22
|
-
// - file paths
|
|
23
|
-
// deterministic and diffable.
|
|
22
|
+
// - file paths use one portable, injective project coordinate and the output
|
|
23
|
+
// is sorted, so the dump is deterministic and diffable.
|
|
24
24
|
//
|
|
25
25
|
// Structural derivations the schema also defines (file nodes, contains/exports
|
|
26
26
|
// edges) are left to the TypeScript loader, which has the node set in hand and
|
|
@@ -56,22 +56,51 @@ type DumpDecorator struct {
|
|
|
56
56
|
Arguments []DumpDecoratorArgument `json:"arguments"`
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// DumpEnumMember is one member of an enum on the wire: the name a caller writes
|
|
60
|
+
// and the value it carries. `value` is omitted for a member the checker could
|
|
61
|
+
// not fold to a constant — the name still stands, and the name is what a caller
|
|
62
|
+
// asking about the enum came for.
|
|
63
|
+
type DumpEnumMember struct {
|
|
64
|
+
Name string `json:"name"`
|
|
65
|
+
Value string `json:"value,omitempty"`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// DumpObjectMember is one direct object-literal member carried on its variable
|
|
69
|
+
// node. Line and Signature are rendered from the same Program-owned source text
|
|
70
|
+
// as the node evidence, so the outline cannot race a later disk write.
|
|
71
|
+
type DumpObjectMember struct {
|
|
72
|
+
Name string `json:"name"`
|
|
73
|
+
Kind string `json:"kind"`
|
|
74
|
+
Line int `json:"line,omitempty"`
|
|
75
|
+
Signature string `json:"signature,omitempty"`
|
|
76
|
+
}
|
|
77
|
+
|
|
59
78
|
// DumpNode is the wire shape of a graph node. Lowercase json keys are the
|
|
60
79
|
// contract; the Go field names are not.
|
|
61
80
|
type DumpNode struct {
|
|
62
|
-
ID
|
|
63
|
-
Kind
|
|
64
|
-
Name
|
|
65
|
-
QualifiedName
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
ID string `json:"id"`
|
|
82
|
+
Kind string `json:"kind"`
|
|
83
|
+
Name string `json:"name"`
|
|
84
|
+
QualifiedName string `json:"qualifiedName,omitempty"`
|
|
85
|
+
// Signature is the declaration head, cut where the compiler says the body
|
|
86
|
+
// opens. A consumer that reconstructed it by scanning physical lines both
|
|
87
|
+
// leaked implementation text when a declaration shared its line with its body
|
|
88
|
+
// and stopped early when the head itself contained a brace — a type-literal
|
|
89
|
+
// parameter, an object return type, a destructured parameter. Neither is a
|
|
90
|
+
// guess the consumer can win, so the producer renders it here.
|
|
91
|
+
Signature string `json:"signature,omitempty"`
|
|
92
|
+
File string `json:"file"`
|
|
93
|
+
External bool `json:"external"`
|
|
94
|
+
Ignored bool `json:"ignored,omitempty"`
|
|
95
|
+
Exported bool `json:"exported,omitempty"`
|
|
96
|
+
Closure bool `json:"closure,omitempty"`
|
|
97
|
+
Modifiers []string `json:"modifiers,omitempty"`
|
|
98
|
+
Literals []string `json:"literals,omitempty"`
|
|
99
|
+
EnumMembers []DumpEnumMember `json:"enumMembers,omitempty"`
|
|
100
|
+
ObjectMembers []DumpObjectMember `json:"objectMembers,omitempty"`
|
|
101
|
+
Evidence *DumpEvidence `json:"evidence,omitempty"`
|
|
102
|
+
Implementation *DumpEvidence `json:"implementation,omitempty"`
|
|
103
|
+
Decorators []DumpDecorator `json:"decorators,omitempty"`
|
|
75
104
|
}
|
|
76
105
|
|
|
77
106
|
// DumpEdge is the wire shape of a graph edge. Lowercase json keys are the
|
|
@@ -117,12 +146,13 @@ type DumpOrigin struct {
|
|
|
117
146
|
}
|
|
118
147
|
|
|
119
148
|
// NewDump projects a built graph onto the export shape. project is the absolute
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
|
|
149
|
+
// root of the portable path coordinate; ignored is the git-ignored source set
|
|
150
|
+
// (nil for a non-git project); sources maps a source file's physical path to its
|
|
151
|
+
// text so byte spans become line/col evidence (nil omits evidence); origin is
|
|
152
|
+
// the snapshot evidence that proves where the facts came from. It returns an
|
|
153
|
+
// error before serialization when a path is on another filesystem root or two
|
|
154
|
+
// physical sources would collide at one wire identity.
|
|
155
|
+
func NewDump(g *Graph, project, tsconfig string, ignored map[string]bool, sources map[string]string, origin DumpOrigin) (Dump, error) {
|
|
126
156
|
ctx := newDumpContext(project, sources)
|
|
127
157
|
|
|
128
158
|
// Decorators ride on their target node; group by the internal node id before
|
|
@@ -148,16 +178,22 @@ func NewDump(g *Graph, project, tsconfig string, ignored map[string]bool, source
|
|
|
148
178
|
name, qualified = ctx.rel(name), ""
|
|
149
179
|
}
|
|
150
180
|
nodes = append(nodes, DumpNode{
|
|
151
|
-
ID:
|
|
152
|
-
Kind:
|
|
153
|
-
Name:
|
|
154
|
-
QualifiedName:
|
|
155
|
-
File:
|
|
156
|
-
External:
|
|
157
|
-
Ignored:
|
|
158
|
-
Exported:
|
|
159
|
-
Closure:
|
|
160
|
-
Modifiers:
|
|
181
|
+
ID: ctx.relID(n.ID),
|
|
182
|
+
Kind: string(n.Kind),
|
|
183
|
+
Name: name,
|
|
184
|
+
QualifiedName: qualified,
|
|
185
|
+
File: ctx.rel(n.File),
|
|
186
|
+
External: n.External,
|
|
187
|
+
Ignored: ignored[n.File],
|
|
188
|
+
Exported: n.Exported,
|
|
189
|
+
Closure: n.Closure,
|
|
190
|
+
Modifiers: n.Modifiers,
|
|
191
|
+
// Uncapped, like every other fact here: the dump is the whole graph and
|
|
192
|
+
// the MCP layer is what applies a response cap and marks it.
|
|
193
|
+
Literals: n.Literals,
|
|
194
|
+
EnumMembers: dumpEnumMembers(n.EnumMembers),
|
|
195
|
+
ObjectMembers: ctx.objectMembers(n),
|
|
196
|
+
Signature: ctx.declarationSignature(n),
|
|
161
197
|
Evidence: withoutFile(ctx.evidence(n.File, n.Pos, n.End)),
|
|
162
198
|
Implementation: ctx.evidence(n.ImplementationFile, n.ImplementationPos, n.ImplementationEnd),
|
|
163
199
|
Decorators: decByNode[n.ID],
|
|
@@ -186,7 +222,7 @@ func NewDump(g *Graph, project, tsconfig string, ignored map[string]bool, source
|
|
|
186
222
|
|
|
187
223
|
// The schema version describes this code, not the caller's belief about it,
|
|
188
224
|
// so stamp it here rather than trusting what came in.
|
|
189
|
-
provenance := origin.Provenance
|
|
225
|
+
provenance := ctx.provenance(origin.Provenance)
|
|
190
226
|
provenance.SchemaVersion = DumpSchemaVersion
|
|
191
227
|
|
|
192
228
|
// A nil slice encodes as JSON null, and null is not an empty list: a reader
|
|
@@ -205,25 +241,74 @@ func NewDump(g *Graph, project, tsconfig string, ignored map[string]bool, source
|
|
|
205
241
|
provenance.Universe.Roots = []RootFile{}
|
|
206
242
|
}
|
|
207
243
|
|
|
208
|
-
diagnostics := origin.Diagnostics
|
|
244
|
+
diagnostics := ctx.diagnostics(origin.Diagnostics)
|
|
209
245
|
if diagnostics == nil {
|
|
210
246
|
diagnostics = []Diagnostic{}
|
|
211
247
|
}
|
|
212
248
|
|
|
213
|
-
|
|
249
|
+
dump := Dump{
|
|
214
250
|
Project: project,
|
|
215
|
-
Tsconfig: tsconfig,
|
|
251
|
+
Tsconfig: ctx.rel(tsconfig),
|
|
216
252
|
Provenance: provenance,
|
|
217
253
|
Diagnostics: diagnostics,
|
|
218
254
|
Nodes: nodes,
|
|
219
255
|
Edges: edges,
|
|
220
256
|
}
|
|
257
|
+
if err := ctx.pathError(); err != nil {
|
|
258
|
+
return Dump{}, err
|
|
259
|
+
}
|
|
260
|
+
return dump, nil
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// dumpEnumMembers projects an enum's members onto the wire shape, nil for a
|
|
264
|
+
// node that declares none so the key stays off every other kind.
|
|
265
|
+
func dumpEnumMembers(members []EnumMember) []DumpEnumMember {
|
|
266
|
+
if len(members) == 0 {
|
|
267
|
+
return nil
|
|
268
|
+
}
|
|
269
|
+
out := make([]DumpEnumMember, 0, len(members))
|
|
270
|
+
for _, member := range members {
|
|
271
|
+
out = append(out, DumpEnumMember{Name: member.Name, Value: member.Value})
|
|
272
|
+
}
|
|
273
|
+
return out
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// objectMembers projects AST-owned object member identity and snapshot-owned
|
|
277
|
+
// display text. A missing source omits only line/signature; identity still came
|
|
278
|
+
// from the AST and therefore remains sound.
|
|
279
|
+
func (c *dumpContext) objectMembers(node *Node) []DumpObjectMember {
|
|
280
|
+
if len(node.ObjectMembers) == 0 {
|
|
281
|
+
return nil
|
|
282
|
+
}
|
|
283
|
+
out := make([]DumpObjectMember, 0, len(node.ObjectMembers))
|
|
284
|
+
for _, member := range node.ObjectMembers {
|
|
285
|
+
dumped := DumpObjectMember{
|
|
286
|
+
Name: member.Name,
|
|
287
|
+
Kind: objectMemberWireKind(member.Kind),
|
|
288
|
+
}
|
|
289
|
+
if evidence := c.evidence(node.File, member.Pos, member.End); evidence != nil {
|
|
290
|
+
dumped.Line = evidence.StartLine
|
|
291
|
+
}
|
|
292
|
+
dumped.Signature = c.objectMemberSignature(node.File, member)
|
|
293
|
+
out = append(out, dumped)
|
|
294
|
+
}
|
|
295
|
+
return out
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
func objectMemberWireKind(kind NodeKind) string {
|
|
299
|
+
if kind == NodeMethod {
|
|
300
|
+
return "method"
|
|
301
|
+
}
|
|
302
|
+
return "property"
|
|
221
303
|
}
|
|
222
304
|
|
|
223
305
|
// MarshalDump serializes a built graph to the export JSON, indented when pretty.
|
|
224
306
|
// See NewDump for the parameters.
|
|
225
307
|
func MarshalDump(g *Graph, project, tsconfig string, ignored map[string]bool, sources map[string]string, origin DumpOrigin, pretty bool) ([]byte, error) {
|
|
226
|
-
d := NewDump(g, project, tsconfig, ignored, sources, origin)
|
|
308
|
+
d, err := NewDump(g, project, tsconfig, ignored, sources, origin)
|
|
309
|
+
if err != nil {
|
|
310
|
+
return nil, err
|
|
311
|
+
}
|
|
227
312
|
if pretty {
|
|
228
313
|
return json.MarshalIndent(d, "", " ")
|
|
229
314
|
}
|
|
@@ -239,12 +324,16 @@ func MarshalDump(g *Graph, project, tsconfig string, ignored map[string]bool, so
|
|
|
239
324
|
// copy of it held live beside the first — half a gigabyte of peak heap that
|
|
240
325
|
// bought nothing, because the bytes were already exactly what stdout wanted.
|
|
241
326
|
func EncodeDump(w io.Writer, g *Graph, project, tsconfig string, ignored map[string]bool, sources map[string]string, origin DumpOrigin, pretty bool) error {
|
|
327
|
+
dump, err := NewDump(g, project, tsconfig, ignored, sources, origin)
|
|
328
|
+
if err != nil {
|
|
329
|
+
return err
|
|
330
|
+
}
|
|
242
331
|
buffered := bufio.NewWriterSize(w, 1<<20)
|
|
243
332
|
encoder := json.NewEncoder(buffered)
|
|
244
333
|
if pretty {
|
|
245
334
|
encoder.SetIndent("", " ")
|
|
246
335
|
}
|
|
247
|
-
if err := encoder.Encode(
|
|
336
|
+
if err := encoder.Encode(dump); err != nil {
|
|
248
337
|
return err
|
|
249
338
|
}
|
|
250
339
|
return buffered.Flush()
|
|
@@ -282,6 +371,8 @@ func wireEdgeKind(kind EdgeKind, origin string) string {
|
|
|
282
371
|
return "extends"
|
|
283
372
|
}
|
|
284
373
|
return "implements"
|
|
374
|
+
case EdgeMemberRelation:
|
|
375
|
+
return origin
|
|
285
376
|
default:
|
|
286
377
|
return string(kind)
|
|
287
378
|
}
|
|
@@ -307,41 +398,30 @@ func nodeNames(n *Node) (simple, qualified string) {
|
|
|
307
398
|
return n.Simple, n.Name
|
|
308
399
|
}
|
|
309
400
|
|
|
310
|
-
// dumpContext
|
|
311
|
-
//
|
|
312
|
-
//
|
|
401
|
+
// dumpContext maps paths and turns byte spans into line/col evidence, caching a
|
|
402
|
+
// per-file line index so a large file's many edges cost O(log n) each instead
|
|
403
|
+
// of a re-scan.
|
|
313
404
|
type dumpContext struct {
|
|
314
|
-
|
|
405
|
+
paths *dumpPathMapper
|
|
315
406
|
sources map[string]string
|
|
316
407
|
lines map[string]lineStarts
|
|
317
408
|
}
|
|
318
409
|
|
|
319
410
|
func newDumpContext(project string, sources map[string]string) *dumpContext {
|
|
320
411
|
return &dumpContext{
|
|
321
|
-
|
|
412
|
+
paths: newDumpPathMapper(project),
|
|
322
413
|
sources: sources,
|
|
323
414
|
lines: map[string]lineStarts{},
|
|
324
415
|
}
|
|
325
416
|
}
|
|
326
417
|
|
|
327
|
-
// rel
|
|
328
|
-
// node_modules-relative tail so a dependency leaf stays readable.
|
|
418
|
+
// rel delegates every identity-bearing path to the dump's one cached mapper.
|
|
329
419
|
func (c *dumpContext) rel(file string) string {
|
|
330
|
-
|
|
331
|
-
if c.project != "" {
|
|
332
|
-
if f == c.project {
|
|
333
|
-
return ""
|
|
334
|
-
}
|
|
335
|
-
if strings.HasPrefix(f, c.project+"/") {
|
|
336
|
-
return f[len(c.project)+1:]
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
if i := strings.LastIndex(f, "/node_modules/"); i >= 0 {
|
|
340
|
-
return f[i+1:]
|
|
341
|
-
}
|
|
342
|
-
return f
|
|
420
|
+
return c.paths.mapPath(file)
|
|
343
421
|
}
|
|
344
422
|
|
|
423
|
+
func (c *dumpContext) pathError() error { return c.paths.err() }
|
|
424
|
+
|
|
345
425
|
// relID relativizes the path portion of a node id ("path#qualifiedName:kind").
|
|
346
426
|
// An id with no path (a future virtual node) is returned unchanged.
|
|
347
427
|
//
|
|
@@ -349,17 +429,15 @@ func (c *dumpContext) rel(file string) string {
|
|
|
349
429
|
// both relativize. Every other node is named by a symbol, which passes through
|
|
350
430
|
// untouched.
|
|
351
431
|
func (c *dumpContext) relID(id string) string {
|
|
352
|
-
|
|
353
|
-
if
|
|
432
|
+
parts, ok := parseNodeID(id)
|
|
433
|
+
if !ok {
|
|
354
434
|
return id
|
|
355
435
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
name := strings.TrimSuffix(tail, suffix)
|
|
360
|
-
return head + "#" + c.rel(name) + suffix
|
|
436
|
+
name := parts.name
|
|
437
|
+
if parts.kind == NodeModule {
|
|
438
|
+
name = c.rel(name)
|
|
361
439
|
}
|
|
362
|
-
return
|
|
440
|
+
return nodeID(c.rel(parts.path), name, parts.kind)
|
|
363
441
|
}
|
|
364
442
|
|
|
365
443
|
// evidence builds the line/col span for a byte range in file, or nil when the
|
|
@@ -384,7 +462,7 @@ func (c *dumpContext) evidence(file string, pos, end int) *DumpEvidence {
|
|
|
384
462
|
// leading whitespace and doc comments before the token. Advance to the first
|
|
385
463
|
// code character so the line/column point at the declaration, not its banner
|
|
386
464
|
// or its indentation.
|
|
387
|
-
pos =
|
|
465
|
+
pos = FirstCodeOffset(text, pos)
|
|
388
466
|
sl, sc := ls.at(pos)
|
|
389
467
|
ev := &DumpEvidence{File: c.rel(file), StartLine: sl, StartCol: sc}
|
|
390
468
|
if end > pos && end <= len(text) {
|
|
@@ -393,36 +471,178 @@ func (c *dumpContext) evidence(file string, pos, end int) *DumpEvidence {
|
|
|
393
471
|
return ev
|
|
394
472
|
}
|
|
395
473
|
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
func
|
|
401
|
-
|
|
402
|
-
|
|
474
|
+
// declarationSignature renders a declaration's head from the same
|
|
475
|
+
// Program-owned source text as its evidence, so the outline cannot race a later
|
|
476
|
+
// disk write. Empty when the producer could not bound the head, which leaves the
|
|
477
|
+
// consumer on its existing line scan rather than handing it a wrong cut.
|
|
478
|
+
func (c *dumpContext) declarationSignature(n *Node) string {
|
|
479
|
+
if n == nil || n.SignatureEnd <= n.Pos || c.sources == nil {
|
|
480
|
+
return ""
|
|
481
|
+
}
|
|
482
|
+
text, ok := c.sources[n.File]
|
|
483
|
+
if !ok {
|
|
484
|
+
return ""
|
|
485
|
+
}
|
|
486
|
+
pos := FirstCodeOffset(text, n.Pos)
|
|
487
|
+
end := min(n.SignatureEnd, len(text))
|
|
488
|
+
if pos >= end {
|
|
489
|
+
return ""
|
|
490
|
+
}
|
|
491
|
+
return strings.TrimSpace(text[pos:end])
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// objectMemberSignature reproduces the compact outline details has historically
|
|
495
|
+
// returned, but slices it from Program-owned text while the dump is built
|
|
496
|
+
// instead of reopening the live file later.
|
|
497
|
+
func (c *dumpContext) objectMemberSignature(file string, member ObjectMember) string {
|
|
498
|
+
pos, end := member.Pos, member.SignatureEnd
|
|
499
|
+
if pos < 0 || end <= pos || c.sources == nil {
|
|
500
|
+
return ""
|
|
501
|
+
}
|
|
502
|
+
text, ok := c.sources[file]
|
|
503
|
+
if !ok || pos > len(text) {
|
|
504
|
+
return ""
|
|
505
|
+
}
|
|
506
|
+
pos = FirstCodeOffset(text, pos)
|
|
507
|
+
if end > len(text) {
|
|
508
|
+
end = len(text)
|
|
509
|
+
}
|
|
510
|
+
if member.SignatureTokenLen > 0 {
|
|
511
|
+
end = FirstCodeOffset(text, end)
|
|
512
|
+
end = min(end+member.SignatureTokenLen, len(text))
|
|
513
|
+
}
|
|
514
|
+
if pos >= end {
|
|
515
|
+
return ""
|
|
516
|
+
}
|
|
517
|
+
if pos >= end {
|
|
518
|
+
return ""
|
|
519
|
+
}
|
|
520
|
+
signature := strings.TrimSuffix(compactObjectMemberSignature(text[pos:end]), ",")
|
|
521
|
+
const maxObjectMemberSignatureRunes = 160
|
|
522
|
+
runes := []rune(signature)
|
|
523
|
+
if len(runes) > maxObjectMemberSignatureRunes {
|
|
524
|
+
signature = string(runes[:maxObjectMemberSignatureRunes-3]) + "..."
|
|
525
|
+
}
|
|
526
|
+
return signature
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// compactObjectMemberSignature collapses trivia outside lexical values while
|
|
530
|
+
// leaving quoted strings, template literals, regular expressions, and comments
|
|
531
|
+
// byte-for-byte intact. A display outline may be compact, but changing literal
|
|
532
|
+
// whitespace would change the declaration it claims to quote.
|
|
533
|
+
func compactObjectMemberSignature(text string) string {
|
|
534
|
+
var out strings.Builder
|
|
535
|
+
pendingSpace := false
|
|
536
|
+
for i := 0; i < len(text); {
|
|
537
|
+
if width := sourceWhitespaceWidth(text, i); width > 0 {
|
|
538
|
+
pendingSpace = out.Len() > 0
|
|
539
|
+
i += width
|
|
540
|
+
continue
|
|
541
|
+
}
|
|
542
|
+
if pendingSpace {
|
|
543
|
+
out.WriteByte(' ')
|
|
544
|
+
pendingSpace = false
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
end := i + 1
|
|
548
|
+
switch text[i] {
|
|
549
|
+
case '\'', '"':
|
|
550
|
+
end = quotedSourceEnd(text, i, text[i])
|
|
551
|
+
case '`':
|
|
552
|
+
end = templateSourceEnd(text, i)
|
|
553
|
+
case '/':
|
|
554
|
+
switch {
|
|
555
|
+
case i+1 < len(text) && text[i+1] == '/':
|
|
556
|
+
end = LineCommentEnd(text, i)
|
|
557
|
+
case i+1 < len(text) && text[i+1] == '*':
|
|
558
|
+
end = blockCommentEnd(text, i)
|
|
559
|
+
default:
|
|
560
|
+
if candidate := regularExpressionEnd(text, i); candidate > i {
|
|
561
|
+
end = candidate
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
out.WriteString(text[i:end])
|
|
566
|
+
i = end
|
|
567
|
+
}
|
|
568
|
+
return strings.TrimSpace(out.String())
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
func quotedSourceEnd(text string, start int, quote byte) int {
|
|
572
|
+
escaped := false
|
|
573
|
+
for i := start + 1; i < len(text); i++ {
|
|
574
|
+
if escaped {
|
|
575
|
+
escaped = false
|
|
576
|
+
continue
|
|
577
|
+
}
|
|
578
|
+
if text[i] == '\\' {
|
|
579
|
+
escaped = true
|
|
580
|
+
continue
|
|
581
|
+
}
|
|
582
|
+
if text[i] == quote {
|
|
583
|
+
return i + 1
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return len(text)
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// templateSourceEnd returns the last unescaped backtick in the member span.
|
|
590
|
+
// This deliberately treats substitutions and nested templates as one protected
|
|
591
|
+
// lexical region: preserving a little extra spacing is safer than compacting
|
|
592
|
+
// whitespace that belongs to either template's value.
|
|
593
|
+
func templateSourceEnd(text string, start int) int {
|
|
594
|
+
end := len(text)
|
|
595
|
+
for i := len(text) - 1; i > start; i-- {
|
|
596
|
+
if text[i] == '`' && !sourceByteEscaped(text, i) {
|
|
597
|
+
return i + 1
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return end
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
func sourceByteEscaped(text string, pos int) bool {
|
|
604
|
+
slashes := 0
|
|
605
|
+
for i := pos - 1; i >= 0 && text[i] == '\\'; i-- {
|
|
606
|
+
slashes++
|
|
607
|
+
}
|
|
608
|
+
return slashes%2 == 1
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
func blockCommentEnd(text string, start int) int {
|
|
612
|
+
if end := strings.Index(text[start+2:], "*/"); end >= 0 {
|
|
613
|
+
return start + 2 + end + 2
|
|
614
|
+
}
|
|
615
|
+
return len(text)
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// regularExpressionEnd conservatively protects a slash-delimited region when
|
|
619
|
+
// one closes on the same source line. Division can look the same without parser
|
|
620
|
+
// context; preserving its spaces is harmless, while compacting a real regular
|
|
621
|
+
// expression would change its pattern.
|
|
622
|
+
func regularExpressionEnd(text string, start int) int {
|
|
623
|
+
escaped := false
|
|
624
|
+
inClass := false
|
|
625
|
+
for i := start + 1; i < len(text); i++ {
|
|
403
626
|
switch {
|
|
404
|
-
case text
|
|
627
|
+
case lineTerminatorWidth(text, i) > 0:
|
|
628
|
+
return start
|
|
629
|
+
case escaped:
|
|
630
|
+
escaped = false
|
|
631
|
+
case text[i] == '\\':
|
|
632
|
+
escaped = true
|
|
633
|
+
case text[i] == '[':
|
|
634
|
+
inClass = true
|
|
635
|
+
case text[i] == ']':
|
|
636
|
+
inClass = false
|
|
637
|
+
case text[i] == '/' && !inClass:
|
|
405
638
|
i++
|
|
406
|
-
|
|
407
|
-
i += 2
|
|
408
|
-
for i < len(text) && text[i] != '\n' {
|
|
639
|
+
for i < len(text) && ((text[i] >= 'a' && text[i] <= 'z') || (text[i] >= 'A' && text[i] <= 'Z')) {
|
|
409
640
|
i++
|
|
410
641
|
}
|
|
411
|
-
case text[i] == '/' && i+1 < len(text) && text[i+1] == '*':
|
|
412
|
-
i += 2
|
|
413
|
-
for i+1 < len(text) && !(text[i] == '*' && text[i+1] == '/') {
|
|
414
|
-
i++
|
|
415
|
-
}
|
|
416
|
-
if i+1 < len(text) {
|
|
417
|
-
i += 2
|
|
418
|
-
} else {
|
|
419
|
-
i = len(text)
|
|
420
|
-
}
|
|
421
|
-
default:
|
|
422
642
|
return i
|
|
423
643
|
}
|
|
424
644
|
}
|
|
425
|
-
return
|
|
645
|
+
return start
|
|
426
646
|
}
|
|
427
647
|
|
|
428
648
|
// edgeEvidence is the evidence range for an edge's source expression.
|
|
@@ -445,28 +665,12 @@ func withoutFile(ev *DumpEvidence) *DumpEvidence {
|
|
|
445
665
|
return ev
|
|
446
666
|
}
|
|
447
667
|
|
|
448
|
-
// nodeFile recovers the source file path embedded in a node id
|
|
449
|
-
// ("path#qualifiedName:kind"); "" for an id without a path.
|
|
450
|
-
func nodeFile(id string) string {
|
|
451
|
-
if hash := strings.Index(id, "#"); hash >= 0 {
|
|
452
|
-
return id[:hash]
|
|
453
|
-
}
|
|
454
|
-
return ""
|
|
455
|
-
}
|
|
456
|
-
|
|
457
668
|
// lineStarts holds the byte offset of each line's start, so an offset maps to a
|
|
458
669
|
// 1-based line/column by binary search.
|
|
459
670
|
type lineStarts []int
|
|
460
671
|
|
|
461
672
|
func newLineStarts(text string) lineStarts {
|
|
462
|
-
|
|
463
|
-
starts[0] = 0
|
|
464
|
-
for i := 0; i < len(text); i++ {
|
|
465
|
-
if text[i] == '\n' {
|
|
466
|
-
starts = append(starts, i+1)
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
return starts
|
|
673
|
+
return lineStarts(ECMALineStarts(text))
|
|
470
674
|
}
|
|
471
675
|
|
|
472
676
|
// at returns the 1-based line and column of a byte offset.
|