@notjustcoders/ioc-arise
Version:
Arise type-safe IoC containers from your code. Zero overhead, zero coupling.
140 lines • 4.99 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TsConfigPathsResolver = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
class TsConfigPathsResolver {
constructor(startDir) {
this.baseUrl = null;
this.paths = {};
const tsConfigPath = this.findTsConfig((0, path_1.resolve)(startDir));
if (tsConfigPath) {
this.loadTsConfig(tsConfigPath);
}
}
findTsConfig(startDir) {
let dir = startDir;
while (true) {
const candidate = (0, path_1.join)(dir, 'tsconfig.json');
if ((0, fs_1.existsSync)(candidate))
return candidate;
const parent = (0, path_1.dirname)(dir);
if (parent === dir)
return null; // reached filesystem root
dir = parent;
}
}
/**
* Strips JSON comments while respecting string literals.
* A simple regex is not safe here because tsconfig path patterns like
* "@/*" contain "/*" which would be mis-detected as a block comment.
*/
stripJsonComments(raw) {
let result = '';
let i = 0;
while (i < raw.length) {
const ch = raw[i];
if (ch === '"') {
// Consume the entire string literal verbatim
result += ch;
i++;
while (i < raw.length) {
const c = raw[i];
if (c === '\\') {
result += c + (raw[i + 1] ?? '');
i += 2;
}
else {
result += c;
i++;
if (c === '"')
break;
}
}
}
else if (ch === '/' && raw[i + 1] === '/') {
// Line comment — skip to end of line
while (i < raw.length && raw[i] !== '\n')
i++;
}
else if (ch === '/' && raw[i + 1] === '*') {
// Block comment — skip to */
i += 2;
while (i < raw.length) {
if (raw[i] === '*' && raw[i + 1] === '/') {
i += 2;
break;
}
i++;
}
}
else {
result += ch;
i++;
}
}
return result;
}
loadTsConfig(tsConfigPath) {
try {
const raw = (0, fs_1.readFileSync)(tsConfigPath, 'utf-8');
const stripped = this.stripJsonComments(raw)
// Strip trailing commas before } or ]
.replace(/,(\s*[}\]])/g, '$1');
const config = JSON.parse(stripped);
const co = config.compilerOptions ?? {};
if (!co.paths)
return;
this.baseUrl = co.baseUrl
? (0, path_1.resolve)((0, path_1.dirname)(tsConfigPath), co.baseUrl)
: (0, path_1.dirname)(tsConfigPath);
this.paths = co.paths;
}
catch {
// Silently ignore unparseable tsconfig
}
}
/**
* Resolves a path-alias import to an absolute filesystem path.
* Returns null if the import is a regular relative import or no alias matches.
*/
resolveImportToAbsolute(importPath) {
if (!this.baseUrl || !importPath || importPath.startsWith('.'))
return null;
for (const [pattern, targets] of Object.entries(this.paths)) {
for (const target of targets) {
const resolved = this.tryMatch(importPath, pattern, target);
if (resolved)
return resolved;
}
}
return null;
}
tryMatch(importPath, pattern, target) {
if (pattern.endsWith('/*')) {
const prefix = pattern.slice(0, -2);
if (!importPath.startsWith(prefix + '/'))
return null;
const rest = importPath.slice(prefix.length + 1);
const targetBase = target.endsWith('/*') ? target.slice(0, -2) : target;
return this.findFile((0, path_1.join)(this.baseUrl, targetBase, rest));
}
// Exact pattern match
if (importPath !== pattern)
return null;
return this.findFile((0, path_1.join)(this.baseUrl, target));
}
findFile(basePath) {
const candidates = [basePath, basePath + '.ts', (0, path_1.join)(basePath, 'index.ts')];
for (const c of candidates) {
if ((0, fs_1.existsSync)(c))
return c;
}
return null;
}
hasPathAliases() {
return this.baseUrl !== null && Object.keys(this.paths).length > 0;
}
}
exports.TsConfigPathsResolver = TsConfigPathsResolver;
//# sourceMappingURL=tsConfigPathsResolver.js.map