astring

repository·main·Indexed 23 days ago

https://github.com/davidbonnet/astring

A tiny and fast JavaScript code generator that produces code from ESTree-compliant Abstract Syntax Trees (ASTs). Version 1.9.0 supports modern ECMAScript versions and stage 3 proposals. It features a `generate()` function for rendering code, support for source maps, comment generation, and a customizable generator for extending node handlers. It also includes a CLI tool to convert JSON-formatted ASTs into JavaScript code.

Tokens
4.4K
Snippets
8
Records
18
Agent score
77%

What's inside astring

  1. Extend Astring with a custom generator

    main

    You can extend Astring by providing a custom generator in the options object. A generator is an object mapping node names to functions. Each function receives two arguments:

    1. node: The AST node to generate code from.
    2. state: An object exposing a write(string) method to append generated code.

    To inherit existing behavior, use Object.assign({}, astring.GENERATOR, { ... }) to create your custom generator.

    Example: Adding support for AwaitExpression

    var customGenerator = Object.assign({}, astring.GENERATOR, {
      AwaitExpression: function (node, state) {
        state.write('await ')
        var argument = node.argument
        if (argument != null) {
          this[argument.type](argument, state)
        }
      },
    })
    
    var ast = {
      type: 'AwaitExpression',
      argument: {
        type: 'CallExpression',
        callee: {
          type: 'Identifier',
          name: 'callable',
        },
        arguments: [],
      },
    }
    
    var code = astring.generate(ast, {
      generator: customGenerator,
    })
    console.log(code === 'await callable();\n' ? 'It works!' : 'Something went wrong!')
    // Make sure the astring module is imported and that `Object.assign` is defined
    
    // Create a custom generator that inherits from Astring's base generator
    var customGenerator = Object.assign({}, astring.GENERATOR, {
      AwaitExpression: function (node, state) {
        state.write('await ')
        var argument = node.argument
        if (argument != null) {
          this[argument.type](argument, state)
        }
      },
    })
    // Obtain a custom AST somehow (note that this AST is not obtained from a valid code)
    var ast = {
      type: 'AwaitExpression',
      argument: {
        type: 'CallExpression',
        callee: {
          type: 'Identifier',
          name: 'callable',
        },
        arguments: [],
      },
    }
    // Format it
    var code = astring.generate(ast, {
      generator: customGenerator,
    })
    // Check it
    console.log(
      code === 'await callable();\n' ? 'It works!' : 'Something went wrong!',
    )
  2. Import astring

    main

    Depending on your environment, use one of the following import methods:

    Deno

    const { generate } = await import('https://deno.land/x/astring/src/astring.js')

    JavaScript Modules (ESM)

    import { generate } from 'astring'

    CommonJS

    const { generate } = require('astring')

    Browser (via script tag)

    Include dist/astring.min.js in your HTML. The module exposes a global astring variable.

    <script src="astring.min.js" type="text/javascript"></script>
    <script type="text/javascript">
      var generate = astring.generate
    </script>
  3. Install astring

    main

    You can install astring using npm or JSR.

    Note: astring relies on String.prototype.repeat(amount) and String.prototype.endsWith(string). If your environment does not support these, you must provide polyfills like string.prototype.repeat, string.prototype.endsWith, or babel-polyfill.

    npm

    npm install astring

    JSR (Deno)

    deno add @davidbonnet/astring
  4. Customize code generation with a custom generator

    main

    Astring allows you to provide a custom generator object via the options in generate(). The generator object must implement methods corresponding to the ESTree node types (e.g., Program, VariableDeclaration, Identifier) with the signature (node, state). This allows you to override how specific node types are rendered.

    The state object passed to your generator methods provides:

    • state.write(text): Appends text to the output.
    • state.indent: The indentation string.
    • state.indentLevel: The current indentation level.
    • state.lineEnd: The line ending string.
    • state.expressionsPrecedence: The precedence map used for parenthesis logic.
  5. Understand the State object in generator functions

    main

    When implementing a custom Generator, each function receives a State object. This object tracks the current generation context and provides methods to write code.

    Properties:

    • output: The current output string.
    • write(code: string, node?: EstreeNode): Writes the provided code string to the output.
    • writeComments: boolean: Indicates if comments should be written.
    • indent: string: The indentation string.
    • lineEnd: string: The line ending string.
    • indentLevel: number: The current indentation level.
    • line?: number: Current line number.
    • column?: number: Current column number.
    • mapping?: Mapping: Current source map mapping.
  6. Generate source maps with generate()

    main

    To generate source maps, provide a sourceMap generator instance in the options object. Note that the AST nodes must include location information (e.g., via locations: true in your parser) for the mapping to work.

    Example

    var code = 'function add(a, b) { return a + b; }\n'
    var ast = acorn.parse(code, {
      ecmaVersion: 6,
      sourceType: 'module',
      locations: true,
    })
    
    var map = new sourceMap.SourceMapGenerator({
      file: 'script.js',
    })
    
    var formattedCode = generate(ast, {
      sourceMap: map,
    })
    
    console.log(map.toString())
    var code = 'function add(a, b) { return a + b; }\n'
    var ast = acorn.parse(code, {
      ecmaVersion: 6,
      sourceType: 'module',
      // Locations are needed in order for the source map generator to work
      locations: true,
    })
    // Create empty source map generator
    var map = new sourceMap.SourceMapGenerator({
      // Source file name must be set and will be used for mappings
      file: 'script.js',
    })
    var formattedCode = generate(ast, {
      // Enable source maps
      sourceMap: map,
    })
    // Display generated source map
    console.log(map.toString())
  7. Write rendered code to a writable stream

    main

    If you provide an output stream in the options object, generate() will write the rendered code directly to that stream and return the stream instance.

    Example (Node.js)

    var code = 'let answer = 4 + 7 * 5 + 3;\n'
    var ast = acorn.parse(code, { ecmaVersion: 6 })
    
    // Format it and write the result to stdout
    var stream = astring.generate(ast, {
      output: process.stdout,
    })
    
    // The returned value is the output stream
    console.log('Does stream equal process.stdout?', stream === process.stdout)
    // Make sure acorn and astring modules are imported
    
    // Set example code
    var code = 'let answer = 4 + 7 * 5 + 3;\n'
    // Parse it into an AST
    var ast = acorn.parse(code, { ecmaVersion: 6 })
    // Format it and write the result to stdout
    var stream = astring.generate(ast, {
      output: process.stdout,
    })
    // The returned value is the output stream
    console.log('Does stream equal process.stdout?', stream === process.stdout)
  8. Generate code with comments

    main

    Astring supports comment generation if comments are attached to the AST nodes. You can use a tool like Astravel to attach comments parsed from the source code to their corresponding nodes.

    Example

    var code =
      [
        '// Compute the answer to everything',
        'let answer = 4 + 7 * 5 + 3;',
        '// Display it',
        'console.log(answer);',
      ].join('\n') + '\n'
    
    var comments = []
    var ast = acorn.parse(code, {
      ecmaVersion: 6,
      locations: true,
      onComment: comments,
    })
    
    // Attach comments to AST nodes
    astravel.attachComments(ast, comments)
    
    // Format it with comments enabled
    var formattedCode = astring.generate(ast, {
      comments: true,
    })
    
    console.log(code === formattedCode ? 'It works!' : 'Something went wrong…')
    // Make sure acorn, astravel and astring modules are imported
    
    // Set example code
    var code =
      [
        '// Compute the answer to everything',
        'let answer = 4 + 7 * 5 + 3;',
        '// Display it',
        'console.log(answer);',
      ].join('\n') + '\n'
    // Parse it into an AST and retrieve the list of comments
    var comments = []
    var ast = acorn.parse(code, {
      ecmaVersion: 6,
      locations: true,
      onComment: comments,
    })
    // Attach comments to AST nodes
    astravel.attachComments(ast, comments)
    // Format it into a code string
    var formattedCode = astring.generate(ast, {
      comments: true,
    })
    // Check it
    console.log(code === formattedCode ? 'It works!' : 'Something went wrong…')
  9. Use generate() to render code from an AST

    main

    The generate(node, options) function returns a string representing the rendered code of the provided ESTree-compliant AST node. If an output stream is provided in the options, it writes to that stream and returns it.

    Options

    • indent: string to use for indentation (defaults to " ")
    • lineEnd: string to use for line endings (defaults to "\n")
    • startingIndentLevel: indent level to start from (defaults to 0)
    • comments: generate comments if true (defaults to false)
    • output: output stream to write the rendered code to (defaults to null)
    • generator: custom code generator (defaults to GENERATOR)
    • sourceMap: source map generator (defaults to null)
    • expressionsPrecedence: custom map of node types and their precedence level (defaults to EXPRESSIONS_PRECEDENCE)

    Example: Basic Generation

    // Using acorn to parse code into an AST
    var code = 'let answer = 4 + 7 * 5 + 3;\n'
    var ast = acorn.parse(code, { ecmaVersion: 6 })
    
    // Format it into a code string
    var formattedCode = astring.generate(ast)
    console.log(formattedCode)
    var code = 'let answer = 4 + 7 * 5 + 3;\n'
    var ast = acorn.parse(code, { ecmaVersion: 6 })
    var formattedCode = astring.generate(ast)
    console.log(code === formattedCode ? 'It works!' : 'Something went wrong…')
  10. Configure code generation with Options

    main

    The Options interface allows you to customize the output format of the generated code.

    Key options include:

    • indent: String used for indentation (defaults to "␣␣").
    • lineEnd: String used for line endings (defaults to "\n").
    • startingIndentLevel: The initial indentation level (defaults to 0).
    • comments: Boolean to enable or disable comment generation (defaults to false).
    • output: An optional Writable stream to write the rendered code to.
    • sourceMap: An optional SourceMapGenerator to include source mappings.
    • generator: A custom Generator object to override default node-type rendering logic.
  11. Use the astring CLI to convert AST JSON to code

    main

    The bin/astring utility converts JSON-formatted ESTree compliant ASTs into JavaScript code. It reads from files or stdin and prints to stdout.

    CLI Arguments

    • -i, --indent: string to use as indentation (defaults to " ")
    • -l, --line-end: string to use for line endings (defaults to "\n")
    • -s, --starting-indent-level: indent level to start from (defaults to 0)
    • -h, --help: print a usage message and exit
    • -v, --version: print package version and exit

    Usage Examples

    Pipe Acorn output directly to Astring:

    acorn --ecma6 script.js | astring > result.js

    Use an intermediary JSON file:

    acorn --ecma6 script.js > ast.json
    astring ast.json > result.js

    Read code from stdin and prettify:

    cat | acorn --ecma6 | astring
  12. Extend or override code generation with Generator

    main
    The Generator type is a mapping where each key is an Estree node type (e.g., Literal, BinaryExpression) and the value is a function that defines how to render that specific node. You can provide a custom Generator via the options.generator property in generate() to implement custom rendering logic for specific node types.