Tree Transformations
Putout Script

🦎 PutoutScript

Among the maxims on Lord Naoshige’s wall there was this one: ”Matters of’ great concern should be treated lightly.” Master lttei commented, β€œMatters of small concern should be treated seriously.”

(c) Yamamoto Tsunetomo "Hagakure"

🦎PutoutScript β€” JavaScript-compatible language which adds additional meaning to Identifiers in AST-template. It is supported by all types of 🐊Putout plugins (opens in a new tab). Take a look at rule syntax (opens in a new tab) for more information.

☝️ In the command line, patterns are specified with a flag --transform.

Pattern matching

Pattern matching searches code for a given pattern. For example, the expression pattern say('hello 🐊') can match a full expression or be part of a subexpression:

loud(say('hello 🐊'))

In the same way, the statement pattern return __ can match a top statement in a function or any nested statement:

const when = () => {
    if (isTime())
        return 'now';
    
    return 'laiter';
};

__ template value

The double low dush template value (__) abstracts away Identifiers, Expressions and Literals.

__args template value

The __args template value abstracts away a sequence of zero or more arguments.

Function calls

Use the __args template value to search for function calls with arguments. For example, the pattern sey(__args) finds calls regardless of its arguments.

say('hello 🐊');

Method calls

The __args template value can also be used to search for method calls. For example, the pattern __object.say(__args) matches:

crocodile.say('hello 🐊');

The __ template value can also be used for the function name. Indeed, In some cases, you may want to match any function definitions: regular functions, methods, but also arrow functions. In that case you can use __ in place of the name of the function to match named or anonymous functions. For example, the pattern function __(__a) {} will match any function with one parameter:

function say(a) {
    return 'hello 🐊';
}
const talk = function(a) {
    return 'hello 🐊';
};

Strings

The "__a" template value can be used to search for strings containing any data. The pattern crocodile.say("__a") matches:

crocodile.say('hello 🐊');

This also works with constant propagation.

In languages where regular expressions use a special syntax (e.g., Javascript), the pattern /__a/ will match any regular expression construct:

const animalRegExp = /🐊|πŸ¦›/;

Binary operations

The __a can match arguments to binary operations. The pattern const __a = __b + __c matches:

const friends = '🐊' + 'πŸ¦›';

Containers

The __array and __object template values can match container data structures like, arrays, objects.

The pattern const friends = __array matches:

const friends = [
    '🐊',
    'πŸ¦›',
];

The pattern const animal = __object matches:

const animal = {
    '🐊': 'πŸ¦›',
};

Conditionals and loops

The __ can be used inside conditionals or loops. 🦎 PutoutScript:

if (__a)
    __b;

matches:

if (friends.includes('🐊'))
    return `🐊 friend of πŸ¦›`;

Template variables can match a conditional or loop body if the body statement information is re-used later. 🦎 PutoutScript:

if (__a)
    __body;

matches:

if (friends.includes('πŸ¦›'))
    return `πŸ¦› friend of 🐊`;

Template variables

Template variables are an abstraction to match code when you don’t know the value or contents ahead of time, similar to capture groups in regular expressions. Template variables can be used to track values across a specific code scope. This includes variables, functions, arguments, classes, object methods, imports and more.

Template variables look like __a, __b, etc. They begin with a __ and can only contain one character.

Expression template variable

🦎 PutoutScript __a + __b matches the following code examples:

'🐊' + 'πŸ“Ό';

Import template variable

Template variable __imports can be used to match imports. For example, import __imports matches:

import fs from 'fs/promises';

Reoccuring template variable

Re-using template variables shows their true power. Detect assignment of sum of duplicate nodes:

const __a = __b + __b;

Assignment of duplicate nodes sum detected:

const sum = 2 + 2;

Literal template variable

You can use "__a" to match any string literal. This is similar to using __a, but the content of the string is stored in the template variable __a, which can then be used later.

Typed template variable

Same thing works with TypeScript types, such pattern const __a: __b = __c, finds:

const answer: number = 42;

Statements and expressions

JavaScript differentiate between expressions and statements. Expressions can appear inside if conditions, in function call arguments, etc. Statements can not appear everywhere; they are sequence of operations (using ; as a separator/terminator) or special control flow constructs (if, while, etc.):

  • βœ… say() is an expression
  • βœ… say(); is a statement

When you write the expression foo() in a pattern, 🦎PutoutScript will visit every expression and sub-expression in your program and try to find a match.

Partial expressions

Partial expressions are not valid patterns. For example, the following is invalid:

'🐊' +

A complete expression is needed (like '🐊' + __a). Same with partial statements.

☝️ Find what you needed in this doc? Create an issue (opens in a new tab) if you need any help 😏!