ttsc 0.26.2 → 0.28.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 +9 -0
- package/cmd/graphbench/main.go +26 -16
- package/cmd/graphdump/main.go +37 -12
- package/cmd/graphdump/run_declares_the_capability_its_output_carries_test.go +137 -0
- package/cmd/graphdump/run_reports_an_unknown_flag_without_exiting_the_process_test.go +56 -0
- package/cmd/graphdump/run_reports_an_unloadable_project_without_printing_a_dump_test.go +46 -0
- package/cmd/ttsc/api_transform.go +28 -3
- package/cmd/ttscgraph/auxiliary_identity_state_tracks_retargets_not_source_contents_test.go +85 -0
- package/cmd/ttscgraph/disk_realpath_other.go +16 -0
- package/cmd/ttscgraph/disk_realpath_windows.go +56 -0
- package/cmd/ttscgraph/dump.go +43 -11
- package/cmd/ttscgraph/run_dump_claims_artifacts_only_when_it_asked_test.go +135 -0
- package/cmd/ttscgraph/run_dump_declares_the_capability_its_output_carries_test.go +96 -0
- package/cmd/ttscgraph/serve.go +357 -60
- package/cmd/ttscgraph/serve_adopts_a_republished_artifact_set_test.go +348 -0
- package/cmd/ttscgraph/serve_projects_an_artifact_no_source_owns_test.go +90 -0
- package/cmd/ttscgraph/serve_shards.go +31 -9
- package/driver/config_discovery.go +121 -0
- package/driver/emit_plugin.go +77 -11
- package/driver/graph.go +64 -11
- package/driver/input_observation.go +273 -0
- package/driver/input_observation_test.go +149 -0
- package/driver/plugins.go +506 -15
- package/driver/program.go +54 -1
- package/driver/resolution_candidates.go +13 -5
- package/driver/rewrite.go +45 -2
- package/driver/transform_dependencies.go +283 -0
- package/driver/transform_dependencies_declares_every_file_without_a_contributor_test.go +28 -0
- package/driver/transform_dependencies_drops_a_self_reported_dependency_test.go +21 -0
- package/driver/transform_dependencies_keys_a_reported_path_like_the_envelope_test.go +46 -0
- package/driver/transform_dependencies_requires_every_contributor_to_declare_test.go +38 -0
- package/driver/transform_dependencies_stays_silent_for_an_undeclared_contributor_test.go +23 -0
- package/driver/transform_dependencies_withdraws_completeness_after_an_unusable_dependency_test.go +37 -0
- package/internal/graph/artifacts.go +230 -0
- package/internal/graph/artifacts_index_what_a_plugin_published_test.go +154 -0
- package/internal/graph/artifacts_survive_a_partial_rebuild_test.go +89 -0
- package/internal/graph/build.go +35 -0
- package/internal/graph/build_releases_the_scratch_it_no_longer_reads_test.go +118 -0
- package/internal/graph/doc_refs_are_the_use_the_checker_already_counts_test.go +93 -0
- package/internal/graph/doc_refs_resolve_documentation_links_test.go +335 -0
- package/internal/graph/doc_tags.go +416 -0
- package/internal/graph/doc_tags_are_claimed_before_they_are_read_test.go +131 -0
- package/internal/graph/doc_tags_capture_every_unrecognized_tag_test.go +165 -0
- package/internal/graph/doc_tags_read_only_what_the_parser_attached_test.go +97 -0
- package/internal/graph/doc_tags_stop_at_the_external_boundary_test.go +59 -0
- package/internal/graph/doc_tags_survive_line_endings_and_repeated_blocks_test.go +146 -0
- package/internal/graph/dump.go +60 -13
- package/internal/graph/dump_checkout_paths_are_stable_test.go +2 -2
- package/internal/graph/dump_paths.go +2 -2
- package/internal/graph/dump_paths_are_portable_and_injective_test.go +1 -1
- package/internal/graph/edge_kinds_names_every_declared_kind_test.go +49 -0
- package/internal/graph/edges.go +1 -0
- package/internal/graph/graph.go +79 -4
- package/internal/graph/provenance.go +29 -2
- package/lib/TtscCompiler.d.ts +12 -11
- package/lib/TtscCompiler.js +44 -29
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/compiler/internal/captureProcessOutput.js +2 -2
- package/lib/compiler/internal/captureProcessOutput.js.map +1 -1
- package/lib/compiler/internal/compileProjectInMemory.js +4 -3
- package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/project/createNativeProjectContextArgs.d.ts +9 -0
- package/lib/compiler/internal/project/createNativeProjectContextArgs.js +16 -4
- package/lib/compiler/internal/project/createNativeProjectContextArgs.js.map +1 -1
- package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -0
- package/lib/compiler/internal/resolveEmittedJavaScript.js +3 -0
- package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +1 -1
- package/lib/compiler/internal/runBuild.js +80 -34
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.js +2 -2
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +28 -0
- package/lib/compiler/internal/sharedHostHelpers.js +39 -0
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.js +12 -4
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.d.ts +3 -0
- package/lib/compiler/internal/transformProjectInMemory.js +239 -35
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/schema.js +12 -5
- package/lib/flags/schema.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/internal/createCanonicalTempDirectory.d.ts +17 -0
- package/lib/internal/createCanonicalTempDirectory.js +42 -0
- package/lib/internal/createCanonicalTempDirectory.js.map +1 -0
- package/lib/internal/projectInputPathIdentity.d.ts +3 -0
- package/lib/internal/projectInputPathIdentity.js +11 -5
- package/lib/internal/projectInputPathIdentity.js.map +1 -1
- package/lib/internal/resolveNodeBinary.d.ts +14 -0
- package/lib/internal/resolveNodeBinary.js +150 -0
- package/lib/internal/resolveNodeBinary.js.map +1 -0
- package/lib/internal/resolveSafeCacheCleanupTargets.d.ts +15 -0
- package/lib/internal/resolveSafeCacheCleanupTargets.js +61 -0
- package/lib/internal/resolveSafeCacheCleanupTargets.js.map +1 -0
- package/lib/launcher/internal/prepareExecution.d.ts +3 -0
- package/lib/launcher/internal/prepareExecution.js +42 -14
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +25 -13
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +18 -3
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +2 -0
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +47 -3
- package/lib/launcher/internal/runtimeHooks.js +746 -64
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/plugin/internal/buildSourcePlugin.d.ts +62 -8
- package/lib/plugin/internal/buildSourcePlugin.js +754 -72
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/capabilityResolutionCache.d.ts +76 -0
- package/lib/plugin/internal/capabilityResolutionCache.js +224 -0
- package/lib/plugin/internal/capabilityResolutionCache.js.map +1 -0
- package/lib/plugin/internal/loadProjectPlugins.d.ts +17 -5
- package/lib/plugin/internal/loadProjectPlugins.js +1339 -77
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/plugin/resolveCapabilityPlugins.d.ts +55 -0
- package/lib/plugin/resolveCapabilityPlugins.js +118 -0
- package/lib/plugin/resolveCapabilityPlugins.js.map +1 -0
- package/lib/register.d.ts +1 -0
- package/lib/register.js +46 -0
- package/lib/register.js.map +1 -0
- package/lib/structures/ITtscCompilerContext.d.ts +6 -3
- package/lib/structures/ITtscCompilerTransformation.d.ts +38 -0
- package/lib/structures/ITtscPlugin.d.ts +33 -0
- package/package.json +16 -9
- package/shim/ast/parent.go +16 -3
- package/shim/checker/enums_gen.go +11 -0
- package/shim/checker/go.mod +9 -0
- package/shim/checker/go.sum +22 -0
- package/shim/checker/shim.go +78 -0
- package/shim/compiler/emithook.go +10 -0
- package/shim/compiler/incremental.go +45 -8
- package/src/TtscCompiler.ts +55 -33
- package/src/compiler/internal/captureProcessOutput.ts +3 -2
- package/src/compiler/internal/compileProjectInMemory.ts +4 -3
- package/src/compiler/internal/project/createNativeProjectContextArgs.ts +19 -4
- package/src/compiler/internal/resolveEmittedJavaScript.ts +6 -0
- package/src/compiler/internal/runBuild.ts +102 -39
- package/src/compiler/internal/runSingleFileEmit.ts +2 -2
- package/src/compiler/internal/sharedHostHelpers.ts +44 -0
- package/src/compiler/internal/startResidentTransform.ts +12 -3
- package/src/compiler/internal/transformProjectInMemory.ts +410 -39
- package/src/flags/schema.ts +12 -5
- package/src/index.ts +1 -0
- package/src/internal/createCanonicalTempDirectory.ts +53 -0
- package/src/internal/projectInputPathIdentity.ts +17 -4
- package/src/internal/resolveNodeBinary.ts +173 -0
- package/src/internal/resolveSafeCacheCleanupTargets.ts +85 -0
- package/src/launcher/internal/prepareExecution.ts +50 -14
- package/src/launcher/internal/runTtsc.ts +24 -13
- package/src/launcher/internal/runTtscserver.ts +18 -3
- package/src/launcher/internal/runTtsx.ts +2 -0
- package/src/launcher/internal/runtimeHooks.ts +934 -68
- package/src/plugin/internal/buildSourcePlugin.ts +984 -79
- package/src/plugin/internal/capabilityResolutionCache.ts +305 -0
- package/src/plugin/internal/loadProjectPlugins.ts +1718 -104
- package/src/plugin/resolveCapabilityPlugins.ts +171 -0
- package/src/register.ts +47 -0
- package/src/structures/ITtscCompilerContext.ts +6 -3
- package/src/structures/ITtscCompilerTransformation.ts +46 -0
- package/src/structures/ITtscPlugin.ts +36 -0
- package/tools/shim_audit/baseline.json +0 -2
- package/utility/host.go +18 -3
- package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +0 -5
- package/lib/internal/assertSafeExplicitCacheDirectory.js +0 -26
- package/lib/internal/assertSafeExplicitCacheDirectory.js.map +0 -1
- package/src/internal/assertSafeExplicitCacheDirectory.ts +0 -29
|
@@ -23,9 +23,10 @@ type ModuleResolutionContext struct {
|
|
|
23
23
|
|
|
24
24
|
// SupersedingModuleCandidates returns, per source-file envelope key, the
|
|
25
25
|
// resolution candidates strictly ahead of each module target the loaded
|
|
26
|
-
// program selected.
|
|
27
|
-
//
|
|
28
|
-
//
|
|
26
|
+
// program selected. When the selected target is reported by physical path, it
|
|
27
|
+
// also retains that candidate's lexical spelling. A missing member or a link
|
|
28
|
+
// retarget can therefore become a freshness input without making the whole
|
|
29
|
+
// resolution search (including lower-priority paths) an invalidation input.
|
|
29
30
|
//
|
|
30
31
|
// Candidate enumeration is host-owned. It mirrors the resident graph session's
|
|
31
32
|
// speculation for relative modules, paths, rootDirs, package imports/exports,
|
|
@@ -75,7 +76,10 @@ func SupersedingModuleCandidates(prog *Program, cwd string) map[string][]string
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
// ModuleResolutionPredecessors returns the paths in the host-owned candidate
|
|
78
|
-
// search that precede resolvedFileName.
|
|
79
|
+
// search that precede resolvedFileName. When the compiler reports the winner by
|
|
80
|
+
// physical realpath, the result also retains that selected candidate's lexical
|
|
81
|
+
// spelling: retargeting its symlink/junction can change resolution without
|
|
82
|
+
// changing the old or new target bytes. An unresolved specifier keeps the full
|
|
79
83
|
// candidate list through ModuleResolutionCandidates; a resolved specifier must
|
|
80
84
|
// not track a lower-priority path because creating it cannot alter resolution.
|
|
81
85
|
func ModuleResolutionPredecessors(
|
|
@@ -101,7 +105,11 @@ func ModuleResolutionPredecessors(
|
|
|
101
105
|
// spellings already agree pays nothing for it.
|
|
102
106
|
for index, candidate := range candidates {
|
|
103
107
|
if sameExistingPath(candidate, resolvedFileName) {
|
|
104
|
-
|
|
108
|
+
end := index
|
|
109
|
+
if !sameCandidatePath(candidate, resolvedFileName, caseSensitive) {
|
|
110
|
+
end++
|
|
111
|
+
}
|
|
112
|
+
return compactStringsInOrder(candidates[:end])
|
|
105
113
|
}
|
|
106
114
|
}
|
|
107
115
|
// Never widen freshness to candidates whose precedence relative to the
|
package/driver/rewrite.go
CHANGED
|
@@ -129,7 +129,7 @@ func (p *Program) EmitAllRaw(writeFile shimcompiler.WriteFile) (*shimcompiler.Em
|
|
|
129
129
|
}
|
|
130
130
|
return DefaultWriteFile(fileName, text)
|
|
131
131
|
}
|
|
132
|
-
result := p.
|
|
132
|
+
result := p.emitProgram(shimcompiler.EmitOptions{
|
|
133
133
|
WriteFile: wf,
|
|
134
134
|
})
|
|
135
135
|
return result, p.convertProgramDiagnostics(result.Diagnostics), nil
|
|
@@ -197,13 +197,56 @@ func (p *Program) emit(rs *RewriteSet, target *ast.SourceFile, writeFile shimcom
|
|
|
197
197
|
return DefaultWriteFile(fileName, patched)
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
result := p.
|
|
200
|
+
result := p.emitProgram(shimcompiler.EmitOptions{
|
|
201
201
|
TargetSourceFile: target,
|
|
202
202
|
WriteFile: wf,
|
|
203
203
|
})
|
|
204
204
|
return result, p.convertProgramDiagnostics(result.Diagnostics), nil
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
// emitProgram runs one whole-program or single-file emit, taking tsgo's
|
|
208
|
+
// incremental lane when the resolved compiler options ask for build
|
|
209
|
+
// information.
|
|
210
|
+
//
|
|
211
|
+
// tsgo's own CLI branches the same way — `performIncrementalCompilation` vs
|
|
212
|
+
// `performCompilation`, on `CompilerOptions.IsIncremental()` — but it branches
|
|
213
|
+
// in `internal/execute`, which a host constructing its Program in-process never
|
|
214
|
+
// enters. ttsc always took the plain branch, so `incremental`, `composite`, and
|
|
215
|
+
// `tsBuildInfoFile` parsed, resolved, and then vanished: a plugin-carrying
|
|
216
|
+
// project emitted its JavaScript and no `.tsbuildinfo` at all (issue #1188).
|
|
217
|
+
// `driver/emit_containment.go` had already exempted `.tsbuildinfo` from the
|
|
218
|
+
// outDir guard for a write that could not happen.
|
|
219
|
+
//
|
|
220
|
+
// A single-file emit stays on the plain lane. Build information describes a
|
|
221
|
+
// whole program, and tsgo's incremental program returns early on a
|
|
222
|
+
// `TargetSourceFile` request without writing any, so routing it there would
|
|
223
|
+
// only add a snapshot computation nothing reads.
|
|
224
|
+
func (p *Program) emitProgram(options shimcompiler.EmitOptions) *shimcompiler.EmitResult {
|
|
225
|
+
ctx := context.Background()
|
|
226
|
+
if options.TargetSourceFile == nil && p.emitsBuildInfo() {
|
|
227
|
+
return shimcompiler.EmitFreshWithBuildInfo(ctx, p.TSProgram, options)
|
|
228
|
+
}
|
|
229
|
+
return p.TSProgram.Emit(ctx, options)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// emitsBuildInfo reports whether this program's resolved options ask tsgo to
|
|
233
|
+
// write build information.
|
|
234
|
+
//
|
|
235
|
+
// The question is delegated to `CompilerOptions.IsIncremental`, the same
|
|
236
|
+
// predicate tsgo's own CLI branches on, rather than restated as
|
|
237
|
+
// `Incremental || Composite` here: a second copy of the rule is a second thing
|
|
238
|
+
// to keep in step with upstream.
|
|
239
|
+
func (p *Program) emitsBuildInfo() bool {
|
|
240
|
+
if p == nil || p.TSProgram == nil {
|
|
241
|
+
return false
|
|
242
|
+
}
|
|
243
|
+
options := p.TSProgram.Options()
|
|
244
|
+
if options == nil {
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
return options.IsIncremental()
|
|
248
|
+
}
|
|
249
|
+
|
|
207
250
|
// DefaultWriteFile is the default disk writer used when EmitAll's caller does not
|
|
208
251
|
// supply a custom WriteFile callback.
|
|
209
252
|
func DefaultWriteFile(fileName, text string) error {
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
package driver
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"sort"
|
|
5
|
+
"sync"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// TransformDependencies is the transform envelope's dependency side channel:
|
|
9
|
+
// the per-file input lists a producer reports and the subset of files whose
|
|
10
|
+
// list is complete.
|
|
11
|
+
//
|
|
12
|
+
// A producer that declares nothing leaves both fields empty, so its envelope is
|
|
13
|
+
// byte-identical to what it was before this existed. The lane with no linked
|
|
14
|
+
// plugin at all is the deliberate exception: nothing there can contribute to a
|
|
15
|
+
// file, so every file is listed.
|
|
16
|
+
type TransformDependencies struct {
|
|
17
|
+
// Complete lists the files whose Dependencies entry is the whole input set
|
|
18
|
+
// beyond the file itself and the universal compiler-option chain.
|
|
19
|
+
Complete []string
|
|
20
|
+
// Dependencies maps a transformed file to the files whose content influenced
|
|
21
|
+
// its output, keyed and valued like every other envelope section.
|
|
22
|
+
Dependencies map[string][]string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// TransformDependenciesFor computes the dependency side channel for the files
|
|
26
|
+
// this program transforms, keyed against cwd.
|
|
27
|
+
//
|
|
28
|
+
// **The rule that makes any of this declarable.** ttsc's own source-to-source
|
|
29
|
+
// transform is syntactic: the built-in native host answers with each file's
|
|
30
|
+
// parsed text, and the linked-plugin generic host prints the parsed AST through
|
|
31
|
+
// a printer that is handed neither a checker nor an emit resolver. Neither lane
|
|
32
|
+
// runs the emit transformer chain, so none of the type-driven lowerings happen
|
|
33
|
+
// there — no type-driven import elision, no `design:type` metadata, no `enum`,
|
|
34
|
+
// `namespace`, or JSX lowering, and no declaration emit. The output of a file
|
|
35
|
+
// the host alone produced is therefore a function of that file's own text and
|
|
36
|
+
// the compiler options, and nothing the type system knows about any other file
|
|
37
|
+
// can change it.
|
|
38
|
+
//
|
|
39
|
+
// What that leaves is the plugins. A source preamble is prepended to every
|
|
40
|
+
// file's text before parsing, and a program plugin mutates the parsed AST, so
|
|
41
|
+
// either can make an output depend on anything it consulted — including, for a
|
|
42
|
+
// checker-driven plugin, the whole type graph. Such a plugin is the only party
|
|
43
|
+
// that knows what it read, which is why the declaration is per (plugin, file)
|
|
44
|
+
// and why a file is complete only when every plugin that can contribute to it
|
|
45
|
+
// declared it. An emit-only plugin is not a contributor here: its transform runs
|
|
46
|
+
// in `build`, which produces no envelope.
|
|
47
|
+
//
|
|
48
|
+
// With no linked plugin at all the contributor set is empty and every file is
|
|
49
|
+
// complete with an empty list, which is the rule above stated for the lane that
|
|
50
|
+
// has nothing but the host in it. A contributor that declares nothing leaves
|
|
51
|
+
// every file unlisted, which is exactly the behaviour of every producer written
|
|
52
|
+
// before this existed.
|
|
53
|
+
//
|
|
54
|
+
// An embedder that supplies LoadProgramOptions.SourcePreamble itself, rather
|
|
55
|
+
// than obtaining it from a linked plugin, makes the same claim about that text
|
|
56
|
+
// by calling this: the preamble must be a function of inputs its envelope
|
|
57
|
+
// reports elsewhere, the way a plugin's preamble is a function of the config
|
|
58
|
+
// files it reports as host inputs.
|
|
59
|
+
func (p *Program) TransformDependenciesFor(cwd string) TransformDependencies {
|
|
60
|
+
if p == nil {
|
|
61
|
+
return TransformDependencies{}
|
|
62
|
+
}
|
|
63
|
+
// Declarations are made from inside the plugin hooks, so they exist only
|
|
64
|
+
// after those hooks ran. The apply is latched, so this cannot re-run them.
|
|
65
|
+
_ = p.ApplyLinkedPlugins()
|
|
66
|
+
files := p.sourceFilesRaw()
|
|
67
|
+
keys := make([]string, 0, len(files))
|
|
68
|
+
for _, file := range files {
|
|
69
|
+
keys = append(keys, TransformOutputKey(cwd, file.FileName()))
|
|
70
|
+
}
|
|
71
|
+
return p.plugins.transformDependencies(keys)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// transformDependencies aggregates every contributing plugin's declarations
|
|
75
|
+
// over the envelope keys of the transformed files.
|
|
76
|
+
func (state linkedPluginState) transformDependencies(keys []string) TransformDependencies {
|
|
77
|
+
return aggregateTransformDependencies(keys, state.transformContributors(), state.declarations)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// aggregateTransformDependencies folds the declarations of the given
|
|
81
|
+
// contributors into one envelope side channel.
|
|
82
|
+
//
|
|
83
|
+
// Separated from the entry classification above so the aggregation rule can be
|
|
84
|
+
// exercised without the process-wide plugin registry that classification reads.
|
|
85
|
+
func aggregateTransformDependencies(keys []string, contributors []int, declarations *pluginFileDeclarations) TransformDependencies {
|
|
86
|
+
out := TransformDependencies{}
|
|
87
|
+
for _, key := range keys {
|
|
88
|
+
declared := 0
|
|
89
|
+
inputs := map[string]struct{}{}
|
|
90
|
+
for _, index := range contributors {
|
|
91
|
+
declaration := declarations.lookup(index)
|
|
92
|
+
if declaration == nil {
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
for _, input := range declaration.dependenciesOf(key) {
|
|
96
|
+
// A file never depends on itself: the file's own text is outside the
|
|
97
|
+
// completeness contract by construction, and a self-edge would only
|
|
98
|
+
// make consumers register the module they are already transforming.
|
|
99
|
+
if input != key {
|
|
100
|
+
inputs[input] = struct{}{}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if declaration.declaresComplete(key) {
|
|
104
|
+
declared++
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if len(inputs) != 0 {
|
|
108
|
+
out.Dependencies = appendDependencyEntry(out.Dependencies, key, inputs)
|
|
109
|
+
}
|
|
110
|
+
if declared == len(contributors) {
|
|
111
|
+
out.Complete = append(out.Complete, key)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
sort.Strings(out.Complete)
|
|
115
|
+
return out
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// appendDependencyEntry records one file's sorted dependency list, allocating
|
|
119
|
+
// the map only for a producer that reported something.
|
|
120
|
+
func appendDependencyEntry(into map[string][]string, key string, inputs map[string]struct{}) map[string][]string {
|
|
121
|
+
if into == nil {
|
|
122
|
+
into = map[string][]string{}
|
|
123
|
+
}
|
|
124
|
+
entry := make([]string, 0, len(inputs))
|
|
125
|
+
for input := range inputs {
|
|
126
|
+
entry = append(entry, input)
|
|
127
|
+
}
|
|
128
|
+
sort.Strings(entry)
|
|
129
|
+
into[key] = entry
|
|
130
|
+
return into
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// transformContributors returns the indexes of the linked plugin entries that
|
|
134
|
+
// can influence source-to-source transform output.
|
|
135
|
+
//
|
|
136
|
+
// An entry with no registered plugin cannot be inspected, so it counts as a
|
|
137
|
+
// contributor that declared nothing; the host fails that entry elsewhere, and
|
|
138
|
+
// until it does, silence is the conservative answer.
|
|
139
|
+
func (state linkedPluginState) transformContributors() []int {
|
|
140
|
+
contributors := make([]int, 0, len(state.entries))
|
|
141
|
+
for index := range state.entries {
|
|
142
|
+
plugin, ok := registeredPlugin(index)
|
|
143
|
+
if !ok {
|
|
144
|
+
contributors = append(contributors, index)
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
_, preamble := plugin.(SourcePreamblePlugin)
|
|
148
|
+
_, program := plugin.(ProgramPlugin)
|
|
149
|
+
if preamble || program {
|
|
150
|
+
contributors = append(contributors, index)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return contributors
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// pluginFileDeclarations holds one declaration record per linked plugin entry.
|
|
157
|
+
type pluginFileDeclarations struct {
|
|
158
|
+
mu sync.Mutex
|
|
159
|
+
plugins map[int]*pluginFileDeclaration
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// pluginFileDeclaration is one plugin's reported dependencies and completeness
|
|
163
|
+
// claims, keyed by envelope key.
|
|
164
|
+
type pluginFileDeclaration struct {
|
|
165
|
+
complete map[string]struct{}
|
|
166
|
+
completeAll bool
|
|
167
|
+
dependencies map[string]map[string]struct{}
|
|
168
|
+
mu sync.Mutex
|
|
169
|
+
// rejected holds the files whose reported list this plugin could not state
|
|
170
|
+
// in full, because one of its members was unusable as a key.
|
|
171
|
+
rejected map[string]struct{}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
func newPluginFileDeclarations() *pluginFileDeclarations {
|
|
175
|
+
return &pluginFileDeclarations{plugins: map[int]*pluginFileDeclaration{}}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// forPlugin returns the declaration record of one plugin entry, creating it on
|
|
179
|
+
// first use. A nil ledger still answers a usable record so a hand-built state
|
|
180
|
+
// (unit tests, embedders) never panics through a plugin's reporting call.
|
|
181
|
+
func (declarations *pluginFileDeclarations) forPlugin(index int) *pluginFileDeclaration {
|
|
182
|
+
record := &pluginFileDeclaration{
|
|
183
|
+
complete: map[string]struct{}{},
|
|
184
|
+
dependencies: map[string]map[string]struct{}{},
|
|
185
|
+
rejected: map[string]struct{}{},
|
|
186
|
+
}
|
|
187
|
+
if declarations == nil {
|
|
188
|
+
return record
|
|
189
|
+
}
|
|
190
|
+
declarations.mu.Lock()
|
|
191
|
+
defer declarations.mu.Unlock()
|
|
192
|
+
if existing, ok := declarations.plugins[index]; ok {
|
|
193
|
+
return existing
|
|
194
|
+
}
|
|
195
|
+
declarations.plugins[index] = record
|
|
196
|
+
return record
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// lookup returns the declaration record of one plugin entry, or nil when that
|
|
200
|
+
// entry never reported anything.
|
|
201
|
+
func (declarations *pluginFileDeclarations) lookup(index int) *pluginFileDeclaration {
|
|
202
|
+
if declarations == nil {
|
|
203
|
+
return nil
|
|
204
|
+
}
|
|
205
|
+
declarations.mu.Lock()
|
|
206
|
+
defer declarations.mu.Unlock()
|
|
207
|
+
return declarations.plugins[index]
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
func (declaration *pluginFileDeclaration) addDependency(file string, dependency string) {
|
|
211
|
+
if declaration == nil {
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
declaration.mu.Lock()
|
|
215
|
+
defer declaration.mu.Unlock()
|
|
216
|
+
entry, ok := declaration.dependencies[file]
|
|
217
|
+
if !ok {
|
|
218
|
+
entry = map[string]struct{}{}
|
|
219
|
+
declaration.dependencies[file] = entry
|
|
220
|
+
}
|
|
221
|
+
entry[dependency] = struct{}{}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// rejectDependency withdraws this plugin's completeness claim for one file.
|
|
225
|
+
func (declaration *pluginFileDeclaration) rejectDependency(file string) {
|
|
226
|
+
if declaration == nil {
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
declaration.mu.Lock()
|
|
230
|
+
defer declaration.mu.Unlock()
|
|
231
|
+
declaration.rejected[file] = struct{}{}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
func (declaration *pluginFileDeclaration) addComplete(file string) {
|
|
235
|
+
if declaration == nil {
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
declaration.mu.Lock()
|
|
239
|
+
defer declaration.mu.Unlock()
|
|
240
|
+
declaration.complete[file] = struct{}{}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
func (declaration *pluginFileDeclaration) completeEveryFile() {
|
|
244
|
+
if declaration == nil {
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
declaration.mu.Lock()
|
|
248
|
+
defer declaration.mu.Unlock()
|
|
249
|
+
declaration.completeAll = true
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
func (declaration *pluginFileDeclaration) dependenciesOf(file string) []string {
|
|
253
|
+
if declaration == nil {
|
|
254
|
+
return nil
|
|
255
|
+
}
|
|
256
|
+
declaration.mu.Lock()
|
|
257
|
+
defer declaration.mu.Unlock()
|
|
258
|
+
entry, ok := declaration.dependencies[file]
|
|
259
|
+
if !ok {
|
|
260
|
+
return nil
|
|
261
|
+
}
|
|
262
|
+
out := make([]string, 0, len(entry))
|
|
263
|
+
for input := range entry {
|
|
264
|
+
out = append(out, input)
|
|
265
|
+
}
|
|
266
|
+
return out
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
func (declaration *pluginFileDeclaration) declaresComplete(file string) bool {
|
|
270
|
+
if declaration == nil {
|
|
271
|
+
return false
|
|
272
|
+
}
|
|
273
|
+
declaration.mu.Lock()
|
|
274
|
+
defer declaration.mu.Unlock()
|
|
275
|
+
if _, rejected := declaration.rejected[file]; rejected {
|
|
276
|
+
return false
|
|
277
|
+
}
|
|
278
|
+
if declaration.completeAll {
|
|
279
|
+
return true
|
|
280
|
+
}
|
|
281
|
+
_, ok := declaration.complete[file]
|
|
282
|
+
return ok
|
|
283
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package driver
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"reflect"
|
|
5
|
+
"testing"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// TestTransformDependenciesDeclaresEveryFileWithoutAContributor verifies the
|
|
9
|
+
// rule the host declares under when nothing else can contribute: a
|
|
10
|
+
// source-to-source transform is syntactic, so a file the host alone produced
|
|
11
|
+
// depends on its own text and the compiler options, and on nothing else.
|
|
12
|
+
//
|
|
13
|
+
// With an empty contributor set the aggregation is vacuously satisfied for
|
|
14
|
+
// every file, which is what lets a plugin-free project's consumer stop
|
|
15
|
+
// revalidating each delivered module's whole reference closure
|
|
16
|
+
// (samchon/ttsc#1259).
|
|
17
|
+
func TestTransformDependenciesDeclaresEveryFileWithoutAContributor(t *testing.T) {
|
|
18
|
+
keys := []string{"src/main.ts", "src/types.ts"}
|
|
19
|
+
|
|
20
|
+
out := aggregateTransformDependencies(keys, nil, newPluginFileDeclarations())
|
|
21
|
+
|
|
22
|
+
if !reflect.DeepEqual(out.Complete, []string{"src/main.ts", "src/types.ts"}) {
|
|
23
|
+
t.Fatalf("expected every file declared complete, got %v", out.Complete)
|
|
24
|
+
}
|
|
25
|
+
if out.Dependencies != nil {
|
|
26
|
+
t.Fatalf("expected no dependency entries, got %v", out.Dependencies)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package driver
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
// TestTransformDependenciesDropsASelfReportedDependency verifies a file never
|
|
6
|
+
// enters its own dependency list.
|
|
7
|
+
//
|
|
8
|
+
// The file's own text sits outside the completeness contract by construction —
|
|
9
|
+
// every consumer compares it before anything else — so a self-edge would only
|
|
10
|
+
// make a bundler register the module it is already transforming as one of that
|
|
11
|
+
// module's watch inputs.
|
|
12
|
+
func TestTransformDependenciesDropsASelfReportedDependency(t *testing.T) {
|
|
13
|
+
declarations := newPluginFileDeclarations()
|
|
14
|
+
declarations.forPlugin(0).addDependency("src/main.ts", "src/main.ts")
|
|
15
|
+
|
|
16
|
+
out := aggregateTransformDependencies([]string{"src/main.ts"}, []int{0}, declarations)
|
|
17
|
+
|
|
18
|
+
if len(out.Dependencies) != 0 {
|
|
19
|
+
t.Fatalf("expected the self-reported dependency to be dropped, got %v", out.Dependencies)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
package driver
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"reflect"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// TestTransformDependenciesKeysAReportedPathLikeTheEnvelope verifies a plugin's
|
|
10
|
+
// reported paths reach the envelope under the same key convention as every other
|
|
11
|
+
// section.
|
|
12
|
+
//
|
|
13
|
+
// A plugin reports the spellings its host handed it — absolute program file
|
|
14
|
+
// names, which the compiler normalizes to forward slashes on every platform, or
|
|
15
|
+
// paths relative to the plugin's cwd. Keying either differently from
|
|
16
|
+
// `typescript` would leave a consumer unable to join the sections at all, and on
|
|
17
|
+
// Windows the two spellings differ by separator alone, which is exactly the kind
|
|
18
|
+
// of mismatch that passes on POSIX CI and fails on a developer's machine.
|
|
19
|
+
func TestTransformDependenciesKeysAReportedPathLikeTheEnvelope(t *testing.T) {
|
|
20
|
+
// A real directory, so the absolute spellings below are absolute under the
|
|
21
|
+
// running platform's own rule rather than under POSIX's alone.
|
|
22
|
+
cwd := t.TempDir()
|
|
23
|
+
declarations := newPluginFileDeclarations()
|
|
24
|
+
ctx := PluginContext{
|
|
25
|
+
Cwd: cwd,
|
|
26
|
+
reportFileDependency: declarations.forPlugin(0).addDependency,
|
|
27
|
+
reportFileComplete: declarations.forPlugin(0).addComplete,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// The compiler's own spelling of a source file, and a cwd-relative one.
|
|
31
|
+
ctx.ReportFileDependency(
|
|
32
|
+
filepath.ToSlash(filepath.Join(cwd, "src", "main.ts")),
|
|
33
|
+
filepath.Join("src", "consulted.d.ts"),
|
|
34
|
+
)
|
|
35
|
+
ctx.ReportFileDependenciesComplete(filepath.Join(cwd, "src", "main.ts"))
|
|
36
|
+
ctx.ReportFileDependency(" ", "src/ignored.d.ts")
|
|
37
|
+
|
|
38
|
+
out := aggregateTransformDependencies([]string{"src/main.ts"}, []int{0}, declarations)
|
|
39
|
+
|
|
40
|
+
if !reflect.DeepEqual(out.Complete, []string{"src/main.ts"}) {
|
|
41
|
+
t.Fatalf("expected the reported file to key like the envelope, got %v", out.Complete)
|
|
42
|
+
}
|
|
43
|
+
if !reflect.DeepEqual(out.Dependencies["src/main.ts"], []string{"src/consulted.d.ts"}) {
|
|
44
|
+
t.Fatalf("expected the reported dependency to key like the envelope, got %v", out.Dependencies)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package driver
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"reflect"
|
|
5
|
+
"testing"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// TestTransformDependenciesRequiresEveryContributorToDeclare verifies the
|
|
9
|
+
// aggregation rule over a composed plugin set: a file is complete only when
|
|
10
|
+
// every contributing plugin declared it, because a consumer cannot attribute
|
|
11
|
+
// one plugin's reported inputs back to it.
|
|
12
|
+
//
|
|
13
|
+
// It also pins that reporting and declaring are separate acts: a plugin's
|
|
14
|
+
// dependency list widens what consumers invalidate on even while the file
|
|
15
|
+
// itself stays unlisted (samchon/ttsc#1263).
|
|
16
|
+
func TestTransformDependenciesRequiresEveryContributorToDeclare(t *testing.T) {
|
|
17
|
+
declarations := newPluginFileDeclarations()
|
|
18
|
+
first := declarations.forPlugin(0)
|
|
19
|
+
first.addDependency("src/main.ts", "src/consulted.d.ts")
|
|
20
|
+
first.addComplete("src/main.ts")
|
|
21
|
+
keys := []string{"src/main.ts"}
|
|
22
|
+
|
|
23
|
+
partial := aggregateTransformDependencies(keys, []int{0, 1}, declarations)
|
|
24
|
+
|
|
25
|
+
if len(partial.Complete) != 0 {
|
|
26
|
+
t.Fatalf("expected the silent contributor to block the claim, got %v", partial.Complete)
|
|
27
|
+
}
|
|
28
|
+
if !reflect.DeepEqual(partial.Dependencies["src/main.ts"], []string{"src/consulted.d.ts"}) {
|
|
29
|
+
t.Fatalf("expected the reported dependency to survive, got %v", partial.Dependencies)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declarations.forPlugin(1).completeEveryFile()
|
|
33
|
+
full := aggregateTransformDependencies(keys, []int{0, 1}, declarations)
|
|
34
|
+
|
|
35
|
+
if !reflect.DeepEqual(full.Complete, []string{"src/main.ts"}) {
|
|
36
|
+
t.Fatalf("expected both contributors to complete the claim, got %v", full.Complete)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
package driver
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
// TestTransformDependenciesStaysSilentForAnUndeclaredContributor verifies that
|
|
6
|
+
// a plugin able to change transform output blocks every completeness claim
|
|
7
|
+
// until it makes one itself.
|
|
8
|
+
//
|
|
9
|
+
// This is the compatibility half of the contract: every producer written before
|
|
10
|
+
// the declaration existed keeps the host-owned bound exactly, and a plugin whose
|
|
11
|
+
// output depends on the type graph cannot have that bound narrowed on its
|
|
12
|
+
// behalf (samchon/ttsc#1263).
|
|
13
|
+
func TestTransformDependenciesStaysSilentForAnUndeclaredContributor(t *testing.T) {
|
|
14
|
+
out := aggregateTransformDependencies(
|
|
15
|
+
[]string{"src/main.ts"},
|
|
16
|
+
[]int{0},
|
|
17
|
+
newPluginFileDeclarations(),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if len(out.Complete) != 0 {
|
|
21
|
+
t.Fatalf("expected no completeness declaration, got %v", out.Complete)
|
|
22
|
+
}
|
|
23
|
+
}
|
package/driver/transform_dependencies_withdraws_completeness_after_an_unusable_dependency_test.go
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package driver
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
// TestTransformDependenciesWithdrawsCompletenessAfterAnUnusableDependency
|
|
6
|
+
// verifies a dependency the host could not key withdraws the claim it belonged
|
|
7
|
+
// to, instead of leaving a complete list with a member missing from it.
|
|
8
|
+
//
|
|
9
|
+
// A plugin whose own lookup answered nothing for one input is the case: it
|
|
10
|
+
// reports what it has and declares the file complete, and a host that dropped
|
|
11
|
+
// the unusable member would publish exactly the under-declaration the protocol
|
|
12
|
+
// blames on the plugin — manufactured by the host. Only the affected file loses
|
|
13
|
+
// the claim; the plugin's other files keep theirs.
|
|
14
|
+
func TestTransformDependenciesWithdrawsCompletenessAfterAnUnusableDependency(t *testing.T) {
|
|
15
|
+
cwd := t.TempDir()
|
|
16
|
+
declarations := newPluginFileDeclarations()
|
|
17
|
+
record := declarations.forPlugin(0)
|
|
18
|
+
ctx := PluginContext{
|
|
19
|
+
Cwd: cwd,
|
|
20
|
+
reportFileDependency: record.addDependency,
|
|
21
|
+
reportFileDependencyRejected: record.rejectDependency,
|
|
22
|
+
reportEveryFileComplete: record.completeEveryFile,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
ctx.ReportDependenciesComplete()
|
|
26
|
+
ctx.ReportFileDependency("src/main.ts", "")
|
|
27
|
+
|
|
28
|
+
out := aggregateTransformDependencies(
|
|
29
|
+
[]string{"src/main.ts", "src/other.ts"},
|
|
30
|
+
[]int{0},
|
|
31
|
+
declarations,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if len(out.Complete) != 1 || out.Complete[0] != "src/other.ts" {
|
|
35
|
+
t.Fatalf("expected only the unaffected file to stay complete, got %v", out.Complete)
|
|
36
|
+
}
|
|
37
|
+
}
|