UNPKG

typia

Version:

Superfast runtime validators with only one line

167 lines 7.94 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ImportTransformer = void 0; const path_1 = __importDefault(require("path")); const typescript_1 = __importDefault(require("typescript")); var ImportTransformer; (function (ImportTransformer) { ImportTransformer.transform = (props) => (context) => (file) => transform_file({ top: props.from, to: props.to, context, file, }); const transform_file = (props) => { if (props.file.isDeclarationFile) return props.file; const from = get_directory_path(path_1.default.resolve(props.file.getSourceFile().fileName)); const to = from.replace(props.top, props.to); // First pass: transform relative imports let transformedFile = typescript_1.default.visitEachChild(props.file, (node) => transform_node({ top: props.top, from, to, node, }), props.context); // Second pass: remove unused typia imports transformedFile = removeUnusedTypiaImports(transformedFile); return transformedFile; }; const transform_node = (props) => { if (!typescript_1.default.isImportDeclaration(props.node) || !typescript_1.default.isStringLiteral(props.node.moduleSpecifier)) return props.node; const text = props.node.moduleSpecifier.text; if (text[0] !== ".") return props.node; const location = path_1.default.resolve(props.from, text); if (location.indexOf(props.top) === 0) return props.node; const replaced = (() => { const simple = path_1.default .relative(props.to, location) .split(path_1.default.sep) .join("/"); return simple[0] === "." ? simple : `./${simple}`; })(); return typescript_1.default.factory.createImportDeclaration(undefined, props.node.importClause, typescript_1.default.factory.createStringLiteral(replaced), props.node.assertClause); }; })(ImportTransformer || (exports.ImportTransformer = ImportTransformer = {})); const get_directory_path = (file) => { const split = path_1.default.resolve(file).split(path_1.default.sep); split.pop(); return path_1.default.resolve(split.join(path_1.default.sep)); }; /** Remove unused typia imports that are only used in transformable calls */ const removeUnusedTypiaImports = (file) => { const imports = new Map(); for (const stmt of file.statements) { if (typescript_1.default.isImportDeclaration(stmt) === false || typescript_1.default.isStringLiteral(stmt.moduleSpecifier) === false || stmt.moduleSpecifier.text !== "typia" || stmt.importClause === undefined) continue; // Track default import (import typia from 'typia') if (stmt.importClause.name) imports.set(stmt.importClause.name.text, { declaration: stmt, default: true, }); // Track named imports (import { tags } from 'typia') - keep these if (stmt.importClause.namedBindings && typescript_1.default.isNamedImports(stmt.importClause.namedBindings)) for (const element of stmt.importClause.namedBindings.elements) imports.set(element.name.text, { declaration: stmt, default: false, }); } if (imports.size === 0) return file; // No typia imports to check // Find usage of typia identifiers that are NOT transformable calls const nonTransformableUsage = new Set(); const checkUsage = (node) => { if (typescript_1.default.isIdentifier(node) && imports.has(node.text)) { const identifier = node.text; // Check if this identifier is being used as part of a property access if (node.parent && typescript_1.default.isPropertyAccessExpression(node.parent) && node.parent.expression === node) { // This is typia.something - check if it's a transformable call pattern if (!isLikelyTransformableCall(node.parent)) nonTransformableUsage.add(identifier); } else // Direct usage of the typia identifier (not as property access) // This is definitely non-transformable usage nonTransformableUsage.add(identifier); } typescript_1.default.forEachChild(node, checkUsage); }; // Check all statements except import declarations for (const stmt of file.statements) if (!typescript_1.default.isImportDeclaration(stmt) || !typescript_1.default.isStringLiteral(stmt.moduleSpecifier) || stmt.moduleSpecifier.text !== "typia") checkUsage(stmt); // Update import statements const newStatements = file.statements .map((stmt) => { if (typescript_1.default.isImportDeclaration(stmt) && typescript_1.default.isStringLiteral(stmt.moduleSpecifier) && stmt.moduleSpecifier.text === "typia" && stmt.importClause) { const newImportClause = filterTypiaImportClause(stmt.importClause, nonTransformableUsage); if (newImportClause) return typescript_1.default.factory.createImportDeclaration(stmt.modifiers, newImportClause, stmt.moduleSpecifier, stmt.attributes); return null; // Skip adding the import if all imports are unused } return stmt; }) .filter((stmt) => stmt !== null); return typescript_1.default.factory.updateSourceFile(file, newStatements, file.isDeclarationFile, file.referencedFiles, file.typeReferenceDirectives, file.hasNoDefaultLib, file.libReferenceDirectives); }; /** * Check if a property access expression looks like a transformable typia call * This uses heuristics to detect patterns like typia.xxx(), * typia.namespace.xxx() */ const isLikelyTransformableCall = (node) => { // Check if this is eventually part of a call expression let current = node; // Walk up the chain to find if this leads to a call expression // Handle patterns like: typia.xxx(), typia.namespace.xxx() while (typescript_1.default.isPropertyAccessExpression(current)) { current = current.parent; } // If the final result is a call expression, this is likely transformable if (typescript_1.default.isCallExpression(current) && (current.expression === node || (typescript_1.default.isPropertyAccessExpression(current.expression) && isTypiaPropertyChain(current.expression)))) { return true; } return false; }; /** Check if a property access expression is part of a typia.xxx.yyy chain */ const isTypiaPropertyChain = (node) => { let current = node; while (typescript_1.default.isPropertyAccessExpression(current)) { current = current.expression; } return typescript_1.default.isIdentifier(current) && current.text === "typia"; }; /** Filter import clause to remove unused default imports */ const filterTypiaImportClause = (importClause, usedImports) => { const hasDefaultImport = importClause.name && usedImports.has(importClause.name.text); const namedBindings = importClause.namedBindings; // Always keep named bindings like { tags } // Return undefined if no imports are used if (!hasDefaultImport && !namedBindings) { return undefined; } return typescript_1.default.factory.createImportClause(importClause.isTypeOnly, hasDefaultImport ? importClause.name : undefined, namedBindings); }; //# sourceMappingURL=ImportTransformer.js.map