|
| 1 | +import { SourceNode } from "source-map"; |
| 2 | +import * as ts from "typescript"; |
| 3 | +import * as tstl from ".."; |
| 4 | + |
| 5 | +function lualibFileVisitor(file: ts.SourceFile, context: tstl.TransformationContext): tstl.File { |
| 6 | + // Get all imports in file |
| 7 | + const imports = file.statements.filter(ts.isImportDeclaration); |
| 8 | + |
| 9 | + const importNames = new Set<string>(); |
| 10 | + for (const { importClause } of imports) { |
| 11 | + if (importClause?.namedBindings && ts.isNamedImports(importClause.namedBindings)) { |
| 12 | + for (const { name } of importClause.namedBindings.elements) { |
| 13 | + importNames.add(name.text); |
| 14 | + } |
| 15 | + } |
| 16 | + } |
| 17 | + |
| 18 | + // Transpile file as normal with tstl |
| 19 | + const fileResult = context.superTransformNode(file)[0] as tstl.File; |
| 20 | + |
| 21 | + // Find all exports assignments |
| 22 | + const exportInitializers = new Map<string, tstl.Expression>(); |
| 23 | + for (const s of fileResult.statements) { |
| 24 | + if (tstl.isAssignmentStatement(s) && isExportTableIndex(s.left[0])) { |
| 25 | + exportInitializers.set(s.left[0].index.value, s.right[0]); |
| 26 | + } |
| 27 | + } |
| 28 | + |
| 29 | + // Replace export aliases with initializers |
| 30 | + for (let i = 0; i < fileResult.statements.length; i++) { |
| 31 | + const statement = fileResult.statements[i]; |
| 32 | + if (isExportAlias(statement)) { |
| 33 | + const name = statement.left[0]; |
| 34 | + fileResult.statements[i] = tstl.createAssignmentStatement(name, exportInitializers.get(name.text)); |
| 35 | + } |
| 36 | + } |
| 37 | + |
| 38 | + // Filter out import/export statements |
| 39 | + const shouldIgnoreImportsExports = (node: tstl.Node) => |
| 40 | + !isExportTableDeclaration(node) && |
| 41 | + !isRequire(node) && |
| 42 | + !isImport(node, importNames) && |
| 43 | + !isExportAssignment(node) && |
| 44 | + !isExportsReturn(node); |
| 45 | + const filteredStatements = fileResult.statements.filter(shouldIgnoreImportsExports); |
| 46 | + |
| 47 | + if (filteredStatements.length > 0) { |
| 48 | + // If there are local statements, wrap them in a do ... end with exports outside |
| 49 | + const exports = tstl.createVariableDeclarationStatement([...exportInitializers.keys()].map(k => tstl.createIdentifier(k))); |
| 50 | + |
| 51 | + fileResult.statements = [exports, tstl.createDoStatement(filteredStatements)]; |
| 52 | + } else { |
| 53 | + const newStatements = []; |
| 54 | + for (const [exportName, initializer] of exportInitializers) { |
| 55 | + newStatements.push(tstl.createVariableDeclarationStatement(tstl.createIdentifier(exportName), initializer)); |
| 56 | + } |
| 57 | + fileResult.statements = newStatements; |
| 58 | + } |
| 59 | + |
| 60 | + return fileResult; |
| 61 | +} |
| 62 | + |
| 63 | +class LuaLibPrinter extends tstl.LuaPrinter { |
| 64 | + // Strip all exports during print |
| 65 | + public printTableIndexExpression(expression: tstl.TableIndexExpression): SourceNode { |
| 66 | + if (tstl.isIdentifier(expression.table) && expression.table.text === "____exports" && tstl.isStringLiteral(expression.index)) { |
| 67 | + return super.printExpression(tstl.createIdentifier(expression.index.value)); |
| 68 | + } |
| 69 | + return super.printTableIndexExpression(expression); |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +const plugin: tstl.Plugin = { |
| 74 | + visitors: { |
| 75 | + [ts.SyntaxKind.SourceFile]: lualibFileVisitor, |
| 76 | + }, |
| 77 | + printer: (program, emitHost, fileName, file) => new LuaLibPrinter(emitHost, program, fileName).print(file), |
| 78 | +}; |
| 79 | + |
| 80 | +// eslint-disable-next-line import/no-default-export |
| 81 | +export default plugin; |
| 82 | + |
| 83 | +function isExportTableDeclaration(node: tstl.Node): node is tstl.VariableDeclarationStatement & { left: [] } { |
| 84 | + return tstl.isVariableDeclarationStatement(node) && isExportTable(node.left[0]); |
| 85 | +} |
| 86 | + |
| 87 | +function isExportTable(node: tstl.Node): node is tstl.Identifier { |
| 88 | + return tstl.isIdentifier(node) && node.text === "____exports"; |
| 89 | +} |
| 90 | + |
| 91 | +function isExportTableIndex(node: tstl.Node): node is tstl.TableIndexExpression & { index: tstl.StringLiteral } { |
| 92 | + return tstl.isTableIndexExpression(node) && isExportTable(node.table) && tstl.isStringLiteral(node.index); |
| 93 | +} |
| 94 | + |
| 95 | +function isExportAlias(node: tstl.Node): node is tstl.VariableDeclarationStatement { |
| 96 | + return tstl.isVariableDeclarationStatement(node) && node.right !== undefined && isExportTableIndex(node.right[0]); |
| 97 | +} |
| 98 | + |
| 99 | +function isExportAssignment(node: tstl.Node) { |
| 100 | + return tstl.isAssignmentStatement(node) && isExportTableIndex(node.left[0]); |
| 101 | +} |
| 102 | + |
| 103 | +function isRequire(node: tstl.Node) { |
| 104 | + return ( |
| 105 | + tstl.isVariableDeclarationStatement(node) && |
| 106 | + node.right && |
| 107 | + tstl.isCallExpression(node.right[0]) && |
| 108 | + tstl.isIdentifier(node.right[0].expression) && |
| 109 | + node.right[0].expression.text === "require" |
| 110 | + ); |
| 111 | +} |
| 112 | + |
| 113 | +function isImport(node: tstl.Node, importNames: Set<string>) { |
| 114 | + return tstl.isVariableDeclarationStatement(node) && importNames.has(node.left[0].text); |
| 115 | +} |
| 116 | + |
| 117 | +function isExportsReturn(node: tstl.Node) { |
| 118 | + return ( |
| 119 | + tstl.isReturnStatement(node) && |
| 120 | + tstl.isIdentifier(node.expressions[0]) && |
| 121 | + node.expressions[0].text === "____exports" |
| 122 | + ); |
| 123 | +} |
0 commit comments