rbxts-transformer-t-new-new
Version:
TypeScript transformer which converts TypeScript types to t entities
153 lines (129 loc) • 3.97 kB
text/typescript
import ts, { factory } from "typescript";
import path from "path";
import fs from "fs";
import * as transformerUtil from "./transformer";
import { buildType } from "./transformer";
export default function transformer(
program: ts.Program,
): ts.TransformerFactory<ts.SourceFile> {
return (context: ts.TransformationContext) => (file: ts.SourceFile) => {
const replaceIndexNode = (file: ts.SourceFile) => {
const importFilter = transformerUtil.is_t_ImportDeclaration(program);
return ts.factory.updateSourceFile(
file,
file.statements.filter((s) => !importFilter(s)),
file.isDeclarationFile,
file.referencedFiles,
file.typeReferenceDirectives,
file.hasNoDefaultLib,
file.libReferenceDirectives,
);
};
return visitNodeAndChildren(replaceIndexNode(file), program, context);
};
}
function visitNodeAndChildren(
node: ts.SourceFile,
program: ts.Program,
context: ts.TransformationContext,
): ts.SourceFile;
function visitNodeAndChildren(
node: ts.Node,
program: ts.Program,
context: ts.TransformationContext,
): ts.Node | undefined;
function visitNodeAndChildren(
node: ts.Node,
program: ts.Program,
context: ts.TransformationContext,
): ts.Node | undefined {
return ts.visitEachChild(
visitNode(node, program),
(childNode) => visitNodeAndChildren(childNode, program, context),
context,
);
}
function visitNode(node: ts.SourceFile, program: ts.Program): ts.SourceFile;
function visitNode(node: ts.Node, program: ts.Program): ts.Node | undefined;
function visitNode(
node: ts.Node,
program: ts.Program,
): ts.VisitResult<ts.Node> | undefined {
if (isModuleImportExpression(node, program)) {
return [
factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([
factory.createImportSpecifier(
false,
undefined,
factory.createIdentifier(transformerUtil.OBJECT_NAME),
),
]),
),
factory.createStringLiteral("@rbxts/t"),
),
node,
];
}
if (ts.isCallExpression(node)) return visitCallExpression(node, program);
return node;
}
function handleTerrifyCallExpression(
node: ts.CallExpression,
functionName: string,
typeChecker: ts.TypeChecker,
) {
switch (functionName) {
case transformerUtil.MARCO_NAME: {
const typeArguments = node.typeArguments;
if (typeArguments === undefined || typeArguments.length === 0)
throw new Error(`Please pass a type argument to the $terrify function`);
const type = typeChecker.getTypeFromTypeNode(typeArguments[0]);
return buildType(type, typeChecker);
}
default:
throw `function ${functionName} cannot be handled by this version of rbxts-interface-to-t`;
}
}
function visitCallExpression(node: ts.CallExpression, program: ts.Program) {
const typeChecker = program.getTypeChecker();
const signature = typeChecker.getResolvedSignature(node);
if (!signature) return node;
const { declaration } = signature;
if (
!declaration ||
ts.isJSDocSignature(declaration) ||
!isModule(declaration.getSourceFile())
)
return node;
const functionName = declaration.name && declaration.name.getText();
if (!functionName) return node;
return handleTerrifyCallExpression(node, functionName, typeChecker);
}
const sourceText = fs.readFileSync(
path.join(__dirname, "..", "index.d.ts"),
"utf8",
);
function isModule(sourceFile: ts.SourceFile) {
return sourceFile.text === sourceText;
}
function isModuleImportExpression(node: ts.Node, program: ts.Program) {
if (!ts.isImportDeclaration(node)) return false;
if (!node.importClause) return false;
const namedBindings = node.importClause.namedBindings;
if (!node.importClause.name && !namedBindings) return false;
const importSymbol = program
.getTypeChecker()
.getSymbolAtLocation(node.moduleSpecifier);
if (
!importSymbol ||
!isModule(importSymbol.valueDeclaration!.getSourceFile())
)
// TODO
return false;
return true;
}