├── .github └── workflows │ └── npm-publish.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── demos └── taro-demo │ ├── .eslintrc.js │ ├── .prettierrc │ ├── README.md │ ├── babel.config.js │ ├── config │ ├── dev.js │ ├── index.js │ └── prod.js │ ├── global.d.ts │ ├── package.json │ ├── project.config.json │ ├── project.tt.json │ ├── src │ ├── app.config.ts │ ├── app.less │ ├── app.tsx │ ├── index.html │ └── pages │ │ └── index │ │ ├── index.config.ts │ │ ├── index.less │ │ └── index.tsx │ ├── tsconfig.json │ └── yarn.lock ├── docs └── imgs │ ├── code-demo.jpeg │ └── demo.jpeg ├── jest.config.js ├── package.json ├── rollup.config.js ├── src ├── index.ts ├── js-interpreter │ ├── acorn.js │ └── interpreter.js └── react-interpreter │ ├── ReactInterpreter.ts │ ├── constants.ts │ ├── createAsyncSwitcher.test.ts │ ├── createAsyncSwitcher.ts │ ├── injectGlobalObject.ts │ ├── transformComponent.test.tsx │ └── transformComponent.ts ├── tsconfig.json └── yarn.lock /.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 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | 12 | publish-npm: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: 16 19 | registry-url: https://registry.npmjs.org/ 20 | - run: yarn 21 | - run: npm run release 22 | env: 23 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | dist 107 | lib 108 | es 109 | types -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "eslintIntegration": true, 3 | "tabWidth": 4, 4 | "singleQuote": true, 5 | "semi": false, 6 | "printWidth": 120, 7 | "bracketSpacing": true, 8 | "jsxBracketSameLine": false, 9 | "jsxSingleQuote": true, 10 | "trailingComma": "es5" 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 wuchangming 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-interpreter 2 | **⚠️⚠️⚠️ 该方案不再维护,改为 [mini-hot](https://github.com/mini-hot/mini-hot) 中维护** 3 | 4 | React 沙盒 📦,可理解为 React 版的 `eval()` 。该沙盒运行机制可使基于 React 实现的小程序框架「如 Taro3 等」拥有 🚀 **热更新**能力。 5 | 6 | Gzip Size 7 | NPM Version 8 | 9 | ## 安装 10 | 11 | ``` 12 | npm install react-interpreter --save 13 | ``` 14 | 15 | 或者 16 | 17 | ``` 18 | yarn add react-interpreter --save 19 | ``` 20 | 21 | ## API 22 | 23 | ### `ReactInterpreter` - React 沙盒组件 24 | 25 | --- 26 | 27 | - ### **Props** 28 | 29 | - #### `code` -- React 沙盒运行的代码字符串 30 | 31 | ⚠️ `PS: React 沙盒组件运行的字符串代码需使用 es5 编写的函数组件,不支持 hooks、class 组件。不直接支持 jsx 写法,可以先通过` [**babel 进行转换**](https://babeljs.io/repl/#?browsers=defaults&build=&builtIns=false&corejs=3.6&spec=false&loose=false&code_lz=Q&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Creact%2Cstage-2&prettier=true&targets=&version=7.17.2&externalPlugins=&assumptions=%7B%7D) 32 | 33 | ```ts 34 | import { ReactInterpreter } from 'react-interpreter' 35 | import { View, Text } from '@tarojs/components' 36 | /* 37 | 【Babel 编译前组件代码】 38 | */ 39 | /* 40 | 注意:这个组件名命名只要不和注入的组件重名就行,没有特别要求 41 | function MyComp() { 42 | return ( 43 | 52 | Hello World ! 53 | 54 | ) 55 | } 56 | */ 57 | /* 58 | 【Babel 编译后组件代码 string】 59 | */ 60 | const codeString = `function MyComp() { 61 | return React.createElement( 62 | View, 63 | { 64 | style: { 65 | backgroundColor: '#00C28E', 66 | height: '100vh', 67 | display: 'flex', 68 | alignItems: 'center', 69 | justifyContent: 'center', 70 | }, 71 | }, 72 | React.createElement(Text, null, 'Hello World !') 73 | ) 74 | }` 75 | const MyComp = () => ( 76 | 83 | ) 84 | ``` 85 | 86 | - 效果图 87 | 88 | 89 | 90 | - #### `globalObject` -- 需要注入沙盒中的全局变量 91 | 92 | ```ts 93 | globalObject = { 94 | wx, // 注入 wx 全局变量 95 | console, // 注入 console 控制台 96 | } 97 | ``` 98 | 99 | - #### `componentMap` -- 需要注入沙盒中的 React 组件 100 | 101 | ```ts 102 | import { View } from '@tarojs/components' 103 | componentMap = { 104 | View, 105 | } 106 | ``` 107 | 108 | - #### `globalObjectComplexPropLevel` -- 全局变量复杂属性最大层级 109 | 110 | `默认值:3` 111 | 112 | `设置被注入的全局变量的复杂属性最大层级。为了保证转化效率,大于该层级的任何不能 JSON.stringify 的内容都会被丢弃掉「如 function 和出现循环引用的 object 等」。` 113 | 114 | - #### `沙盒组件 props 传值方式` 115 | 116 | `除了 ReactInterpreter API 外的其他 props 都会被直接透传到沙盒内的组件` 117 | 118 | ```ts 119 | const codeString = ` 120 | function MyComp(props) { 121 | return /*#__PURE__*/ React.createElement( 122 | Button, 123 | { 124 | onClick: props.onClickMe 125 | }, 126 | "I am a button -- ", 127 | props.btnName 128 | ); 129 | } 130 | ` 131 | 132 | const MyComp = () => ( 133 | { 141 | console.log('我被点击了!') 142 | }} 143 | > 144 | ) 145 | ``` 146 | 147 | ### `JSInterpreter` - JS 沙盒 148 | 149 | --- 150 | 151 | 如果只需要执行 JS ,可直接使用 JSInterpreter 152 | 153 | - ### 基本用法 154 | 155 | ```ts 156 | import { JSInterpreter } from 'react-interpreter' 157 | 158 | const myInterpreter = new JSInterpreter('6 * 7') 159 | myInterpreter.run() 160 | console.log(myInterpreter.value) 161 | ``` 162 | 163 | JSInterpreter 代码基本都是使用的 [JS-Interpreter](https://github.com/NeilFraser/JS-Interpreter) 项目,只做了对微信小程序相关 bug 的修复,所以详细文档可直接参考 JS-Interpreter 文档: [https://neil.fraser.name/software/JS-Interpreter/docs.html](https://neil.fraser.name/software/JS-Interpreter/docs.html) 164 | 165 | ## 实例 Demo 166 | 167 | - ### Taro3 中用法示例 [查看 Demo 项目](./demos/taro-demo/) 168 | 169 | ## 灵感来源 170 | 171 | - [JS-Interpreter](https://github.com/NeilFraser/JS-Interpreter) 172 | - [jsjs](https://github.com/bramblex/jsjs) 173 | -------------------------------------------------------------------------------- /demos/taro-demo/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // "extends": ["taro/react"], 3 | "rules": { 4 | "react/jsx-uses-react": "off", 5 | "react/react-in-jsx-scope": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demos/taro-demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "eslintIntegration": true, 3 | "tabWidth": 4, 4 | "singleQuote": true, 5 | "semi": false, 6 | "printWidth": 120, 7 | "bracketSpacing": true, 8 | "jsxBracketSameLine": false, 9 | "jsxSingleQuote": true, 10 | "trailingComma": "es5" 11 | } 12 | -------------------------------------------------------------------------------- /demos/taro-demo/README.md: -------------------------------------------------------------------------------- 1 | # react-interpreter 的 Taro Demo 2 | 3 | ## 启动命令 4 | step1: 5 | ``` 6 | npm i 7 | ``` 8 | step2: 9 | 10 | ``` 11 | npm run dev:weapp 12 | ``` -------------------------------------------------------------------------------- /demos/taro-demo/babel.config.js: -------------------------------------------------------------------------------- 1 | // babel-preset-taro 更多选项和默认值: 2 | // https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md 3 | module.exports = { 4 | presets: [ 5 | ['taro', { 6 | framework: 'react', 7 | ts: true 8 | }] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /demos/taro-demo/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"development"' 4 | }, 5 | defineConstants: { 6 | }, 7 | mini: {}, 8 | h5: {} 9 | } 10 | -------------------------------------------------------------------------------- /demos/taro-demo/config/index.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | projectName: 'taro-demo', 3 | date: '2022-2-9', 4 | designWidth: 750, 5 | deviceRatio: { 6 | 640: 2.34 / 2, 7 | 750: 1, 8 | 828: 1.81 / 2 9 | }, 10 | sourceRoot: 'src', 11 | outputRoot: 'dist', 12 | plugins: [], 13 | defineConstants: { 14 | }, 15 | copy: { 16 | patterns: [ 17 | ], 18 | options: { 19 | } 20 | }, 21 | framework: 'react', 22 | mini: { 23 | postcss: { 24 | pxtransform: { 25 | enable: true, 26 | config: { 27 | 28 | } 29 | }, 30 | url: { 31 | enable: true, 32 | config: { 33 | limit: 1024 // 设定转换尺寸上限 34 | } 35 | }, 36 | cssModules: { 37 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 38 | config: { 39 | namingPattern: 'module', // 转换模式,取值为 global/module 40 | generateScopedName: '[name]__[local]___[hash:base64:5]' 41 | } 42 | } 43 | } 44 | }, 45 | h5: { 46 | publicPath: '/', 47 | staticDirectory: 'static', 48 | postcss: { 49 | autoprefixer: { 50 | enable: true, 51 | config: { 52 | } 53 | }, 54 | cssModules: { 55 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 56 | config: { 57 | namingPattern: 'module', // 转换模式,取值为 global/module 58 | generateScopedName: '[name]__[local]___[hash:base64:5]' 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | module.exports = function (merge) { 66 | if (process.env.NODE_ENV === 'development') { 67 | return merge({}, config, require('./dev')) 68 | } 69 | return merge({}, config, require('./prod')) 70 | } 71 | -------------------------------------------------------------------------------- /demos/taro-demo/config/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"production"' 4 | }, 5 | defineConstants: { 6 | }, 7 | mini: {}, 8 | h5: { 9 | /** 10 | * WebpackChain 插件配置 11 | * @docs https://github.com/neutrinojs/webpack-chain 12 | */ 13 | // webpackChain (chain) { 14 | // /** 15 | // * 如果 h5 端编译后体积过大,可以使用 webpack-bundle-analyzer 插件对打包体积进行分析。 16 | // * @docs https://github.com/webpack-contrib/webpack-bundle-analyzer 17 | // */ 18 | // chain.plugin('analyzer') 19 | // .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, []) 20 | 21 | // /** 22 | // * 如果 h5 端首屏加载时间过长,可以使用 prerender-spa-plugin 插件预加载首页。 23 | // * @docs https://github.com/chrisvfritz/prerender-spa-plugin 24 | // */ 25 | // if (process.env.TARO_ENV === 'h5') { 26 | // const path = require('path') 27 | // const Prerender = require('prerender-spa-plugin') 28 | // const staticDir = path.join(__dirname, '..', 'dist') 29 | // chain 30 | // .plugin('prerender') 31 | // .use(new Prerender({ 32 | // staticDir, 33 | // routes: [ '/pages/index/index' ], 34 | // postProcess: (context) => ({ ...context, outputPath: path.join(staticDir, 'index.html') }) 35 | // })) 36 | // } 37 | // } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /demos/taro-demo/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "*.png"; 4 | declare module "*.gif"; 5 | declare module "*.jpg"; 6 | declare module "*.jpeg"; 7 | declare module "*.svg"; 8 | declare module "*.css"; 9 | declare module "*.less"; 10 | declare module "*.scss"; 11 | declare module "*.sass"; 12 | declare module "*.styl"; 13 | 14 | declare namespace JSX { 15 | interface IntrinsicElements { 16 | 'import': React.DetailedHTMLProps, HTMLEmbedElement> 17 | } 18 | } 19 | 20 | // @ts-ignore 21 | declare const process: { 22 | env: { 23 | TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt' | 'quickapp' | 'qq' | 'jd'; 24 | [key: string]: any; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /demos/taro-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taro-demo", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "react-interpreter 的 Taro Demo", 6 | "templateInfo": { 7 | "name": "mobx", 8 | "typescript": true, 9 | "css": "less" 10 | }, 11 | "scripts": { 12 | "build:weapp": "taro build --type weapp", 13 | "build:swan": "taro build --type swan", 14 | "build:alipay": "taro build --type alipay", 15 | "build:tt": "taro build --type tt", 16 | "build:h5": "taro build --type h5", 17 | "build:rn": "taro build --type rn", 18 | "build:qq": "taro build --type qq", 19 | "build:quickapp": "taro build --type quickapp", 20 | "dev:weapp": "npm run build:weapp -- --watch", 21 | "dev:swan": "npm run build:swan -- --watch", 22 | "dev:alipay": "npm run build:alipay -- --watch", 23 | "dev:tt": "npm run build:tt -- --watch", 24 | "dev:h5": "npm run build:h5 -- --watch", 25 | "dev:rn": "npm run build:rn -- --watch", 26 | "dev:qq": "npm run build:qq -- --watch", 27 | "dev:quickapp": "npm run build:quickapp -- --watch" 28 | }, 29 | "browserslist": [ 30 | "last 3 versions", 31 | "Android >= 4.1", 32 | "ios >= 8" 33 | ], 34 | "author": "", 35 | "license": "MIT", 36 | "dependencies": { 37 | "@babel/runtime": "^7.7.7", 38 | "@tarojs/cli": "3.4.1", 39 | "@tarojs/components": "3.4.1", 40 | "@tarojs/plugin-framework-react": "3.4.1", 41 | "@tarojs/react": "3.4.1", 42 | "@tarojs/runtime": "3.4.1", 43 | "@tarojs/taro": "3.4.1", 44 | "mobx": "^4.8.0", 45 | "mobx-react": "^6.1.4", 46 | "react": "^17.0.0", 47 | "react-dom": "^17.0.0" 48 | }, 49 | "devDependencies": { 50 | "@babel/core": "^7.8.0", 51 | "@tarojs/mini-runner": "3.4.1", 52 | "@tarojs/webpack-runner": "3.4.1", 53 | "@types/react": "^17.0.2", 54 | "@types/webpack-env": "^1.13.6", 55 | "@typescript-eslint/eslint-plugin": "^4.15.1", 56 | "@typescript-eslint/parser": "^4.15.1", 57 | "babel-preset-taro": "3.4.1", 58 | "eslint": "^6.8.0", 59 | "eslint-config-taro": "3.4.1", 60 | "eslint-plugin-import": "^2.12.0", 61 | "eslint-plugin-react": "^7.8.2", 62 | "eslint-plugin-react-hooks": "^4.2.0", 63 | "react-interpreter": "^0.3.0", 64 | "stylelint": "9.3.0", 65 | "typescript": "^4.1.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /demos/taro-demo/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "dist/", 3 | "projectname": "taro-demo", 4 | "description": "react-interpreter 的 Taro Demo", 5 | "appid": "touristappid", 6 | "setting": { 7 | "urlCheck": false, 8 | "es6": false, 9 | "enhance": false, 10 | "postcss": false, 11 | "preloadBackgroundData": false, 12 | "minified": false, 13 | "newFeature": false, 14 | "coverView": true, 15 | "nodeModules": false, 16 | "autoAudits": false, 17 | "showShadowRootInWxmlPanel": true, 18 | "scopeDataCheck": false, 19 | "uglifyFileName": false, 20 | "checkInvalidKey": true, 21 | "checkSiteMap": true, 22 | "uploadWithSourceMap": true, 23 | "compileHotReLoad": false, 24 | "lazyloadPlaceholderEnable": false, 25 | "useMultiFrameRuntime": true, 26 | "useApiHook": true, 27 | "useApiHostProcess": true, 28 | "babelSetting": { 29 | "ignore": [], 30 | "disablePlugins": [], 31 | "outputPath": "" 32 | }, 33 | "enableEngineNative": false, 34 | "useIsolateContext": false, 35 | "userConfirmedBundleSwitch": false, 36 | "packNpmManually": false, 37 | "packNpmRelationList": [], 38 | "minifyWXSS": true, 39 | "disableUseStrict": false, 40 | "minifyWXML": true, 41 | "showES6CompileOption": false, 42 | "useCompilerPlugins": false 43 | }, 44 | "compileType": "miniprogram", 45 | "libVersion": "2.22.0", 46 | "condition": {} 47 | } -------------------------------------------------------------------------------- /demos/taro-demo/project.tt.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "./", 3 | "projectname": "taro-demo", 4 | "description": "react-interpreter 的 Taro Demo", 5 | "appid": "touristappid", 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": false, 9 | "postcss": false, 10 | "minified": false 11 | }, 12 | "compileType": "miniprogram" 13 | } 14 | -------------------------------------------------------------------------------- /demos/taro-demo/src/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | pages: [ 3 | 'pages/index/index' 4 | ], 5 | window: { 6 | backgroundTextStyle: 'light', 7 | navigationBarBackgroundColor: '#fff', 8 | navigationBarTitleText: 'WeChat', 9 | navigationBarTextStyle: 'black' 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /demos/taro-demo/src/app.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangming/react-interpreter/e8c5b0bfc2b5dfa381afc242d9dfd4a3f351e588/demos/taro-demo/src/app.less -------------------------------------------------------------------------------- /demos/taro-demo/src/app.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'react' 2 | import './app.less' 3 | 4 | class App extends Component { 5 | componentDidMount() {} 6 | 7 | componentDidShow() {} 8 | 9 | componentDidHide() {} 10 | 11 | componentDidCatchError() {} 12 | 13 | // this.props.children 就是要渲染的页面 14 | render() { 15 | return this.props.children 16 | } 17 | } 18 | 19 | export default App 20 | -------------------------------------------------------------------------------- /demos/taro-demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /demos/taro-demo/src/pages/index/index.config.ts: -------------------------------------------------------------------------------- 1 | export default definePageConfig({ 2 | navigationBarTitleText: 'taro-demo' 3 | }) 4 | -------------------------------------------------------------------------------- /demos/taro-demo/src/pages/index/index.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangming/react-interpreter/e8c5b0bfc2b5dfa381afc242d9dfd4a3f351e588/demos/taro-demo/src/pages/index/index.less -------------------------------------------------------------------------------- /demos/taro-demo/src/pages/index/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactInterpreter } from 'react-interpreter' 2 | import Taro from '@tarojs/taro' 3 | import * as taroComps from '@tarojs/components' 4 | import { Component } from 'react' 5 | import './index.less' 6 | 7 | const codeString = ` 8 | function MyReactInterpreterComp() { 9 | return /*#__PURE__*/ React.createElement( 10 | View, 11 | { 12 | style: { 13 | backgroundColor: "pink", 14 | height: "100vh", 15 | display: "flex", 16 | alignItems: "center" 17 | } 18 | }, 19 | /*#__PURE__*/ React.createElement( 20 | Button, 21 | { 22 | style: { 23 | backgroundColor: "blue", 24 | color: "#FFFFFF" 25 | }, 26 | onClick: function onClick() { 27 | Taro.showToast({ 28 | icon: "none", 29 | title: "😂😂😂" 30 | }); 31 | } 32 | }, 33 | "Click Me!" 34 | ) 35 | ); 36 | } 37 | ` 38 | 39 | class Index extends Component { 40 | render() { 41 | return ( 42 | 49 | ) 50 | } 51 | } 52 | 53 | export default Index 54 | -------------------------------------------------------------------------------- /demos/taro-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "removeComments": false, 6 | "preserveConstEnums": true, 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | "outDir": "lib", 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "strictNullChecks": true, 15 | "sourceMap": true, 16 | "baseUrl": ".", 17 | "rootDir": ".", 18 | "jsx": "react-jsx", 19 | "allowJs": true, 20 | "resolveJsonModule": true, 21 | "typeRoots": [ 22 | "node_modules/@types", 23 | "global.d.ts" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "dist" 29 | ], 30 | "compileOnSave": false 31 | } 32 | -------------------------------------------------------------------------------- /docs/imgs/code-demo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangming/react-interpreter/e8c5b0bfc2b5dfa381afc242d9dfd4a3f351e588/docs/imgs/code-demo.jpeg -------------------------------------------------------------------------------- /docs/imgs/demo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangming/react-interpreter/e8c5b0bfc2b5dfa381afc242d9dfd4a3f351e588/docs/imgs/demo.jpeg -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-interpreter", 3 | "version": "0.3.0", 4 | "description": "react interpreter", 5 | "main": "lib/react-interpreter.js", 6 | "unpkg": "dist/react-interpreter.js", 7 | "module": "es/react-interpreter.js", 8 | "types": "types/index.d.ts", 9 | "files": [ 10 | "dist", 11 | "lib", 12 | "es", 13 | "src", 14 | "types" 15 | ], 16 | "scripts": { 17 | "test": "jest", 18 | "build": "rollup -c", 19 | "prepare-release": "npm run test && npm run build", 20 | "release": "npm run prepare-release && npm publish" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/wuchangming/react-interpreter.git" 25 | }, 26 | "author": "wuchangming", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/wuchangming/react-interpreter/issues" 30 | }, 31 | "homepage": "https://github.com/wuchangming/react-interpreter#readme", 32 | "devDependencies": { 33 | "@babel/core": "^7.17.4", 34 | "@babel/plugin-transform-runtime": "^7.17.0", 35 | "@rollup/plugin-babel": "^5.3.0", 36 | "@rollup/plugin-commonjs": "^21.0.1", 37 | "@rollup/plugin-node-resolve": "^13.1.3", 38 | "@rollup/plugin-replace": "^3.1.0", 39 | "@types/jest": "^27.4.0", 40 | "@types/react": "^17.0.39", 41 | "jest": "^27.5.1", 42 | "react": "^17.0.2", 43 | "rollup": "^2.67.2", 44 | "rollup-plugin-terser": "^7.0.2", 45 | "rollup-plugin-typescript2": "^0.31.2", 46 | "ts-jest": "^27.1.3", 47 | "typescript": "^4.5.5" 48 | }, 49 | "dependencies": { 50 | "@babel/runtime": "^7.17.2" 51 | }, 52 | "peerDependencies": { 53 | "react": "*" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { env } from 'process' 2 | import nodeResolve from '@rollup/plugin-node-resolve' 3 | import babel from '@rollup/plugin-babel' 4 | import replace from '@rollup/plugin-replace' 5 | import typescript from 'rollup-plugin-typescript2' 6 | import { terser } from 'rollup-plugin-terser' 7 | import commonjs from '@rollup/plugin-commonjs' 8 | 9 | import pkg from './package.json' 10 | 11 | const extensions = ['.ts', '.tsx', '.js'] 12 | const noDeclarationFiles = { compilerOptions: { declaration: false } } 13 | 14 | const babelRuntimeVersion = pkg.dependencies['@babel/runtime'].replace(/^[^0-9]*/, '') 15 | 16 | const makeExternalPredicate = (externalArr) => { 17 | if (externalArr.length === 0) { 18 | return () => false 19 | } 20 | const pattern = new RegExp(`^(${externalArr.join('|')})($|/)`) 21 | return (id) => pattern.test(id) 22 | } 23 | 24 | function getBanner(filename) { 25 | const date = new Date(env.SOURCE_DATE_EPOCH ? 1000 * +env.SOURCE_DATE_EPOCH : Date.now()).toUTCString() 26 | return `/* 27 | @license 28 | ${filename} v${pkg.version} 29 | ${date} 30 | @author ${pkg.author} 31 | https://github.com/wuchangming/react-interpreter 32 | Released under the MIT License. 33 | */` 34 | } 35 | 36 | export default async function () { 37 | return [ 38 | // CommonJS 39 | { 40 | input: 'src/index.ts', 41 | output: { 42 | banner: getBanner('react-interpreter.js'), 43 | file: 'lib/react-interpreter.js', 44 | format: 'cjs', 45 | indent: false, 46 | }, 47 | external: makeExternalPredicate([ 48 | ...Object.keys(pkg.dependencies || {}), 49 | ...Object.keys(pkg.peerDependencies || {}), 50 | ]), 51 | plugins: [ 52 | commonjs(), 53 | nodeResolve({ 54 | extensions, 55 | }), 56 | typescript({ useTsconfigDeclarationDir: true }), 57 | babel({ 58 | extensions, 59 | plugins: [['@babel/plugin-transform-runtime', { version: babelRuntimeVersion }]], 60 | babelHelpers: 'runtime', 61 | }), 62 | ], 63 | }, 64 | 65 | // ES 66 | { 67 | input: 'src/index.ts', 68 | output: { 69 | banner: getBanner('react-interpreter.js'), 70 | file: 'es/react-interpreter.js', 71 | format: 'es', 72 | indent: false, 73 | }, 74 | external: makeExternalPredicate([ 75 | ...Object.keys(pkg.dependencies || {}), 76 | ...Object.keys(pkg.peerDependencies || {}), 77 | ]), 78 | plugins: [ 79 | commonjs(), 80 | nodeResolve({ 81 | extensions, 82 | }), 83 | typescript({ tsconfigOverride: noDeclarationFiles }), 84 | babel({ 85 | extensions, 86 | plugins: [ 87 | ['@babel/plugin-transform-runtime', { version: babelRuntimeVersion, useESModules: true }], 88 | ], 89 | babelHelpers: 'runtime', 90 | }), 91 | ], 92 | }, 93 | 94 | // // ES for Browsers 95 | { 96 | input: 'src/index.ts', 97 | output: { 98 | banner: getBanner('react-interpreter.mjs'), 99 | file: 'es/react-interpreter.mjs', 100 | format: 'es', 101 | indent: false, 102 | }, 103 | external: makeExternalPredicate([ 104 | ...Object.keys(pkg.dependencies || {}), 105 | ...Object.keys(pkg.peerDependencies || {}), 106 | ]), 107 | plugins: [ 108 | commonjs(), 109 | nodeResolve({ 110 | extensions, 111 | }), 112 | replace({ 113 | preventAssignment: true, 114 | 'process.env.NODE_ENV': JSON.stringify('production'), 115 | }), 116 | typescript({ tsconfigOverride: noDeclarationFiles }), 117 | babel({ 118 | extensions, 119 | exclude: 'node_modules/**', 120 | skipPreflightCheck: true, 121 | babelHelpers: 'bundled', 122 | }), 123 | terser({ 124 | compress: { 125 | pure_getters: true, 126 | unsafe: true, 127 | unsafe_comps: true, 128 | warnings: false, 129 | }, 130 | }), 131 | ], 132 | }, 133 | 134 | // // UMD Development 135 | { 136 | input: 'src/index.ts', 137 | output: { 138 | banner: getBanner('react-interpreter.js'), 139 | file: 'dist/react-interpreter.js', 140 | format: 'umd', 141 | name: 'ReactInterpreter', 142 | indent: false, 143 | globals: { 144 | react: 'React', 145 | }, 146 | }, 147 | external: makeExternalPredicate([ 148 | ...Object.keys(pkg.dependencies || {}), 149 | ...Object.keys(pkg.peerDependencies || {}), 150 | ]), 151 | plugins: [ 152 | commonjs(), 153 | nodeResolve({ 154 | extensions, 155 | }), 156 | typescript({ tsconfigOverride: noDeclarationFiles }), 157 | babel({ 158 | extensions, 159 | exclude: 'node_modules/**', 160 | babelHelpers: 'bundled', 161 | }), 162 | replace({ 163 | preventAssignment: true, 164 | 'process.env.NODE_ENV': JSON.stringify('development'), 165 | }), 166 | ], 167 | }, 168 | 169 | // // UMD Production 170 | { 171 | input: 'src/index.ts', 172 | output: { 173 | banner: getBanner('react-interpreter.min.js'), 174 | file: 'dist/react-interpreter.min.js', 175 | format: 'umd', 176 | name: 'ReactInterpreter', 177 | indent: false, 178 | globals: { 179 | react: 'React', 180 | }, 181 | }, 182 | external: makeExternalPredicate([ 183 | ...Object.keys(pkg.dependencies || {}), 184 | ...Object.keys(pkg.peerDependencies || {}), 185 | ]), 186 | plugins: [ 187 | commonjs(), 188 | nodeResolve({ 189 | extensions, 190 | }), 191 | typescript({ tsconfigOverride: noDeclarationFiles }), 192 | babel({ 193 | extensions, 194 | exclude: 'node_modules/**', 195 | skipPreflightCheck: true, 196 | babelHelpers: 'bundled', 197 | }), 198 | replace({ 199 | preventAssignment: true, 200 | 'process.env.NODE_ENV': JSON.stringify('production'), 201 | }), 202 | terser({ 203 | compress: { 204 | pure_getters: true, 205 | unsafe: true, 206 | unsafe_comps: true, 207 | warnings: false, 208 | }, 209 | }), 210 | ], 211 | }, 212 | ] 213 | } 214 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import JSInterpreter from './js-interpreter/interpreter' 2 | import { ReactInterpreter } from './react-interpreter/ReactInterpreter' 3 | 4 | export { JSInterpreter, ReactInterpreter } 5 | -------------------------------------------------------------------------------- /src/js-interpreter/acorn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 该文件从 https://github.com/NeilFraser/JS-Interpreter 拷贝过来 3 | * 为什么不直接用 npm 最新版本的 acorn ?因为那个体积比较大,而且这个版本好像也没什么问题 4 | * 5 | */ 6 | /* eslint-disable no-shadow */ 7 | /* eslint-disable no-undef */ 8 | // Acorn is a tiny, fast JavaScript parser written in JavaScript. 9 | // 10 | // Acorn was written by Marijn Haverbeke and released under an MIT 11 | // license. The Unicode regexps (for identifiers and whitespace) were 12 | // taken from [Esprima](http://esprima.org) by Ariya Hidayat. 13 | // 14 | // Git repositories for Acorn are available at 15 | // 16 | // http://marijnhaverbeke.nl/git/acorn 17 | // https://github.com/marijnh/acorn.git 18 | // 19 | // Please use the [github bug tracker][ghbt] to report issues. 20 | // 21 | // [ghbt]: https://github.com/marijnh/acorn/issues 22 | // 23 | // This file defines the main parser interface. The library also comes 24 | // with a [error-tolerant parser][dammit] and an 25 | // [abstract syntax tree walker][walk], defined in other files. 26 | // 27 | // [dammit]: acorn_loose.js 28 | // [walk]: util/walk.js 29 | 30 | (function(root, mod) { 31 | if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS 32 | if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD 33 | mod(root.acorn || (root.acorn = {})); // Plain browser env 34 | })(this, function(exports) { 35 | "use strict"; 36 | 37 | exports.version = "0.4.1"; 38 | 39 | // The main exported interface (under `self.acorn` when in the 40 | // browser) is a `parse` function that takes a code string and 41 | // returns an abstract syntax tree as specified by [Mozilla parser 42 | // API][api], with the caveat that the SpiderMonkey-specific syntax 43 | // (`let`, `yield`, inline XML, etc) is not recognized. 44 | // 45 | // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API 46 | 47 | var options, input, inputLen, sourceFile; 48 | 49 | exports.parse = function(inpt, opts) { 50 | input = String(inpt); inputLen = input.length; 51 | setOptions(opts); 52 | initTokenState(); 53 | return parseTopLevel(options.program); 54 | }; 55 | 56 | // A second optional argument can be given to further configure 57 | // the parser process. These options are recognized: 58 | 59 | var defaultOptions = exports.defaultOptions = { 60 | // `ecmaVersion` indicates the ECMAScript version to parse. Must 61 | // be either 3 or 5. This 62 | // influences support for strict mode, the set of reserved words, and 63 | // support for getters and setter. 64 | ecmaVersion: 5, 65 | // Turn on `strictSemicolons` to prevent the parser from doing 66 | // automatic semicolon insertion. 67 | strictSemicolons: false, 68 | // When `allowTrailingCommas` is false, the parser will not allow 69 | // trailing commas in array and object literals. 70 | allowTrailingCommas: true, 71 | // By default, reserved words are not enforced. Enable 72 | // `forbidReserved` to enforce them. 73 | forbidReserved: false, 74 | // When `locations` is on, `loc` properties holding objects with 75 | // `start` and `end` properties in `{line, column}` form (with 76 | // line being 1-based and column 0-based) will be attached to the 77 | // nodes. 78 | locations: false, 79 | // A function can be passed as `onComment` option, which will 80 | // cause Acorn to call that function with `(block, text, start, 81 | // end)` parameters whenever a comment is skipped. `block` is a 82 | // boolean indicating whether this is a block (`/* */`) comment, 83 | // `text` is the content of the comment, and `start` and `end` are 84 | // character offsets that denote the start and end of the comment. 85 | // When the `locations` option is on, two more parameters are 86 | // passed, the full `{line, column}` locations of the start and 87 | // end of the comments. 88 | onComment: null, 89 | // Nodes have their start and end characters offsets recorded in 90 | // `start` and `end` properties (directly on the node, rather than 91 | // the `loc` object, which holds line/column data. To also add a 92 | // [semi-standardized][range] `range` property holding a `[start, 93 | // end]` array with the same numbers, set the `ranges` option to 94 | // `true`. 95 | // 96 | // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 97 | ranges: false, 98 | // It is possible to parse multiple files into a single AST by 99 | // passing the tree produced by parsing the first file as 100 | // `program` option in subsequent parses. This will add the 101 | // toplevel forms of the parsed file to the `Program` (top) node 102 | // of an existing parse tree. 103 | program: null, 104 | // When `location` is on, you can pass this to record the source 105 | // file in every node's `loc` object. 106 | sourceFile: null, 107 | // This value, if given, is stored in every node, whether 108 | // `location` is on or off. 109 | directSourceFile: null 110 | }; 111 | 112 | function setOptions(opts) { 113 | options = opts || {}; 114 | for (var opt in defaultOptions) if (!Object.prototype.hasOwnProperty.call(options, opt)) 115 | options[opt] = defaultOptions[opt]; 116 | sourceFile = options.sourceFile || null; 117 | } 118 | 119 | // The `getLineInfo` function is mostly useful when the 120 | // `locations` option is off (for performance reasons) and you 121 | // want to find the line/column position for a given character 122 | // offset. `input` should be the code string that the offset refers 123 | // into. 124 | 125 | var getLineInfo = exports.getLineInfo = function(input, offset) { 126 | for (var line = 1, cur = 0;;) { 127 | lineBreak.lastIndex = cur; 128 | var match = lineBreak.exec(input); 129 | if (match && match.index < offset) { 130 | ++line; 131 | cur = match.index + match[0].length; 132 | } else break; 133 | } 134 | return {line: line, column: offset - cur}; 135 | }; 136 | 137 | // Acorn is organized as a tokenizer and a recursive-descent parser. 138 | // The `tokenize` export provides an interface to the tokenizer. 139 | // Because the tokenizer is optimized for being efficiently used by 140 | // the Acorn parser itself, this interface is somewhat crude and not 141 | // very modular. Performing another parse or call to `tokenize` will 142 | // reset the internal state, and invalidate existing tokenizers. 143 | 144 | exports.tokenize = function(inpt, opts) { 145 | input = String(inpt); inputLen = input.length; 146 | setOptions(opts); 147 | initTokenState(); 148 | 149 | var t = {}; 150 | function getToken(forceRegexp) { 151 | readToken(forceRegexp); 152 | t.start = tokStart; t.end = tokEnd; 153 | t.startLoc = tokStartLoc; t.endLoc = tokEndLoc; 154 | t.type = tokType; t.value = tokVal; 155 | return t; 156 | } 157 | getToken.jumpTo = function(pos, reAllowed) { 158 | tokPos = pos; 159 | if (options.locations) { 160 | tokCurLine = 1; 161 | tokLineStart = lineBreak.lastIndex = 0; 162 | var match; 163 | while ((match = lineBreak.exec(input)) && match.index < pos) { 164 | ++tokCurLine; 165 | tokLineStart = match.index + match[0].length; 166 | } 167 | } 168 | tokRegexpAllowed = reAllowed; 169 | skipSpace(); 170 | }; 171 | return getToken; 172 | }; 173 | 174 | // State is kept in (closure-)global variables. We already saw the 175 | // `options`, `input`, and `inputLen` variables above. 176 | 177 | // The current position of the tokenizer in the input. 178 | 179 | var tokPos; 180 | 181 | // The start and end offsets of the current token. 182 | 183 | var tokStart, tokEnd; 184 | 185 | // When `options.locations` is true, these hold objects 186 | // containing the tokens start and end line/column pairs. 187 | 188 | var tokStartLoc, tokEndLoc; 189 | 190 | // The type and value of the current token. Token types are objects, 191 | // named by variables against which they can be compared, and 192 | // holding properties that describe them (indicating, for example, 193 | // the precedence of an infix operator, and the original name of a 194 | // keyword token). The kind of value that's held in `tokVal` depends 195 | // on the type of the token. For literals, it is the literal value, 196 | // for operators, the operator name, and so on. 197 | 198 | var tokType, tokVal; 199 | 200 | // Interal state for the tokenizer. To distinguish between division 201 | // operators and regular expressions, it remembers whether the last 202 | // token was one that is allowed to be followed by an expression. 203 | // (If it is, a slash is probably a regexp, if it isn't it's a 204 | // division operator. See the `parseStatement` function for a 205 | // caveat.) 206 | 207 | var tokRegexpAllowed; 208 | 209 | // When `options.locations` is true, these are used to keep 210 | // track of the current line, and know when a new line has been 211 | // entered. 212 | 213 | var tokCurLine, tokLineStart; 214 | 215 | // These store the position of the previous token, which is useful 216 | // when finishing a node and assigning its `end` position. 217 | 218 | var lastStart, lastEnd, lastEndLoc; 219 | 220 | // This is the parser's state. `inFunction` is used to reject 221 | // `return` statements outside of functions, `labels` to verify that 222 | // `break` and `continue` have somewhere to jump to, and `strict` 223 | // indicates whether strict mode is on. 224 | 225 | var inFunction, labels, strict; 226 | 227 | // This function is used to raise exceptions on parse errors. It 228 | // takes an offset integer (into the current `input`) to indicate 229 | // the location of the error, attaches the position to the end 230 | // of the error message, and then raises a `SyntaxError` with that 231 | // message. 232 | 233 | function raise(pos, message) { 234 | var loc = getLineInfo(input, pos); 235 | message += " (" + loc.line + ":" + loc.column + ")"; 236 | var err = new SyntaxError(message); 237 | err.pos = pos; err.loc = loc; err.raisedAt = tokPos; 238 | throw err; 239 | } 240 | 241 | // Reused empty array added for node fields that are always empty. 242 | 243 | var empty = []; 244 | 245 | // ## Token types 246 | 247 | // The assignment of fine-grained, information-carrying type objects 248 | // allows the tokenizer to store the information it has about a 249 | // token in a way that is very cheap for the parser to look up. 250 | 251 | // All token type variables start with an underscore, to make them 252 | // easy to recognize. 253 | 254 | // These are the general types. The `type` property is only used to 255 | // make them recognizeable when debugging. 256 | 257 | var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"}; 258 | var _name = {type: "name"}, _eof = {type: "eof"}; 259 | 260 | // Keyword tokens. The `keyword` property (also used in keyword-like 261 | // operators) indicates that the token originated from an 262 | // identifier-like word, which is used when parsing property names. 263 | // 264 | // The `beforeExpr` property is used to disambiguate between regular 265 | // expressions and divisions. It is set on all token types that can 266 | // be followed by an expression (thus, a slash after them would be a 267 | // regular expression). 268 | // 269 | // `isLoop` marks a keyword as starting a loop, which is important 270 | // to know when parsing a label, in order to allow or disallow 271 | // continue jumps to that label. 272 | 273 | var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"}; 274 | var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"}; 275 | var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true}; 276 | var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"}; 277 | var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"}; 278 | var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"}; 279 | var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true}; 280 | var _this = {keyword: "this"}; 281 | 282 | // The keywords that denote values. 283 | 284 | var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true}; 285 | var _false = {keyword: "false", atomValue: false}; 286 | 287 | // Some keywords are treated as regular operators. `in` sometimes 288 | // (when parsing `for`) needs to be tested against specifically, so 289 | // we assign a variable name to it for quick comparing. 290 | 291 | var _in = {keyword: "in", binop: 7, beforeExpr: true}; 292 | 293 | // Map keyword names to token types. 294 | 295 | var keywordTypes = {"break": _break, "case": _case, "catch": _catch, 296 | "continue": _continue, "debugger": _debugger, "default": _default, 297 | "do": _do, "else": _else, "finally": _finally, "for": _for, 298 | "function": _function, "if": _if, "return": _return, "switch": _switch, 299 | "throw": _throw, "try": _try, "var": _var, "while": _while, "with": _with, 300 | "null": _null, "true": _true, "false": _false, "new": _new, "in": _in, 301 | "instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this, 302 | "typeof": {keyword: "typeof", prefix: true, beforeExpr: true}, 303 | "void": {keyword: "void", prefix: true, beforeExpr: true}, 304 | "delete": {keyword: "delete", prefix: true, beforeExpr: true}}; 305 | 306 | // Punctuation token types. Again, the `type` property is purely for debugging. 307 | 308 | var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true}; 309 | var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"}; 310 | var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; 311 | var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true}; 312 | 313 | // Operators. These carry several kinds of properties to help the 314 | // parser use them properly (the presence of these properties is 315 | // what categorizes them as operators). 316 | // 317 | // `binop`, when present, specifies that this operator is a binary 318 | // operator, and will refer to its precedence. 319 | // 320 | // `prefix` and `postfix` mark the operator as a prefix or postfix 321 | // unary operator. `isUpdate` specifies that the node produced by 322 | // the operator should be of type UpdateExpression rather than 323 | // simply UnaryExpression (`++` and `--`). 324 | // 325 | // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as 326 | // binary operators with a very low precedence, that should result 327 | // in AssignmentExpression nodes. 328 | 329 | var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true}; 330 | var _assign = {isAssign: true, beforeExpr: true}; 331 | var _incDec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true}; 332 | var _logicalOR = {binop: 1, beforeExpr: true}; 333 | var _logicalAND = {binop: 2, beforeExpr: true}; 334 | var _bitwiseOR = {binop: 3, beforeExpr: true}; 335 | var _bitwiseXOR = {binop: 4, beforeExpr: true}; 336 | var _bitwiseAND = {binop: 5, beforeExpr: true}; 337 | var _equality = {binop: 6, beforeExpr: true}; 338 | var _relational = {binop: 7, beforeExpr: true}; 339 | var _bitShift = {binop: 8, beforeExpr: true}; 340 | var _plusMin = {binop: 9, prefix: true, beforeExpr: true}; 341 | var _multiplyModulo = {binop: 10, beforeExpr: true}; 342 | 343 | // Provide access to the token types for external users of the 344 | // tokenizer. 345 | 346 | exports.tokTypes = {bracketL: _bracketL, bracketR: _bracketR, braceL: _braceL, braceR: _braceR, 347 | parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon, 348 | dot: _dot, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof, 349 | num: _num, regexp: _regexp, string: _string}; 350 | for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw]; 351 | 352 | // Acorn's original code built up functions using strings for maximum efficiency. 353 | // However, this triggered a CSP unsafe-eval requirement. Here's a slower, but 354 | // simpler approach. -- Neil Fraser, January 2022. 355 | // https://github.com/NeilFraser/JS-Interpreter/issues/228 356 | function makePredicate(words) { 357 | words = words.split(" "); 358 | var set = Object.create(null); 359 | for (var i = 0; i < words.length; i++) { 360 | set[words[i]] = true; 361 | } 362 | return function(str) { 363 | return set[str] || false; 364 | }; 365 | } 366 | 367 | // The ECMAScript 3 reserved word list. 368 | 369 | var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile"); 370 | 371 | // ECMAScript 5 reserved words. 372 | 373 | var isReservedWord5 = makePredicate("class enum extends super const export import"); 374 | 375 | // The additional reserved words in strict mode. 376 | 377 | var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield"); 378 | 379 | // The forbidden variable names in strict mode. 380 | 381 | var isStrictBadIdWord = makePredicate("eval arguments"); 382 | 383 | // And the keywords. 384 | 385 | var isKeyword = makePredicate("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"); 386 | 387 | // ## Character categories 388 | 389 | // Big ugly regular expressions that match characters in the 390 | // whitespace, identifier, and identifier-start categories. These 391 | // are only applied when a character is found to actually have a 392 | // code point above 128. 393 | 394 | var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/; 395 | var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"; 396 | var nonASCIIidentifierChars = "\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f"; 397 | var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); 398 | var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); 399 | 400 | // Whether a single character denotes a newline. 401 | 402 | var newline = /[\n\r\u2028\u2029]/; 403 | 404 | // Matches a whole line break (where CRLF is considered a single 405 | // line break). Used to count lines. 406 | 407 | var lineBreak = /\r\n|[\n\r\u2028\u2029]/g; 408 | 409 | // Test whether a given character code starts an identifier. 410 | 411 | var isIdentifierStart = exports.isIdentifierStart = function(code) { 412 | if (code < 65) return code === 36; 413 | if (code < 91) return true; 414 | if (code < 97) return code === 95; 415 | if (code < 123)return true; 416 | return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)); 417 | }; 418 | 419 | // Test whether a given character is part of an identifier. 420 | 421 | var isIdentifierChar = exports.isIdentifierChar = function(code) { 422 | if (code < 48) return code === 36; 423 | if (code < 58) return true; 424 | if (code < 65) return false; 425 | if (code < 91) return true; 426 | if (code < 97) return code === 95; 427 | if (code < 123)return true; 428 | return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); 429 | }; 430 | 431 | // ## Tokenizer 432 | 433 | // These are used when `options.locations` is on, for the 434 | // `tokStartLoc` and `tokEndLoc` properties. 435 | 436 | function line_loc_t() { 437 | this.line = tokCurLine; 438 | this.column = tokPos - tokLineStart; 439 | } 440 | 441 | // Reset the token state. Used at the start of a parse. 442 | 443 | function initTokenState() { 444 | tokCurLine = 1; 445 | tokPos = tokLineStart = 0; 446 | tokRegexpAllowed = true; 447 | skipSpace(); 448 | } 449 | 450 | // Called at the end of every token. Sets `tokEnd`, `tokVal`, and 451 | // `tokRegexpAllowed`, and skips the space after the token, so that 452 | // the next one's `tokStart` will point at the right position. 453 | 454 | function finishToken(type, val) { 455 | tokEnd = tokPos; 456 | if (options.locations) tokEndLoc = new line_loc_t; 457 | tokType = type; 458 | skipSpace(); 459 | tokVal = val; 460 | tokRegexpAllowed = type.beforeExpr; 461 | } 462 | 463 | function skipBlockComment() { 464 | var startLoc = options.onComment && options.locations && new line_loc_t; 465 | var start = tokPos, end = input.indexOf("*/", tokPos += 2); 466 | if (end === -1) raise(tokPos - 2, "Unterminated comment"); 467 | tokPos = end + 2; 468 | if (options.locations) { 469 | lineBreak.lastIndex = start; 470 | var match; 471 | while ((match = lineBreak.exec(input)) && match.index < tokPos) { 472 | ++tokCurLine; 473 | tokLineStart = match.index + match[0].length; 474 | } 475 | } 476 | if (options.onComment) 477 | options.onComment(true, input.slice(start + 2, end), start, tokPos, 478 | startLoc, options.locations && new line_loc_t); 479 | } 480 | 481 | function skipLineComment() { 482 | var start = tokPos; 483 | var startLoc = options.onComment && options.locations && new line_loc_t; 484 | var ch = input.charCodeAt(tokPos+=2); 485 | while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) { 486 | ++tokPos; 487 | ch = input.charCodeAt(tokPos); 488 | } 489 | if (options.onComment) 490 | options.onComment(false, input.slice(start + 2, tokPos), start, tokPos, 491 | startLoc, options.locations && new line_loc_t); 492 | } 493 | 494 | // Called at the start of the parse and after every token. Skips 495 | // whitespace and comments, and. 496 | 497 | function skipSpace() { 498 | while (tokPos < inputLen) { 499 | var ch = input.charCodeAt(tokPos); 500 | if (ch === 32) { // ' ' 501 | ++tokPos; 502 | } else if (ch === 13) { 503 | ++tokPos; 504 | var next = input.charCodeAt(tokPos); 505 | if (next === 10) { 506 | ++tokPos; 507 | } 508 | if (options.locations) { 509 | ++tokCurLine; 510 | tokLineStart = tokPos; 511 | } 512 | } else if (ch === 10 || ch === 8232 || ch === 8233) { 513 | ++tokPos; 514 | if (options.locations) { 515 | ++tokCurLine; 516 | tokLineStart = tokPos; 517 | } 518 | } else if (ch > 8 && ch < 14) { 519 | ++tokPos; 520 | } else if (ch === 47) { // '/' 521 | var next = input.charCodeAt(tokPos + 1); 522 | if (next === 42) { // '*' 523 | skipBlockComment(); 524 | } else if (next === 47) { // '/' 525 | skipLineComment(); 526 | } else break; 527 | } else if (ch === 160) { // '\xa0' 528 | ++tokPos; 529 | } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) { 530 | ++tokPos; 531 | } else { 532 | break; 533 | } 534 | } 535 | } 536 | 537 | // ### Token reading 538 | 539 | // This is the function that is called to fetch the next token. It 540 | // is somewhat obscure, because it works in character codes rather 541 | // than characters, and because operator parsing has been inlined 542 | // into it. 543 | // 544 | // All in the name of speed. 545 | // 546 | // The `forceRegexp` parameter is used in the one case where the 547 | // `tokRegexpAllowed` trick does not work. See `parseStatement`. 548 | 549 | function readToken_dot() { 550 | var next = input.charCodeAt(tokPos + 1); 551 | if (next >= 48 && next <= 57) return readNumber(true); 552 | ++tokPos; 553 | return finishToken(_dot); 554 | } 555 | 556 | function readToken_slash() { // '/' 557 | var next = input.charCodeAt(tokPos + 1); 558 | if (tokRegexpAllowed) {++tokPos; return readRegexp();} 559 | if (next === 61) return finishOp(_assign, 2); 560 | return finishOp(_slash, 1); 561 | } 562 | 563 | function readToken_mult_modulo() { // '%*' 564 | var next = input.charCodeAt(tokPos + 1); 565 | if (next === 61) return finishOp(_assign, 2); 566 | return finishOp(_multiplyModulo, 1); 567 | } 568 | 569 | function readToken_pipe_amp(code) { // '|&' 570 | var next = input.charCodeAt(tokPos + 1); 571 | if (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2); 572 | if (next === 61) return finishOp(_assign, 2); 573 | return finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1); 574 | } 575 | 576 | function readToken_caret() { // '^' 577 | var next = input.charCodeAt(tokPos + 1); 578 | if (next === 61) return finishOp(_assign, 2); 579 | return finishOp(_bitwiseXOR, 1); 580 | } 581 | 582 | function readToken_plus_min(code) { // '+-' 583 | var next = input.charCodeAt(tokPos + 1); 584 | if (next === code) { 585 | if (next == 45 && input.charCodeAt(tokPos + 2) == 62 && 586 | newline.test(input.slice(lastEnd, tokPos))) { 587 | // A `-->` line comment 588 | tokPos += 3; 589 | skipLineComment(); 590 | skipSpace(); 591 | return readToken(); 592 | } 593 | return finishOp(_incDec, 2); 594 | } 595 | if (next === 61) return finishOp(_assign, 2); 596 | return finishOp(_plusMin, 1); 597 | } 598 | 599 | function readToken_lt_gt(code) { // '<>' 600 | var next = input.charCodeAt(tokPos + 1); 601 | var size = 1; 602 | if (next === code) { 603 | size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2; 604 | if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1); 605 | return finishOp(_bitShift, size); 606 | } 607 | if (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 && 608 | input.charCodeAt(tokPos + 3) == 45) { 609 | // `