├── .env ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Cargo.toml ├── README.md ├── benches ├── major_libs.rs └── major_libs_spanned.rs ├── code_of_conduct.md ├── codecov.yml ├── examples ├── angular.rs ├── future_work.rs ├── js_to_json-esprima.rs ├── js_to_json.rs ├── js_to_ron.rs ├── simple.js ├── simple.mjs ├── simple.rs ├── simple_builder.rs └── simple_module.rs ├── license.txt ├── package.json ├── scripts └── run_moz_central_test.sh ├── src ├── comment_handler.rs ├── error.rs ├── formal_params.rs ├── globals.rs ├── lexical_names.rs ├── lhs.rs ├── lib.rs ├── regex.rs └── spanned │ └── mod.rs └── tests ├── comment_handler.rs ├── everything_js.rs ├── libs_common.rs ├── major_libs.rs ├── snapshots ├── everything_js__es2015_module-10.snap ├── everything_js__es2015_module-2.snap ├── everything_js__es2015_module-3.snap ├── everything_js__es2015_module-4.snap ├── everything_js__es2015_module-5.snap ├── everything_js__es2015_module-6.snap ├── everything_js__es2015_module-7.snap ├── everything_js__es2015_module-8.snap ├── everything_js__es2015_module-9.snap ├── everything_js__es2015_module.snap ├── everything_js__es2015_script.snap └── everything_js__es5.snap ├── snippets.rs └── spider_monkey.rs /.env: -------------------------------------------------------------------------------- 1 | # required for the moz_central and test262 tests 2 | export RUST_MIN_STACK=9999999 3 | # writes failures to disk for additional inspection 4 | export RESSA_WRITE_FAILURES=1 5 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Setup Node.js for use with actions 13 | uses: actions/setup-node@v3 14 | - name: install js test libs from npm 15 | run: npm install 16 | - name: Build 17 | run: cargo build 18 | - name: curl moz_central 19 | run: curl https://hg.mozilla.org/mozilla-central/archive/tip.zip/js/src/jit-test/tests/ --output moz-central.zip 20 | - name: unzip moz-central 21 | run: unzip -qq moz-central.zip -d moz-central 22 | - name: Run tests 23 | run: cargo test --release --features=moz_central 24 | env: 25 | RUST_MIN_STACK: 9999999 26 | - name: Cache node_modules 27 | uses: actions/cache@v3 28 | with: 29 | path: ./node_modules 30 | key: ${{ runner.os }}.node_modules 31 | - name: before cargo cache 32 | run: rm -rf ~/.cargo/registry 33 | - name: Cache cargo directory 34 | uses: actions/cache@v3 35 | with: 36 | key: ${{ runner.os }}.cargo 37 | path: ~/.cargo 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | node_modules 5 | **/.DS_Store 6 | *.log 7 | *.rus 8 | *.js 9 | *.mjs 10 | *.json 11 | .vscode 12 | package-lock.json 13 | benchmark.md 14 | moz-central 15 | *.out 16 | *.log 17 | *.ron 18 | /test262 19 | /failures 20 | !tests/test262/hilight.js 21 | test262_full 22 | *.zip 23 | bench.sh 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | addons: 4 | apt: 5 | packages: 6 | - libssl-dev 7 | rust: 8 | - stable 9 | - nightly 10 | install: 11 | - npm i 12 | cache: 13 | - directories: 14 | - node_modules 15 | - /home/travis/.cargo 16 | - moz-central 17 | before_cache: 18 | - rm -rf /home/travis/.cargo/registry 19 | - | 20 | if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then 21 | bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) 22 | fi 23 | script: 24 | - cargo test --features moz_central 25 | after_success: | 26 | if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then 27 | cargo tarpaulin --out Xml && 28 | bash <(curl -s https://codecov.io/bash) 29 | fi 30 | matrix: 31 | allow_failures: 32 | - rust: nightly 33 | notifications: 34 | email: never 35 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you are interested in contributing, I would be super happy for the help. This is not my 4 | full time job so please be patient if I am slow to respond. 5 | 6 | If you are running into any issues, please open an issue before opening a pull request. 7 | 8 | If you want to start in on an existing issue, please make a comment on it so we don't have 9 | two people working on the same issue 10 | 11 | ## Testing 12 | 13 | ### Memory Issues 14 | 15 | The parsers defined here are recursive decent parsers, meaning they heavily rely on recursion 16 | which ends up being problematic for the stack size. For running tests it is recommended to use 17 | the environment variable `RUST_MIN_STACK` set to `9999999` (7 nines). Otherwise you will probably 18 | encounter the error: 19 | 20 | ```sh 21 | thread '' has overflowed its stack 22 | fatal runtime error: stack overflow 23 | error: test failed, to rerun pass `--test ` 24 | 25 | Caused by: 26 | process didn't exit successfully: `-` (signal: 6, SIGABRT: process abort signal) 27 | ``` 28 | 29 | [See this issue for more details](https://github.com/rusty-ecma/RESSA/issues/76) 30 | 31 | ### Extra Files 32 | 33 | There are a few sets of JavaScript files that are required to run the tests in this repository. 34 | 35 | #### NPM files 36 | 37 | This set can be easily acquired by running `npm install` in the root of this project. 38 | 39 | #### Spider Monkey Files 40 | 41 | An additional test is also available behind a feature flag `moz_central` that requires the JIT Test files from the FireFox repository, the expectation is that these will exist in the folder `moz-central` in the root of this project. To get these files you can either manually download and unzip them by following [this link](https://hg.mozilla.org/mozilla-central/archive/tip.zip/js/src/jit-test/tests/) or you can execute the following command. 42 | 43 | ```sh 44 | curl https://hg.mozilla.org/mozilla-central/archive/tip.zip/js/src/jit-test/tests/ --output moz-central.zip 45 | unzip -q moz-central.zip -d moz-central 46 | ``` 47 | 48 | To run these tests simply execute the following command. 49 | 50 | ```sh 51 | cargo test --features moz_central -- moz_central 52 | ``` 53 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ressa" 3 | version = "0.8.2" 4 | authors = ["Robert Masen "] 5 | repository = "https://github.com/rusty-ecma/RESSA" 6 | description = "An ECMAscript parser" 7 | license = "MIT" 8 | readme = "./README.md" 9 | keywords = ["JavaScript", "parsing", "JS", "ES", "ECMA"] 10 | categories = ["parsing", "text-processing", "web-programming"] 11 | edition = "2021" 12 | 13 | [dependencies] 14 | hash-chain = "0.3" 15 | log = "0.4" 16 | ress = "0.11" 17 | resast = "0.5" 18 | res-regex = "0.1" 19 | tracing = "0.1" 20 | 21 | [features] 22 | default = [] 23 | # This feature populates a field on the Parser `_look_ahead` that will contain a debug format 24 | # string of the look_ahead token. Very helpful when debugging this crate with gdb/lldb as sometimes 25 | # the property shape of the `Token` isn't formatted well 26 | debug_look_ahead = [] 27 | # This feature disables the moz_central tests by default as they tend to run long on most 28 | # development machines and require a larger minimum stack size to pass 29 | moz_central = [] 30 | 31 | [dev-dependencies] 32 | criterion = "0.4" 33 | docopt = "1" 34 | env_logger = "0.9" 35 | insta = "1.19" 36 | lazy_static = "1" 37 | serde = { version = "1", features = ["derive"] } 38 | serde_json = "1" 39 | serde_yaml = "0.9" 40 | term-painter = "0.3" 41 | walkdir = "2" 42 | 43 | [[bench]] 44 | name = "major_libs" 45 | harness = false 46 | 47 | [[example]] 48 | name = "js-to-json" 49 | path = "examples/js_to_json.rs" 50 | required-features = ["resast/serialization"] 51 | 52 | [[example]] 53 | name = "js-to-json-esprima" 54 | path = "examples/js_to_json-esprima.rs" 55 | required-features = ["resast/esprima"] 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RESSA 2 | 3 | [![Rust](https://github.com/rusty-ecma/RESSA/workflows/Rust/badge.svg?branch=featureless_test262)](https://github.com/rusty-ecma/RESSA/actions) 4 | 5 | [![crates.io](https://img.shields.io/crates/v/ressa.svg)](https://crates.io/crates/ressa) 6 | [![last commit master](https://img.shields.io/github/last-commit/FreeMasen/RESSA.svg)](https://github.com/FreeMasen/RESSA/commits/master) 7 | 8 | > Rust EcmaScript Syntax Analyzer 9 | 10 | This project is part of a series of crates designed to enable developers to create JavaScript development tools using the Rust programming language. [Rusty ECMA Details](#rusty-ecma-details) 11 | 12 | The two major pieces that users will interact with are the `Parser` struct and the enums defined by [`resast`](https://github.com/FreeMasen/resast) 13 | 14 | ## `Parser` 15 | 16 | The parser struct will be the main way to convert text into an `AST`. 17 | Conveniently `Parser` implements `Iterator` over `Result`, 18 | this means that you can evaluate your JS in pieces from top to bottom. 19 | 20 | > Note: By default the `Parser` will not be able to handle js module features, 21 | > [see the module example](./examples/simple_module.rs) for details on how to parse js modules 22 | 23 | ### Iterator Example 24 | 25 | ```rust 26 | use resast::prelude::*; 27 | use ressa::*; 28 | 29 | fn main() { 30 | let js = "function helloWorld() { alert('Hello world'); }"; 31 | let p = Parser::new(&js).unwrap(); 32 | let f = ProgramPart::decl(Decl::Func(Func { 33 | id: Some(Ident::from("helloWorld")), 34 | params: vec![], 35 | body: FuncBody(vec![ProgramPart::Stmt(Stmt::Expr(Expr::Call(CallExpr { 36 | callee: Box::new(Expr::ident_from("alert")), 37 | arguments: vec![Expr::Lit(Lit::String(StringLit::Single(Cow::Owned( 38 | "Hello world".to_string(), 39 | ))))], 40 | })))]), 41 | generator: false, 42 | is_async: false, 43 | })); 44 | for part in p { 45 | assert_eq!(part.unwrap(), f); 46 | } 47 | } 48 | ``` 49 | 50 | Another way to interact with a `Parser` would be to utilize the `parse` method. This method will iterate over all of the found `ProgramParts` and collect them into a `Program`, 51 | 52 | ### Parse Example 53 | 54 | ```rust 55 | use ressa::{ 56 | Parser, 57 | }; 58 | use resast::ref_tree::prelude::*; 59 | fn main() { 60 | let js = " 61 | function Thing() { 62 | return 'stuff'; 63 | } 64 | "; 65 | let mut parser = Parser::new(js).expect("Failed to create parser"); 66 | let program = parser.parse().expect("Unable to parse text"); 67 | match program { 68 | Program::Script(_parts) => println!("found a script"), 69 | Program::Mod(_parts) => println!("found an es6 module"), 70 | } 71 | } 72 | ``` 73 | Once you get to the inner `parts` of a `Program` you have a `Vec` which will operate the same as the [iterator example](#iterator-example) 74 | 75 | # Rusty ECMA Details 76 | 77 | ## The Rust ECMA Crates 78 | 79 | - [RESS](https://github.com/rusty-ecma/ress) - Tokenizer or Scanner 80 | - [RESSA](https://github.com/rusty-ecma/ressa) - Parser 81 | - [RESAST](https://github.com/rusty-ecma/resast) - AST 82 | - [RESW](https://github.com/rusty-ecma/resw) - Writer 83 | 84 | ## Why So Many? 85 | 86 | While much of what each crate provides is closely coupled with the other crates, the main goal is to provide the largest amount of customizability. For example, someone writing a fuzzer would only need the `RESAST` and `RESW`, it seems silly to require that they also pull in `RESS` and `RESSA` needlessly. 87 | -------------------------------------------------------------------------------- /benches/major_libs.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | // #![feature(test)] 3 | //! This benchmarking suite is extremely naive 4 | //! I use it internally to determine if I have 5 | //! been able to make large impact performance 6 | //! improvements in both this crate 7 | //! and `ress` 8 | //! If you want to full output please run 9 | //! `node ./bencher.js` in the crate root 10 | //! this will collect the results and 11 | //! build a table that will be written to 12 | //! benchmark.md 13 | //! This will include information about 14 | //! the parser overhead above the scanner 15 | //! and a naive comparison against 16 | //! [esprima](https://github.com/jquery/esprima) 17 | extern crate ress; 18 | extern crate ressa; 19 | // extern crate test; 20 | #[macro_use] 21 | extern crate lazy_static; 22 | 23 | #[macro_use] 24 | extern crate criterion; 25 | 26 | use std::{fs::read_to_string, path::PathBuf}; 27 | 28 | use criterion::{black_box, Criterion}; 29 | use ressa::Parser; 30 | 31 | lazy_static! { 32 | static ref NG: String = get_js(Lib::Angular).unwrap(); 33 | static ref NG_MIN: String = get_min_js(Lib::Angular).unwrap(); 34 | static ref JQ: String = get_js(Lib::Jquery).unwrap(); 35 | static ref JQ_MIN: String = get_min_js(Lib::Jquery).unwrap(); 36 | static ref REACT: String = get_js(Lib::React).unwrap(); 37 | static ref REACT_MIN: String = get_min_js(Lib::React).unwrap(); 38 | static ref REACT_DOM: String = get_js(Lib::ReactDom).unwrap(); 39 | static ref REACT_DOM_MIN: String = get_min_js(Lib::ReactDom).unwrap(); 40 | static ref VUE: String = get_js(Lib::Vue).unwrap(); 41 | static ref VUE_MIN: String = get_min_js(Lib::Vue).unwrap(); 42 | static ref EV5: String = get_js(Lib::Es5).unwrap(); 43 | static ref EV2015: String = get_js(Lib::Es2015S).unwrap(); 44 | static ref EVMOD: String = get_js(Lib::Es2015M).unwrap(); 45 | } 46 | 47 | fn angular1(c: &mut Criterion) { 48 | bench(c, &NG, "angular1", false); 49 | } 50 | 51 | fn angular1_min(c: &mut Criterion) { 52 | bench(c, &NG_MIN, "angular1_min", false); 53 | } 54 | 55 | fn jquery(c: &mut Criterion) { 56 | bench(c, &JQ, "jquery", false); 57 | } 58 | 59 | fn jquery_min(c: &mut Criterion) { 60 | bench(c, &JQ_MIN, "jquery_min", false); 61 | } 62 | 63 | fn react(c: &mut Criterion) { 64 | bench(c, &REACT, "react", false); 65 | } 66 | 67 | fn react_min(c: &mut Criterion) { 68 | bench(c, &REACT_MIN, "react_min", false); 69 | } 70 | 71 | fn react_dom(c: &mut Criterion) { 72 | bench(c, &REACT_DOM, "react_dom", false); 73 | } 74 | 75 | fn react_dom_min(c: &mut Criterion) { 76 | bench(c, &REACT_DOM_MIN, "react_dom_min", false); 77 | } 78 | 79 | fn vue(c: &mut Criterion) { 80 | bench(c, &VUE, "vue", false); 81 | } 82 | 83 | fn vue_min(c: &mut Criterion) { 84 | bench(c, &VUE_MIN, "vue_min", false); 85 | } 86 | 87 | fn es5(c: &mut Criterion) { 88 | bench(c, &EV5, "es5", false); 89 | } 90 | 91 | fn es2015(c: &mut Criterion) { 92 | bench(c, &EV2015, "es2015", false); 93 | } 94 | 95 | fn es_module(c: &mut Criterion) { 96 | bench(c, &EVMOD, "es_module", true); 97 | } 98 | 99 | fn bench(c: &mut Criterion, js: &str, name: &'static str, module: bool) { 100 | c.bench_function(name, |b| { 101 | b.iter(|| { 102 | let p = Parser::builder() 103 | .js(&js) 104 | .module(module) 105 | .build() 106 | .expect("Unable to crate new parser for es2015-module.js"); 107 | for i in p { 108 | match i { 109 | Ok(p) => { 110 | black_box(p); 111 | } 112 | Err(e) => { 113 | if let Some(text) = format_error(js, &e) { 114 | panic!("{}:\n{}", e, text); 115 | } else { 116 | panic!("{}", e); 117 | } 118 | } 119 | } 120 | } 121 | }) 122 | }); 123 | } 124 | 125 | fn format_error(js: &str, e: &ressa::Error) -> Option { 126 | let pos = e.position()?; 127 | let line_count = js.lines().count(); 128 | if line_count < 5 { 129 | return Some(js.to_string()); 130 | } 131 | let skip = pos.line.saturating_sub(2); 132 | Some( 133 | js.lines() 134 | .enumerate() 135 | .skip(skip) 136 | .take(5) 137 | .map(|(i, l)| { 138 | if i + 1 == pos.line { 139 | let whitespace = " ".repeat(pos.column); 140 | let tail_ct = l.len() - pos.column; 141 | let arrows = "^".repeat(tail_ct); 142 | format!("{}\n{}{}\n", l, whitespace, arrows) 143 | } else { 144 | format!("{}\n", l) 145 | } 146 | }) 147 | .collect(), 148 | ) 149 | } 150 | 151 | fn npm_install() -> Result<(), ::std::io::Error> { 152 | let mut c = ::std::process::Command::new("npm"); 153 | c.arg("i"); 154 | c.output()?; 155 | Ok(()) 156 | } 157 | 158 | enum Lib { 159 | Jquery, 160 | Angular, 161 | React, 162 | ReactDom, 163 | Vue, 164 | Es5, 165 | Es2015S, 166 | Es2015M, 167 | } 168 | 169 | impl Lib { 170 | pub fn path(&self) -> String { 171 | match self { 172 | Lib::Jquery => "node_modules/jquery/dist/jquery.js".into(), 173 | Lib::Angular => "node_modules/angular/angular.js".into(), 174 | Lib::React => "node_modules/react/umd/react.development.js".into(), 175 | Lib::ReactDom => "node_modules/react-dom/umd/react-dom.development.js".into(), 176 | Lib::Vue => "node_modules/vue/dist/vue.js".into(), 177 | Lib::Es5 => "node_modules/everything.js/es5.js".into(), 178 | Lib::Es2015S => "node_modules/everything.js/es2015-script.js".into(), 179 | Lib::Es2015M => "node_modules/everything.js/es2015-module.js".into(), 180 | } 181 | } 182 | 183 | pub fn min_path(&self) -> String { 184 | match self { 185 | Lib::Jquery => "node_modules/jquery/dist/jquery.min.js".into(), 186 | Lib::Angular => "node_modules/angular/angular.min.js".into(), 187 | Lib::React => "node_modules/react/umd/react.production.min.js".into(), 188 | Lib::ReactDom => "node_modules/react-dom/umd/react-dom.production.min.js".into(), 189 | Lib::Vue => "node_modules/vue/dist/vue.min.js".into(), 190 | _ => unreachable!(), 191 | } 192 | } 193 | } 194 | 195 | fn get_js(l: Lib) -> Result { 196 | let path = PathBuf::from(l.path()); 197 | if !path.exists() { 198 | npm_install()?; 199 | if !path.exists() { 200 | panic!("npm install failed to make {} available", path.display()); 201 | } 202 | } 203 | read_to_string(path) 204 | } 205 | 206 | fn get_min_js(l: Lib) -> Result { 207 | let path = PathBuf::from(l.min_path()); 208 | if !path.exists() { 209 | npm_install()?; 210 | if !path.exists() { 211 | panic!("npm install failed to make {} available", path.display()); 212 | } 213 | } 214 | read_to_string(path) 215 | } 216 | 217 | criterion_group!( 218 | benches, 219 | angular1, 220 | angular1_min, 221 | jquery, 222 | jquery_min, 223 | react, 224 | react_min, 225 | react_dom, 226 | react_dom_min, 227 | vue, 228 | vue_min, 229 | es5, 230 | es2015, 231 | es_module 232 | ); 233 | criterion_main!(benches); 234 | -------------------------------------------------------------------------------- /benches/major_libs_spanned.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | // #![feature(test)] 3 | //! This benchmarking suite is extremely naive 4 | //! I use it internally to determine if I have 5 | //! been able to make large impact performance 6 | //! improvements in both this crate 7 | //! and `ress` 8 | //! If you want to full output please run 9 | //! `node ./bencher.js` in the crate root 10 | //! this will collect the results and 11 | //! build a table that will be written to 12 | //! benchmark.md 13 | //! This will include information about 14 | //! the parser overhead above the scanner 15 | //! and a naive comparison against 16 | //! [esprima](https://github.com/jquery/esprima) 17 | extern crate ress; 18 | extern crate ressa; 19 | // extern crate test; 20 | #[macro_use] 21 | extern crate lazy_static; 22 | 23 | #[macro_use] 24 | extern crate criterion; 25 | 26 | use std::{fs::read_to_string, path::PathBuf}; 27 | 28 | use criterion::{black_box, Criterion}; 29 | use ressa::spanned::Parser; 30 | 31 | lazy_static! { 32 | static ref NG: String = get_js(Lib::Angular).unwrap(); 33 | static ref NG_MIN: String = get_min_js(Lib::Angular).unwrap(); 34 | static ref JQ: String = get_js(Lib::Jquery).unwrap(); 35 | static ref JQ_MIN: String = get_min_js(Lib::Jquery).unwrap(); 36 | static ref REACT: String = get_js(Lib::React).unwrap(); 37 | static ref REACT_MIN: String = get_min_js(Lib::React).unwrap(); 38 | static ref REACT_DOM: String = get_js(Lib::ReactDom).unwrap(); 39 | static ref REACT_DOM_MIN: String = get_min_js(Lib::ReactDom).unwrap(); 40 | static ref VUE: String = get_js(Lib::Vue).unwrap(); 41 | static ref VUE_MIN: String = get_min_js(Lib::Vue).unwrap(); 42 | static ref EV5: String = get_js(Lib::Es5).unwrap(); 43 | static ref EV2015: String = get_js(Lib::Es2015S).unwrap(); 44 | static ref EVMOD: String = get_js(Lib::Es2015M).unwrap(); 45 | } 46 | 47 | fn angular1(c: &mut Criterion) { 48 | bench(c, &NG, "angular1", false); 49 | } 50 | 51 | fn angular1_min(c: &mut Criterion) { 52 | bench(c, &NG_MIN, "angular1_min", false); 53 | } 54 | 55 | fn jquery(c: &mut Criterion) { 56 | bench(c, &JQ, "jquery", false); 57 | } 58 | 59 | fn jquery_min(c: &mut Criterion) { 60 | bench(c, &JQ_MIN, "jquery_min", false); 61 | } 62 | 63 | fn react(c: &mut Criterion) { 64 | bench(c, &REACT, "react", false); 65 | } 66 | 67 | fn react_min(c: &mut Criterion) { 68 | bench(c, &REACT_MIN, "react_min", false); 69 | } 70 | 71 | fn react_dom(c: &mut Criterion) { 72 | bench(c, &REACT_DOM, "react_dom", false); 73 | } 74 | 75 | fn react_dom_min(c: &mut Criterion) { 76 | bench(c, &REACT_DOM_MIN, "react_dom_min", false); 77 | } 78 | 79 | fn vue(c: &mut Criterion) { 80 | bench(c, &VUE, "vue", false); 81 | } 82 | 83 | fn vue_min(c: &mut Criterion) { 84 | bench(c, &VUE_MIN, "vue_min", false); 85 | } 86 | 87 | fn es5(c: &mut Criterion) { 88 | bench(c, &EV5, "es5", false); 89 | } 90 | 91 | fn es2015(c: &mut Criterion) { 92 | bench(c, &EV2015, "es2015", false); 93 | } 94 | 95 | fn es_module(c: &mut Criterion) { 96 | bench(c, &EVMOD, "es_module", true); 97 | } 98 | 99 | fn bench(c: &mut Criterion, js: &str, name: &'static str, module: bool) { 100 | c.bench_function(name, |b| { 101 | b.iter(|| { 102 | let p = Parser::builder() 103 | .js(&js) 104 | .module(module) 105 | .build() 106 | .expect("Unable to crate new parser for es2015-module.js"); 107 | for i in p { 108 | match i { 109 | Ok(p) => { 110 | black_box(p); 111 | } 112 | Err(e) => { 113 | if let Some(text) = format_error(js, &e) { 114 | panic!("{}:\n{}", e, text); 115 | } else { 116 | panic!("{}", e); 117 | } 118 | } 119 | } 120 | } 121 | }) 122 | }); 123 | } 124 | 125 | fn format_error(js: &str, e: &ressa::Error) -> Option { 126 | let pos = e.position()?; 127 | let line_count = js.lines().count(); 128 | if line_count < 5 { 129 | return Some(js.to_string()); 130 | } 131 | let skip = pos.line.saturating_sub(2); 132 | Some( 133 | js.lines() 134 | .enumerate() 135 | .skip(skip) 136 | .take(5) 137 | .map(|(i, l)| { 138 | if i + 1 == pos.line { 139 | let whitespace = " ".repeat(pos.column); 140 | let tail_ct = l.len() - pos.column; 141 | let arrows = "^".repeat(tail_ct); 142 | format!("{}\n{}{}\n", l, whitespace, arrows) 143 | } else { 144 | format!("{}\n", l) 145 | } 146 | }) 147 | .collect(), 148 | ) 149 | } 150 | 151 | fn npm_install() -> Result<(), ::std::io::Error> { 152 | let mut c = ::std::process::Command::new("npm"); 153 | c.arg("i"); 154 | c.output()?; 155 | Ok(()) 156 | } 157 | 158 | enum Lib { 159 | Jquery, 160 | Angular, 161 | React, 162 | ReactDom, 163 | Vue, 164 | Es5, 165 | Es2015S, 166 | Es2015M, 167 | } 168 | 169 | impl Lib { 170 | pub fn path(&self) -> String { 171 | match self { 172 | Lib::Jquery => "node_modules/jquery/dist/jquery.js".into(), 173 | Lib::Angular => "node_modules/angular/angular.js".into(), 174 | Lib::React => "node_modules/react/umd/react.development.js".into(), 175 | Lib::ReactDom => "node_modules/react-dom/umd/react-dom.development.js".into(), 176 | Lib::Vue => "node_modules/vue/dist/vue.js".into(), 177 | Lib::Es5 => "node_modules/everything.js/es5.js".into(), 178 | Lib::Es2015S => "node_modules/everything.js/es2015-script.js".into(), 179 | Lib::Es2015M => "node_modules/everything.js/es2015-module.js".into(), 180 | } 181 | } 182 | 183 | pub fn min_path(&self) -> String { 184 | match self { 185 | Lib::Jquery => "node_modules/jquery/dist/jquery.min.js".into(), 186 | Lib::Angular => "node_modules/angular/angular.min.js".into(), 187 | Lib::React => "node_modules/react/umd/react.production.min.js".into(), 188 | Lib::ReactDom => "node_modules/react-dom/umd/react-dom.production.min.js".into(), 189 | Lib::Vue => "node_modules/vue/dist/vue.min.js".into(), 190 | _ => unreachable!(), 191 | } 192 | } 193 | } 194 | 195 | fn get_js(l: Lib) -> Result { 196 | let path = PathBuf::from(l.path()); 197 | if !path.exists() { 198 | npm_install()?; 199 | if !path.exists() { 200 | panic!("npm install failed to make {} available", path.display()); 201 | } 202 | } 203 | read_to_string(path) 204 | } 205 | 206 | fn get_min_js(l: Lib) -> Result { 207 | let path = PathBuf::from(l.min_path()); 208 | if !path.exists() { 209 | npm_install()?; 210 | if !path.exists() { 211 | panic!("npm install failed to make {} available", path.display()); 212 | } 213 | } 214 | read_to_string(path) 215 | } 216 | 217 | criterion_group!( 218 | benches, 219 | angular1, 220 | angular1_min, 221 | jquery, 222 | jquery_min, 223 | react, 224 | react_min, 225 | react_dom, 226 | react_dom_min, 227 | vue, 228 | vue_min, 229 | es5, 230 | es2015, 231 | es_module 232 | ); 233 | criterion_main!(benches); 234 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | * Demonstrating empathy and kindness toward other people 14 | * Being respectful of differing opinions, viewpoints, and experiences 15 | * Giving and gracefully accepting constructive feedback 16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | * Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | * The use of sexualized language or imagery, and sexual attention or 22 | advances of any kind 23 | * Trolling, insulting or derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or email 26 | address, without their explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a 28 | professional setting 29 | 30 | ## Enforcement Responsibilities 31 | 32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 33 | 34 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 35 | 36 | ## Scope 37 | 38 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 39 | 40 | ## Enforcement 41 | 42 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. 43 | 44 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 45 | 46 | ## Enforcement Guidelines 47 | 48 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 49 | 50 | ### 1. Correction 51 | 52 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 53 | 54 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 55 | 56 | ### 2. Warning 57 | 58 | **Community Impact**: A violation through a single incident or series of actions. 59 | 60 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 61 | 62 | ### 3. Temporary Ban 63 | 64 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 65 | 66 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 67 | 68 | ### 4. Permanent Ban 69 | 70 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 71 | 72 | **Consequence**: A permanent ban from any sort of public interaction within the project community. 73 | 74 | ## Attribution 75 | 76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 77 | available at https://www.contributor-covenant.org/version/2/0/code-of-conduct.html. 78 | 79 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 80 | 81 | [homepage]: https://www.contributor-covenant.org 82 | 83 | For answers to common questions about this code of conduct, see the FAQ at 84 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 85 | 86 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | informational: true 6 | patch: 7 | default: 8 | enabled: no 9 | if_not_found: success 10 | -------------------------------------------------------------------------------- /examples/angular.rs: -------------------------------------------------------------------------------- 1 | extern crate ressa; 2 | 3 | use ressa::Parser; 4 | 5 | fn main() { 6 | let _ = env_logger::try_init(); 7 | let js = include_str!("../node_modules/angular/angular.js"); 8 | for i in 0..100 { 9 | log::debug!("starting pass {}", i); 10 | for item in Parser::new(js).unwrap() { 11 | let unwrapped = item.unwrap(); 12 | ::std::mem::forget(unwrapped); 13 | } 14 | log::debug!("ended pass {}", i); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/future_work.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::read_to_string, path::PathBuf}; 2 | 3 | use docopt::Docopt; 4 | use ress::prelude::*; 5 | use ressa::{CommentHandler, Parser, Span}; 6 | use serde::Deserialize; 7 | use term_painter::{Color, ToStyle}; 8 | use walkdir::WalkDir; 9 | 10 | static USAGE: &str = " 11 | Future Work 12 | A command line tool for evaluating TODO and FIXME comments 13 | in your javascript files 14 | 15 | Usage: 16 | future-work [options] 17 | future-work -h | --help 18 | 19 | Options: 20 | -h --help show this screen 21 | -d --dir PATH Evaluate all .js files in a directory 22 | -f --file PATH Evaluate only one file 23 | --no-color Do not color output 24 | "; 25 | #[derive(Deserialize)] 26 | struct Args { 27 | flag_no_color: bool, 28 | flag_dir: Option, 29 | flag_file: Option, 30 | } 31 | 32 | fn main() -> Result<(), Box> { 33 | let args: Args = Docopt::new(USAGE) 34 | .and_then(|o| o.deserialize()) 35 | .unwrap_or_else(|e| e.exit()); 36 | if args.flag_dir.is_none() && args.flag_file.is_none() { 37 | println!("{}", USAGE); 38 | return Ok(()); 39 | } 40 | print_header(!args.flag_no_color); 41 | if let Some(dir) = args.flag_dir { 42 | for entry in WalkDir::new(&dir) { 43 | let entry = entry?; 44 | if entry.file_name().to_string_lossy().ends_with(".js") { 45 | parse_file(&entry.into_path(), !args.flag_no_color)?; 46 | } 47 | } 48 | } 49 | if let Some(f) = args.flag_file { 50 | parse_file(&f, !args.flag_no_color)?; 51 | } 52 | Ok(()) 53 | } 54 | 55 | fn parse_file(path: &PathBuf, color: bool) -> Result<(), Box> { 56 | print_path(path, color); 57 | let js = get_js(path)?; 58 | let lines = find_lines(&js); 59 | let ch = WorkHandler { 60 | color, 61 | lines, 62 | counter: 0, 63 | }; 64 | let mut p = Parser::builder().js(&js).with_comment_handler(ch)?; 65 | let _ = p.parse()?; 66 | Ok(()) 67 | } 68 | 69 | fn get_js(path: &PathBuf) -> Result> { 70 | let text = read_to_string(&path)?; 71 | if text.starts_with("#!") { 72 | let mut lines = text.lines(); 73 | let _ = lines.next(); 74 | Ok(lines.collect::>().join("\n")) 75 | } else { 76 | Ok(text) 77 | } 78 | } 79 | 80 | fn print_header(color: bool) { 81 | let line1 = "▐▚▚▚▚▚▚▚▚▚▚▚▚▚▚▚▚▚▌"; 82 | let line2 = " FUTURE WORK "; 83 | if color { 84 | println!("{}", Color::BrightCyan.bold().paint(line1)); 85 | println!("{}", Color::BrightCyan.bold().paint(line2)); 86 | println!("{}", Color::BrightCyan.bold().paint(line1)); 87 | } else { 88 | println!("{}", line1); 89 | println!("{}", line2); 90 | println!("{}", line1); 91 | } 92 | } 93 | 94 | fn print_path(path: &PathBuf, color: bool) { 95 | let line1 = "═══════════════════"; 96 | let line2 = format!("{}", path.display()); 97 | if color { 98 | println!("{}", Color::BrightCyan.bold().paint(line1)); 99 | println!("{}", Color::BrightCyan.bold().paint(&line2)); 100 | println!("{}", Color::BrightCyan.bold().paint(line1)); 101 | } else { 102 | println!("{}", line1); 103 | println!("{}", line2); 104 | println!("{}", line1); 105 | } 106 | } 107 | 108 | enum FutureWork { 109 | FixMe(Span, String), 110 | ToDo(Span, String), 111 | } 112 | 113 | struct WorkHandler { 114 | color: bool, 115 | lines: Vec<(usize, usize)>, 116 | counter: usize, 117 | } 118 | 119 | impl Drop for WorkHandler { 120 | fn drop(&mut self) { 121 | if self.counter == 0 { 122 | let msg = "No future work here!"; 123 | if self.color { 124 | println!("{}", Color::Green.bold().paint(msg)); 125 | } else { 126 | println!("{}", msg); 127 | } 128 | } 129 | } 130 | } 131 | 132 | impl WorkHandler { 133 | fn report_work(&self, work: FutureWork) { 134 | match work { 135 | FutureWork::FixMe(span, msg) => { 136 | let line = self.get_line_number(&span); 137 | if self.color { 138 | println!("{}: ({}) {}", Color::Red.bold().paint("FIXME"), line, msg); 139 | } else { 140 | println!("{}: ({}) {}", "FIXME", line, msg); 141 | } 142 | } 143 | FutureWork::ToDo(span, msg) => { 144 | let line = self.get_line_number(&span); 145 | if self.color { 146 | println!( 147 | " {}: ({}) {}", 148 | Color::BrightCyan.bold().paint("TODO"), 149 | line, 150 | msg 151 | ); 152 | } else { 153 | println!(" {}: ({}) {}", "TODO", line, msg); 154 | } 155 | } 156 | } 157 | } 158 | 159 | fn get_line_number(&self, span: &Span) -> usize { 160 | fn find_line(lines: &[(usize, usize)], index: usize, span: &Span) -> usize { 161 | let current_len = lines.len(); 162 | if current_len == 1 { 163 | index + 1 164 | } else { 165 | let half = current_len >> 1; 166 | if lines[half - 1].1 + 1 >= span.start { 167 | find_line(&lines[..half], index, span) 168 | } else { 169 | find_line(&lines[half..], index + half, span) 170 | } 171 | } 172 | } 173 | find_line(&self.lines, 0, span) 174 | } 175 | } 176 | 177 | impl<'a> CommentHandler<'a> for WorkHandler { 178 | fn handle_comment(&mut self, comment: Item<&str>) { 179 | match comment.token { 180 | Token::Comment(c) => { 181 | if c.is_multi_line() { 182 | let mut counter = 0; 183 | for line in c.content.lines() { 184 | let span = Span::new(comment.span.start + counter, comment.span.end); 185 | if let Some(work) = parse_line(span, line) { 186 | self.counter += 1; 187 | self.report_work(work); 188 | } 189 | counter += line.len(); 190 | } 191 | } else { 192 | if let Some(work) = parse_line(comment.span, &c.content) { 193 | self.counter += 1; 194 | self.report_work(work); 195 | } 196 | } 197 | } 198 | _ => (), 199 | } 200 | } 201 | } 202 | 203 | fn parse_line(span: Span, line: &str) -> Option { 204 | let (todo, prefix) = if line.trim().starts_with("TODO") { 205 | (true, "TODO") 206 | } else if line.trim().starts_with("FIXME") { 207 | (false, "FIXME") 208 | } else { 209 | return None; 210 | }; 211 | let work = line.trim_start_matches(prefix); 212 | let work = work.trim_start_matches(":"); 213 | let work = work.trim_start().to_owned(); 214 | Some(if todo { 215 | FutureWork::ToDo(span, work) 216 | } else { 217 | FutureWork::FixMe(span, work) 218 | }) 219 | } 220 | 221 | fn find_lines(text: &str) -> Vec<(usize, usize)> { 222 | // Obviously we will start at 0 223 | let mut line_start = 0; 224 | // This is the byte position, not the character 225 | // position to account for multi byte chars 226 | let mut byte_position = 0; 227 | // loop over the characters 228 | let mut ret: Vec<(usize, usize)> = text 229 | .chars() 230 | .filter_map(|c| { 231 | let ret = match c { 232 | '\r' => { 233 | // look ahead 1 char to see if it is a newline pair 234 | // if so, don't include it, it will get included in the next 235 | // iteration 236 | if let Some(next) = text.get(byte_position..byte_position + 2) { 237 | if next == "\r\n" { 238 | None 239 | } else { 240 | let ret = (line_start, byte_position); 241 | line_start = byte_position + 1; 242 | Some(ret) 243 | } 244 | } else { 245 | None 246 | } 247 | } 248 | '\n' => { 249 | let ret = (line_start, byte_position); 250 | line_start = byte_position + 1; 251 | Some(ret) 252 | } 253 | '\u{2028}' | '\u{2029}' => { 254 | //These new line characters are both 3 bytes in length 255 | //that means we need to include this calculation in both 256 | // the end field and the next line start 257 | let ret = (line_start, byte_position + 2); 258 | line_start = byte_position + 3; 259 | Some(ret) 260 | } 261 | _ => None, 262 | }; 263 | // Since chars can be up to 4 bytes wide, we 264 | // want to move the full width of the current byte 265 | byte_position += c.len_utf8(); 266 | ret 267 | }) 268 | .collect(); 269 | // Since we shouldn't have only a new line char at EOF, 270 | // This will capture the last line of the text 271 | ret.push((line_start, text.len().saturating_sub(1))); 272 | ret 273 | } 274 | -------------------------------------------------------------------------------- /examples/js_to_json-esprima.rs: -------------------------------------------------------------------------------- 1 | use docopt::Docopt; 2 | use serde::Deserialize; 3 | use std::{ 4 | error::Error, 5 | ffi::OsStr, 6 | fs::{read_to_string, write}, 7 | path::PathBuf, 8 | }; 9 | 10 | use ressa::Parser; 11 | 12 | static USAGE: &str = " 13 | js_to_json 14 | 15 | Usage: 16 | js_to_json [options] 17 | js_to_json -h | --help 18 | 19 | Options: 20 | -h --help show this screen 21 | -p --pretty prettify json output 22 | -o --out PATH Where to save the json 23 | "; 24 | 25 | #[derive(Deserialize)] 26 | struct Args { 27 | arg_path: PathBuf, 28 | flag_pretty: bool, 29 | flag_out: Option, 30 | } 31 | 32 | fn main() -> Result<(), Box> { 33 | let args: Args = Docopt::new(USAGE) 34 | .and_then(|o| o.deserialize()) 35 | .unwrap_or_else(|e| e.exit()); 36 | if args.arg_path.exists() { 37 | let json = gen_json(args.arg_path, args.flag_pretty)?; 38 | if let Some(mut out) = args.flag_out { 39 | if out.is_dir() { 40 | out.push("ressa.json"); 41 | } 42 | write(out, json.as_bytes())?; 43 | } else { 44 | println!("{}", json); 45 | } 46 | } else { 47 | eprintln!("{}", USAGE); 48 | } 49 | Ok(()) 50 | } 51 | 52 | fn gen_json(from: PathBuf, pretty: bool) -> Result> { 53 | let js = read_to_string(&from)?; 54 | let mut p = Parser::builder() 55 | .js(&js) 56 | .module(from.extension() == Some(&OsStr::new(".mjs"))) 57 | .build()?; 58 | let ast = p.parse()?; 59 | let ret = if pretty { 60 | serde_json::to_string_pretty(&ast)? 61 | } else { 62 | serde_json::to_string(&ast)? 63 | }; 64 | Ok(ret) 65 | } 66 | -------------------------------------------------------------------------------- /examples/js_to_json.rs: -------------------------------------------------------------------------------- 1 | use docopt::Docopt; 2 | use serde::Deserialize; 3 | use std::{ 4 | error::Error, 5 | ffi::OsStr, 6 | fs::{read_to_string, write}, 7 | path::PathBuf, 8 | }; 9 | 10 | use ressa::Parser; 11 | 12 | static USAGE: &str = " 13 | js_to_json 14 | 15 | Usage: 16 | js_to_json [options] 17 | js_to_json -h | --help 18 | 19 | Options: 20 | -h --help show this screen 21 | -p --pretty prettify json output 22 | -o --out PATH Where to save the json 23 | "; 24 | 25 | #[derive(Deserialize)] 26 | struct Args { 27 | arg_path: PathBuf, 28 | flag_pretty: bool, 29 | flag_out: Option, 30 | } 31 | 32 | fn main() -> Result<(), Box> { 33 | let args: Args = Docopt::new(USAGE) 34 | .and_then(|o| o.deserialize()) 35 | .unwrap_or_else(|e| e.exit()); 36 | if args.arg_path.exists() { 37 | let json = gen_json(args.arg_path, args.flag_pretty)?; 38 | if let Some(mut out) = args.flag_out { 39 | if out.is_dir() { 40 | out.push("ressa.json"); 41 | } 42 | write(out, json.as_bytes())?; 43 | } else { 44 | println!("{}", json); 45 | } 46 | } else { 47 | eprintln!("{}", USAGE); 48 | } 49 | Ok(()) 50 | } 51 | 52 | fn gen_json(from: PathBuf, pretty: bool) -> Result> { 53 | let js = read_to_string(&from)?; 54 | let mut p = Parser::builder() 55 | .js(&js) 56 | .module(from.extension() == Some(&OsStr::new(".mjs"))) 57 | .build()?; 58 | let ast = p.parse()?; 59 | let ret = if pretty { 60 | serde_json::to_string_pretty(&ast)? 61 | } else { 62 | serde_json::to_string(&ast)? 63 | }; 64 | Ok(ret) 65 | } 66 | -------------------------------------------------------------------------------- /examples/js_to_ron.rs: -------------------------------------------------------------------------------- 1 | extern crate ressa; 2 | use ressa::*; 3 | 4 | fn main() { 5 | let _ = env_logger::try_init(); 6 | let mut args = ::std::env::args(); 7 | let _ = args.next().unwrap(); 8 | let path = args.next().expect("One argument required"); 9 | let js = ::std::fs::read_to_string(path).expect("Failed to read path"); 10 | let module = if let Some(flag) = args.next() { 11 | flag.ends_with("m") || flag.ends_with("module") 12 | } else { 13 | false 14 | }; 15 | let mut p = Parser::builder().module(module).js(&js).build().unwrap(); 16 | let ast = p.parse().unwrap(); 17 | println!("{:#?}", ast); 18 | } 19 | -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | const cp = require('child_process'); 2 | const fs = require('fs'); 3 | const prog = require('progress'); 4 | 5 | 6 | function dd(infile, outfile, bytesize) { 7 | console.log('Getting started'); 8 | var bar; 9 | var currentBytes; 10 | 11 | fs.stat(infile, function(err, stat) { 12 | if (err) return console.error('Unable to get infile stats', err.message); 13 | console.log(`moving \n\t${infile}`); 14 | console.log(`to \n\t${outfile}`); 15 | var inFileSize = stat.size; 16 | bar = new prog('Progress [:bar] :percent :current :total', 17 | { 18 | total: inFileSize, 19 | complete: '‡', 20 | incomplete: ' ' 21 | }); 22 | 23 | var dd = cp.spawn('dd', [`if=${infile}`, `of=${outfile}`, `bs=${bytesize || '1m'}`]); 24 | var interval = setInterval(function() { 25 | if (bar.complete) { 26 | clearInterval(interval) 27 | console.log('Finishing up'); 28 | } else { 29 | dd.kill('SIGINFO'); 30 | } 31 | }, 100); 32 | dd.addListener('exit', function(code, sig) { 33 | if (code == 0) { 34 | bar.tick(bar.total - bar.curr); 35 | console.log('Complete'); 36 | process.exit(); 37 | } else { 38 | console.log(`Exit with code ${code}: ${sig}`); 39 | process.exit(); 40 | } 41 | }); 42 | // TODO: Add color formatting 43 | dd.stderr.on('data', function(data) { 44 | console.log('dd.stderr.on("data", ' + data); 45 | if (typeof data != 'string') data = data.toString('utf8'); 46 | var status = parse(data); 47 | var update; 48 | if (status) { 49 | update = status - currentBytes; 50 | currentBytes = status; 51 | if (!bar.complete) bar.tick(update); 52 | } 53 | }); 54 | }); 55 | } 56 | 57 | function parse(text) { 58 | var lines = text.split('\n') 59 | var line = lines[2] 60 | if (!line) { 61 | line = lines[0] 62 | } 63 | var words = line.split(' ') 64 | return Number.parseInt(words[0]) 65 | } 66 | 67 | var ifile; 68 | var ofile; 69 | var bs; 70 | 71 | if (process.argv[2]) { 72 | ifile = process.argv[2] 73 | } else { 74 | console.error('no ifile'); 75 | process.exit(); 76 | } 77 | if (process.argv[3]) { 78 | ofile = process.argv[3] 79 | } else { 80 | console.error('no ofile'); 81 | process.exit(); 82 | } 83 | 84 | if (process.argv[4]) { 85 | bs = process.argv[4] 86 | } 87 | 88 | dd(ifile, ofile, bs); 89 | 90 | //FIXME nothing used after this 91 | var gen = function*() { 92 | yield 'one'; 93 | yield 'two'; 94 | yield 'three'; 95 | } 96 | let generator = gen(); 97 | let current = generator.next(); 98 | while (!current.done) { 99 | console.log('current value:', current.value); 100 | current = generator.next(); 101 | } 102 | 103 | var {a, b, c} = {a: 1, b: 2, c: 3}; -------------------------------------------------------------------------------- /examples/simple.mjs: -------------------------------------------------------------------------------- 1 | export default class Thing { 2 | constructor(a = 'a', b = 'b', c = 'c') { 3 | this.a = a; 4 | this.b = b; 5 | this.c = c; 6 | } 7 | 8 | get d() { 9 | let c_type = typeof this.c; 10 | if (c_type == 'string') { 11 | let _c = this.c.charCodeAt(0); 12 | return String.fromCharCode(_c + 1); 13 | } else if (c_type == 'number') { 14 | return c + 1; 15 | } else { 16 | throw new Error('Unable to compute d'); 17 | } 18 | } 19 | } 20 | 21 | export function main() { 22 | let x = new Thing(); 23 | return x.d; 24 | } 25 | 26 | console.log(main()); -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | extern crate ressa; 2 | use ressa::*; 3 | fn main() { 4 | let js = include_str!("simple.js"); 5 | let mut p = Parser::new(js).unwrap(); 6 | let script = p.parse().unwrap(); 7 | println!("{:#?}", script); 8 | } 9 | -------------------------------------------------------------------------------- /examples/simple_builder.rs: -------------------------------------------------------------------------------- 1 | extern crate ressa; 2 | use ressa::*; 3 | fn main() { 4 | let js = include_str!("simple.js"); 5 | let mut p = Parser::builder() 6 | .module(false) 7 | .tolerant(false) 8 | .js(js) 9 | .build() 10 | .unwrap(); 11 | let script = p.parse().unwrap(); 12 | println!("{:#?}", script); 13 | } 14 | -------------------------------------------------------------------------------- /examples/simple_module.rs: -------------------------------------------------------------------------------- 1 | extern crate ressa; 2 | use ressa::*; 3 | fn main() { 4 | let js = include_str!("simple.mjs"); 5 | let mut p = Parser::builder() 6 | .module(true) 7 | .tolerant(false) 8 | .js(js) 9 | .build() 10 | .unwrap(); 11 | let module = p.parse().unwrap(); 12 | println!("{:#?}", module); 13 | } 14 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018 Robert F. Masen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "acorn": "^8.7.0", 4 | "angular": "^1.6.10", 5 | "dexie": "^3.2.2", 6 | "esprima": "^4.0.1", 7 | "everything.js": "^1.0.3", 8 | "jquery": "^3.3.1", 9 | "moment": "^2.22.2", 10 | "react": "^16.4.2", 11 | "react-dom": "^16.4.2", 12 | "vue": "^2.5.17" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /scripts/run_moz_central_test.sh: -------------------------------------------------------------------------------- 1 | RUST_MIN_STACK=9999999 cargo test moz_central --test all --features moz_central -- --nocapture 2 | -------------------------------------------------------------------------------- /src/comment_handler.rs: -------------------------------------------------------------------------------- 1 | use ress::prelude::*; 2 | 3 | /// A comment handler will allow you to specify 4 | /// behavior about what to do with comments 5 | /// officially comments are supposed to operate 6 | /// the same as whitespace so the default behavior 7 | /// would be to throw away any comments found 8 | pub trait CommentHandler<'a> { 9 | fn handle_comment(&mut self, comment: Item<&'a str>); 10 | } 11 | /// The default comment handler, 12 | /// this will discard comments 13 | /// provided to it 14 | pub struct DefaultCommentHandler; 15 | 16 | impl<'a> CommentHandler<'a> for DefaultCommentHandler { 17 | fn handle_comment(&mut self, _: Item<&'a str>) {} 18 | } 19 | 20 | impl<'a, F> CommentHandler<'a> for F 21 | where 22 | F: FnMut(Item<&'a str>), 23 | { 24 | fn handle_comment(&mut self, item: Item<&'a str>) { 25 | self(item) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use ress::{tokens::Keyword, Position}; 2 | use std::fmt::{Display, Formatter, Result}; 3 | #[derive(Debug)] 4 | pub enum Error { 5 | UnexpectedEoF, 6 | ParseAfterEoF, 7 | RestrictedIdent(Position), 8 | UnexpectedToken(Position, String), 9 | UnableToReinterpret(Position, String, String), 10 | Redecl(Position, String), 11 | OperationError(Position, String), 12 | InvalidGetterParams(Position), 13 | InvalidSetterParams(Position), 14 | NonStrictFeatureInStrictContext(Position, String), 15 | InvalidImportError(Position), 16 | InvalidExportError(Position), 17 | InvalidYield(Position), 18 | InvalidUseOfContextualKeyword(Position, String), 19 | TryWithNoCatchOrFinally(Position), 20 | InvalidCatchArg(Position), 21 | ThrowWithNoArg(Position), 22 | UnknownOptionalLabel(Position, Keyword<()>, String), 23 | InvalidOptionalLabel(Position), 24 | UseOfModuleFeatureOutsideOfModule(Position, String), 25 | NewLineAfterFatArrow(Position), 26 | StrictModeArgumentsOrEval(Position), 27 | InvalidSuper(Position), 28 | InvalidFuncPosition(Position, String), 29 | InvalidClassPosition(Position, String), 30 | ForOfInAssign(Position, String), 31 | ContinueOutsideOfIteration(Position), 32 | InvalidParameter(Position, String), 33 | OctalLiteral(Position), 34 | HtmlCommentInModule(Position), 35 | InvalidRegEx(Position, String), 36 | InvalidTrailingComma(Position), 37 | InvalidEscape(Position, String), 38 | LexicalRedecl(Position, Position, String), 39 | InvalidLHS(Position), 40 | ForOfNotSimple(Position), 41 | InvalidStartOfExpressionStmt(Position, String), 42 | DuplicateExport(Position, String), 43 | UndefinedExports(Vec), 44 | ContinueOfNotIterationLabel(Position, String), 45 | Scanner(ress::error::Error), 46 | Other(Box), 47 | Misc(String), 48 | } 49 | 50 | impl Display for Error { 51 | fn fmt(&self, f: &mut Formatter) -> Result { 52 | match self { 53 | Error::UnexpectedToken(ref pos, ref msg) => write!(f, "Unexpected Token at {}: {}", pos, msg), 54 | Error::UnexpectedEoF => write!(f, "Unexpectedly found the end of the file"), 55 | Error::ParseAfterEoF => write!(f, "Parser attempted to get the next token after finding the end of the file"), 56 | Error::RestrictedIdent(pos) => write!(f, "Restricted word used as identifier at {}", pos), 57 | Error::UnableToReinterpret(ref pos, ref from, ref to) => write!(f, "Unable to re-interpret from {} to {} at {}", from, to, pos), 58 | Error::Redecl(ref pos, ref ident) => write!(f, "Nested label at {} shadows parent: {}", pos, ident), 59 | Error::OperationError(ref pos, ref msg) => write!(f, "Invalid operation, {}: {}", pos, msg), 60 | Error::InvalidGetterParams(ref pos) => write!(f, "Found a getter method that takes arguments at {}, getter methods cannot take arguments", pos), 61 | Error::InvalidSetterParams(ref pos) => write!(f, "Found a setter method that takes more or less than 1 argument at {}, setter methods must take only one argument", pos), 62 | Error::InvalidYield(ref pos) => write!(f, "Yield cannot be used as an identifier at {}", pos), 63 | Error::NonStrictFeatureInStrictContext(ref pos, ref name) => write!(f, "Attempting to use non-strict feature {} in a strict context at {}", name, pos), 64 | Error::InvalidImportError(ref pos) => write!(f, "Inavlid use of es6 import syntax at {}", pos), 65 | Error::InvalidExportError(ref pos) => write!(f, "Inavlid use of es6 export syntax at {}", pos), 66 | Error::InvalidUseOfContextualKeyword(ref pos, ref ident) => write!(f, "Inavlid use of contextual keyword {} at {}", pos, ident), 67 | Error::TryWithNoCatchOrFinally(ref pos) => write!(f, "Try catch block must have catch or finally clause at {}", pos), 68 | Error::InvalidCatchArg(ref pos) => write!(f, "Inavlid parameter in catch block at {}", pos), 69 | Error::ThrowWithNoArg(ref pos) => write!(f, "Throw statements without an argument {}", pos), 70 | Error::UnknownOptionalLabel(ref pos, ref key, ref label) => write!(f, "Attempt to {0} {1} but {1} is unknown in this scope at {2}", key.to_string(), label, pos), 71 | Error::InvalidOptionalLabel(ref pos) => write!(f, "Attempt to break with no label is not allowed unless in an iteration or switch: {}", pos), 72 | Error::UseOfModuleFeatureOutsideOfModule(ref pos, ref feature) => write!(f, "Used {} at {} which is only available inside of an es6 module", feature, pos), 73 | Error::NewLineAfterFatArrow(ref pos) => write!(f, "New line after fat arrow at {}", pos), 74 | Error::StrictModeArgumentsOrEval(ref pos) => write!(f, "arguments or eval used as an identifier in strict mode near {}", pos), 75 | Error::InvalidFuncPosition(ref pos, ref msg) => write!(f, "{} at {}", msg, pos), 76 | Error::InvalidClassPosition(ref pos, ref msg) => write!(f, "{} at {}", msg, pos), 77 | Error::ForOfInAssign(ref pos, ref msg) => write!(f, "{} at {}", msg, pos), 78 | Error::InvalidSuper(ref pos) => write!(f, "Invalid use of super at {}", pos), 79 | Error::ContinueOutsideOfIteration(ref pos) => write!(f, "Invalid use of continue at {}", pos), 80 | Error::InvalidParameter(ref pos, ref msg) => write!(f, "Invalid paramter at {} -- {}", pos, msg), 81 | Error::OctalLiteral(ref pos) => write!(f, "Invalid use of octal literal at {}", pos), 82 | Error::HtmlCommentInModule(ref pos) => write!(f, "HTML Comments are not available in module code: {}", pos), 83 | Error::InvalidRegEx(ref pos, ref msg) => write!(f, "Invalid regular expression literal at {} -- {}", pos, msg), 84 | Error::InvalidTrailingComma(ref pos) => write!(f, "Invalid trailing comma at {}", pos), 85 | Error::InvalidEscape(ref pos, ref msg) => write!(f, "{} at {}", msg, pos), 86 | Error::LexicalRedecl(ref orig, ref redecl, ref id) => write!(f, "identifier {} was previously declared at {} and again at {}", id, orig, redecl), 87 | Error::InvalidLHS(ref pos) => write!(f, "invalid left hand side at {}", pos), 88 | Error::ForOfNotSimple(ref pos) => write!(f, "initializer of a for-of loop must be a simple assignment target {}", pos), 89 | Error::InvalidStartOfExpressionStmt(ref pos, ref token) => write!(f, "Expression statement cannot start with {} at {}", token, pos), 90 | Error::DuplicateExport(ref pos, ref token) => write!(f, "Found duplicate export with name {} at {}", token, pos), 91 | Error::UndefinedExports(ref names) => write!(f, "Undefined exports in module: {}", names.join(", ")), 92 | Error::ContinueOfNotIterationLabel(ref pos, ref token) => write!(f, "Label `{}` is does not label a loop, continue is invalid at {}", token, pos), 93 | Error::Scanner(ref e) => write!(f, "Error when tokenizing {}", e), 94 | Error::Other(ref e) => write!(f, "{}", e), 95 | Error::Misc(ref e) => write!(f, "{}", e), 96 | } 97 | } 98 | } 99 | 100 | impl Error { 101 | pub fn unable_to_reinterpret(pos: Position, from: &str, to: &str) -> Self { 102 | Error::UnableToReinterpret(pos, from.to_owned(), to.to_owned()) 103 | } 104 | 105 | pub fn position(&self) -> Option { 106 | use self::Error::*; 107 | match self { 108 | RestrictedIdent(p) => Some(*p), 109 | UnexpectedToken(p, _) => Some(*p), 110 | UnableToReinterpret(p, _, _) => Some(*p), 111 | Redecl(p, _) => Some(*p), 112 | OperationError(p, _) => Some(*p), 113 | InvalidGetterParams(p) => Some(*p), 114 | InvalidSetterParams(p) => Some(*p), 115 | NonStrictFeatureInStrictContext(p, _) => Some(*p), 116 | InvalidImportError(p) => Some(*p), 117 | InvalidExportError(p) => Some(*p), 118 | InvalidYield(p) => Some(*p), 119 | InvalidUseOfContextualKeyword(p, _) => Some(*p), 120 | TryWithNoCatchOrFinally(p) => Some(*p), 121 | InvalidCatchArg(p) => Some(*p), 122 | ThrowWithNoArg(p) => Some(*p), 123 | UnknownOptionalLabel(p, _, _) => Some(*p), 124 | InvalidOptionalLabel(p) => Some(*p), 125 | UseOfModuleFeatureOutsideOfModule(p, _) => Some(*p), 126 | NewLineAfterFatArrow(p) => Some(*p), 127 | StrictModeArgumentsOrEval(p) => Some(*p), 128 | InvalidSuper(p) => Some(*p), 129 | InvalidFuncPosition(p, _) => Some(*p), 130 | InvalidClassPosition(p, _) => Some(*p), 131 | ForOfInAssign(p, _) => Some(*p), 132 | ContinueOutsideOfIteration(p) => Some(*p), 133 | InvalidParameter(p, _) => Some(*p), 134 | OctalLiteral(p) => Some(*p), 135 | HtmlCommentInModule(p) => Some(*p), 136 | InvalidRegEx(p, _) => Some(*p), 137 | InvalidTrailingComma(p) => Some(*p), 138 | InvalidEscape(p, _) => Some(*p), 139 | LexicalRedecl(p, _, _) => Some(*p), 140 | InvalidLHS(p) => Some(*p), 141 | ForOfNotSimple(p) => Some(*p), 142 | InvalidStartOfExpressionStmt(p, _) => Some(*p), 143 | DuplicateExport(p, _) => Some(*p), 144 | ContinueOfNotIterationLabel(p, _) => Some(*p), 145 | Scanner(e) => Some(Position { 146 | line: e.line, 147 | column: e.column, 148 | }), 149 | _ => None, 150 | } 151 | } 152 | } 153 | 154 | impl From<::std::io::Error> for Error { 155 | fn from(other: ::std::io::Error) -> Self { 156 | Error::Other(Box::new(other)) 157 | } 158 | } 159 | impl ::std::error::Error for Error {} 160 | 161 | impl From for Error { 162 | fn from(other: ress::error::Error) -> Self { 163 | Error::Scanner(other) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/formal_params.rs: -------------------------------------------------------------------------------- 1 | use resast::spanned::expr::{Lit, ObjProp, Prop, PropKey, PropValue}; 2 | use resast::spanned::pat::{ArrayPatPart, ObjPatPart, Pat}; 3 | use resast::spanned::{FuncArg, ListEntry, Slice}; 4 | use std::borrow::Cow; 5 | use std::collections::HashSet; 6 | 7 | type Param<'a> = ListEntry<'a, FuncArg<'a>>; 8 | 9 | pub struct FormalParams<'a> { 10 | pub simple: bool, 11 | pub open_paren: Slice<'a>, 12 | pub params: Vec>, 13 | pub close_paren: Slice<'a>, 14 | pub strict: bool, 15 | pub found_restricted: bool, 16 | } 17 | 18 | pub struct FormalsList<'a> { 19 | pub keyword_async: Option>, 20 | pub open_paren: Option>, 21 | pub params: Vec>, 22 | pub close_paren: Option>, 23 | } 24 | 25 | pub fn have_duplicates<'a>(params: &[Param<'a>]) -> bool { 26 | if let Err(first_dupe) = find_duplicate(params) { 27 | log::error!("Found duplicate parameter: {}", first_dupe); 28 | true 29 | } else { 30 | false 31 | } 32 | } 33 | pub fn find_duplicate<'a>(params: &[Param<'a>]) -> Result<(), Cow<'a, str>> { 34 | let mut set = HashSet::new(); 35 | for param in params.iter() { 36 | match ¶m.item { 37 | FuncArg::Expr(expr) => { 38 | update_with_expr(expr, &mut set)?; 39 | } 40 | FuncArg::Pat(pat) => { 41 | update_with_pat(pat, &mut set)?; 42 | } 43 | FuncArg::Rest(rest_pat) => { 44 | update_with_pat(&rest_pat.pat, &mut set)?; 45 | } 46 | } 47 | } 48 | Ok(()) 49 | } 50 | pub fn update_with_expr<'a>( 51 | expr: &resast::spanned::expr::Expr<'a>, 52 | set: &mut HashSet>, 53 | ) -> Result<(), Cow<'a, str>> { 54 | use resast::spanned::expr::{AssignExpr, AssignLeft}; 55 | log::trace!("update_with_expr {:?} {:?}", expr, set); 56 | match expr { 57 | resast::spanned::expr::Expr::Ident(id) => { 58 | if !set.insert(id.slice.source.clone()) { 59 | return Err(id.slice.source.clone()); 60 | } 61 | } 62 | resast::spanned::expr::Expr::Assign(AssignExpr { left, .. }) => match left { 63 | AssignLeft::Expr(assign) => { 64 | update_with_expr(assign, set)?; 65 | } 66 | AssignLeft::Pat(pat) => { 67 | update_with_pat(pat, set)?; 68 | } 69 | }, 70 | resast::spanned::expr::Expr::Obj(obj) => { 71 | for prop in &obj.props { 72 | match &prop.item { 73 | ObjProp::Prop(prop) => { 74 | update_with_prop(prop, set)?; 75 | } 76 | ObjProp::Spread(expr) => { 77 | update_with_expr(&expr.expr, set)?; 78 | } 79 | } 80 | } 81 | } 82 | _ => (), 83 | } 84 | Ok(()) 85 | } 86 | pub fn update_with_pat<'a>( 87 | pat: &resast::spanned::pat::Pat<'a>, 88 | set: &mut HashSet>, 89 | ) -> Result<(), Cow<'a, str>> { 90 | log::trace!("update_with_pat {:?} {:?}", pat, set); 91 | match pat { 92 | Pat::Ident(id) => { 93 | if !set.insert(id.slice.source.clone()) { 94 | return Err(id.slice.source.clone()); 95 | } 96 | } 97 | Pat::Array(arr) => { 98 | for part in &arr.elements { 99 | if let Some(part) = &part.item { 100 | match part { 101 | ArrayPatPart::Pat(pat) => { 102 | update_with_pat(pat, set)?; 103 | } 104 | ArrayPatPart::Expr(expr) => { 105 | update_with_expr(expr, set)?; 106 | } 107 | ArrayPatPart::Rest(rest) => update_with_pat(&rest.pat, set)?, 108 | } 109 | } 110 | } 111 | } 112 | Pat::Obj(obj) => { 113 | for part in &obj.props { 114 | match &part.item { 115 | ObjPatPart::Assign(prop) => { 116 | update_with_prop(prop, set)?; 117 | } 118 | ObjPatPart::Rest(pat) => { 119 | update_with_pat(&pat.pat, set)?; 120 | } 121 | } 122 | } 123 | } 124 | Pat::Assign(assign) => { 125 | update_with_pat(&*assign.left, set)?; 126 | } 127 | } 128 | Ok(()) 129 | } 130 | 131 | fn update_with_prop<'a>( 132 | prop: &Prop<'a>, 133 | set: &mut HashSet>, 134 | ) -> Result<(), Cow<'a, str>> { 135 | match prop { 136 | Prop::Init(value) => { 137 | if let Some(value) = &value.value { 138 | update_with_prop_value(value, set) 139 | } else { 140 | update_with_prop_key(&value.key.value, set) 141 | } 142 | } 143 | Prop::Method(value) => update_with_prop_key(&value.id.value, set), 144 | Prop::Ctor(_value) => Ok(()), 145 | Prop::Get(value) => update_with_prop_key(&value.id.value, set), 146 | Prop::Set(value) => update_with_prop_key(&value.id.value, set), 147 | } 148 | } 149 | 150 | fn update_with_prop_value<'a>( 151 | prop: &PropValue<'a>, 152 | set: &mut HashSet>, 153 | ) -> Result<(), Cow<'a, str>> { 154 | log::trace!("update_with_prop {:?}, {:?}", prop, set); 155 | match &prop { 156 | PropValue::Expr(expr) => { 157 | update_with_expr(expr, set)?; 158 | } 159 | PropValue::Pat(pat) => { 160 | update_with_pat(pat, set)?; 161 | } 162 | PropValue::Method(_) => {} 163 | } 164 | Ok(()) 165 | } 166 | 167 | fn update_with_prop_key<'a>( 168 | key: &PropKey<'a>, 169 | set: &mut HashSet>, 170 | ) -> Result<(), Cow<'a, str>> { 171 | match key { 172 | PropKey::Lit(lit) => update_with_lit(lit, set), 173 | PropKey::Expr(expr) => update_with_expr(expr, set), 174 | PropKey::Pat(pat) => update_with_pat(pat, set), 175 | } 176 | } 177 | 178 | fn update_with_lit<'a>(lit: &Lit<'a>, set: &mut HashSet>) -> Result<(), Cow<'a, str>> { 179 | log::trace!("update_with_lit {:?}, {:?}", lit, set); 180 | if let Lit::String(s) = lit { 181 | if !set.insert(s.content.source.clone()) { 182 | return Err(s.content.source.clone()); 183 | } 184 | } 185 | Ok(()) 186 | } 187 | -------------------------------------------------------------------------------- /src/globals.rs: -------------------------------------------------------------------------------- 1 | static ARRAY: &str = "Array"; 2 | static ARRAY_BUFFER: &str = "ArrayBuffer"; 3 | static BIG_INT: &str = "BigInt"; 4 | static BIG_INT_64_ARRAY: &str = "BigInt64Array"; 5 | static BigUint64Array: &str = "BigUint64Array"; 6 | static Boolean: &str = "Boolean"; 7 | static DataView: &str = "DataView"; 8 | static Date: &str = "Date"; 9 | static Error: &str = "Error"; 10 | static EvalError: &str = "EvalError"; 11 | static Float32Array: &str = "Float32Array"; 12 | static Float64Array: &str = "Float64Array"; 13 | static Function: &str = "Function"; 14 | static Int8Array: &str = "Int8Array"; 15 | static Int16Array: &str = "Int16Array"; 16 | static Int32Array: &str = "Int32Array"; 17 | static Map: &str = "Map"; 18 | static Number: &str = "Number"; 19 | static Object: &str = "Object"; 20 | static Promise: &str = "Promise"; 21 | static Proxy: &str = "Proxy"; 22 | static RangeError: &str = "RangeError"; 23 | static ReferenceError: &str = "ReferenceError"; 24 | static RegExp: &str = "RegExp"; 25 | static Set: &str = "Set"; 26 | static SharedArrayBuffer: &str = "SharedArrayBuffer"; 27 | static String: &str = "String"; 28 | static Symbol: &str = "Symbol"; 29 | static SyntaxError: &str = "SyntaxError"; 30 | static TypeError: &str = "TypeError"; 31 | static Uint8Array: &str = "Uint8Array"; 32 | static Uint8ClampedArray: &str = "Uint8ClampedArray"; 33 | static Uint16Array: &str = "Uint16Array"; 34 | static Uint32Array: &str = "Uint32Array"; 35 | static URIError: &str = "URIError"; 36 | static WeakMap: &str = "WeakMap"; 37 | static WeakSet: &str = "WeakSet"; 38 | static EVAL = &str = "eval";; 39 | static isFinite: &str = "isFinite";; 40 | static isNaN: &str = "isNaN";; 41 | static parseFloat: &str = "parseFloat";; 42 | static parseInt: &str = "parseInt"; 43 | 44 | static decodeURI: &str = "decodeURI"; 45 | static decodeURIComponent: &str = "decodeURIComponent"; 46 | static encodeURI: &str = "encodeURI"; 47 | static encodeURIComponent: &str = "encodeURIComponent"; -------------------------------------------------------------------------------- /src/lexical_names.rs: -------------------------------------------------------------------------------- 1 | use super::{Error, Position, Res}; 2 | use hash_chain::ChainMap; 3 | use resast::spanned::{ 4 | decl::ExportSpecifier, 5 | expr::{Expr, Lit, Prop, PropKey, PropValue}, 6 | pat::{ArrayPatPart, ObjPatPart, Pat}, 7 | Ident, 8 | }; 9 | 10 | use std::{borrow::Cow, collections::HashSet}; 11 | type LexMap<'a> = ChainMap, Position>; 12 | type VarMap<'a> = ChainMap, Vec>; 13 | #[derive(Clone, Copy, Debug)] 14 | pub enum DeclKind { 15 | Lex(bool), 16 | Var(bool), 17 | Func(bool), 18 | SimpleCatch, 19 | } 20 | 21 | pub struct DuplicateNameDetector<'a> { 22 | pub states: Vec, 23 | lex: LexMap<'a>, 24 | var: VarMap<'a>, 25 | func: LexMap<'a>, 26 | first_lexes: Vec>>, 27 | /// Hashmap of identifiers exported 28 | /// from this module and a flag for if they 29 | /// have a corresponding declaration 30 | undefined_module_exports: HashSet>, 31 | exports: HashSet>, 32 | } 33 | 34 | impl<'a> Default for DuplicateNameDetector<'a> { 35 | fn default() -> Self { 36 | Self { 37 | states: vec![Scope::default()], 38 | lex: LexMap::default(), 39 | var: VarMap::default(), 40 | func: LexMap::default(), 41 | first_lexes: Vec::new(), 42 | undefined_module_exports: HashSet::new(), 43 | exports: HashSet::new(), 44 | } 45 | } 46 | } 47 | 48 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 49 | pub enum Scope { 50 | Top, 51 | FuncTop, 52 | SimpleCatch, 53 | For, 54 | Catch, 55 | Switch, 56 | Block, 57 | } 58 | impl Default for Scope { 59 | fn default() -> Self { 60 | Self::Top 61 | } 62 | } 63 | 64 | impl Scope { 65 | pub fn is_top(self) -> bool { 66 | self == Scope::Top 67 | } 68 | pub fn is_func_top(self) -> bool { 69 | self == Scope::FuncTop 70 | } 71 | pub fn is_simple_catch(self) -> bool { 72 | self == Scope::SimpleCatch 73 | } 74 | 75 | pub fn funcs_as_var(self, is_module: bool) -> bool { 76 | self.is_func_top() || !is_module && self.is_top() 77 | } 78 | } 79 | 80 | impl<'a> DuplicateNameDetector<'a> { 81 | pub fn current_funcs_as_var(&self, is_module: bool) -> bool { 82 | if let Some(&scope) = self.states.last() { 83 | scope.funcs_as_var(is_module) 84 | } else { 85 | false 86 | } 87 | } 88 | pub fn last_scope(&self) -> Option { 89 | self.states.last().copied() 90 | } 91 | pub fn declare(&mut self, i: Cow<'a, str>, kind: DeclKind, pos: Position) -> Res<()> { 92 | log::trace!("DuplicateNameDetector::declare {} {:?} {:?}", i, kind, pos); 93 | match kind { 94 | DeclKind::Lex(is_module) => { 95 | self.check_var(i.clone(), pos)?; 96 | self.check_func(i.clone(), pos)?; 97 | if let Some(first) = self.first_lexes.last_mut() { 98 | if first.is_none() { 99 | *first = Some(i.clone()); 100 | } 101 | } 102 | if is_module { 103 | self.undefined_module_exports.remove(&i); 104 | } 105 | self.add_lex(i, pos) 106 | } 107 | DeclKind::Var(is_module) => { 108 | for (idx, scope) in self.states.iter().enumerate().rev() { 109 | log::trace!("checking scope {}", idx); 110 | let error = if self.lex.has_at(idx, &i) && !scope.is_simple_catch() { 111 | if let Some(Some(lex)) = self.first_lexes.get(idx) { 112 | &i != lex 113 | } else { 114 | true 115 | } 116 | } else { 117 | log::trace!( 118 | "looking for dupe in {} funcs_as_var: {}, funcs_has {}", 119 | idx, 120 | scope.funcs_as_var(is_module), 121 | self.func.has_at(idx, &i) 122 | ); 123 | !scope.funcs_as_var(is_module) && self.func.has_at(idx, &i) 124 | }; 125 | if error { 126 | let ret = match self.lex.get_before(idx + 1, &i) { 127 | Some(orig) => Err(Error::LexicalRedecl(*orig, pos, i.to_string())), 128 | None => Err(Error::OperationError( 129 | pos, 130 | format!("lexical map couldn't find {} before {}", i, idx), 131 | )), 132 | }; 133 | return ret; 134 | } 135 | if scope.is_func_top() || scope.is_top() { 136 | break; 137 | } 138 | } 139 | if is_module { 140 | self.undefined_module_exports.remove(&i); 141 | } 142 | self.add_var(i.clone(), pos); 143 | Ok(()) 144 | } 145 | DeclKind::Func(is_module) => { 146 | let state = if let Some(state) = self.states.last() { 147 | log::trace!("last state found {:?}", state); 148 | *state 149 | } else { 150 | Scope::default() 151 | }; 152 | self.check_lex(i.clone(), pos)?; 153 | log::trace!("not in lexical decls"); 154 | if !state.funcs_as_var(is_module) { 155 | log::trace!("state does not indicate functions should be treated as vars"); 156 | self.check_var(i.clone(), pos)?; 157 | } 158 | self.add_func(i, pos) 159 | } 160 | DeclKind::SimpleCatch => { 161 | self.lex.insert(i.clone(), pos); 162 | Ok(()) 163 | } 164 | } 165 | } 166 | pub fn declare_pat(&mut self, pat: &Pat<'a>, kind: DeclKind, pos: Position) -> Res<()> { 167 | log::trace!("declare_pat {:?} {:?} {:?}", pat, kind, pos); 168 | match pat { 169 | Pat::Ident(ref i) => { 170 | log::trace!("add_pat ident {:?}", i.slice.source); 171 | self.declare(i.slice.source.clone(), kind, pos) 172 | } 173 | Pat::Array(ref a) => { 174 | for part in &a.elements { 175 | if let Some(ref i) = part.item { 176 | match i { 177 | ArrayPatPart::Expr(ex) => self.declare_expr(ex, kind, pos)?, 178 | ArrayPatPart::Pat(pat) => self.declare_pat(pat, kind, pos)?, 179 | ArrayPatPart::Rest(rest) => self.declare_pat(&rest.pat, kind, pos)?, 180 | } 181 | } 182 | } 183 | Ok(()) 184 | } 185 | Pat::Assign(ref a) => self.declare_pat(&*a.left, kind, pos), 186 | Pat::Obj(ref o) => { 187 | for part in &o.props { 188 | match &part.item { 189 | ObjPatPart::Assign(prop) => self.declare_prop(prop, kind, pos)?, 190 | ObjPatPart::Rest(pat) => self.declare_pat(&pat.pat, kind, pos)?, 191 | } 192 | } 193 | Ok(()) 194 | } 195 | } 196 | } 197 | 198 | fn declare_prop(&mut self, prop: &Prop<'a>, kind: DeclKind, pos: Position) -> Res<()> { 199 | log::trace!("declare_prop {:?} {:?} {:?}", prop, kind, pos); 200 | match &prop { 201 | Prop::Init(prop) => match &prop.value { 202 | Some(value) => match value { 203 | PropValue::Expr(expr) => self.declare_expr(expr, kind, pos), 204 | PropValue::Pat(pat) => self.declare_pat(pat, kind, pos), 205 | PropValue::Method(_) => Ok(()), 206 | }, 207 | None => match &prop.key.value { 208 | PropKey::Lit(lit) => self.declare_literal_ident(lit, kind, pos), 209 | PropKey::Expr(expr) => self.declare_expr(expr, kind, pos), 210 | PropKey::Pat(pat) => self.declare_pat(pat, kind, pos), 211 | }, 212 | }, 213 | _ => Ok(()), 214 | } 215 | } 216 | fn declare_literal_ident(&mut self, lit: &Lit<'a>, kind: DeclKind, pos: Position) -> Res<()> { 217 | log::trace!("declare_literal_ident {:?} {:?} {:?}", lit, kind, pos); 218 | match lit { 219 | Lit::String(s) => self.declare(s.content.source.clone(), kind, pos), 220 | _ => Err(Error::RestrictedIdent(pos)), 221 | } 222 | } 223 | pub fn declare_expr(&mut self, expr: &Expr<'a>, kind: DeclKind, pos: Position) -> Res<()> { 224 | log::trace!("declare_expr {:?} {:?} {:?}", expr, kind, pos); 225 | if let Expr::Ident(ref i) = expr { 226 | log::trace!("add_expr ident {:?}", i.slice.source); 227 | self.declare(i.slice.source.clone(), kind, pos) 228 | } else { 229 | Ok(()) 230 | } 231 | } 232 | fn check_var(&mut self, i: Cow<'a, str>, pos: Position) -> Res<()> { 233 | log::trace!("check_var {:?} {:?}", i, pos); 234 | if self.var.last_has(&i) { 235 | if let Some(poses) = self.var.get(&i) { 236 | if let Some(old_pos) = poses.last() { 237 | if *old_pos < pos { 238 | return Err(Error::LexicalRedecl(*old_pos, pos, i.to_string())); 239 | } 240 | } 241 | } 242 | } 243 | Ok(()) 244 | } 245 | 246 | fn check_func(&mut self, i: Cow<'a, str>, pos: Position) -> Res<()> { 247 | log::trace!("check_func {:?} {:?}", i, pos); 248 | check(&mut self.func, i, pos) 249 | } 250 | fn add_func(&mut self, i: Cow<'a, str>, pos: Position) -> Res<()> { 251 | log::trace!("add_func {:?} {:?}", i, pos); 252 | let _ = self.func.insert(i, pos); 253 | Ok(()) 254 | } 255 | fn check_lex(&mut self, i: Cow<'a, str>, pos: Position) -> Res<()> { 256 | log::trace!("check_lex {:?} {:?}", i, pos); 257 | check(&mut self.lex, i, pos) 258 | } 259 | 260 | fn add_var(&mut self, i: Cow<'a, str>, pos: Position) { 261 | log::trace!("add_var {:?} {:?}", i, pos); 262 | if let Some(v) = self.var.get_mut(&i) { 263 | v.push(pos); 264 | } else { 265 | self.var.insert(i.clone(), vec![pos]); 266 | } 267 | } 268 | 269 | fn add_lex(&mut self, i: Cow<'a, str>, pos: Position) -> Res<()> { 270 | log::trace!("add_lex {:?} {:?}", i, pos); 271 | add(&mut self.lex, i, pos)?; 272 | Ok(()) 273 | } 274 | pub fn new_child(&mut self, scope: Scope) { 275 | self.lex.new_child(); 276 | self.var.new_child(); 277 | self.func.new_child(); 278 | self.states.push(scope); 279 | self.first_lexes.push(None); 280 | } 281 | 282 | pub fn remove_child(&mut self) { 283 | let _ = self.lex.remove_child(); 284 | let _ = self.func.remove_child(); 285 | if let Some(old_scope) = self.states.pop() { 286 | self.remove_var_child(old_scope); 287 | } else { 288 | panic!("attempted to pop state at bottom of stack") 289 | } 290 | self.first_lexes.pop(); 291 | } 292 | fn remove_var_child(&mut self, scope: Scope) { 293 | if scope.is_func_top() { 294 | let _ = self.var.remove_child(); 295 | } else if let Some(old) = self.var.remove_child() { 296 | for (key, mut value) in old { 297 | if self.var.last_has(&key) { 298 | if let Some(list) = self.var.get_mut(&key) { 299 | list.append(&mut value); 300 | } 301 | } else { 302 | self.var.insert(key, value); 303 | } 304 | } 305 | } 306 | } 307 | 308 | pub fn add_export_spec(&mut self, spec: &ExportSpecifier<'a>, pos: Position) -> Res<()> { 309 | log::trace!("add_export_spec {:?} {:?}", spec, pos); 310 | self.add_export_ident(&spec.local.slice.source, pos)?; 311 | self.undefined_module_export_guard(spec.local.slice.source.clone()); 312 | Ok(()) 313 | } 314 | 315 | pub fn removed_undefined_export(&mut self, id: &Ident<'a>) { 316 | log::trace!("removed_undefined_export {:?}", id); 317 | self.undefined_module_exports.remove(&id.slice.source); 318 | } 319 | 320 | pub fn add_export_ident(&mut self, id: &Cow<'a, str>, pos: Position) -> Res<()> { 321 | log::trace!("add_export_ident {:?} {:?}", id, pos); 322 | if !self.exports.insert(id.clone()) { 323 | Err(Error::DuplicateExport(pos, id.to_string())) 324 | } else { 325 | Ok(()) 326 | } 327 | } 328 | 329 | pub fn undefined_module_export_guard(&mut self, id: Cow<'a, str>) { 330 | log::trace!("add_module_export: {}", id); 331 | if !self.var.has_at(0, &id) && !self.lex.has_at(0, &id) { 332 | self.undefined_module_exports.insert(id); 333 | } 334 | } 335 | 336 | pub fn has_undefined_exports(&self) -> bool { 337 | !self.undefined_module_exports.is_empty() 338 | } 339 | 340 | pub fn get_undefined_exports(&self) -> Vec { 341 | self.undefined_module_exports 342 | .iter() 343 | .map(|n| n.to_string()) 344 | .collect() 345 | } 346 | } 347 | 348 | /// check the last tier in the chain map for an identifier 349 | fn check<'a>(map: &mut LexMap<'a>, i: Cow<'a, str>, pos: Position) -> Res<()> { 350 | log::trace!("check {:?} {:?} {:?}", map, i, pos); 351 | log::trace!("checking for {}", i); 352 | if map.last_has(&i) { 353 | if let Some(old_pos) = map.get(&i) { 354 | if *old_pos < pos { 355 | return Err(Error::LexicalRedecl(*old_pos, pos, i.to_string())); 356 | } 357 | } 358 | } 359 | Ok(()) 360 | } 361 | 362 | pub fn add<'a>(map: &mut LexMap<'a>, i: Cow<'a, str>, start: Position) -> Res<()> { 363 | log::trace!("add {:?} {:?} {:?}", map, i, start); 364 | if let Some(old_pos) = map.insert(i.clone(), start) { 365 | if old_pos < start { 366 | Err(Error::LexicalRedecl(old_pos, start, i.to_string())) 367 | } else { 368 | Ok(()) 369 | } 370 | } else { 371 | Ok(()) 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /src/lhs.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | // ^^^^^^^^^^^^^^^^^ 3 | // most of this file is prep for a re-write 4 | 5 | use crate::{Error, Position}; 6 | use resast::spanned::{ 7 | expr::{ArrayExpr, AssignExpr, AssignLeft, Expr, ObjExpr, ObjProp, Prop, PropKey}, 8 | pat::{ArrayPatPart, ObjPatPart, Pat, RestPat}, 9 | stmt::LoopLeft, 10 | Ident, ListEntry, VarKind, 11 | }; 12 | use std::{borrow::Cow, collections::HashSet}; 13 | type Res = Result<(), Error>; 14 | 15 | pub fn is_simple_expr<'a>(expr: &Expr<'a>) -> bool { 16 | log::trace!("is_simple_expr {:?}", expr); 17 | match expr { 18 | Expr::This(_) => false, 19 | _ => true, 20 | } 21 | } 22 | 23 | pub fn is_simple_pat<'a>(pat: &Pat<'a>) -> bool { 24 | log::trace!("is_simple_pat {:?}", pat); 25 | match pat { 26 | Pat::Ident(ref id) => match &*id.slice.source { 27 | "this" => false, 28 | _ => true, 29 | }, 30 | _ => true, 31 | } 32 | } 33 | pub fn check_lhs_expr<'a>(expr: &Expr<'a>, allow_let: bool, pos: Position, strict: bool) -> Res { 34 | match expr { 35 | Expr::Ident(ref id) => check_ident(id, allow_let, pos, strict), 36 | Expr::Obj(ref obj) => check_obj_expr(obj, allow_let, pos, strict), 37 | Expr::Assign(ref assign) => check_assign_expr(assign, allow_let, pos, strict), 38 | Expr::Array(ref a) => check_array_expr(a, allow_let, pos, strict), 39 | Expr::Spread(ref s) => check_lhs_expr(&s.expr, allow_let, pos, strict), 40 | _ => Err(Error::InvalidLHS(pos)), 41 | } 42 | } 43 | 44 | pub fn check_lhs_pat<'a>(pat: &Pat<'a>, allow_let: bool, pos: Position, strict: bool) -> Res { 45 | match pat { 46 | Pat::Ident(ref id) => check_ident(id, allow_let, pos, strict), 47 | Pat::Obj(ref obj) => check_obj_pat(&obj.props, allow_let, pos, strict), 48 | Pat::Assign(ref assign) => check_assign_pat(assign, allow_let, pos, strict), 49 | Pat::Array(ref a) => check_array_pat(&a.elements, allow_let, pos, strict), 50 | } 51 | } 52 | 53 | fn check_ident<'a>(id: &Ident<'a>, allow_let: bool, pos: Position, strict: bool) -> Res { 54 | if !allow_let && &id.slice.source == "let" { 55 | Err(Error::InvalidUseOfContextualKeyword(pos, "let".to_string())) 56 | } else if strict && is_strict_reserved(&id) || is_restricted_word(&id) { 57 | Err(Error::RestrictedIdent(pos)) 58 | } else { 59 | Ok(()) 60 | } 61 | } 62 | 63 | fn check_obj_expr<'a>(obj: &ObjExpr<'a>, allow_let: bool, pos: Position, strict: bool) -> Res { 64 | for part in &obj.props { 65 | check_obj_prop(&part.item, allow_let, pos, strict)?; 66 | } 67 | Ok(()) 68 | } 69 | 70 | fn check_obj_pat<'a>( 71 | obj: &[ListEntry<'a, ObjPatPart<'a>>], 72 | allow_let: bool, 73 | pos: Position, 74 | strict: bool, 75 | ) -> Res { 76 | for part in obj { 77 | check_obj_pat_part(&part.item, allow_let, pos, strict)?; 78 | } 79 | Ok(()) 80 | } 81 | 82 | fn check_assign_expr<'a>( 83 | assign: &AssignExpr<'a>, 84 | allow_let: bool, 85 | pos: Position, 86 | strict: bool, 87 | ) -> Res { 88 | match &assign.left { 89 | AssignLeft::Expr(ref e) => check_lhs_expr(e, allow_let, pos, strict), 90 | AssignLeft::Pat(ref p) => check_lhs_pat(p, allow_let, pos, strict), 91 | } 92 | } 93 | 94 | fn check_assign_pat<'a>( 95 | assign: &resast::spanned::pat::AssignPat<'a>, 96 | allow_let: bool, 97 | pos: Position, 98 | strict: bool, 99 | ) -> Res { 100 | check_lhs_pat(&*assign.left, allow_let, pos, strict) 101 | } 102 | 103 | fn check_array_expr<'a>(array: &ArrayExpr, allow_let: bool, pos: Position, strict: bool) -> Res { 104 | for part in &array.elements { 105 | if let Some(expr) = &part.item { 106 | check_lhs_expr(expr, allow_let, pos, strict)?; 107 | } 108 | } 109 | Ok(()) 110 | } 111 | 112 | fn check_array_pat<'a>( 113 | array: &[ListEntry<'a, Option>>], 114 | allow_let: bool, 115 | pos: Position, 116 | strict: bool, 117 | ) -> Res { 118 | for part in array { 119 | if let Some(part) = &part.item { 120 | match part { 121 | ArrayPatPart::Expr(expr) => check_lhs_expr(expr, allow_let, pos, strict)?, 122 | ArrayPatPart::Pat(pat) => check_lhs_pat(pat, allow_let, pos, strict)?, 123 | ArrayPatPart::Rest(RestPat { pat, .. }) => { 124 | check_lhs_pat(pat, allow_let, pos, strict)? 125 | } 126 | } 127 | } 128 | } 129 | Ok(()) 130 | } 131 | 132 | fn check_obj_prop<'a>(prop: &ObjProp<'a>, allow_let: bool, pos: Position, strict: bool) -> Res { 133 | match prop { 134 | ObjProp::Prop(ref p) => check_prop(p, allow_let, pos, strict), 135 | ObjProp::Spread(ref p) => check_lhs_expr(&p.expr, allow_let, pos, strict), 136 | } 137 | } 138 | 139 | fn check_obj_pat_part<'a>( 140 | part: &ObjPatPart<'a>, 141 | allow_let: bool, 142 | pos: Position, 143 | strict: bool, 144 | ) -> Res { 145 | match part { 146 | ObjPatPart::Assign(prop) => check_prop(prop, allow_let, pos, strict), 147 | _ => Err(Error::InvalidLHS(pos)), 148 | } 149 | } 150 | 151 | fn check_prop<'a>(prop: &Prop<'a>, allow_let: bool, pos: Position, strict: bool) -> Res { 152 | match prop { 153 | Prop::Init(value) => check_prop_key(&value.key.value, allow_let, pos, strict), 154 | _ => Err(Error::InvalidLHS(pos)), 155 | } 156 | } 157 | 158 | pub fn check_prop_key<'a>(key: &PropKey<'a>, allow_let: bool, pos: Position, strict: bool) -> Res { 159 | match &key { 160 | PropKey::Lit(_value) => Err(Error::InvalidLHS(pos)), 161 | PropKey::Expr(value) => check_lhs_expr(value, allow_let, pos, strict), 162 | PropKey::Pat(value) => check_lhs_pat(value, allow_let, pos, strict), 163 | } 164 | } 165 | 166 | pub fn check_loop_left<'a>(left: &LoopLeft<'a>, pos: Position) -> Res { 167 | let mut set = HashSet::new(); 168 | match left { 169 | LoopLeft::Expr(expr) => check_loop_left_expr(expr, pos, &mut set), 170 | LoopLeft::Pat(pat) => check_loop_left_pat(pat, pos, &mut set), 171 | LoopLeft::Variable(kind, decls) => { 172 | if matches!(kind, VarKind::Var(_)) { 173 | Ok(()) 174 | } else { 175 | check_loop_left_pat(&decls.id, pos, &mut set) 176 | } 177 | } 178 | } 179 | } 180 | 181 | pub fn check_loop_head_expr<'a>(left: &Expr<'a>, pos: Position) -> Res { 182 | log::debug!("check_loop_head_expr"); 183 | let mut set = HashSet::new(); 184 | match left { 185 | Expr::Array(ref a) => check_binding_array(&a.elements, pos, &mut set), 186 | Expr::Obj(ref o) => check_binding_obj(&o.props, pos, &mut set), 187 | Expr::Assign(ref a) => check_loop_left_expr(&*a.right, pos, &mut set), 188 | _ => Ok(()), 189 | } 190 | } 191 | 192 | fn check_binding_obj<'a>( 193 | obj: &[ListEntry<'a, ObjProp<'a>>], 194 | pos: Position, 195 | set: &mut HashSet>, 196 | ) -> Res { 197 | log::debug!("check_binding_obj"); 198 | for part in obj { 199 | if let ObjProp::Prop(prop) = &part.item { 200 | match prop { 201 | Prop::Init(inner) => { 202 | check_loop_left_prop_key(&inner.key.value, pos, set)?; 203 | } 204 | _ => return Err(Error::InvalidLHS(pos)), 205 | } 206 | } 207 | } 208 | Ok(()) 209 | } 210 | 211 | pub fn check_binding_array<'a>( 212 | a: &[ListEntry<'a, Option>>], 213 | pos: Position, 214 | set: &mut HashSet>, 215 | ) -> Res { 216 | log::debug!("check_binding_array"); 217 | for part in a { 218 | if let Some(part) = &part.item { 219 | if let Expr::Sequence(_) = part { 220 | return Err(Error::InvalidLHS(pos)); 221 | } 222 | check_loop_left_expr(part, pos, set)?; 223 | } 224 | } 225 | Ok(()) 226 | } 227 | 228 | fn check_loop_left_prop_key<'a>( 229 | prop: &PropKey<'a>, 230 | pos: Position, 231 | set: &mut HashSet>, 232 | ) -> Res { 233 | log::debug!("check_loop_left_prop_key"); 234 | match prop { 235 | PropKey::Expr(expr) => check_loop_left_expr(expr, pos, set), 236 | PropKey::Pat(pat) => check_loop_left_pat(pat, pos, set), 237 | _ => Err(Error::InvalidLHS(pos)), 238 | } 239 | } 240 | 241 | fn check_loop_left_expr<'a>( 242 | expr: &Expr<'a>, 243 | pos: Position, 244 | set: &mut HashSet>, 245 | ) -> Res { 246 | log::debug!("check_loop_left_expr"); 247 | match expr { 248 | Expr::Ident(ident) => { 249 | if !set.insert(ident.slice.source.clone()) { 250 | Err(Error::InvalidLHS(pos)) 251 | } else { 252 | Ok(()) 253 | } 254 | } 255 | _ => Ok(()), 256 | } 257 | } 258 | 259 | fn check_loop_left_pat<'a>(pat: &Pat<'a>, pos: Position, set: &mut HashSet>) -> Res { 260 | log::debug!("check_loop_left_pat"); 261 | match pat { 262 | Pat::Ident(ident) => { 263 | if !set.insert(ident.slice.source.clone()) { 264 | Err(Error::InvalidLHS(pos)) 265 | } else { 266 | Ok(()) 267 | } 268 | } 269 | Pat::Array(a) => { 270 | for p in &a.elements { 271 | if let Some(p) = &p.item { 272 | match p { 273 | ArrayPatPart::Expr(expr) => check_loop_left_expr(expr, pos, set)?, 274 | ArrayPatPart::Pat(pat) => check_loop_left_pat(pat, pos, set)?, 275 | ArrayPatPart::Rest(RestPat { dots: _, pat }) => { 276 | check_loop_left_pat(pat, pos, set)? 277 | } 278 | } 279 | } 280 | } 281 | Ok(()) 282 | } 283 | _ => Ok(()), 284 | } 285 | } 286 | 287 | #[inline] 288 | fn is_restricted_word(word: &Ident) -> bool { 289 | &word.slice.source == "eval" || &word.slice.source == "arguments" 290 | } 291 | /// Check if this &str is in the list of reserved 292 | /// words in the context of 'use strict' 293 | #[inline] 294 | fn is_strict_reserved(word: &Ident) -> bool { 295 | word.slice.source == "implements" 296 | || word.slice.source == "interface" 297 | || word.slice.source == "package" 298 | || word.slice.source == "private" 299 | || word.slice.source == "protected" 300 | || word.slice.source == "public" 301 | || word.slice.source == "static" 302 | || word.slice.source == "yield" 303 | || word.slice.source == "let" 304 | } 305 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! RESSA (Rusty ECMAScript Syntax Analyzer) 2 | //! A library for parsing js files 3 | //! 4 | //! The main interface for this library would be 5 | //! the `Parser` iterator. A parser is constructed 6 | //! either via the `::new()` function or a `Builder`. 7 | //! As part of the constructor, you have to provide 8 | //! the js you want to parse as an `&str`. 9 | //! 10 | //! Once constructed the parser will return a 11 | //! `ProgramPart` for each iteration. 12 | //! 13 | //! A very simple example might look like this 14 | //! ``` 15 | //! use ressa::Parser; 16 | //! use resast::prelude::*; 17 | //! fn main() { 18 | //! let js = "function helloWorld() { alert('Hello world'); }"; 19 | //! let p = Parser::new(&js).unwrap(); 20 | //! let f = ProgramPart::decl( 21 | //! Decl::Func( 22 | //! Func { 23 | //! id: Some(Ident::from("helloWorld")), 24 | //! params: Vec::new(), 25 | //! body: FuncBody( 26 | //! vec![ 27 | //! ProgramPart::Stmt( 28 | //! Stmt::Expr( 29 | //! Expr::Call( 30 | //! CallExpr { 31 | //! callee: Box::new( 32 | //! Expr::ident_from("alert") 33 | //! ), 34 | //! arguments: vec![ 35 | //! Expr::Lit( 36 | //! Lit::single_string_from("Hello world") 37 | //! ) 38 | //! ], 39 | //! } 40 | //! ) 41 | //! ) 42 | //! ) 43 | //! ] 44 | //! ), 45 | //! generator: false, 46 | //! is_async: false, 47 | //! } 48 | //! ) 49 | //! ); 50 | //! for part in p { 51 | //! // assert_eq!(part.unwrap(), f); 52 | //! } 53 | //! } 54 | //!``` 55 | //! checkout the `examples` folders for slightly larger 56 | //! examples. 57 | //! 58 | 59 | use ress::prelude::*; 60 | pub use ress::Span; 61 | 62 | mod comment_handler; 63 | mod error; 64 | mod formal_params; 65 | mod lexical_names; 66 | mod lhs; 67 | mod regex; 68 | pub mod spanned; 69 | 70 | pub use crate::comment_handler::CommentHandler; 71 | pub use crate::comment_handler::DefaultCommentHandler; 72 | pub use crate::error::Error; 73 | 74 | use resast::prelude::*; 75 | 76 | use std::collections::HashMap; 77 | 78 | /// The current configuration options. 79 | /// This will most likely increase over time 80 | struct Config { 81 | /// whether or not to tolerate a subset of errors 82 | tolerant: bool, 83 | } 84 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 85 | enum LabelKind { 86 | Iteration, 87 | Other, 88 | Unknown, 89 | } 90 | 91 | /// The current parsing context. 92 | /// This structure holds the relevant 93 | /// information to know when some 94 | /// text might behave differently 95 | /// depending on what has come before it 96 | struct Context<'a> { 97 | /// If the current JS should be treated 98 | /// as a JS module 99 | is_module: bool, 100 | /// If `in` is allowed as an identifier 101 | allow_in: bool, 102 | /// If a strict directive is allowed 103 | allow_strict_directive: bool, 104 | /// If `yield` is allowed as an identifier 105 | allow_yield: bool, 106 | /// If await is allowed as an identifier 107 | allow_await: bool, 108 | /// if super is allowed as a keyword 109 | allow_super: bool, 110 | /// if super is allowed to be part of a call expression 111 | /// allow_super should always be true when this is true 112 | /// but not the other way around. This is only valid in a 113 | /// constructor 114 | allow_super_call: bool, 115 | /// If we have found any possible naming errors 116 | /// which are not yet resolved 117 | first_covert_initialized_name_error: Option>, 118 | /// If the current expressions is an assignment target 119 | is_assignment_target: bool, 120 | /// If the current expression is a binding element 121 | is_binding_element: bool, 122 | /// If we have entered a function body 123 | in_function_body: bool, 124 | /// If we have entered a loop block 125 | in_iteration: bool, 126 | /// If we have entered a switch block 127 | in_switch: bool, 128 | /// The currently known labels, this applies 129 | /// to labels only, not all identifiers. Errors 130 | /// at that level would need to be handled by 131 | /// the calling scope 132 | label_set: HashMap<&'a str, LabelKind>, 133 | /// If the current scope has a `'use strict';` directive 134 | /// in the prelude 135 | strict: bool, 136 | lexical_names: lexical_names::DuplicateNameDetector<'a>, 137 | /// If the scanner has a pending line terminator 138 | /// before the next token 139 | has_line_term: bool, 140 | /// If we have passed the initial prelude where a valid 141 | /// `'use strict'` directive would exist 142 | past_prolog: bool, 143 | /// If we encounter an error, the iterator should stop 144 | errored: bool, 145 | /// If we find a directive with an octal escape 146 | /// we need to error if a 'use strict' directive 147 | /// is then found 148 | found_directive_octal_escape: bool, 149 | } 150 | 151 | impl Default for Config { 152 | fn default() -> Self { 153 | log::trace!("default config"); 154 | Self { tolerant: false } 155 | } 156 | } 157 | 158 | impl<'a> Default for Context<'a> { 159 | fn default() -> Self { 160 | log::trace!("default context",); 161 | Self { 162 | is_module: false, 163 | allow_await: true, 164 | allow_in: true, 165 | allow_strict_directive: true, 166 | allow_yield: true, 167 | allow_super: false, 168 | allow_super_call: false, 169 | first_covert_initialized_name_error: None, 170 | is_assignment_target: false, 171 | is_binding_element: false, 172 | in_function_body: false, 173 | in_iteration: false, 174 | in_switch: false, 175 | label_set: HashMap::new(), 176 | strict: false, 177 | lexical_names: lexical_names::DuplicateNameDetector::default(), 178 | has_line_term: false, 179 | past_prolog: false, 180 | errored: false, 181 | found_directive_octal_escape: false, 182 | } 183 | } 184 | } 185 | impl<'a> Context<'a> { 186 | #[tracing::instrument(level = "trace", skip(self))] 187 | pub fn set_allow_super(&mut self, value: bool) { 188 | self.allow_super = value; 189 | } 190 | #[tracing::instrument(level = "trace", skip(self))] 191 | pub fn set_is_assignment_target(&mut self, value: bool) -> bool { 192 | let old = self.is_assignment_target; 193 | self.is_assignment_target = value; 194 | old 195 | } 196 | #[tracing::instrument(level = "trace", skip(self))] 197 | pub fn set_is_binding_element(&mut self, value: bool) -> bool { 198 | let old = self.is_binding_element; 199 | self.is_binding_element = value; 200 | old 201 | } 202 | } 203 | /// This is used to create a `Parser` using 204 | /// the builder method 205 | #[derive(Default)] 206 | pub struct Builder<'b> { 207 | inner: crate::spanned::Builder<'b>, 208 | } 209 | 210 | impl<'b> Builder<'b> { 211 | pub fn new() -> Self { 212 | Self::default() 213 | } 214 | /// Enable or disable error tolerance 215 | /// default: `false` 216 | pub fn set_tolerant(&mut self, value: bool) { 217 | self.inner.set_tolerant(value); 218 | } 219 | /// Enable or disable error tolerance with a builder 220 | /// pattern 221 | /// default: `false` 222 | pub fn tolerant(mut self, value: bool) -> Self { 223 | self.set_tolerant(value); 224 | self 225 | } 226 | /// Set the parsing context to module or script 227 | /// default: `false` (script) 228 | pub fn set_module(&mut self, value: bool) { 229 | self.inner.set_module(value); 230 | } 231 | /// Set the parsing context to module or script 232 | /// with a builder pattern 233 | /// default: `false` (script) 234 | pub fn module(mut self, value: bool) -> Self { 235 | self.set_module(value); 236 | self 237 | } 238 | /// Set the js text that this parser would operate 239 | /// on 240 | pub fn set_js(&mut self, js: &'b str) { 241 | self.inner.set_js(js); 242 | } 243 | /// Set the js text that this parser would operate 244 | /// on with a builder pattern 245 | pub fn js(mut self, js: &'b str) -> Self { 246 | self.set_js(js); 247 | self 248 | } 249 | /// Complete the builder pattern returning 250 | /// `Result` 251 | pub fn build(self) -> Res> { 252 | let inner = self.inner.build()?; 253 | Ok(Parser { inner }) 254 | } 255 | } 256 | 257 | impl<'b> Builder<'b> { 258 | pub fn with_comment_handler(self, handler: CH) -> Res> 259 | where 260 | CH: CommentHandler<'b>, 261 | { 262 | let inner = self.inner.with_comment_handler(handler)?; 263 | Ok(Parser { inner }) 264 | } 265 | } 266 | 267 | /// This is the primary interface that you would interact with. 268 | /// There are two main ways to use it, the first is to utilize 269 | /// the `Iterator` implementation. Each iteration will return 270 | /// a `Result`. 271 | /// The other option is to use the `parse` method, which is just 272 | /// a wrapper around the `collect` method on `Iterator`, however 273 | /// the final result will be a `Result` and the 274 | /// `ProgramPart` collection will be the inner data. Since modern 275 | /// js allows for both `Module`s as well as `Script`s, these will be 276 | /// the two `enum` variants. 277 | pub struct Parser<'a, CH> { 278 | inner: crate::spanned::Parser<'a, CH>, 279 | } 280 | /// The start/end index of a line 281 | #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] 282 | pub struct Line { 283 | start: usize, 284 | end: usize, 285 | } 286 | /// The result type for the Parser operations 287 | type Res = Result; 288 | 289 | impl<'a> Parser<'a, DefaultCommentHandler> { 290 | /// Create a new parser with the provided 291 | /// javascript 292 | /// This will default to parsing in the 293 | /// script context and discard comments. 294 | /// If you wanted change this behavior 295 | /// utilize the `Builder` pattern 296 | pub fn new(text: &'a str) -> Res { 297 | let inner = crate::spanned::Parser::new(text)?; 298 | Ok(Self { inner }) 299 | } 300 | } 301 | 302 | impl<'a> Parser<'a, ()> { 303 | pub fn builder() -> Builder<'a> { 304 | Builder::new() 305 | } 306 | } 307 | 308 | impl<'b, CH> Parser<'b, CH> 309 | where 310 | CH: CommentHandler<'b> + Sized, 311 | { 312 | /// Wrapper around the `Iterator` implementation for 313 | /// Parser 314 | /// ``` 315 | /// extern crate ressa; 316 | /// use ressa::Parser; 317 | /// use resast::prelude::*; 318 | /// fn main() { 319 | /// let js = "function helloWorld() { alert('Hello world'); }"; 320 | /// let mut p = Parser::new(&js).unwrap(); 321 | /// let call = CallExpr { 322 | /// callee: Box::new(Expr::ident_from("alert")), 323 | /// arguments: vec![Expr::Lit(Lit::single_string_from("Hello world"))], 324 | /// }; 325 | /// let expectation = Program::Script(vec![ProgramPart::Decl(Decl::Func(Func { 326 | /// id: Some(Ident::from("helloWorld")), 327 | /// params: Vec::new(), 328 | /// body: FuncBody(vec![ProgramPart::Stmt(Stmt::Expr(Expr::Call(call)))]), 329 | /// generator: false, 330 | /// is_async: false, 331 | /// }))]); 332 | /// let program = p.parse().unwrap(); 333 | /// //assert_eq!(program, expectation); 334 | /// } 335 | /// ``` 336 | pub fn parse(&mut self) -> Res { 337 | let ret = self.inner.parse()?; 338 | Ok(ret.into()) 339 | } 340 | 341 | pub fn next_position(&self) -> SourceLocation { 342 | self.inner.next_position() 343 | } 344 | 345 | pub fn comment_handler(&self) -> &CH { 346 | &self.inner.comment_handler 347 | } 348 | pub fn comment_handler_mut(&mut self) -> &mut CH { 349 | &mut self.inner.comment_handler 350 | } 351 | } 352 | 353 | impl<'b, CH> Iterator for Parser<'b, CH> 354 | where 355 | CH: CommentHandler<'b> + Sized, 356 | { 357 | type Item = Res>; 358 | fn next(&mut self) -> Option { 359 | let ret = self.inner.next()?; 360 | Some(ret.map(Into::into)) 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /src/regex.rs: -------------------------------------------------------------------------------- 1 | use super::{Error, Res}; 2 | use res_regex::RegexParser; 3 | /// Validate that an already parsed regular expression 4 | /// literal does not contain any illegal constructs 5 | /// like a duplicate flag or invalid class range 6 | pub fn validate_regex<'a>(regex: &'a str) -> Res<()> { 7 | RegexParser::new(®ex) 8 | .map_err(|e| Error::Other(Box::new(e)))? 9 | .validate() 10 | .map_err(|e| Error::Other(Box::new(e)))?; 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /tests/comment_handler.rs: -------------------------------------------------------------------------------- 1 | use ress::prelude::*; 2 | #[test] 3 | fn comment_handler1() { 4 | let js = " 5 | /** 6 | * @param s {string} The string to trim 7 | * @returns {string} 8 | */ 9 | function trimString(s) { 10 | return s.trim(); 11 | } 12 | "; 13 | let mut docs = vec![]; 14 | let ch = |comments: Item<&str>| { 15 | if let Token::Comment(comment) = comments.token { 16 | docs.push(comment.to_string()) 17 | } 18 | }; 19 | let _ = ressa::Parser::builder() 20 | .js(js) 21 | .with_comment_handler(ch) 22 | .expect("failed to create parser") 23 | .parse() 24 | .expect("failed to parse js"); 25 | assert!(docs.len() == 1, "docs not updated for comment"); 26 | assert_eq!( 27 | docs[0], 28 | "/** 29 | * @param s {string} The string to trim 30 | * @returns {string} 31 | */" 32 | ); 33 | } 34 | 35 | #[test] 36 | fn comment_handler2() { 37 | let js = " 38 | /** 39 | * @param s {string} The string to trim 40 | * @returns {string} 41 | */ 42 | function trimString(s) { 43 | /** 44 | * @param s {string} The string to trim 45 | * @returns {string} 46 | */ 47 | function trimStringInner(s) { 48 | return s.trim(); 49 | } 50 | return trimStringInner(s); 51 | }"; 52 | let mut docs = vec![]; 53 | let ch = |comments: Item<&str>| { 54 | if let Token::Comment(comment) = comments.token { 55 | docs.push(comment.to_string()) 56 | } 57 | }; 58 | let _ = ressa::Parser::builder() 59 | .js(js) 60 | .with_comment_handler(ch) 61 | .expect("failed to create parser") 62 | .parse() 63 | .expect("failed to parse js"); 64 | assert!(docs.len() == 2, "docs not updated for comment"); 65 | assert_eq!( 66 | docs[0], 67 | "/** 68 | * @param s {string} The string to trim 69 | * @returns {string} 70 | */" 71 | ); 72 | assert_eq!( 73 | docs[1], 74 | "/** 75 | * @param s {string} The string to trim 76 | * @returns {string} 77 | */" 78 | ); 79 | } 80 | 81 | #[test] 82 | fn comment_handler3() { 83 | use ressa::CommentHandler; 84 | #[derive(Clone)] 85 | struct Ch { 86 | pub comments: Vec<(SourceLocation, String)>, 87 | } 88 | impl Ch { 89 | fn new() -> Self { 90 | Self { comments: vec![] } 91 | } 92 | } 93 | impl<'a> CommentHandler<'a> for Ch { 94 | fn handle_comment(&mut self, comment: Item<&'a str>) { 95 | let loc = comment.location; 96 | if let Token::Comment(comment) = comment.token { 97 | let s = comment.to_string(); 98 | self.comments.push((loc, s)) 99 | } 100 | } 101 | } 102 | let js = " 103 | /** 104 | * @param s {string} The string to trim 105 | * @returns {string} 106 | */ 107 | function trimString(s) { 108 | /** 109 | * @param s {string} The string to trim 110 | * @returns {string} 111 | */ 112 | function trimStringInner(s) { 113 | return s.trim(); 114 | } 115 | return trimStringInner(s); 116 | }"; 117 | let ch = Ch::new(); 118 | let mut p = ressa::Parser::builder() 119 | .js(js) 120 | .with_comment_handler(ch) 121 | .expect("failed to create parser"); 122 | let _res = p.parse().expect("failed to parse js"); 123 | assert_eq!(p.comment_handler().comments.len(), 2); 124 | assert_eq!( 125 | p.comment_handler().comments[0].1, 126 | "/** 127 | * @param s {string} The string to trim 128 | * @returns {string} 129 | */" 130 | ); 131 | assert_eq!( 132 | p.comment_handler().comments[1].1, 133 | "/** 134 | * @param s {string} The string to trim 135 | * @returns {string} 136 | */" 137 | ); 138 | } 139 | -------------------------------------------------------------------------------- /tests/everything_js.rs: -------------------------------------------------------------------------------- 1 | mod libs_common; 2 | use env_logger; 3 | use libs_common::{get_js_file, EverythingVersion, Lib}; 4 | 5 | use ressa::Parser; 6 | #[test] 7 | fn es5() { 8 | let _ = env_logger::builder().is_test(true).try_init().ok(); 9 | log::info!("ES5"); 10 | let path = Lib::Everything(EverythingVersion::Es5).path(); 11 | log::debug!("path: {:?}", path); 12 | let js = get_js_file(&path).unwrap_or_else(|e| panic!("Faield to get {:?}\n{}", path, e)); 13 | let mut p = Parser::new(&js).expect("Failed to create parser"); 14 | let tokens = p.parse().unwrap(); 15 | insta::assert_debug_snapshot!(tokens); 16 | } 17 | 18 | #[test] 19 | fn es2015_script() { 20 | let _ = env_logger::builder().is_test(true).try_init().ok(); 21 | log::info!("ES2015 Script"); 22 | let path = Lib::Everything(EverythingVersion::Es2015Script).path(); 23 | let js = get_js_file(&path).expect(&format!("Failed to get {:?}", path)); 24 | let mut p = Parser::new(&js).expect("Failed to create parser"); 25 | let tokens = p.parse().unwrap(); 26 | insta::assert_debug_snapshot!(tokens); 27 | } 28 | 29 | #[test] 30 | fn es2015_module() { 31 | log::info!("ES2015 Module"); 32 | let _ = env_logger::builder().is_test(true).try_init().ok(); 33 | let path = Lib::Everything(EverythingVersion::Es2015Module).path(); 34 | let js = get_js_file(&path).expect(&format!("Failed to get {:?}", path)); 35 | let mut p = ressa::spanned::Parser::builder() 36 | .module(true) 37 | .js(&js) 38 | .build() 39 | .expect("Failed to create parser"); 40 | let tokens = p.parse().unwrap(); 41 | insta::assert_debug_snapshot!(tokens); 42 | // only one default export is allowed so these must be run ad-hoc 43 | let js_list = vec![ 44 | "export default function (){}", 45 | "export default function* i16(){}", 46 | "export default function* (){}", 47 | "export default class i17 {}", 48 | "export default class i18 extends i19 {}", 49 | "export default class {}", 50 | "export default x = 0;", 51 | "export default 0;", 52 | "export default (0, 1);", 53 | ]; 54 | for export in js_list { 55 | let p = Parser::builder() 56 | .module(true) 57 | .js(export) 58 | .build() 59 | .expect("Failed to create parser"); 60 | let res: Vec<_> = p 61 | .map(|i| match i { 62 | Ok(i) => i, 63 | Err(e) => panic!("Error parsing {}\n{}", export, e), 64 | }) 65 | .collect(); 66 | insta::assert_debug_snapshot!(res) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/libs_common.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | use std::{fs::read_to_string, io::Error}; 3 | 4 | #[derive(Clone, Copy, Debug)] 5 | pub enum Lib { 6 | Jquery, 7 | Angular, 8 | React, 9 | ReactDom, 10 | Vue, 11 | Moment, 12 | Dexie, 13 | Everything(EverythingVersion), 14 | } 15 | #[derive(Clone, Copy, Debug)] 16 | pub enum EverythingVersion { 17 | Es5, 18 | Es2015Module, 19 | Es2015Script, 20 | } 21 | 22 | impl EverythingVersion { 23 | fn file_name(&self) -> &str { 24 | match self { 25 | EverythingVersion::Es5 => "es5.js", 26 | EverythingVersion::Es2015Script => "es2015-script.js", 27 | EverythingVersion::Es2015Module => "es2015-module.js", 28 | } 29 | } 30 | } 31 | 32 | impl Lib { 33 | pub fn path(&self) -> String { 34 | match self { 35 | Lib::Jquery => "node_modules/jquery/dist/jquery.js".into(), 36 | Lib::Angular => "node_modules/angular/angular.js".into(), 37 | Lib::React => "node_modules/react/umd/react.development.js".into(), 38 | Lib::ReactDom => "node_modules/react-dom/umd/react-dom.development.js".into(), 39 | Lib::Vue => "node_modules/vue/dist/vue.js".into(), 40 | Lib::Moment => "node_modules/moment/moment.js".into(), 41 | Lib::Dexie => "node_modules/dexie/dist/dexie.js".into(), 42 | Lib::Everything(kind) => format!("node_modules/everything.js/{}", kind.file_name()), 43 | } 44 | } 45 | 46 | pub fn min_path(&self) -> Option { 47 | match self { 48 | Lib::Jquery => Some("node_modules/jquery/dist/jquery.min.js".into()), 49 | Lib::Angular => Some("node_modules/angular/angular.min.js".into()), 50 | Lib::React => Some("node_modules/react/umd/react.production.min.js".into()), 51 | Lib::ReactDom => Some("node_modules/react-dom/umd/react-dom.production.min.js".into()), 52 | Lib::Vue => Some("node_modules/vue/dist/vue.js".into()), 53 | Lib::Moment => Some("node_modules/moment/min/moment.min.js".into()), 54 | Lib::Dexie => Some("node_modules/dexie/dist/dexie.min.js".into()), 55 | _ => None, 56 | } 57 | } 58 | } 59 | 60 | pub fn get_js_file(path: impl AsRef<::std::path::Path>) -> Result { 61 | let path = path.as_ref(); 62 | if !path.exists() { 63 | npm_install()?; 64 | if !path.exists() { 65 | panic!("npm install failed to make {:?} available", path); 66 | } 67 | } 68 | read_to_string(path) 69 | } 70 | 71 | pub fn npm_install() -> Result<(), Error> { 72 | let mut c = ::std::process::Command::new("npm"); 73 | c.arg("i"); 74 | c.output()?; 75 | Ok(()) 76 | } 77 | -------------------------------------------------------------------------------- /tests/major_libs.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use env_logger; 3 | use ressa::Parser; 4 | 5 | mod libs_common; 6 | 7 | use libs_common::Lib; 8 | 9 | #[test] 10 | fn angular1() { 11 | let (normal, min) = get_js(Lib::Angular).expect("Unable to get angular js"); 12 | run_test("angular", normal, min); 13 | } 14 | #[test] 15 | fn react_core() { 16 | let (normal, min) = get_js(Lib::React).expect("Unable to get react js"); 17 | run_test("react", normal, min); 18 | } 19 | 20 | #[test] 21 | fn react_dom() { 22 | let (normal, min) = get_js(Lib::ReactDom).expect("Unable to get react-dom js"); 23 | run_test("react-dom", normal, min); 24 | } 25 | 26 | #[test] 27 | fn vue() { 28 | let (normal, min) = get_js(Lib::Vue).expect("Unable to get vue js"); 29 | run_test("vue", normal, min); 30 | } 31 | #[test] 32 | fn vue_esm() { 33 | let js = ::std::fs::read_to_string("node_modules/vue/dist/vue.esm.js").unwrap(); 34 | run_test("vue_module", js, String::new()); 35 | } 36 | 37 | #[test] 38 | fn jquery() { 39 | let (normal, min) = get_js(Lib::Jquery).expect("Unable to get jquery js"); 40 | run_test("jquery", normal, min); 41 | } 42 | 43 | #[test] 44 | fn moment() { 45 | let (normal, min) = get_js(Lib::Moment).expect("Unable to get moment js"); 46 | run_test("moment", normal, min); 47 | } 48 | 49 | #[test] 50 | fn dexie() { 51 | let (normal, min) = get_js(Lib::Dexie).expect("Unable to get dexie js"); 52 | run_test("dexie", normal, min); 53 | } 54 | 55 | fn run_test(name: &str, normal: String, min: String) { 56 | let _ = env_logger::builder().is_test(true).try_init().ok(); 57 | println!("parsing: {} chars", min.len()); 58 | let mut p = Parser::builder() 59 | .js(&normal) 60 | .module(name.contains("module")) 61 | .build() 62 | .expect(&format!("Unable to create {} parser", name)); 63 | let r = p.parse(); 64 | handle_result(r, name); 65 | let mut p = Parser::builder() 66 | .js(&normal) 67 | .module(name.contains("module")) 68 | .build() 69 | .expect(&format!("Unable to create {}.min", name)); 70 | let r = p.parse(); 71 | handle_result(r, &format!("{}.min", name)); 72 | } 73 | 74 | fn handle_result<'a>( 75 | result: Result, ressa::Error>, 76 | name: &str, 77 | ) -> resast::Program<'a> { 78 | match result { 79 | Ok(result) => result, 80 | Err(e) => panic!("Unable to parse {0}\n{1}\n{1:?}", name, e), 81 | } 82 | } 83 | 84 | fn get_js(l: Lib) -> Result<(String, String), ::std::io::Error> { 85 | Ok((get_normal_js(l)?, get_min_js(l)?)) 86 | } 87 | 88 | fn get_normal_js(l: Lib) -> Result { 89 | libs_common::get_js_file(l.path()) 90 | } 91 | 92 | fn get_min_js(l: Lib) -> Result { 93 | if let Some(p) = l.min_path() { 94 | libs_common::get_js_file(&p) 95 | } else { 96 | Err(::std::io::Error::new( 97 | ::std::io::ErrorKind::NotFound, 98 | format!("No min path for lib: {:?}", l), 99 | )) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/snapshots/everything_js__es2015_module-10.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/everything_js.rs 3 | expression: res 4 | --- 5 | [ 6 | Decl( 7 | Export( 8 | Default( 9 | Expr( 10 | Sequence( 11 | [ 12 | Lit( 13 | Number( 14 | "0", 15 | ), 16 | ), 17 | Lit( 18 | Number( 19 | "1", 20 | ), 21 | ), 22 | ], 23 | ), 24 | ), 25 | ), 26 | ), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /tests/snapshots/everything_js__es2015_module-2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/everything_js.rs 3 | expression: res 4 | --- 5 | [ 6 | Decl( 7 | Export( 8 | Default( 9 | Decl( 10 | Func( 11 | Func { 12 | id: None, 13 | params: [], 14 | body: FuncBody( 15 | [], 16 | ), 17 | generator: false, 18 | is_async: false, 19 | }, 20 | ), 21 | ), 22 | ), 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /tests/snapshots/everything_js__es2015_module-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/everything_js.rs 3 | expression: res 4 | --- 5 | [ 6 | Decl( 7 | Export( 8 | Default( 9 | Decl( 10 | Func( 11 | Func { 12 | id: Some( 13 | Ident { 14 | name: "i16", 15 | }, 16 | ), 17 | params: [], 18 | body: FuncBody( 19 | [], 20 | ), 21 | generator: true, 22 | is_async: false, 23 | }, 24 | ), 25 | ), 26 | ), 27 | ), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /tests/snapshots/everything_js__es2015_module-4.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/everything_js.rs 3 | expression: res 4 | --- 5 | [ 6 | Decl( 7 | Export( 8 | Default( 9 | Decl( 10 | Func( 11 | Func { 12 | id: None, 13 | params: [], 14 | body: FuncBody( 15 | [], 16 | ), 17 | generator: true, 18 | is_async: false, 19 | }, 20 | ), 21 | ), 22 | ), 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /tests/snapshots/everything_js__es2015_module-5.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/everything_js.rs 3 | expression: res 4 | --- 5 | [ 6 | Decl( 7 | Export( 8 | Default( 9 | Decl( 10 | Class( 11 | Class { 12 | id: Some( 13 | Ident { 14 | name: "i17", 15 | }, 16 | ), 17 | super_class: None, 18 | body: ClassBody( 19 | [], 20 | ), 21 | }, 22 | ), 23 | ), 24 | ), 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /tests/snapshots/everything_js__es2015_module-6.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/everything_js.rs 3 | expression: res 4 | --- 5 | [ 6 | Decl( 7 | Export( 8 | Default( 9 | Decl( 10 | Class( 11 | Class { 12 | id: Some( 13 | Ident { 14 | name: "i18", 15 | }, 16 | ), 17 | super_class: Some( 18 | Ident( 19 | Ident { 20 | name: "i19", 21 | }, 22 | ), 23 | ), 24 | body: ClassBody( 25 | [], 26 | ), 27 | }, 28 | ), 29 | ), 30 | ), 31 | ), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /tests/snapshots/everything_js__es2015_module-7.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/everything_js.rs 3 | expression: res 4 | --- 5 | [ 6 | Decl( 7 | Export( 8 | Default( 9 | Decl( 10 | Class( 11 | Class { 12 | id: None, 13 | super_class: None, 14 | body: ClassBody( 15 | [], 16 | ), 17 | }, 18 | ), 19 | ), 20 | ), 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /tests/snapshots/everything_js__es2015_module-8.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/everything_js.rs 3 | expression: res 4 | --- 5 | [ 6 | Decl( 7 | Export( 8 | Default( 9 | Expr( 10 | Assign( 11 | AssignExpr { 12 | operator: Equal, 13 | left: Expr( 14 | Ident( 15 | Ident { 16 | name: "x", 17 | }, 18 | ), 19 | ), 20 | right: Lit( 21 | Number( 22 | "0", 23 | ), 24 | ), 25 | }, 26 | ), 27 | ), 28 | ), 29 | ), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /tests/snapshots/everything_js__es2015_module-9.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/everything_js.rs 3 | expression: res 4 | --- 5 | [ 6 | Decl( 7 | Export( 8 | Default( 9 | Expr( 10 | Lit( 11 | Number( 12 | "0", 13 | ), 14 | ), 15 | ), 16 | ), 17 | ), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /tests/snippets.rs: -------------------------------------------------------------------------------- 1 | use resast::prelude::*; 2 | use ressa::*; 3 | use std::borrow::Cow; 4 | #[test] 5 | fn doc1() { 6 | let js = "function helloWorld() { alert('Hello world'); }"; 7 | let p = Parser::new(&js).unwrap(); 8 | let f = ProgramPart::decl(Decl::Func(Func { 9 | id: Some(Ident::from("helloWorld")), 10 | params: vec![], 11 | body: FuncBody(vec![ProgramPart::Stmt(Stmt::Expr(Expr::Call(CallExpr { 12 | callee: Box::new(Expr::ident_from("alert")), 13 | arguments: vec![Expr::Lit(Lit::single_string_from("Hello world"))], 14 | })))]), 15 | generator: false, 16 | is_async: false, 17 | })); 18 | for part in p { 19 | assert_eq!(part.unwrap(), f); 20 | } 21 | } 22 | 23 | #[test] 24 | fn readme_iter_example() { 25 | let js = "function helloWorld() { alert('Hello world'); }"; 26 | let p = Parser::new(&js).unwrap(); 27 | let f = ProgramPart::decl(Decl::Func(Func { 28 | id: Some(Ident::from("helloWorld")), 29 | params: vec![], 30 | body: FuncBody(vec![ProgramPart::Stmt(Stmt::Expr(Expr::Call(CallExpr { 31 | callee: Box::new(Expr::ident_from("alert")), 32 | arguments: vec![Expr::Lit(Lit::String(StringLit::Single(Cow::Owned( 33 | "Hello world".to_string(), 34 | ))))], 35 | })))]), 36 | generator: false, 37 | is_async: false, 38 | })); 39 | for part in p { 40 | assert_eq!(part.unwrap(), f); 41 | } 42 | } 43 | 44 | #[test] 45 | fn arrow_func_args() { 46 | let js = "(a, b = 0, [c,, d = 0, ...e], {f, g: h, i = 0, i: j = 0}, ...k) => {;};"; 47 | let mut parser = Parser::new(&js).unwrap(); 48 | let _parsed = parser.parse().unwrap(); 49 | } 50 | 51 | #[test] 52 | fn destructuring_default() { 53 | let _ = env_logger::builder().is_test(true).try_init().ok(); 54 | let js = "[a = {y: 2}, a.x = 1] = [];"; 55 | let mut parser = Parser::new(js).expect("failed to create parser"); 56 | parser.parse().expect("failed to parser js"); 57 | } 58 | #[test] 59 | fn destructuring_obj() { 60 | let _ = env_logger::builder().is_test(true).try_init().ok(); 61 | let js = "0, [...{} [throwlhs()]] = iterable;"; 62 | let mut parser = Parser::new(js).expect("failed to create parser"); 63 | parser.parse().expect("failed to parser js"); 64 | } 65 | 66 | #[test] 67 | fn strict_global_yield() { 68 | let _ = env_logger::builder().is_test(true).try_init().ok(); 69 | let js = "'use strict' 70 | yield; 71 | "; 72 | let mut parser = Parser::new(js).expect("failed to create parser"); 73 | let expect = parser.parse(); 74 | if let Err(ressa::Error::NonStrictFeatureInStrictContext(_, _)) = expect { 75 | () 76 | } else { 77 | panic!("Incorrectly parsed reserved word as identifier"); 78 | } 79 | } 80 | 81 | #[test] 82 | fn new_line_in_fat_arrow() { 83 | let js = "var af = x 84 | => x;"; 85 | let mut parser = Parser::new(js).expect("failed to create parser"); 86 | let expect = parser.parse(); 87 | if let Err(ressa::Error::NewLineAfterFatArrow(_)) = expect { 88 | () 89 | } else { 90 | panic!( 91 | "Incorrectly parsed arrow function with new line after =>\n{:?}", 92 | expect 93 | ); 94 | } 95 | } 96 | 97 | #[test] 98 | fn arguments_as_param_arrow() { 99 | let _ = env_logger::builder().is_test(true).try_init().ok(); 100 | let js = "'use strict'; 101 | var x = arguments => arguments;"; 102 | let mut parser = Parser::new(js).expect("failed to create parser"); 103 | let expect = parser.parse(); 104 | if let Err(ressa::Error::StrictModeArgumentsOrEval(_)) = expect { 105 | () 106 | } else { 107 | panic!("Incorrectly parsed arguments as param in strict mode"); 108 | } 109 | } 110 | 111 | #[test] 112 | fn duplicate_proto() { 113 | let _ = env_logger::builder().is_test(true).try_init().ok(); 114 | let js = "({ 115 | __proto__: Number, 116 | '__proto__': Number, 117 | });"; 118 | let mut parser = Parser::new(js).expect("failed to create parser"); 119 | let expect = parser.parse(); 120 | if let Err(ressa::Error::Redecl(_, _)) = expect { 121 | () 122 | } else { 123 | panic!( 124 | "Incorrectly parsed multiple __proto__ properties:\n\t{:?}", 125 | expect 126 | ); 127 | } 128 | } 129 | #[test] 130 | #[should_panic = "expected to fail on super call in function"] 131 | fn super_in_func() { 132 | let _ = env_logger::builder().is_test(true).try_init().ok(); 133 | let js = "function() { super() }"; 134 | let mut p = Parser::new(js).unwrap(); 135 | p.parse() 136 | .expect("expected to fail on super call in function"); 137 | } 138 | 139 | #[test] 140 | fn super_in_ctor() { 141 | let _ = env_logger::builder().is_test(true).try_init().ok(); 142 | let js = " 143 | class A {} 144 | class B extends A { 145 | constructor() { 146 | super() 147 | } 148 | }"; 149 | let mut p = Parser::new(js).unwrap(); 150 | p.parse().expect("failed to handle super call in ctor"); 151 | } 152 | 153 | #[test] 154 | fn super_in_method() { 155 | let _ = env_logger::builder().is_test(true).try_init().ok(); 156 | let js = " 157 | class A {} 158 | class B extends A { 159 | thing() { 160 | return super.stuff; 161 | } 162 | }"; 163 | let mut p = Parser::new(js).unwrap(); 164 | p.parse() 165 | .expect("failed to handle super property in method"); 166 | } 167 | 168 | #[test] 169 | fn super_in_async_method() { 170 | run_test( 171 | "var x = { async method(x = super.method()) { await 1; } }", 172 | false, 173 | ) 174 | .unwrap(); 175 | } 176 | 177 | #[test] 178 | #[should_panic = "super calls should only be allowed in ctors"] 179 | fn super_in_method_neg() { 180 | let _ = env_logger::builder().is_test(true).try_init().ok(); 181 | let js = " 182 | class A {} 183 | class B { 184 | thing() { 185 | super(); 186 | } 187 | }"; 188 | let mut p = Parser::new(js).unwrap(); 189 | p.parse() 190 | .expect("super calls should only be allowed in ctors"); 191 | } 192 | #[test] 193 | #[should_panic = "super is invalid in an arrow"] 194 | fn super_in_arrow_func() { 195 | let _ = env_logger::builder().is_test(true).try_init().ok(); 196 | let js = "() => super();"; 197 | let mut p = Parser::new(js).unwrap(); 198 | p.parse().expect("super is invalid in an arrow"); 199 | } 200 | #[test] 201 | fn super_in_lit_getter() { 202 | let _ = env_logger::builder().is_test(true).try_init().ok(); 203 | let js = "({ 204 | get a() { return super.stuff; } 205 | });"; 206 | let mut p = Parser::new(js).unwrap(); 207 | p.parse().unwrap(); 208 | } 209 | 210 | #[test] 211 | #[should_panic = "assignment not allowed in `for in` lhs"] 212 | fn init_in_for_in_loop() { 213 | let _ = env_logger::builder().is_test(true).try_init().ok(); 214 | let js = "for (a = 0 in {}) {}"; 215 | let mut p = Parser::new(js).unwrap(); 216 | p.parse().expect("assignment not allowed in `for in` lhs"); 217 | } 218 | 219 | #[test] 220 | #[should_panic = "assignment not allowed in `for in` lhs"] 221 | fn var_init_in_for_in_loop_strict() { 222 | let _ = env_logger::builder().is_test(true).try_init().ok(); 223 | let js = "'use strict'; 224 | for (var a = 0 in {}) {}"; 225 | let mut p = Parser::new(js).unwrap(); 226 | p.parse().expect("assignment not allowed in `for in` lhs"); 227 | } 228 | #[test] 229 | fn var_init_in_for_in_loop_not_strict() { 230 | let _ = env_logger::builder().is_test(true).try_init().ok(); 231 | let js = "for (var a = 0 in {}) {}"; 232 | let mut p = Parser::new(js).unwrap(); 233 | p.parse().expect("assignment not allowed in `for in` lhs"); 234 | } 235 | #[test] 236 | #[should_panic = "use strict not allowed with fancy params"] 237 | fn arrow_funct_non_simple_args_use_strict_in_body() { 238 | let _ = env_logger::builder().is_test(true).try_init().ok(); 239 | let js = "var x = (a = 0) => { 240 | 'use strict'; 241 | };"; 242 | let mut p = Parser::new(js).unwrap(); 243 | p.parse().expect("use strict not allowed with fancy params"); 244 | } 245 | 246 | #[test] 247 | fn nested_await_ident() { 248 | run_test( 249 | "var await; 250 | async function i() { 251 | function b() { 252 | await = 0; 253 | } 254 | }", 255 | false, 256 | ) 257 | .unwrap(); 258 | } 259 | 260 | #[test] 261 | fn nested_dupe_ident() { 262 | run_test( 263 | "(function() {})(function() { 264 | function offset (token, separator) { 265 | addFormatToken(token, 0, 0, function () { 266 | var offset = this.utcOffset(); 267 | var sign = '+'; 268 | if (offset < 0) { 269 | offset = -offset; 270 | sign = '-'; 271 | } 272 | return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); 273 | }); 274 | } 275 | })", 276 | false, 277 | ).unwrap(); 278 | } 279 | 280 | #[test] 281 | fn super_in_class_expr_ctor() { 282 | let _ = env_logger::builder().is_test(true).try_init().ok(); 283 | let js = "new class extends Other { 284 | constructor() { 285 | super(); 286 | } 287 | }"; 288 | let mut p = Parser::new(js).unwrap(); 289 | p.parse().expect("super in class expr ctor"); 290 | } 291 | #[test] 292 | fn line_term_comment() { 293 | let _ = env_logger::builder().is_test(true).try_init().ok(); 294 | let js = "''/* 295 | */''"; 296 | let mut parser = Parser::new(js).expect("failed to create parser"); 297 | parser.parse().unwrap(); 298 | } 299 | 300 | #[test] 301 | fn await_as_ident() { 302 | run_test("var await;", false).unwrap(); 303 | } 304 | #[test] 305 | #[should_panic = "await is always reserved in a module"] 306 | fn await_as_ident_module() { 307 | run_test("var await;", true).expect("await is always reserved in a module"); 308 | } 309 | #[test] 310 | fn await_as_ident_strict() { 311 | run_test("'use strict';var await;", false).unwrap(); 312 | } 313 | #[test] 314 | #[should_panic = "await is reserved in an async fn"] 315 | fn await_as_ident_async_fn() { 316 | let _ = env_logger::builder().is_test(true).try_init().ok(); 317 | let js = "async function() { var await = 0; }"; 318 | let mut p = Parser::builder().js(js).module(true).build().unwrap(); 319 | p.parse().expect("await is reserved in an async fn"); 320 | } 321 | 322 | #[test] 323 | #[should_panic = "export is reserved"] 324 | fn export_as_ident() { 325 | run_test(r"var expor\u0074;", false).expect("export is reserved"); 326 | } 327 | 328 | #[test] 329 | fn async_arrow_await() { 330 | run_test("async () => await 0;", false).unwrap(); 331 | } 332 | 333 | #[test] 334 | fn use_strict_in_complicated_arrow() { 335 | run_test("f = (a = 0) => b => { 'use strict'; };", false).unwrap(); 336 | } 337 | 338 | #[test] 339 | fn yield_as_param_in_method() { 340 | run_test("var x = { y(yield) { return yield; } };", false).unwrap(); 341 | } 342 | 343 | #[test] 344 | fn async_await() { 345 | run_test( 346 | "var f = async function() { 347 | try { 348 | await new Promise((r) => r()); 349 | } catch (e) { 350 | await new Promise((r, j) => j(e)); 351 | } finally { 352 | await new Promise((r) => r()); 353 | } 354 | }", 355 | false, 356 | ) 357 | .unwrap(); 358 | } 359 | 360 | #[test] 361 | fn for_in_head_let() { 362 | run_test("for(var let in {});", false).unwrap(); 363 | } 364 | 365 | #[test] 366 | fn import_aliased_eval() { 367 | run_test("import { eval as _eval } from './other.js';", true).unwrap(); 368 | } 369 | 370 | #[test] 371 | fn static_ident() { 372 | run_test("var static = 0;", false).unwrap(); 373 | } 374 | 375 | #[test] 376 | fn let_in_obj_init() { 377 | run_test( 378 | "var let = 1; 379 | var o = {let};", 380 | false, 381 | ) 382 | .unwrap(); 383 | } 384 | 385 | #[test] 386 | fn await_as_class_ident() { 387 | run_test("class await {}", false).unwrap(); 388 | } 389 | #[test] 390 | fn await_as_class_ident_expr() { 391 | run_test("var ctor = class a\\u0077ait {}", false).unwrap(); 392 | } 393 | 394 | #[test] 395 | fn let_in_for_loop() { 396 | run_test( 397 | "let = 1; 398 | for (let; ; ) 399 | break;", 400 | false, 401 | ) 402 | .unwrap(); 403 | } 404 | #[test] 405 | fn let_in_for_loop2() { 406 | run_test( 407 | "let = 1; 408 | for (let = 2; ; ) 409 | break;", 410 | false, 411 | ) 412 | .unwrap(); 413 | } 414 | 415 | #[test] 416 | fn async_obj_lit_method() { 417 | run_test("var x = { async m() { await 0 } }", false).unwrap(); 418 | } 419 | 420 | #[test] 421 | fn await_as_label() { 422 | run_test("await: 1;", false).unwrap(); 423 | } 424 | 425 | #[test] 426 | #[should_panic] 427 | fn await_as_default_in_param() { 428 | run_test("(q=await) => {}", false).unwrap(); 429 | } 430 | 431 | #[test] 432 | #[should_panic] 433 | fn duplicate_params_strict() { 434 | run_test( 435 | "'use strict'; 436 | function fn(x, x) { }", 437 | false, 438 | ) 439 | .unwrap(); 440 | } 441 | 442 | #[test] 443 | #[should_panic] 444 | fn duplicate_params_strict_inner() { 445 | run_test("function fn(x, x) { 'use strict' }", false).unwrap(); 446 | } 447 | #[test] 448 | #[should_panic] 449 | fn duplicate_params_strict_inner_expr() { 450 | run_test("(function (x, x) { 'use strict' })", false).unwrap(); 451 | } 452 | 453 | #[test] 454 | #[should_panic] 455 | fn duplicate_params_arrow() { 456 | run_test("var fn = (x, x) => {}", false).unwrap(); 457 | } 458 | #[test] 459 | #[should_panic] 460 | fn duplicate_params_arrow_array_pattern() { 461 | run_test("var fn = ([x, x]) => {}", false).unwrap(); 462 | } 463 | #[test] 464 | #[should_panic] 465 | fn duplicate_params_arrow_array_ident_pattern() { 466 | run_test("var fn = (x, [y, x]) => {}", false).unwrap(); 467 | } 468 | #[test] 469 | #[should_panic] 470 | fn duplicate_params_arrow_obj_pattern() { 471 | run_test("var fn = ({x, x}) => {}", false).unwrap(); 472 | } 473 | #[test] 474 | #[should_panic] 475 | fn duplicate_params_arrow_obj_ident_pattern() { 476 | run_test("var fn = (x, {y, x}) => {}", false).unwrap(); 477 | } 478 | 479 | #[test] 480 | fn html_comment_close() { 481 | run_test( 482 | "--> this is a comment 483 | --> also a comment", 484 | false, 485 | ) 486 | .unwrap() 487 | } 488 | #[test] 489 | #[should_panic] 490 | fn html_comment_close_module() { 491 | run_test("/**/--> this is a comment", true).unwrap() 492 | } 493 | #[test] 494 | #[should_panic] 495 | fn html_comment_close_not_first_token() { 496 | run_test(";--> this is not a comment", false).unwrap() 497 | } 498 | 499 | #[test] 500 | #[should_panic = "LexicalRedecl"] 501 | fn dupe_ident_let_then_var() { 502 | run_test("{let a; var a;}", false).unwrap() 503 | } 504 | #[test] 505 | #[should_panic = "LexicalRedecl"] 506 | fn dupe_in_switch_let_then_var() { 507 | run_test("switch (true) { case 1: let q; default: var q; }", false).unwrap(); 508 | } 509 | #[test] 510 | fn dupe_ident_var_then_var() { 511 | run_test("function q() { { var a; var a } }", false).unwrap() 512 | } 513 | 514 | #[test] 515 | fn dupe_simple_catch() { 516 | run_test("try { } catch (e) { var e = 'stuff'; }", false).unwrap(); 517 | } 518 | #[test] 519 | #[should_panic = "LexicalRedecl"] 520 | fn dupe_switch_func_then_let() { 521 | run_test( 522 | "switch (true) { case 0: function a() {}; default: let a; }", 523 | false, 524 | ) 525 | .unwrap(); 526 | } 527 | 528 | #[test] 529 | fn dupe_func_at_top() { 530 | run_test( 531 | " 532 | function f() { return; } 533 | function f() { return 0; } 534 | ", 535 | false, 536 | ) 537 | .unwrap(); 538 | } 539 | 540 | #[test] 541 | fn labeled_function() { 542 | run_test("label: function g() {}", false).unwrap(); 543 | } 544 | 545 | #[test] 546 | fn closure_argument_scoping() { 547 | run_test( 548 | "check(() => { 549 | let a = 0; 550 | let b = 1; 551 | }); 552 | check(() => { 553 | let a = 0; 554 | let b = 1; 555 | });", 556 | false, 557 | ) 558 | .unwrap(); 559 | } 560 | #[test] 561 | fn destructing_with_defaults_complicated() { 562 | run_test( 563 | " 564 | const {a: {a: b = 3} = {a: undefined}} = {}; 565 | var a = 0; 566 | ", 567 | false, 568 | ) 569 | .unwrap(); 570 | } 571 | 572 | #[test] 573 | #[should_panic = "ForOfNotSimple"] 574 | fn for_of_this_lhs() { 575 | run_test("for (this of []) ;", false).unwrap(); 576 | } 577 | 578 | #[test] 579 | #[should_panic = "InvalidStartOfExpressionStmt"] 580 | fn let_open_brace() { 581 | run_test( 582 | "for (var i in []) let 583 | [a] = 0;", 584 | false, 585 | ) 586 | .unwrap(); 587 | } 588 | #[test] 589 | #[should_panic] 590 | fn async_async_obj_prop() { 591 | run_test("({async async});", false).unwrap(); 592 | } 593 | #[test] 594 | #[should_panic] 595 | fn export_in_obj_method_body() { 596 | run_test("({ get m() { export default null; } });", true).unwrap(); 597 | } 598 | 599 | #[test] 600 | fn export_default_class() { 601 | run_test( 602 | " 603 | export default class Name { }", 604 | true, 605 | ) 606 | .unwrap(); 607 | } 608 | #[test] 609 | #[should_panic = "DuplicateExport"] 610 | fn duplicate_export() { 611 | run_test( 612 | "export default class Name {} 613 | export function Name() {}", 614 | true, 615 | ) 616 | .unwrap(); 617 | } 618 | #[test] 619 | #[should_panic = "DuplicateExport"] 620 | fn duplicate_export_prev_decl() { 621 | run_test( 622 | "let x = 0; 623 | export { x, x }", 624 | true, 625 | ) 626 | .unwrap(); 627 | } 628 | 629 | #[test] 630 | #[should_panic = "LexicalRedecl"] 631 | fn arrow_fn_dupe_param_lex() { 632 | run_test("(x, y) => { let x = 0; }", false).unwrap(); 633 | } 634 | 635 | #[test] 636 | #[should_panic] 637 | fn async_arrow_rest_trailing_comma() { 638 | run_test("(async (...a,) => { });", false).unwrap() 639 | } 640 | 641 | #[test] 642 | #[should_panic] 643 | fn async_function_dupe_param_lex() { 644 | run_test("async function f(a) { let a; }", false).unwrap(); 645 | } 646 | 647 | #[test] 648 | fn export_as_as_as() { 649 | run_test( 650 | " 651 | var as = null; 652 | export { 653 | as as as 654 | };", 655 | true, 656 | ) 657 | .unwrap(); 658 | } 659 | 660 | #[test] 661 | fn dupe_var_strict() { 662 | run_test( 663 | "'use strict'; 664 | function f(a) { 665 | var a; 666 | }", 667 | false, 668 | ) 669 | .unwrap(); 670 | } 671 | 672 | #[test] 673 | #[should_panic] 674 | fn strict_mode_string_oct_lit() { 675 | run_test("'use strict';'\\08'", false).unwrap(); 676 | } 677 | 678 | #[test] 679 | #[should_panic] 680 | fn strict_mode_after_oct_escape() { 681 | run_test(r#"'\07'; 'use strict';"#, false).unwrap(); 682 | } 683 | 684 | #[test] 685 | fn obj_init_arg() { 686 | run_test("({a = 2}) => a", false).unwrap(); 687 | } 688 | 689 | #[test] 690 | #[should_panic] 691 | fn obj_init_prop_init() { 692 | run_test("({a = 2})", false).unwrap(); 693 | } 694 | 695 | #[test] 696 | #[should_panic] 697 | fn strict_fn_body_arguments_as_arg() { 698 | run_test("function a(arguments) { 'use strict'; }", false).unwrap(); 699 | } 700 | 701 | #[test] 702 | #[should_panic] 703 | fn dupe_ident_in_loop_left() { 704 | run_test("for (const [a, a] of []);", false).unwrap(); 705 | } 706 | 707 | #[test] 708 | fn assign_multiple_props_nested() { 709 | run_test( 710 | "var a = { 711 | b: c += 1, 712 | d: e += 1, 713 | };", 714 | false, 715 | ) 716 | .unwrap(); 717 | } 718 | 719 | #[test] 720 | #[ignore] 721 | fn invalid_group_regression() { 722 | // TODO: named regex groups 723 | run_test(r#"var re = /(?a)|b/"#, false).unwrap(); 724 | } 725 | 726 | #[test] 727 | fn async_func_tokens() { 728 | let mut p = Parser::builder() 729 | .js("async function f() {}") 730 | .build() 731 | .unwrap(); 732 | let tokens = p.parse().unwrap(); 733 | assert_eq!( 734 | Program::Script(vec![ProgramPart::Decl(Decl::Func(Func { 735 | body: FuncBody(vec![]), 736 | generator: false, 737 | id: Some(Ident::new("f".to_owned())), 738 | is_async: true, 739 | params: vec![], 740 | }))]), 741 | tokens 742 | ); 743 | } 744 | 745 | #[test] 746 | fn func_decl_tokens() { 747 | let mut p = Parser::builder().js("function f() {}").build().unwrap(); 748 | let tokens = p.parse().unwrap(); 749 | assert_eq!( 750 | Program::Script(vec![ProgramPart::Decl(Decl::Func(Func { 751 | body: FuncBody(vec![]), 752 | generator: false, 753 | id: Some(Ident::new("f".to_owned())), 754 | is_async: false, 755 | params: vec![], 756 | }))]), 757 | tokens 758 | ); 759 | } 760 | 761 | #[test] 762 | fn class_extended_by_call() { 763 | env_logger::builder().is_test(true).try_init().ok(); 764 | let mut p = Parser::builder() 765 | .js("class C extends D() {}") 766 | .build() 767 | .unwrap(); 768 | let tokens = p.parse().unwrap(); 769 | let callee = Ident::new("D".to_owned()); 770 | let callee = Expr::Ident(callee); 771 | let callee = Box::new(callee); 772 | let super_class = CallExpr { 773 | callee, 774 | arguments: vec![], 775 | }; 776 | let super_class = Expr::Call(super_class); 777 | let super_class = Box::new(super_class); 778 | let super_class = Some(super_class); 779 | assert_eq!( 780 | Program::Script(vec![ProgramPart::Decl(Decl::Class(Class { 781 | body: ClassBody(vec![]), 782 | id: Some(Ident::new("C".to_owned())), 783 | super_class, 784 | }))]), 785 | tokens 786 | ); 787 | } 788 | #[test] 789 | fn class_anon_extended_by_call() { 790 | env_logger::builder().is_test(true).try_init().ok(); 791 | let mut p = Parser::builder() 792 | .js("let c = class extends D() {}") 793 | .build() 794 | .unwrap(); 795 | let tokens = p.parse().unwrap(); 796 | let callee = Ident::new("D".to_owned()); 797 | let callee = Expr::Ident(callee); 798 | let callee = Box::new(callee); 799 | let super_class = CallExpr { 800 | callee, 801 | arguments: vec![], 802 | }; 803 | let super_class = Expr::Call(super_class); 804 | let super_class = Box::new(super_class); 805 | let super_class = Some(super_class); 806 | assert_eq!( 807 | Program::Script(vec![ProgramPart::Decl(Decl::Var( 808 | VarKind::Let, 809 | vec![VarDecl { 810 | id: Pat::Ident(Ident::new("c".to_owned())), 811 | init: Some(Expr::Class(Class { 812 | body: ClassBody(vec![]), 813 | id: None, 814 | super_class, 815 | })) 816 | }] 817 | ))]), 818 | tokens 819 | ); 820 | } 821 | #[test] 822 | fn class_async_static_method() { 823 | env_logger::builder().is_test(true).try_init().ok(); 824 | let mut p = Parser::builder() 825 | .js("class C { 826 | static async m() {} 827 | }") 828 | .build() 829 | .unwrap(); 830 | let tokens = p.parse().unwrap(); 831 | 832 | assert_eq!( 833 | Program::Script(vec![ProgramPart::Decl(Decl::Class(Class { 834 | body: ClassBody(vec![Prop { 835 | key: PropKey::Expr(Expr::Ident(Ident::new("m".to_owned()))), 836 | value: PropValue::Expr(Expr::Func(Func { 837 | id: None, 838 | params: vec![], 839 | body: FuncBody(vec![]), 840 | generator: false, 841 | is_async: true 842 | })), 843 | kind: PropKind::Method, 844 | method: true, 845 | computed: false, 846 | short_hand: false, 847 | is_static: true 848 | }]), 849 | id: Some(Ident::new("C".to_owned())), 850 | super_class: None, 851 | }))]), 852 | tokens 853 | ); 854 | } 855 | 856 | #[test] 857 | fn class_method_export() { 858 | let js = r#" 859 | class A { 860 | foo(x) {} 861 | } 862 | 863 | export { A } 864 | "#; 865 | run_test(js, true).unwrap(); 866 | } 867 | 868 | #[test] 869 | fn export_then_assign_two_lines() { 870 | run_spanned_test( 871 | "export let a; 872 | a = 0;", 873 | true, 874 | ) 875 | .unwrap(); 876 | } 877 | 878 | #[test] 879 | fn export_then_assign_one_line() { 880 | run_spanned_test("export let a; a = 0;", true).unwrap(); 881 | } 882 | 883 | #[test] 884 | fn redecl_error_in_nested_arrow() { 885 | let js = r#"(() => { 886 | var a = [1]; 887 | let arrow = (b) => { 888 | var [b] = a; 889 | }; 890 | })();"#; 891 | run_test(js, false).unwrap(); 892 | } 893 | 894 | #[test] 895 | fn redecl_error_in_nested_funcs() { 896 | let js = r#"(function() { 897 | var a = [1]; 898 | let arrow = function(b) { 899 | var [b] = a; 900 | }; 901 | })();"#; 902 | run_test(js, false).unwrap(); 903 | } 904 | 905 | #[test] 906 | fn generator_prop() { 907 | env_logger::builder().is_test(true).try_init().ok(); 908 | let mut p = Parser::builder().js("({*g() {}})").build().unwrap(); 909 | let tokens = p.parse().unwrap(); 910 | 911 | assert_eq!( 912 | Program::Script(vec![ProgramPart::Stmt(Stmt::Expr(Expr::Obj(vec![ 913 | ObjProp::Prop(Prop { 914 | computed: false, 915 | is_static: false, 916 | key: PropKey::Expr(Expr::Ident(Ident::new("g".to_owned()))), 917 | kind: PropKind::Method, 918 | method: true, 919 | short_hand: false, 920 | value: PropValue::Expr(Expr::Func(Func { 921 | body: FuncBody(vec![]), 922 | generator: true, 923 | id: None, 924 | is_async: false, 925 | params: vec![], 926 | })) 927 | }) 928 | ])))]), 929 | tokens 930 | ); 931 | } 932 | 933 | #[test] 934 | fn super_tagged_template_in_ctor() { 935 | env_logger::builder().is_test(true).try_init().ok(); 936 | let mut p = Parser::builder() 937 | .js("class X { 938 | constructor() { 939 | super()`template` 940 | } 941 | }") 942 | .build() 943 | .unwrap(); 944 | let tokens = p.parse().unwrap(); 945 | 946 | assert_eq!( 947 | Program::Script(vec![ProgramPart::Decl(Decl::Class(Class { 948 | id: Some(Ident::from("X")), 949 | super_class: None, 950 | body: ClassBody(vec![Prop { 951 | computed: false, 952 | is_static: false, 953 | key: PropKey::Expr(Expr::Ident(Ident::from("constructor"))), 954 | kind: PropKind::Ctor, 955 | method: true, 956 | short_hand: false, 957 | value: PropValue::Expr(Expr::Func(Func { 958 | id: None, 959 | generator: false, 960 | is_async: false, 961 | params: vec![], 962 | body: FuncBody(vec![ProgramPart::Stmt(Stmt::Expr(Expr::TaggedTemplate( 963 | TaggedTemplateExpr { 964 | tag: Box::new(Expr::Call(CallExpr { 965 | callee: Box::new(Expr::Super), 966 | arguments: vec![], 967 | })), 968 | quasi: TemplateLit { 969 | expressions: vec![], 970 | quasis: vec![TemplateElement { 971 | tail: true, 972 | cooked: std::borrow::Cow::Borrowed("template"), 973 | raw: std::borrow::Cow::Borrowed("`template`"), 974 | }] 975 | } 976 | } 977 | )))]) 978 | })), 979 | }]) 980 | }))]), 981 | tokens 982 | ); 983 | } 984 | 985 | #[test] 986 | fn super_in_new_class_expr() { 987 | env_logger::builder().is_test(true).try_init().ok(); 988 | let mut p = Parser::builder() 989 | .js("new class extends X { constructor(a = (()=>{ super() })()) { } }") 990 | .build() 991 | .unwrap(); 992 | let tokens = p.parse().unwrap(); 993 | let call_super = CallExpr { 994 | callee: Box::new(Expr::Super), 995 | arguments: vec![], 996 | }; 997 | let arrow_call_super = ArrowFuncExpr { 998 | id: None, 999 | expression: false, 1000 | generator: false, 1001 | body: ArrowFuncBody::FuncBody(FuncBody(vec![ProgramPart::Stmt(Stmt::Expr(Expr::Call( 1002 | call_super, 1003 | )))])), 1004 | is_async: false, 1005 | params: vec![], 1006 | }; 1007 | let arrow_call_super = Expr::ArrowFunc(arrow_call_super); 1008 | let call_arrow = CallExpr { 1009 | callee: Box::new(arrow_call_super), 1010 | arguments: vec![], 1011 | }; 1012 | let call_arrow = Expr::Call(call_arrow); 1013 | let assign_left = Box::new(Pat::ident_from("a")); 1014 | let assign_arrow = AssignPat { 1015 | left: assign_left, 1016 | right: Box::new(call_arrow), 1017 | }; 1018 | let key = PropKey::Expr(Expr::ident_from("constructor")); 1019 | let value = Func { 1020 | id: None, 1021 | params: vec![FuncArg::Pat(Pat::Assign(assign_arrow))], 1022 | body: FuncBody(vec![]), 1023 | generator: false, 1024 | is_async: false, 1025 | }; 1026 | let value = PropValue::Expr(Expr::Func(value)); 1027 | let ctor = Prop { 1028 | computed: false, 1029 | is_static: false, 1030 | method: true, 1031 | short_hand: false, 1032 | kind: PropKind::Ctor, 1033 | key, 1034 | value, 1035 | }; 1036 | 1037 | assert_eq!( 1038 | Program::Script(vec![ProgramPart::Stmt(Stmt::Expr(Expr::New(NewExpr { 1039 | arguments: vec![], 1040 | callee: Box::new(Expr::Class(Class { 1041 | id: None, 1042 | super_class: Some(Box::new(Expr::Ident(Ident::from("X")))), 1043 | body: ClassBody(vec![ctor]) 1044 | })) 1045 | })))]), 1046 | tokens 1047 | ); 1048 | } 1049 | 1050 | #[test] 1051 | fn static_get_method() { 1052 | env_logger::builder().is_test(true).try_init().ok(); 1053 | let mut p = Parser::builder() 1054 | .js("class X { 1055 | static get e() {} 1056 | }") 1057 | .build() 1058 | .unwrap(); 1059 | let tokens = p.parse().unwrap(); 1060 | 1061 | assert_eq!( 1062 | Program::Script(vec![ProgramPart::Decl(Decl::Class(Class { 1063 | id: Some(Ident::from("X")), 1064 | super_class: None, 1065 | body: ClassBody(vec![Prop { 1066 | computed: false, 1067 | is_static: true, 1068 | key: PropKey::Expr(Expr::Ident(Ident::from("e"))), 1069 | kind: PropKind::Get, 1070 | method: false, 1071 | short_hand: false, 1072 | value: PropValue::Expr(Expr::Func(Func { 1073 | id: None, 1074 | generator: false, 1075 | is_async: false, 1076 | params: vec![], 1077 | body: FuncBody(vec![]) 1078 | })), 1079 | }]) 1080 | }))]), 1081 | tokens 1082 | ); 1083 | } 1084 | 1085 | #[test] 1086 | fn generator_method() { 1087 | env_logger::builder().is_test(true).try_init().ok(); 1088 | let mut p = Parser::builder() 1089 | .js("class X { 1090 | static *e() {} 1091 | }") 1092 | .build() 1093 | .unwrap(); 1094 | let tokens = p.parse().unwrap(); 1095 | 1096 | assert_eq!( 1097 | Program::Script(vec![ProgramPart::Decl(Decl::Class(Class { 1098 | id: Some(Ident::from("X")), 1099 | super_class: None, 1100 | body: ClassBody(vec![Prop { 1101 | computed: false, 1102 | is_static: true, 1103 | key: PropKey::Expr(Expr::Ident(Ident::from("e"))), 1104 | kind: PropKind::Method, 1105 | method: true, 1106 | short_hand: false, 1107 | value: PropValue::Expr(Expr::Func(Func { 1108 | id: None, 1109 | generator: true, 1110 | is_async: false, 1111 | params: vec![], 1112 | body: FuncBody(vec![]) 1113 | })), 1114 | }]) 1115 | }))]), 1116 | tokens 1117 | ); 1118 | } 1119 | 1120 | #[test] 1121 | fn export_all() { 1122 | env_logger::builder().is_test(true).try_init().ok(); 1123 | let mut p = Parser::builder() 1124 | .js("export * from 'module';") 1125 | .module(true) 1126 | .build() 1127 | .unwrap(); 1128 | let tokens = p.parse().unwrap(); 1129 | 1130 | assert_eq!( 1131 | Program::Mod(vec![ProgramPart::Decl(Decl::Export(Box::new( 1132 | ModExport::All(Lit::String(StringLit::Single(Cow::Borrowed("module")))) 1133 | )))]), 1134 | tokens 1135 | ); 1136 | } 1137 | 1138 | #[test] 1139 | fn for_lhs() { 1140 | env_logger::builder().is_test(true).try_init().ok(); 1141 | run_test("for(var x=(0 in[])in{});", false).unwrap(); 1142 | } 1143 | 1144 | #[test] 1145 | fn class_ctor_scope() { 1146 | env_logger::builder().is_test(true).try_init().ok(); 1147 | run_test("class e { 1148 | constructor(t) {} 1149 | 1150 | get a() { 1151 | let t; 1152 | } 1153 | 1154 | get b() { 1155 | let t; 1156 | } 1157 | }", false).unwrap(); 1158 | } 1159 | 1160 | #[test] 1161 | fn import_default() { 1162 | env_logger::builder().is_test(true).try_init().ok(); 1163 | let mut p = Parser::builder() 1164 | .js("import i from 'module'") 1165 | .module(true) 1166 | .build() 1167 | .unwrap(); 1168 | let tokens = p.parse().unwrap(); 1169 | 1170 | assert_eq!( 1171 | Program::Mod(vec![ProgramPart::Decl(Decl::Import(Box::new(ModImport { 1172 | source: Lit::String(StringLit::Single(Cow::Borrowed("module"))), 1173 | specifiers: vec![ImportSpecifier::Default(Ident::from("i"))] 1174 | })))]), 1175 | tokens 1176 | ); 1177 | } 1178 | 1179 | #[test] 1180 | fn loop_yield() { 1181 | env_logger::builder().is_test(true).try_init().ok(); 1182 | let mut p = Parser::builder() 1183 | .js("var x = { 1184 | *['y']() { 1185 | yield 0; 1186 | yield 0; 1187 | } 1188 | };") 1189 | .build() 1190 | .unwrap(); 1191 | let tokens = p.parse().unwrap(); 1192 | assert_eq!( 1193 | Program::Script(vec![ProgramPart::Decl(Decl::Var( 1194 | VarKind::Var, 1195 | vec![VarDecl { 1196 | id: Pat::Ident(Ident::from("x")), 1197 | init: Some(Expr::Obj(vec![ObjProp::Prop(Prop { 1198 | key: PropKey::Lit(Lit::String(StringLit::Single(Cow::Borrowed("y")))), 1199 | computed: true, 1200 | is_static: false, 1201 | kind: PropKind::Method, 1202 | method: true, 1203 | short_hand: false, 1204 | value: PropValue::Expr(Expr::Func(Func { 1205 | id: None, 1206 | params: vec![], 1207 | body: FuncBody(vec![ 1208 | ProgramPart::Stmt(Stmt::Expr(Expr::Yield(YieldExpr { 1209 | argument: Some(Box::new(Expr::Lit(Lit::Number(Cow::Borrowed( 1210 | "0" 1211 | ))))), 1212 | delegate: false, 1213 | }))), 1214 | ProgramPart::Stmt(Stmt::Expr(Expr::Yield(YieldExpr { 1215 | argument: Some(Box::new(Expr::Lit(Lit::Number(Cow::Borrowed( 1216 | "0" 1217 | ))))), 1218 | delegate: false, 1219 | }))), 1220 | ]), 1221 | generator: true, 1222 | is_async: false, 1223 | })) 1224 | })])) 1225 | }] 1226 | ))]), 1227 | tokens 1228 | ); 1229 | } 1230 | 1231 | #[test] 1232 | fn obj_expr_stmt() { 1233 | use resast::spanned::{ 1234 | expr::{Expr, ObjExpr, WrappedExpr}, 1235 | stmt::Stmt, 1236 | Program, ProgramPart, Slice, SourceLocation, 1237 | }; 1238 | use ressa::spanned::Parser; 1239 | env_logger::builder().is_test(true).try_init().ok(); 1240 | let mut p = Parser::builder().js("({});").build().unwrap(); 1241 | let tokens = p.parse().unwrap(); 1242 | let open_brace = Slice { 1243 | source: "{".into(), 1244 | loc: SourceLocation::new(1, 2, 1, 3), 1245 | }; 1246 | let close_brace = Slice { 1247 | source: "}".into(), 1248 | loc: SourceLocation::new(1, 3, 1, 4), 1249 | }; 1250 | let obj = ObjExpr { 1251 | open_brace, 1252 | close_brace, 1253 | props: vec![], 1254 | }; 1255 | let expr = Expr::Obj(obj); 1256 | let wrapped = WrappedExpr { 1257 | open_paren: Slice { 1258 | source: "(".into(), 1259 | loc: SourceLocation::new(1, 1, 1, 2), 1260 | }, 1261 | expr, 1262 | close_paren: Slice { 1263 | source: ")".into(), 1264 | loc: SourceLocation::new(1, 4, 1, 5), 1265 | }, 1266 | }; 1267 | let expr = Expr::Wrapped(Box::new(wrapped)); 1268 | assert_eq!( 1269 | Program::Script(vec![ProgramPart::Stmt(Stmt::Expr { 1270 | expr, 1271 | semi_colon: Some(Slice { 1272 | source: ";".into(), 1273 | loc: SourceLocation::new(1, 5, 1, 6) 1274 | }) 1275 | })]), 1276 | tokens 1277 | ); 1278 | } 1279 | 1280 | #[test] 1281 | fn setter_scope() { 1282 | let js = "class A { 1283 | set b(b) { let d; } 1284 | set c(c) { let d; } 1285 | } 1286 | "; 1287 | run_test(js, false).unwrap(); 1288 | } 1289 | 1290 | #[test] 1291 | #[ignore = "Diagnostic to see how badly our recursive decent is performing"] 1292 | fn blow_the_stack() { 1293 | fn do_it(ct: usize) { 1294 | eprintln!("do_it {}", ct); 1295 | let mut js = String::from("function x() {"); 1296 | for _i in 1..ct { 1297 | js.push_str("return function() {"); 1298 | } 1299 | for _i in 0..ct { 1300 | js.push('}'); 1301 | } 1302 | run_test(&js, false).unwrap(); 1303 | } 1304 | for i in 1..7 { 1305 | do_it(i) 1306 | } 1307 | } 1308 | #[test] 1309 | #[ignore = "Diagnostic to see how badly our recursive decent is performing"] 1310 | fn blow_the_stack_spanned() { 1311 | use ressa::spanned::Parser; 1312 | env_logger::builder().is_test(true).try_init().ok(); 1313 | fn do_it(ct: usize) { 1314 | eprintln!("do_it {}", ct); 1315 | let mut js = String::from("function x() {"); 1316 | for _i in 1..ct { 1317 | js.push_str("return function() {"); 1318 | } 1319 | for _i in 0..ct { 1320 | js.push('}'); 1321 | } 1322 | let mut p = Parser::builder().js(&js).module(false).build().unwrap(); 1323 | p.parse().unwrap(); 1324 | // run_test(&js, false).unwrap(); 1325 | } 1326 | for i in 1..100 { 1327 | do_it(i) 1328 | } 1329 | } 1330 | 1331 | fn run_test(js: &str, as_mod: bool) -> Result<(), ressa::Error> { 1332 | env_logger::builder().is_test(true).try_init().ok(); 1333 | let mut p = Parser::builder().js(js).module(as_mod).build()?; 1334 | p.parse()?; 1335 | Ok(()) 1336 | } 1337 | 1338 | fn run_spanned_test<'a>(js: &'a str, as_mod: bool) -> Result<(), ressa::Error> { 1339 | use ressa::spanned::Parser; 1340 | env_logger::builder().is_test(true).try_init().ok(); 1341 | let mut p = Parser::builder().js(js).module(as_mod).build()?; 1342 | p.parse()?; 1343 | Ok(()) 1344 | } 1345 | -------------------------------------------------------------------------------- /tests/spider_monkey.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "moz_central")] 2 | 3 | use ressa::{Builder, Error}; 4 | use std::path::Path; 5 | use walkdir::WalkDir; 6 | #[cfg(windows)] 7 | static ESPARSE: &str = "node_modules/.bin/esparse.cmd"; 8 | #[cfg(not(windows))] 9 | static ESPARSE: &str = "node_modules/.bin/esparse"; 10 | 11 | #[test] 12 | fn moz_central() { 13 | let moz_central_path = Path::new("./moz-central"); 14 | if !moz_central_path.exists() { 15 | panic!("Unable to run this test without the files in ./moz-central see CONTRIBUTING.md for more information"); 16 | } 17 | let failures = walk(&moz_central_path); 18 | let fail_count = failures.iter().filter(|(_, ok_list)| !ok_list).count(); 19 | for (msg, _) in failures.iter().filter(|(_, ok_list)| *ok_list) { 20 | println!("W-{}", msg); 21 | } 22 | if fail_count > 0 { 23 | eprintln!("----------"); 24 | eprintln!("FAILURES"); 25 | eprintln!("----------"); 26 | for (msg, _) in failures.iter().filter(|(_, ok_list)| !ok_list) { 27 | eprintln!("{}", msg); 28 | } 29 | panic!("Failed to parse {} moz_central files", fail_count); 30 | } 31 | } 32 | 33 | fn walk(path: &Path) -> Vec<(String, bool)> { 34 | let mut ret = Vec::new(); 35 | for file_path in WalkDir::new(path).into_iter() { 36 | let file_path = file_path.expect(&format!("Error for file {}", path.display())); 37 | if file_path.path().is_file() { 38 | let test = if let Some(ext) = file_path.path().extension() { 39 | ext == "js" 40 | } else { 41 | false 42 | }; 43 | if !test { 44 | continue; 45 | } 46 | if let Err(e) = run(&file_path.path()) { 47 | let loc = match &e { 48 | Error::UnexpectedToken(ref pos, _) 49 | | Error::UnableToReinterpret(ref pos, _, _) 50 | | Error::Redecl(ref pos, _) 51 | | Error::OperationError(ref pos, _) 52 | | Error::InvalidGetterParams(ref pos) 53 | | Error::InvalidSetterParams(ref pos) 54 | | Error::NonStrictFeatureInStrictContext(ref pos, _) 55 | | Error::InvalidImportError(ref pos) 56 | | Error::InvalidExportError(ref pos) 57 | | Error::InvalidUseOfContextualKeyword(ref pos, _) 58 | | Error::TryWithNoCatchOrFinally(ref pos) 59 | | Error::InvalidCatchArg(ref pos) 60 | | Error::ThrowWithNoArg(ref pos) 61 | | Error::UnknownOptionalLabel(ref pos, _, _) 62 | | Error::InvalidOptionalLabel(ref pos) 63 | | Error::UseOfModuleFeatureOutsideOfModule(ref pos, _) => format!( 64 | "{}:{}:{}", 65 | &file_path.path().to_str().unwrap(), 66 | pos.line, 67 | pos.column 68 | ), 69 | _ => format!("{}", file_path.path().display()), 70 | }; 71 | let mut msg = format!("Parse Failure {}\n\t\"{}\"", e, loc); 72 | let ok_list = match ::std::process::Command::new(ESPARSE) 73 | .arg(file_path.path()) 74 | .output() 75 | { 76 | Ok(op) => { 77 | if !op.status.success() { 78 | let mut msg2 = format!( 79 | "esparse failure: \nstderr: {:?}", 80 | String::from_utf8_lossy(&op.stderr) 81 | ); 82 | msg2.push_str(&format!( 83 | "stdout: {:?}", 84 | String::from_utf8_lossy(&op.stdout) 85 | )); 86 | Some(msg2) 87 | } else { 88 | let name = file_path.file_name(); 89 | let failures_path = Path::new("failures"); 90 | if !failures_path.exists() { 91 | std::fs::create_dir_all(&failures_path) 92 | .expect("Failed to create out path for failures"); 93 | } 94 | let mut out_path = failures_path.join(name); 95 | out_path.set_extension("json"); 96 | ::std::fs::write( 97 | &out_path, 98 | String::from_utf8_lossy(&op.stdout).to_string(), 99 | ) 100 | .expect(&format!("failed to wirte {}", out_path.display())); 101 | None 102 | } 103 | } 104 | Err(e) => { 105 | panic!("failed to exec esparse {}", e); 106 | } 107 | }; 108 | let ok_list = if let Some(msg2) = ok_list { 109 | msg.push_str(&format!("\n{}", msg2)); 110 | true 111 | } else { 112 | false 113 | }; 114 | ret.push((msg, ok_list)); 115 | } 116 | } 117 | } 118 | ret 119 | } 120 | 121 | fn run(file: &Path) -> Result<(), Error> { 122 | // Named regex groups 123 | if file.ends_with("bug1640487.js") || file.ends_with("bug1640592.js") { 124 | return Ok(()); 125 | } 126 | let mut contents = ::std::fs::read_to_string(file)?; 127 | if contents.starts_with("|") { 128 | // bad comment 129 | contents = format!("//{}", contents); 130 | } 131 | if let Some(first) = contents.lines().next() { 132 | if first.contains("SyntaxError") { 133 | return Ok(()); 134 | } 135 | //--> in last line 136 | if first.contains("error:InternalError") { 137 | contents = contents.replace("-->", "//"); 138 | } 139 | } 140 | let module = contents.starts_with("// |jit-test| module"); 141 | let b = Builder::new(); 142 | let parser = b.js(&contents).module(module).build()?; 143 | for part in parser { 144 | let _part = part?; 145 | } 146 | Ok(()) 147 | } 148 | --------------------------------------------------------------------------------