Files
bsky-social-app/plugins/lexiconBarrels.js
T
Oleksii Bulenok c26da684c9 refactor
2026-09-07 14:31:01 +02:00

95 lines
2.9 KiB
JavaScript

/*
* Shared mechanics for reading the generated lexicon barrels, used by
* babel-plugin-lexicon-leaf-imports and its test suites. The tests keep their
* own walking logic (an export-graph oracle vs the plugin's filesystem
* heuristic); only this mechanical layer - how a specifier resolves to a file
* and how a barrel parses into its re-export map - is shared, so the copies
* cannot drift apart.
*/
const fs = require('node:fs')
const parser = require('@babel/parser')
const path = require('node:path')
/** Extensions codegen emits: `.ts` for app sources, `.js` for the SDK dist. */
const EXTS = ['.ts', '.js']
const EXT_RE = new RegExp(`\\.(${EXTS.map(e => e.slice(1)).join('|')})$`)
/** Matches the SDK's compiled barrel entry, e.g. '../lexicons/index.js'. */
const SDK_BARREL_RE = /(^|\/)lexicons\/index\.js$/
/**
* Resolve a barrel/leaf specifier relative to the importing file: the exact
* path when it already carries a known extension, otherwise the first of
* EXTS that exists.
*
* @param {string} fromFile
* @param {string} spec
* @returns {string | null} absolute file path, or null
*/
function resolveModuleFile(fromFile, spec) {
const abs = path.resolve(path.dirname(fromFile), spec)
if (EXT_RE.test(abs) && fs.existsSync(abs)) return abs
for (const ext of EXTS) {
if (fs.existsSync(abs + ext)) return abs + ext
}
return null
}
/** Absolute barrel file -> Map(exported name -> source specifier), or null. */
const barrelExportCache = new Map()
/**
* Parse a barrel file into its namespace re-export map. Returns null if the
* file contains anything other than `export * as X from '...'` statements
* (plus type-only exports) - chains through such a file cannot be proven, so
* lookups fail and the caller bails.
*
* @param {string} file
* @returns {Map<string, string> | null}
*/
function barrelExports(file) {
let map = barrelExportCache.get(file)
if (map !== undefined) return map
map = new Map()
try {
const ast = parser.parse(fs.readFileSync(file, 'utf8'), {
sourceType: 'module',
plugins: ['typescript'],
})
for (const stmt of ast.program.body) {
if (stmt.exportKind === 'type') continue
if (
stmt.type !== 'ExportNamedDeclaration' ||
!stmt.source ||
stmt.declaration ||
stmt.specifiers.length === 0 ||
!stmt.specifiers.every(s => s.type === 'ExportNamespaceSpecifier')
) {
map = null
break
}
for (const s of stmt.specifiers) {
const name =
s.exported.type === 'Identifier' ? s.exported.name : s.exported.value
map.set(name, stmt.source.value)
}
}
} catch {
map = null
}
barrelExportCache.set(file, map)
return map
}
function clearBarrelExportCache() {
barrelExportCache.clear()
}
module.exports = {
EXTS,
SDK_BARREL_RE,
resolveModuleFile,
barrelExports,
clearBarrelExportCache,
}