UNPKG

typia

Version:

Superfast runtime validators with only one line

165 lines (162 loc) 7.14 kB
import path from 'path'; import ts from '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.resolve(props.file.getSourceFile().fileName)); const to = from.replace(props.top, props.to); // First pass: transform relative imports let transformedFile = ts.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 (!ts.isImportDeclaration(props.node) || !ts.isStringLiteral(props.node.moduleSpecifier)) return props.node; const text = props.node.moduleSpecifier.text; if (text[0] !== ".") return props.node; const location = path.resolve(props.from, text); if (location.indexOf(props.top) === 0) return props.node; const replaced = (() => { const simple = path .relative(props.to, location) .split(path.sep) .join("/"); return simple[0] === "." ? simple : `./${simple}`; })(); return ts.factory.createImportDeclaration(undefined, props.node.importClause, ts.factory.createStringLiteral(replaced), props.node.assertClause); }; })(ImportTransformer || (ImportTransformer = {})); const get_directory_path = (file) => { const split = path.resolve(file).split(path.sep); split.pop(); return path.resolve(split.join(path.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 (ts.isImportDeclaration(stmt) === false || ts.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 && ts.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 (ts.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 && ts.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); } ts.forEachChild(node, checkUsage); }; // Check all statements except import declarations for (const stmt of file.statements) if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier) || stmt.moduleSpecifier.text !== "typia") checkUsage(stmt); // Update import statements const newStatements = file.statements .map((stmt) => { if (ts.isImportDeclaration(stmt) && ts.isStringLiteral(stmt.moduleSpecifier) && stmt.moduleSpecifier.text === "typia" && stmt.importClause) { const newImportClause = filterTypiaImportClause(stmt.importClause, nonTransformableUsage); if (newImportClause) return ts.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 ts.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 (ts.isPropertyAccessExpression(current)) { current = current.parent; } // If the final result is a call expression, this is likely transformable if (ts.isCallExpression(current) && (current.expression === node || (ts.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 (ts.isPropertyAccessExpression(current)) { current = current.expression; } return ts.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 ts.factory.createImportClause(importClause.isTypeOnly, hasDefaultImport ? importClause.name : undefined, namedBindings); }; export { ImportTransformer }; //# sourceMappingURL=ImportTransformer.mjs.map