@putout/printer
TypeScript icon, indicating that this package has built-in type declarations

11.11.0 • Public • Published

Printer License NPM version Build Status Coverage Status

Prints Babel AST to readable JavaScript. Use 🐊Putout to parse your code.

You may also use Babel 8 with estree-to-babel for ESTree and Babel AST to put .extra.raw to .raw (which is simpler for transforms, no need to use Optional Chaining and add extra values every time).

  • ☝️ Similar to Recast, but twice faster, also simpler and easier in maintenance, since it supports only Babel.
  • ☝️ As opinionated as Prettier, but has more user-friendly output and works directly with AST.
  • ☝️ Like ESLint but works directly with Babel AST.
  • ☝️ Easily extendable with help of Overrides.

Supports:

Install

npm i @putout/printer

🐊 Support of Printer

Printer has first class support from 🐊Putout with help of @putout/plugin-printer. So install:

npm i @putout/plugin-printer -aD

And update .putout.json:

{
    "printer": "putout",
    "plugins": ["printer"]
}

To benefit from it.

Example

const {print} = require('@putout/printer');
const {parse} = require('putout');
const ast = parse('const a = (b, c) => {const d = 5; return a;}');

print(ast);
// returns
`
const a = (b, c) => {
    const d = 5;
    return a;
};
`;

Overrides

When you need to extend syntax of @putout/printer just pass a function which receives:

  • path, Babel Path
  • print, a function to output result of printing into token array;

When path contains to dashes __ and name, it is the same as: write(path.get('right')), and this is actually traverse(path.get('right')) shortened to simplify read and process.

Here is how you can override AssignmentPattern:

const ast = parse('const {a = 5} = b');

print(ast, {
    format: {
        indent: '    ',
        newline: '\n',
        space: ' ',
        splitter: '\n',
        quote: `'`,
        endOfFile: '\n',
    },
    semantics: {
        comments: true,
        maxSpecifiersInOneLine: 2,
        maxElementsInOneLine: 3,
        maxLogicalsInOneLine: 3,
        maxVariablesInOneLine: 4,
        maxTypesInOneLine: 3,
        maxPropertiesInOneLine: 2,
        maxPropertiesLengthInOneLine: 15,
        trailingComma: true,
        encodeSingleQuote: true,
        encodeDoubleQuote: false,
        roundBraces: {
            arrow: true,
            sequence: true,
            assign: false,
            new: true,
        },
    },
    visitors: {
        AssignmentPattern(path, {print}) {
            print('/* [hello world] */= ');
            print('__right');
        },
    },
});

// returns
'const {a/* [hello world] */= 5} = b;\n';

format

Options related to visuals and not related to logic of output can be changed with help of format, you can override next options:

const overrides = {
    format: {
        indent: '    ',
        newline: '\n',
        space: ' ',
        splitter: '\n',
        endOfFile: '\n',
    },
};
  • indent - use two spaces, tabs, or anything you want;
  • newline - symbol used for line separation;
  • space - default symbol used for space character;
  • splitter - mandatory symbol that used inside of statements like this:

Default options produce:

if (a > 3)
    console.log('ok');
else
    console.log('not ok');

But you can override them with:

const overrides = {
    format: {
        indent: '',
        newline: '',
        space: '',
        splitter: ' ',
    },
};

And have minified code:

if(a>3)console.log('ok');else console.log('not ok');

Semantics

Options used to configure logic of output, similar to ESLint rules:

  • maxElementsInOneLine - count of ArrayExpression and ArrayPattern elements placed in one line.
  • maxLogicalsInOneLine - count of LogicalExpression elements placed in one line.
  • maxVariablesInOneLine - count of VariableDeclarators in one line.
  • maxPropertiesInOneLine - count of ObjectProperties in one line.
  • maxPropertiesLengthInOneLine - maximum length of Object Property, when violated splits event if maxPropertiesInOneLine satisfies;
  • roundBraces to output braces or not
    • arrow: In a single argument arrow function expressions enabled: (a) => {}, disabled: a => {};
    • sequence: In sequence expressions: enabled: for(let e of l) (a(), b()), disabled: for(let e of l) a(), b();
    • assign: In assignment expressions: enabled: (e.o=w(e.o), disabled: e.o=w(e.o);
    • new: In new expressions: enabled: new Date(), disabled: new Date;

Visitors API

When you want to improve support of existing visitor or extend Printer with a new ones, you need next base operations:

override

When you need to override behavior of existing visitor use:

import {
    print,
    visitors as v,
} from '@putout/printer';

print(ast, {
    visitors: {
        CallExpression(path, printer, semantics) {
            const {print} = printer;
            
            if (!path.node.goldstein)
                return v.CallExpression(path, printer, semantics);
            
            print('__goldstein');
        },
    },
});

print

Used in previous example print can be used for a couple purposes:

  • to write string;
  • to write node when object passed;
  • to write node when string started with __;
print(ast, {
    visitors: {
        AssignmentPattern(path, {print, maybe}) {
            maybe.write.newline(path.parentPath.isCallExpression());
            print('/* [hello world] */= ');
            print('__right');
        },
    },
});

maybe

When you need some condition use maybe. For example, to add newline only when parent node is CallExpression you can use maybe.write.newline(condition):

print(ast, {
    visitors: {
        AssignmentPattern(path, {write, maybe}) {
            maybe.write.newline(path.parentPath.isCallExpression());
            write(' /* [hello world] */= ');
            write('__right');
        },
    },
});

write

When you going to output string you can use low-level function write:

print(ast, {
    visitors: {
        BlockStatement(path, {write}) {
            write('hello');
        },
    },
});

indent

When you need to add indentation use indent, for example when you output body, you need to increment indentation, and then decrement it back:

print(ast, {
    visitors: {
        BlockStatement(path, {write, indent}) {
            write('{');
            indent.inc();
            indent();
            write('some;');
            indent.dec();
            write('{');
        },
    },
});

traverse

When you need to traverse node path, you can use traverse:

print(ast, {
    visitors: {
        AssignmentExpression(path, {traverse}) {
            traverse(path.get('left'));
        },
    },
});

This is the same as print('__left') but more low-level, and supports only objects.

Speed Comparison

About speed, for file speed.js:

const {readFileSync} = require('node:fs');

const putout = require('putout');
const parser = require('@babel/parser');

const code = readFileSync('./lib/tokenize/tokenize.js', 'utf8');
const ast = parser.parse(code);

speed('recast');
speed('putout');

function speed(printer) {
    console.time(printer);
    
    for (let i = 0; i < 1000; i++) {
        putout(code, {
            printer,
            plugins: ['remove-unused-variables'],
        });
    }
    
    console.timeEnd(printer);
}

With contents of tokenize.js, we have:

image

License

MIT

Package Sidebar

Install

npm i @putout/printer

Weekly Downloads

9,231

Version

11.11.0

License

MIT

Unpacked Size

244 kB

Total Files

145

Last publish

Collaborators

  • coderaiser