svelte-docinfo 0.4.0 → 0.5.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.
@@ -13,7 +13,6 @@
13
13
  * @module
14
14
  */
15
15
  import type { DeclarationJson, ModuleJson } from './types.js';
16
- import type { ReExportInfo } from './declaration-build.js';
17
16
  import type { SourceFileInfo } from './source.js';
18
17
  /**
19
18
  * A duplicate declaration with its full metadata and module path.
@@ -27,6 +26,14 @@ export interface DuplicateDeclaration {
27
26
  /**
28
27
  * Find duplicate declaration names across modules.
29
28
  *
29
+ * A duplicate is two *different things* sharing a name in the flat namespace.
30
+ * Occurrences are compared by canonical identity — `aliasOf` chains are
31
+ * resolved first, so an alias and its canonical (or two aliases of the same
32
+ * canonical) are one thing, not a collision. Documenting a same-name re-export
33
+ * (which synthesizes an alias) or re-exporting a component under its own name
34
+ * (`export {default as Foo} from './Foo.svelte'`) therefore doesn't flag.
35
+ * When a name does flag, all occurrences are reported, aliases included.
36
+ *
30
37
  * Callers can decide how to handle duplicates (throw, warn, ignore).
31
38
  *
32
39
  * @returns `Map` of declaration names to their `DuplicateDeclaration` occurrences (only includes duplicates)
@@ -46,59 +53,59 @@ export interface DuplicateDeclaration {
46
53
  * ```
47
54
  */
48
55
  export declare const findDuplicates: (modules: Array<ModuleJson>) => Map<string, Array<DuplicateDeclaration>>;
56
+ /**
57
+ * Code-unit string comparator for deterministic output ordering.
58
+ *
59
+ * Unlike `localeCompare` (host-locale/ICU-dependent, so byte-identical input
60
+ * can serialize in different orders on different machines), code-unit order is
61
+ * environment-independent — and it matches the default `Array.prototype.sort`
62
+ * used for `alsoExportedFrom`, `dependencies`, and `dependents`.
63
+ */
64
+ export declare const compareStrings: (a: string, b: string) => number;
49
65
  /**
50
66
  * Sort modules alphabetically by path for deterministic output and cleaner diffs.
51
67
  *
68
+ * Code-unit order (`compareStrings`) so the output is environment-independent.
69
+ *
52
70
  * @param modules - the modules to sort
53
71
  * @returns a new sorted array (does not mutate input)
54
72
  */
55
73
  export declare const sortModules: (modules: Array<ModuleJson>) => Array<ModuleJson>;
56
74
  /**
57
- * A collected re-export with its source module context.
58
- *
59
- * Used during the two-phase re-export resolution:
60
- * 1. Phase 1: Collect re-exports from each module during analysis
61
- * 2. Phase 2: Group by original module and merge into `alsoExportedFrom`
62
- */
63
- export interface ReExportEntry {
64
- /** The module that re-exports the declaration. */
65
- reExportingModule: string;
66
- /** The re-export info (name and original module). */
67
- reExport: ReExportInfo;
68
- }
69
- /**
70
- * Build `alsoExportedFrom` arrays from collected re-export data.
71
- *
72
- * This function resolves the two-phase re-export problem:
75
+ * Build `alsoExportedFrom` arrays from the modules' forward re-export edges.
73
76
  *
74
- * **Problem**: When module A re-exports from module B, we discover this while
75
- * analyzing A, but need to update B's declarations. However, B may already be
76
- * processed or may be processed later.
77
- *
78
- * **Solution**: Collect all re-exports in phase 1, then merge them in phase 2
79
- * after all modules are analyzed.
77
+ * Each module carries its same-name re-export edges as `ModuleJson.reExports`
78
+ * (collected in phase 1); this phase-2 pass inverts them onto the canonical
79
+ * declarations so both directions of the same fact are queryable. Edges whose
80
+ * canonical module or declaration is absent from `modules` are skipped — the
81
+ * forward entry remains without a back-link (see `ReExportJson` for the
82
+ * presence caveats).
80
83
  *
81
84
  * Component-only fields on renamed component aliases (`props`,
82
85
  * `acceptsChildren`, etc.) are populated separately by `resolveComponentAliases`
83
86
  * — call it after this function. They split because they touch disjoint fields
84
87
  * and have different inputs.
85
88
  *
86
- * @param modules - the modules array with all modules (will be mutated)
87
- * @param collectedReExports - array of re-exports collected during phase 1
88
- * @mutates modules - unions new re-exporters into `declaration.alsoExportedFrom`
89
+ * @param modules - the modules array with all modules (will be mutated).
90
+ * Must be parsed `ModuleJson`s — wire JSON strips empty arrays, so run
91
+ * raw JSON through `AnalyzeResultJson.parse` first or `reExports` may be
92
+ * `undefined`
93
+ * @mutates modules - unions re-exporters into `declaration.alsoExportedFrom`
89
94
  * (deduped + sorted), so a second call with the same inputs is a no-op
90
95
  *
91
96
  * @example
92
97
  * ```ts
93
98
  * // helpers.ts exports: foo, bar
94
- * // index.ts does: export {foo, bar} from './files.js'
99
+ * // index.ts does: export {foo, bar} from './helpers.js'
100
+ * // (so index.ts's ModuleJson carries reExports:
101
+ * // [{name: 'foo', module: 'helpers.ts'}, {name: 'bar', module: 'helpers.ts'}])
95
102
  * //
96
103
  * // After processing:
97
104
  * // - helpers.ts foo declaration gets: alsoExportedFrom: ['index.ts']
98
105
  * // - helpers.ts bar declaration gets: alsoExportedFrom: ['index.ts']
99
106
  * ```
100
107
  */
101
- export declare const mergeReExports: (modules: Array<ModuleJson>, collectedReExports: Array<ReExportEntry>) => void;
108
+ export declare const mergeReExports: (modules: Array<ModuleJson>) => void;
102
109
  /**
103
110
  * Copy props/acceptsChildren/lang/etc. from canonical component declarations
104
111
  * onto synthesized component-aliased declarations.
@@ -142,4 +149,87 @@ export declare const resolveComponentAliases: (modules: Array<ModuleJson>) => vo
142
149
  export declare const computeDependents: (files: ReadonlyArray<SourceFileInfo>) => Array<SourceFileInfo & {
143
150
  dependents?: ReadonlyArray<string>;
144
151
  }>;
152
+ /**
153
+ * One name on a module's resolved export surface.
154
+ */
155
+ export interface ExportSurfaceEntry {
156
+ /**
157
+ * Exported name, in the docinfo model's terms — Svelte components appear
158
+ * under their filename-derived name (the model's convention for default
159
+ * exports of `.svelte` files), not `'default'`.
160
+ */
161
+ name: string;
162
+ /**
163
+ * How the name reaches this module's surface: an own declaration
164
+ * (including synthesized aliases), a same-name re-export edge, a direct
165
+ * external re-export, or projection through `export * from './x'`.
166
+ */
167
+ via: 'declaration' | 'reExport' | 'external' | 'star';
168
+ /** Canonical module path, when known (`undefined` for external entries). */
169
+ module?: string;
170
+ /** The canonical declaration, when present in the analyzed set. */
171
+ declaration?: DeclarationJson;
172
+ /** Package specifier for external entries (as written in the statement). */
173
+ specifier?: string;
174
+ /** Name inside the external package when renamed. */
175
+ originalName?: string;
176
+ /** Type-only re-export — the name is erased at runtime. */
177
+ typeOnly?: boolean;
178
+ /** For star-projected entries: the `starExports` target the name arrived through. */
179
+ starFrom?: string;
180
+ }
181
+ /**
182
+ * A module's resolved export surface — see `resolveExportSurface`.
183
+ */
184
+ export interface ExportSurface {
185
+ /** Surface entries, sorted by `name` (code-unit order). */
186
+ entries: Array<ExportSurfaceEntry>;
187
+ /**
188
+ * Star targets (own or transitive) absent from the analyzed set — their
189
+ * projected names are unknown, so the surface is incomplete.
190
+ */
191
+ unresolvedStarExports: Array<string>;
192
+ /**
193
+ * External star specifiers reachable from this module (own or transitive)
194
+ * — their projected names are unknowable without analyzing the package.
195
+ */
196
+ externalStarExports: Array<string>;
197
+ }
198
+ /**
199
+ * Resolve a module's full export surface from the analyzed model, applying
200
+ * ES module semantics to star exports.
201
+ *
202
+ * Combines the module's own `declarations` (including synthesized aliases),
203
+ * `reExports` edges, `externalReExports`, and transitively-resolved
204
+ * `starExports` into one deduped, name-sorted list. The ES rules applied to
205
+ * star projection:
206
+ *
207
+ * - explicit exports (declarations, edges, externals) shadow star-projected
208
+ * names
209
+ * - a name projected by two stars that resolve to *different* canonicals is
210
+ * ambiguous and excluded (same canonical through a diamond is included
211
+ * once)
212
+ * - `default` is never star-projected — including canonical Svelte component
213
+ * declarations, which represent their file's default export. (Caveat: a
214
+ * star-projected re-export edge whose canonical is a component is treated
215
+ * as a default-slot re-export and skipped; a `<script module>` const
216
+ * sharing the component's exact name would be skipped with it.)
217
+ *
218
+ * A Position-3 alias and its `reExports` edge are the same fact — the
219
+ * declaration entry wins, inheriting the edge's `typeOnly`.
220
+ *
221
+ * Cyclic star graphs terminate by contributing nothing along the back-edge
222
+ * (an approximation of ES fixpoint resolution — fine in practice). Surfaces
223
+ * resolved inside a cycle are path-relative and not memoized, so sibling
224
+ * star paths resolve independently rather than inheriting them. Star
225
+ * targets missing from `modules` are reported in `unresolvedStarExports`
226
+ * rather than guessed at; `externalStarExports` aggregates external star
227
+ * specifiers reachable from the module, whose names are unknowable.
228
+ *
229
+ * @param modules - the analyzed modules (parsed `ModuleJson`s — run wire
230
+ * JSON through `AnalyzeResultJson.parse` first)
231
+ * @param path - the module to resolve, as a `ModuleJson.path` value
232
+ * @returns the resolved surface, or `null` when `path` isn't in `modules`
233
+ */
234
+ export declare const resolveExportSurface: (modules: Array<ModuleJson>, path: string) => ExportSurface | null;
145
235
  //# sourceMappingURL=postprocess.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"postprocess.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/postprocess.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAA2B,eAAe,EAAE,UAAU,EAAC,MAAM,YAAY,CAAC;AACtF,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAuBhD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,qCAAqC;IACrC,WAAW,EAAE,eAAe,CAAC;IAC7B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,cAAc,GAC1B,SAAS,KAAK,CAAC,UAAU,CAAC,KACxB,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,oBAAoB,CAAC,CA4BzC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,KAAK,CAAC,UAAU,CAAC,KAAG,KAAK,CAAC,UAAU,CAExE,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC7B,kDAAkD;IAClD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,QAAQ,EAAE,YAAY,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,cAAc,GAC1B,SAAS,KAAK,CAAC,UAAU,CAAC,EAC1B,oBAAoB,KAAK,CAAC,aAAa,CAAC,KACtC,IAkCF,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,KAAK,CAAC,UAAU,CAAC,KAAG,IAoDpE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,OAAO,aAAa,CAAC,cAAc,CAAC,KAClC,KAAK,CAAC,cAAc,GAAG;IAAC,UAAU,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAAC,CAwD7D,CAAC"}
1
+ {"version":3,"file":"postprocess.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/postprocess.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAA2B,eAAe,EAAE,UAAU,EAAC,MAAM,YAAY,CAAC;AACtF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAuBhD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,qCAAqC;IACrC,WAAW,EAAE,eAAe,CAAC;IAC7B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;CACf;AA6CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,cAAc,GAC1B,SAAS,KAAK,CAAC,UAAU,CAAC,KACxB,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,oBAAoB,CAAC,CAwCzC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAAsC,CAAC;AAE7F;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,KAAK,CAAC,UAAU,CAAC,KAAG,KAAK,CAAC,UAAU,CAExE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS,KAAK,CAAC,UAAU,CAAC,KAAG,IAmC3D,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,KAAK,CAAC,UAAU,CAAC,KAAG,IAoDpE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,OAAO,aAAa,CAAC,cAAc,CAAC,KAClC,KAAK,CAAC,cAAc,GAAG;IAAC,UAAU,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAAC,CAwD7D,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,GAAG,EAAE,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;IACtD,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,2DAA2D;IAC3D,OAAO,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACnC;;;OAGG;IACH,qBAAqB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrC;;;OAGG;IACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACnC;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,KAAK,CAAC,UAAU,CAAC,EAC1B,MAAM,MAAM,KACV,aAAa,GAAG,IA+IlB,CAAC"}
@@ -32,9 +32,56 @@ const posixifyArray = (arr) => {
32
32
  }
33
33
  return changed ?? arr;
34
34
  };
35
+ /**
36
+ * Build the `(module, name)` → declaration lookup used for `aliasOf`-chain
37
+ * walking. Key format: `` `${modulePath}\n${name}` `` — `\n` can't appear in
38
+ * either part.
39
+ */
40
+ const buildDeclarationIndex = (modules) => {
41
+ const byIdentity = new Map();
42
+ for (const mod of modules) {
43
+ for (const declaration of mod.declarations) {
44
+ byIdentity.set(`${mod.path}\n${declaration.name}`, declaration);
45
+ }
46
+ }
47
+ return byIdentity;
48
+ };
49
+ /**
50
+ * Resolve a declaration to its canonical identity. A same-name re-export of
51
+ * an intermediate rename produces an alias pointing at another alias, so a
52
+ * single hop isn't enough — follow the chain through declarations present in
53
+ * the index. The terminal is the declaration *object* when it's in the set
54
+ * (object identity keeps two same-`(module, name)` declarations distinct in
55
+ * pathological inputs), else the dangling `(module, name)` key (two aliases
56
+ * of the same absent canonical still resolve to one identity). The visited
57
+ * set guards malformed cyclic input.
58
+ */
59
+ const resolveCanonicalIdentity = (byIdentity, modulePath, declaration) => {
60
+ let current = declaration;
61
+ const visited = new Set([`${modulePath}\n${declaration.name}`]);
62
+ while (current.aliasOf) {
63
+ const nextKey = `${current.aliasOf.module}\n${current.aliasOf.name}`;
64
+ if (visited.has(nextKey))
65
+ return nextKey;
66
+ visited.add(nextKey);
67
+ const next = byIdentity.get(nextKey);
68
+ if (!next)
69
+ return nextKey;
70
+ current = next;
71
+ }
72
+ return current;
73
+ };
35
74
  /**
36
75
  * Find duplicate declaration names across modules.
37
76
  *
77
+ * A duplicate is two *different things* sharing a name in the flat namespace.
78
+ * Occurrences are compared by canonical identity — `aliasOf` chains are
79
+ * resolved first, so an alias and its canonical (or two aliases of the same
80
+ * canonical) are one thing, not a collision. Documenting a same-name re-export
81
+ * (which synthesizes an alias) or re-exporting a component under its own name
82
+ * (`export {default as Foo} from './Foo.svelte'`) therefore doesn't flag.
83
+ * When a name does flag, all occurrences are reported, aliases included.
84
+ *
38
85
  * Callers can decide how to handle duplicates (throw, warn, ignore).
39
86
  *
40
87
  * @returns `Map` of declaration names to their `DuplicateDeclaration` occurrences (only includes duplicates)
@@ -54,10 +101,13 @@ const posixifyArray = (arr) => {
54
101
  * ```
55
102
  */
56
103
  export const findDuplicates = (modules) => {
104
+ const byIdentity = buildDeclarationIndex(modules);
105
+ const resolveCanonical = (modulePath, declaration) => resolveCanonicalIdentity(byIdentity, modulePath, declaration);
106
+ // Collect declaration names with their canonical identities. The default
107
+ // slot is module-scoped per the JS spec — every module can have its own
108
+ // `'default'` and they don't collide — so skip `name === 'default'`
109
+ // entries from this flat-namespace check.
57
110
  const allOccurrences = new Map();
58
- // Collect declaration names. The default slot is module-scoped per the JS
59
- // spec — every module can have its own `'default'` and they don't collide
60
- // — so skip `name === 'default'` entries from this flat-namespace check.
61
111
  for (const mod of modules) {
62
112
  for (const declaration of mod.declarations) {
63
113
  if (declaration.name === 'default')
@@ -68,74 +118,90 @@ export const findDuplicates = (modules) => {
68
118
  allOccurrences.get(declaration.name).push({
69
119
  declaration,
70
120
  module: mod.path,
121
+ canonical: resolveCanonical(mod.path, declaration),
71
122
  });
72
123
  }
73
124
  }
74
- // Filter to only duplicates
125
+ // A name is duplicated only when its occurrences span >1 canonical identity
75
126
  const duplicates = new Map();
76
127
  for (const [name, occurrences] of allOccurrences) {
77
- if (occurrences.length > 1) {
78
- duplicates.set(name, occurrences);
128
+ const identities = new Set(occurrences.map((o) => o.canonical));
129
+ if (identities.size > 1) {
130
+ duplicates.set(name, occurrences.map(({ declaration, module }) => ({ declaration, module })));
79
131
  }
80
132
  }
81
133
  return duplicates;
82
134
  };
135
+ /**
136
+ * Code-unit string comparator for deterministic output ordering.
137
+ *
138
+ * Unlike `localeCompare` (host-locale/ICU-dependent, so byte-identical input
139
+ * can serialize in different orders on different machines), code-unit order is
140
+ * environment-independent — and it matches the default `Array.prototype.sort`
141
+ * used for `alsoExportedFrom`, `dependencies`, and `dependents`.
142
+ */
143
+ export const compareStrings = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
83
144
  /**
84
145
  * Sort modules alphabetically by path for deterministic output and cleaner diffs.
85
146
  *
147
+ * Code-unit order (`compareStrings`) so the output is environment-independent.
148
+ *
86
149
  * @param modules - the modules to sort
87
150
  * @returns a new sorted array (does not mutate input)
88
151
  */
89
152
  export const sortModules = (modules) => {
90
- return modules.slice().sort((a, b) => a.path.localeCompare(b.path));
153
+ return modules.slice().sort((a, b) => compareStrings(a.path, b.path));
91
154
  };
92
155
  /**
93
- * Build `alsoExportedFrom` arrays from collected re-export data.
94
- *
95
- * This function resolves the two-phase re-export problem:
96
- *
97
- * **Problem**: When module A re-exports from module B, we discover this while
98
- * analyzing A, but need to update B's declarations. However, B may already be
99
- * processed or may be processed later.
156
+ * Build `alsoExportedFrom` arrays from the modules' forward re-export edges.
100
157
  *
101
- * **Solution**: Collect all re-exports in phase 1, then merge them in phase 2
102
- * after all modules are analyzed.
158
+ * Each module carries its same-name re-export edges as `ModuleJson.reExports`
159
+ * (collected in phase 1); this phase-2 pass inverts them onto the canonical
160
+ * declarations so both directions of the same fact are queryable. Edges whose
161
+ * canonical module or declaration is absent from `modules` are skipped — the
162
+ * forward entry remains without a back-link (see `ReExportJson` for the
163
+ * presence caveats).
103
164
  *
104
165
  * Component-only fields on renamed component aliases (`props`,
105
166
  * `acceptsChildren`, etc.) are populated separately by `resolveComponentAliases`
106
167
  * — call it after this function. They split because they touch disjoint fields
107
168
  * and have different inputs.
108
169
  *
109
- * @param modules - the modules array with all modules (will be mutated)
110
- * @param collectedReExports - array of re-exports collected during phase 1
111
- * @mutates modules - unions new re-exporters into `declaration.alsoExportedFrom`
170
+ * @param modules - the modules array with all modules (will be mutated).
171
+ * Must be parsed `ModuleJson`s — wire JSON strips empty arrays, so run
172
+ * raw JSON through `AnalyzeResultJson.parse` first or `reExports` may be
173
+ * `undefined`
174
+ * @mutates modules - unions re-exporters into `declaration.alsoExportedFrom`
112
175
  * (deduped + sorted), so a second call with the same inputs is a no-op
113
176
  *
114
177
  * @example
115
178
  * ```ts
116
179
  * // helpers.ts exports: foo, bar
117
- * // index.ts does: export {foo, bar} from './files.js'
180
+ * // index.ts does: export {foo, bar} from './helpers.js'
181
+ * // (so index.ts's ModuleJson carries reExports:
182
+ * // [{name: 'foo', module: 'helpers.ts'}, {name: 'bar', module: 'helpers.ts'}])
118
183
  * //
119
184
  * // After processing:
120
185
  * // - helpers.ts foo declaration gets: alsoExportedFrom: ['index.ts']
121
186
  * // - helpers.ts bar declaration gets: alsoExportedFrom: ['index.ts']
122
187
  * ```
123
188
  */
124
- export const mergeReExports = (modules, collectedReExports) => {
125
- // Group re-exports by `(originalModule, name)`. The default slot keys as
189
+ export const mergeReExports = (modules) => {
190
+ // Group edges by `(canonical module, name)`. The default slot keys as
126
191
  // `'default'` like any other name — each module owns its own default slot,
127
192
  // so the per-module map prevents cross-module collisions naturally.
128
193
  const reExportMap = new Map();
129
- for (const { reExportingModule, reExport } of collectedReExports) {
130
- const { name, originalModule } = reExport;
131
- if (!reExportMap.has(originalModule)) {
132
- reExportMap.set(originalModule, new Map());
133
- }
134
- const moduleMap = reExportMap.get(originalModule);
135
- if (!moduleMap.has(name)) {
136
- moduleMap.set(name, []);
194
+ for (const mod of modules) {
195
+ for (const { name, module: originalModule } of mod.reExports) {
196
+ if (!reExportMap.has(originalModule)) {
197
+ reExportMap.set(originalModule, new Map());
198
+ }
199
+ const moduleMap = reExportMap.get(originalModule);
200
+ if (!moduleMap.has(name)) {
201
+ moduleMap.set(name, []);
202
+ }
203
+ moduleMap.get(name).push(mod.path);
137
204
  }
138
- moduleMap.get(name).push(reExportingModule);
139
205
  }
140
206
  // Merge into original declarations
141
207
  for (const mod of modules) {
@@ -303,3 +369,185 @@ export const computeDependents = (files) => {
303
369
  };
304
370
  });
305
371
  };
372
+ /**
373
+ * Resolve a module's full export surface from the analyzed model, applying
374
+ * ES module semantics to star exports.
375
+ *
376
+ * Combines the module's own `declarations` (including synthesized aliases),
377
+ * `reExports` edges, `externalReExports`, and transitively-resolved
378
+ * `starExports` into one deduped, name-sorted list. The ES rules applied to
379
+ * star projection:
380
+ *
381
+ * - explicit exports (declarations, edges, externals) shadow star-projected
382
+ * names
383
+ * - a name projected by two stars that resolve to *different* canonicals is
384
+ * ambiguous and excluded (same canonical through a diamond is included
385
+ * once)
386
+ * - `default` is never star-projected — including canonical Svelte component
387
+ * declarations, which represent their file's default export. (Caveat: a
388
+ * star-projected re-export edge whose canonical is a component is treated
389
+ * as a default-slot re-export and skipped; a `<script module>` const
390
+ * sharing the component's exact name would be skipped with it.)
391
+ *
392
+ * A Position-3 alias and its `reExports` edge are the same fact — the
393
+ * declaration entry wins, inheriting the edge's `typeOnly`.
394
+ *
395
+ * Cyclic star graphs terminate by contributing nothing along the back-edge
396
+ * (an approximation of ES fixpoint resolution — fine in practice). Surfaces
397
+ * resolved inside a cycle are path-relative and not memoized, so sibling
398
+ * star paths resolve independently rather than inheriting them. Star
399
+ * targets missing from `modules` are reported in `unresolvedStarExports`
400
+ * rather than guessed at; `externalStarExports` aggregates external star
401
+ * specifiers reachable from the module, whose names are unknowable.
402
+ *
403
+ * @param modules - the analyzed modules (parsed `ModuleJson`s — run wire
404
+ * JSON through `AnalyzeResultJson.parse` first)
405
+ * @param path - the module to resolve, as a `ModuleJson.path` value
406
+ * @returns the resolved surface, or `null` when `path` isn't in `modules`
407
+ */
408
+ export const resolveExportSurface = (modules, path) => {
409
+ const byPath = new Map(modules.map((m) => [m.path, m]));
410
+ if (!byPath.has(path))
411
+ return null;
412
+ const byIdentity = buildDeclarationIndex(modules);
413
+ const cache = new Map();
414
+ const visiting = new Set();
415
+ const emptySurface = () => ({
416
+ entries: new Map(),
417
+ unresolvedStars: new Set(),
418
+ externalStars: new Set(),
419
+ });
420
+ // Only surfaces resolved without hitting a cycle back-edge are memoized.
421
+ // A surface computed while one of its star ancestors is mid-resolution is
422
+ // relative to that path — it can carry names a full resolution would
423
+ // exclude as ambiguous (the back-edge contributed nothing to weigh them
424
+ // against). Caching it would leak the path-relative answer to sibling
425
+ // star paths; tainted surfaces are instead recomputed per consumer.
426
+ // Recomputation is bounded by `visiting` and only occurs inside cyclic
427
+ // clusters, which are pathological to begin with.
428
+ const resolveModule = (modulePath) => {
429
+ const cached = cache.get(modulePath);
430
+ if (cached)
431
+ return { surface: cached, tainted: false };
432
+ // Cycle break: a star back-edge contributes nothing
433
+ if (visiting.has(modulePath))
434
+ return { surface: emptySurface(), tainted: true };
435
+ visiting.add(modulePath);
436
+ const mod = byPath.get(modulePath);
437
+ const surface = emptySurface();
438
+ let tainted = false;
439
+ const { entries } = surface;
440
+ // 1. Own declarations — including synthesized aliases and `default`
441
+ const edgesByName = new Map(mod.reExports.map((e) => [e.name, e]));
442
+ for (const declaration of mod.declarations) {
443
+ // A Position-3 alias duplicates its edge; carry the edge's typeOnly
444
+ const matchingEdge = declaration.aliasOf &&
445
+ edgesByName.get(declaration.name)?.module === declaration.aliasOf.module
446
+ ? edgesByName.get(declaration.name)
447
+ : undefined;
448
+ entries.set(declaration.name, {
449
+ entry: {
450
+ name: declaration.name,
451
+ via: 'declaration',
452
+ module: modulePath,
453
+ declaration,
454
+ ...(matchingEdge?.typeOnly ? { typeOnly: true } : {}),
455
+ },
456
+ identity: resolveCanonicalIdentity(byIdentity, modulePath, declaration),
457
+ });
458
+ }
459
+ // 2. Same-name re-export edges not already covered by a declaration.
460
+ // Edges can collide on `name` (Svelte default re-keying — see
461
+ // `ReExportJson`); the surface is keyed by name, so the first edge in
462
+ // sort order wins and the collider is dropped
463
+ for (const edge of mod.reExports) {
464
+ if (entries.has(edge.name))
465
+ continue;
466
+ const canonical = byIdentity.get(`${edge.module}\n${edge.name}`);
467
+ entries.set(edge.name, {
468
+ entry: {
469
+ name: edge.name,
470
+ via: 'reExport',
471
+ module: edge.module,
472
+ ...(canonical ? { declaration: canonical } : {}),
473
+ ...(edge.typeOnly ? { typeOnly: true } : {}),
474
+ },
475
+ identity: canonical
476
+ ? resolveCanonicalIdentity(byIdentity, edge.module, canonical)
477
+ : `${edge.module}\n${edge.name}`,
478
+ });
479
+ }
480
+ // 3. Direct external re-exports
481
+ for (const external of mod.externalReExports) {
482
+ if (entries.has(external.name))
483
+ continue;
484
+ entries.set(external.name, {
485
+ entry: {
486
+ name: external.name,
487
+ via: 'external',
488
+ specifier: external.specifier,
489
+ ...(external.originalName !== undefined ? { originalName: external.originalName } : {}),
490
+ ...(external.typeOnly ? { typeOnly: true } : {}),
491
+ },
492
+ identity: `ext\n${external.specifier}\n${external.originalName ?? external.name}`,
493
+ });
494
+ }
495
+ // 4. External stars — names unknowable, surface incompleteness recorded
496
+ for (const specifier of mod.externalStarExports) {
497
+ surface.externalStars.add(specifier);
498
+ }
499
+ // 5. Star projection, with ES shadowing and ambiguity rules
500
+ const excluded = new Set();
501
+ for (const target of mod.starExports) {
502
+ if (!byPath.has(target)) {
503
+ surface.unresolvedStars.add(target);
504
+ continue;
505
+ }
506
+ const resolved = resolveModule(target);
507
+ if (resolved.tainted)
508
+ tainted = true;
509
+ const sub = resolved.surface;
510
+ for (const star of sub.unresolvedStars)
511
+ surface.unresolvedStars.add(star);
512
+ for (const star of sub.externalStars)
513
+ surface.externalStars.add(star);
514
+ for (const { entry, identity } of sub.entries.values()) {
515
+ // `default` never projects — nor do canonical Svelte components,
516
+ // which represent their file's default export (whether reached
517
+ // as a declaration or through a re-export edge)
518
+ if (entry.name === 'default')
519
+ continue;
520
+ if (entry.declaration?.kind === 'component' && !entry.declaration.aliasOf)
521
+ continue;
522
+ if (excluded.has(entry.name))
523
+ continue;
524
+ const existing = entries.get(entry.name);
525
+ if (existing) {
526
+ // Explicit exports shadow; identical canonicals (diamond) merge
527
+ if (existing.entry.via !== 'star' || existing.identity === identity)
528
+ continue;
529
+ // Ambiguous between two stars — excluded per ES semantics
530
+ entries.delete(entry.name);
531
+ excluded.add(entry.name);
532
+ continue;
533
+ }
534
+ entries.set(entry.name, {
535
+ entry: { ...entry, via: 'star', starFrom: target },
536
+ identity,
537
+ });
538
+ }
539
+ }
540
+ visiting.delete(modulePath);
541
+ if (!tainted)
542
+ cache.set(modulePath, surface);
543
+ return { surface, tainted };
544
+ };
545
+ const resolved = resolveModule(path).surface;
546
+ return {
547
+ entries: Array.from(resolved.entries.values())
548
+ .map(({ entry }) => entry)
549
+ .sort((a, b) => compareStrings(a.name, b.name)),
550
+ unresolvedStarExports: Array.from(resolved.unresolvedStars).sort(),
551
+ externalStarExports: Array.from(resolved.externalStars).sort(),
552
+ };
553
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAC,QAAQ,EAAsB,MAAM,2BAA2B,CAAC;AAGxE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EAA2C,kBAAkB,EAAC,MAAM,YAAY,CAAC;AAC7F,OAAO,KAAK,EAGX,cAAc,EACd,MAAM,wBAAwB,CAAC;AAKhC,OAAO,EAAC,KAAK,cAAc,EAA0C,MAAM,aAAa,CAAC;AACzF,OAAO,EAAC,KAAK,mBAAmB,EAAsB,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,0FAA0F;AAC1F,KAAK,SAAS,GAAG,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IACjC,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,sGAAsG;IACtG,IAAI,EAAE,IAAI,GAAG,SAAS,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACvC,WAAW,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,GAAI,YAAY,cAAc,KAAG,eAoDlE,CAAC;AAkHF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,cAAc,MAAM,KAAG,MAAM,GAAG,SAcpE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GAAI,cAAc,MAAM,KAAG,MAAM,GAAG,SA0BxE,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,GAAI,eAAe,MAAM,KAAG,MAAM,GAAG,SAU3E,CAAC;AA0PF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,KAAG,OAExD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,GACpC,aAAa,EAAE,CAAC,IAAI,EACpB,SAAS,EAAE,CAAC,WAAW,KACrB,KAAK,CAAC,kBAAkB,CAqB1B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,KAAG,OAKxD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B,GAAI,YAAY,KAAK,CAAC,kBAAkB,CAAC,KAAG,MAGtF,CAAC;AA8NF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,mBAAmB,GAC/B,YAAY,cAAc,GAAG;IAAC,UAAU,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAAC,EACjE,YAAY,MAAM,EAClB,SAAS,EAAE,CAAC,WAAW,EACvB,SAAS,mBAAmB,EAC5B,aAAa,KAAK,CAAC,UAAU,CAAC,EAC9B,SAAS,EAAE,CAAC,OAAO,EACnB,aAAa,iBAAiB,KAC5B,cAAc,GAAG,SAuNnB,CAAC"}
1
+ {"version":3,"file":"svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAC,QAAQ,EAAsB,MAAM,2BAA2B,CAAC;AAGxE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EAA2C,kBAAkB,EAAC,MAAM,YAAY,CAAC;AAC7F,OAAO,KAAK,EAGX,cAAc,EACd,MAAM,wBAAwB,CAAC;AAKhC,OAAO,EAAC,KAAK,cAAc,EAA0C,MAAM,aAAa,CAAC;AACzF,OAAO,EAAC,KAAK,mBAAmB,EAAsB,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,0FAA0F;AAC1F,KAAK,SAAS,GAAG,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IACjC,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,sGAAsG;IACtG,IAAI,EAAE,IAAI,GAAG,SAAS,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACvC,WAAW,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,GAAI,YAAY,cAAc,KAAG,eAoDlE,CAAC;AAkHF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,cAAc,MAAM,KAAG,MAAM,GAAG,SAcpE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GAAI,cAAc,MAAM,KAAG,MAAM,GAAG,SA0BxE,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,GAAI,eAAe,MAAM,KAAG,MAAM,GAAG,SAU3E,CAAC;AA0PF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,KAAG,OAExD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,GACpC,aAAa,EAAE,CAAC,IAAI,EACpB,SAAS,EAAE,CAAC,WAAW,KACrB,KAAK,CAAC,kBAAkB,CAqB1B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,KAAG,OAKxD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B,GAAI,YAAY,KAAK,CAAC,kBAAkB,CAAC,KAAG,MAGtF,CAAC;AA8NF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,mBAAmB,GAC/B,YAAY,cAAc,GAAG;IAAC,UAAU,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAAC,EACjE,YAAY,MAAM,EAClB,SAAS,EAAE,CAAC,WAAW,EACvB,SAAS,mBAAmB,EAC5B,aAAa,KAAK,CAAC,UAAU,CAAC,EAC9B,SAAS,EAAE,CAAC,OAAO,EACnB,aAAa,iBAAiB,KAC5B,cAAc,GAAG,SAoQnB,CAAC"}