Skip to content

Commit

Permalink
Introduce jsToPHPTranslator (WordPress#210)
Browse files Browse the repository at this point in the history
* Introduce jsToPHPTranslator

It solves the problem of escaping arguments when writing PHP code in JavaScript.

Before:
```js
const code = `define('WP_HOME', "${absoluteUrl}");`
// if absoluteUrl contains the '"' character, this code will break
```

After:
```js
const code = t.define('WP_HOME', absoluteUrl).toString();
// absoluteUrl is correctly escaped and can even be an array
// or an object
```

* Format
  • Loading branch information
adamziel authored Apr 18, 2023
1 parent 026f39d commit c015930
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/php-wasm/common/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type {
WithPHPIniBindings,
} from './php';

export { jsToPHPTranslator } from './js-to-php-translator';
export type { PHPResponse } from './php-response';
export type { ErrnoError } from './rethrow-file-system-error';

Expand Down
77 changes: 77 additions & 0 deletions packages/php-wasm/common/src/lib/js-to-php-translator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { jsToPHPTranslator } from './js-to-php-translator';

describe('PHP Translator', () => {
let t: ReturnType<typeof jsToPHPTranslator>;

beforeEach(() => {
t = jsToPHPTranslator();
});

test('translate function calls', () => {
const code = t.echo('Hello, World!');
expect(code + '').toBe('echo("Hello, World!")');
});

test('translate function calls with multiple arguments', () => {
const code = t.multiply(5, 3);
expect(code + '').toBe('multiply(5, 3)');
});

test('translate variable access', () => {
const code = t.$variable;
expect(code + '').toBe('$variable');
});

test('translate variable assignment', () => {
const code = t.assign(t.$variable, 42);
expect(code + '').toBe('assign($variable, 42)');
});

test('translate arrays and objects', () => {
const code = t.someFunction({ key: 'value' }, [1, 2, 3]);
expect(code + '').toBe(
'someFunction(array("key" => "value"), array(1, 2, 3))'
);
});

test('translate nested arrays and objects', () => {
const code = t.someFunction({ outer: { inner: 'value' } }, [
1,
['a', 'b'],
3,
]);
expect(code + '').toBe(
'someFunction(array("outer" => array("inner" => "value")), array(1, array("a", "b"), 3))'
);
});

test('translate composed function calls', () => {
const code = t.file_put_contents(t.get_path(), 'data');
expect(code + '').toBe('file_put_contents(get_path(), "data")');
});

test('translate multiple composed function calls', () => {
const code = t.operation(
t.first_function(),
t.second_function(t.$variable)
);
expect(code + '').toBe(
'operation(first_function(), second_function($variable))'
);
});

test('properly encode strings', () => {
const code = t.echo('Hello, "World!"');
expect(code + '').toBe('echo("Hello, \\"World!\\"")');
});

test('properly encode strings with special characters', () => {
const code = t.echo('Hello,\nWorld!');
expect(code + '').toBe('echo("Hello,\\nWorld!")');
});

test('properly encode strings with unicode characters', () => {
const code = t.echo('こんにちは');
expect(code + '').toBe('echo("こんにちは")');
});
});
82 changes: 82 additions & 0 deletions packages/php-wasm/common/src/lib/js-to-php-translator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const literal = Symbol('literal');

function jsToPhp(value: any): string {
if (typeof value === 'string') {
if (value.startsWith('$')) {
return value;
} else {
return JSON.stringify(value);
}
} else if (typeof value === 'number') {
return value.toString();
} else if (Array.isArray(value)) {
const phpArray = value.map(jsToPhp).join(', ');
return `array(${phpArray})`;
} else if (typeof value === 'object') {
if (literal in value) {
return value.toString();
} else {
const phpAssocArray = Object.entries(value)
.map(
([key, val]) => `${JSON.stringify(key)} => ${jsToPhp(val)}`
)
.join(', ');
return `array(${phpAssocArray})`;
}
} else if (typeof value === 'function') {
return value();
}
return '';
}

const handler: ProxyHandler<any> = {
get: (target, prop: string) => {
const result = function (...argumentsList: any[]) {
if (prop.startsWith('$')) {
return prop;
}
return {
[literal]: true,
toString() {
const args = argumentsList
.map((arg) => jsToPhp(arg))
.join(', ');
return `${prop}(${args})`;
},
};
};
result.toString = () => {
return jsToPhp(prop);
};
return result;
},
};

/**
* Creates a new JS to PHP translator.
*
* A translator is an object where PHP functions are accessible as properties.
*
* @example
* ```js
* const t = jsToPHPTranslator();
* const code = t.echo('Hello, World!');
* // code is echo("Hello, World!")
* ```
*
* @example
* ```js
* const t = jsToPHPTranslator();
* const absoluteUrl = 'http://example.com';
* const code = `
* ${t.define('WP_HOME', absoluteUrl)};
* ${t.define('WP_SITEURL', absoluteUrl)};
* `;
* // code is:
* // define("WP_HOME", "http://example.com");
* // define("WP_SITEURL", "http://example.com");
* ```
*/
export function jsToPHPTranslator() {
return new Proxy({}, handler);
}
3 changes: 2 additions & 1 deletion packages/php-wasm/common/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
"src/**/*.d.ts",
"src/lib/js-to-php-translator.ts"
]
}
1 change: 1 addition & 0 deletions packages/php-wasm/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ global.TextDecoder = TextDecoder as any;
export * from './lib';

export {
jsToPHPTranslator,
LatestSupportedPHPVersion,
PHPBrowser,
SupportedPHPVersions,
Expand Down
1 change: 1 addition & 0 deletions packages/php-wasm/web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export {
PHPBrowser,
exposeAPI,
consumeAPI,
jsToPHPTranslator,
SupportedPHPVersions,
SupportedPHPVersionsList,
LatestSupportedPHPVersion,
Expand Down

0 comments on commit c015930

Please sign in to comment.