
Security News
Node.js TSC Votes to Stop Distributing Corepack
Corepack will be phased out from future Node.js releases following a TSC vote.
Transform PHP ASTs the easy way. Just as tasty as Yufka, but with different ingredients.
npm install --save gyros
IMPORTANT: Gyros is ESM-only. Read more.
Gyros is the ideal tool for programmatically making small & simple modifications to your PHP code in JavaScript.
As an introducing example, let's put a function wrapper around all array literals:
import { gyros } from 'gyros'
const source = `
$xs = [1, 2, [3, 4]];
$ys = [5, 6];
var_dump([$xs, $ys]);
`
const result = gyros(source, (node, { update, source }) => {
if (node.kind === 'array') {
update(`fun(${source()})`)
}
})
console.log(result.toString())
Output:
$xs = fun([1, 2, fun([3, 4])]);
$ys = fun([5, 6]);
var_dump(fun([$xs, $ys]));
function gyros(source, options = {}, manipulator)
Transform the string source
with the function manipulator
, returning an output object.
For every node in the AST, manipulator(node, helpers)
fires. The recursive walk is an
in-order traversal, so children get called before their parents. This makes it easier to write nested transforms since transforming parents often requires transforming their children first anyway.
The gyros()
return value is an object with two properties:
code
– contains the transformed source codemap
– holds the resulting source map object, as generated by magic-string
Calling .toString()
on a Gyros result object will return its source code
.
Pro Tip: Don't know how a PHP AST looks like? Have a look at astexplorer.net to get an idea.
All options are, as the name suggests, optional. If you want to provide an options object, its place is between the source
code and the manipulator
function.
There are two parse modes available: code
and eval
. The default is eval
.
The code
parse mode allows to parse PHP code as it appears "in the wild", i.e. with enclosing <?php
tags. The default eval
mode only parses pure PHP code, with no enclosing tags.
gyros('<!doctype html><?= "Hello World!" ?>', { parseMode: 'code' }, (node, helpers) => {
// Parse the `source` as mixed HTML/PHP code
})
Any options for the underlying php-parser
can be passed to options.phpParser
:
gyros(source, { phpParser: { parser: { suppressErrors: true } } }, (node, helpers) => {
// Parse the `source` in loose mode
})
Gyros uses magic-string
under the hood to generate source maps for your code modifications. You can pass its source map options as options.sourceMap
:
gyros(source, { sourceMap: { hires: true } }, (node, helpers) => {
// Create a high-resolution source map
})
The helpers
object passed to the manipulator
function exposes the following methods. All of these methods handle the current AST node (the one that's passed to the manipulator as its first argument).
However, all of these methods take an AST node as an optional first parameter if you want to access other nodes.
Example:
gyros('$x = 1', (node, { source }) => { if (node.kind === 'assign') { // `node` refers to the `$x = 1` Expression source() // returns "$x = 1" source(node.right) // returns "1" } })
source()
Return the source code for the given node, including any modifications made to child nodes:
gyros('(true)', (node, { source, update }) => {
if (node.kind === 'boolean') {
source() // returns "true"
update('false')
source() // returns "false"
}
})
update(replacement)
Replace the source of the affected node with the replacement
string:
const result = gyros('4 + 2', (node, { source, update }) => {
if (node.kind === 'bin') {
update(source(node.left) + source(node.right))
}
})
console.log(result.toString())
Output:
42
parent(levels = 1)
From the starting node, climb up the syntax tree levels
times. Getting an ancestor node of the program root yields undefined
.
gyros('$x = [1]', (node, { parent }) => {
if (node.kind === 'number') {
// `node` refers to the `1` number literal
parent() // same as parent(1), refers to the `1` as an array element
parent(2) // refers to the `[1]` expression
parent(3) // refers to the `$x = [1]` assignment expression
parent(4) // refers to the `$x = [1]` statement
parent(5) // refers to the program as a whole (root node)
parent(6) // yields `undefined`, same as parent(6), parent(7) etc.
}
})
Tip: If you want to extract manipulation behavior into standalone functions, you can access the helpers directly on the gyros
instance (e.g. gyros.source()
) where they are not bound to a specific node:
// Standalone function, increments node's value if it's a number
const increment = node => {
if (node.kind === 'number') {
gyros.update(node, String(Number(node.value) + 1))
}
}
const result = gyros('$x = 1', node => {
increment(node)
})
console.log(result.toString())
Output:
$x = 2
The manipulator
function may return a Promise. If it does, Gyros will wait for that to resolve, making the whole gyros()
function return a Promise resolving to the result object (instead of returning the result object directly):
import got from 'got' // see www.npmjs.com/package/got
const source = `
$content = curl("https://example.com")
`
const deferredResult = gyros(source, async (node, { source, update }) => {
if (node.kind === 'call' && node.what.kind === 'name' && node.what.name === 'curl') {
// Replace all curl() calls with their actual content
// Get the URL (will only work for simple string literals)
const url = node.arguments[0].value
// Fetch the URL's contents
const contents = (await got(url)).body
// Replace the cUrl() call with the fetched contents
update(JSON.stringify(contents))
}
})
// Result is not available immediately, we need to await it
deferredResult.then(result => {
console.log(result.toString())
})
Output:
$content = "<!doctype html>\n<html>\n[...]\n</html>"
Note: You have to return a promise if you want to commit updates asynchronously. Once the manipulator function is done running, any
update()
calls originating from it will throw an error.
FAQs
Transform PHP ASTs the easy way
We found that gyros demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Corepack will be phased out from future Node.js releases following a TSC vote.
Research
Security News
Research uncovers Black Basta's plans to exploit package registries for ransomware delivery alongside evidence of similar attacks already targeting open source ecosystems.
Security News
Oxlint's beta release introduces 500+ built-in linting rules while delivering twice the speed of previous versions, with future support planned for custom plugins and improved IDE integration.