@shapeshift-labs/frontier-lang-compiler 0.2.116 → 0.2.118

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 CHANGED
@@ -238,6 +238,35 @@ console.log(project.outputProjectSymbolGraph.importEdges[0].packageName); // "@p
238
238
  console.log(project.outputProjectSymbolGraph.importEdges[0].packageExportCondition); // "import"
239
239
  ```
240
240
 
241
+ NodeNext-style JS extension specifiers can resolve to supplied TS source files.
242
+ For example, `import './runtime.js'` can resolve to `src/runtime.ts` when that is
243
+ the available project document. Graph edges record `resolutionPathVariant` as
244
+ `"extension-substitution"` so coordinators can distinguish exact source matches
245
+ from source-extension substitutions during stale checks and merge admission.
246
+
247
+ Package `imports` maps are also modeled for `#internal` specifiers. Top-level
248
+ `moduleResolution.imports` applies from `packageRoot`/`root`, while
249
+ `packages[name].imports` applies to the nearest configured package root. Graph
250
+ edges record `packageImportKey`, `packageImportCondition`, and
251
+ `packageImportTarget` so merge admission can distinguish private aliases from
252
+ external or unresolved imports:
253
+
254
+ ```js
255
+ const project = safeMergeJsTsProject({
256
+ includeOutputProjectSymbolGraph: true,
257
+ moduleResolution: {
258
+ imports: { '#internal/*': { import: './src/internal/*.ts', default: './src/internal/*.js' } },
259
+ packageExportConditions: ['import', 'default']
260
+ },
261
+ baseFiles,
262
+ workerFiles,
263
+ headFiles
264
+ });
265
+
266
+ console.log(project.outputProjectSymbolGraph.importEdges[0].resolutionKind); // "package-import-source"
267
+ console.log(project.outputProjectSymbolGraph.importEdges[0].packageImportKey); // "#internal/*"
268
+ ```
269
+
241
270
  Named re-export identities also include symbol links when the project graph has
242
271
  enough evidence. For `export { thing as renamedThing } from './thing.js'`,
243
272
  `reExportIdentities[]` records the source module, imported/exported names,
@@ -1,6 +1,7 @@
1
1
  export type NativeProjectModuleResolutionPaths = Readonly<Record<string, readonly string[] | string>>;
2
2
  export type NativeProjectPackageExportTarget = string | readonly string[] | NativeProjectPackageConditionalExports;
3
3
  export type NativeProjectPackageExports = NativeProjectPackageExportTarget | Readonly<Record<string, NativeProjectPackageExportTarget>>;
4
+ export type NativeProjectPackageImports = Readonly<Record<string, NativeProjectPackageExportTarget>>;
4
5
 
5
6
  export interface NativeProjectPackageConditionalExports {
6
7
  readonly [condition: string]: NativeProjectPackageExportTarget;
@@ -12,12 +13,17 @@ export interface NativeProjectPackageResolutionOptions {
12
13
  readonly main?: string;
13
14
  readonly types?: string;
14
15
  readonly exports?: NativeProjectPackageExports;
16
+ readonly imports?: NativeProjectPackageImports;
15
17
  }
16
18
 
17
19
  export interface NativeProjectModuleResolutionOptions {
20
+ readonly root?: string;
21
+ readonly packageRoot?: string;
18
22
  readonly baseUrl?: string;
19
23
  readonly paths?: NativeProjectModuleResolutionPaths;
20
24
  readonly aliases?: NativeProjectModuleResolutionPaths;
25
+ readonly imports?: NativeProjectPackageImports;
26
+ readonly packageImports?: NativeProjectPackageImports;
21
27
  readonly packages?: Readonly<Record<string, NativeProjectPackageResolutionOptions>>;
22
28
  readonly conditions?: readonly string[];
23
29
  readonly packageExportConditions?: readonly string[];
@@ -107,10 +107,10 @@ export interface NativeProjectSymbolGraphModuleEdgeRecord {
107
107
  readonly resolvedModulePath?: string;
108
108
  readonly targetDocumentId?: string;
109
109
  readonly resolvedTargetSymbolId?: string;
110
- readonly resolutionKind?: 'relative-source' | 'relative-missing' | 'alias-source' | 'alias-missing' | 'path-alias-source' | 'path-alias-missing' | 'base-url-source' | 'base-url-missing' | 'package-source' | 'package-missing' | 'package-external' | string;
111
- readonly packageName?: string;
112
- readonly packageSubpath?: string;
113
- readonly packageExportCondition?: string;
110
+ readonly resolutionKind?: 'relative-source' | 'relative-missing' | 'alias-source' | 'alias-missing' | 'path-alias-source' | 'path-alias-missing' | 'base-url-source' | 'base-url-missing' | 'package-source' | 'package-missing' | 'package-external' | 'package-import-source' | 'package-import-missing' | 'package-import-external' | string;
111
+ readonly resolutionPathVariant?: 'exact' | 'extension-substitution' | 'extensionless' | 'index' | string;
112
+ readonly packageName?: string; readonly packageSubpath?: string; readonly packageExportCondition?: string;
113
+ readonly packageImportKey?: string; readonly packageImportCondition?: string; readonly packageImportTarget?: string;
114
114
  readonly importKind?: string;
115
115
  readonly exportKind?: string;
116
116
  readonly importedName?: string;
@@ -240,9 +240,13 @@ function moduleEdgeRecord(relation, moduleEdgeByRelation, symbolsById, documents
240
240
  resolvedModulePath: resolution?.path,
241
241
  targetDocumentId: resolution?.documentId,
242
242
  resolutionKind: resolution?.kind,
243
+ resolutionPathVariant: resolution?.resolutionPathVariant,
243
244
  packageName: resolution?.packageName,
244
245
  packageSubpath: resolution?.packageSubpath,
245
246
  packageExportCondition: resolution?.packageExportCondition,
247
+ packageImportKey: resolution?.packageImportKey,
248
+ packageImportCondition: resolution?.packageImportCondition,
249
+ packageImportTarget: resolution?.packageImportTarget,
246
250
  importKind: firstString(moduleEdge.importKind, value.importKind, symbolMetadata.importKind),
247
251
  exportKind: firstString(moduleEdge.exportKind, value.exportKind, symbolMetadata.exportKind),
248
252
  importedName: firstString(moduleEdge.importedName, value.importedName, symbolMetadata.importedName),
@@ -0,0 +1,52 @@
1
+ export function modulePathCandidates(path) {
2
+ const normalized = String(path ?? '');
3
+ return uniqueCandidates([
4
+ candidate(normalized, 'exact'),
5
+ ...extensionSubstitutionCandidates(normalized),
6
+ ...extensionlessCandidates(normalized),
7
+ ...indexCandidates(normalized)
8
+ ]);
9
+ }
10
+
11
+ function extensionSubstitutionCandidates(path) {
12
+ const entries = [
13
+ ['.js', ['.ts', '.tsx', '.d.ts', '.jsx']],
14
+ ['.jsx', ['.tsx', '.ts', '.d.ts']],
15
+ ['.mjs', ['.mts', '.ts', '.d.mts']],
16
+ ['.cjs', ['.cts', '.ts', '.d.cts']]
17
+ ];
18
+ for (const [from, targets] of entries) {
19
+ if (!path.endsWith(from)) continue;
20
+ const base = path.slice(0, -from.length);
21
+ return targets.map((target) => candidate(`${base}${target}`, 'extension-substitution'));
22
+ }
23
+ return [];
24
+ }
25
+
26
+ function extensionlessCandidates(path) {
27
+ if (hasKnownExtension(path)) return [];
28
+ return ['.js', '.ts', '.tsx', '.jsx', '.mjs', '.mts', '.cjs', '.cts', '.d.ts']
29
+ .map((extension) => candidate(`${path}${extension}`, 'extensionless'));
30
+ }
31
+
32
+ function indexCandidates(path) {
33
+ return ['.js', '.ts', '.tsx', '.jsx', '.mjs', '.mts', '.cjs', '.cts', '.d.ts']
34
+ .map((extension) => candidate(`${path}/index${extension}`, 'index'));
35
+ }
36
+
37
+ function hasKnownExtension(path) {
38
+ return /\.(?:[cm]?[jt]sx?|d\.[cm]?ts)$/.test(path);
39
+ }
40
+
41
+ function candidate(path, variant) {
42
+ return { path, variant };
43
+ }
44
+
45
+ function uniqueCandidates(candidates) {
46
+ const seen = new Set();
47
+ return candidates.filter((entry) => {
48
+ if (!entry.path || seen.has(entry.path)) return false;
49
+ seen.add(entry.path);
50
+ return true;
51
+ });
52
+ }
@@ -1,3 +1,5 @@
1
+ import { modulePathCandidates } from './projectSymbolGraphModulePathCandidates.js';
2
+
1
3
  export function resolveRelativeProjectModule(sourcePath, moduleSpecifier, documentsByPath) {
2
4
  if (!sourcePath || !moduleSpecifier || !moduleSpecifier.startsWith('.')) return undefined;
3
5
  const base = sourcePath.includes('/') ? sourcePath.slice(0, sourcePath.lastIndexOf('/')) : '';
@@ -6,6 +8,7 @@ export function resolveRelativeProjectModule(sourcePath, moduleSpecifier, docume
6
8
  return {
7
9
  path: target?.path ?? unresolvedPath,
8
10
  documentId: target?.id,
11
+ resolutionPathVariant: target?.resolutionPathVariant,
9
12
  kind: target ? 'relative-source' : 'relative-missing'
10
13
  };
11
14
  }
@@ -13,6 +16,7 @@ export function resolveRelativeProjectModule(sourcePath, moduleSpecifier, docume
13
16
  export function resolveProjectModule(sourcePath, moduleSpecifier, documentsByPath, moduleResolution) {
14
17
  if (!sourcePath || !moduleSpecifier) return undefined;
15
18
  if (String(moduleSpecifier).startsWith('.')) return resolveRelativeProjectModule(sourcePath, moduleSpecifier, documentsByPath);
19
+ if (String(moduleSpecifier).startsWith('#')) return resolvePackageImportProjectModule(sourcePath, moduleSpecifier, documentsByPath, moduleResolution);
16
20
  return resolveConfiguredProjectModule(moduleSpecifier, documentsByPath, moduleResolution);
17
21
  }
18
22
 
@@ -68,12 +72,38 @@ function resolveConfiguredProjectModule(moduleSpecifier, documentsByPath, module
68
72
  for (const candidate of candidates) {
69
73
  const target = moduleTargetDocument(candidate.path, documentsByPath);
70
74
  const packageFields = packageResolutionFields(candidate, packageInfo);
71
- if (target) return { path: target.path, documentId: target.id, kind: `${candidate.kind}-source`, ...packageFields };
75
+ if (target) return { path: target.path, documentId: target.id, resolutionPathVariant: target.resolutionPathVariant, kind: `${candidate.kind}-source`, ...packageFields };
72
76
  firstMissing ??= { path: candidate.path, kind: `${candidate.kind}-missing`, ...packageFields };
73
77
  }
74
78
  return firstMissing ?? (packageInfo ? { kind: 'package-external', ...packageInfo } : undefined);
75
79
  }
76
80
 
81
+ function resolvePackageImportProjectModule(sourcePath, moduleSpecifier, documentsByPath, moduleResolution = {}) {
82
+ const packageContext = packageImportContext(sourcePath, moduleResolution);
83
+ const importsValue = packageContext.imports;
84
+ if (!importsValue) return { kind: 'package-import-external', packageImportKey: moduleSpecifier };
85
+ const match = packageImportMapValue(importsValue, moduleSpecifier);
86
+ if (!match) return { kind: 'package-import-external', packageImportKey: moduleSpecifier };
87
+ let firstMissing;
88
+ for (const target of exportTargetsForValue(match.value, packageConditions(moduleResolution))) {
89
+ const packageImportTarget = target.path;
90
+ if (!packageImportTarget || !String(packageImportTarget).startsWith('.')) {
91
+ return { kind: 'package-import-external', packageImportKey: match.key, packageImportCondition: target.condition, packageImportTarget };
92
+ }
93
+ const candidatePath = normalizeProjectPath(joinProjectPath(packageContext.root, packageImportTarget));
94
+ const resolved = moduleTargetDocument(candidatePath, documentsByPath);
95
+ const record = {
96
+ packageImportKey: match.key,
97
+ packageImportCondition: target.condition,
98
+ packageImportTarget,
99
+ packageName: packageContext.packageName
100
+ };
101
+ if (resolved) return { path: resolved.path, documentId: resolved.id, resolutionPathVariant: resolved.resolutionPathVariant, kind: 'package-import-source', ...record };
102
+ firstMissing ??= { path: candidatePath, kind: 'package-import-missing', ...record };
103
+ }
104
+ return firstMissing ?? { kind: 'package-import-external', packageImportKey: match.key };
105
+ }
106
+
77
107
  function configuredModuleCandidates(moduleSpecifier, moduleResolution = {}, packageInfo) {
78
108
  const compilerOptions = moduleResolution.compilerOptions ?? {};
79
109
  const baseUrl = normalizeProjectPath(moduleResolution.baseUrl ?? compilerOptions.baseUrl ?? '');
@@ -136,6 +166,31 @@ function packageExportTargets(exportsValue, subpath, conditions) {
136
166
  return exportTargetsForValue(exportMapValue(exportsValue, subpath), conditions);
137
167
  }
138
168
 
169
+ function packageImportMapValue(importsValue, moduleSpecifier) {
170
+ if (!isRecord(importsValue)) return undefined;
171
+ if (Object.prototype.hasOwnProperty.call(importsValue, moduleSpecifier)) return { key: moduleSpecifier, value: importsValue[moduleSpecifier] };
172
+ for (const [pattern, value] of Object.entries(importsValue)) {
173
+ const capture = patternCapture(moduleSpecifier, pattern);
174
+ if (capture !== undefined) return { key: pattern, value: replaceExportTargetCapture(value, capture) };
175
+ }
176
+ return undefined;
177
+ }
178
+
179
+ function packageImportContext(sourcePath, moduleResolution = {}) {
180
+ const packages = Object.entries(moduleResolution.packages ?? {})
181
+ .map(([packageName, options]) => ({
182
+ packageName,
183
+ root: normalizeProjectPath(options.root ?? ''),
184
+ imports: options.imports
185
+ }))
186
+ .filter((entry) => entry.imports && pathInsideRoot(sourcePath, entry.root))
187
+ .sort((left, right) => right.root.length - left.root.length);
188
+ return packages[0] ?? {
189
+ root: normalizeProjectPath(moduleResolution.packageRoot ?? moduleResolution.root ?? ''),
190
+ imports: moduleResolution.imports ?? moduleResolution.packageImports
191
+ };
192
+ }
193
+
139
194
  function exportMapValue(exportsValue, subpath) {
140
195
  if (!isRecord(exportsValue) || !Object.keys(exportsValue).some((key) => key.startsWith('.'))) return subpath === '.' ? exportsValue : undefined;
141
196
  return exportsValue[subpath] ?? patternExportMapValue(exportsValue, subpath);
@@ -172,16 +227,12 @@ function packageFallbackTargets(options, subpath) {
172
227
 
173
228
  function moduleTargetDocument(path, documentsByPath) {
174
229
  for (const candidate of modulePathCandidates(path)) {
175
- const document = documentsByPath.get(candidate);
176
- if (document) return document;
230
+ const document = documentsByPath.get(candidate.path);
231
+ if (document) return { ...document, resolutionPathVariant: candidate.variant };
177
232
  }
178
233
  return undefined;
179
234
  }
180
235
 
181
- function modulePathCandidates(path) {
182
- return [path, `${path}.js`, `${path}.ts`, `${path}.tsx`, `${path}.jsx`, `${path}.d.ts`, `${path}/index.js`, `${path}/index.ts`, `${path}/index.d.ts`];
183
- }
184
-
185
236
  function targetExportName(edge) {
186
237
  const name = edge.importedName ?? edge.localName ?? edge.exportedName;
187
238
  if (!name || name === '*') return undefined;
@@ -229,7 +280,10 @@ function packageResolutionFields(candidate, packageInfo) {
229
280
  return {
230
281
  packageName: candidate.packageName ?? packageInfo?.packageName,
231
282
  packageSubpath: candidate.packageSubpath ?? packageInfo?.packageSubpath,
232
- packageExportCondition: candidate.packageExportCondition
283
+ packageExportCondition: candidate.packageExportCondition,
284
+ packageImportKey: candidate.packageImportKey,
285
+ packageImportCondition: candidate.packageImportCondition,
286
+ packageImportTarget: candidate.packageImportTarget
233
287
  };
234
288
  }
235
289
 
@@ -250,3 +304,9 @@ function normalizeProjectPath(path) {
250
304
  }
251
305
  return parts.join('/');
252
306
  }
307
+
308
+ function pathInsideRoot(sourcePath, root) {
309
+ if (!root) return true;
310
+ const normalized = normalizeProjectPath(sourcePath);
311
+ return normalized === root || normalized.startsWith(`${root}/`);
312
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.116",
3
+ "version": "0.2.118",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",