diff --git a/src/baseTranspiler.ts b/src/baseTranspiler.ts index 9d20361c..8b449739 100644 --- a/src/baseTranspiler.ts +++ b/src/baseTranspiler.ts @@ -190,6 +190,7 @@ class BaseTranspiler { uncamelcaseIdentifiers; asyncTranspiling; requiresReturnType; + supportVariableType; requiresParameterType; supportsFalsyOrTruthyValues; requiresCallExpressionCast; @@ -202,6 +203,7 @@ class BaseTranspiler { this.id = "base"; this.uncamelcaseIdentifiers = false; this.requiresReturnType = false; + this.supportVariableType = false; this.requiresParameterType = false; this.supportsFalsyOrTruthyValues = true; this.requiresCallExpressionCast = false; diff --git a/src/phpTranspiler.ts b/src/phpTranspiler.ts index a8628709..56fb04db 100644 --- a/src/phpTranspiler.ts +++ b/src/phpTranspiler.ts @@ -52,6 +52,7 @@ export class PhpTranspiler extends BaseTranspiler { super(config); this.id = "php"; this.asyncTranspiling = config['async'] ?? true; + this.supportVariableType = config['supportVariableType'] ?? false; this.uncamelcaseIdentifiers = config['uncamelcaseIdentifiers'] ?? false; this.removeVariableDeclarationForFunctionExpression = config['removeFunctionAssignToVariable'] ?? false; this.includeFunctionNameInFunctionExpressionDeclaration = config['includeFunctionNameInFunctionExpressionDeclaration'] ?? false; @@ -99,6 +100,20 @@ export class PhpTranspiler extends BaseTranspiler { return `'${identifier}'`; // Transpile function reference as string } } + // add type support to variable + if (this.supportVariableType) { + const typeRaw = global.checker.getTypeAtLocation(node); + let type = this.getTypeFromRawType(typeRaw); + if ( + ts.isPropertyDeclaration(valueDecl) || + (ts.isParameter(valueDecl) && ts.isParameter(node.parent)) + ) { + if (type === 'object' && typeRaw?.intrinsicName === 'number') { + type = 'float'; + } + return `${type} $${identifier}`; + } + } } // below is commented, due to : https://github.com/ccxt/ast-transpiler/pull/15 @@ -116,6 +131,13 @@ export class PhpTranspiler extends BaseTranspiler { return identifier; } + getTypeFromRawType(type) { + const typeValue = super.getTypeFromRawType(type); + if (typeValue === undefined && type?.symbol?.escapedName) { + return type.symbol.escapedName; + } + return typeValue; + } getCustomOperatorIfAny(left, right, operator) { const STRING_CONCAT = '.'; diff --git a/tests/integration/source/transpilable.ts b/tests/integration/source/transpilable.ts index b4f1203a..b4cc7b07 100644 --- a/tests/integration/source/transpilable.ts +++ b/tests/integration/source/transpilable.ts @@ -10,6 +10,10 @@ class Second { class Test { + strprop: string = "test"; + numprop: number = 1; + boolprop: boolean = false; + public functionWithOptionals(a: string, c: number | undefined = undefined, d = 1) { console.log(a); if (c !== undefined) { @@ -114,6 +118,11 @@ class Test { this.testJavaScope(); } + + someMethod(arg1: boolean, arg2: number) { + const res = arg1; + console.log(res); + } } export { diff --git a/tests/integration/test.ts b/tests/integration/test.ts index def2634a..0e22b7be 100644 --- a/tests/integration/test.ts +++ b/tests/integration/test.ts @@ -9,6 +9,8 @@ const PY_TRANSPILABLE_FILE = "./tests/integration/py/transpilable.py"; const PHP_TRANSPILABLE_FILE = "./tests/integration/php/transpilable.php"; const CS_TRANSPILABLE_FILE = "./tests/integration/cs/transpilable.cs"; const GO_TRANSPILABLE_FILE = "./tests/integration/go/transpilable.go"; + +const PHP_TRANSPILABLE_FILE_WITH_TYPES = "./tests/integration/php/transpilable_with_types.php"; const JAVA_TRANSPILABLE_FILE = "./tests/integration/java/app/src/main/java/org/example/Transpilable.java"; @@ -37,6 +39,13 @@ const langConfig = [ language: "go", async: true }, + { + language: "php", + async: true, + parser: { + supportVariableType: true + } + }, { language: "java", }, @@ -55,8 +64,12 @@ function transpileTests() { const transpiler = new Transpiler(parseConfig); const result = transpiler.transpileDifferentLanguagesByPath(langConfig as any, TS_TRANSPILABLE_FILE); - let phpRes = `` as string; - phpRes = (phpRes as any).replaceAll('var_dump', 'custom_echo'); + let phpResWrapper = (content) => { + const res = `` as string; + return (res as any).replaceAll('var_dump', 'custom_echo'); + }; + const phpRes = phpResWrapper(result[2].content); + const phpResWithTypes = phpResWrapper(result[4].content); const pythonAsync = result[1].content; let csharp = 'namespace tests;\n' + result[0].content; csharp = csharp.replace('class Test', 'partial class Test'); @@ -74,6 +87,7 @@ function transpileTests() { const go = 'package main\n' + goImports + result[3].content; writeFileSync(PHP_TRANSPILABLE_FILE, phpRes.toString()); + writeFileSync(PHP_TRANSPILABLE_FILE_WITH_TYPES, phpResWithTypes.toString()); writeFileSync(PY_TRANSPILABLE_FILE, pythonAsync); writeFileSync(CS_TRANSPILABLE_FILE, csharp); writeFileSync(GO_TRANSPILABLE_FILE, go); @@ -85,6 +99,8 @@ function runCommand(command) { exec(command, (error, stdout, stderr) => { if (stderr !== undefined || stderr !== null) { stderr = stderr.replace('Debugger attached.\nWaiting for the debugger to disconnect...\n', ''); + // fix for windows + stderr = stderr.replace('Debugger attached.\r','').replace('\nWaiting for the debugger to disconnect...\r\n', ''); } if (stderr.startsWith("Debugger listening") && stderr.includes("For help, see: https://nodejs.org/en/docs/inspector")) { stderr = undefined; diff --git a/tests/phpTranspiler.test.ts b/tests/phpTranspiler.test.ts index 4503c305..34b1d090 100644 --- a/tests/phpTranspiler.test.ts +++ b/tests/phpTranspiler.test.ts @@ -7,6 +7,8 @@ jest.mock('module',()=>({ })); let transpiler: Transpiler; +let transpilerWithType: Transpiler; + beforeAll(() => { const config = { @@ -18,6 +20,14 @@ beforeAll(() => { } } transpiler = new Transpiler(config); + + + const config2 ={ + 'php': { + 'supportVariableType': true + } + } + transpilerWithType = new Transpiler(config2); }) describe('php transpiling tests', () => { @@ -896,4 +906,40 @@ describe('php transpiling tests', () => { const output = transpiler.transpilePhp(ts).content; expect(output).toBe(result); }); + test('should support types', () => { + const nl = '\n'; + const ts = + 'class RefClass {}' + nl + + 'class BasicClass {' + nl + + ' public stringProp: string;' + nl + + ' public numProp: number;' + nl + + ' public boolProp: boolean;' + nl + + ' public refProp: RefClass;' + nl + + ' public constructor(arg1: string, arg2: number, arg3: boolean, arg4: RefClass) {' + nl + + ' this.stringProp = arg1;' + nl + + ' this.numProp = arg2;' + nl + + ' this.boolProp = arg3;' + nl + + ' this.refProp = arg4;' + nl + + ' }' + nl + + '}'; + const php = + 'class RefClass {' + nl + + '' + nl + + '}' + nl + + 'class BasicClass {' + nl + + ' public string $stringProp;' + nl + + ' public object $numProp;' + nl + + ' public bool $boolProp;' + nl + + ' public RefClass $refProp;' + nl + + '' + nl + + ' function __construct(string $arg1, object $arg2, bool $arg3, RefClass $arg4) {' + nl + + ' $this->stringProp = $arg1;' + nl + + ' $this->numProp = $arg2;' + nl + + ' $this->boolProp = $arg3;' + nl + + ' $this->refProp = $arg4;' + nl + + ' }' + nl + + '}' + nl; + const output = transpilerWithType.transpilePhp(ts).content; + expect(output).toBe(php); + }); });