Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
dist/
test/
tap-snapshots/
test/input/
6 changes: 3 additions & 3 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2018
"ecmaVersion": 2020
},
"env": {
"es6": true,
"node": true
},
"rules": {
"semi": 2,
"no-cond-assign": 0,
"no-process-env": 2,
"no-var": 2,
"comma-dangle": ["error", "never"],
"semi": [2, "always"],
"quotes": ["error", "double", {"allowTemplateLiterals": true}]
}
}
18 changes: 18 additions & 0 deletions .github/eslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"problemMatcher": [
{
"owner": "eslint-compact",
"pattern": [
{
"regexp": "^(.+):\\sline\\s(\\d+),\\scol\\s(\\d+),\\s(Error|Warning|Info)\\s-\\s(.+)\\s\\((.+)\\)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5,
"code": 6
}
]
}
]
}
30 changes: 17 additions & 13 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
name: Node CI
# https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

on: [push]
name: Node.js CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
os: [ubuntu-latest]
node-version: [10.x, 12.x]

runs-on: ${{ matrix.os }}
node-version: [14.x]

steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: yarn install and test
run: |
yarn install --frozen-lockfile
yarn test
env:
CI: true
- run: yarn --frozen-lockfile
- run: |
echo ::add-matcher::.github/eslint.json
yarn run eslint . --format=compact
- run: yarn test
6 changes: 1 addition & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
*.sublime-workspace
.DS_Store
.nyc_output
dist/
node_modules
npm-debug.log
.idea
node_modules/
10 changes: 2 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# @observablehq/parser

[![Node CI](https://github.com/observablehq/parser/workflows/Node%20CI/badge.svg)](https://github.com/observablehq/parser/actions?workflow=Node+CI)

To parse a cell:

```js
Expand Down Expand Up @@ -423,15 +421,11 @@ Injecting a view injects both the view symbol (`viewof foo`) and the value symbo

## API Reference

<a href="#parseCell" name="parseCell">#</a> <b>parseCell</b>(<i>input</i>[, <i>options</i>]) [<>](https://github.com/observablehq/parser/blob/master/src/parse.js "Source")
<a href="#parseCell" name="parseCell">#</a> <b>parseCell</b>(<i>input</i>[, <i>options</i>]) [<>](https://github.com/observablehq/parser/blob/main/src/parse.js "Source")

Returns a [cell](#cell).

<a href="#parseModule" name="parseModule">#</a> <b>parseModule</b>(<i>input</i>[, <i>options</i>]) [<>](https://github.com/observablehq/parser/blob/master/src/parse.js "Source")

Returns a [program](#program).

<a href="#peekId" name="peekId">#</a> <b>peekId</b>(<i>input</i>) [<>](https://github.com/observablehq/parser/blob/master/src/parse.js "Source")
<a href="#peekId" name="peekId">#</a> <b>peekId</b>(<i>input</i>) [<>](https://github.com/observablehq/parser/blob/main/src/peek.js "Source")

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

Expand Down
54 changes: 31 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,44 +1,52 @@
{
"name": "@observablehq/parser",
"description": "An Acorn parser for Observable JavaScript",
"version": "5.0.0",
"license": "ISC",
"main": "dist/parser.min.js",
"module": "src/index.js",
"author": {
"name": "Observable, Inc.",
"url": "https://observablehq.com"
},
"license": "ISC",
"type": "module",
"main": "src/index.js",
"module": "src/index.js",
"jsdelivr": "dist/parser.min.js",
"unpkg": "dist/parser.min.js",
"exports": {
"umd": "./dist/parser.min.js",
"default": "./src/index.js"
},
"repository": {
"type": "git",
"url": "https://github.com/observablehq/parser.git"
},
"files": [
"dist/**/*.js",
"src/**/*.js"
],
"engines": {
"node": ">=14.5.0"
},
"scripts": {
"test": "eslint . && tap 'test/**/*-test.js'",
"snapshot": "TAP_SNAPSHOT=1 tap 'test/**/*-test.js'",
"test": "mkdir -p test/output && mocha -r module-alias/register 'test/**/*-test.js' && eslint src test",
"prepublishOnly": "rm -rf dist && rollup -c",
"postpublish": "git push && git push --tags"
},
"husky": {
"hooks": {
"pre-commit": "yarn test"
}
"_moduleAliases": {
"@observablehq/parser": "./src/index.js"
},
"dependencies": {
"acorn": "^7.1.1",
"acorn-walk": "^7.0.0"
"acorn": "8",
"acorn-walk": "8"
},
"devDependencies": {
"eslint": "^6.7.2",
"esm": "^3.0.84",
"rollup": "^2.26.11",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.0.0",
"rollup-plugin-terser": "^7.0.2",
"tap": "^14.10.2",
"husky": "^3.1.0"
"eslint": "8",
"mocha": "9",
"module-alias": "2",
"rollup": "2",
"rollup-plugin-terser": "7"
},
"files": [
"dist/**/*.js",
"src/**/*.js"
]
"publishConfig": {
"access": "public"
}
}
24 changes: 0 additions & 24 deletions parser.sublime-project

This file was deleted.

4 changes: 0 additions & 4 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import commonjs from "rollup-plugin-commonjs";
import node from "rollup-plugin-node-resolve";
import {terser} from "rollup-plugin-terser";
import * as meta from "./package.json";

Expand All @@ -9,8 +7,6 @@ export default [
{
input: "src/index.js",
plugins: [
node(),
commonjs(),
terser({
output: {preamble: copyright},
mangle: {reserved: ["RequireError"]}
Expand Down
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export {parseCell, peekId, CellParser, TemplateCellParser, parseModule, ModuleParser} from "./parse.js";
export {parseCell, CellParser, TemplateCellParser} from "./parse.js";
export {peekId} from "./peek.js";
export {default as walk} from "./walk.js";
105 changes: 4 additions & 101 deletions src/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ const SCOPE_FUNCTION = 2;
const SCOPE_ASYNC = 4;
const SCOPE_GENERATOR = 8;

const STATE_START = Symbol("start");
const STATE_MODIFIER = Symbol("modifier");
const STATE_FUNCTION = Symbol("function");
const STATE_NAME = Symbol("name");

export function parseCell(input, {tag, raw, globals, ...options} = {}) {
let cell;
// Parse empty input as JavaScript to keep ensure resulting ast
Expand All @@ -31,79 +26,9 @@ export function parseCell(input, {tag, raw, globals, ...options} = {}) {
return cell;
}

/*
┌─────┐
┌───────────│START│─function|class
│ └─────┘ │
viewof|mutable|async │ ▼
│ │ ┌────────┐ ┌─┐
▼ │ │FUNCTION│◀───▶│*│
┌────────┐ │ └────────┘ └─┘
│MODIFIER│ │ │
└────────┘ name name
│ │ │
└──name─┐ │ ▼
▼ │ ┌─────────────┐
┌────────┐ │ │FUNCTION_NAME│
│ NAME │◀─┘ └─────────────┘
└────────┘
=
┌────────┐
│ EQ │
└────────┘
*/

export function peekId(input) {
let state = STATE_START;
let name;
try {
for (const token of Parser.tokenizer(input, {ecmaVersion: 11})) {
switch (state) {
case STATE_START:
case STATE_MODIFIER: {
if (token.type === tt.name) {
if (
state === STATE_START &&
(token.value === "viewof" ||
token.value === "mutable" ||
token.value === "async")
) {
state = STATE_MODIFIER;
continue;
}
state = STATE_NAME;
name = token;
continue;
}
if (token.type === tt._function || token.type === tt._class) {
state = STATE_FUNCTION;
continue;
}
break;
}
case STATE_NAME: {
if (token.type === tt.eq) return name.value;
break;
}
case STATE_FUNCTION: {
if (token.type === tt.star) continue;
if (token.type === tt.name && token.end < input.length)
return token.value;
break;
}
}
return;
}
} catch (ignore) {
return;
}
}

export class CellParser extends Parser {
constructor(options, ...args) {
super(Object.assign({ecmaVersion: 12}, options), ...args);
super(Object.assign({ecmaVersion: 13}, options), ...args);
}
enterScope(flags) {
if (flags & SCOPE_FUNCTION) ++this.O_function;
Expand Down Expand Up @@ -161,7 +86,7 @@ export class CellParser extends Parser {
} else {
node.local = node.imported;
}
this.checkLVal(node.local, "let");
this.checkLValSimple(node.local, "let");
if (identifiers.has(node.local.name)) {
this.raise(node.local.start, `Identifier '${node.local.name}' has already been declared`);
}
Expand Down Expand Up @@ -271,8 +196,8 @@ export class CellParser extends Parser {
}
return super.checkUnreserved(node);
}
checkLVal(expr, bindingType, checkClashes) {
return super.checkLVal(
checkLValSimple(expr, bindingType, checkClashes) {
return super.checkLValSimple(
expr.type === "MutableExpression" ? expr.id : expr,
bindingType,
checkClashes
Expand Down Expand Up @@ -367,28 +292,6 @@ function readTemplateToken() {
return this.finishToken(tt.invalidTemplate, this.input.slice(this.start, this.pos));
}

export function parseModule(input, {globals} = {}) {
const program = ModuleParser.parse(input);
for (const cell of program.cells) {
parseReferences(cell, input, globals);
parseFeatures(cell, input, globals);
}
return program;
}

export class ModuleParser extends CellParser {
parseTopLevel(node) {
if (!node.cells) node.cells = [];
while (this.type !== tt.eof) {
const cell = this.parseCell(this.startNode());
cell.input = this.input;
node.cells.push(cell);
}
this.next();
return this.finishNode(node, "Program");
}
}

export class CellTagParser extends Parser {
constructor(options, ...args) {
super(Object.assign({ecmaVersion: 12}, options), ...args);
Expand Down
Loading