Skip to content

observablehq/parser

Repository files navigation

@observablehq/parser

To parse a cell:

import {parseCell} from "@observablehq/parser";

const cell = parseCell(`hello = "world"`);

Examples

(In these examples, the node.start and node.end indexes into the input string are not shown for brevity. If options.locations is true, node.loc will also be populated with the line and column numbers.)

An expression cell (where cell.body is a type of expression):

1 + 2
{
  "type": "Cell",
  "id": null,
  "async": false,
  "generator": false,
  "body": {
    "type": "BinaryExpression",
    "left": {
      "type": "Literal",
      "value": 1,
      "raw": "1"
    },
    "operator": "+",
    "right": {
      "type": "Literal",
      "value": 2,
      "raw": "2"
    }
  }
}

A block cell (where cell.body is a BlockStatement):

{
  return 1 + 2;
}
{
  "type": "Cell",
  "id": null,
  "async": false,
  "generator": false,
  "body": {
    "type": "BlockStatement",
    "body": [
      {
        "type": "ReturnStatement",
        "argument": {
          "type": "BinaryExpression",
          "left": {
            "type": "Literal",
            "value": 1,
            "raw": "1"
          },
          "operator": "+",
          "right": {
            "type": "Literal",
            "value": 2,
            "raw": "2"
          }
        }
      }
    ]
  }
}

An empty cell (where cell.body is null):

{
  "type": "Cell",
  "id": null,
  "async": false,
  "generator": false,
  "body": null
}

A named expression cell (where cell.id is an Identifier):

foo = 42
{
  "type": "Cell",
  "id": {
    "type": "Identifier",
    "name": "foo"
  },
  "async": false,
  "generator": false,
  "body": {
    "type": "Literal",
    "value": 42,
    "raw": "42"
  }
}

A named block cell (where cell.id is an Identifier):

foo = {
  return 42;
}
{
  "type": "Cell",
  "id": {
    "type": "Identifier",
    "name": "foo"
  },
  "async": false,
  "generator": false,
  "body": {
    "type": "BlockStatement",
    "body": [
      {
        "type": "ReturnStatement",
        "argument": {
          "type": "Literal",
          "value": 42,
          "raw": "42"
        }
      }
    ]
  }
}

An asynchronous expression cell (where cell.async is true):

2 * await value
{
  "type": "Cell",
  "id": null,
  "async": true,
  "generator": false,
  "body": {
    "type": "BinaryExpression",
    "left": {
      "type": "Literal",
      "value": 2,
      "raw": "2"
    },
    "operator": "*",
    "right": {
      "type": "AwaitExpression",
      "argument": {
        "type": "Identifier",
        "name": "value"
      }
    }
  }
}

A generator expression cell (where cell.generator is true):

yield* [1, 2, 3]
{
  "type": "Cell",
  "id": null,
  "async": false,
  "generator": true,
  "body": {
    "type": "YieldExpression",
    "delegate": true,
    "argument": {
      "type": "ArrayExpression",
      "elements": [
        {
          "type": "Literal",
          "value": 1,
          "raw": "1"
        },
        {
          "type": "Literal",
          "value": 2,
          "raw": "2"
        },
        {
          "type": "Literal",
          "value": 3,
          "raw": "3"
        }
      ]
    }
  }
}

A viewof expression cell (where cell.id is a ViewExpression):

viewof x = DOM.range()
{
  "type": "Cell",
  "id": {
    "type": "ViewExpression",
    "id": {
      "type": "Identifier",
      "name": "x"
    }
  },
  "async": false,
  "generator": false,
  "body": {
    "type": "CallExpression",
    "callee": {
      "type": "MemberExpression",
      "object": {
        "type": "Identifier",
        "name": "DOM"
      },
      "property": {
        "type": "Identifier",
        "name": "range"
      },
      "computed": false
    },
    "arguments": []
  }
}

A viewof reference within an expression cell (where cell.body contains a ViewExpression):

viewof x.tagName
{
  "type": "Cell",
  "id": null,
  "async": false,
  "generator": false,
  "body": {
    "type": "MemberExpression",
    "object": {
      "type": "ViewExpression",
      "id": {
        "type": "Identifier",
        "name": "x"
      }
    },
    "property": {
      "type": "Identifier",
      "name": "tagName"
    },
    "computed": false
  }
}

An import cell (where cell.body is an ImportDeclaration):

import {foo} from "module"
{
  "type": "Cell",
  "id": null,
  "async": false,
  "generator": false,
  "body": {
    "type": "ImportDeclaration",
    "specifiers": [
      {
        "type": "ImportSpecifier",
        "view": false,
        "imported": {
          "type": "Identifier",
          "name": "foo"
        },
        "local": {
          "type": "Identifier",
          "name": "foo"
        }
      }
    ],
    "source": {
      "type": "Literal",
      "value": "module",
      "raw": "\"module\""
    }
  }
}

Importing a view (where specifier.view is true):

import {viewof foo} from "module"
{
  "type": "Cell",
  "id": null,
  "async": false,
  "generator": false,
  "body": {
    "type": "ImportDeclaration",
    "specifiers": [
      {
        "type": "ImportSpecifier",
        "view": true,
        "imported": {
          "type": "Identifier",
          "name": "foo"
        },
        "local": {
          "type": "Identifier",
          "name": "foo"
        }
      }
    ],
    "source": {
      "type": "Literal",
      "value": "module",
      "raw": "\"module\""
    }
  }
}

Importing a view imports both the view symbol (viewof foo) and the value symbol (foo). Likewise, if the specified view is renamed during import (e.g., viewof foo as bar), both the view symbol and the value symbol are renamed (e.g., viewof bar and bar).

Importing with injection (where declaration.injections is present):

import {chart} with {sales as data} from "@mbostock/d3-bar-chart"
{
  "type": "Cell",
  "id": null,
  "async": false,
  "generator": false,
  "body": {
    "type": "ImportDeclaration",
    "specifiers": [
      {
        "type": "ImportSpecifier",
        "view": false,
        "imported": {
          "type": "Identifier",
          "name": "chart"
        },
        "local": {
          "type": "Identifier",
          "name": "chart"
        }
      }
    ],
    "injections": [
      {
        "type": "ImportSpecifier",
        "view": false,
        "imported": {
          "type": "Identifier",
          "name": "sales"
        },
        "local": {
          "type": "Identifier",
          "name": "data"
        }
      }
    ],
    "source": {
      "type": "Literal",
      "value": "@mbostock/d3-bar-chart",
      "raw": "\"@mbostock/d3-bar-chart\""
    }
  }
}

For an injection, specifier.imported and specifier.local are reversed compared to a normal specifier: they are from the perspective of the imported module rather than the importing module. So in the example above, the importing module’s variable sales is injected into the imported module (the chart), replacing the variable data.

Injecting a view injects both the view symbol (viewof foo) and the value symbol (foo). Likewise, if the specified view is renamed during injection (e.g., viewof foo as bar), both the view symbol and the value symbol are renamed (e.g., viewof bar and bar).

API Reference

# parseCell(input[, options]) <>

Returns a cell.

# peekId(input) <>

Tries to find the ID of a cell given a snippet of its contents, and returns it as a string if found.

Cell

# cell.id

The name of the cell: null if the cell is anonymous; otherwise an Identifier or a ViewExpression.

# cell.body

The body of the cell: null for an empty cell; an ImportDeclaration for an import cell; otherwise a BlockStatement or an expression node.

# cell.async

A boolean indicating whether the cell body is asynchronous (i.e., whether it contains an await statement). False for import and empty cells.

# cell.generator

A boolean indicating whether the cell body is a generator (i.e., whether it contains a yield statement). False for import and empty cells.

ViewExpression

# view.id

The view identifier: an Identifier.

ImportDeclaration

# declaration.injections

An array of ImportSpecifier nodes, if the import declaration has a with clause, and otherwise null.

ImportSpecifier

# specifier.view

A boolean indicating whether the import specifies a view.