├── .commitlintrc.json ├── .editorconfig ├── .eslintrc.json ├── .github └── workflows │ ├── node.js.yml │ └── npm-publish.yml ├── .gitignore ├── .husky └── commit-msg ├── .npmignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── package-cjs.json ├── package-lock.json ├── package.json ├── packages ├── arrow │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ ├── test │ │ ├── index.test.js │ │ └── unit_tests.html │ └── types │ │ └── tsd.d.ts ├── assignment │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ ├── test │ │ ├── index.test.js │ │ └── unit_tests.html │ └── types │ │ └── tsd.d.ts ├── async-await │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ ├── test │ │ ├── index.test.js │ │ └── unit_tests.html │ └── types │ │ └── tsd.d.ts ├── comment │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ ├── test │ │ ├── index.test.js │ │ └── unit_tests.html │ └── types │ │ └── tsd.d.ts ├── new │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ ├── test │ │ ├── index.test.js │ │ └── unit_tests.html │ └── types │ │ └── tsd.d.ts ├── numbers │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ ├── test │ │ ├── index.test.js │ │ └── unit_tests.html │ └── types │ │ └── tsd.d.ts ├── object │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ ├── test │ │ ├── index.test.js │ │ └── unit_tests.html │ └── types │ │ └── tsd.d.ts ├── regex │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ ├── test │ │ ├── index.test.js │ │ └── unit_tests.html │ └── types │ │ └── tsd.d.ts ├── spread │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ ├── test │ │ ├── index.test.js │ │ └── unit_tests.html │ └── types │ │ └── tsd.d.ts ├── template │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ ├── test │ │ ├── index.test.js │ │ └── unit_tests.html │ └── types │ │ └── tsd.d.ts └── ternary │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ └── index.js │ ├── test │ ├── index.test.js │ └── unit_tests.html │ └── types │ └── tsd.d.ts ├── plugin.rollup.config.js ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── release.sh ├── rollup.config.js ├── src ├── docco.css ├── hooks.js ├── index.js ├── jsep.js └── plugins.js ├── test ├── hooks.test.js ├── jsep.test.js ├── packages │ └── combinedPlugins.test.js ├── performance.test.js ├── playground.html ├── plugins.test.js ├── resources │ ├── esprima.js │ ├── qunit-2.10.0.css │ └── qunit-2.10.0.js ├── test_utils.js ├── tests.js └── unit_tests.html └── typings └── tsd.d.ts /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-angular"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = tab 8 | indent_size = 2 9 | tab_width = 2 10 | 11 | [{package.json,}] 12 | indent_style = tab 13 | indent_size = 2 14 | tab_width = 2 15 | 16 | [*.json] 17 | indent_style = tab 18 | indent_size = 2 19 | tab_width = 2 20 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": ["**/dist/**/*.js"], 3 | "parserOptions": { 4 | "ecmaVersion": 2020, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "impliedStrict": true 8 | } 9 | }, 10 | "env": { 11 | "browser": true, 12 | "node": true 13 | }, 14 | "rules": { 15 | "semi": 1, 16 | "no-dupe-args": 1, 17 | "no-dupe-keys": 1, 18 | "no-unreachable": 1, 19 | "valid-typeof": 1, 20 | "curly": 1, 21 | "no-useless-call": 1, 22 | "brace-style": [1, "stroustrup"], 23 | "no-mixed-spaces-and-tabs": [1, "smart-tabs"], 24 | "spaced-comment": [1, "always", { 25 | "block": { 26 | "exceptions": ["*"] 27 | } 28 | }], 29 | "indent": [ 30 | "error", 31 | "tab", 32 | { 33 | "SwitchCase": 1 34 | } 35 | ], 36 | "linebreak-style": 0, 37 | "arrow-spacing": 1, 38 | "comma-spacing": 1, 39 | "keyword-spacing": 1 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | pull_request: 8 | branches: [ master, next, beta, alpha ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [18.x, 20.x, 22.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - name: Cache pnpm modules 26 | uses: actions/cache@v4 27 | with: 28 | path: ~/.pnpm-store 29 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 30 | restore-keys: | 31 | ${{ runner.os }}- 32 | - uses: pnpm/action-setup@v4 33 | with: 34 | version: ^7 35 | run_install: true 36 | - run: pnpm install 37 | - run: pnpm run default 38 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | # and see: https://github.com/semantic-release/semantic-release/blob/1405b94296059c0c6878fb8b626e2c5da9317632/docs/recipes/github-actions.md 4 | 5 | name: Release 6 | on: 7 | push: 8 | branches: 9 | - master 10 | - next 11 | - beta 12 | - alpha 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | node-version: [20.x] 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - name: Cache pnpm modules 26 | uses: actions/cache@v4 27 | with: 28 | path: ~/.pnpm-store 29 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 30 | restore-keys: | 31 | ${{ runner.os }}- 32 | - uses: pnpm/action-setup@v4 33 | with: 34 | version: 6.0.2 35 | run_install: true 36 | - run: pnpm install --ignore-scripts 37 | - run: pnpm run default 38 | - run: pnpm run release 39 | env: 40 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 41 | NPM_TOKEN: ${{secrets.npm_token}} 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .idea 3 | .vscode 4 | .DS_Store 5 | node_modules/ 6 | *.zip 7 | 8 | # Build files 9 | annotated_source/ 10 | dist/ 11 | docs/ 12 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !package.json 4 | !LICENSE 5 | !README.md 6 | !dist/**/* 7 | !typings/**/* 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [1.4.0](https://github.com/EricSmekens/jsep/compare/v1.3.9...v1.4.0) (2024-11-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add license file to build output ([f6678fb](https://github.com/EricSmekens/jsep/commit/f6678fb0869188e9c9575fed231864f75e99af74)), closes [#267](https://github.com/EricSmekens/jsep/issues/267) 7 | 8 | 9 | ### Features 10 | 11 | * add nullish coallescing (??) and exponentiation (**) binary_ops ([7321a7b](https://github.com/EricSmekens/jsep/commit/7321a7be17e48868495a177e7ab3e690fdf84c9f)), closes [#268](https://github.com/EricSmekens/jsep/issues/268) 12 | 13 | ## [1.3.9](https://github.com/EricSmekens/jsep/compare/v1.3.8...v1.3.9) (2024-07-12) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * add types add/remove/removeAll functions ([b4a5b1a](https://github.com/EricSmekens/jsep/commit/b4a5b1a9f3163f493b80bd13390c716a0f88fab3)), closes [#238](https://github.com/EricSmekens/jsep/issues/238) 19 | * add types for SequenceExpressions ([d0cf015](https://github.com/EricSmekens/jsep/commit/d0cf015b08b5b3f16e6645be97402186932a555d)), closes [#261](https://github.com/EricSmekens/jsep/issues/261) 20 | * detect unexpected ] in expression "[1,2][]" ([70b8ea6](https://github.com/EricSmekens/jsep/commit/70b8ea6942380feec8ad49b876dec8a7c125ea31)), closes [#256](https://github.com/EricSmekens/jsep/issues/256) 21 | * do not access prototype ([037d1db](https://github.com/EricSmekens/jsep/commit/037d1dbf5f4bd9b98dd6a7ae4c7251f018452cce)), closes [#239](https://github.com/EricSmekens/jsep/issues/239) 22 | * throwError return type ([e0df539](https://github.com/EricSmekens/jsep/commit/e0df539e188d9dfc1da21be57f227567788c951f)), closes [#252](https://github.com/EricSmekens/jsep/issues/252) 23 | * update Array elements typing ([097ae12](https://github.com/EricSmekens/jsep/commit/097ae12a681f78720f1d6158184734bd5e7eea1c)) 24 | 25 | ## [1.3.8](https://github.com/EricSmekens/jsep/compare/v1.3.7...v1.3.8) (2022-12-06) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * support TypeScript's Node16 resolution ([0c2cb45](https://github.com/EricSmekens/jsep/commit/0c2cb45c8b6c7eca165c65c664abdb967f376d02)) 31 | 32 | ## [1.3.7](https://github.com/EricSmekens/jsep/compare/v1.3.6...v1.3.7) (2022-09-18) 33 | 34 | 35 | ### Bug Fixes 36 | 37 | * add "types" export ([505a81a](https://github.com/EricSmekens/jsep/commit/505a81a4f46202d64b6d832cbb993850c6dcc6f3)) 38 | * export default IPlugin from all plugins ([cd38da5](https://github.com/EricSmekens/jsep/commit/cd38da58e0a32d8cc05fe1e4ac3791459ee90986)), closes [#231](https://github.com/EricSmekens/jsep/issues/231) 39 | 40 | ## [1.3.6](https://github.com/EricSmekens/jsep/compare/v1.3.5...v1.3.6) (2022-04-27) 41 | 42 | 43 | ### Bug Fixes 44 | 45 | * update release on package.json change ([c1ceb54](https://github.com/EricSmekens/jsep/commit/c1ceb54c80a923bdf2f799499c7840043aa7b537)) 46 | 47 | ## [1.3.4](https://github.com/EricSmekens/jsep/compare/v1.3.3...v1.3.4) (2022-03-22) 48 | 49 | 50 | ### Bug Fixes 51 | 52 | * embed correct version into builds ([9e90f3d](https://github.com/EricSmekens/jsep/commit/9e90f3d7045002c67269d28a8cdddeb0abaef7e1)), closes [#216](https://github.com/EricSmekens/jsep/issues/216) 53 | 54 | ## [1.3.3](https://github.com/6utt3rfly/jsep/compare/v1.3.2...v1.3.3) (2022-03-22) 55 | 56 | 57 | ### Bug Fixes 58 | 59 | * add version to plugin for testing ([1a148c7](https://github.com/6utt3rfly/jsep/commit/1a148c77e0ec0cf4edc4f107da7aa7c453c72e3e)) 60 | 61 | ## [1.3.2](https://github.com/6utt3rfly/jsep/compare/v1.3.1...v1.3.2) (2022-03-22) 62 | 63 | 64 | ### Bug Fixes 65 | 66 | * pass next version into build script ([8270873](https://github.com/6utt3rfly/jsep/commit/8270873d38066f2f53b6da53c88530ceae0560d6)) 67 | * update commitPaths ([3a1cc14](https://github.com/6utt3rfly/jsep/commit/3a1cc14fdf3d470027b81093f970da1f4fad8273)) 68 | 69 | ## [1.3.1](https://github.com/6utt3rfly/jsep/compare/v1.3.0...v1.3.1) (2022-03-22) 70 | 71 | 72 | ### Bug Fixes 73 | 74 | * test change to ternary ([33f0494](https://github.com/6utt3rfly/jsep/commit/33f0494683c037d2ec88a00e2babcc4e218792d8)) 75 | * test change to ternary ([fca0aa5](https://github.com/6utt3rfly/jsep/commit/fca0aa573db774aa63596d3a7868a0c4884a73ea)) 76 | 77 | # [1.3.0](https://github.com/EricSmekens/jsep/compare/v1.2.0...v1.3.0) (2022-02-28) 78 | 79 | 80 | ### Bug Fixes 81 | 82 | * types for optional member expression now in generic baseType ([79fb0dc](https://github.com/EricSmekens/jsep/commit/79fb0dc0420a682896becee18e9b8ebc23652df9)), closes [#200](https://github.com/EricSmekens/jsep/issues/200) 83 | 84 | 85 | ### Features 86 | 87 | * assignment right-to-left and precedence with ternary ([e5652eb](https://github.com/EricSmekens/jsep/commit/e5652ebfff9c7d9b730bb0f21a1f4f22b1e3787d)), closes [#189](https://github.com/EricSmekens/jsep/issues/189) 88 | * simplify ternary since to stop handling ':' binary operator ([4196623](https://github.com/EricSmekens/jsep/commit/419662398101bfc07c646375b966a7427713fb70)) 89 | 90 | # [1.3.0-beta.1](https://github.com/EricSmekens/jsep/compare/v1.2.0...v1.3.0-beta.1) (2021-12-13) 91 | 92 | 93 | ### Bug Fixes 94 | 95 | * types for optional member expression now in generic baseType ([79fb0dc](https://github.com/EricSmekens/jsep/commit/79fb0dc0420a682896becee18e9b8ebc23652df9)), closes [#200](https://github.com/EricSmekens/jsep/issues/200) 96 | 97 | 98 | ### Features 99 | 100 | * assignment right-to-left and precedence with ternary ([e5652eb](https://github.com/EricSmekens/jsep/commit/e5652ebfff9c7d9b730bb0f21a1f4f22b1e3787d)), closes [#189](https://github.com/EricSmekens/jsep/issues/189) 101 | * simplify ternary since to stop handling ':' binary operator ([4196623](https://github.com/EricSmekens/jsep/commit/419662398101bfc07c646375b966a7427713fb70)) 102 | 103 | # [1.2.0](https://github.com/EricSmekens/jsep/compare/v1.1.2...v1.2.0) (2021-10-31) 104 | 105 | 106 | ### Features 107 | 108 | * add right-associative support for binary operators ([2da8834](https://github.com/EricSmekens/jsep/commit/2da88343910685f1b65f3b3560896ca4212bd130)), closes [#195](https://github.com/EricSmekens/jsep/issues/195) 109 | 110 | ## [1.1.2](https://github.com/EricSmekens/jsep/compare/v1.1.1...v1.1.2) (2021-10-17) 111 | 112 | 113 | ### Bug Fixes 114 | 115 | * add missing `optional` property to types ([e2f758e](https://github.com/EricSmekens/jsep/commit/e2f758ea1a23675942e411d25629f01b6d45d867)), closes [#185](https://github.com/EricSmekens/jsep/issues/185) 116 | 117 | ## [1.1.1](https://github.com/EricSmekens/jsep/compare/v1.1.0...v1.1.1) (2021-10-13) 118 | 119 | 120 | ### Bug Fixes 121 | 122 | * literal 'raw' value now matches input string ([432c514](https://github.com/EricSmekens/jsep/commit/432c51485d360e8d6db2b75d0296cd93a0277146)), closes [#192](https://github.com/EricSmekens/jsep/issues/192) 123 | 124 | # [1.1.0](https://github.com/EricSmekens/jsep/compare/v1.0.3...v1.1.0) (2021-10-03) 125 | 126 | 127 | ### Features 128 | 129 | * add optional chaining support (?.) ([56d1e3d](https://github.com/EricSmekens/jsep/commit/56d1e3daeba094a87b02432a8b6a5d3fecb1c4ed)) 130 | 131 | ## [1.0.3](https://github.com/EricSmekens/jsep/compare/v1.0.2...v1.0.3) (2021-09-02) 132 | 133 | 134 | ### Bug Fixes 135 | 136 | * firefox compatibility ([3e51523](https://github.com/EricSmekens/jsep/commit/3e51523ff56b69b08366356a9e5789873ed4f491)), closes [#178](https://github.com/EricSmekens/jsep/issues/178) 137 | 138 | ## 1.0.2 - 2021-08-26 139 | Republish to include types. 140 | 141 | ## 1.0.1 - 2021-08-24 142 | Fixed support for CommonJS modules. This is only a republish. 143 | 144 | ## 1.0.0 - 2021-08-22 145 | Rewrote to ESM, added a plugin system, and fixed numerous grammar issues. For most standard use-cases, this release should not be a breaking change, as every effort was made to maintain compatibility. 146 | 147 | ### Breaking Changes 148 | - renamed `build` folder to `dist` (#130). The package file structure is now: 149 | ``` 150 | ├── CHANGELOG.md 151 | ├── LICENSE 152 | ├── README.md 153 | ├── package.json 154 | ├── dist 155 | │ ├── jsep.js 156 | │ ├── jsep.min.js 157 | │ ├── cjs 158 | │ │ ├── jsep.cjs.js 159 | │ │ ├── jsep.cjs.min.js 160 | │ ├── iife 161 | │ │ ├── jsep.iife.js 162 | │ │ ├── jsep.iife.min.js 163 | ``` 164 | - Removed `LogicalExpression` and treat it as a `BinaryExpression` (#100) 165 | - Call arguments must be either all comma-separated or all space-separated, but not mixed 166 | - unary expressions with no argument now throw `missing unaryOp argument` 167 | - binary expressions now require the left-hand side to exist 168 | - conditional (ternary) expressions with no condition now throw `unexpected "?"` 169 | - `.` now throws `unexpected .` 170 | - `()()` now throws `unexpected "("` 171 | - `a.this`, `a.true`, `a.false`, `a.null` now match esprima and treat the property as an identifier instead of a literal or ThisExpression 172 | 173 | ### Added 174 | - Added a plugin system, including plugins for 175 | - arrow expressions (`() => ...`) 176 | - assigment and update expressions (`a = 2`, `a++`) 177 | - async/await (`await a.find(async (v1, v2) => await v1(v2))`) 178 | - comments (`/* .. */` and `// ...`) 179 | - new expressions (`new Date()`) 180 | - object expressions (`{a: 1, b}`) 181 | - regex support (`/123/ig`) 182 | - spread operator (`fn(...a)`, `[1, ...b]`, `{...c}`) 183 | - template expressions (`` `hi ${name}` ``, `` msg`hig ${name}` ``) 184 | 185 | ### Updated 186 | - `(1, 2)` now returns a SequenceExpression instead of throwing an `Unclosed (` error 187 | - moved the ConditionExpression (ternary) into a plugin, but it is still included by default 188 | 189 | ## 0.4.0 - 2021-03-21 190 | ### Added 191 | - You can add or remove additional valid identifier chars. 192 | - Support for gobble properties from array/strings. e.g. (`[1].length`) 193 | 194 | ### Updated 195 | - Updated several dependancies for audit fixes. 196 | 197 | ## 0.3.5 - 2018-08-23 198 | ### Updated 199 | - Development dependencies update for audit fixes. 200 | 201 | ## 0.3.4 - 2018-03-29 202 | ### Fixed 203 | - Fixed identifiers as custom ops (#68,#83) 204 | 205 | ## 0.3.3 - 2017-12-16 206 | ### Notice 207 | - No functional changes, only updated support for typescript definitions. 208 | - There may be a few known issues, check the issue page for details. 209 | ### Changed 210 | - Updated typings. 211 | - Updated grunt-uglify for 0.03 kB smaller jsep.min.js! :) 212 | 213 | ## 0.3.2 - 2017-08-31 214 | ### Notice 215 | First version that was using a CHANGELOG.md. 216 | 217 | ### Added 218 | - Typings 219 | - Functions to remove all binary/unary/etc. ops. 220 | 221 | ### Fixed 222 | - Supports multiline expressions. 223 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # JSEP Contributing Guide 2 | 3 | ## Set Up 4 | 5 | 1. Run `pnpm install` 6 | 2. Run `pnpm run prepare` 7 | 8 | ## Code style 9 | 10 | Please follow the code style of the rest of the project. 11 | This is enforced via ESLint, whose config is [here](.eslintrc.json). 12 | You can run `npm run lint` before committing to ensure your code does not violate the code style, 13 | or -- better -- install an ESLint plugin for your editor, to see issues inline as you edit code. 14 | 15 | Here are the rules in the ESLint file with some commentary about what they are (since JSON does not allow comments): 16 | 17 | | Rule | Code | What it does | 18 | | ---- | ---- | ------------ | 19 | | [semi](https://eslint.org/docs/rules/semi) | `"semi": 1` | Mandatory semicolons | 20 | | [no-dupe-args](https://eslint.org/docs/rules/no-dupe-args) | `"no-dupe-args": 1` | no duplicate parameter names in function declarations or expressions | 21 | | [no-dupe-keys](https://eslint.org/docs/rules/no-dupe-keys) | `"no-dupe-keys": 1` | no duplicate keys in object literals | 22 | | [no-unreachable](https://eslint.org/docs/rules/no-unreachable) | `"no-unreachable": 1` | no unreachable code after `return`, `throw`, `continue`, and `break` statements. | 23 | | [valid-typeof](https://eslint.org/docs/rules/valid-typeof) | `"valid-typeof": 1` | enforces comparing typeof expressions to valid string literals | 24 | | [curly](https://eslint.org/docs/rules/curly) | `"curly": 1` | No block statements without curly braces | 25 | | [no-useless-call](https://eslint.org/docs/rules/no-useless-call) | `"no-useless-call": 1` | No useless `function.call()` or `.apply()` when it can be replaced with a regular function call | 26 | | [brace-style](https://eslint.org/docs/rules/brace-style) | `"brace-style": [1,"stroustrup"]` | Stroustrup variant of _one true brace style_: `{` on the same line, `}` on its own line, `else`/`catch`/`finally` on separate lines | 27 | | [no-mixed-spaces-and-tabs](https://eslint.org/docs/rules/no-mixed-spaces-and-tabs) | `"no-mixed-spaces-and-tabs": [1,"smart-tabs"]` | Tabs for indentation, spaces for alignment, no mixed spaces and tabs otherwise | 28 | | [spaced-comment](https://eslint.org/docs/rules/spaced-comment) | `"spaced-comment": [1,"always",{"block":{"exceptions":["*"]}}]` | Mandatory space after `//` and `/*` | 29 | | [arrow-spacing](https://eslint.org/docs/rules/arrow-spacing) | `"arrow-spacing": 1` | Spacing around `=>` | 30 | | [comma-spacing](https://eslint.org/docs/rules/comma-spacing) | `"comma-spacing": 1` | Enforce spacing after comma, and not before | 31 | | [keyword-spacing](https://eslint.org/docs/rules/keyword-spacing) | `"keyword-spacing": 1` | Spacing around keywords | 32 | 33 | 40 | 41 | ## Commit Messages 42 | 43 | Commit messages MUST be in the [angular commit-message format](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-format) 44 | Which can be summarized as: 45 | ``` 46 | (): 47 | 48 | 49 | 50 | 51 | ``` 52 | 53 | `` Must be `build | ci | docs | feat | fix | perf | refactor | test` 54 | This field is used to control the semantic versioning of the release following a merge of the commit 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Stephen Oney, https://ericsmekens.github.io/jsep/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## jsep: A Tiny JavaScript Expression Parser 2 | 3 | [jsep](https://ericsmekens.github.io/jsep/) is a simple expression parser written in JavaScript. It can parse JavaScript expressions but not operations. The difference between expressions and operations is akin to the difference between a cell in an Excel spreadsheet vs. a proper JavaScript program. 4 | 5 | ### Why jsep? 6 | 7 | I wanted a lightweight, tiny parser to be included in one of my other libraries. [esprima](http://esprima.org/) and other parsers are great, but had more power than I need and were *way* too large to be included in a library that I wanted to keep relatively small. 8 | 9 | jsep's output is almost identical to [esprima's](http://esprima.org/doc/index.html#ast), which is in turn based on [SpiderMonkey's](https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API). 10 | 11 | ### Custom Build 12 | 13 | While in the jsep project directory, run: 14 | 15 | ```bash 16 | npm install 17 | npm run default 18 | ``` 19 | 20 | The jsep built files will be in the build/ directory. 21 | 22 | ### Usage 23 | 24 | #### Client-side 25 | 26 | ```html 27 | 31 | 32 | 33 | ... 34 | let parse_tree = jsep("1 + 1"); 35 | ``` 36 | 37 | #### Node.JS 38 | 39 | First, run `npm install jsep`. Then, in your source file: 40 | 41 | ```javascript 42 | // ESM: 43 | import jsep from 'jsep'; 44 | const parse_tree = jsep('1 + 1'); 45 | 46 | // or: 47 | import { Jsep } from 'jsep'; 48 | const parse_tree = Jsep.parse('1 + 1'); 49 | 50 | 51 | // CJS: 52 | const jsep = require('jsep').default; 53 | const parsed = jsep('1 + 1'); 54 | 55 | // or: 56 | const { Jsep } = require('jsep'); 57 | const parse_tree = Jsep.parse('1 + 1'); 58 | ``` 59 | 60 | #### Custom Operators 61 | 62 | ```javascript 63 | // Add a custom ^ binary operator with precedence 10 64 | // (Note that higher number = higher precedence) 65 | jsep.addBinaryOp("^", 10); 66 | 67 | // Add exponentiation operator (right-to-left) 68 | jsep.addBinaryOp('**', 11, true); // now included by default 69 | 70 | // Add a custom @ unary operator 71 | jsep.addUnaryOp('@'); 72 | 73 | // Remove a binary operator 74 | jsep.removeBinaryOp(">>>"); 75 | 76 | // Remove a unary operator 77 | jsep.removeUnaryOp("~"); 78 | ``` 79 | 80 | #### Custom Identifiers 81 | 82 | You can add or remove additional valid identifier chars. ('_' and '$' are already treated like this.) 83 | 84 | ```javascript 85 | // Add a custom @ identifier 86 | jsep.addIdentifierChar("@"); 87 | 88 | // Removes a custom @ identifier 89 | jsep.removeIdentifierChar('@'); 90 | ``` 91 | 92 | #### Custom Literals 93 | 94 | You can add or remove additional valid literals. By default, only `true`, `false`, and `null` are defined 95 | ```javascript 96 | // Add standard JS literals: 97 | jsep.addLiteral('undefined', undefined); 98 | jsep.addLiteral('Infinity', Infinity); 99 | jsep.addLiteral('NaN', NaN); 100 | 101 | // Remove "null" literal from default definition 102 | jsep.removeLiteral('null'); 103 | ``` 104 | 105 | ### Plugins 106 | JSEP supports defining custom hooks for extending or modifying the expression parsing. 107 | Plugins are registered by calling `jsep.plugins.register()` with the plugin(s) as the argument(s). 108 | 109 | #### JSEP-provided plugins: 110 | | | | 111 | |-----------------------------------|-------------------------------------------------------------------------------------------| 112 | | [ternary](packages/ternary) | Built-in by default, adds support for ternary `a ? b : c` expressions | 113 | | [arrow](packages/arrow) | Adds arrow-function support: `v => !!v` | 114 | | [assignment](packages/assignment) | Adds assignment and update expression support: `a = 2`, `a++` | 115 | | [comment](packages/comment) | Adds support for ignoring comments: `a /* ignore this */ > 1 // ignore this too` | 116 | | [new](packages/new) | Adds 'new' keyword support: `new Date()` | 117 | | [numbers](packages/numbers) | Adds hex, octal, and binary number support, ignore _ char | 118 | | [object](packages/object) | Adds object expression support: `{ a: 1, b: { c }}` | 119 | | [regex](packages/regex) | Adds support for regular expression literals: `/[a-z]{2}/ig` | 120 | | [spread](packages/spread) | Adds support for the spread operator, `fn(...[1, ...a])`. Works with `object` plugin, too | 121 | | [template](packages/template) | Adds template literal support: `` `hi ${name}` `` | 122 | | | | 123 | 124 | #### How to add plugins: 125 | Plugins have a `name` property so that they can only be registered once. 126 | Any subsequent registrations will have no effect. Add a plugin by registering it with JSEP: 127 | 128 | ```javascript 129 | import jsep from 'jsep'; 130 | import ternary from '@jsep-plugin/ternary'; 131 | import object from '@jsep-plugin/object'; 132 | jsep.plugins.register(object); 133 | jsep.plugins.register(ternary, object); 134 | ``` 135 | 136 | #### List plugins: 137 | Plugins are stored in an object, keyed by their name. 138 | They can be retrieved through `jsep.plugins.registered`. 139 | 140 | #### Writing Your Own Plugin: 141 | Plugins are objects with two properties: `name` and `init`. 142 | Here's a simple plugin example: 143 | ```javascript 144 | const plugin = { 145 | name: 'the plugin', 146 | init(jsep) { 147 | jsep.addIdentifierChar('@'); 148 | jsep.hooks.add('gobble-expression', function myPlugin(env) { 149 | if (this.char === '@') { 150 | this.index += 1; 151 | env.node = { 152 | type: 'MyCustom@Detector', 153 | }; 154 | } 155 | }); 156 | }, 157 | }; 158 | ``` 159 | This example would treat the `@` character as a custom expression, returning 160 | a node of type `MyCustom@Detector`. 161 | 162 | ##### Hooks 163 | Most plugins will make use of hooks to modify the parsing behavior of jsep. 164 | All hooks are bound to the jsep instance, are called with a single argument, and return void. 165 | The `this` context provides access to the internal parsing methods of jsep 166 | to allow reuse as needed. Some hook types will pass an object that allows reading/writing 167 | the `node` property as needed. 168 | 169 | ##### Hook Types 170 | * `before-all`: called just before starting all expression parsing. 171 | * `after-all`: called after parsing all. Read/Write `arg.node` as required. 172 | * `gobble-expression`: called just before attempting to parse an expression. Set `arg.node` as required. 173 | * `after-expression`: called just after parsing an expression. Read/Write `arg.node` as required. 174 | * `gobble-token`: called just before attempting to parse a token. Set `arg.node` as required. 175 | * `after-token`: called just after parsing a token. Read/Write `arg.node` as required. 176 | * `gobble-spaces`: called when gobbling whitespace. 177 | 178 | ##### The `this` context of Hooks 179 | ```typescript 180 | export interface HookScope { 181 | index: number; 182 | readonly expr: string; 183 | readonly char: string; // current character of the expression 184 | readonly code: number; // current character code of the expression 185 | gobbleSpaces: () => void; 186 | gobbleExpressions: (untilICode?: number) => Expression[]; 187 | gobbleExpression: () => Expression; 188 | gobbleBinaryOp: () => PossibleExpression; 189 | gobbleBinaryExpression: () => PossibleExpression; 190 | gobbleToken: () => PossibleExpression; 191 | gobbleTokenProperty: (node: Expression) => Expression; 192 | gobbleNumericLiteral: () => PossibleExpression; 193 | gobbleStringLiteral: () => PossibleExpression; 194 | gobbleIdentifier: () => PossibleExpression; 195 | gobbleArguments: (untilICode: number) => PossibleExpression; 196 | gobbleGroup: () => Expression; 197 | gobbleArray: () => PossibleExpression; 198 | throwError: (msg: string) => never; 199 | } 200 | ``` 201 | 202 | ### License 203 | 204 | jsep is under the MIT license. See LICENSE file. 205 | 206 | ### Thanks 207 | 208 | Some parts of the latest version of jsep were adapted from the esprima parser. 209 | -------------------------------------------------------------------------------- /package-cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsep", 3 | "version": "1.4.0", 4 | "description": "a tiny JavaScript expression parser", 5 | "author": "Stephen Oney (http://from.so/)", 6 | "maintainers": [ 7 | "Eric Smekens (https://github.com/EricSmekens)", 8 | "Lea Verou (https://github.com/LeaVerou)" 9 | ], 10 | "homepage": "https://ericsmekens.github.io/jsep/", 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/EricSmekens/jsep.git" 15 | }, 16 | "type": "module", 17 | "main": "./dist/cjs/jsep.cjs.js", 18 | "module": "./dist/jsep.js", 19 | "exports": { 20 | ".": { 21 | "types": "./typings/tsd.d.ts", 22 | "require": "./dist/cjs/jsep.cjs.js", 23 | "default": "./dist/jsep.js" 24 | } 25 | }, 26 | "typings": "typings/tsd.d.ts", 27 | "private": false, 28 | "devDependencies": { 29 | "@commitlint/cli": "^13.1.0", 30 | "@commitlint/config-angular": "^13.1.0", 31 | "@rollup/plugin-replace": "^2.4.2", 32 | "@semantic-release/changelog": "^5.0.1", 33 | "@semantic-release/exec": "^6.0.3", 34 | "@semantic-release/git": "^9.0.0", 35 | "benchmark": "^2.1.4", 36 | "docco": "^0.9.1", 37 | "eslint": "^7.23.0", 38 | "http-server": "^14.1.1", 39 | "husky": "^7.0.0", 40 | "node-qunit-puppeteer": "^2.1.2", 41 | "puppeteer": "^19.9.0", 42 | "rollup": "^2.44.0", 43 | "rollup-plugin-delete": "^2.0.0", 44 | "rollup-plugin-terser": "^7.0.2", 45 | "semantic-release-monorepo": "^7.0.5", 46 | "semantic-release-plus": "^18.4.1" 47 | }, 48 | "engines": { 49 | "node": ">= 10.16.0" 50 | }, 51 | "directories": { 52 | "test": "test" 53 | }, 54 | "release": { 55 | "commitPaths": [ 56 | "src/", 57 | "packages/ternary/src/", 58 | "types", 59 | "typings/", 60 | ".npmignore", 61 | "package*.json", 62 | "rollup*.js" 63 | ], 64 | "branches": [ 65 | "master", 66 | { 67 | "name": "alpha", 68 | "prerelease": true 69 | }, 70 | { 71 | "name": "beta", 72 | "prerelease": true 73 | } 74 | ], 75 | "plugins": [ 76 | [ 77 | "@semantic-release/commit-analyzer", 78 | { 79 | "preset": "angular", 80 | "parserOpts": { 81 | "noteKeywords": [ 82 | "BREAKING CHANGE", 83 | "BREAKING CHANGES", 84 | "BREAKING" 85 | ] 86 | } 87 | } 88 | ], 89 | [ 90 | "@semantic-release/release-notes-generator", 91 | { 92 | "preset": "angular", 93 | "parserOpts": { 94 | "noteKeywords": [ 95 | "BREAKING CHANGE", 96 | "BREAKING CHANGES", 97 | "BREAKING" 98 | ] 99 | }, 100 | "writerOpts": { 101 | "commitsSort": [ 102 | "scope", 103 | "subject" 104 | ] 105 | } 106 | } 107 | ], 108 | "@semantic-release/changelog", 109 | [ 110 | "@semantic-release/exec", 111 | { 112 | "prepareCmd": "NEXT_VERSION=${nextRelease.version} pnpm run build" 113 | } 114 | ], 115 | [ 116 | "@semantic-release/npm", 117 | { 118 | "tarballDir": "./" 119 | } 120 | ], 121 | [ 122 | "@semantic-release/git", 123 | { 124 | "message": "build: ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 125 | } 126 | ], 127 | [ 128 | "@semantic-release/github", 129 | { 130 | "assets": [ 131 | { 132 | "path": "./*.tgz", 133 | "label": "build" 134 | } 135 | ] 136 | } 137 | ] 138 | ] 139 | }, 140 | "scripts": { 141 | "default": "npm run lint && npm run build:all && npm run test:all && npm run docco", 142 | "build": "npx rollup -c && cp package-cjs.json dist/cjs/package.json", 143 | "build:watch": "npx rollup -c --watch", 144 | "build:all": "pnpm run build -r", 145 | "test": "npx http-server -p 49649 --silent & npx node-qunit-puppeteer http://localhost:49649/test/unit_tests.html", 146 | "test:all": "npx http-server -p 49649 --silent & pnpm run test -r --workspace-concurrency=1", 147 | "test:performance": "node test/performance.test.js", 148 | "docco": "npx docco src/jsep.js --css=src/docco.css --output=annotated_source/", 149 | "lint": "npx eslint src/**/*.js test/*.js test/packages/**/*.js packages/**/*.js", 150 | "prepare": "husky install", 151 | "release": "./release.sh" 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /packages/arrow/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !package.json 4 | !LICENSE 5 | !README.md 6 | !dist/**/* 7 | !types/**/* 8 | -------------------------------------------------------------------------------- /packages/arrow/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [@jsep-plugin/arrow-v1.0.6](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/arrow-v1.0.5...@jsep-plugin/arrow-v1.0.6) (2024-11-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add license file to build output ([f6678fb](https://github.com/EricSmekens/jsep/commit/f6678fb0869188e9c9575fed231864f75e99af74)), closes [#267](https://github.com/EricSmekens/jsep/issues/267) 7 | 8 | # [@jsep-plugin/arrow-v1.0.5](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/arrow-v1.0.4...@jsep-plugin/arrow-v1.0.5) (2022-09-18) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * export default IPlugin from all plugins ([cd38da5](https://github.com/EricSmekens/jsep/commit/cd38da58e0a32d8cc05fe1e4ac3791459ee90986)), closes [#231](https://github.com/EricSmekens/jsep/issues/231) 14 | 15 | # [@jsep-plugin/arrow-v1.0.4](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/arrow-v1.0.3...@jsep-plugin/arrow-v1.0.4) (2022-03-22) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * embed correct version into builds ([9e90f3d](https://github.com/EricSmekens/jsep/commit/9e90f3d7045002c67269d28a8cdddeb0abaef7e1)), closes [#216](https://github.com/EricSmekens/jsep/issues/216) 21 | 22 | # [@jsep-plugin/arrow-v1.0.3](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/arrow-v1.0.2...@jsep-plugin/arrow-v1.0.3) (2022-03-22) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * add version to plugin for testing ([a0d0933](https://github.com/EricSmekens/jsep/commit/a0d09339713714787f0e7df5e1942f5f620bee21)) 28 | * add version to plugin for testing ([1a148c7](https://github.com/EricSmekens/jsep/commit/1a148c77e0ec0cf4edc4f107da7aa7c453c72e3e)) 29 | * test arrow change ([d081d7a](https://github.com/EricSmekens/jsep/commit/d081d7a4eb963dd801716896e030204b1ac7bd1f)) 30 | * test arrow change ([1a228e6](https://github.com/EricSmekens/jsep/commit/1a228e6004988482bbbc0932deb2dcb664546ea3)) 31 | 32 | # [@jsep-plugin/arrow-v1.0.2](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/arrow-v1.0.1...@jsep-plugin/arrow-v1.0.2) (2022-02-28) 33 | 34 | 35 | ### Bug Fixes 36 | 37 | * allow assignment to nested arrow functions ([06e0092](https://github.com/EricSmekens/jsep/commit/06e00922bef2e22136c15d2110e99fc4a2986658)), closes [#204](https://github.com/EricSmekens/jsep/issues/204) 38 | * correct arrow body as token instead of expression ([f471d66](https://github.com/EricSmekens/jsep/commit/f471d66b3b26c215b3c1a378827bd7e5ee5f1b6d)), closes [#204](https://github.com/EricSmekens/jsep/issues/204) 39 | * priority of arrow just lower than assignment ([b5c2407](https://github.com/EricSmekens/jsep/commit/b5c2407acf338d53c8fcc453c575fccd680abed0)) 40 | * priority of arrow with updated assignment plugin ([2a09ce3](https://github.com/EricSmekens/jsep/commit/2a09ce396e42c6017b4416f22bb63df0b59dca72)) 41 | 42 | # [@jsep-plugin/arrow-v1.0.2-beta.1](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/arrow-v1.0.1...@jsep-plugin/arrow-v1.0.2-beta.1) (2022-01-11) 43 | 44 | 45 | ### Bug Fixes 46 | 47 | * allow assignment to nested arrow functions ([06e0092](https://github.com/EricSmekens/jsep/commit/06e00922bef2e22136c15d2110e99fc4a2986658)), closes [#204](https://github.com/EricSmekens/jsep/issues/204) 48 | * correct arrow body as token instead of expression ([f471d66](https://github.com/EricSmekens/jsep/commit/f471d66b3b26c215b3c1a378827bd7e5ee5f1b6d)), closes [#204](https://github.com/EricSmekens/jsep/issues/204) 49 | * priority of arrow just lower than assignment ([b5c2407](https://github.com/EricSmekens/jsep/commit/b5c2407acf338d53c8fcc453c575fccd680abed0)) 50 | * priority of arrow with updated assignment plugin ([2a09ce3](https://github.com/EricSmekens/jsep/commit/2a09ce396e42c6017b4416f22bb63df0b59dca72)) 51 | 52 | ## 1.0.1 - 2021-08-26 53 | republish with types included 54 | 55 | ## 1.0.0 - 2021-08-24 56 | Initial Release 57 | -------------------------------------------------------------------------------- /packages/arrow/README.md: -------------------------------------------------------------------------------- 1 | [npm]: https://img.shields.io/npm/v/@jsep-plugin/arrow 2 | [npm-url]: https://www.npmjs.com/package/@jsep-plugin/arrow 3 | [size]: https://packagephobia.now.sh/badge?p=@jsep-plugin/arrow 4 | [size-url]: https://packagephobia.now.sh/result?p=@jsep-plugin/arrow 5 | 6 | [![npm][npm]][npm-url] 7 | [![size][size]][size-url] 8 | 9 | # @jsep-plugin/arrow 10 | 11 | A JSEP plugin for adding arrow-function support. Allows expressions of the form: 12 | 13 | ```javascript 14 | jsep('a.find(v => v === 1)'); 15 | jsep('a.map((v, i) => i)'); 16 | jsep('a.map(() => true)'); 17 | ``` 18 | 19 | ## Install 20 | 21 | ```console 22 | npm install @jsep-plugin/arrow 23 | # or 24 | yarn add @jsep-plugin/arrow 25 | ``` 26 | 27 | ## Usage 28 | ```javascript 29 | import jsep from 'jsep'; 30 | import jsepArrow from '@jsep-plugin/arrow'; 31 | jsep.plugins.register(jsepArrow); 32 | ``` 33 | 34 | ## Meta 35 | 36 | [LICENSE (MIT)](/LICENSE) 37 | -------------------------------------------------------------------------------- /packages/arrow/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsep-plugin/arrow", 3 | "version": "1.0.6", 4 | "description": "Adds arrow-function support", 5 | "author": "Shelly (https://github.com/6utt3rfly)", 6 | "maintainers": [ 7 | "Eric Smekens (https://github.com/EricSmekens)", 8 | "Lea Verou (https://github.com/LeaVerou)", 9 | "Shelly (https://github.com/6utt3rfly)" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/arrow#readme", 15 | "license": "MIT", 16 | "repository": { 17 | "url": "EricSmekens/jsep", 18 | "directory": "packages/arrow" 19 | }, 20 | "type": "module", 21 | "main": "./dist/cjs/index.cjs.js", 22 | "module": "./dist/index.js", 23 | "types": "types/tsd.d.ts", 24 | "peerDependencies": { 25 | "jsep": "^0.4.0||^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "rollup": "^2.44.0", 29 | "rollup-plugin-delete": "^2.0.0" 30 | }, 31 | "engines": { 32 | "node": ">= 10.16.0" 33 | }, 34 | "scripts": { 35 | "build": "rollup -c ../../plugin.rollup.config.js && cp ../../package-cjs.json dist/cjs/package.json && cp ../../LICENSE ./", 36 | "test": "cd ../../ && http-server -p 49649 --silent & node-qunit-puppeteer http://localhost:49649/packages/arrow/test/unit_tests.html", 37 | "lint": "eslint src/**/*.js test/**/*.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/arrow/src/index.js: -------------------------------------------------------------------------------- 1 | const ARROW_EXP = 'ArrowFunctionExpression'; 2 | 3 | export default { 4 | name: 'arrow', 5 | 6 | init(jsep) { 7 | // arrow-function expressions: () => x, v => v, (a, b) => v 8 | jsep.addBinaryOp('=>', 0.1, true); 9 | 10 | // this hook searches for the special case () => ... 11 | // which would normally throw an error because of the invalid LHS to the bin op 12 | jsep.hooks.add('gobble-expression', function gobbleEmptyArrowArg(env) { 13 | this.gobbleSpaces(); 14 | if (this.code === jsep.OPAREN_CODE) { 15 | const backupIndex = this.index; 16 | this.index++; 17 | 18 | this.gobbleSpaces(); 19 | if (this.code === jsep.CPAREN_CODE) { 20 | this.index++; 21 | 22 | const biop = this.gobbleBinaryOp(); 23 | if (biop === '=>') { 24 | // () => ... 25 | const body = this.gobbleBinaryExpression(); 26 | if (!body) { 27 | this.throwError("Expected expression after " + biop); 28 | } 29 | env.node = { 30 | type: ARROW_EXP, 31 | params: null, 32 | body, 33 | }; 34 | return; 35 | } 36 | } 37 | this.index = backupIndex; 38 | } 39 | }); 40 | 41 | jsep.hooks.add('after-expression', function fixBinaryArrow(env) { 42 | updateBinariesToArrows(env.node); 43 | }); 44 | 45 | function updateBinariesToArrows(node) { 46 | if (node) { 47 | // Traverse full tree, converting any sub-object nodes as needed 48 | Object.values(node).forEach((val) => { 49 | if (val && typeof val === 'object') { 50 | updateBinariesToArrows(val); 51 | } 52 | }); 53 | 54 | if (node.operator === '=>') { 55 | node.type = ARROW_EXP; 56 | node.params = node.left ? [node.left] : null; 57 | node.body = node.right; 58 | if (node.params && node.params[0].type === jsep.SEQUENCE_EXP) { 59 | node.params = node.params[0].expressions; 60 | } 61 | delete node.left; 62 | delete node.right; 63 | delete node.operator; 64 | } 65 | } 66 | } 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /packages/arrow/test/index.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../../../src/jsep.js'; 2 | import arrow from '../src/index.js'; 3 | import { testParser, resetJsepDefaults } from '../../../test/test_utils.js'; 4 | 5 | const { test } = QUnit; 6 | 7 | (function () { 8 | QUnit.module('Plugin:Arrow Function', (qunit) => { 9 | qunit.before(() => jsep.plugins.register(arrow)); 10 | qunit.after(resetJsepDefaults); 11 | 12 | test('should parse basic arrow expression with no args, () =>', (assert) => { 13 | testParser('a.find(() => true)', { 14 | type: "CallExpression", 15 | arguments: [ 16 | { 17 | type: "ArrowFunctionExpression", 18 | params: null, 19 | body: { 20 | type: "Literal", 21 | value: true, 22 | raw: "true" 23 | } 24 | } 25 | ], 26 | callee: { 27 | type: "MemberExpression", 28 | computed: false, 29 | object: { 30 | type: "Identifier", 31 | name: "a" 32 | }, 33 | property: { 34 | type: "Identifier", 35 | name: "find" 36 | } 37 | } 38 | }, assert); 39 | }); 40 | 41 | test('should parse basic arrow expression with single non-parenthesized arg, v =>', (assert) => { 42 | testParser('[1, 2].find(v => v === 2)', { 43 | type: "CallExpression", 44 | arguments: [ 45 | { 46 | type: "ArrowFunctionExpression", 47 | params: [ 48 | { 49 | type: "Identifier", 50 | name: "v" 51 | } 52 | ], 53 | body: { 54 | type: "BinaryExpression", 55 | operator: "===", 56 | left: { 57 | type: "Identifier", 58 | name: "v" 59 | }, 60 | right: { 61 | type: "Literal", 62 | value: 2, 63 | raw: "2" 64 | } 65 | } 66 | } 67 | ], 68 | callee: { 69 | type: "MemberExpression", 70 | computed: false, 71 | object: { 72 | type: "ArrayExpression", 73 | elements: [ 74 | { 75 | type: "Literal", 76 | value: 1, 77 | raw: "1" 78 | }, 79 | { 80 | type: "Literal", 81 | value: 2, 82 | raw: "2" 83 | } 84 | ] 85 | }, 86 | property: { 87 | type: "Identifier", 88 | name: "find" 89 | } 90 | } 91 | }, assert); 92 | }); 93 | 94 | test('should parse basic arrow expression with multiple arga, (a, b) =>', (assert) => { 95 | testParser('a.find((val, key) => key === "abc")', { 96 | type: "CallExpression", 97 | arguments: [ 98 | { 99 | type: "ArrowFunctionExpression", 100 | params: [ 101 | { 102 | type: "Identifier", 103 | name: "val" 104 | }, 105 | { 106 | type: "Identifier", 107 | name: "key" 108 | } 109 | ], 110 | body: { 111 | type: "BinaryExpression", 112 | operator: "===", 113 | left: { 114 | type: "Identifier", 115 | name: "key" 116 | }, 117 | right: { 118 | type: "Literal", 119 | value: "abc", 120 | raw: "\"abc\"" 121 | } 122 | } 123 | } 124 | ], 125 | callee: { 126 | type: "MemberExpression", 127 | computed: false, 128 | object: { 129 | type: "Identifier", 130 | name: "a" 131 | }, 132 | property: { 133 | type: "Identifier", 134 | name: "find" 135 | } 136 | } 137 | }, assert); 138 | }); 139 | 140 | test('should parse nested arrow function correctly', (assert) => { 141 | testParser('x => y => a + b', { 142 | type: 'ArrowFunctionExpression', 143 | params: [ 144 | { 145 | type: 'Identifier', 146 | name: 'x' 147 | } 148 | ], 149 | body: { 150 | type: 'ArrowFunctionExpression', 151 | params: [ 152 | { 153 | type: 'Identifier', 154 | name: 'y' 155 | } 156 | ], 157 | body: { 158 | type: 'BinaryExpression', 159 | operator: '+', 160 | left: { 161 | type: 'Identifier', 162 | name: 'a' 163 | }, 164 | right: { 165 | type: 'Identifier', 166 | name: 'b' 167 | } 168 | } 169 | } 170 | }, assert); 171 | }); 172 | 173 | [ 174 | '(["a", "b"].find(v => v === "b").length > 1 || 2) === true', 175 | 'a.find(val => key === "abc")', 176 | 'a.find(() => []).length > 2', 177 | '(a || b).find(v => v(1))', 178 | 'a.find(( ) => 1)', 179 | ].forEach(expr => { 180 | test(`should parse expr "${expr}" without error`, (assert) => { 181 | testParser(expr, {}, assert); 182 | }); 183 | }); 184 | 185 | [ 186 | '() =>', 187 | 'a.find(( ) => )', 188 | 'a.find(( ', 189 | ].forEach(expr => { 190 | QUnit.test(`should throw on invalid expr "${expr}"`, (assert) => { 191 | assert.throws(() => jsep(expr)); 192 | }); 193 | }); 194 | }); 195 | }()); 196 | -------------------------------------------------------------------------------- /packages/arrow/test/unit_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Plugin-Arrow Unit Tests

16 |

17 |
18 |

19 |
    20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/arrow/types/tsd.d.ts: -------------------------------------------------------------------------------- 1 | import * as jsep from 'jsep'; 2 | import { Expression, IPlugin } from 'jsep'; 3 | export const name: string; 4 | export function init(this: typeof jsep): void; 5 | 6 | export interface ArrowExpression extends Expression { 7 | type: 'ArrowFunctionExpression'; 8 | params: Expression[] | null; 9 | body: Expression; 10 | async?: boolean; 11 | } 12 | 13 | declare const _export: IPlugin; 14 | export default _export; 15 | -------------------------------------------------------------------------------- /packages/assignment/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !package.json 4 | !LICENSE 5 | !README.md 6 | !dist/**/* 7 | !types/**/* 8 | -------------------------------------------------------------------------------- /packages/assignment/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [@jsep-plugin/assignment-v1.3.0](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/assignment-v1.2.1...@jsep-plugin/assignment-v1.3.0) (2024-11-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add license file to build output ([f6678fb](https://github.com/EricSmekens/jsep/commit/f6678fb0869188e9c9575fed231864f75e99af74)), closes [#267](https://github.com/EricSmekens/jsep/issues/267) 7 | 8 | 9 | ### Features 10 | 11 | * add logical assignment operators ||= &&= ??= (es2021) ([773a063](https://github.com/EricSmekens/jsep/commit/773a0631785648862baceb76ba5c5574237fe999)) 12 | 13 | # [@jsep-plugin/assignment-v1.2.1](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/assignment-v1.2.0...@jsep-plugin/assignment-v1.2.1) (2022-09-18) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * export default IPlugin from all plugins ([cd38da5](https://github.com/EricSmekens/jsep/commit/cd38da58e0a32d8cc05fe1e4ac3791459ee90986)), closes [#231](https://github.com/EricSmekens/jsep/issues/231) 19 | 20 | # [@jsep-plugin/assignment-v1.2.0](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/assignment-v1.1.0...@jsep-plugin/assignment-v1.2.0) (2022-02-28) 21 | 22 | 23 | ### Features 24 | 25 | * assignment right-to-left and precedence with ternary ([e5652eb](https://github.com/EricSmekens/jsep/commit/e5652ebfff9c7d9b730bb0f21a1f4f22b1e3787d)), closes [#189](https://github.com/EricSmekens/jsep/issues/189) 26 | 27 | # [@jsep-plugin/assignment-v1.2.0-beta.1](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/assignment-v1.1.0...@jsep-plugin/assignment-v1.2.0-beta.1) (2021-12-13) 28 | 29 | 30 | ### Features 31 | 32 | * assignment right-to-left and precedence with ternary ([e5652eb](https://github.com/EricSmekens/jsep/commit/e5652ebfff9c7d9b730bb0f21a1f4f22b1e3787d)), closes [#189](https://github.com/EricSmekens/jsep/issues/189) 33 | 34 | # [@jsep-plugin/assignment-v1.1.0](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/assignment-v1.0.1...@jsep-plugin/assignment-v1.1.0) (2021-10-17) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * assignment operators in multiple operator expressions ([592b422](https://github.com/EricSmekens/jsep/commit/592b4224c03d2fa8dde6626d0f89dc30edabf1f1)) 40 | 41 | 42 | ### Features 43 | 44 | * expose plugin constants to support extensibility ([6754d98](https://github.com/EricSmekens/jsep/commit/6754d98539bab4968080681b28ee1518bf2d5e3f)) 45 | 46 | ## 1.0.1 - 2021-08-26 47 | Fix prefix-update expression as part of a BinaryExpression (++a == 2). 48 | 49 | Republish with types included. 50 | 51 | ## 1.0.0 - 2021-08-24 52 | Initial Release 53 | -------------------------------------------------------------------------------- /packages/assignment/README.md: -------------------------------------------------------------------------------- 1 | [npm]: https://img.shields.io/npm/v/@jsep-plugin/assignment 2 | [npm-url]: https://www.npmjs.com/package/@jsep-plugin/assignment 3 | [size]: https://packagephobia.now.sh/badge?p=@jsep-plugin/assignment 4 | [size-url]: https://packagephobia.now.sh/result?p=@jsep-plugin/assignment 5 | 6 | [![npm][npm]][npm-url] 7 | [![size][size]][size-url] 8 | 9 | # @jsep-plugin/assignment 10 | 11 | A JSEP plugin for adding assignment expression support. Allows expressions of the form: 12 | 13 | ```javascript 14 | jsep('a = 2'); 15 | jsep('a += 2'); 16 | jsep('a++'); 17 | jsep('--aa'); 18 | ``` 19 | 20 | ## Install 21 | 22 | ```console 23 | npm install @jsep-plugin/assignment 24 | # or 25 | yarn add @jsep-plugin/assignment 26 | ``` 27 | 28 | ## Usage 29 | ```javascript 30 | import jsep from 'jsep'; 31 | import jsepAssignment from '@jsep-plugin/assignment'; 32 | jsep.plugins.register(jsepAssignment); 33 | ``` 34 | 35 | ## Meta 36 | 37 | [LICENSE (MIT)](/LICENSE) 38 | -------------------------------------------------------------------------------- /packages/assignment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsep-plugin/assignment", 3 | "version": "1.3.0", 4 | "description": "Adds assignment expression support", 5 | "author": "Shelly (https://github.com/6utt3rfly)", 6 | "maintainers": [ 7 | "Eric Smekens (https://github.com/EricSmekens)", 8 | "Lea Verou (https://github.com/LeaVerou)", 9 | "Shelly (https://github.com/6utt3rfly)" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/assignment#readme", 15 | "license": "MIT", 16 | "repository": { 17 | "url": "EricSmekens/jsep", 18 | "directory": "packages/assignment" 19 | }, 20 | "type": "module", 21 | "main": "./dist/cjs/index.cjs.js", 22 | "module": "./dist/index.js", 23 | "types": "types/tsd.d.ts", 24 | "peerDependencies": { 25 | "jsep": "^0.4.0||^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "rollup": "^2.44.0", 29 | "rollup-plugin-delete": "^2.0.0" 30 | }, 31 | "engines": { 32 | "node": ">= 10.16.0" 33 | }, 34 | "scripts": { 35 | "build": "rollup -c ../../plugin.rollup.config.js && cp ../../package-cjs.json dist/cjs/package.json && cp ../../LICENSE ./", 36 | "test": "cd ../../ && http-server -p 49649 --silent & node-qunit-puppeteer http://localhost:49649/packages/assignment/test/unit_tests.html", 37 | "lint": "eslint src/**/*.js test/**/*.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/assignment/src/index.js: -------------------------------------------------------------------------------- 1 | const PLUS_CODE = 43; // + 2 | const MINUS_CODE = 45; // - 3 | 4 | const plugin = { 5 | name: 'assignment', 6 | 7 | assignmentOperators: new Set([ 8 | '=', 9 | '*=', 10 | '**=', 11 | '/=', 12 | '%=', 13 | '+=', 14 | '-=', 15 | '<<=', 16 | '>>=', 17 | '>>>=', 18 | '&=', 19 | '^=', 20 | '|=', 21 | '||=', 22 | '&&=', 23 | '??=', 24 | ]), 25 | updateOperators: [PLUS_CODE, MINUS_CODE], 26 | assignmentPrecedence: 0.9, 27 | 28 | init(jsep) { 29 | const updateNodeTypes = [jsep.IDENTIFIER, jsep.MEMBER_EXP]; 30 | plugin.assignmentOperators.forEach(op => jsep.addBinaryOp(op, plugin.assignmentPrecedence, true)); 31 | 32 | jsep.hooks.add('gobble-token', function gobbleUpdatePrefix(env) { 33 | const code = this.code; 34 | if (plugin.updateOperators.some(c => c === code && c === this.expr.charCodeAt(this.index + 1))) { 35 | this.index += 2; 36 | env.node = { 37 | type: 'UpdateExpression', 38 | operator: code === PLUS_CODE ? '++' : '--', 39 | argument: this.gobbleTokenProperty(this.gobbleIdentifier()), 40 | prefix: true, 41 | }; 42 | if (!env.node.argument || !updateNodeTypes.includes(env.node.argument.type)) { 43 | this.throwError(`Unexpected ${env.node.operator}`); 44 | } 45 | } 46 | }); 47 | 48 | jsep.hooks.add('after-token', function gobbleUpdatePostfix(env) { 49 | if (env.node) { 50 | const code = this.code; 51 | if (plugin.updateOperators.some(c => c === code && c === this.expr.charCodeAt(this.index + 1))) { 52 | if (!updateNodeTypes.includes(env.node.type)) { 53 | this.throwError(`Unexpected ${env.node.operator}`); 54 | } 55 | this.index += 2; 56 | env.node = { 57 | type: 'UpdateExpression', 58 | operator: code === PLUS_CODE ? '++' : '--', 59 | argument: env.node, 60 | prefix: false, 61 | }; 62 | } 63 | } 64 | }); 65 | 66 | jsep.hooks.add('after-expression', function gobbleAssignment(env) { 67 | if (env.node) { 68 | // Note: Binaries can be chained in a single expression to respect 69 | // operator precedence (i.e. a = b = 1 + 2 + 3) 70 | // Update all binary assignment nodes in the tree 71 | updateBinariesToAssignments(env.node); 72 | } 73 | }); 74 | 75 | function updateBinariesToAssignments(node) { 76 | if (plugin.assignmentOperators.has(node.operator)) { 77 | node.type = 'AssignmentExpression'; 78 | updateBinariesToAssignments(node.left); 79 | updateBinariesToAssignments(node.right); 80 | } 81 | else if (!node.operator) { 82 | Object.values(node).forEach((val) => { 83 | if (val && typeof val === 'object') { 84 | updateBinariesToAssignments(val); 85 | } 86 | }); 87 | } 88 | } 89 | }, 90 | }; 91 | export default plugin; 92 | -------------------------------------------------------------------------------- /packages/assignment/test/index.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../../../src/index.js'; 2 | import assignment from '../src/index.js'; 3 | import {testParser, resetJsepDefaults, esprimaComparisonTest} from '../../../test/test_utils.js'; 4 | 5 | const { test } = QUnit; 6 | 7 | (function () { 8 | QUnit.module('Plugin:Assignment', (qunit) => { 9 | const operators = [ 10 | '=', 11 | '*=', 12 | '**=', 13 | '/=', 14 | '%=', 15 | '+=', 16 | '-=', 17 | '<<=', 18 | '>>=', 19 | '>>>=', 20 | '&=', 21 | '^=', 22 | '|=', 23 | '||=', 24 | '&&=', 25 | '??=', 26 | ]; 27 | 28 | qunit.before(() => jsep.plugins.register(assignment)); 29 | qunit.after(resetJsepDefaults); 30 | 31 | operators.forEach(op => test(`should correctly parse assignment operator, ${op}`, (assert) => { 32 | testParser(`a ${op} 2`, { 33 | type: "AssignmentExpression", 34 | operator: op, 35 | left: { 36 | type: "Identifier", 37 | name: "a" 38 | }, 39 | right: { 40 | type: "Literal", 41 | value: 2, 42 | raw: "2" 43 | } 44 | }, assert); 45 | })); 46 | 47 | test('should correctly parse chained assignment', (assert) => { 48 | testParser('a = b = c = d', { 49 | type: 'AssignmentExpression', 50 | operator: '=', 51 | left: { 52 | type: 'Identifier', 53 | name: 'a' 54 | }, 55 | right: { 56 | type: 'AssignmentExpression', 57 | operator: '=', 58 | left: { 59 | type: 'Identifier', 60 | name: 'b' 61 | }, 62 | right: { 63 | type: 'AssignmentExpression', 64 | operator: '=', 65 | left: { 66 | type: 'Identifier', 67 | name: 'c' 68 | }, 69 | right: { 70 | type: 'Identifier', 71 | name: 'd' 72 | } 73 | } 74 | } 75 | }, assert); 76 | }); 77 | 78 | test('should correctly parse chained assignment in ternary', (assert) => { 79 | testParser('a = b = c ? d : e = 2', { 80 | type: 'AssignmentExpression', 81 | operator: '=', 82 | left: { 83 | type: 'Identifier', 84 | name: 'a' 85 | }, 86 | right: { 87 | type: 'AssignmentExpression', 88 | operator: '=', 89 | left: { 90 | type: 'Identifier', 91 | name: 'b' 92 | }, 93 | right: { 94 | type: 'ConditionalExpression', 95 | test: { 96 | type: 'Identifier', 97 | name: 'c' 98 | }, 99 | consequent: { 100 | type: 'Identifier', 101 | name: 'd' 102 | }, 103 | alternate: { 104 | type: 'AssignmentExpression', 105 | operator: '=', 106 | left: { 107 | type: 'Identifier', 108 | name: 'e' 109 | }, 110 | right: { 111 | type: 'Literal', 112 | value: 2, 113 | raw: '2' 114 | } 115 | } 116 | } 117 | } 118 | }, assert); 119 | }); 120 | 121 | test('should correctly parse assignment in ternary alternate', (assert) => { 122 | testParser('a ? fn(a) : a = 1', { 123 | type: 'ConditionalExpression', 124 | test: { 125 | type: 'Identifier', 126 | name: 'a' 127 | }, 128 | consequent: { 129 | type: 'CallExpression', 130 | arguments: [ 131 | { 132 | type: 'Identifier', 133 | name: 'a' 134 | } 135 | ], 136 | callee: { 137 | type: 'Identifier', 138 | name: 'fn' 139 | } 140 | }, 141 | alternate: { 142 | type: 'AssignmentExpression', 143 | operator: '=', 144 | left: { 145 | type: 'Identifier', 146 | name: 'a' 147 | }, 148 | right: { 149 | type: 'Literal', 150 | value: 1, 151 | raw: '1' 152 | } 153 | } 154 | }, assert); 155 | }); 156 | 157 | test('should correctly parse assignment in consequent and alternate', (assert) => { 158 | testParser('a = b + 2 ? c = 3 : d = 4', { 159 | type: 'AssignmentExpression', 160 | operator: '=', 161 | left: { 162 | type: 'Identifier', 163 | name: 'a' 164 | }, 165 | right: { 166 | type: 'ConditionalExpression', 167 | test: { 168 | type: 'BinaryExpression', 169 | left: { 170 | type: 'Identifier', 171 | name: 'b' 172 | }, 173 | operator: '+', 174 | right: { 175 | type: 'Literal', 176 | value: 2, 177 | raw: '2' 178 | } 179 | }, 180 | consequent: { 181 | type: 'AssignmentExpression', 182 | operator: '=', 183 | left: { 184 | type: 'Identifier', 185 | name: 'c' 186 | }, 187 | right: { 188 | type: 'Literal', 189 | value: 3, 190 | raw: '3' 191 | } 192 | }, 193 | alternate: { 194 | type: 'AssignmentExpression', 195 | operator: '=', 196 | left: { 197 | type: 'Identifier', 198 | name: 'd' 199 | }, 200 | right: { 201 | type: 'Literal', 202 | value: 4, 203 | raw: '4' 204 | } 205 | } 206 | } 207 | }, assert); 208 | }); 209 | 210 | [ 211 | { expr: 'a++', expect: { operator: '++', prefix: false } }, 212 | { expr: 'a--', expect: { operator: '--', prefix: false } }, 213 | { expr: '++a', expect: { operator: '++', prefix: true } }, 214 | { expr: '--a', expect: { operator: '--', prefix: true } }, 215 | ].forEach(testCase => test(`should handle basic update expression ${testCase.expr}`, (assert) => { 216 | testParser(testCase.expr, Object.assign({ 217 | type: "UpdateExpression", 218 | argument: { 219 | type: "Identifier", 220 | name: "a", 221 | }, 222 | }, testCase.expect), assert); 223 | })); 224 | 225 | [ 226 | 'fn()++', 227 | '1++', 228 | '++', 229 | '(a + b)++', 230 | '--fn()', 231 | '--1', 232 | '--', 233 | '--(a + b)', 234 | ].forEach(expr => test(`should throw on invalid update expression, ${expr}`, (assert) => { 235 | assert.throws(() => jsep(expr)); 236 | })); 237 | 238 | [ 239 | ...operators 240 | .filter(op => !['**=', '||=', '&&=', '??='].includes(op)) // not supported by esprima 241 | .map(op => `a ${op} 2`), 242 | 'a++', 243 | '++a', 244 | 'a--', 245 | '--a', 246 | '++a == 2', 247 | 'a++ == 2', 248 | '2 == ++a', 249 | '2 == a++', 250 | 'a ? a[1]++ : --b', 251 | ].forEach((expr) => { 252 | test(`should match Esprima: ${expr}`, function (assert) { 253 | esprimaComparisonTest(expr, assert); 254 | }); 255 | }); 256 | }); 257 | }()); 258 | -------------------------------------------------------------------------------- /packages/assignment/test/unit_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

    Plugin-Assignment Unit Tests

    16 |

    17 |
    18 |

    19 |
      20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/assignment/types/tsd.d.ts: -------------------------------------------------------------------------------- 1 | import * as jsep from 'jsep'; 2 | import { Expression, IPlugin } from 'jsep'; 3 | export const name: string; 4 | export const assignmentOperators: Set; 5 | export const updateOperators: number[]; 6 | export const assignmentPrecedence: number; 7 | export function init(this: typeof jsep): void; 8 | 9 | export interface UpdateExpression extends Expression { 10 | type: 'UpdateExpression'; 11 | operator: '++' | '--'; 12 | argument: Expression; 13 | prefix: boolean; 14 | } 15 | 16 | export interface AssignmentExpression extends Expression { 17 | type: 'AssignmentExpression'; 18 | operator: '=' 19 | | '*=' 20 | | '**=' 21 | | '/=' 22 | | '%=' 23 | | '+=' 24 | | '-=' 25 | | '<<=' 26 | | '>>=' 27 | | '>>>=' 28 | | '&=' 29 | | '^=' 30 | | '|=' 31 | | '||=' 32 | | '&&=' 33 | | '??='; 34 | left: Expression; 35 | right: Expression; 36 | } 37 | 38 | declare const _export: IPlugin; 39 | export default _export; 40 | -------------------------------------------------------------------------------- /packages/async-await/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !package.json 4 | !LICENSE 5 | !README.md 6 | !dist/**/* 7 | !types/**/* 8 | -------------------------------------------------------------------------------- /packages/async-await/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [@jsep-plugin/async-await-v1.0.3](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/async-await-v1.0.2...@jsep-plugin/async-await-v1.0.3) (2024-11-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add license file to build output ([f6678fb](https://github.com/EricSmekens/jsep/commit/f6678fb0869188e9c9575fed231864f75e99af74)), closes [#267](https://github.com/EricSmekens/jsep/issues/267) 7 | 8 | # [@jsep-plugin/async-await-v1.0.2](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/async-await-v1.0.1...@jsep-plugin/async-await-v1.0.2) (2022-09-18) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * export default IPlugin from all plugins ([cd38da5](https://github.com/EricSmekens/jsep/commit/cd38da58e0a32d8cc05fe1e4ac3791459ee90986)), closes [#231](https://github.com/EricSmekens/jsep/issues/231) 14 | 15 | ## 1.0.1 - 2021-08-26 16 | republish with types included 17 | 18 | ## 1.0.0 - 2021-08-24 19 | Initial Release 20 | -------------------------------------------------------------------------------- /packages/async-await/README.md: -------------------------------------------------------------------------------- 1 | [npm]: https://img.shields.io/npm/v/@jsep-plugin/async-await 2 | [npm-url]: https://www.npmjs.com/package/@jsep-plugin/async-await 3 | [size]: https://packagephobia.now.sh/badge?p=@jsep-plugin/async-await 4 | [size-url]: https://packagephobia.now.sh/result?p=@jsep-plugin/async-await 5 | 6 | [![npm][npm]][npm-url] 7 | [![size][size]][size-url] 8 | 9 | # @jsep-plugin/async-await 10 | 11 | A JSEP plugin for adding async/await support. Allows expressions of the form: 12 | 13 | ```javascript 14 | jsep('await a'); 15 | jsep('await a.find(async (v1, v2) => await v1(v2))'); 16 | ``` 17 | 18 | ## Install 19 | 20 | ```console 21 | npm install @jsep-plugin/async-await 22 | # or 23 | yarn add @jsep-plugin/async-await 24 | ``` 25 | 26 | ## Usage 27 | ```javascript 28 | import jsep from 'jsep'; 29 | import jsepAsyncAwait from '@jsep-plugin/async-await'; 30 | jsep.plugins.register(jsepAsyncAwait); 31 | ``` 32 | 33 | ## Meta 34 | 35 | [LICENSE (MIT)](/LICENSE) 36 | -------------------------------------------------------------------------------- /packages/async-await/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsep-plugin/async-await", 3 | "version": "1.0.3", 4 | "description": "Adds async/await support", 5 | "author": "Shelly (https://github.com/6utt3rfly)", 6 | "maintainers": [ 7 | "Eric Smekens (https://github.com/EricSmekens)", 8 | "Lea Verou (https://github.com/LeaVerou)", 9 | "Shelly (https://github.com/6utt3rfly)" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/async-await#readme", 15 | "license": "MIT", 16 | "repository": { 17 | "url": "EricSmekens/jsep", 18 | "directory": "packages/async-await" 19 | }, 20 | "type": "module", 21 | "main": "./dist/cjs/index.cjs.js", 22 | "module": "./dist/index.js", 23 | "types": "types/tsd.d.ts", 24 | "peerDependencies": { 25 | "jsep": "^0.4.0||^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "rollup": "^2.44.0", 29 | "rollup-plugin-delete": "^2.0.0" 30 | }, 31 | "engines": { 32 | "node": ">= 10.16.0" 33 | }, 34 | "scripts": { 35 | "build": "rollup -c ../../plugin.rollup.config.js && cp ../../package-cjs.json dist/cjs/package.json && cp ../../LICENSE ./", 36 | "test": "cd ../../ && http-server -p 49649 --silent & node-qunit-puppeteer http://localhost:49649/packages/async-await/test/unit_tests.html", 37 | "lint": "eslint src/**/*.js test/**/*.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/async-await/src/index.js: -------------------------------------------------------------------------------- 1 | const ARROW_EXP = 'ArrowFunctionExpression'; 2 | const A_CODE = 97; // a (start of 'async') 3 | 4 | export default { 5 | name: 'asyncAwait', 6 | 7 | init(jsep) { 8 | jsep.hooks.add('gobble-token', function gobbleAsync(env) { 9 | if (this.code === A_CODE && !jsep.isIdentifierStart(this.expr.charCodeAt(this.index + 5))) { 10 | const sub = this.expr.substr(this.index + 1, 4); 11 | if (sub === 'wait') { 12 | // found 'await' 13 | this.index += 5; 14 | const argument = this.gobbleToken(); 15 | if (!argument) { 16 | this.throwError('unexpected \'await\''); 17 | } 18 | env.node = { 19 | type: 'AwaitExpression', 20 | argument, 21 | }; 22 | } 23 | else if (sub === 'sync') { 24 | // found 'async' 25 | this.index += 5; 26 | env.node = this.gobbleExpression(); 27 | if (env.node && env.node.type === ARROW_EXP) { 28 | env.node.async = true; 29 | } 30 | else { 31 | throw new Error('unexpected \'async\''); 32 | } 33 | } 34 | } 35 | }); 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /packages/async-await/test/index.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../../../src/jsep.js'; 2 | import arrow from '../../arrow/src/index.js'; 3 | import asyncAwait from '../src/index.js'; 4 | import { testParser, resetJsepDefaults, findNode } from '../../../test/test_utils.js'; 5 | 6 | const { test } = QUnit; 7 | 8 | (function () { 9 | QUnit.module('Plugin:AsyncAwait', (qunit) => { 10 | qunit.before(() => jsep.plugins.register( 11 | arrow, 12 | asyncAwait 13 | )); 14 | qunit.after(resetJsepDefaults); 15 | 16 | test('should parse basic await expression', (assert) => { 17 | testParser('await a', { 18 | type: "AwaitExpression", 19 | argument: { 20 | type: "Identifier", 21 | name: "a" 22 | }, 23 | }, assert); 24 | }); 25 | 26 | test('should parse arrow async await (multi args)', (assert) => { 27 | testParser('await a.run(async () => await v1)', { 28 | type: 'AwaitExpression', 29 | argument: { 30 | type: 'CallExpression', 31 | arguments: [ 32 | { 33 | type: 'ArrowFunctionExpression', 34 | params: null, 35 | body: { 36 | type: 'AwaitExpression', 37 | argument: { 38 | type: 'Identifier', 39 | name: 'v1', 40 | }, 41 | }, 42 | async: true, 43 | }, 44 | ], 45 | callee: {}, 46 | }, 47 | }, assert); 48 | }); 49 | 50 | test('should parse arrow async await (no args)', (assert) => { 51 | testParser('await a.find(async (v1, v2) => await v1(v2))', { 52 | type: 'AwaitExpression', 53 | argument: { 54 | type: 'CallExpression', 55 | arguments: [ 56 | { 57 | type: 'ArrowFunctionExpression', 58 | params: [ 59 | { 60 | type: 'Identifier', 61 | name: 'v1', 62 | }, 63 | { 64 | type: 'Identifier', 65 | name: 'v2', 66 | } 67 | ], 68 | body: { 69 | type: 'AwaitExpression', 70 | argument: { 71 | type: 'CallExpression', 72 | arguments: [ 73 | { 74 | type: 'Identifier', 75 | name: 'v2', 76 | }, 77 | ], 78 | callee: { 79 | type: 'Identifier', 80 | name: 'v1', 81 | }, 82 | }, 83 | }, 84 | async: true, 85 | }, 86 | ], 87 | callee: { 88 | type: 'MemberExpression', 89 | computed: false, 90 | object: { 91 | type: 'Identifier', 92 | name: 'a', 93 | }, 94 | property: { 95 | type: 'Identifier', 96 | name: 'find', 97 | }, 98 | }, 99 | }, 100 | }, assert); 101 | }); 102 | 103 | [ 104 | 'asyncing(123)', 105 | 'awaiting(123)', 106 | '["async", "await"]', 107 | ].forEach((expr) => { 108 | test(`should ignore async/await literal match in ${expr}`, (assert) => { 109 | const parsed = jsep(expr); 110 | assert.equal(findNode(parsed, n => n.type === 'await' || n.async)); 111 | }); 112 | }); 113 | 114 | [ 115 | 'await a.find(async v => await v)', 116 | 'a.find(async ([v]) => await v)', 117 | 'a.find(async () => await x)', 118 | ].forEach(expr => { 119 | test(`should parse expr "${expr}" without error`, (assert) => { 120 | testParser(expr, {}, assert); 121 | }); 122 | }); 123 | 124 | [ 125 | 'async 123', 126 | 'async a + b', 127 | 'a.find(async () + 2)', 128 | ].forEach(expr => test(`should throw on invalid async expression, ${expr}`, (assert) => { 129 | assert.throws(() => jsep(expr)); 130 | })); 131 | }); 132 | }()); 133 | -------------------------------------------------------------------------------- /packages/async-await/test/unit_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

      Plugin-Async/Await Unit Tests

      16 |

      17 |
      18 |

      19 |
        20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/async-await/types/tsd.d.ts: -------------------------------------------------------------------------------- 1 | import * as jsep from 'jsep'; 2 | import { Expression, IPlugin } from 'jsep'; 3 | export const name: string; 4 | export function init(this: typeof jsep): void; 5 | 6 | export interface AwaitExpression extends Expression { 7 | type: 'AwaitExpression'; 8 | argument: Expression; 9 | } 10 | 11 | declare const _export: IPlugin; 12 | export default _export; 13 | -------------------------------------------------------------------------------- /packages/comment/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !package.json 4 | !LICENSE 5 | !README.md 6 | !dist/**/* 7 | !types/**/* 8 | -------------------------------------------------------------------------------- /packages/comment/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [@jsep-plugin/comment-v1.0.4](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/comment-v1.0.3...@jsep-plugin/comment-v1.0.4) (2024-11-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add license file to build output ([f6678fb](https://github.com/EricSmekens/jsep/commit/f6678fb0869188e9c9575fed231864f75e99af74)), closes [#267](https://github.com/EricSmekens/jsep/issues/267) 7 | 8 | # [@jsep-plugin/comment-v1.0.3](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/comment-v1.0.2...@jsep-plugin/comment-v1.0.3) (2022-09-18) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * export default IPlugin from all plugins ([cd38da5](https://github.com/EricSmekens/jsep/commit/cd38da58e0a32d8cc05fe1e4ac3791459ee90986)), closes [#231](https://github.com/EricSmekens/jsep/issues/231) 14 | 15 | # [@jsep-plugin/comment-v1.0.2](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/comment-v1.0.1...@jsep-plugin/comment-v1.0.2) (2021-10-17) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * // comments now works correctly across line feeds ([82f0a78](https://github.com/EricSmekens/jsep/commit/82f0a7862b10b381dbd344d1180a700f853001ad)), closes [#191](https://github.com/EricSmekens/jsep/issues/191) 21 | * fix comment not gobbling spaces after the comment ([3b57880](https://github.com/EricSmekens/jsep/commit/3b5788010ee68a5bf098fc02128abcedcfe8896d)), closes [#191](https://github.com/EricSmekens/jsep/issues/191) 22 | 23 | ## 1.0.1 - 2021-08-26 24 | republish with types included 25 | 26 | ## 1.0.0 - 2021-08-24 27 | Initial Release 28 | -------------------------------------------------------------------------------- /packages/comment/README.md: -------------------------------------------------------------------------------- 1 | [npm]: https://img.shields.io/npm/v/@jsep-plugin/comment 2 | [npm-url]: https://www.npmjs.com/package/@jsep-plugin/comment 3 | [size]: https://packagephobia.now.sh/badge?p=@jsep-plugin/comment 4 | [size-url]: https://packagephobia.now.sh/result?p=@jsep-plugin/comment 5 | 6 | [![npm][npm]][npm-url] 7 | [![size][size]][size-url] 8 | 9 | # @jsep-plugin/comment 10 | 11 | A JSEP plugin for adding comment expression support. Allows expressions of the form: 12 | 13 | ```javascript 14 | jsep('a = 2 // end of line comment'); 15 | jsep('a /* ignore this */ += 2'); 16 | jsep('a /* ignore \n this */ ++'); 17 | ``` 18 | 19 | ## Install 20 | 21 | ```console 22 | npm install @jsep-plugin/comment 23 | # or 24 | yarn add @jsep-plugin/comment 25 | ``` 26 | 27 | ## Usage 28 | ```javascript 29 | import jsep from 'jsep'; 30 | import jsepComment from '@jsep-plugin/comment'; 31 | jsep.plugins.register(jsepComment); 32 | ``` 33 | 34 | ## Meta 35 | 36 | [LICENSE (MIT)](/LICENSE) 37 | -------------------------------------------------------------------------------- /packages/comment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsep-plugin/comment", 3 | "version": "1.0.4", 4 | "description": "Adds comment expression support", 5 | "author": "Shelly (https://github.com/6utt3rfly)", 6 | "maintainers": [ 7 | "Eric Smekens (https://github.com/EricSmekens)", 8 | "Lea Verou (https://github.com/LeaVerou)", 9 | "Shelly (https://github.com/6utt3rfly)" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/comment#readme", 15 | "license": "MIT", 16 | "repository": { 17 | "url": "EricSmekens/jsep", 18 | "directory": "packages/comment" 19 | }, 20 | "type": "module", 21 | "main": "./dist/cjs/index.cjs.js", 22 | "module": "./dist/index.js", 23 | "types": "types/tsd.d.ts", 24 | "peerDependencies": { 25 | "jsep": "^0.4.0||^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "rollup": "^2.44.0", 29 | "rollup-plugin-delete": "^2.0.0" 30 | }, 31 | "engines": { 32 | "node": ">= 10.16.0" 33 | }, 34 | "scripts": { 35 | "build": "rollup -c ../../plugin.rollup.config.js && cp ../../package-cjs.json dist/cjs/package.json && cp ../../LICENSE ./", 36 | "test": "cd ../../ && http-server -p 49649 --silent & node-qunit-puppeteer http://localhost:49649/packages/comment/test/unit_tests.html", 37 | "lint": "eslint src/**/*.js test/**/*.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/comment/src/index.js: -------------------------------------------------------------------------------- 1 | const FSLSH_CODE = 47; // / 2 | const ASTSK_CODE = 42; // * 3 | 4 | export default { 5 | name: 'comment', 6 | 7 | init(jsep) { 8 | // treat all comments as whitespace to remove from parsing 9 | jsep.hooks.add('gobble-spaces', function gobbleComment() { 10 | if (this.code === FSLSH_CODE) { 11 | let ch = this.expr.charCodeAt(this.index + 1); 12 | if (ch === FSLSH_CODE) { 13 | // '//': read to end of line/input 14 | this.index += 1; 15 | while (ch !== jsep.LF_CODE && !isNaN(ch)) { 16 | ch = this.expr.charCodeAt(++this.index); 17 | } 18 | this.gobbleSpaces(); 19 | } 20 | else if (ch === ASTSK_CODE) { 21 | // read to */ or end of input 22 | this.index += 2; 23 | while (!isNaN(ch)) { 24 | ch = this.expr.charCodeAt(this.index++); 25 | if (ch === ASTSK_CODE) { 26 | ch = this.expr.charCodeAt(this.index++); 27 | if (ch === FSLSH_CODE) { 28 | this.gobbleSpaces(); 29 | return; 30 | } 31 | } 32 | } 33 | 34 | // missing closing */ 35 | this.throwError('Missing closing comment, */'); 36 | } 37 | } 38 | }); 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /packages/comment/test/index.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../../../src/index.js'; 2 | import comment from '../src/index.js'; 3 | import { testParser, resetJsepDefaults } from '../../../test/test_utils.js'; 4 | 5 | const { test } = QUnit; 6 | 7 | (function () { 8 | QUnit.module('Plugin:Comment', (qunit) => { 9 | qunit.before(() => jsep.plugins.register(comment)); 10 | qunit.after(resetJsepDefaults); 11 | 12 | [ 13 | 'a /* ignore this */> 1 // ignore this too', 14 | 'a/* ignore this */ > 1 // ignore this too', 15 | '(a/* ignore this */ )>/**/ 1 // ignore this too', 16 | 'a /* ignore *\r\n *this */> 1 // ignore this too', 17 | 'a /*ignore *\r\n *this *///\n> 1 // ignore this too', 18 | 'a // ignore this\r\n> 1', 19 | 'a /** {param} \r\n */> 1', 20 | '// a\na > 1', 21 | '// a\n //\n a > 1', 22 | ].forEach(expr => test(`should parse out comments from expression ${expr}`, (assert) => { 23 | testParser(expr, { 24 | type: 'BinaryExpression', 25 | operator: '>', 26 | left: { name: 'a' }, 27 | right: { value: 1 }, 28 | }, assert); 29 | })); 30 | 31 | [ 32 | 'a /* no close comment', 33 | ].forEach(expr => test(`should give an error for invalid comment expression ${expr}`, (assert) => { 34 | assert.throws(() => jsep(expr)); 35 | })); 36 | }); 37 | }()); 38 | -------------------------------------------------------------------------------- /packages/comment/test/unit_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

        Plugin-Comment Unit Tests

        16 |

        17 |
        18 |

        19 |
          20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/comment/types/tsd.d.ts: -------------------------------------------------------------------------------- 1 | import * as jsep from 'jsep'; 2 | import { IPlugin } from 'jsep'; 3 | export const name: string; 4 | export function init(this: typeof jsep): void; 5 | 6 | declare const _export: IPlugin; 7 | export default _export; 8 | -------------------------------------------------------------------------------- /packages/new/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !package.json 4 | !LICENSE 5 | !README.md 6 | !dist/**/* 7 | !types/**/* 8 | -------------------------------------------------------------------------------- /packages/new/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [@jsep-plugin/new-v1.0.4](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/new-v1.0.3...@jsep-plugin/new-v1.0.4) (2024-11-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add license file to build output ([f6678fb](https://github.com/EricSmekens/jsep/commit/f6678fb0869188e9c9575fed231864f75e99af74)), closes [#267](https://github.com/EricSmekens/jsep/issues/267) 7 | 8 | # [@jsep-plugin/new-v1.0.3](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/new-v1.0.2...@jsep-plugin/new-v1.0.3) (2023-02-07) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * update 'new' callee member expressions correctly ([b1fae2b](https://github.com/EricSmekens/jsep/commit/b1fae2bbbc6e4f20313bb24ec9ee09a6dc055796)), closes [#243](https://github.com/EricSmekens/jsep/issues/243) 14 | 15 | # [@jsep-plugin/new-v1.0.2](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/new-v1.0.1...@jsep-plugin/new-v1.0.2) (2022-09-18) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * export default IPlugin from all plugins ([cd38da5](https://github.com/EricSmekens/jsep/commit/cd38da58e0a32d8cc05fe1e4ac3791459ee90986)), closes [#231](https://github.com/EricSmekens/jsep/issues/231) 21 | 22 | ## 1.0.1 - 2021-08-26 23 | republish with types included 24 | 25 | ## 1.0.0 - 2021-08-24 26 | Initial Release 27 | -------------------------------------------------------------------------------- /packages/new/README.md: -------------------------------------------------------------------------------- 1 | [npm]: https://img.shields.io/npm/v/@jsep-plugin/new 2 | [npm-url]: https://www.npmjs.com/package/@jsep-plugin/new 3 | [size]: https://packagephobia.now.sh/badge?p=@jsep-plugin/new 4 | [size-url]: https://packagephobia.now.sh/result?p=@jsep-plugin/new 5 | 6 | [![npm][npm]][npm-url] 7 | [![size][size]][size-url] 8 | 9 | # @jsep-plugin/new 10 | 11 | A JSEP plugin for adding `new` expression support. Allows expressions of the form: 12 | 13 | ```javascript 14 | jsep('new Date()'); 15 | jsep('new RegExp("^\d+$")'); 16 | ``` 17 | 18 | ## Install 19 | 20 | ```console 21 | npm install @jsep-plugin/new 22 | # or 23 | yarn add @jsep-plugin/new 24 | ``` 25 | 26 | ## Usage 27 | ```javascript 28 | import jsep from 'jsep'; 29 | import jsepNew from '@jsep-plugin/new'; 30 | jsep.plugins.register(jsepNew); 31 | ``` 32 | 33 | ## Meta 34 | 35 | [LICENSE (MIT)](/LICENSE) 36 | -------------------------------------------------------------------------------- /packages/new/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsep-plugin/new", 3 | "version": "1.0.4", 4 | "description": "Adds 'new' expression support", 5 | "author": "Shelly (https://github.com/6utt3rfly)", 6 | "maintainers": [ 7 | "Eric Smekens (https://github.com/EricSmekens)", 8 | "Lea Verou (https://github.com/LeaVerou)", 9 | "Shelly (https://github.com/6utt3rfly)" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/new#readme", 15 | "license": "MIT", 16 | "repository": { 17 | "url": "EricSmekens/jsep", 18 | "directory": "packages/new" 19 | }, 20 | "type": "module", 21 | "main": "./dist/cjs/index.cjs.js", 22 | "module": "./dist/index.js", 23 | "types": "types/tsd.d.ts", 24 | "peerDependencies": { 25 | "jsep": "^0.4.0||^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "rollup": "^2.44.0", 29 | "rollup-plugin-delete": "^2.0.0" 30 | }, 31 | "engines": { 32 | "node": ">= 10.16.0" 33 | }, 34 | "scripts": { 35 | "build": "rollup -c ../../plugin.rollup.config.js && cp ../../package-cjs.json dist/cjs/package.json && cp ../../LICENSE ./", 36 | "test": "cd ../../ && http-server -p 49649 --silent & node-qunit-puppeteer http://localhost:49649/packages/new/test/unit_tests.html", 37 | "lint": "eslint src/**/*.js test/**/*.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/new/src/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'new', 3 | 4 | init(jsep) { 5 | jsep.addUnaryOp('new'); 6 | 7 | jsep.hooks.add('after-token', function gobbleNew(env) { 8 | const node = env.node; 9 | if (node && node.operator === 'new') { 10 | if (!node.argument || ![jsep.CALL_EXP, jsep.MEMBER_EXP].includes(node.argument.type)) { 11 | this.throwError('Expected new function()'); 12 | } 13 | env.node = node.argument; 14 | 15 | // Change CALL_EXP to NewExpression (could be a nested member, even within a call expr) 16 | let callNode = env.node; 17 | while (callNode.type === jsep.MEMBER_EXP || ( 18 | callNode.type === jsep.CALL_EXP && callNode.callee.type === jsep.MEMBER_EXP)) { 19 | callNode = callNode.type === jsep.MEMBER_EXP 20 | ? callNode.object 21 | : callNode.callee.object; 22 | } 23 | callNode.type = 'NewExpression'; 24 | } 25 | }); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /packages/new/test/index.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../../../src/index.js'; 2 | import jsepNew from '../src/index.js'; 3 | import { testParser, resetJsepDefaults } from '../../../test/test_utils.js'; 4 | 5 | const { test } = QUnit; 6 | 7 | (function () { 8 | QUnit.module('Plugin:New', (qunit) => { 9 | qunit.before(() => jsep.plugins.register(jsepNew)); 10 | qunit.after(resetJsepDefaults); 11 | 12 | test('should parse basic "new" expression', (assert) => { 13 | testParser('new Date(123)', { 14 | type: "NewExpression", 15 | arguments: [ 16 | { 17 | type: "Literal", 18 | value: 123, 19 | raw: "123" 20 | } 21 | ], 22 | callee: { 23 | type: "Identifier", 24 | name: "Date" 25 | } 26 | }, assert); 27 | }); 28 | 29 | test('should parse new with member expression', (assert) => { 30 | testParser('new Date(1).month', { 31 | type: "MemberExpression", 32 | object: { 33 | type: 'NewExpression', 34 | arguments: [ 35 | { 36 | type: 'Literal', 37 | value: 1, 38 | }, 39 | ], 40 | callee: { 41 | type: 'Identifier', 42 | name: 'Date', 43 | }, 44 | }, 45 | property: { 46 | type: 'Identifier', 47 | name: 'month', 48 | }, 49 | }, assert); 50 | }); 51 | 52 | test('should parse multiple nested new expressions', (assert) => { 53 | testParser('new Date(new Date().setDate(new Date().getDate() - 5))', { 54 | type: 'NewExpression', 55 | arguments: [ 56 | { 57 | type: 'CallExpression', 58 | arguments: [ 59 | { 60 | type: 'BinaryExpression', 61 | operator: '-', 62 | left: { 63 | type: 'CallExpression', 64 | arguments: [], 65 | callee: { 66 | type: 'MemberExpression', 67 | computed: false, 68 | object: { 69 | type: 'NewExpression', 70 | arguments: [], 71 | callee: { 72 | type: 'Identifier', 73 | name: 'Date' 74 | } 75 | }, 76 | property: { 77 | type: 'Identifier', 78 | name: 'getDate' 79 | } 80 | } 81 | }, 82 | right: { 83 | type: 'Literal', 84 | value: 5, 85 | raw: '5' 86 | } 87 | } 88 | ], 89 | callee: { 90 | type: 'MemberExpression', 91 | computed: false, 92 | object: { 93 | type: 'NewExpression', 94 | arguments: [], 95 | callee: { 96 | type: 'Identifier', 97 | name: 'Date' 98 | } 99 | }, 100 | property: { 101 | type: 'Identifier', 102 | name: 'setDate' 103 | } 104 | } 105 | } 106 | ], 107 | callee: { 108 | type: 'Identifier', 109 | name: 'Date' 110 | } 111 | }, assert); 112 | }); 113 | 114 | [ 115 | 'new A().b', 116 | 'new A()["b"].c + 2', 117 | 'new A() + 2', 118 | 'new A() != null', 119 | 'new A(), new B()', 120 | '[new A(), new A()]', 121 | 'new A("1")', 122 | 'new A(1, 2)', 123 | ].forEach(expr => test(`should not throw any errors for ${expr}`, (assert) => { 124 | testParser(expr, {}, assert); 125 | })); 126 | 127 | [ 128 | 'new A', 129 | 'new A,new B', 130 | 'fn(new A)', 131 | '!new A', 132 | 'new 123', 133 | 'new (a > 2 ? A : B)', 134 | 'new (a > 1)', 135 | ].forEach(expr => test(`should give an error for invalid expression ${expr}`, (assert) => { 136 | assert.throws(() => jsep(expr)); 137 | })); 138 | }); 139 | }()); 140 | -------------------------------------------------------------------------------- /packages/new/test/unit_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

          Plugin-New Unit Tests

          16 |

          17 |
          18 |

          19 |
            20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/new/types/tsd.d.ts: -------------------------------------------------------------------------------- 1 | import * as jsep from 'jsep'; 2 | import { Expression, IPlugin } from 'jsep'; 3 | export const name: string; 4 | export function init(this: typeof jsep): void; 5 | 6 | export interface NewExpression extends Expression { 7 | type: 'NewExpression'; 8 | arguments: Expression[]; 9 | callee: Expression; 10 | } 11 | 12 | declare const _export: IPlugin; 13 | export default _export; 14 | -------------------------------------------------------------------------------- /packages/numbers/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !package.json 4 | !LICENSE 5 | !README.md 6 | !dist/**/* 7 | !types/**/* 8 | -------------------------------------------------------------------------------- /packages/numbers/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [@jsep-plugin/numbers-v1.0.2](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/numbers-v1.0.1...@jsep-plugin/numbers-v1.0.2) (2024-11-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add license file to build output ([f6678fb](https://github.com/EricSmekens/jsep/commit/f6678fb0869188e9c9575fed231864f75e99af74)), closes [#267](https://github.com/EricSmekens/jsep/issues/267) 7 | 8 | # [@jsep-plugin/numbers-v1.0.1](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/numbers-v1.0.0...@jsep-plugin/numbers-v1.0.1) (2022-09-18) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * export default IPlugin from all plugins ([cd38da5](https://github.com/EricSmekens/jsep/commit/cd38da58e0a32d8cc05fe1e4ac3791459ee90986)), closes [#231](https://github.com/EricSmekens/jsep/issues/231) 14 | 15 | # @jsep-plugin/numbers-v1.0.0 (2022-01-27) 16 | 17 | 18 | ### Features 19 | 20 | * add 'numbers' plugin to expand allowable number formats ([43ce298](https://github.com/EricSmekens/jsep/commit/43ce298599d6b7d668324a7c3b42ac84ee654d1c)), closes [#206](https://github.com/EricSmekens/jsep/issues/206) 21 | -------------------------------------------------------------------------------- /packages/numbers/README.md: -------------------------------------------------------------------------------- 1 | [npm]: https://img.shields.io/npm/v/@jsep-plugin/numbers 2 | [npm-url]: https://www.npmjs.com/package/@jsep-plugin/numbers 3 | [size]: https://packagephobia.now.sh/badge?p=@jsep-plugin/numbers 4 | [size-url]: https://packagephobia.now.sh/result?p=@jsep-plugin/numbers 5 | 6 | [![npm][npm]][npm-url] 7 | [![size][size]][size-url] 8 | 9 | # @jsep-plugin/numbers 10 | 11 | A JSEP plugin for adding support for additional number formats, hexadecimal, binary and octal. 12 | It also adds support to ignore _ characters in numbers 13 | 14 | ```javascript 15 | jsep('0xA'); // hexadecimal (10) 16 | jsep('0b01001010'); // binary (74) 17 | jsep('0644'); // octal (420) 18 | jsep('0o644'); // octal (420) 19 | jsep('115_200'); // decimal (115200) 20 | ``` 21 | 22 | ## Install 23 | 24 | ```console 25 | npm install @jsep-plugin/numbers 26 | # or 27 | yarn add @jsep-plugin/numbers 28 | ``` 29 | 30 | ## Usage 31 | ```javascript 32 | import jsep from 'jsep'; 33 | import jsepNumbers from '@jsep-plugin/numbers'; 34 | jsep.plugins.register(jsepNumbers); 35 | ``` 36 | 37 | ## Meta 38 | 39 | [LICENSE (MIT)](/LICENSE) 40 | -------------------------------------------------------------------------------- /packages/numbers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsep-plugin/numbers", 3 | "version": "1.0.2", 4 | "description": "Adds support for binary, hex, and octal number formats, including _", 5 | "author": "Shelly (https://github.com/6utt3rfly)", 6 | "maintainers": [ 7 | "Eric Smekens (https://github.com/EricSmekens)", 8 | "Lea Verou (https://github.com/LeaVerou)", 9 | "Shelly (https://github.com/6utt3rfly)" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/numbers#readme", 15 | "license": "MIT", 16 | "repository": { 17 | "url": "EricSmekens/jsep", 18 | "directory": "packages/numbers" 19 | }, 20 | "type": "module", 21 | "main": "./dist/cjs/index.cjs.js", 22 | "module": "./dist/index.js", 23 | "types": "types/tsd.d.ts", 24 | "peerDependencies": { 25 | "jsep": "^0.4.0||^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "rollup": "^2.44.0", 29 | "rollup-plugin-delete": "^2.0.0" 30 | }, 31 | "engines": { 32 | "node": ">= 10.16.0" 33 | }, 34 | "scripts": { 35 | "build": "rollup -c ../../plugin.rollup.config.js && cp ../../package-cjs.json dist/cjs/package.json && cp ../../LICENSE ./", 36 | "test": "cd ../../ && http-server -p 49649 --silent & node-qunit-puppeteer http://localhost:49649/packages/numbers/test/unit_tests.html", 37 | "lint": "eslint src/**/*.js test/**/*.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/numbers/src/index.js: -------------------------------------------------------------------------------- 1 | const NUM_0_CODE = 48; 2 | const UNDERSCORE = 95; 3 | 4 | export default { 5 | name: 'numbers', 6 | 7 | init(Jsep) { 8 | Jsep.hooks.add('gobble-token', function gobbleNumber(env) { 9 | if (this.code === NUM_0_CODE) { 10 | const startIndex = this.index; 11 | const numType = this.expr.charAt(this.index + 1); 12 | const ranges = getNumberCodeRanges.call(this, numType); 13 | if (!ranges) { 14 | return; 15 | } 16 | 17 | let number = ''; 18 | while (isUnderscoreOrWithinRange(this.code, ranges)) { 19 | if (this.code === UNDERSCORE) { 20 | this.index ++; 21 | } 22 | else { 23 | number += this.expr.charAt(this.index++); 24 | } 25 | } 26 | 27 | // confirm valid syntax after building number string within ranges 28 | if (Jsep.isIdentifierPart(this.code)) { 29 | if (Jsep.isDecimalDigit(this.code) && Jsep.isDecimalDigit(numType.charCodeAt(0))) { 30 | // abort octal processing and reset to ignore the leading 0 31 | this.index = startIndex + 1; 32 | gobbleBase10.call(this, env); 33 | return; 34 | } 35 | this.throwError('unexpected char within number'); 36 | } 37 | 38 | env.node = { 39 | type: Jsep.LITERAL, 40 | value: parseInt(number, getNumberBase(numType)), 41 | raw: this.expr.substring(startIndex, this.index), 42 | }; 43 | } 44 | else if (Jsep.isDecimalDigit(this.code) || this.code === Jsep.PERIOD_CODE) { 45 | gobbleBase10.call(this, env); 46 | } 47 | }); 48 | 49 | /** 50 | * Gets the range of allowable number codes (decimal) and updates index 51 | * @param {string} numType 52 | * @returns {number[][]|null} 53 | */ 54 | function getNumberCodeRanges(numType) { 55 | if (numType === 'x' || numType === 'X') { 56 | this.index += 2; 57 | return [ 58 | [48, 57], // 0-9 59 | [65, 70], // A-F 60 | [97, 102], // a-f 61 | ]; 62 | } 63 | else if (numType === 'b' || numType === 'B') { 64 | this.index += 2; 65 | return [[48, 49]]; // 0-1 66 | } 67 | else if (numType === 'o' || numType === 'O' || 68 | (numType >= '0' && numType <= '7')) { // 0-7 allows non-standard 0644 = 420 69 | this.index += numType <= '7' ? 1 : 2; 70 | return [[48, 55]]; // 0-7 71 | } 72 | return null; 73 | } 74 | 75 | /** 76 | * Supports Hex, Octal and Binary only 77 | * @param {string} numType 78 | * @returns {16|8|2} 79 | */ 80 | function getNumberBase(numType) { 81 | if (numType === 'x' || numType === 'X') { 82 | return 16; 83 | } 84 | else if (numType === 'b' || numType === 'B') { 85 | return 2; 86 | } 87 | // default (includes non-stand 044) 88 | return 8; 89 | } 90 | 91 | /** 92 | * Verifies number code is within given ranges 93 | * @param {number} code 94 | * @param {number[][]} ranges 95 | */ 96 | function isUnderscoreOrWithinRange(code, ranges) { 97 | return code === UNDERSCORE || 98 | ranges.some(([min, max]) => code >= min && code <= max); 99 | } 100 | 101 | /** 102 | * Same as core gobbleNumericLiteral, but allows for _ char 103 | * @param {{ context?: typeof Jsep, node?: Expression }} env 104 | */ 105 | function gobbleBase10(env) { 106 | const startIndex = this.index; 107 | let number = ''; 108 | 109 | const gobbleDigits = () => { 110 | while (Jsep.isDecimalDigit(this.code) || this.code === UNDERSCORE) { 111 | if (this.code === UNDERSCORE) { 112 | this.index++; 113 | } 114 | else { 115 | number += this.expr.charAt(this.index++); 116 | } 117 | } 118 | }; 119 | 120 | gobbleDigits(); 121 | if (this.code === Jsep.PERIOD_CODE) { // can start with a decimal marker 122 | number += this.expr.charAt(this.index++); 123 | 124 | gobbleDigits(); 125 | } 126 | 127 | let ch = this.char; 128 | if (ch === 'e' || ch === 'E') { // exponent marker 129 | number += this.expr.charAt(this.index++); 130 | ch = this.char; 131 | 132 | if (ch === '+' || ch === '-') { // exponent sign 133 | number += this.expr.charAt(this.index++); 134 | } 135 | 136 | gobbleDigits(); // exponent itself 137 | 138 | if (!Jsep.isDecimalDigit(this.expr.charCodeAt(this.index - 1)) ) { 139 | this.throwError('Expected exponent (' + number + this.char + ')'); 140 | } 141 | } 142 | 143 | const chCode = this.code; 144 | 145 | // Check to make sure this isn't a variable name that start with a number (123abc) 146 | if (Jsep.isIdentifierStart(chCode)) { 147 | this.throwError('Variable names cannot start with a number (' + 148 | number + this.char + ')'); 149 | } 150 | else if (chCode === Jsep.PERIOD_CODE) { 151 | if (number.length > 1) { 152 | this.throwError(`Unexpected period ${JSON.stringify({ chCode, number }, null, 2)}`); 153 | } 154 | // leave other error-handling to jsep core. Also allows spread operator. 155 | this.index = startIndex; 156 | return; 157 | } 158 | 159 | env.node = { 160 | type: Jsep.LITERAL, 161 | value: parseFloat(number), 162 | raw: this.expr.substring(startIndex, this.index), 163 | }; 164 | } 165 | } 166 | }; 167 | -------------------------------------------------------------------------------- /packages/numbers/test/index.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../../../src/index.js'; 2 | import jsepNumbers from '../src/index.js'; 3 | import { testParser, resetJsepDefaults } from '../../../test/test_utils.js'; 4 | 5 | const { test } = QUnit; 6 | 7 | (function () { 8 | QUnit.module('Plugin:Numbers', (qunit) => { 9 | qunit.before(() => jsep.plugins.register(jsepNumbers)); 10 | qunit.after(resetJsepDefaults); 11 | 12 | [ 13 | ['0xA', 10], 14 | ['0xfF', 255], 15 | ['0xA_a', 170], 16 | ['0b01010101', 85], 17 | ['0b0000_0111', 7], 18 | ['0b1000_0000__0000_0000__0000_0000__0000_0000', 2147483648], 19 | ['0755', 493], 20 | ['0o644', 420], 21 | ['0795', 795], // aborts octal, reverts to decimal 22 | ['115_200', 115200], 23 | ['10_000E1', 10000E1], 24 | ['1_0E2_0', 10E20], 25 | ['1_0E+2_0', 10E20], 26 | ['1E-2', 1e-2], 27 | ['1_234.567_89', 1234.56789], 28 | ['0.1', 0.1], 29 | ['.1', .1], 30 | ].forEach(([expr, value]) => test(`should parse the value correctly ${expr}`, (assert) => { 31 | testParser(expr, { value }, assert); 32 | })); 33 | 34 | [ 35 | '0b12abc', 36 | '123abc', 37 | '1_2_abc', 38 | '0755a', 39 | '0x1Z', 40 | '12_34.56.78', 41 | 'ab_cd.123', 42 | '1_2E3.4', 43 | ].forEach(expr => test(`should give an error for invalid expression ${expr}`, (assert) => { 44 | assert.throws(() => jsep(expr)); 45 | })); 46 | 47 | [ 48 | '_123', 49 | '_0b123', 50 | ].forEach((expr) => test(`should still parse identifier correctly ${expr}`, (assert) => { 51 | testParser(expr, { type: 'Identifier', name: expr }, assert); 52 | })); 53 | }); 54 | }()); 55 | -------------------------------------------------------------------------------- /packages/numbers/test/unit_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

            Plugin-Numbers Unit Tests

            16 |

            17 |
            18 |

            19 |
              20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/numbers/types/tsd.d.ts: -------------------------------------------------------------------------------- 1 | import * as jsep from 'jsep'; 2 | import { IPlugin } from 'jsep'; 3 | export const name: string; 4 | export function init(this: typeof jsep): void; 5 | 6 | declare const _export: IPlugin; 7 | export default _export; 8 | -------------------------------------------------------------------------------- /packages/object/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !package.json 4 | !LICENSE 5 | !README.md 6 | !dist/**/* 7 | !types/**/* 8 | -------------------------------------------------------------------------------- /packages/object/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [@jsep-plugin/object-v1.2.2](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/object-v1.2.1...@jsep-plugin/object-v1.2.2) (2024-11-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add license file to build output ([f6678fb](https://github.com/EricSmekens/jsep/commit/f6678fb0869188e9c9575fed231864f75e99af74)), closes [#267](https://github.com/EricSmekens/jsep/issues/267) 7 | 8 | # [@jsep-plugin/object-v1.2.1](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/object-v1.2.0...@jsep-plugin/object-v1.2.1) (2022-09-18) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * export default IPlugin from all plugins ([cd38da5](https://github.com/EricSmekens/jsep/commit/cd38da58e0a32d8cc05fe1e4ac3791459ee90986)), closes [#231](https://github.com/EricSmekens/jsep/issues/231) 14 | 15 | # [@jsep-plugin/object-v1.2.0](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/object-v1.1.0...@jsep-plugin/object-v1.2.0) (2022-02-28) 16 | 17 | 18 | ### Features 19 | 20 | * rewrite object plugin to avoid adding ':' as a binary operator ([9a5feb1](https://github.com/EricSmekens/jsep/commit/9a5feb1f831215a7d8e55047c8e3aea8293736f8)) 21 | 22 | # [@jsep-plugin/object-v1.2.0-beta.1](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/object-v1.1.0...@jsep-plugin/object-v1.2.0-beta.1) (2021-12-13) 23 | 24 | 25 | ### Features 26 | 27 | * rewrite object plugin to avoid adding ':' as a binary operator ([9a5feb1](https://github.com/EricSmekens/jsep/commit/9a5feb1f831215a7d8e55047c8e3aea8293736f8)) 28 | 29 | # [@jsep-plugin/object-v1.1.0](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/object-v1.0.1...@jsep-plugin/object-v1.1.0) (2021-10-03) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * add value key for IDENTIFIER object properties for consistency ([486525d](https://github.com/EricSmekens/jsep/commit/486525dd691dd71ba7a2bef7ad82871d14df7a8b)) 35 | * binary : operator should be greater than assignment operators ([6b58011](https://github.com/EricSmekens/jsep/commit/6b58011da86987fe55d96e3a88dcb94c2350b8b5)) 36 | 37 | 38 | ### Features 39 | 40 | * add support for member expressions of an object `{ a: 1 }['a']` ([b5bcc60](https://github.com/EricSmekens/jsep/commit/b5bcc60e3055fe3808b1a24cdccac202c437dcf8)) 41 | 42 | # @jsep-plugin/object-v1.0.0 (2021-10-03) 43 | 44 | 45 | ### Bug Fixes 46 | 47 | * add value key for IDENTIFIER object properties for consistency ([486525d](https://github.com/EricSmekens/jsep/commit/486525dd691dd71ba7a2bef7ad82871d14df7a8b)) 48 | * binary : operator should be greater than assignment operators ([6b58011](https://github.com/EricSmekens/jsep/commit/6b58011da86987fe55d96e3a88dcb94c2350b8b5)) 49 | 50 | 51 | ### Features 52 | 53 | * add support for member expressions of an object `{ a: 1 }['a']` ([b5bcc60](https://github.com/EricSmekens/jsep/commit/b5bcc60e3055fe3808b1a24cdccac202c437dcf8)) 54 | 55 | ## 1.0.1 - 2021-08-26 56 | republish with types included 57 | 58 | ## 1.0.0 - 2021-08-24 59 | Initial Release 60 | -------------------------------------------------------------------------------- /packages/object/README.md: -------------------------------------------------------------------------------- 1 | [npm]: https://img.shields.io/npm/v/@jsep-plugin/object 2 | [npm-url]: https://www.npmjs.com/package/@jsep-plugin/object 3 | [size]: https://packagephobia.now.sh/badge?p=@jsep-plugin/object 4 | [size-url]: https://packagephobia.now.sh/result?p=@jsep-plugin/object 5 | 6 | [![npm][npm]][npm-url] 7 | [![size][size]][size-url] 8 | 9 | # @jsep-plugin/object 10 | 11 | A JSEP plugin for adding object expression support. Allows expressions of the form: 12 | 13 | ```javascript 14 | jsep('{ a: 1, b: { c }}[d]'); 15 | ``` 16 | 17 | ## Install 18 | 19 | ```console 20 | npm install @jsep-plugin/object 21 | # or 22 | yarn add @jsep-plugin/object 23 | ``` 24 | 25 | ## Usage 26 | ```javascript 27 | import jsep from 'jsep'; 28 | import jsepObject from '@jsep-plugin/object'; 29 | jsep.plugins.register(jsepObject); 30 | ``` 31 | 32 | ## Meta 33 | 34 | [LICENSE (MIT)](/LICENSE) 35 | -------------------------------------------------------------------------------- /packages/object/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsep-plugin/object", 3 | "version": "1.2.2", 4 | "description": "Adds object expression support", 5 | "author": "Shelly (https://github.com/6utt3rfly)", 6 | "maintainers": [ 7 | "Eric Smekens (https://github.com/EricSmekens)", 8 | "Lea Verou (https://github.com/LeaVerou)", 9 | "Shelly (https://github.com/6utt3rfly)" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/object#readme", 15 | "license": "MIT", 16 | "repository": { 17 | "url": "EricSmekens/jsep", 18 | "directory": "packages/object" 19 | }, 20 | "type": "module", 21 | "main": "./dist/cjs/index.cjs.js", 22 | "module": "./dist/index.js", 23 | "types": "types/tsd.d.ts", 24 | "peerDependencies": { 25 | "jsep": "^0.4.0||^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "rollup": "^2.44.0", 29 | "rollup-plugin-delete": "^2.0.0" 30 | }, 31 | "engines": { 32 | "node": ">= 10.16.0" 33 | }, 34 | "scripts": { 35 | "build": "rollup -c ../../plugin.rollup.config.js && cp ../../package-cjs.json dist/cjs/package.json && cp ../../LICENSE ./", 36 | "test": "cd ../../ && http-server -p 49649 --silent & node-qunit-puppeteer http://localhost:49649/packages/object/test/unit_tests.html", 37 | "lint": "eslint src/**/*.js test/**/*.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/object/src/index.js: -------------------------------------------------------------------------------- 1 | const OCURLY_CODE = 123; // { 2 | const CCURLY_CODE = 125; // } 3 | const OBJECT_EXP = 'ObjectExpression'; 4 | const PROPERTY = 'Property'; 5 | 6 | export default { 7 | name: 'object', 8 | 9 | init(jsep) { 10 | // Object literal support 11 | function gobbleObjectExpression(env) { 12 | if (this.code === OCURLY_CODE) { 13 | this.index++; 14 | const properties = []; 15 | 16 | while (!isNaN(this.code)) { 17 | this.gobbleSpaces(); 18 | if (this.code === CCURLY_CODE) { 19 | this.index++; 20 | env.node = this.gobbleTokenProperty({ 21 | type: OBJECT_EXP, 22 | properties, 23 | }); 24 | return; 25 | } 26 | 27 | // Note: using gobbleExpression instead of gobbleToken to support object destructuring 28 | const key = this.gobbleExpression(); 29 | if (!key) { 30 | break; // missing } 31 | } 32 | 33 | this.gobbleSpaces(); 34 | if (key.type === jsep.IDENTIFIER && (this.code === jsep.COMMA_CODE || this.code === CCURLY_CODE)) { 35 | // property value shorthand 36 | properties.push({ 37 | type: PROPERTY, 38 | computed: false, 39 | key, 40 | value: key, 41 | shorthand: true, 42 | }); 43 | } 44 | else if (this.code === jsep.COLON_CODE) { 45 | this.index++; 46 | const value = this.gobbleExpression(); 47 | 48 | if (!value) { 49 | this.throwError('unexpected object property'); 50 | } 51 | const computed = key.type === jsep.ARRAY_EXP; 52 | properties.push({ 53 | type: PROPERTY, 54 | computed, 55 | key: computed 56 | ? key.elements[0] 57 | : key, 58 | value: value, 59 | shorthand: false, 60 | }); 61 | this.gobbleSpaces(); 62 | } 63 | else if (key) { 64 | // spread, assignment (object destructuring with defaults), etc. 65 | properties.push(key); 66 | } 67 | 68 | if (this.code === jsep.COMMA_CODE) { 69 | this.index++; 70 | } 71 | } 72 | this.throwError('missing }'); 73 | } 74 | } 75 | 76 | jsep.hooks.add('gobble-token', gobbleObjectExpression); 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /packages/object/test/index.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../../../src/index.js'; 2 | import object from '../src/index.js'; 3 | import { testParser, resetJsepDefaults } from '../../../test/test_utils.js'; 4 | 5 | const { test } = QUnit; 6 | 7 | (function () { 8 | QUnit.module('Plugin:Object', (qunit) => { 9 | qunit.before(() => jsep.plugins.register(object)); 10 | qunit.after(resetJsepDefaults); 11 | 12 | test('should parse basic object expression', (assert) => { 13 | testParser('({ a: 1, b: 2 })', { 14 | type: "ObjectExpression", 15 | properties: [ 16 | { 17 | type: "Property", 18 | computed: false, 19 | key: { 20 | type: "Identifier", 21 | name: "a" 22 | }, 23 | value: { 24 | type: "Literal", 25 | value: 1, 26 | raw: "1" 27 | }, 28 | shorthand: false 29 | }, 30 | { 31 | type: "Property", 32 | computed: false, 33 | key: { 34 | type: "Identifier", 35 | name: "b" 36 | }, 37 | value: { 38 | type: "Literal", 39 | value: 2, 40 | raw: "2" 41 | }, 42 | shorthand: false 43 | } 44 | ] 45 | }, assert); 46 | }); 47 | 48 | test('should parse object with variable key', (assert) => { 49 | testParser('{ [key || key2]: { a: 0 } }', { 50 | type: "ObjectExpression", 51 | properties: [ 52 | { 53 | type: "Property", 54 | computed: true, 55 | key: { 56 | type: "BinaryExpression", 57 | operator: "||", 58 | left: { 59 | type: "Identifier", 60 | name: "key" 61 | }, 62 | right: { 63 | type: "Identifier", 64 | name: "key2" 65 | } 66 | }, 67 | value: { 68 | type: "ObjectExpression", 69 | properties: [ 70 | { 71 | type: "Property", 72 | computed: false, 73 | key: { 74 | type: "Identifier", 75 | name: "a" 76 | }, 77 | value: { 78 | type: "Literal", 79 | value: 0, 80 | raw: "0" 81 | }, 82 | shorthand: false 83 | } 84 | ] 85 | }, 86 | shorthand: false 87 | } 88 | ] 89 | }, assert); 90 | }); 91 | 92 | test('should parse object with member expression', (assert) => { 93 | testParser('{a:1}[b]', { 94 | type: 'MemberExpression', 95 | computed: true, 96 | object: { 97 | type: 'ObjectExpression', 98 | properties: [ 99 | { 100 | type: 'Property', 101 | computed: false, 102 | key: { 103 | type: 'Identifier', 104 | name: 'a' 105 | }, 106 | value: { 107 | type: 'Literal', 108 | value: 1, 109 | raw: '1' 110 | }, 111 | shorthand: false 112 | } 113 | ] 114 | }, 115 | property: { 116 | type: 'Identifier', 117 | name: 'b' 118 | } 119 | }, assert); 120 | }); 121 | 122 | test('should parse nested objects', (assert) => { 123 | QUnit.dump.maxDepth = 10; 124 | testParser('{ a: { b: { c: 1 } } }', { 125 | type: 'ObjectExpression', 126 | properties: [ 127 | { 128 | type: 'Property', 129 | computed: false, 130 | shorthand: false, 131 | key: { 132 | type: 'Identifier', 133 | name: 'a', 134 | }, 135 | value: { 136 | type: 'ObjectExpression', 137 | properties: [ 138 | { 139 | type: 'Property', 140 | computed: false, 141 | shorthand: false, 142 | key: { 143 | type: 'Identifier', 144 | name: 'b', 145 | }, 146 | value: { 147 | type: 'ObjectExpression', 148 | properties: [ 149 | { 150 | type: 'Property', 151 | computed: false, 152 | shorthand: false, 153 | key: { 154 | type: 'Identifier', 155 | name: 'c', 156 | }, 157 | value: { 158 | type: 'Literal', 159 | value: 1, 160 | raw: '1', 161 | }, 162 | }, 163 | ], 164 | }, 165 | }, 166 | ], 167 | }, 168 | }, 169 | ], 170 | }, assert); 171 | }); 172 | 173 | test('should parse nested complex ternary with object plugin', (assert) => { 174 | testParser('a ? b ? 1 : c ? d ? e ? 3 : 4 : 5 : 6 : 7', { 175 | type: 'ConditionalExpression', 176 | test: { 177 | type: 'Identifier', 178 | name: 'a' 179 | }, 180 | consequent: { 181 | type: 'ConditionalExpression', 182 | test: { 183 | type: 'Identifier', 184 | name: 'b' 185 | }, 186 | consequent: { 187 | type: 'Literal', 188 | value: 1, 189 | raw: '1' 190 | }, 191 | alternate: { 192 | type: 'ConditionalExpression', 193 | test: { 194 | type: 'Identifier', 195 | name: 'c' 196 | }, 197 | consequent: { 198 | type: 'ConditionalExpression', 199 | test: { 200 | type: 'Identifier', 201 | name: 'd' 202 | }, 203 | consequent: { 204 | type: 'ConditionalExpression', 205 | test: { 206 | type: 'Identifier', 207 | name: 'e' 208 | }, 209 | consequent: { 210 | type: 'Literal', 211 | value: 3, 212 | raw: '3' 213 | }, 214 | alternate: { 215 | type: 'Literal', 216 | value: 4, 217 | raw: '4' 218 | } 219 | }, 220 | alternate: { 221 | type: 'Literal', 222 | value: 5, 223 | raw: '5' 224 | } 225 | }, 226 | alternate: { 227 | type: 'Literal', 228 | value: 6, 229 | raw: '6' 230 | } 231 | } 232 | }, 233 | alternate: { 234 | type: 'Literal', 235 | value: 7, 236 | raw: '7' 237 | } 238 | }, assert); 239 | }); 240 | 241 | test('should parse nested ternary consequent/alternates while object plugin loaded', (assert) => { 242 | testParser('a ? 0 : b ? 1 : 2', { 243 | type: 'ConditionalExpression', 244 | test: { 245 | type: 'Identifier', 246 | name: 'a' 247 | }, 248 | consequent: { 249 | type: 'Literal', 250 | value: 0, 251 | raw: '0' 252 | }, 253 | alternate: { 254 | type: 'ConditionalExpression', 255 | test: { 256 | type: 'Identifier', 257 | name: 'b' 258 | }, 259 | consequent: { 260 | type: 'Literal', 261 | value: 1, 262 | raw: '1' 263 | }, 264 | alternate: { 265 | type: 'Literal', 266 | value: 2, 267 | raw: '2' 268 | } 269 | } 270 | }, assert); 271 | }); 272 | 273 | test('should parse nested ternary with object values', (assert) => { 274 | testParser('a ? { a: 1 } : b ? { b: 1 } : { c: 1 }[c] === 1 ? \'c\' : null', { 275 | type: 'ConditionalExpression', 276 | test: { 277 | type: 'Identifier', 278 | name: 'a' 279 | }, 280 | consequent: { 281 | type: 'ObjectExpression', 282 | properties: [ 283 | { 284 | type: 'Property', 285 | computed: false, 286 | key: { 287 | type: 'Identifier', 288 | name: 'a' 289 | }, 290 | value: { 291 | type: 'Literal', 292 | value: 1, 293 | raw: '1' 294 | }, 295 | shorthand: false 296 | } 297 | ] 298 | }, 299 | alternate: { 300 | type: 'ConditionalExpression', 301 | test: { 302 | type: 'Identifier', 303 | name: 'b' 304 | }, 305 | consequent: { 306 | type: 'ObjectExpression', 307 | properties: [ 308 | { 309 | type: 'Property', 310 | computed: false, 311 | key: { 312 | type: 'Identifier', 313 | name: 'b' 314 | }, 315 | value: { 316 | type: 'Literal', 317 | value: 1, 318 | raw: '1' 319 | }, 320 | shorthand: false 321 | } 322 | ] 323 | }, 324 | alternate: { 325 | type: 'ConditionalExpression', 326 | test: { 327 | type: 'BinaryExpression', 328 | operator: '===', 329 | left: { 330 | type: 'MemberExpression', 331 | computed: true, 332 | object: { 333 | type: 'ObjectExpression', 334 | properties: [ 335 | { 336 | type: 'Property', 337 | computed: false, 338 | key: { 339 | type: 'Identifier', 340 | name: 'c' 341 | }, 342 | value: { 343 | type: 'Literal', 344 | value: 1, 345 | raw: '1' 346 | }, 347 | shorthand: false 348 | } 349 | ] 350 | }, 351 | property: { 352 | type: 'Identifier', 353 | name: 'c' 354 | } 355 | }, 356 | right: { 357 | type: 'Literal', 358 | value: 1, 359 | raw: '1' 360 | } 361 | }, 362 | consequent: { 363 | type: 'Literal', 364 | value: 'c', 365 | raw: '\'c\'' 366 | }, 367 | alternate: { 368 | type: 'Literal', 369 | value: null, 370 | raw: 'null' 371 | } 372 | } 373 | } 374 | }, assert); 375 | }); 376 | 377 | [ 378 | '{ a: b ? 1 : 2, c }', // mixed object/ternary 379 | 'fn({ a: 1 })', // function argument 380 | 'a ? 0 : b ? 1 : 2', // nested ternary with no () 381 | ].forEach(expr => test(`should not throw an error ${expr}`, (assert) => { 382 | testParser(expr, {}, assert); 383 | })); 384 | 385 | [ 386 | '{ a: }', // missing value 387 | '{ a: 1 ', // missing } 388 | '{ a: 2 ? 3, b }', // missing : in ternary 389 | ].forEach(expr => test(`should give an error for invalid expression: "${expr}"`, (assert) => { 390 | assert.throws(() => jsep(expr)); 391 | })); 392 | }); 393 | }()); 394 | -------------------------------------------------------------------------------- /packages/object/test/unit_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

              Plugin-object Unit Tests

              16 |

              17 |
              18 |

              19 |
                20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/object/types/tsd.d.ts: -------------------------------------------------------------------------------- 1 | import * as jsep from 'jsep'; 2 | import { Expression, IPlugin } from 'jsep'; 3 | export const name: string; 4 | export function init(this: typeof jsep): void; 5 | 6 | export interface ObjectExpression extends Expression { 7 | type: 'ObjectExpression'; 8 | properties: Property[]; 9 | } 10 | 11 | export interface Property extends Expression { 12 | type: 'Property'; 13 | computed: boolean; 14 | key: Expression; 15 | shorthand: boolean; 16 | value?: Expression; 17 | } 18 | 19 | declare const _export: IPlugin; 20 | export default _export; 21 | -------------------------------------------------------------------------------- /packages/regex/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !package.json 4 | !LICENSE 5 | !README.md 6 | !dist/**/* 7 | !types/**/* 8 | -------------------------------------------------------------------------------- /packages/regex/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [@jsep-plugin/regex-v1.0.4](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/regex-v1.0.3...@jsep-plugin/regex-v1.0.4) (2024-11-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add license file to build output ([f6678fb](https://github.com/EricSmekens/jsep/commit/f6678fb0869188e9c9575fed231864f75e99af74)), closes [#267](https://github.com/EricSmekens/jsep/issues/267) 7 | 8 | # [@jsep-plugin/regex-v1.0.3](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/regex-v1.0.2...@jsep-plugin/regex-v1.0.3) (2022-09-18) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * export default IPlugin from all plugins ([cd38da5](https://github.com/EricSmekens/jsep/commit/cd38da58e0a32d8cc05fe1e4ac3791459ee90986)), closes [#231](https://github.com/EricSmekens/jsep/issues/231) 14 | 15 | # [@jsep-plugin/regex-v1.0.2](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/regex-v1.0.1...@jsep-plugin/regex-v1.0.2) (2022-03-22) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * handle charsets and escaped / within regex ([6c08994](https://github.com/EricSmekens/jsep/commit/6c08994e84fe9373e9b612ca9b29b83e0f6bde91)), closes [#214](https://github.com/EricSmekens/jsep/issues/214) 21 | 22 | ## 1.0.1 - 2021-08-26 23 | republish with types included 24 | 25 | ## 1.0.0 - 2021-08-24 26 | Initial Release 27 | -------------------------------------------------------------------------------- /packages/regex/README.md: -------------------------------------------------------------------------------- 1 | [npm]: https://img.shields.io/npm/v/@jsep-plugin/regex 2 | [npm-url]: https://www.npmjs.com/package/@jsep-plugin/regex 3 | [size]: https://packagephobia.now.sh/badge?p=@jsep-plugin/regex 4 | [size-url]: https://packagephobia.now.sh/result?p=@jsep-plugin/regex 5 | 6 | [![npm][npm]][npm-url] 7 | [![size][size]][size-url] 8 | 9 | # @jsep-plugin/regex 10 | 11 | A JSEP plugin for adding regex expression support. Allows expressions of the form: 12 | 13 | ```javascript 14 | jsep('/abc/'); 15 | jsep('/abc/ig'); 16 | jsep('/[a-z]{3}/ig.test(a)'); 17 | ``` 18 | 19 | ## Install 20 | 21 | ```console 22 | npm install @jsep-plugin/regex 23 | # or 24 | yarn add @jsep-plugin/regex 25 | ``` 26 | 27 | ## Usage 28 | ```javascript 29 | import jsep from 'jsep'; 30 | import jsepRegex from '@jsep-plugin/regex'; 31 | jsep.plugins.register(jsepRegex); 32 | ``` 33 | 34 | ## Meta 35 | 36 | [LICENSE (MIT)](/LICENSE) 37 | -------------------------------------------------------------------------------- /packages/regex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsep-plugin/regex", 3 | "version": "1.0.4", 4 | "description": "Adds regex expression support", 5 | "author": "Shelly (https://github.com/6utt3rfly)", 6 | "maintainers": [ 7 | "Eric Smekens (https://github.com/EricSmekens)", 8 | "Lea Verou (https://github.com/LeaVerou)", 9 | "Shelly (https://github.com/6utt3rfly)" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/regex#readme", 15 | "license": "MIT", 16 | "repository": { 17 | "url": "EricSmekens/jsep", 18 | "directory": "packages/regex" 19 | }, 20 | "type": "module", 21 | "main": "./dist/cjs/index.cjs.js", 22 | "module": "./dist/index.js", 23 | "types": "types/tsd.d.ts", 24 | "peerDependencies": { 25 | "jsep": "^0.4.0||^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "rollup": "^2.44.0", 29 | "rollup-plugin-delete": "^2.0.0" 30 | }, 31 | "engines": { 32 | "node": ">= 10.16.0" 33 | }, 34 | "scripts": { 35 | "build": "rollup -c ../../plugin.rollup.config.js && cp ../../package-cjs.json dist/cjs/package.json && cp ../../LICENSE ./", 36 | "test": "cd ../../ && http-server -p 49649 --silent & node-qunit-puppeteer http://localhost:49649/packages/regex/test/unit_tests.html", 37 | "lint": "eslint src/**/*.js test/**/*.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/regex/src/index.js: -------------------------------------------------------------------------------- 1 | const FSLASH_CODE = 47; // '/' 2 | const BSLASH_CODE = 92; // '\\' 3 | 4 | export default { 5 | name: 'regex', 6 | 7 | init(jsep) { 8 | // Regex literal: /abc123/ig 9 | jsep.hooks.add('gobble-token', function gobbleRegexLiteral(env) { 10 | if (this.code === FSLASH_CODE) { 11 | const patternIndex = ++this.index; 12 | 13 | let inCharSet = false; 14 | while (this.index < this.expr.length) { 15 | if (this.code === FSLASH_CODE && !inCharSet) { 16 | const pattern = this.expr.slice(patternIndex, this.index); 17 | 18 | let flags = ''; 19 | while (++this.index < this.expr.length) { 20 | const code = this.code; 21 | if ((code >= 97 && code <= 122) // a...z 22 | || (code >= 65 && code <= 90) // A...Z 23 | || (code >= 48 && code <= 57)) { // 0-9 24 | flags += this.char; 25 | } 26 | else { 27 | break; 28 | } 29 | } 30 | 31 | let value; 32 | try { 33 | value = new RegExp(pattern, flags); 34 | } 35 | catch (e) { 36 | this.throwError(e.message); 37 | } 38 | 39 | env.node = { 40 | type: jsep.LITERAL, 41 | value, 42 | raw: this.expr.slice(patternIndex - 1, this.index), 43 | }; 44 | 45 | // allow . [] and () after regex: /regex/.test(a) 46 | env.node = this.gobbleTokenProperty(env.node); 47 | return env.node; 48 | } 49 | if (this.code === jsep.OBRACK_CODE) { 50 | inCharSet = true; 51 | } 52 | else if (inCharSet && this.code === jsep.CBRACK_CODE) { 53 | inCharSet = false; 54 | } 55 | this.index += this.code === BSLASH_CODE ? 2 : 1; 56 | } 57 | this.throwError('Unclosed Regex'); 58 | } 59 | }); 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /packages/regex/test/index.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../../../src/index.js'; 2 | import jsepRegex from '../src/index.js'; 3 | import {testParser, resetJsepDefaults, esprimaComparisonTest} from '../../../test/test_utils.js'; 4 | 5 | const { test } = QUnit; 6 | 7 | (function () { 8 | QUnit.module('Plugin:Regex Literal', (qunit) => { 9 | qunit.before(() => jsep.plugins.register(jsepRegex)); 10 | qunit.after(resetJsepDefaults); 11 | 12 | test('should parse basic regular expression', (assert) => { 13 | testParser('/abc/', { 14 | type: 'Literal', 15 | value: /abc/, 16 | raw: '/abc/', 17 | }, assert); 18 | }); 19 | 20 | test('should parse basic regex with flags', (assert) => { 21 | testParser('/abc/ig', { 22 | type: 'Literal', 23 | value: /abc/gi, 24 | raw: '/abc/ig', 25 | }, assert); 26 | }); 27 | 28 | test('should handle escapes properly', (assert) => { 29 | testParser('/\\d{3}/', { 30 | type: 'Literal', 31 | value: /\d{3}/, 32 | raw: '/\\d{3}/', 33 | }, assert); 34 | }); 35 | 36 | test('should be able to escape / properly', (assert) => { 37 | testParser('/^\\/$/', { 38 | type: 'Literal', 39 | value: /^\/$/, 40 | raw: '/^\\/$/', 41 | }, assert); 42 | }); 43 | 44 | test('should parse more complex regex within expression', (assert) => { 45 | testParser('a && /[a-z]{3}/ig.test(b)', { 46 | type: 'BinaryExpression', // Note: Esprima = LogicalExpression, but but jsep has `&&` as a binary op 47 | operator: '&&', 48 | left: { 49 | type: 'Identifier', 50 | name: 'a' 51 | }, 52 | right: { 53 | type: 'CallExpression', 54 | arguments: [ 55 | { 56 | type: 'Identifier', 57 | name: 'b' 58 | } 59 | ], 60 | callee: { 61 | type: 'MemberExpression', 62 | computed: false, 63 | object: { 64 | type: 'Literal', 65 | value: /[a-z]{3}/ig, 66 | raw: '/[a-z]{3}/ig' 67 | }, 68 | property: { 69 | type: 'Identifier', 70 | name: 'test' 71 | } 72 | } 73 | } 74 | }, assert); 75 | }); 76 | 77 | [ 78 | '/[a-z]{3}/ig.test(b)', 79 | '/\\d(?=px)/', 80 | 'a / 2', // regex should not interfere with binary operator 81 | '/\\//', 82 | '/\\//', // => /\// 83 | '/[\\/]/', // => /[\/]/ 84 | '/[/]/', // => /[/]/ 85 | '/[\\]/]/', // => /[\]]/ 86 | '/\\\\\\//', // => /\\\// 87 | '/\\[/', // => /\[/ 88 | '2 / 3', // binop 89 | '2 / 3 / 4', // binop 90 | ].forEach((expr) => { 91 | test(`${expr} should match Esprima`, function (assert) { 92 | esprimaComparisonTest(expr, assert); 93 | }); 94 | }); 95 | 96 | [ 97 | '/\d(?=px)/.test(a)', 98 | 'a / /123/', 99 | '/123/ig["test"](b)', 100 | '/123/["test"](b)', 101 | '/\\p{Emoji_Presentation}/gu.test("ticket to 大阪 costs ¥2000 👌.")', 102 | '/abc/+/123/', 103 | ].forEach(expr => test(`should not throw an error on expression ${expr}`, (assert) => { 104 | testParser(expr, {}, assert); 105 | })); 106 | 107 | [ 108 | '/abc', // unclosed regex 109 | '/a/xzw', // invalid flag 110 | '/a/xyz.test(a)', // invalid flag 111 | '/a(/', // unclosed ( 112 | '/a[/', // unclosed [ 113 | '/[\\]/', // unclosed [ 114 | '/\\/', // unclosed regex 115 | ].forEach(expr => test(`should give an error for invalid expression ${expr}`, (assert) => { 116 | assert.throws(() => jsep(expr)); 117 | })); 118 | 119 | QUnit.module('Regex with binary / operators', (qu) => { 120 | qu.beforeEach(() => { 121 | resetJsepDefaults(); 122 | jsep.plugins.register(jsepRegex); 123 | jsep.addBinaryOp('/='); 124 | }); 125 | qu.after(resetJsepDefaults); 126 | 127 | test('Should parse correctly with /= and regex', (assert) => { 128 | testParser('a /= (/^\\d+$/.test(b) ? +b / 2 : 1)', { 129 | type: 'BinaryExpression', // (assignment plugin would convert to AssignmentExpression) 130 | operator: '/=', 131 | left: { 132 | type: 'Identifier', 133 | name: 'a' 134 | }, 135 | right: { 136 | type: 'ConditionalExpression', 137 | test: { 138 | type: 'CallExpression', 139 | arguments: [ 140 | { 141 | type: 'Identifier', 142 | name: 'b' 143 | } 144 | ], 145 | callee: { 146 | type: 'MemberExpression', 147 | computed: false, 148 | object: { 149 | type: 'Literal', 150 | value: /^\d+$/, 151 | raw: '/^\\d+$/' 152 | }, 153 | property: { 154 | type: 'Identifier', 155 | name: 'test' 156 | } 157 | } 158 | }, 159 | consequent: { 160 | type: 'BinaryExpression', 161 | operator: '/', 162 | left: { 163 | type: 'UnaryExpression', 164 | operator: '+', 165 | argument: { 166 | type: 'Identifier', 167 | name: 'b' 168 | }, 169 | prefix: true 170 | }, 171 | right: { 172 | type: 'Literal', 173 | value: 2, 174 | raw: '2' 175 | } 176 | }, 177 | alternate: { 178 | type: 'Literal', 179 | value: 1, 180 | raw: '1' 181 | } 182 | } 183 | }, assert); 184 | }); 185 | }); 186 | }); 187 | }()); 188 | -------------------------------------------------------------------------------- /packages/regex/test/unit_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

                Plugin-regex Unit Tests

                16 |

                17 |
                18 |

                19 |
                  20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/regex/types/tsd.d.ts: -------------------------------------------------------------------------------- 1 | import * as jsep from 'jsep'; 2 | import { IPlugin } from 'jsep'; 3 | export const name: string; 4 | export function init(this: typeof jsep): void; 5 | 6 | declare const _export: IPlugin; 7 | export default _export; 8 | -------------------------------------------------------------------------------- /packages/spread/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !package.json 4 | !LICENSE 5 | !README.md 6 | !dist/**/* 7 | !types/**/* 8 | -------------------------------------------------------------------------------- /packages/spread/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [@jsep-plugin/spread-v1.0.3](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/spread-v1.0.2...@jsep-plugin/spread-v1.0.3) (2024-11-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add license file to build output ([f6678fb](https://github.com/EricSmekens/jsep/commit/f6678fb0869188e9c9575fed231864f75e99af74)), closes [#267](https://github.com/EricSmekens/jsep/issues/267) 7 | 8 | # [@jsep-plugin/spread-v1.0.2](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/spread-v1.0.1...@jsep-plugin/spread-v1.0.2) (2022-09-18) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * export default IPlugin from all plugins ([cd38da5](https://github.com/EricSmekens/jsep/commit/cd38da58e0a32d8cc05fe1e4ac3791459ee90986)), closes [#231](https://github.com/EricSmekens/jsep/issues/231) 14 | 15 | ## 1.0.1 - 2021-08-26 16 | republish with types included 17 | 18 | ## 1.0.0 - 2021-08-24 19 | Initial Release 20 | -------------------------------------------------------------------------------- /packages/spread/README.md: -------------------------------------------------------------------------------- 1 | [npm]: https://img.shields.io/npm/v/@jsep-plugin/spread 2 | [npm-url]: https://www.npmjs.com/package/@jsep-plugin/spread 3 | [size]: https://packagephobia.now.sh/badge?p=@jsep-plugin/spread 4 | [size-url]: https://packagephobia.now.sh/result?p=@jsep-plugin/spread 5 | 6 | [![npm][npm]][npm-url] 7 | [![size][size]][size-url] 8 | 9 | # @jsep-plugin/spread 10 | 11 | A JSEP plugin for adding spread expression support. Allows expressions of the form: 12 | 13 | ```javascript 14 | jsep('fn(1, ...a)'); 15 | jsep('{ ...a }'); 16 | jsep('[...a]'); 17 | ``` 18 | 19 | ## Install 20 | 21 | ```console 22 | npm install @jsep-plugin/spread 23 | # or 24 | yarn add @jsep-plugin/spread 25 | ``` 26 | 27 | ## Usage 28 | ```javascript 29 | import jsep from 'jsep'; 30 | import jsepSpread from '@jsep-plugin/spread'; 31 | jsep.plugins.register(jsepSpread); 32 | ``` 33 | 34 | ## Meta 35 | 36 | [LICENSE (MIT)](/LICENSE) 37 | -------------------------------------------------------------------------------- /packages/spread/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsep-plugin/spread", 3 | "version": "1.0.3", 4 | "description": "Adds spread (...) expression support", 5 | "author": "Shelly (https://github.com/6utt3rfly)", 6 | "maintainers": [ 7 | "Eric Smekens (https://github.com/EricSmekens)", 8 | "Lea Verou (https://github.com/LeaVerou)", 9 | "Shelly (https://github.com/6utt3rfly)" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/spread#readme", 15 | "license": "MIT", 16 | "repository": { 17 | "url": "EricSmekens/jsep", 18 | "directory": "packages/spread" 19 | }, 20 | "type": "module", 21 | "main": "./dist/cjs/index.cjs.js", 22 | "module": "./dist/index.js", 23 | "types": "types/tsd.d.ts", 24 | "peerDependencies": { 25 | "jsep": "^0.4.0||^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "rollup": "^2.44.0", 29 | "rollup-plugin-delete": "^2.0.0" 30 | }, 31 | "engines": { 32 | "node": ">= 10.16.0" 33 | }, 34 | "scripts": { 35 | "build": "rollup -c ../../plugin.rollup.config.js && cp ../../package-cjs.json dist/cjs/package.json && cp ../../LICENSE ./", 36 | "test": "cd ../../ && http-server -p 49649 --silent & node-qunit-puppeteer http://localhost:49649/packages/spread/test/unit_tests.html", 37 | "lint": "eslint src/**/*.js test/**/*.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/spread/src/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'jsepSpread', 3 | 4 | init(jsep) { 5 | // Spread operator: ...a 6 | // Works in objects { ...a }, arrays [...a], function args fn(...a) 7 | // NOTE: does not prevent `a ? ...b : ...c` or `...123` 8 | jsep.hooks.add('gobble-token', function gobbleSpread(env) { 9 | if ([0, 1, 2].every(i => this.expr.charCodeAt(this.index + i) === jsep.PERIOD_CODE)) { 10 | this.index += 3; 11 | env.node = { 12 | type: 'SpreadElement', 13 | argument: this.gobbleExpression(), 14 | }; 15 | } 16 | }); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/spread/test/index.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../../../src/index.js'; 2 | import spread from '../src/index.js'; 3 | import {resetJsepDefaults, testParser} from '../../../test/test_utils.js'; 4 | 5 | const { test } = QUnit; 6 | 7 | (function () { 8 | QUnit.module('Plugin:Spread', (qunit) => { 9 | qunit.before(() => jsep.plugins.register(spread)); 10 | qunit.after(resetJsepDefaults); 11 | 12 | test('should parse array spread', (assert) => { 13 | testParser('[...a]', { 14 | type: 'ArrayExpression', 15 | elements: [ 16 | { 17 | type: 'SpreadElement', 18 | argument: { 19 | type: 'Identifier', 20 | 'name': 'a', 21 | }, 22 | }, 23 | ] 24 | }, assert); 25 | }); 26 | 27 | test('should parse function spread', (assert) => { 28 | testParser('fn(1, ...b)', { 29 | type: 'CallExpression', 30 | arguments: [ 31 | { 32 | type: 'Literal', 33 | value: 1, 34 | raw: '1' 35 | }, 36 | { 37 | type: 'SpreadElement', 38 | argument: { 39 | type: 'Identifier', 40 | name: 'b' 41 | } 42 | } 43 | ], 44 | callee: { 45 | type: 'Identifier', 46 | name: 'fn' 47 | } 48 | }, assert); 49 | }); 50 | 51 | test('should not throw any errors', (assert) => { 52 | [ 53 | 'fn(...123)', // NOTE: invalid iterator is not checked by jsep (same for [....4] = ... 0.4) 54 | 'fn(..."abc")', 55 | '[1, ...[2, 3]]', 56 | '[1, ...(a ? b : c)]', 57 | ].forEach(expr => testParser(expr, {}, assert)); 58 | }); 59 | 60 | test('should give an error for invalid expressions', (assert) => { 61 | [ 62 | // '[a ? ...b : ...c]', // is invalid JS, should be `...(a ? b : c)`, but jsep doesn't error 63 | '[.....5]', // extra .. 64 | '[..2]', // missing . 65 | '[...3', // missing ] 66 | ].forEach(expr => assert.throws(() => jsep(expr))); 67 | }); 68 | }); 69 | }()); 70 | -------------------------------------------------------------------------------- /packages/spread/test/unit_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

                  Plugin-spread Unit Tests

                  16 |

                  17 |
                  18 |

                  19 |
                    20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/spread/types/tsd.d.ts: -------------------------------------------------------------------------------- 1 | import * as jsep from 'jsep'; 2 | import { Expression, IPlugin } from 'jsep'; 3 | export const name: string; 4 | export function init(this: typeof jsep): void; 5 | 6 | export interface SpreadElement extends Expression { 7 | type: 'SpreadElement'; 8 | argument: Expression; 9 | } 10 | 11 | declare const _export: IPlugin; 12 | export default _export; 13 | -------------------------------------------------------------------------------- /packages/template/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !package.json 4 | !LICENSE 5 | !README.md 6 | !dist/**/* 7 | !types/**/* 8 | -------------------------------------------------------------------------------- /packages/template/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [@jsep-plugin/template-v1.0.5](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/template-v1.0.4...@jsep-plugin/template-v1.0.5) (2024-11-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add license file to build output ([f6678fb](https://github.com/EricSmekens/jsep/commit/f6678fb0869188e9c9575fed231864f75e99af74)), closes [#267](https://github.com/EricSmekens/jsep/issues/267) 7 | 8 | # [@jsep-plugin/template-v1.0.4](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/template-v1.0.3...@jsep-plugin/template-v1.0.4) (2024-03-27) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * support member exp after template and tagged template ([69dc0b1](https://github.com/EricSmekens/jsep/commit/69dc0b1e23ce6de43a3762a27017f7b5a1ca6aad)), closes [#259](https://github.com/EricSmekens/jsep/issues/259) 14 | 15 | # [@jsep-plugin/template-v1.0.3](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/template-v1.0.2...@jsep-plugin/template-v1.0.3) (2023-07-19) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * support member exp before tagged template (String.raw) ([92e6de0](https://github.com/EricSmekens/jsep/commit/92e6de0ae588da408b214cd923f6e15d6568f9ff)) 21 | 22 | # [@jsep-plugin/template-v1.0.2](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/template-v1.0.1...@jsep-plugin/template-v1.0.2) (2022-09-18) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * export default IPlugin from all plugins ([cd38da5](https://github.com/EricSmekens/jsep/commit/cd38da58e0a32d8cc05fe1e4ac3791459ee90986)), closes [#231](https://github.com/EricSmekens/jsep/issues/231) 28 | 29 | ## 1.0.1 - 2021-08-26 30 | republish with types included 31 | 32 | ## 1.0.0 - 2021-08-24 33 | Initial Release 34 | -------------------------------------------------------------------------------- /packages/template/README.md: -------------------------------------------------------------------------------- 1 | [npm]: https://img.shields.io/npm/v/@jsep-plugin/template 2 | [npm-url]: https://www.npmjs.com/package/@jsep-plugin/template 3 | [size]: https://packagephobia.now.sh/badge?p=@jsep-plugin/template 4 | [size-url]: https://packagephobia.now.sh/result?p=@jsep-plugin/template 5 | 6 | [![npm][npm]][npm-url] 7 | [![size][size]][size-url] 8 | 9 | # @jsep-plugin/template 10 | 11 | A JSEP plugin for adding template literal expression support. Allows expressions of the form: 12 | 13 | ```javascript 14 | jsep('`hi ${name}`'); 15 | jsep('msg`hi ${name}`'); 16 | ``` 17 | 18 | ## Install 19 | 20 | ```console 21 | npm install @jsep-plugin/template 22 | # or 23 | yarn add @jsep-plugin/template 24 | ``` 25 | 26 | ## Usage 27 | ```javascript 28 | import jsep from 'jsep'; 29 | import jsepTemplateLiteral from '@jsep-plugin/template'; 30 | jsep.plugins.register(jsepTemplateLiteral); 31 | ``` 32 | 33 | ## Meta 34 | 35 | [LICENSE (MIT)](/LICENSE) 36 | -------------------------------------------------------------------------------- /packages/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsep-plugin/template", 3 | "version": "1.0.5", 4 | "description": "Adds template literal expression support", 5 | "author": "Shelly (https://github.com/6utt3rfly)", 6 | "maintainers": [ 7 | "Eric Smekens (https://github.com/EricSmekens)", 8 | "Lea Verou (https://github.com/LeaVerou)", 9 | "Shelly (https://github.com/6utt3rfly)" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/template#readme", 15 | "license": "MIT", 16 | "repository": { 17 | "url": "EricSmekens/jsep", 18 | "directory": "packages/template" 19 | }, 20 | "type": "module", 21 | "main": "./dist/cjs/index.cjs.js", 22 | "module": "./dist/index.js", 23 | "types": "types/tsd.d.ts", 24 | "peerDependencies": { 25 | "jsep": "^0.4.0||^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "rollup": "^2.44.0", 29 | "rollup-plugin-delete": "^2.0.0" 30 | }, 31 | "engines": { 32 | "node": ">= 10.16.0" 33 | }, 34 | "scripts": { 35 | "build": "rollup -c ../../plugin.rollup.config.js && cp ../../package-cjs.json dist/cjs/package.json && cp ../../LICENSE ./", 36 | "test": "cd ../../ && http-server -p 49649 --silent & node-qunit-puppeteer http://localhost:49649/packages/template/test/unit_tests.html", 37 | "lint": "eslint src/**/*.js test/**/*.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/template/src/index.js: -------------------------------------------------------------------------------- 1 | const BTICK_CODE = 96; // ` 2 | const CCURLY_CODE = 125; // } 3 | const TAGGED_TEMPLATE_EXPRESSION = 'TaggedTemplateExpression'; 4 | const TEMPLATE_LITERAL = 'TemplateLiteral'; 5 | const TEMPLATE_ELEMENT = 'TemplateElement'; 6 | 7 | export default { 8 | name: 'jsepTemplateLiteral', 9 | 10 | init(jsep) { 11 | function gobbleTemplateLiteral(env, gobbleMember = true) { 12 | if (this.code === BTICK_CODE) { 13 | const node = { 14 | type: TEMPLATE_LITERAL, 15 | quasis: [], 16 | expressions: [], 17 | }; 18 | let cooked = ''; 19 | let raw = ''; 20 | let closed = false; 21 | const length = this.expr.length; 22 | const pushQuasi = () => node.quasis.push({ 23 | type: TEMPLATE_ELEMENT, 24 | value: { 25 | raw, 26 | cooked, 27 | }, 28 | tail: closed, 29 | }); 30 | 31 | while (this.index < length) { 32 | let ch = this.expr.charAt(++this.index); 33 | 34 | if (ch === '`') { 35 | this.index += 1; 36 | closed = true; 37 | pushQuasi(); 38 | 39 | env.node = node; 40 | 41 | if (gobbleMember) { 42 | // allow . [] and () after template: `foo`.length 43 | env.node = this.gobbleTokenProperty(env.node); 44 | } 45 | 46 | return env.node; 47 | } 48 | else if (ch === '$' && this.expr.charAt(this.index + 1) === '{') { 49 | this.index += 2; 50 | pushQuasi(); 51 | raw = ''; 52 | cooked = ''; 53 | node.expressions.push(...this.gobbleExpressions(CCURLY_CODE)); 54 | if (this.code !== CCURLY_CODE) { 55 | this.throwError('unclosed ${'); 56 | } 57 | } 58 | else if (ch === '\\') { 59 | // Check for all of the common escape codes 60 | raw += ch; 61 | ch = this.expr.charAt(++this.index); 62 | raw += ch; 63 | 64 | switch (ch) { 65 | case 'n': cooked += '\n'; break; 66 | case 'r': cooked += '\r'; break; 67 | case 't': cooked += '\t'; break; 68 | case 'b': cooked += '\b'; break; 69 | case 'f': cooked += '\f'; break; 70 | case 'v': cooked += '\x0B'; break; 71 | default : cooked += ch; 72 | } 73 | } 74 | else { 75 | cooked += ch; 76 | raw += ch; 77 | } 78 | } 79 | this.throwError('Unclosed `'); 80 | } 81 | } 82 | 83 | jsep.hooks.add('gobble-token', gobbleTemplateLiteral); 84 | 85 | jsep.hooks.add('after-token', function gobbleTaggedTemplateIdentifier(env) { 86 | if ((env.node.type === jsep.IDENTIFIER || env.node.type === jsep.MEMBER_EXP) && this.code === BTICK_CODE) { 87 | env.node = { 88 | type: TAGGED_TEMPLATE_EXPRESSION, 89 | tag: env.node, 90 | quasi: gobbleTemplateLiteral.bind(this)(env, false), 91 | }; 92 | 93 | // allow . [] and () after tagged template: bar`foo`.length 94 | env.node = this.gobbleTokenProperty(env.node); 95 | 96 | return env.node; 97 | } 98 | }); 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /packages/template/test/index.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../../../src/index.js'; 2 | import templateLiteral from '../src/index.js'; 3 | import {testParser, resetJsepDefaults} from '../../../test/test_utils.js'; 4 | 5 | const { test } = QUnit; 6 | 7 | (function () { 8 | QUnit.module('Plugin:Template Literal', (qunit) => { 9 | qunit.before(() => jsep.plugins.register(templateLiteral)); 10 | qunit.after(resetJsepDefaults); 11 | 12 | test('should parse basic template literal expression', (assert) => { 13 | testParser('`hi ${name}`', { 14 | type: 'TemplateLiteral', 15 | quasis: [ 16 | { 17 | type: 'TemplateElement', 18 | value: { 19 | raw: 'hi ', 20 | cooked: 'hi ', 21 | }, 22 | tail: false, 23 | }, 24 | { 25 | type: 'TemplateElement', 26 | value: { 27 | raw: '', 28 | cooked: '', 29 | }, 30 | tail: true, 31 | }, 32 | ], 33 | expressions: [ 34 | { 35 | type: 'Identifier', 36 | name: 'name', 37 | }, 38 | ], 39 | }, assert); 40 | }); 41 | 42 | test('should parse template literal member access', (assert) => { 43 | testParser('`foo`.bar', { 44 | type: 'MemberExpression', 45 | computed: false, 46 | object: { 47 | type: 'TemplateLiteral', 48 | quasis: [ 49 | { 50 | type: 'TemplateElement', 51 | value: { 52 | raw: 'foo', 53 | cooked: 'foo', 54 | }, 55 | tail: true, 56 | } 57 | ], 58 | expressions: [] 59 | }, 60 | property: { 61 | type: 'Identifier', 62 | name: 'bar' 63 | } 64 | }, assert); 65 | }); 66 | 67 | test('should parse tagged template literal member access', (assert) => { 68 | testParser('String.raw`foo`.bar', { 69 | type: "MemberExpression", 70 | computed: false, 71 | object: { 72 | type: "TaggedTemplateExpression", 73 | tag: { 74 | type: "MemberExpression", 75 | computed: false, 76 | object: { 77 | type: "Identifier", 78 | name: "String" 79 | }, 80 | property: { 81 | type: "Identifier", 82 | name: "raw" 83 | } 84 | }, 85 | quasi: { 86 | type: "TemplateLiteral", 87 | quasis: [ 88 | { 89 | type: "TemplateElement", 90 | value: { 91 | raw: "foo", 92 | cooked: "foo" 93 | }, 94 | tail: true 95 | } 96 | ], 97 | expressions: [] 98 | } 99 | }, 100 | property: { 101 | type: "Identifier", 102 | name: "bar" 103 | } 104 | }, assert); 105 | }); 106 | 107 | test('should parse tagged, nested template literal expression', (assert) => { 108 | testParser('abc`token ${`nested ${`deeply` + "str"} blah`}`', { 109 | type: 'TaggedTemplateExpression', 110 | tag: { 111 | type: 'Identifier', 112 | name: 'abc', 113 | }, 114 | quasi: { 115 | type: 'TemplateLiteral', 116 | quasis: [ 117 | { 118 | type: 'TemplateElement', 119 | value: { 120 | raw: 'token ', 121 | cooked: 'token ', 122 | }, 123 | tail: false, 124 | }, 125 | { 126 | type: 'TemplateElement', 127 | value: { 128 | raw: '', 129 | cooked: '', 130 | }, 131 | tail: true, 132 | }, 133 | ], 134 | expressions: [ 135 | { 136 | type: 'TemplateLiteral', 137 | quasis: [ 138 | { 139 | type: 'TemplateElement', 140 | value: { 141 | raw: 'nested ', 142 | cooked: 'nested ', 143 | }, 144 | tail: false, 145 | }, 146 | { 147 | type: 'TemplateElement', 148 | value: { 149 | raw: ' blah', 150 | cooked: ' blah', 151 | }, 152 | tail: true, 153 | }, 154 | ], 155 | expressions: [ 156 | { 157 | type: 'BinaryExpression', 158 | operator: '+', 159 | left: { 160 | type: 'TemplateLiteral', 161 | quasis: [ 162 | { 163 | type: 'TemplateElement', 164 | value: { 165 | raw: 'deeply', 166 | cooked: 'deeply', 167 | }, 168 | tail: true, 169 | }, 170 | ], 171 | expressions: [], 172 | }, 173 | right: { 174 | type: 'Literal', 175 | value: 'str', 176 | raw: '"str"', 177 | }, 178 | }, 179 | ], 180 | }, 181 | ], 182 | }, 183 | }, assert); 184 | }); 185 | 186 | test('should parse String.raw tagged, nested template literal expression', (assert) => { 187 | testParser('String.raw`token ${`nested ${`deeply` + "str"} blah`}`', { 188 | type: 'TaggedTemplateExpression', 189 | tag: { 190 | type: "MemberExpression", 191 | computed: false, 192 | object: { 193 | type: "Identifier", 194 | name: "String" 195 | }, 196 | property: { 197 | type: "Identifier", 198 | name:"raw" 199 | } 200 | }, 201 | quasi: { 202 | type: 'TemplateLiteral', 203 | quasis: [ 204 | { 205 | type: 'TemplateElement', 206 | value: { 207 | raw: 'token ', 208 | cooked: 'token ', 209 | }, 210 | tail: false, 211 | }, 212 | { 213 | type: 'TemplateElement', 214 | value: { 215 | raw: '', 216 | cooked: '', 217 | }, 218 | tail: true, 219 | }, 220 | ], 221 | expressions: [ 222 | { 223 | type: 'TemplateLiteral', 224 | quasis: [ 225 | { 226 | type: 'TemplateElement', 227 | value: { 228 | raw: 'nested ', 229 | cooked: 'nested ', 230 | }, 231 | tail: false, 232 | }, 233 | { 234 | type: 'TemplateElement', 235 | value: { 236 | raw: ' blah', 237 | cooked: ' blah', 238 | }, 239 | tail: true, 240 | }, 241 | ], 242 | expressions: [ 243 | { 244 | type: 'BinaryExpression', 245 | operator: '+', 246 | left: { 247 | type: 'TemplateLiteral', 248 | quasis: [ 249 | { 250 | type: 'TemplateElement', 251 | value: { 252 | raw: 'deeply', 253 | cooked: 'deeply', 254 | }, 255 | tail: true, 256 | }, 257 | ], 258 | expressions: [], 259 | }, 260 | right: { 261 | type: 'Literal', 262 | value: 'str', 263 | raw: '"str"', 264 | }, 265 | }, 266 | ], 267 | }, 268 | ], 269 | }, 270 | }, assert); 271 | }); 272 | 273 | test('should parse multiple vars within template literal expression', (assert) => { 274 | testParser('`hi ${last}, ${first} ${middle}!`', { 275 | type: 'TemplateLiteral', 276 | quasis: [ 277 | { 278 | type: 'TemplateElement', 279 | value: { 280 | raw: 'hi ', 281 | cooked: 'hi ', 282 | }, 283 | tail: false, 284 | }, 285 | { 286 | type: 'TemplateElement', 287 | value: { 288 | raw: ', ', 289 | cooked: ', ', 290 | }, 291 | tail: false, 292 | }, 293 | { 294 | type: 'TemplateElement', 295 | value: { 296 | raw: ' ', 297 | cooked: ' ', 298 | }, 299 | tail: false, 300 | }, 301 | { 302 | type: 'TemplateElement', 303 | value: { 304 | raw: '!', 305 | cooked: '!', 306 | }, 307 | tail: true, 308 | }, 309 | ], 310 | expressions: [ 311 | { 312 | type: 'Identifier', 313 | name: 'last', 314 | }, 315 | { 316 | type: 'Identifier', 317 | name: 'first', 318 | }, 319 | { 320 | type: 'Identifier', 321 | name: 'middle', 322 | }, 323 | ], 324 | }, assert); 325 | }); 326 | 327 | test('should handle cooked (escaped) chars', (assert) => { 328 | testParser('`hi\\n\t`', { 329 | type: 'TemplateLiteral', 330 | quasis: [ 331 | { 332 | type: 'TemplateElement', 333 | value: { 334 | raw: 'hi\\n\t', 335 | cooked: 'hi\n\t', 336 | }, 337 | tail: true, 338 | }, 339 | ], 340 | expressions: [], 341 | }, assert); 342 | }); 343 | 344 | [ 345 | '`a\nbc${ b ? 1 : 2 }`', // mixed template/ternary 346 | ].forEach(expr => test(`should not throw an error on expression ${expr}`, (assert) => { 347 | testParser(expr, {}, assert); 348 | })); 349 | 350 | [ 351 | '`abc ${ `', 352 | 'abc`123', 353 | 'abc`${a`', 354 | ].forEach(expr => test(`should give an error for invalid expression ${expr}`, (assert) => { 355 | assert.throws(() => jsep(expr)); 356 | })); 357 | }); 358 | }()); 359 | -------------------------------------------------------------------------------- /packages/template/test/unit_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

                    Plugin-template Unit Tests

                    16 |

                    17 |
                    18 |

                    19 |
                      20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/template/types/tsd.d.ts: -------------------------------------------------------------------------------- 1 | import * as jsep from 'jsep'; 2 | import { Expression, IPlugin } from 'jsep'; 3 | export const name: string; 4 | export function init(this: typeof jsep): void; 5 | 6 | export interface TaggedTemplateExpression extends Expression { 7 | type: 'TaggedTemplateExpression'; 8 | readonly tag: Expression; 9 | readonly quasi: TemplateLiteral; 10 | } 11 | 12 | export interface TemplateElement extends Expression { 13 | type: 'TemplateElement'; 14 | value: { cooked: string; raw: string }; 15 | tail: boolean; 16 | } 17 | 18 | export interface TemplateLiteral extends Expression { 19 | type: 'TemplateLiteral'; 20 | quasis: TemplateElement[]; 21 | expressions: Expression[]; 22 | } 23 | 24 | declare const _export: IPlugin; 25 | export default _export; 26 | -------------------------------------------------------------------------------- /packages/ternary/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !package.json 4 | !LICENSE 5 | !README.md 6 | !dist/**/* 7 | !types/**/* 8 | -------------------------------------------------------------------------------- /packages/ternary/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [@jsep-plugin/ternary-v1.1.4](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/ternary-v1.1.3...@jsep-plugin/ternary-v1.1.4) (2024-11-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * add license file to build output ([f6678fb](https://github.com/EricSmekens/jsep/commit/f6678fb0869188e9c9575fed231864f75e99af74)), closes [#267](https://github.com/EricSmekens/jsep/issues/267) 7 | 8 | # [@jsep-plugin/ternary-v1.1.3](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/ternary-v1.1.2...@jsep-plugin/ternary-v1.1.3) (2022-09-18) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * export default IPlugin from all plugins ([cd38da5](https://github.com/EricSmekens/jsep/commit/cd38da58e0a32d8cc05fe1e4ac3791459ee90986)), closes [#231](https://github.com/EricSmekens/jsep/issues/231) 14 | 15 | # [@jsep-plugin/ternary-v1.1.2](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/ternary-v1.1.1...@jsep-plugin/ternary-v1.1.2) (2022-03-22) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * embed correct version into builds ([9e90f3d](https://github.com/EricSmekens/jsep/commit/9e90f3d7045002c67269d28a8cdddeb0abaef7e1)), closes [#216](https://github.com/EricSmekens/jsep/issues/216) 21 | 22 | # [@jsep-plugin/ternary-v1.1.1](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/ternary-v1.1.0...@jsep-plugin/ternary-v1.1.1) (2022-03-22) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * test change to ternary ([33f0494](https://github.com/EricSmekens/jsep/commit/33f0494683c037d2ec88a00e2babcc4e218792d8)) 28 | * test change to ternary ([fca0aa5](https://github.com/EricSmekens/jsep/commit/fca0aa573db774aa63596d3a7868a0c4884a73ea)) 29 | 30 | # [@jsep-plugin/ternary-v1.1.0](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/ternary-v1.0.2...@jsep-plugin/ternary-v1.1.0) (2022-02-28) 31 | 32 | 33 | ### Features 34 | 35 | * assignment right-to-left and precedence with ternary ([e5652eb](https://github.com/EricSmekens/jsep/commit/e5652ebfff9c7d9b730bb0f21a1f4f22b1e3787d)), closes [#189](https://github.com/EricSmekens/jsep/issues/189) 36 | * simplify ternary since to stop handling ':' binary operator ([4196623](https://github.com/EricSmekens/jsep/commit/419662398101bfc07c646375b966a7427713fb70)) 37 | 38 | # [@jsep-plugin/ternary-v1.1.0-beta.1](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/ternary-v1.0.2...@jsep-plugin/ternary-v1.1.0-beta.1) (2021-12-13) 39 | 40 | 41 | ### Features 42 | 43 | * assignment right-to-left and precedence with ternary ([e5652eb](https://github.com/EricSmekens/jsep/commit/e5652ebfff9c7d9b730bb0f21a1f4f22b1e3787d)), closes [#189](https://github.com/EricSmekens/jsep/issues/189) 44 | * simplify ternary since to stop handling ':' binary operator ([4196623](https://github.com/EricSmekens/jsep/commit/419662398101bfc07c646375b966a7427713fb70)) 45 | 46 | # [@jsep-plugin/ternary-v1.0.2](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/ternary-v1.0.1...@jsep-plugin/ternary-v1.0.2) (2021-10-17) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * ternary precedence against assignment precedence ([1e91c02](https://github.com/EricSmekens/jsep/commit/1e91c02a4b6182e1b3919ae133624763917a827f)), closes [#188](https://github.com/EricSmekens/jsep/issues/188) [#189](https://github.com/EricSmekens/jsep/issues/189) 52 | 53 | # [@jsep-plugin/ternary-v1.0.1](https://github.com/EricSmekens/jsep/compare/@jsep-plugin/ternary-v1.0.0...@jsep-plugin/ternary-v1.0.1) (2021-10-03) 54 | 55 | 56 | ### Bug Fixes 57 | 58 | * support nested ternaries with the object plugin loaded ([cf37c29](https://github.com/EricSmekens/jsep/commit/cf37c29940c99355e591849f1f7c0cef6e7ca1af)), closes [#183](https://github.com/EricSmekens/jsep/issues/183) 59 | 60 | # @jsep-plugin/ternary-v1.0.0 (2021-10-03) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * support nested ternaries with the object plugin loaded ([cf37c29](https://github.com/EricSmekens/jsep/commit/cf37c29940c99355e591849f1f7c0cef6e7ca1af)), closes [#183](https://github.com/EricSmekens/jsep/issues/183) 66 | 67 | ## 1.0.1 - 2021-08-26 68 | republish with types included 69 | 70 | ## 1.0.0 - 2021-08-24 71 | Initial Release. Code pulled from core of jsep and adapted into a plugin. 72 | -------------------------------------------------------------------------------- /packages/ternary/README.md: -------------------------------------------------------------------------------- 1 | [npm]: https://img.shields.io/npm/v/@jsep-plugin/ternary 2 | [npm-url]: https://www.npmjs.com/package/@jsep-plugin/ternary 3 | [size]: https://packagephobia.now.sh/badge?p=@jsep-plugin/ternary 4 | [size-url]: https://packagephobia.now.sh/result?p=@jsep-plugin/ternary 5 | 6 | [![npm][npm]][npm-url] 7 | [![size][size]][size-url] 8 | 9 | # @jsep-plugin/ternary 10 | 11 | A JSEP plugin for adding ternary expression support. Allows expressions of the form: 12 | 13 | ```javascript 14 | jsep('a ? 1 : 2'); 15 | ``` 16 | 17 | ## Install 18 | 19 | ```console 20 | npm install @jsep-plugin/ternary 21 | # or 22 | yarn add @jsep-plugin/ternary 23 | ``` 24 | 25 | ## Usage 26 | ```javascript 27 | import jsep from 'jsep'; 28 | import jsepTernary from '@jsep-plugin/ternary'; 29 | jsep.plugins.register(jsepTernary); 30 | ``` 31 | 32 | ## Meta 33 | 34 | [LICENSE (MIT)](/LICENSE) 35 | -------------------------------------------------------------------------------- /packages/ternary/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsep-plugin/ternary", 3 | "version": "1.1.4", 4 | "description": "Adds ternary expression support", 5 | "author": "Shelly (https://github.com/6utt3rfly)", 6 | "maintainers": [ 7 | "Eric Smekens (https://github.com/EricSmekens)", 8 | "Lea Verou (https://github.com/LeaVerou)", 9 | "Shelly (https://github.com/6utt3rfly)" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/ternary#readme", 15 | "license": "MIT", 16 | "repository": { 17 | "url": "EricSmekens/jsep", 18 | "directory": "packages/ternary" 19 | }, 20 | "type": "module", 21 | "main": "./dist/cjs/index.cjs.js", 22 | "module": "./dist/index.js", 23 | "types": "types/tsd.d.ts", 24 | "peerDependencies": { 25 | "jsep": "^0.4.0||^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "rollup": "^2.44.0", 29 | "rollup-plugin-delete": "^2.0.0" 30 | }, 31 | "engines": { 32 | "node": ">= 10.16.0" 33 | }, 34 | "scripts": { 35 | "build": "rollup -c ../../plugin.rollup.config.js && cp ../../package-cjs.json dist/cjs/package.json && cp ../../LICENSE ./", 36 | "test": "cd ../../ && http-server -p 49649 --silent & node-qunit-puppeteer http://localhost:49649/packages/ternary/test/unit_tests.html", 37 | "lint": "eslint src/**/*.js test/**/*.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/ternary/src/index.js: -------------------------------------------------------------------------------- 1 | const CONDITIONAL_EXP = 'ConditionalExpression'; 2 | 3 | export default { 4 | name: 'ternary', 5 | 6 | init(jsep) { 7 | // Ternary expression: test ? consequent : alternate 8 | jsep.hooks.add('after-expression', function gobbleTernary(env) { 9 | if (env.node && this.code === jsep.QUMARK_CODE) { 10 | this.index++; 11 | const test = env.node; 12 | const consequent = this.gobbleExpression(); 13 | 14 | if (!consequent) { 15 | this.throwError('Expected expression'); 16 | } 17 | 18 | this.gobbleSpaces(); 19 | 20 | if (this.code === jsep.COLON_CODE) { 21 | this.index++; 22 | const alternate = this.gobbleExpression(); 23 | 24 | if (!alternate) { 25 | this.throwError('Expected expression'); 26 | } 27 | env.node = { 28 | type: CONDITIONAL_EXP, 29 | test, 30 | consequent, 31 | alternate, 32 | }; 33 | 34 | // check for operators of higher priority than ternary (i.e. assignment) 35 | // jsep sets || at 1, and assignment at 0.9, and conditional should be between them 36 | if (test.operator && jsep.binary_ops[test.operator] <= 0.9) { 37 | let newTest = test; 38 | while (newTest.right.operator && jsep.binary_ops[newTest.right.operator] <= 0.9) { 39 | newTest = newTest.right; 40 | } 41 | env.node.test = newTest.right; 42 | newTest.right = env.node; 43 | env.node = test; 44 | } 45 | } 46 | else { 47 | this.throwError('Expected :'); 48 | } 49 | } 50 | }); 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /packages/ternary/test/index.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../../../src/jsep.js'; 2 | import ternary from '../src/index.js'; 3 | import {testParser, esprimaComparisonTest} from '../../../test/test_utils.js'; 4 | 5 | jsep.plugins.register(ternary); 6 | const { test } = QUnit; 7 | 8 | (function () { 9 | QUnit.module('Ternary', () => { 10 | test('should match Esprima', function (assert) { 11 | ([ 12 | 'a((1 + 2), (e > 0 ? f : g))', 13 | ]).forEach(function (test) { 14 | esprimaComparisonTest(test, assert); 15 | }); 16 | }); 17 | 18 | test('should parse binary op with ConditionalExpression', function (assert) { 19 | testParser('a||b ? c : d', { type: 'ConditionalExpression' }, assert); 20 | }); 21 | 22 | test('should parse minimal ternary', (assert) => { 23 | testParser('a ? b : c', { 24 | type: 'ConditionalExpression', 25 | test: { 26 | type: 'Identifier', 27 | name: 'a' 28 | }, 29 | consequent: { 30 | type: 'Identifier', 31 | name: 'b' 32 | }, 33 | alternate: { 34 | type: 'Identifier', 35 | name: 'c' 36 | } 37 | }, assert); 38 | }); 39 | 40 | test('should parse secondary ternary on consequent side', (assert) => { 41 | testParser('a ? b ? c : 1 : 2', { 42 | type: 'ConditionalExpression', 43 | test: { 44 | type: 'Identifier', 45 | name: 'a' 46 | }, 47 | alternate: { 48 | type: 'Literal', 49 | value: 2, 50 | raw: '2' 51 | }, 52 | consequent: { 53 | type: 'ConditionalExpression', 54 | test: { 55 | type: 'Identifier', 56 | name: 'b' 57 | }, 58 | consequent: { 59 | type: 'Identifier', 60 | name: 'c' 61 | }, 62 | alternate: { 63 | type: 'Literal', 64 | value: 1, 65 | raw: '1' 66 | } 67 | } 68 | }, assert); 69 | }); 70 | 71 | test('should parse secondary ternary on alternate side', (assert) => { 72 | testParser('a ? b : c ? 1 : 2', { 73 | type: 'ConditionalExpression', 74 | test: { 75 | type: 'Identifier', 76 | name: 'a' 77 | }, 78 | consequent: { 79 | type: 'Identifier', 80 | name: 'b' 81 | }, 82 | alternate: { 83 | type: 'ConditionalExpression', 84 | test: { 85 | type: 'Identifier', 86 | name: 'c' 87 | }, 88 | consequent: { 89 | type: 'Literal', 90 | value: 1, 91 | raw: '1' 92 | }, 93 | alternate: { 94 | type: 'Literal', 95 | value: 2, 96 | raw: '2' 97 | } 98 | } 99 | }, assert); 100 | }); 101 | 102 | test('should parse 3 level ternaries with mixed consequent/alternate', (assert) => { 103 | testParser('a ? b ? 1 : c ? 5 : 6 : 7', { 104 | type: 'ConditionalExpression', 105 | test: { 106 | type: 'Identifier', 107 | name: 'a' 108 | }, 109 | alternate: { 110 | type: 'Literal', 111 | value: 7, 112 | raw: '7' 113 | }, 114 | consequent: { 115 | test: { 116 | type: 'Identifier', 117 | name: 'b' 118 | }, 119 | alternate: { 120 | type: 'ConditionalExpression', 121 | test: { 122 | type: 'Identifier', 123 | name: 'c' 124 | }, 125 | consequent: { 126 | type: 'Literal', 127 | value: 5, 128 | raw: '5' 129 | }, 130 | alternate: { 131 | type: 'Literal', 132 | value: 6, 133 | raw: '6' 134 | } 135 | }, 136 | consequent: { 137 | type: 'Literal', 138 | value: 1, 139 | raw: '1' 140 | }, 141 | type: 'ConditionalExpression' 142 | } 143 | }, assert); 144 | }); 145 | 146 | test('should parse 5-level ternaries mixed consequent/alternate', function (assert) { 147 | testParser('a ? b ? 1 : c ? d ? e ? 3 : 4 : 5 : 6 : 7', { 148 | type: 'ConditionalExpression', 149 | test: { 150 | type: 'Identifier', 151 | name: 'a' 152 | }, 153 | consequent: { 154 | type: 'ConditionalExpression', 155 | test: { 156 | type: 'Identifier', 157 | name: 'b' 158 | }, 159 | consequent: { 160 | type: 'Literal', 161 | value: 1, 162 | raw: '1' 163 | }, 164 | alternate: { 165 | type: 'ConditionalExpression', 166 | test: { 167 | type: 'Identifier', 168 | name: 'c' 169 | }, 170 | consequent: { 171 | type: 'ConditionalExpression', 172 | test: { 173 | type: 'Identifier', 174 | name: 'd' 175 | }, 176 | consequent: { 177 | type: 'ConditionalExpression', 178 | test: { 179 | type: 'Identifier', 180 | name: 'e' 181 | }, 182 | consequent: { 183 | type: 'Literal', 184 | value: 3, 185 | raw: '3' 186 | }, 187 | alternate: { 188 | type: 'Literal', 189 | value: 4, 190 | raw: '4' 191 | } 192 | }, 193 | alternate: { 194 | type: 'Literal', 195 | value: 5, 196 | raw: '5' 197 | } 198 | }, 199 | alternate: { 200 | type: 'Literal', 201 | value: 6, 202 | raw: '6' 203 | } 204 | } 205 | }, 206 | alternate: { 207 | type: 'Literal', 208 | value: 7, 209 | raw: '7' 210 | } 211 | }, assert); 212 | }); 213 | 214 | [ 215 | 'a ? b : ', // missing value 216 | 'a ? b', // missing : 217 | 'a : b ?', // backwards 218 | ].forEach((expr) => { 219 | test(`should give an error for invalid expression ${expr}`, (assert) => { 220 | assert.throws(() => jsep(expr)); 221 | }); 222 | }); 223 | }); 224 | }()); 225 | -------------------------------------------------------------------------------- /packages/ternary/test/unit_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

                      Plugin-ternary Unit Tests

                      16 |

                      17 |
                      18 |

                      19 |
                        20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/ternary/types/tsd.d.ts: -------------------------------------------------------------------------------- 1 | import * as jsep from 'jsep'; 2 | import { IPlugin } from 'jsep'; 3 | export const name: string; 4 | export function init(this: typeof jsep): void; 5 | 6 | declare const _export: IPlugin; 7 | export default _export; 8 | -------------------------------------------------------------------------------- /plugin.rollup.config.js: -------------------------------------------------------------------------------- 1 | import del from 'rollup-plugin-delete'; 2 | import { bundle } from './rollup.config.js'; 3 | 4 | const name = 'index'; 5 | export default [ 6 | { 7 | input: "src/index.js", 8 | output: [ 9 | bundle("esm", name), 10 | bundle("esm.min", name), 11 | bundle("iife", name), 12 | bundle("iife.min", name), 13 | bundle("cjs", name), 14 | bundle("cjs.min", name), 15 | ], 16 | plugins: [ 17 | del({ targets: 'dist/*' }), 18 | ], 19 | }, 20 | ]; 21 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Release jsep and all packages (dependent on what changes were made where) 4 | # To test, run command ./release.sh --debug --no-cli --dry-run 5 | # (or npm run release -- --debug --no-cli --dry-run) 6 | 7 | echo "Semantic-Release JSEP" 8 | pnpx semantic-release "$@" 9 | 10 | packages=($(ls -d packages/*)) 11 | for package in "${packages[@]}"; do 12 | printf "\n\nSemantic-Release $package\n" 13 | (cd $package && pwd && pnpx semantic-release -e semantic-release-monorepo "$@") 14 | done 15 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { terser } from "rollup-plugin-terser"; 2 | import replace from '@rollup/plugin-replace'; 3 | import del from 'rollup-plugin-delete'; 4 | 5 | const { version: pkgVersion } = require('./package.json'); 6 | 7 | const version = process.env.NEXT_VERSION || pkgVersion; 8 | 9 | export const terserConfig = { 10 | compress: true, 11 | mangle: true 12 | }; 13 | 14 | export function bundle(type, name = 'jsep') { 15 | let minify = false; 16 | let format = type.replace(".min", () => { 17 | minify = true; 18 | return ""; 19 | }); 20 | 21 | let suffix = `.${type}`.replace(".esm", ""); 22 | let folder = format === 'esm' ? '' : `${format}/`; 23 | 24 | return { 25 | file: `dist/${folder}${name}${suffix}.js`, 26 | name, 27 | format, 28 | sourcemap: type !== "esm", 29 | exports: format === 'esm' ? 'named' : 'default', 30 | plugins: [ 31 | minify? terser(terserConfig) : undefined, 32 | ] 33 | }; 34 | } 35 | 36 | const versionPlugin = replace({ 37 | "<%= version %>": version, 38 | 39 | // Options: 40 | preventAssignment: false, 41 | delimiters: ['', ''], 42 | }); 43 | 44 | export default [ 45 | { 46 | input: "src/index.js", 47 | output: [ 48 | bundle("esm"), 49 | bundle("esm.min"), 50 | ], 51 | plugins: [ 52 | del({ targets: 'dist/*' }), 53 | versionPlugin, 54 | ], 55 | }, 56 | { 57 | input: "src/index.js", 58 | output: [ 59 | bundle("iife"), 60 | bundle("iife.min"), 61 | bundle("cjs"), 62 | bundle("cjs.min"), 63 | ], 64 | plugins: [ 65 | versionPlugin, 66 | replace({ 67 | 'export class Jsep': 'class Jsep', // single default export 68 | preventAssignment: false, 69 | }), 70 | ], 71 | }, 72 | ]; 73 | -------------------------------------------------------------------------------- /src/hooks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @implements {IHooks} 3 | */ 4 | export default class Hooks { 5 | /** 6 | * @callback HookCallback 7 | * @this {*|Jsep} this 8 | * @param {Jsep} env 9 | * @returns: void 10 | */ 11 | /** 12 | * Adds the given callback to the list of callbacks for the given hook. 13 | * 14 | * The callback will be invoked when the hook it is registered for is run. 15 | * 16 | * One callback function can be registered to multiple hooks and the same hook multiple times. 17 | * 18 | * @param {string|object} name The name of the hook, or an object of callbacks keyed by name 19 | * @param {HookCallback|boolean} callback The callback function which is given environment variables. 20 | * @param {?boolean} [first=false] Will add the hook to the top of the list (defaults to the bottom) 21 | * @public 22 | */ 23 | add(name, callback, first) { 24 | if (typeof arguments[0] != 'string') { 25 | // Multiple hook callbacks, keyed by name 26 | for (let name in arguments[0]) { 27 | this.add(name, arguments[0][name], arguments[1]); 28 | } 29 | } 30 | else { 31 | (Array.isArray(name) ? name : [name]).forEach(function (name) { 32 | this[name] = this[name] || []; 33 | 34 | if (callback) { 35 | this[name][first ? 'unshift' : 'push'](callback); 36 | } 37 | }, this); 38 | } 39 | } 40 | 41 | /** 42 | * Runs a hook invoking all registered callbacks with the given environment variables. 43 | * 44 | * Callbacks will be invoked synchronously and in the order in which they were registered. 45 | * 46 | * @param {string} name The name of the hook. 47 | * @param {Object} env The environment variables of the hook passed to all callbacks registered. 48 | * @public 49 | */ 50 | run(name, env) { 51 | this[name] = this[name] || []; 52 | this[name].forEach(function (callback) { 53 | callback.call(env && env.context ? env.context : env, env); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Add default plugins: 2 | import jsep from './jsep.js'; 3 | import ternary from '../packages/ternary/src/index.js'; 4 | 5 | jsep.plugins.register(ternary); 6 | 7 | export * from './jsep.js'; 8 | export default jsep; 9 | -------------------------------------------------------------------------------- /src/plugins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @implements {IPlugins} 3 | */ 4 | export default class Plugins { 5 | constructor(jsep) { 6 | this.jsep = jsep; 7 | this.registered = {}; 8 | } 9 | 10 | /** 11 | * @callback PluginSetup 12 | * @this {Jsep} jsep 13 | * @returns: void 14 | */ 15 | /** 16 | * Adds the given plugin(s) to the registry 17 | * 18 | * @param {object} plugins 19 | * @param {string} plugins.name The name of the plugin 20 | * @param {PluginSetup} plugins.init The init function 21 | * @public 22 | */ 23 | register(...plugins) { 24 | plugins.forEach((plugin) => { 25 | if (typeof plugin !== 'object' || !plugin.name || !plugin.init) { 26 | throw new Error('Invalid JSEP plugin format'); 27 | } 28 | if (this.registered[plugin.name]) { 29 | // already registered. Ignore. 30 | return; 31 | } 32 | plugin.init(this.jsep); 33 | this.registered[plugin.name] = plugin; 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/hooks.test.js: -------------------------------------------------------------------------------- 1 | import Hooks from '../src/hooks.js'; 2 | 3 | const { test } = QUnit; 4 | 5 | (function () { 6 | QUnit.module('Hooks', (qunit) => { 7 | let callbacks; 8 | const callbackGenerator = (name) => { 9 | callbacks[name] = []; 10 | return (env) => { 11 | callbacks[name].push(env); 12 | }; 13 | }; 14 | qunit.beforeEach(() => { 15 | callbacks = {}; 16 | }); 17 | 18 | QUnit.module('Add', (qunit) => { 19 | let hooks; 20 | qunit.beforeEach(() => { 21 | hooks = new Hooks(); 22 | }); 23 | 24 | test('should initialize without any hooks', (assert) => { 25 | assert.equal(Object.getOwnPropertyNames(hooks).length, 0); 26 | }); 27 | 28 | test('should add a hook by string/callback to the class properties', (assert) => { 29 | hooks.add('test', callbackGenerator('test')); 30 | assert.equal(Object.getOwnPropertyNames(hooks).length, 1); 31 | assert.equal(hooks.test.length, 1); 32 | hooks.test[0]('env'); 33 | assert.equal(callbacks.test.length, 1); 34 | assert.equal(callbacks.test[0], 'env'); 35 | }); 36 | 37 | test('should add by object', (assert) => { 38 | hooks.add({ 39 | test: callbackGenerator('test'), 40 | play: callbackGenerator('play'), 41 | }); 42 | hooks.test[0]('testing'); 43 | hooks.play[0]('playing'); 44 | assert.equal(callbacks.test.length, 1); 45 | assert.equal(callbacks.test[0], 'testing'); 46 | assert.equal(callbacks.play.length, 1); 47 | assert.equal(callbacks.play[0], 'playing'); 48 | }); 49 | 50 | test('should add hooks by string/callback with \'first\' property set', (assert) => { 51 | hooks.add('play', env => env.push('first')); 52 | hooks.add('play', env => env.push('second'), true); 53 | assert.equal(hooks.play.length, 2); 54 | 55 | const arr = []; 56 | hooks.play[0](arr); 57 | hooks.play[1](arr); 58 | assert.equal(arr.length, 2); 59 | assert.equal(arr[0], 'second'); 60 | assert.equal(arr[1], 'first'); 61 | }); 62 | 63 | test('should add hooks by object with \'first\' property set', (assert) => { 64 | hooks.add({ 65 | play: env => env.push('firstPlay'), 66 | }, false); 67 | hooks.add({ 68 | play: env => env.push('secondPlay'), 69 | }, true); 70 | 71 | assert.equal(hooks.play.length, 2); 72 | 73 | const arr = []; 74 | hooks.play[0](arr); 75 | hooks.play[1](arr); 76 | assert.equal(arr.length, 2); 77 | assert.equal(arr[0], 'secondPlay'); 78 | assert.equal(arr[1], 'firstPlay'); 79 | }); 80 | }); 81 | 82 | QUnit.module('run', (qunit) => { 83 | let hooks; 84 | let env; 85 | qunit.beforeEach(() => { 86 | hooks = new Hooks(); 87 | hooks.add('play', env => env.play.push('first')); 88 | hooks.add('play', env => env.play.push('second')); 89 | env = { 90 | play: [], 91 | }; 92 | }); 93 | 94 | test('should run hooks in order with given env', (assert) => { 95 | hooks.run('play', env); 96 | assert.equal(env.play.length, 2); 97 | assert.equal(env.play[0], 'first'); 98 | assert.equal(env.play[1], 'second'); 99 | }); 100 | 101 | test('should be okay with no runners for a hook', (assert) => { 102 | hooks.run('working', env); 103 | assert.equal(env.play.length, 0); 104 | assert.equal(hooks.working.length, 0); 105 | }); 106 | 107 | test('should use context property for \'this\' if it exists', (assert) => { 108 | hooks.add('playing', function (env) { 109 | env.play.push('env'); 110 | this.push('this'); 111 | }); 112 | env.context = env.play; 113 | hooks.run('playing', env); 114 | assert.equal(env.play.length, 2); 115 | assert.equal(env.play[0], 'env'); 116 | assert.equal(env.play[1], 'this'); 117 | }); 118 | }); 119 | }); 120 | }()); 121 | -------------------------------------------------------------------------------- /test/packages/combinedPlugins.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../../src/jsep.js'; 2 | import jsepArrow from '../../packages/arrow/src/index.js'; 3 | import jsepAssignment from '../../packages/assignment/src/index.js'; 4 | import jsepAsyncAwait from '../../packages/async-await/src/index.js'; 5 | import jsepComment from '../../packages/comment/src/index.js'; 6 | import jsepNew from '../../packages/new/src/index.js'; 7 | import jsepNumbers from '../../packages/numbers/src/index.js'; 8 | import jsepObject from '../../packages/object/src/index.js'; 9 | import jsepRegex from '../../packages/regex/src/index.js'; 10 | import jsepSpread from '../../packages/spread/src/index.js'; 11 | import jsepTemplateLiteral from '../../packages/template/src/index.js'; 12 | import jsepTernary from '../../packages/ternary/src/index.js'; 13 | import {testParser, resetJsepDefaults, esprimaComparisonTest} from '../test_utils.js'; 14 | 15 | const { test } = QUnit; 16 | 17 | (function () { 18 | QUnit.module('Plugin:Combined', (qunit) => { 19 | qunit.before(() => { 20 | jsep.plugins.register( 21 | jsepArrow, 22 | jsepAssignment, 23 | jsepAsyncAwait, 24 | jsepComment, 25 | jsepNew, 26 | jsepNumbers, 27 | jsepObject, 28 | jsepRegex, 29 | jsepSpread, 30 | jsepTemplateLiteral, 31 | jsepTernary 32 | ); 33 | }); 34 | qunit.after(resetJsepDefaults); 35 | 36 | [ 37 | 'a.find(() => true)', 38 | '[1, 2].find(v => v === 2)', 39 | 'a.find((val, key) => key === "abc")', 40 | 'a = 2', 41 | 'a = 2', 42 | 'a *= 2', 43 | 'a **= 2', 44 | 'a /= 2', 45 | 'a %= 2', 46 | 'a += 2', 47 | 'a -= 2', 48 | 'a <<= 2', 49 | 'a >>= 2', 50 | 'a >>>= 2', 51 | 'a &= 2', 52 | 'a ^= 2', 53 | 'a |= 2', 54 | 'a++', 55 | 'a--', 56 | '++a', 57 | '--a', 58 | '(["a", "b"].find(v => v === "b").length > 1 || 2) === true', 59 | 'a.find(val => key === "abc")', 60 | 'a.find(() => []).length > 2', 61 | '(a || b).find(v => v(1))', 62 | 'await a', 63 | 'await a.find(async (v1, v2) => await v1(v2))', 64 | 'await a.find(async v => await v)', 65 | 'a.find(async ([v]) => await v)', 66 | 'a.find(async () => await x)', 67 | 'a /* ignore this */> 1 // ignore this too', // especially with regex plugin 68 | 'a /* ignore *\r\n *this */> 1 // ignore this too', 69 | 'a // ignore this\r\n> 1', 70 | 'a /** {param} \r\n*/ > 1', 71 | '// a\na > 1', 72 | 'new Date(123)', 73 | 'new A().b', 74 | 'new A() != null', 75 | 'new A(), new B()', 76 | '[new A(), new A()]', 77 | 'new A("1")', 78 | 'new A(1, 2)', 79 | 80 | // numbers plugin: 81 | '0xA', 82 | '0xfF', 83 | '0xA_a', 84 | '0b01010101', 85 | '0b0000_0111', 86 | '0b1000_0000__0000_0000__0000_0000__0000_0000', 87 | '0755', 88 | '0o644', 89 | '0795', 90 | '115_200', 91 | '10_000E1', 92 | '1_0E2_0', 93 | '1_0E+2_0', 94 | '1E-2', 95 | '1_234.567_89', 96 | '0.1', 97 | '.1', 98 | 99 | '({ a: 1, b: 2 })', 100 | '{ [key || key2]: { a: 0 } }', 101 | '{ a: { b: { c: 1 } } }', 102 | '{ a: b ? 1 : 2, c }', 103 | 'fn({ a: 1 })', 104 | '/abc/', 105 | '/abc/ig', 106 | '/\\d{3}/', 107 | 'a && /[a-z]{3}/ig.test(b)', 108 | '/\d(?=px)/.test(a)', 109 | 'a / /123/', 110 | '/123/ig["test"](b)', 111 | '/123/["test"](b)', 112 | '/\\p{Emoji_Presentation}/gu.test("ticket to 大阪 costs ¥2000 👌.")', 113 | '/abc/+/123/', 114 | '[...a]', 115 | 'fn(1, ...b)', 116 | 'fn(...123)', 117 | 'fn(..."abc")', 118 | '[1, ...[2, 3]]', 119 | '[1, ...(a ? b : c)]', 120 | '{ ...a, ...b, c }', 121 | '`hi ${name}`', 122 | 'abc`token ${`nested ${`deeply` + "str"} blah`}`', 123 | '`hi ${last}, ${first} ${middle}!`', 124 | '`hi\\n\t`', 125 | '`a\nbc${ b ? 1 : 2 }`', 126 | 'a((1 + 2), (e > 0 ? f : g))', 127 | 'a ? b : c', 128 | 'a||b ? c : d', 129 | ].forEach(expr => { 130 | test(`should parse expr "${expr}" without error`, (assert) => { 131 | testParser(expr, {}, assert); 132 | }); 133 | }); 134 | 135 | [ 136 | '() =>', 137 | 'a.find(( ) => )', 138 | 'a.find(( ', 139 | 140 | 'fn()++', 141 | '1++', 142 | '++', 143 | '(a + b)++', 144 | '--fn()', 145 | '--1', 146 | '--', 147 | '--(a + b)', 148 | 149 | 'async 123', 150 | 'async a + b', 151 | 'a.find(async () + 2)', 152 | 153 | 'a /* no close comment', 154 | 155 | 'new A', 156 | 'new A,new B', 157 | 'fn(new A)', 158 | '!new A', 159 | 'new 123', 160 | 'new (a > 2 ? A : B)', 161 | 'new (a > 1)', 162 | 163 | '{ a: }', // missing value 164 | '{ a: 1 ', // missing } 165 | '{ a: 2 ? 3, b }', // missing : in ternary 166 | 167 | '/abc', // unclosed regex 168 | '/a/xzw', // invalid flag 169 | '/a/xyz.test(a)', // invalid flag 170 | '/a(/', // unclosed ( 171 | '/a[/', // unclosed [ 172 | 173 | '[.....5]', // extra .. 174 | '[..2]', // missing . 175 | '[...3', // missing ] 176 | 177 | '1.2.3', 178 | 'check(,)', 179 | 'check(,1,2)', 180 | 'check(1,,2)', 181 | 'check(1,2,)', 182 | 'check(a, b c d) ', 183 | 'check(a, b, c d)', 184 | 'check(a b, c, d)', 185 | 'check(a b c, d)', 186 | 'myFunction(a,b', 187 | '[1,2', 188 | '-1+2-', 189 | ].forEach(expr => test(`should throw on invalid expression, ${expr}`, (assert) => { 190 | assert.throws(() => jsep(expr)); 191 | })); 192 | 193 | ([ 194 | 'a((1 + 2), (e > 0 ? f : g))', 195 | '[1,,3]', 196 | '[1,,]', // this is actually incorrect in esprima 197 | ' true', 198 | 'false ', 199 | ' 1.2 ', 200 | ' .2 ', 201 | 'a', 202 | 'a .b', 203 | 'a.b. c', 204 | 'a [b]', 205 | 'a.b [ c ] ', 206 | '$foo[ bar][ baz].other12 [\'lawl\'][12]', 207 | '$foo [ 12 ] [ baz[z] ].other12*4 + 1 ', 208 | '$foo[ bar][ baz] (a, bb , c ) .other12 [\'lawl\'][12]', 209 | '(a(b(c[!d]).e).f+\'hi\'==2) === true', 210 | '(1,2)', 211 | '(a, a + b > 2)', 212 | 'a((1 + 2), (e > 0 ? f : g))', 213 | '(((1)))', 214 | '(Object.variable.toLowerCase()).length == 3', 215 | '(Object.variable.toLowerCase()) . length == 3', 216 | '[1] + [2]', 217 | 218 | // assignment 219 | 'a = 2', 220 | 'a *= 2', 221 | 'a /= 2', // especially with regex plugin 222 | 'a %= 2', 223 | 'a += 2', 224 | 'a -= 2', 225 | 'a <<= 2', 226 | 'a >>= 2', 227 | 'a >>>= 2', 228 | 'a &= 2', 229 | 'a ^= 2', 230 | 'a |= 2', 231 | '++a == 2', 232 | 'a++ == 2', 233 | '2 == ++a', 234 | '2 == a++', 235 | '++a == 2', 236 | 'a++ == 2', 237 | '2 == ++a', 238 | '2 == a++', 239 | 'a ? a[1]++ : --b', 240 | 241 | // regex 242 | '/[a-z]{3}/ig.test(b)', 243 | '/\d(?=px)/', 244 | ]).forEach(expr => { 245 | test(`should match Esprima: ${expr}`, function (assert) { 246 | esprimaComparisonTest(expr, assert); 247 | }); 248 | }); 249 | 250 | test('should parse arrow object defaults', (assert) => { 251 | testParser('[a].map(({a = 1} = {}) => a)', { 252 | type: 'CallExpression', 253 | arguments: [ 254 | { 255 | type: 'ArrowFunctionExpression', 256 | params: [ 257 | { 258 | type: 'AssignmentExpression', 259 | operator: '=', 260 | left: { 261 | type: 'ObjectExpression', 262 | properties: [ 263 | { 264 | type: 'AssignmentExpression', 265 | operator: '=', 266 | left: { 267 | type: 'Identifier', 268 | name: 'a', 269 | }, 270 | right: { 271 | type: 'Literal', 272 | value: 1, 273 | raw: '1', 274 | }, 275 | }, 276 | ], 277 | }, 278 | right: { 279 | type: 'ObjectExpression', 280 | properties: [], 281 | }, 282 | }, 283 | ], 284 | body: { 285 | type: 'Identifier', 286 | name: 'a' 287 | } 288 | } 289 | ], 290 | callee: {}, 291 | }, assert); 292 | }); 293 | 294 | test('should parse assignment to nested arrow function correctly', (assert) => { 295 | testParser( 'f = x => y => x + y', { 296 | type: 'AssignmentExpression', 297 | operator: '=', 298 | left: { 299 | type: 'Identifier', 300 | name: 'f', 301 | }, 302 | right: { 303 | type: 'ArrowFunctionExpression', 304 | params: [ 305 | { 306 | type: 'Identifier', 307 | name: 'x', 308 | } 309 | ], 310 | body: { 311 | type: 'ArrowFunctionExpression', 312 | params: [ 313 | { 314 | type: 'Identifier', 315 | name: 'y', 316 | } 317 | ], 318 | body: { 319 | type: 'BinaryExpression', 320 | operator: '+', 321 | left: { 322 | type: 'Identifier', 323 | name: 'x', 324 | }, 325 | right: { 326 | type: 'Identifier', 327 | name: 'y', 328 | }, 329 | }, 330 | }, 331 | }, 332 | }, assert); 333 | }); 334 | 335 | ([ 336 | '(() => (x + 1))', 337 | '() => x + 1', 338 | '() => (x + 1)', 339 | ]).forEach(expr => { 340 | test(`should parse arrow body ${expr} same, whether parenthesized or not`, function (assert) { 341 | testParser( expr, { 342 | type: 'ArrowFunctionExpression', 343 | params: null, 344 | body: { 345 | type: 'BinaryExpression', 346 | operator: '+', 347 | left: { 348 | type: 'Identifier', 349 | name: 'x', 350 | }, 351 | right: { 352 | type: 'Literal', 353 | value: 1, 354 | raw: '1', 355 | }, 356 | }, 357 | }, assert); 358 | }); 359 | }); 360 | 361 | test('should parse ()-enclosed, nested arrow correctly', (assert) => { 362 | testParser( 'x => (() => x + 1)', { 363 | type: 'ArrowFunctionExpression', 364 | params: [ 365 | { 366 | type: 'Identifier', 367 | name: 'x', 368 | } 369 | ], 370 | body: { 371 | type: 'ArrowFunctionExpression', 372 | params: null, 373 | body: { 374 | type: 'BinaryExpression', 375 | operator: '+', 376 | left: { 377 | type: 'Identifier', 378 | name: 'x', 379 | }, 380 | right: { 381 | type: 'Literal', 382 | value: 1, 383 | raw: '1', 384 | }, 385 | }, 386 | }, 387 | }, assert); 388 | }); 389 | }); 390 | }()); 391 | -------------------------------------------------------------------------------- /test/performance.test.js: -------------------------------------------------------------------------------- 1 | import jsep from '../src/index.js'; 2 | 3 | import Benchmark from 'benchmark'; 4 | 5 | console.log('Starting performance test.'); 6 | 7 | const tests = [ 8 | {name: 'simple string', value: '\'abc\''}, 9 | {name: 'simple addition', value: '1+2'}, 10 | {name: 'function call', value: 'a(b, c(d,e), f)'}, 11 | {name: 'ternary', value: 'a ? b : c'} 12 | ]; 13 | 14 | const suite = new Benchmark.Suite; 15 | 16 | tests.forEach((test) => { 17 | suite.add(test.name, () => { 18 | jsep(test.value); 19 | }); 20 | }); 21 | 22 | suite 23 | .on('cycle', function (event) { 24 | console.log(String(event.target)); 25 | }) 26 | .on('complete', function () { 27 | console.log('Performance test completed.'); 28 | }) 29 | .run({ 30 | 'async': true 31 | }); 32 | -------------------------------------------------------------------------------- /test/playground.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/plugins.test.js: -------------------------------------------------------------------------------- 1 | import Plugins from '../src/plugins.js'; 2 | 3 | const { test } = QUnit; 4 | 5 | (function () { 6 | QUnit.module('Plugins', (qunit) => { 7 | let callbacks; 8 | const callbackGenerator = (name) => { 9 | callbacks[name] = []; 10 | return (env) => { 11 | callbacks[name].push(env); 12 | }; 13 | }; 14 | qunit.beforeEach(() => { 15 | callbacks = {}; 16 | }); 17 | 18 | QUnit.module('Register', (qunit) => { 19 | let plugins; 20 | qunit.beforeEach(() => { 21 | plugins = new Plugins({ fake: 'jsep' }); 22 | }); 23 | 24 | test('should register plugin with init', (assert) => { 25 | let init; 26 | const plugin = { name: 'plugin', init: jsep => init = jsep }; 27 | plugins.register(plugin); 28 | assert.equal(init.fake, 'jsep'); 29 | }); 30 | 31 | test('should be okay with duplicate registration and not call init again', (assert) => { 32 | const plugin = { name: 'plugin', init: () => {} }; 33 | plugins.register(plugin); 34 | plugin.init = () => { 35 | throw new Error('second init'); 36 | }; 37 | plugins.register(plugin); 38 | assert.equal(Object.keys(plugins.registered).length, 1); 39 | }); 40 | 41 | test('should accept multiple args', (assert) => { 42 | const myPlugins = [ 43 | { name: 'a', init: () => {} }, 44 | { name: 'b', init: () => {} }, 45 | ]; 46 | plugins.register(...myPlugins); 47 | assert.equal(Object.keys(plugins.registered).length, 2); 48 | }); 49 | 50 | test('should throw on invalid plugin argument', (assert) => { 51 | [ 52 | 'noObject', 53 | [null], 54 | 1, 55 | null, 56 | { bad: 'object' }, 57 | { name: '', init: () => {} }, 58 | { name: 'fake' }, 59 | { init: () => {} }, 60 | ].forEach(arg => assert.throws(() => plugins.register(arg))); 61 | }); 62 | }); 63 | }); 64 | }()); 65 | -------------------------------------------------------------------------------- /test/resources/qunit-2.10.0.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * QUnit 2.10.0 3 | * https://qunitjs.com/ 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license 7 | * https://jquery.org/license 8 | * 9 | * Date: 2020-05-02T22:51Z 10 | */ 11 | 12 | /** Font Family and Sizes */ 13 | 14 | 15 | [id^=qunit] button { 16 | font-size: initial; 17 | border: initial; 18 | background-color: buttonface; 19 | } 20 | 21 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult { 22 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 23 | } 24 | 25 | #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 26 | #qunit-tests { font-size: smaller; } 27 | 28 | 29 | /** Resets */ 30 | 31 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 32 | margin: 0; 33 | padding: 0; 34 | } 35 | 36 | 37 | /** Header (excluding toolbar) */ 38 | 39 | #qunit-header { 40 | padding: 0.5em 0 0.5em 1em; 41 | 42 | color: #8699A4; 43 | background-color: #0D3349; 44 | 45 | font-size: 1.5em; 46 | line-height: 1em; 47 | font-weight: 400; 48 | 49 | border-radius: 5px 5px 0 0; 50 | } 51 | 52 | #qunit-header a { 53 | text-decoration: none; 54 | color: #C2CCD1; 55 | } 56 | 57 | #qunit-header a:hover, 58 | #qunit-header a:focus { 59 | color: #FFF; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-filteredTest { 67 | padding: 0.5em 1em 0.5em 1em; 68 | color: #366097; 69 | background-color: #F4FF77; 70 | } 71 | 72 | #qunit-userAgent { 73 | padding: 0.5em 1em 0.5em 1em; 74 | color: #FFF; 75 | background-color: #2B81AF; 76 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 77 | } 78 | 79 | 80 | /** Toolbar */ 81 | 82 | #qunit-testrunner-toolbar { 83 | padding: 0.5em 1em 0.5em 1em; 84 | color: #5E740B; 85 | background-color: #EEE; 86 | } 87 | 88 | #qunit-testrunner-toolbar .clearfix { 89 | height: 0; 90 | clear: both; 91 | } 92 | 93 | #qunit-testrunner-toolbar label { 94 | display: inline-block; 95 | } 96 | 97 | #qunit-testrunner-toolbar input[type=checkbox], 98 | #qunit-testrunner-toolbar input[type=radio] { 99 | margin: 3px; 100 | vertical-align: -2px; 101 | } 102 | 103 | #qunit-testrunner-toolbar input[type=text] { 104 | box-sizing: border-box; 105 | height: 1.6em; 106 | } 107 | 108 | #qunit-toolbar-filters { 109 | float: right; 110 | } 111 | 112 | .qunit-url-config, 113 | .qunit-filter, 114 | #qunit-modulefilter { 115 | display: inline-block; 116 | line-height: 2.1em; 117 | } 118 | 119 | .qunit-filter, 120 | #qunit-modulefilter { 121 | position: relative; 122 | margin-left: 1em; 123 | } 124 | 125 | .qunit-url-config label { 126 | margin-right: 0.5em; 127 | } 128 | 129 | #qunit-modulefilter-search { 130 | box-sizing: border-box; 131 | min-width: 400px; 132 | } 133 | 134 | #qunit-modulefilter-search-container:after { 135 | position: absolute; 136 | right: 0.3em; 137 | content: "\25bc"; 138 | color: black; 139 | } 140 | 141 | #qunit-modulefilter-dropdown { 142 | /* align with #qunit-modulefilter-search */ 143 | box-sizing: border-box; 144 | min-width: 400px; 145 | position: absolute; 146 | right: 0; 147 | top: 50%; 148 | margin-top: 0.8em; 149 | 150 | border: 1px solid #D3D3D3; 151 | border-top: none; 152 | border-radius: 0 0 .25em .25em; 153 | color: #000; 154 | background-color: #F5F5F5; 155 | z-index: 99; 156 | } 157 | 158 | #qunit-modulefilter-dropdown a { 159 | color: inherit; 160 | text-decoration: none; 161 | } 162 | 163 | #qunit-modulefilter-dropdown .clickable.checked { 164 | font-weight: bold; 165 | color: #000; 166 | background-color: #D2E0E6; 167 | } 168 | 169 | #qunit-modulefilter-dropdown .clickable:hover { 170 | color: #FFF; 171 | background-color: #0D3349; 172 | } 173 | 174 | #qunit-modulefilter-actions { 175 | display: block; 176 | overflow: auto; 177 | 178 | /* align with #qunit-modulefilter-dropdown-list */ 179 | font: smaller/1.5em sans-serif; 180 | } 181 | 182 | #qunit-modulefilter-dropdown #qunit-modulefilter-actions > * { 183 | box-sizing: border-box; 184 | max-height: 2.8em; 185 | display: block; 186 | padding: 0.4em; 187 | } 188 | 189 | #qunit-modulefilter-dropdown #qunit-modulefilter-actions > button { 190 | float: right; 191 | font: inherit; 192 | } 193 | 194 | #qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child { 195 | /* insert padding to align with checkbox margins */ 196 | padding-left: 3px; 197 | } 198 | 199 | #qunit-modulefilter-dropdown-list { 200 | max-height: 200px; 201 | overflow-y: auto; 202 | margin: 0; 203 | border-top: 2px groove threedhighlight; 204 | padding: 0.4em 0 0; 205 | font: smaller/1.5em sans-serif; 206 | } 207 | 208 | #qunit-modulefilter-dropdown-list li { 209 | white-space: nowrap; 210 | overflow: hidden; 211 | text-overflow: ellipsis; 212 | } 213 | 214 | #qunit-modulefilter-dropdown-list .clickable { 215 | display: block; 216 | padding-left: 0.15em; 217 | padding-right: 0.5em; 218 | } 219 | 220 | 221 | /** Tests: Pass/Fail */ 222 | 223 | #qunit-tests { 224 | list-style-position: inside; 225 | } 226 | 227 | #qunit-tests li { 228 | padding: 0.4em 1em 0.4em 1em; 229 | border-bottom: 1px solid #FFF; 230 | list-style-position: inside; 231 | } 232 | 233 | #qunit-tests > li { 234 | display: none; 235 | } 236 | 237 | #qunit-tests li.running, 238 | #qunit-tests li.pass, 239 | #qunit-tests li.fail, 240 | #qunit-tests li.skipped, 241 | #qunit-tests li.aborted { 242 | display: list-item; 243 | } 244 | 245 | #qunit-tests.hidepass { 246 | position: relative; 247 | } 248 | 249 | #qunit-tests.hidepass li.running, 250 | #qunit-tests.hidepass li.pass:not(.todo) { 251 | visibility: hidden; 252 | position: absolute; 253 | width: 0; 254 | height: 0; 255 | padding: 0; 256 | border: 0; 257 | margin: 0; 258 | } 259 | 260 | #qunit-tests li strong { 261 | cursor: pointer; 262 | } 263 | 264 | #qunit-tests li.skipped strong { 265 | cursor: default; 266 | } 267 | 268 | #qunit-tests li a { 269 | padding: 0.5em; 270 | color: #C2CCD1; 271 | text-decoration: none; 272 | } 273 | 274 | #qunit-tests li p a { 275 | padding: 0.25em; 276 | color: #6B6464; 277 | } 278 | #qunit-tests li a:hover, 279 | #qunit-tests li a:focus { 280 | color: #000; 281 | } 282 | 283 | #qunit-tests li .runtime { 284 | float: right; 285 | font-size: smaller; 286 | } 287 | 288 | .qunit-assert-list { 289 | margin-top: 0.5em; 290 | padding: 0.5em; 291 | 292 | background-color: #FFF; 293 | 294 | border-radius: 5px; 295 | } 296 | 297 | .qunit-source { 298 | margin: 0.6em 0 0.3em; 299 | } 300 | 301 | .qunit-collapsed { 302 | display: none; 303 | } 304 | 305 | #qunit-tests table { 306 | border-collapse: collapse; 307 | margin-top: 0.2em; 308 | } 309 | 310 | #qunit-tests th { 311 | text-align: right; 312 | vertical-align: top; 313 | padding: 0 0.5em 0 0; 314 | } 315 | 316 | #qunit-tests td { 317 | vertical-align: top; 318 | } 319 | 320 | #qunit-tests pre { 321 | margin: 0; 322 | white-space: pre-wrap; 323 | word-wrap: break-word; 324 | } 325 | 326 | #qunit-tests del { 327 | color: #374E0C; 328 | background-color: #E0F2BE; 329 | text-decoration: none; 330 | } 331 | 332 | #qunit-tests ins { 333 | color: #500; 334 | background-color: #FFCACA; 335 | text-decoration: none; 336 | } 337 | 338 | /*** Test Counts */ 339 | 340 | #qunit-tests b.counts { color: #000; } 341 | #qunit-tests b.passed { color: #5E740B; } 342 | #qunit-tests b.failed { color: #710909; } 343 | 344 | #qunit-tests li li { 345 | padding: 5px; 346 | background-color: #FFF; 347 | border-bottom: none; 348 | list-style-position: inside; 349 | } 350 | 351 | /*** Passing Styles */ 352 | 353 | #qunit-tests li li.pass { 354 | color: #3C510C; 355 | background-color: #FFF; 356 | border-left: 10px solid #C6E746; 357 | } 358 | 359 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 360 | #qunit-tests .pass .test-name { color: #366097; } 361 | 362 | #qunit-tests .pass .test-actual, 363 | #qunit-tests .pass .test-expected { color: #999; } 364 | 365 | #qunit-banner.qunit-pass { background-color: #C6E746; } 366 | 367 | /*** Failing Styles */ 368 | 369 | #qunit-tests li li.fail { 370 | color: #710909; 371 | background-color: #FFF; 372 | border-left: 10px solid #EE5757; 373 | white-space: pre; 374 | } 375 | 376 | #qunit-tests > li:last-child { 377 | border-radius: 0 0 5px 5px; 378 | } 379 | 380 | #qunit-tests .fail { color: #000; background-color: #EE5757; } 381 | #qunit-tests .fail .test-name, 382 | #qunit-tests .fail .module-name { color: #000; } 383 | 384 | #qunit-tests .fail .test-actual { color: #EE5757; } 385 | #qunit-tests .fail .test-expected { color: #008000; } 386 | 387 | #qunit-banner.qunit-fail { background-color: #EE5757; } 388 | 389 | 390 | /*** Aborted tests */ 391 | #qunit-tests .aborted { color: #000; background-color: orange; } 392 | /*** Skipped tests */ 393 | 394 | #qunit-tests .skipped { 395 | background-color: #EBECE9; 396 | } 397 | 398 | #qunit-tests .qunit-todo-label, 399 | #qunit-tests .qunit-skipped-label { 400 | background-color: #F4FF77; 401 | display: inline-block; 402 | font-style: normal; 403 | color: #366097; 404 | line-height: 1.8em; 405 | padding: 0 0.5em; 406 | margin: -0.4em 0.4em -0.4em 0; 407 | } 408 | 409 | #qunit-tests .qunit-todo-label { 410 | background-color: #EEE; 411 | } 412 | 413 | /** Result */ 414 | 415 | #qunit-testresult { 416 | color: #2B81AF; 417 | background-color: #D2E0E6; 418 | 419 | border-bottom: 1px solid #FFF; 420 | } 421 | #qunit-testresult .clearfix { 422 | height: 0; 423 | clear: both; 424 | } 425 | #qunit-testresult .module-name { 426 | font-weight: 700; 427 | } 428 | #qunit-testresult-display { 429 | padding: 0.5em 1em 0.5em 1em; 430 | width: 85%; 431 | float:left; 432 | } 433 | #qunit-testresult-controls { 434 | padding: 0.5em 1em 0.5em 1em; 435 | width: 10%; 436 | float:left; 437 | } 438 | 439 | /** Fixture */ 440 | 441 | #qunit-fixture { 442 | position: absolute; 443 | top: -10000px; 444 | left: -10000px; 445 | width: 1000px; 446 | height: 1000px; 447 | } -------------------------------------------------------------------------------- /test/test_utils.js: -------------------------------------------------------------------------------- 1 | import jsep from '../src/jsep.js'; 2 | 3 | export const binOps = { 4 | '+': (a, b) => a + b, 5 | '-': (a, b) => a - b, 6 | '*': (a, b) => a * b, 7 | '/': (a, b) => a / b, 8 | '%': (a, b) => a % b, 9 | '**': (a, b) => a ** b, // ES2016 10 | '??': (a, b) => a ?? b, // ES2020 11 | }; 12 | 13 | export const unOps = { 14 | '-': a => -a, 15 | '+': a => +a, 16 | }; 17 | 18 | /* eslint-enable brace-style */ 19 | 20 | export function doEval(node) { 21 | if (node.type === 'BinaryExpression') { 22 | return binOps[node.operator](doEval(node.left), doEval(node.right)); 23 | } 24 | else if (node.type === 'UnaryExpression') { 25 | return unOps[node.operator](doEval(node.argument)); 26 | } 27 | else if (node.type === 'Literal') { 28 | return node.value; 29 | } 30 | } 31 | 32 | export function testOpExpression(str, assert) { 33 | assert.equal(doEval(jsep(str)), eval(str)); 34 | } 35 | 36 | export function filterProps(larger, smaller) { 37 | const rv = (typeof larger.length === 'number') ? [] : {}; 38 | for (let propName in smaller) { 39 | let propVal = smaller[propName]; 40 | if (typeof propVal === 'string' || typeof propVal === 'number' || typeof propVal === 'boolean' || propVal === null || propVal instanceof RegExp) { 41 | rv[propName] = larger[propName]; 42 | } 43 | else { 44 | rv[propName] = filterProps(larger[propName], propVal); 45 | } 46 | } 47 | return rv; 48 | } 49 | 50 | export function testParser(inp, out, assert) { 51 | const parsedVal = jsep(inp); 52 | return assert.deepEqual(filterProps(parsedVal, out), out); 53 | } 54 | 55 | export function findNode(ast, condition) { 56 | if (typeof ast !== 'object') { 57 | return undefined; 58 | } 59 | if (condition(ast)) { 60 | return ast; 61 | } 62 | return Object 63 | .getOwnPropertyNames(ast) 64 | .reduce((n, k) => n || findNode(ast[k], condition), undefined); 65 | } 66 | 67 | export function esprimaComparisonTest(str, assert) { 68 | const parsedVal = jsep(str); 69 | const esprimaVal = esprima.parse(str); 70 | return assert.deepEqual(parsedVal, esprimaVal.body[0].expression); 71 | } 72 | 73 | export const defaults = { 74 | hooks: {}, 75 | plugins: Object.assign({}, jsep.plugins.registered), 76 | unary_ops: Object.assign({}, jsep.unary_ops), 77 | binary_ops: Object.assign({}, jsep.binary_ops), 78 | additional_identifier_chars: new Set(jsep.additional_identifier_chars), 79 | literals: Object.assign({}, jsep.literals), 80 | this_str: jsep.this_str, 81 | }; 82 | Object.entries(jsep.hooks).forEach(([hookName, fns]) => { 83 | defaults.hooks[hookName] = [...fns]; 84 | }); 85 | 86 | export function resetJsepDefaults() { 87 | for (let key in jsep.hooks) { 88 | delete jsep.hooks[key]; 89 | } 90 | Object.entries(defaults.hooks).forEach(([hookName, fns]) => { 91 | jsep.hooks[hookName] = [...fns]; 92 | }); 93 | jsep.unary_ops = Object.assign({}, defaults.unary_ops); 94 | jsep.binary_ops = Object.assign({}, defaults.binary_ops); 95 | jsep.additional_identifier_chars = new Set(defaults.additional_identifier_chars); 96 | jsep.literals = Object.assign({}, defaults.literals); 97 | jsep.this_str = defaults.this_str; 98 | jsep.plugins.registered = Object.assign({}, defaults.plugins); 99 | } 100 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | // barrel export of all tests 2 | export * from './jsep.test.js'; 3 | export * from './hooks.test.js'; 4 | export * from './plugins.test.js'; 5 | export * from '../packages/arrow/test/index.test.js'; 6 | export * from '../packages/assignment/test/index.test.js'; 7 | export * from '../packages/async-await/test/index.test.js'; 8 | export * from '../packages/ternary/test/index.test.js'; 9 | export * from '../packages/comment/test/index.test.js'; 10 | export * from '../packages/new/test/index.test.js'; 11 | export * from '../packages/numbers/test/index.test.js'; 12 | export * from '../packages/object/test/index.test.js'; 13 | export * from '../packages/regex/test/index.test.js'; 14 | export * from '../packages/spread/test/index.test.js'; 15 | export * from '../packages/template/test/index.test.js'; 16 | export * from './packages/combinedPlugins.test.js'; 17 | -------------------------------------------------------------------------------- /test/unit_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jsep Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

                        jsep Unit Tests

                        15 |

                        16 |
                        17 |

                        18 |
                          19 | 20 | 21 | -------------------------------------------------------------------------------- /typings/tsd.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'jsep' { 2 | 3 | namespace jsep { 4 | export type baseTypes = string | number | boolean | RegExp | null | undefined | object; 5 | export interface Expression { 6 | type: string; 7 | [key: string]: baseTypes | Expression | Array; 8 | } 9 | 10 | export interface ArrayExpression extends Expression { 11 | type: 'ArrayExpression'; 12 | /** The expression can be null in the case of array holes ([ , , ]) */ 13 | elements: Array; 14 | } 15 | 16 | export interface BinaryExpression extends Expression { 17 | type: 'BinaryExpression'; 18 | operator: string; 19 | left: Expression; 20 | right: Expression; 21 | } 22 | 23 | export interface CallExpression extends Expression { 24 | type: 'CallExpression'; 25 | arguments: Expression[]; 26 | callee: Expression; 27 | } 28 | 29 | export interface Compound extends Expression { 30 | type: 'Compound'; 31 | body: Expression[]; 32 | } 33 | 34 | export interface SequenceExpression extends Expression { 35 | type: 'SequenceExpression'; 36 | expressions: Expression[]; 37 | } 38 | 39 | export interface ConditionalExpression extends Expression { 40 | type: 'ConditionalExpression'; 41 | test: Expression; 42 | consequent: Expression; 43 | alternate: Expression; 44 | } 45 | 46 | export interface Identifier extends Expression { 47 | type: 'Identifier'; 48 | name: string; 49 | } 50 | 51 | export interface Literal extends Expression { 52 | type: 'Literal'; 53 | value: boolean | number | string | RegExp | null; 54 | raw: string; 55 | } 56 | 57 | export interface MemberExpression extends Expression { 58 | type: 'MemberExpression'; 59 | computed: boolean; 60 | object: Expression; 61 | property: Expression; 62 | optional?: boolean; 63 | } 64 | 65 | export interface ThisExpression extends Expression { 66 | type: 'ThisExpression'; 67 | } 68 | 69 | export interface UnaryExpression extends Expression { 70 | type: 'UnaryExpression'; 71 | operator: string; 72 | argument: Expression; 73 | prefix: boolean; 74 | } 75 | 76 | export type ExpressionType = 77 | 'Compound' 78 | | 'SequenceExpression' 79 | | 'Identifier' 80 | | 'MemberExpression' 81 | | 'Literal' 82 | | 'ThisExpression' 83 | | 'CallExpression' 84 | | 'UnaryExpression' 85 | | 'BinaryExpression' 86 | | 'ConditionalExpression' 87 | | 'ArrayExpression'; 88 | 89 | export type CoreExpression = 90 | ArrayExpression 91 | | BinaryExpression 92 | | CallExpression 93 | | Compound 94 | | SequenceExpression 95 | | ConditionalExpression 96 | | Identifier 97 | | Literal 98 | | MemberExpression 99 | | ThisExpression 100 | | UnaryExpression; 101 | 102 | export type PossibleExpression = Expression | undefined; 103 | export interface HookScope { 104 | index: number; 105 | readonly expr: string; 106 | readonly char: string; // current character of the expression 107 | readonly code: number; // current character code of the expression 108 | gobbleSpaces: () => void; 109 | gobbleExpressions: (untilICode?: number) => Expression[]; 110 | gobbleExpression: () => Expression; 111 | gobbleBinaryOp: () => PossibleExpression; 112 | gobbleBinaryExpression: () => PossibleExpression; 113 | gobbleToken: () => PossibleExpression; 114 | gobbleTokenProperty: (node: Expression) => Expression 115 | gobbleNumericLiteral: () => PossibleExpression; 116 | gobbleStringLiteral: () => PossibleExpression; 117 | gobbleIdentifier: () => PossibleExpression; 118 | gobbleArguments: (untilICode: number) => PossibleExpression; 119 | gobbleGroup: () => Expression; 120 | gobbleArray: () => PossibleExpression; 121 | throwError: (msg: string) => never; 122 | } 123 | 124 | export type HookType = 'gobble-expression' | 'after-expression' | 'gobble-token' | 'after-token' | 'gobble-spaces'; 125 | export type HookCallback = (this: HookScope, env: { node?: Expression }) => void; 126 | type HookTypeObj = Partial<{ [key in HookType]: HookCallback}> 127 | 128 | export interface IHooks extends HookTypeObj { 129 | add(name: HookType, cb: HookCallback, first?: boolean): void; 130 | add(obj: { [name in HookType]: HookCallback }, first?: boolean): void; 131 | run(name: string, env: { context?: typeof jsep, node?: Expression }): void; 132 | } 133 | let hooks: IHooks; 134 | 135 | export interface IPlugin { 136 | name: string; 137 | init: (this: typeof jsep) => void; 138 | } 139 | export interface IPlugins { 140 | registered: { [name: string]: IPlugin }; 141 | register: (...plugins: IPlugin[]) => void; 142 | } 143 | let plugins: IPlugins; 144 | 145 | let unary_ops: { [op: string]: any }; 146 | let binary_ops: { [op: string]: number }; 147 | let right_associative: Set; 148 | let additional_identifier_chars: Set; 149 | let literals: { [literal: string]: any }; 150 | let this_str: string; 151 | 152 | function addBinaryOp(operatorName: string, precedence: number, rightToLeft?: boolean): void; 153 | 154 | function addUnaryOp(operatorName: string): void; 155 | 156 | function addLiteral(literalName: string, literalValue: any): void; 157 | 158 | function addIdentifierChar(identifierName: string): void; 159 | 160 | function removeBinaryOp(operatorName: string): void; 161 | 162 | function removeUnaryOp(operatorName: string): void; 163 | 164 | function removeLiteral(literalName: string): void; 165 | 166 | function removeIdentifierChar(identifierName: string): void; 167 | 168 | function removeAllBinaryOps(): void; 169 | 170 | function removeAllUnaryOps(): void; 171 | 172 | function removeAllLiterals(): void; 173 | 174 | const version: string; 175 | } 176 | 177 | function jsep(val: string | jsep.Expression): jsep.Expression; 178 | 179 | export = jsep; 180 | } 181 | --------------------------------------------------------------------------------