@ttsc/metro 0.30.4 → 0.31.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 +11 -6
- package/lib/core/SnapshotReadOperations.d.ts +119 -0
- package/lib/core/SnapshotReadOperations.js +3 -0
- package/lib/core/SnapshotReadOperations.js.map +1 -0
- package/lib/core/SnapshotReadOperations.mjs +2 -0
- package/lib/core/SnapshotReadOperations.mjs.map +1 -0
- package/lib/core/TtscMetroOptions.d.ts +119 -0
- package/lib/core/TtscMetroOptions.js +3 -0
- package/lib/core/TtscMetroOptions.js.map +1 -0
- package/lib/core/TtscMetroOptions.mjs +2 -0
- package/lib/core/TtscMetroOptions.mjs.map +1 -0
- package/lib/core/TtscMetroProjectView.d.ts +64 -0
- package/lib/core/TtscMetroProjectView.js +3 -0
- package/lib/core/TtscMetroProjectView.js.map +1 -0
- package/lib/core/TtscMetroProjectView.mjs +2 -0
- package/lib/core/TtscMetroProjectView.mjs.map +1 -0
- package/lib/core/UpstreamTransformer.d.ts +127 -0
- package/lib/core/UpstreamTransformer.js +3 -0
- package/lib/core/UpstreamTransformer.js.map +1 -0
- package/lib/core/UpstreamTransformer.mjs +2 -0
- package/lib/core/UpstreamTransformer.mjs.map +1 -0
- package/lib/core/fingerprint.d.ts +442 -97
- package/lib/core/fingerprint.js +838 -83
- package/lib/core/fingerprint.js.map +1 -1
- package/lib/core/fingerprint.mjs +839 -84
- package/lib/core/fingerprint.mjs.map +1 -1
- package/lib/core/options.d.ts +81 -58
- package/lib/core/options.js +81 -4
- package/lib/core/options.js.map +1 -1
- package/lib/core/options.mjs +81 -4
- package/lib/core/options.mjs.map +1 -1
- package/lib/core/remapAstLocations.d.ts +76 -0
- package/lib/core/remapAstLocations.js +205 -0
- package/lib/core/remapAstLocations.js.map +1 -0
- package/lib/core/remapAstLocations.mjs +203 -0
- package/lib/core/remapAstLocations.mjs.map +1 -0
- package/lib/core/upstream.d.ts +97 -27
- package/lib/core/upstream.js +109 -8
- package/lib/core/upstream.js.map +1 -1
- package/lib/core/upstream.mjs +109 -9
- package/lib/core/upstream.mjs.map +1 -1
- package/lib/index.d.ts +50 -2
- package/lib/index.js +88 -24
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +89 -25
- package/lib/index.mjs.map +1 -1
- package/lib/transformer.d.ts +183 -6
- package/lib/transformer.js +209 -14
- package/lib/transformer.js.map +1 -1
- package/lib/transformer.mjs +211 -16
- package/lib/transformer.mjs.map +1 -1
- package/package.json +7 -18
- package/src/core/SnapshotReadOperations.ts +121 -0
- package/src/core/TtscMetroOptions.ts +127 -0
- package/src/core/TtscMetroProjectView.ts +74 -0
- package/src/core/UpstreamTransformer.ts +126 -0
- package/src/core/fingerprint.ts +987 -109
- package/src/core/options.ts +85 -62
- package/src/core/remapAstLocations.ts +219 -0
- package/src/core/upstream.ts +110 -26
- package/src/index.ts +99 -28
- package/src/transformer.ts +214 -17
package/src/core/options.ts
CHANGED
|
@@ -1,61 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
* The `project` / `compilerOptions` / `plugins` fields are inherited from
|
|
7
|
-
* `@ttsc/unplugin` so the Metro adapter speaks the exact same configuration
|
|
8
|
-
* language as every other bundler integration. The remaining fields are
|
|
9
|
-
* Metro-specific.
|
|
10
|
-
*
|
|
11
|
-
* Every field is JSON-serialisable on purpose: `withTtsc` runs in the Metro
|
|
12
|
-
* **config** process, but the transformer runs in Metro's **worker** processes,
|
|
13
|
-
* so the resolved options have to survive a structured-clone / env round-trip
|
|
14
|
-
* to reach them (see {@link serializeOptions}). That is why `include`/`exclude`
|
|
15
|
-
* are plain substring patterns rather than `RegExp`.
|
|
16
|
-
*/
|
|
17
|
-
export interface TtscMetroOptions extends TtscUnpluginOptions {
|
|
18
|
-
/**
|
|
19
|
-
* Explicit module path of the upstream Metro Babel transformer to delegate to
|
|
20
|
-
* after the ttsc pass.
|
|
21
|
-
*
|
|
22
|
-
* When omitted it is auto-detected: `@expo/metro-config/babel-transformer`
|
|
23
|
-
* first (Expo), then `@react-native/metro-babel-transformer`, then the legacy
|
|
24
|
-
* `metro-react-native-babel-transformer`.
|
|
25
|
-
*/
|
|
26
|
-
upstreamTransformer?: string;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Substring patterns; when non-empty only files whose path contains one of
|
|
30
|
-
* them are run through the ttsc pass. Non-matching files are passed straight
|
|
31
|
-
* to the upstream transformer.
|
|
32
|
-
*/
|
|
33
|
-
include?: string[];
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Substring patterns; files whose path contains one of them skip the ttsc
|
|
37
|
-
* pass and go straight to the upstream transformer. Applied after
|
|
38
|
-
* {@link include}.
|
|
39
|
-
*/
|
|
40
|
-
exclude?: string[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Fully-resolved options, split into the ttsc-side overlay and Metro-side
|
|
45
|
-
* knobs.
|
|
46
|
-
*/
|
|
47
|
-
export interface ResolvedTtscMetroOptions {
|
|
48
|
-
/** Options forwarded verbatim to the `@ttsc/unplugin` transform core. */
|
|
49
|
-
ttsc: TtscUnpluginOptions;
|
|
50
|
-
/** Explicit upstream transformer module path, or `undefined` to auto-detect. */
|
|
51
|
-
upstreamTransformer?: string;
|
|
52
|
-
/** Resolved include patterns (never `undefined`). */
|
|
53
|
-
include: string[];
|
|
54
|
-
/** Resolved exclude patterns (never `undefined`). */
|
|
55
|
-
exclude: string[];
|
|
56
|
-
/** Private run identity shared by `getCacheKey` and Metro workers. */
|
|
57
|
-
snapshotRunId?: string;
|
|
58
|
-
}
|
|
1
|
+
import type {
|
|
2
|
+
ResolvedTtscMetroOptions,
|
|
3
|
+
TtscMetroOptions,
|
|
4
|
+
} from "./TtscMetroOptions";
|
|
59
5
|
|
|
60
6
|
/**
|
|
61
7
|
* Environment variable that carries the resolved options from the Metro config
|
|
@@ -72,6 +18,42 @@ export const ENV_KEY = "TTSC_METRO_OPTIONS";
|
|
|
72
18
|
/**
|
|
73
19
|
* Serialise user options for transport to the worker processes via
|
|
74
20
|
* {@link ENV_KEY}.
|
|
21
|
+
*
|
|
22
|
+
* A supplied private run identity is written into the payload. This function
|
|
23
|
+
* returns JSON without publishing the environment variable or mutating options.
|
|
24
|
+
* Values must be JSON-serialisable; serialization errors propagate.
|
|
25
|
+
*
|
|
26
|
+
* @evidence contracts/common.md#principled-implementation
|
|
27
|
+
* JSON.stringify and object spread encode the declared worker transport.
|
|
28
|
+
* JSON failures propagate rather than inventing a successful payload.
|
|
29
|
+
*
|
|
30
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
31
|
+
* A single serializer owns the config-to-worker payload. Publication stays
|
|
32
|
+
* with withTtsc, so encoding has no environment side effect.
|
|
33
|
+
*
|
|
34
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
35
|
+
* A supplied private run identity overrides that transport field; this
|
|
36
|
+
* operation returns bytes and does not replace process methods, loader
|
|
37
|
+
* internals or caller options.
|
|
38
|
+
*
|
|
39
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
40
|
+
* The native JSDoc explains the JSON payload, supplied run identity and
|
|
41
|
+
* serialization failure boundary. Checked against the documentation skill:
|
|
42
|
+
* separate paragraphs state the contract and why its nonobvious boundary
|
|
43
|
+
* matters; field comments retain their own useful facts.
|
|
44
|
+
*
|
|
45
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
|
|
46
|
+
* Returns a string and keeps no state or handle.
|
|
47
|
+
*
|
|
48
|
+
* @evidenceExclude contracts/performance.md#efficient-algorithms
|
|
49
|
+
* One JSON.stringify of the user's small options record per config load.
|
|
50
|
+
*
|
|
51
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work
|
|
52
|
+
* Runs once per Metro config load; there is no repeated work to share.
|
|
53
|
+
*
|
|
54
|
+
* @evidenceExclude contracts/portability.md#os-neutral-implementation
|
|
55
|
+
* JSON.stringify escapes backslashes, so a Windows path in the options
|
|
56
|
+
* round-trips; no path is interpreted.
|
|
75
57
|
*/
|
|
76
58
|
export function serializeOptions(
|
|
77
59
|
options: TtscMetroOptions,
|
|
@@ -86,10 +68,50 @@ export function serializeOptions(
|
|
|
86
68
|
/**
|
|
87
69
|
* Reconstruct the resolved options inside a worker process.
|
|
88
70
|
*
|
|
89
|
-
* Reads {@link ENV_KEY}; when it is unset or
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* called `withTtsc(config)` with no explicit
|
|
71
|
+
* Reads {@link ENV_KEY}; when it is unset or contains invalid JSON or a
|
|
72
|
+
* non-object payload, the adapter falls back to defaults: auto-discover
|
|
73
|
+
* `tsconfig.json` and read its configured plugins. This is the standard ttsc
|
|
74
|
+
* behaviour for a project that called `withTtsc(config)` with no explicit
|
|
75
|
+
* options.
|
|
76
|
+
*
|
|
77
|
+
* All common compiler options, including the output-addressing `projectRoot`,
|
|
78
|
+
* survive this transport without interpreting their paths in the config host.
|
|
79
|
+
* A present `plugins` property is forwarded even when its value is `false`;
|
|
80
|
+
* omission leaves plugin selection to the project. The `include` and `exclude`
|
|
81
|
+
* arrays retain only string entries, while invalid array values become empty
|
|
82
|
+
* filters. Only a non-empty string is accepted as the private run identity.
|
|
83
|
+
*
|
|
84
|
+
* @evidence contracts/common.md#principled-implementation
|
|
85
|
+
* JSON.parse reads the adapter-owned environment channel. The parser accepts
|
|
86
|
+
* only object payloads, filters include/exclude to strings and preserves
|
|
87
|
+
* plugins field presence so false differs from omission.
|
|
88
|
+
*
|
|
89
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
90
|
+
* One reconstruction function separates transport parsing and string-array
|
|
91
|
+
* normalization from the resolved object workers consume.
|
|
92
|
+
*
|
|
93
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
94
|
+
* Invalid JSON and non-object payloads use documented defaults, not a failed
|
|
95
|
+
* compiler fallback; this parser patches no global APIs.
|
|
96
|
+
*
|
|
97
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
98
|
+
* The native JSDoc explains absent, invalid-JSON and non-object defaults,
|
|
99
|
+
* plugin-presence semantics and filter normalization. Checked against the
|
|
100
|
+
* documentation skill: separate paragraphs state the contract and why its
|
|
101
|
+
* nonobvious boundary matters; field comments retain their own useful
|
|
102
|
+
* facts.
|
|
103
|
+
*
|
|
104
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
|
|
105
|
+
* Returns a fresh object and keeps no state or handle.
|
|
106
|
+
*
|
|
107
|
+
* @evidenceExclude contracts/performance.md#efficient-algorithms
|
|
108
|
+
* One JSON.parse and a filter over the short include and exclude arrays.
|
|
109
|
+
*
|
|
110
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work
|
|
111
|
+
* The transformer calls it once per worker and memoises the result in options().
|
|
112
|
+
*
|
|
113
|
+
* @evidenceExclude contracts/portability.md#os-neutral-implementation
|
|
114
|
+
* Reads one environment variable as JSON and resolves no path.
|
|
93
115
|
*/
|
|
94
116
|
export function resolveOptionsFromEnv(): ResolvedTtscMetroOptions {
|
|
95
117
|
const raw = process.env[ENV_KEY];
|
|
@@ -97,6 +119,7 @@ export function resolveOptionsFromEnv(): ResolvedTtscMetroOptions {
|
|
|
97
119
|
return {
|
|
98
120
|
ttsc: {
|
|
99
121
|
project: parsed.project,
|
|
122
|
+
projectRoot: parsed.projectRoot,
|
|
100
123
|
compilerOptions: parsed.compilerOptions,
|
|
101
124
|
...("plugins" in parsed ? { plugins: parsed.plugins } : {}),
|
|
102
125
|
},
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { createFilesystemPathIdentityContext } from "ttsc/path-identity";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Move every source location in an upstream transformer's AST from the ttsc
|
|
5
|
+
* transformed text back to the text the module's author wrote
|
|
6
|
+
* (samchon/ttsc#1392).
|
|
7
|
+
*
|
|
8
|
+
* Metro's Babel transformer contract returns an AST, never a map, and Metro
|
|
9
|
+
* builds the module's source map from that AST's `loc` positions against the
|
|
10
|
+
* file it read. The upstream transformer parsed the ttsc transformed text, so
|
|
11
|
+
* without this pass every position after the first change points at the
|
|
12
|
+
* reprinted lines: breakpoints, stack traces, and the error overlay would all
|
|
13
|
+
* be off. Each node's `loc` start and end are looked up in `map` with the usual
|
|
14
|
+
* greatest-lower-bound rule. A node whose start has no mapping into `file`,
|
|
15
|
+
* such as generated code, loses its `loc`, which Babel treats as a synthesized
|
|
16
|
+
* node, so it takes the mapping of the code around it.
|
|
17
|
+
*
|
|
18
|
+
* Babel locations have one-based lines and zero-based UTF-16 columns; source
|
|
19
|
+
* map segments have zero-based lines and columns. The map points from generated
|
|
20
|
+
* text to the authored file. Missing or reversed mapped ends clamp to the
|
|
21
|
+
* mapped start. Traversal tracks visited objects and mutates only AST
|
|
22
|
+
* locations. Absolute sources are compared by filesystem identity within this
|
|
23
|
+
* call, so links and actual directory case capabilities determine the owning
|
|
24
|
+
* source.
|
|
25
|
+
*
|
|
26
|
+
* @param ast The upstream transformer's AST, rewritten in place.
|
|
27
|
+
* @param map The adapter's map from the transformed text to `file`, with
|
|
28
|
+
* absolute `sources`.
|
|
29
|
+
* @param file Absolute path of the module.
|
|
30
|
+
* @evidence contracts/common.md#principled-implementation
|
|
31
|
+
* The adapter consumes its own source-map VLQ segments using
|
|
32
|
+
* greatest-lower-bound lookup, then updates the upstream-owned AST through
|
|
33
|
+
* Metro's returned-AST extension boundary. Iterative traversal tracks
|
|
34
|
+
* visited objects to handle shared/cyclic AST references. Unmapped starts
|
|
35
|
+
* lose loc and invalid ends clamp to the mapped start rather than
|
|
36
|
+
* manufacturing authored positions.
|
|
37
|
+
*
|
|
38
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
39
|
+
* Mapping decoding, position lookup and object traversal remain distinct
|
|
40
|
+
* steps; the traversal owns AST mutation and the local lookup owns coordinate
|
|
41
|
+
* conversion without adding a second Babel parser.
|
|
42
|
+
*
|
|
43
|
+
* @evidence contracts/performance.md#efficient-algorithms
|
|
44
|
+
* Mapping decoding is linear in encoded bytes. A visited Set walks each
|
|
45
|
+
* AST object once; each endpoint uses binary search on its generated line's
|
|
46
|
+
* segments. Temporary storage follows decoded segments and reachable AST
|
|
47
|
+
* objects, and the iterative stack avoids call-depth dependence on the AST.
|
|
48
|
+
* Source identity resolution is shared within this call; missing sources can
|
|
49
|
+
* additionally require ancestor and directory-capability observations.
|
|
50
|
+
*
|
|
51
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work
|
|
52
|
+
* This call mutates one returned AST using that delivery's map; it owns no
|
|
53
|
+
* shared cache and repeated calls would remap already moved coordinates.
|
|
54
|
+
*
|
|
55
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
|
|
56
|
+
* The caller owns the AST. Local decoded lines and traversal state end with
|
|
57
|
+
* the call; no handle or cross-delivery state is retained.
|
|
58
|
+
*
|
|
59
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
60
|
+
* No Babel methods or compiler internals are patched.
|
|
61
|
+
*
|
|
62
|
+
* @evidence contracts/portability.md#os-neutral-implementation
|
|
63
|
+
* The shared filesystem-identity context compares absolute map sources with
|
|
64
|
+
* the delivered file using physical spelling and actual directory case
|
|
65
|
+
* capabilities. Source coordinates remain line/column values independent
|
|
66
|
+
* of native newline spelling; OS names do not fold distinct filenames.
|
|
67
|
+
*
|
|
68
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
69
|
+
* The native JSDoc explains map direction, in-place mutation, coordinate
|
|
70
|
+
* indexing and generated/unmapped locations. Checked against the
|
|
71
|
+
* documentation skill: separate paragraphs state the contract and why its
|
|
72
|
+
* nonobvious boundary matters; field comments retain their own useful
|
|
73
|
+
* facts.
|
|
74
|
+
*/
|
|
75
|
+
export function remapAstLocations(
|
|
76
|
+
ast: unknown,
|
|
77
|
+
map: { mappings: string; sources: readonly string[] },
|
|
78
|
+
file: string,
|
|
79
|
+
): void {
|
|
80
|
+
const identities = createFilesystemPathIdentityContext({
|
|
81
|
+
throwOnRealpathError: false,
|
|
82
|
+
});
|
|
83
|
+
const fileKey = identities.resolve(file).key;
|
|
84
|
+
const own = map.sources.findIndex(
|
|
85
|
+
(source) => identities.resolve(source).key === fileKey,
|
|
86
|
+
);
|
|
87
|
+
if (own < 0) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const lines = decodeSegments(map.mappings);
|
|
91
|
+
const locate = (
|
|
92
|
+
position: unknown,
|
|
93
|
+
): { column: number; line: number } | undefined => {
|
|
94
|
+
if (typeof position !== "object" || position === null) return undefined;
|
|
95
|
+
const { column, line } = position as { column?: unknown; line?: unknown };
|
|
96
|
+
if (typeof line !== "number" || typeof column !== "number") {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
const segments = lines[line - 1];
|
|
100
|
+
if (segments === undefined) return undefined;
|
|
101
|
+
let low = 0;
|
|
102
|
+
let high = segments.length - 1;
|
|
103
|
+
let found: Segment | undefined;
|
|
104
|
+
while (low <= high) {
|
|
105
|
+
const middle = (low + high) >> 1;
|
|
106
|
+
const segment = segments[middle]!;
|
|
107
|
+
if (segment.generatedColumn <= column) {
|
|
108
|
+
found = segment;
|
|
109
|
+
low = middle + 1;
|
|
110
|
+
} else {
|
|
111
|
+
high = middle - 1;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return found === undefined || found.source !== own
|
|
115
|
+
? undefined
|
|
116
|
+
: { column: found.column, line: found.line + 1 };
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const visited = new Set<object>();
|
|
120
|
+
const pending: unknown[] = [ast];
|
|
121
|
+
while (pending.length !== 0) {
|
|
122
|
+
const value = pending.pop();
|
|
123
|
+
if (typeof value !== "object" || value === null || visited.has(value)) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
visited.add(value);
|
|
127
|
+
if (Array.isArray(value)) {
|
|
128
|
+
for (const entryToAppend of value) pending.push(entryToAppend);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const node = value as Record<string, unknown>;
|
|
132
|
+
for (const [key, child] of Object.entries(node)) {
|
|
133
|
+
if (key !== "loc") pending.push(child);
|
|
134
|
+
}
|
|
135
|
+
const loc = node.loc;
|
|
136
|
+
if (typeof loc !== "object" || loc === null) continue;
|
|
137
|
+
const { end, start } = loc as { end?: unknown; start?: unknown };
|
|
138
|
+
const mappedStart = locate(start);
|
|
139
|
+
if (mappedStart === undefined) {
|
|
140
|
+
node.loc = null;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const mappedEnd = locate(end);
|
|
144
|
+
node.loc = {
|
|
145
|
+
...loc,
|
|
146
|
+
end:
|
|
147
|
+
mappedEnd === undefined ||
|
|
148
|
+
mappedEnd.line < mappedStart.line ||
|
|
149
|
+
(mappedEnd.line === mappedStart.line &&
|
|
150
|
+
mappedEnd.column < mappedStart.column)
|
|
151
|
+
? mappedStart
|
|
152
|
+
: mappedEnd,
|
|
153
|
+
start: mappedStart,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** One decoded mapping on a generated line. */
|
|
159
|
+
interface Segment {
|
|
160
|
+
/** Zero-based generated column. */
|
|
161
|
+
generatedColumn: number;
|
|
162
|
+
|
|
163
|
+
/** Index into the map's `sources`, or `-1` for an unmapped segment. */
|
|
164
|
+
source: number;
|
|
165
|
+
|
|
166
|
+
/** Zero-based source line. */
|
|
167
|
+
line: number;
|
|
168
|
+
|
|
169
|
+
/** Zero-based source column. */
|
|
170
|
+
column: number;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Decode `mappings` into its segments per generated line, in column order.
|
|
175
|
+
* Segments of every source are kept, and a segment with no source position is
|
|
176
|
+
* kept with source `-1`, so a lookup that lands on either reports no position
|
|
177
|
+
* instead of reaching past it to an earlier mapping.
|
|
178
|
+
*/
|
|
179
|
+
function decodeSegments(mappings: string): Segment[][] {
|
|
180
|
+
const alphabet =
|
|
181
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
182
|
+
const lines: Segment[][] = [];
|
|
183
|
+
let source = 0;
|
|
184
|
+
let line = 0;
|
|
185
|
+
let column = 0;
|
|
186
|
+
for (const text of mappings.split(";")) {
|
|
187
|
+
const segments: Segment[] = [];
|
|
188
|
+
let generatedColumn = 0;
|
|
189
|
+
for (const encoded of text.split(",")) {
|
|
190
|
+
if (encoded === "") continue;
|
|
191
|
+
const fields: number[] = [];
|
|
192
|
+
let value = 0;
|
|
193
|
+
let shift = 0;
|
|
194
|
+
for (const char of encoded) {
|
|
195
|
+
const digit = alphabet.indexOf(char);
|
|
196
|
+
if (digit < 0) return lines;
|
|
197
|
+
value += (digit & 31) << shift;
|
|
198
|
+
if ((digit & 32) !== 0) {
|
|
199
|
+
shift += 5;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
fields.push((value & 1) !== 0 ? -(value >>> 1) : value >>> 1);
|
|
203
|
+
value = 0;
|
|
204
|
+
shift = 0;
|
|
205
|
+
}
|
|
206
|
+
generatedColumn += fields[0] ?? 0;
|
|
207
|
+
if (fields.length < 4) {
|
|
208
|
+
segments.push({ column: 0, generatedColumn, line: 0, source: -1 });
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
source += fields[1]!;
|
|
212
|
+
line += fields[2]!;
|
|
213
|
+
column += fields[3]!;
|
|
214
|
+
segments.push({ column, generatedColumn, line, source });
|
|
215
|
+
}
|
|
216
|
+
lines.push(segments);
|
|
217
|
+
}
|
|
218
|
+
return lines;
|
|
219
|
+
}
|
package/src/core/upstream.ts
CHANGED
|
@@ -1,25 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import type { UpstreamTransformer } from "./UpstreamTransformer";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* The subset of the Metro Babel-transformer contract this adapter relies on.
|
|
7
|
-
*
|
|
8
|
-
* Metro loads the module named by `transformer.babelTransformerPath` and calls
|
|
9
|
-
* its `transform` once per file, expecting a Babel AST back. `getCacheKey` is
|
|
10
|
-
* an optional export Metro folds into its transform-cache key; Metro invokes it
|
|
11
|
-
* with arguments (e.g. `{ projectRoot, enableBabelRCLookup }`), so it is typed
|
|
12
|
-
* variadic.
|
|
13
|
-
*/
|
|
14
|
-
export interface UpstreamTransformer {
|
|
15
|
-
transform(params: {
|
|
16
|
-
src: string;
|
|
17
|
-
filename: string;
|
|
18
|
-
options: Record<string, unknown>;
|
|
19
|
-
[key: string]: unknown;
|
|
20
|
-
}): Promise<{ ast: object }>;
|
|
21
|
-
getCacheKey?: (...args: unknown[]) => string;
|
|
22
|
-
}
|
|
5
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
23
6
|
|
|
24
7
|
/**
|
|
25
8
|
* Upstream transformer module specifiers tried (in order) when no explicit
|
|
@@ -40,14 +23,55 @@ export const UPSTREAM_CANDIDATES = [
|
|
|
40
23
|
* 1. An explicit `customPath` (the `upstreamTransformer` option);
|
|
41
24
|
* 2. Each of {@link UPSTREAM_CANDIDATES} in turn.
|
|
42
25
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
26
|
+
* `withTtsc` resolves an explicit specifier and the automatic candidates from
|
|
27
|
+
* the consuming project in the Metro config process, and publishes the absolute
|
|
28
|
+
* path it found (see {@link locateProjectUpstreamTransformer}). This worker-side
|
|
29
|
+
* lookup, rooted in the adapter's own location, is what remains for a specifier
|
|
30
|
+
* the project could not resolve, so the adapter carries no Metro/Expo
|
|
31
|
+
* dependency itself. Resolution is not memoised: Node's own module cache
|
|
32
|
+
* already makes the repeated `require` a cheap lookup, and keeping no
|
|
33
|
+
* module-level state lets a changed `upstreamTransformer` always take effect.
|
|
34
|
+
*
|
|
35
|
+
* `load` is the explicit module-loading boundary. Its default uses Node's real
|
|
36
|
+
* `require`; an injected loader must preserve absence versus failure
|
|
37
|
+
* semantics.
|
|
38
|
+
*
|
|
39
|
+
* @evidence contracts/common.md#principled-implementation
|
|
40
|
+
* Node createRequire loads the configured module or documented
|
|
41
|
+
* Expo/React-Native candidates. Resolution is separated from execution: only
|
|
42
|
+
* known entry-absence codes permit another automatic candidate;
|
|
43
|
+
* installed-module initialization failures preserve their cause.
|
|
48
44
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
45
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
46
|
+
* Explicit selection and ordered detection share the loading boundary.
|
|
47
|
+
* tryRequire owns absence classification, leaving this function to choose
|
|
48
|
+
* the transformer and attach contextual errors.
|
|
49
|
+
*
|
|
50
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
51
|
+
* The loader argument is an explicit dependency-injection boundary, not a
|
|
52
|
+
* test-only runtime branch or a patched require.
|
|
53
|
+
*
|
|
54
|
+
* @evidence contracts/portability.md#os-neutral-implementation
|
|
55
|
+
* Node resolution accepts project-resolved absolute paths on Windows and
|
|
56
|
+
* POSIX. createRequire is rooted at this module via import.meta.url; no
|
|
57
|
+
* path is turned into a shell command. Runtime errors propagate through the
|
|
58
|
+
* same policy on every OS.
|
|
59
|
+
*
|
|
60
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
61
|
+
* The native JSDoc explains candidate order, project versus worker
|
|
62
|
+
* resolution and absence versus initialization failures. Checked against
|
|
63
|
+
* the documentation skill: separate paragraphs state the contract and why
|
|
64
|
+
* its nonobvious boundary matters; field comments retain their own useful
|
|
65
|
+
* facts.
|
|
66
|
+
*
|
|
67
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
|
|
68
|
+
* Keeps no state between calls; loaded modules belong to Node's module cache.
|
|
69
|
+
*
|
|
70
|
+
* @evidenceExclude contracts/performance.md#efficient-algorithms
|
|
71
|
+
* At most three candidate probes per call, each a require.resolve followed by one require.
|
|
72
|
+
*
|
|
73
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work
|
|
74
|
+
* Deliberately unmemoised: Node's module cache makes a repeated require cheap and a changed option must take effect.
|
|
51
75
|
*/
|
|
52
76
|
export function resolveUpstreamTransformer(
|
|
53
77
|
customPath?: string,
|
|
@@ -101,6 +125,66 @@ export function resolveUpstreamTransformer(
|
|
|
101
125
|
);
|
|
102
126
|
}
|
|
103
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Locate the automatic upstream transformer the consuming project installed,
|
|
130
|
+
* without executing it.
|
|
131
|
+
*
|
|
132
|
+
* The candidates belong to the app, not to this adapter: under pnpm or a linked
|
|
133
|
+
* workspace the adapter sits outside the app's `node_modules` ancestry, so a
|
|
134
|
+
* lookup rooted in the adapter cannot see a transformer only the app installed.
|
|
135
|
+
* `resolve` is rooted in the project; each candidate is tried in
|
|
136
|
+
* {@link UPSTREAM_CANDIDATES} order and an absent one is skipped, exactly as the
|
|
137
|
+
* worker's probe skips it. Only resolution runs here, so a broken installation
|
|
138
|
+
* still fails where the worker loads it, with its own error.
|
|
139
|
+
*
|
|
140
|
+
* @param resolve Resolves a module specifier from the project.
|
|
141
|
+
* @returns The absolute path of the first installed candidate, or `undefined`.
|
|
142
|
+
* @evidence contracts/common.md#principled-implementation
|
|
143
|
+
* The supplied project resolver uses Node module resolution without
|
|
144
|
+
* executing candidate code. Only recognized entry-absence errors continue
|
|
145
|
+
* probing; other resolver failures propagate.
|
|
146
|
+
*
|
|
147
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
148
|
+
* Project discovery resolves only the shared candidate list; execution is
|
|
149
|
+
* deferred to the worker loader that owns initialization failures.
|
|
150
|
+
*
|
|
151
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
152
|
+
* The documented optional-peer order is a product default, not a
|
|
153
|
+
* fixture-specific answer. No module methods are replaced.
|
|
154
|
+
*
|
|
155
|
+
* @evidence contracts/portability.md#os-neutral-implementation
|
|
156
|
+
* The injected resolver owns project filesystem resolution on the host OS.
|
|
157
|
+
* This operation compares module specifiers and returns its absolute result
|
|
158
|
+
* without inventing separators or quoting a command.
|
|
159
|
+
*
|
|
160
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
161
|
+
* The native JSDoc explains project ownership, ordered probing,
|
|
162
|
+
* nonexecution and the absent result. Checked against the documentation
|
|
163
|
+
* skill: separate paragraphs state the contract and why its nonobvious
|
|
164
|
+
* boundary matters; field comments retain their own useful facts.
|
|
165
|
+
*
|
|
166
|
+
* @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
|
|
167
|
+
* Resolves paths without executing a candidate and retains nothing.
|
|
168
|
+
*
|
|
169
|
+
* @evidenceExclude contracts/performance.md#efficient-algorithms
|
|
170
|
+
* At most three resolutions, stopping at the first one that succeeds.
|
|
171
|
+
*
|
|
172
|
+
* @evidenceExclude contracts/performance.md#reuse-equivalent-work
|
|
173
|
+
* Runs once per withTtsc call, in the config process only.
|
|
174
|
+
*/
|
|
175
|
+
export function locateProjectUpstreamTransformer(
|
|
176
|
+
resolve: (specifier: string) => string,
|
|
177
|
+
): string | undefined {
|
|
178
|
+
for (const candidate of UPSTREAM_CANDIDATES) {
|
|
179
|
+
try {
|
|
180
|
+
return resolve(candidate);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (!isCandidateAbsent(error)) throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
104
188
|
/**
|
|
105
189
|
* Load an upstream transformer module, separating genuine absence from a broken
|
|
106
190
|
* installation.
|