├── .editorconfig ├── .github ├── FUNDING.yml ├── actions │ ├── linux-alpine-node-20 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-alpine-node-22 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-alpine-node-23 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-alpine-node-24 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-arm64-alpine-node-20 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-arm64-alpine-node-22 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-arm64-alpine-node-23 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-arm64-alpine-node-24 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-arm64-node-20 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-arm64-node-22 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-arm64-node-23 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-arm64-node-24 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-node-20 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-node-22 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ ├── linux-node-23 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh │ └── linux-node-24 │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh ├── dependabot.yml └── workflows │ ├── build.yml │ └── tests.yml ├── .gitignore ├── .gitmodules ├── .prettierrc ├── .vscode ├── c_cpp_properties.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── binding.gyp ├── lib ├── accessors.cc ├── addon.cc ├── exec.cc ├── match.cc ├── new.cc ├── replace.cc ├── search.cc ├── split.cc ├── test.cc ├── to_string.cc ├── util.cc ├── util.h └── wrapped_re2.h ├── package-lock.json ├── package.json ├── re2.d.ts ├── re2.js ├── scripts └── verify-build.js ├── tests ├── manual │ ├── matchall-bench.js │ ├── memory-check.js │ ├── memory-monitor.js │ └── worker.js ├── test_exec.js ├── test_general.js ├── test_groups.js ├── test_invalid.js ├── test_match.js ├── test_matchAll.js ├── test_new.js ├── test_prototype.js ├── test_replace.js ├── test_search.js ├── test_source.js ├── test_split.js ├── test_symbols.js ├── test_test.js ├── test_toString.js ├── test_unicode_classes.js └── tests.js ├── ts-tests └── test-types.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [*.{h,cc,cpp}] 12 | indent_style = tab 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: uhop 2 | buy_me_a_coffee: uhop 3 | -------------------------------------------------------------------------------- /.github/actions/linux-alpine-node-20/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine 2 | 3 | RUN apk add --no-cache python3 make gcc g++ linux-headers 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-alpine-node-20/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 20 on Alpine Linux' 2 | description: 'Create a binary artifact for Node 20 on Alpine Linux using musl' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | args: 7 | - ${{inputs.node-version}} 8 | -------------------------------------------------------------------------------- /.github/actions/linux-alpine-node-20/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-alpine-node-22/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-alpine 2 | 3 | RUN apk add --no-cache python3 make gcc g++ linux-headers 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-alpine-node-22/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 22 on Alpine Linux' 2 | description: 'Create a binary artifact for Node 22 on Alpine Linux using musl' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | args: 7 | - ${{inputs.node-version}} 8 | -------------------------------------------------------------------------------- /.github/actions/linux-alpine-node-22/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-alpine-node-23/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:23-alpine 2 | 3 | RUN apk add --no-cache python3 make gcc g++ linux-headers 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-alpine-node-23/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 23 on Alpine Linux' 2 | description: 'Create a binary artifact for Node 23 on Alpine Linux using musl' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | args: 7 | - ${{inputs.node-version}} 8 | -------------------------------------------------------------------------------- /.github/actions/linux-alpine-node-23/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-alpine-node-24/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:24-alpine 2 | 3 | RUN apk add --no-cache python3 make gcc g++ linux-headers 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-alpine-node-24/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 24 on Alpine Linux' 2 | description: 'Create a binary artifact for Node 24 on Alpine Linux using musl' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | args: 7 | - ${{inputs.node-version}} 8 | -------------------------------------------------------------------------------- /.github/actions/linux-alpine-node-24/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-alpine-node-20/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/arm64 node:20-alpine 2 | 3 | RUN apk add --no-cache python3 make gcc g++ linux-headers 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-alpine-node-20/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 20 on Alpine Linux' 2 | description: 'Create a binary artifact for Node 20 on Alpine Linux using musl' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | args: 7 | - ${{inputs.node-version}} 8 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-alpine-node-20/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-alpine-node-22/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/arm64 node:22-alpine 2 | 3 | RUN apk add --no-cache python3 make gcc g++ linux-headers 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-alpine-node-22/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 22 on Alpine Linux' 2 | description: 'Create a binary artifact for Node 22 on Alpine Linux using musl' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | args: 7 | - ${{inputs.node-version}} 8 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-alpine-node-22/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-alpine-node-23/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/arm64 node:23-alpine 2 | 3 | RUN apk add --no-cache python3 make gcc g++ linux-headers 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-alpine-node-23/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 23 on Alpine Linux' 2 | description: 'Create a binary artifact for Node 23 on Alpine Linux using musl' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | args: 7 | - ${{inputs.node-version}} 8 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-alpine-node-23/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-alpine-node-24/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/arm64 node:24-alpine 2 | 3 | RUN apk add --no-cache python3 make gcc g++ linux-headers 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-alpine-node-24/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 24 on Alpine Linux' 2 | description: 'Create a binary artifact for Node 24 on Alpine Linux using musl' 3 | runs: 4 | using: 'docker' 5 | image: 'Dockerfile' 6 | args: 7 | - ${{inputs.node-version}} 8 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-alpine-node-24/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-node-20/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/arm64 node:20-bullseye 2 | 3 | RUN apt install python3 make gcc g++ 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-node-20/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 20 on Debian Bullseye Linux on ARM64' 2 | description: 'Create a binary artifact for Node 20 on Debian Bullseye Linux on ARM64' 3 | inputs: 4 | node-version: 5 | description: 'Node.js version' 6 | required: false 7 | default: '20' 8 | runs: 9 | using: 'docker' 10 | image: 'Dockerfile' 11 | args: 12 | - ${{inputs.node-version}} 13 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-node-20/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-node-22/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/arm64 node:22-bullseye 2 | 3 | RUN apt install python3 make gcc g++ 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-node-22/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 22 on Debian Bullseye Linux on ARM64' 2 | description: 'Create a binary artifact for Node 22 on Debian Bullseye Linux on ARM64' 3 | inputs: 4 | node-version: 5 | description: 'Node.js version' 6 | required: false 7 | default: '22' 8 | runs: 9 | using: 'docker' 10 | image: 'Dockerfile' 11 | args: 12 | - ${{inputs.node-version}} 13 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-node-22/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-node-23/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/arm64 node:23-bullseye 2 | 3 | RUN apt install python3 make gcc g++ 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-node-23/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 23 on Debian Bullseye Linux on ARM64' 2 | description: 'Create a binary artifact for Node 23 on Debian Bullseye Linux on ARM64' 3 | inputs: 4 | node-version: 5 | description: 'Node.js version' 6 | required: false 7 | default: '23' 8 | runs: 9 | using: 'docker' 10 | image: 'Dockerfile' 11 | args: 12 | - ${{inputs.node-version}} 13 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-node-23/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-node-24/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/arm64 node:24-bullseye 2 | 3 | RUN apt install python3 make gcc g++ 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-node-24/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 24 on Debian Bullseye Linux on ARM64' 2 | description: 'Create a binary artifact for Node 24 on Debian Bullseye Linux on ARM64' 3 | inputs: 4 | node-version: 5 | description: 'Node.js version' 6 | required: false 7 | default: '24' 8 | runs: 9 | using: 'docker' 10 | image: 'Dockerfile' 11 | args: 12 | - ${{inputs.node-version}} 13 | -------------------------------------------------------------------------------- /.github/actions/linux-arm64-node-24/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-node-20/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-bullseye 2 | 3 | RUN apt install python3 make gcc g++ 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-node-20/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 20 on Debian Bullseye Linux' 2 | description: 'Create a binary artifact for Node 20 on Debian Bullseye Linux' 3 | inputs: 4 | node-version: 5 | description: 'Node.js version' 6 | required: false 7 | default: '20' 8 | runs: 9 | using: 'docker' 10 | image: 'Dockerfile' 11 | args: 12 | - ${{inputs.node-version}} 13 | -------------------------------------------------------------------------------- /.github/actions/linux-node-20/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-node-22/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-bullseye 2 | 3 | RUN apt install python3 make gcc g++ 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-node-22/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 22 on Debian Bullseye Linux' 2 | description: 'Create a binary artifact for Node 22 on Debian Bullseye Linux' 3 | inputs: 4 | node-version: 5 | description: 'Node.js version' 6 | required: false 7 | default: '22' 8 | runs: 9 | using: 'docker' 10 | image: 'Dockerfile' 11 | args: 12 | - ${{inputs.node-version}} 13 | -------------------------------------------------------------------------------- /.github/actions/linux-node-22/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-node-23/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:23-bullseye 2 | 3 | RUN apt install python3 make gcc g++ 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-node-23/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 23 on Debian Bullseye Linux' 2 | description: 'Create a binary artifact for Node 23 on Debian Bullseye Linux' 3 | inputs: 4 | node-version: 5 | description: 'Node.js version' 6 | required: false 7 | default: '23' 8 | runs: 9 | using: 'docker' 10 | image: 'Dockerfile' 11 | args: 12 | - ${{inputs.node-version}} 13 | -------------------------------------------------------------------------------- /.github/actions/linux-node-23/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/actions/linux-node-24/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:24-bullseye 2 | 3 | RUN apt install python3 make gcc g++ 4 | 5 | COPY entrypoint.sh /entrypoint.sh 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /.github/actions/linux-node-24/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Create a binary artifact for Node 24 on Debian Bullseye Linux' 2 | description: 'Create a binary artifact for Node 24 on Debian Bullseye Linux' 3 | inputs: 4 | node-version: 5 | description: 'Node.js version' 6 | required: false 7 | default: '24' 8 | runs: 9 | using: 'docker' 10 | image: 'Dockerfile' 11 | args: 12 | - ${{inputs.node-version}} 13 | -------------------------------------------------------------------------------- /.github/actions/linux-node-24/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | export USERNAME=`whoami` 6 | export DEVELOPMENT_SKIP_GETTING_ASSET=true 7 | npm i 8 | npm run build --if-present 9 | npm test 10 | npm run save-to-github 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Node.js builds 2 | 3 | on: 4 | push: 5 | tags: 6 | - v?[0-9]+.[0-9]+.[0-9]+.[0-9]+ 7 | - v?[0-9]+.[0-9]+.[0-9]+ 8 | - v?[0-9]+.[0-9]+ 9 | 10 | permissions: 11 | id-token: write 12 | contents: write 13 | attestations: write 14 | 15 | jobs: 16 | create-release: 17 | name: Create release 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - env: 23 | GH_TOKEN: ${{github.token}} 24 | run: | 25 | REF=${{github.ref}} 26 | TAG=${REF#"refs/tags/"} 27 | gh release create -t "Release ${TAG}" -n "" "${{github.ref}}" 28 | 29 | build: 30 | name: Node.js ${{matrix.node-version}} on ${{matrix.os}} 31 | needs: create-release 32 | runs-on: ${{matrix.os}} 33 | strategy: 34 | matrix: 35 | os: [macos-latest, windows-latest, macos-13, windows-11-arm] 36 | node-version: [20, 22, 23, 24] 37 | 38 | steps: 39 | - uses: actions/checkout@v4 40 | with: 41 | submodules: true 42 | - name: Setup Node.js ${{matrix.node-version}} 43 | uses: actions/setup-node@v4 44 | with: 45 | node-version: ${{matrix.node-version}} 46 | - name: Install the package and run tests 47 | env: 48 | DEVELOPMENT_SKIP_GETTING_ASSET: true 49 | run: | 50 | npm i 51 | npm run build --if-present 52 | npm test 53 | - name: Save to GitHub 54 | env: 55 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 56 | run: npm run save-to-github 57 | - name: Attest 58 | if: env.CREATED_ASSET_NAME != '' 59 | uses: actions/attest-build-provenance@v2 60 | with: 61 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 62 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 63 | 64 | build-linux-node-20: 65 | name: Node.js 20 on Bullseye 66 | needs: create-release 67 | runs-on: ubuntu-latest 68 | continue-on-error: true 69 | 70 | steps: 71 | - uses: actions/checkout@v4 72 | with: 73 | submodules: true 74 | - name: Install, test, and create artifact 75 | uses: ./.github/actions/linux-node-20/ 76 | env: 77 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 78 | - name: Attest 79 | if: env.CREATED_ASSET_NAME != '' 80 | uses: actions/attest-build-provenance@v2 81 | with: 82 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 83 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 84 | 85 | build-linux-node-22: 86 | name: Node.js 22 on Bullseye 87 | needs: create-release 88 | runs-on: ubuntu-latest 89 | continue-on-error: true 90 | 91 | steps: 92 | - uses: actions/checkout@v4 93 | with: 94 | submodules: true 95 | - name: Install, test, and create artifact 96 | uses: ./.github/actions/linux-node-22/ 97 | env: 98 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 99 | - name: Attest 100 | if: env.CREATED_ASSET_NAME != '' 101 | uses: actions/attest-build-provenance@v2 102 | with: 103 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 104 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 105 | 106 | build-linux-alpine-node-20: 107 | name: Node.js 20 on Alpine 108 | needs: create-release 109 | runs-on: ubuntu-latest 110 | continue-on-error: true 111 | 112 | steps: 113 | - uses: actions/checkout@v4 114 | with: 115 | submodules: true 116 | - name: Install, test, and create artifact 117 | uses: ./.github/actions/linux-alpine-node-20/ 118 | env: 119 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 120 | - name: Attest 121 | if: env.CREATED_ASSET_NAME != '' 122 | uses: actions/attest-build-provenance@v2 123 | with: 124 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 125 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 126 | 127 | build-linux-alpine-node-22: 128 | name: Node.js 22 on Alpine 129 | needs: create-release 130 | runs-on: ubuntu-latest 131 | continue-on-error: true 132 | 133 | steps: 134 | - uses: actions/checkout@v4 135 | with: 136 | submodules: true 137 | - name: Install, test, and create artifact 138 | uses: ./.github/actions/linux-alpine-node-22/ 139 | env: 140 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 141 | - name: Attest 142 | if: env.CREATED_ASSET_NAME != '' 143 | uses: actions/attest-build-provenance@v2 144 | with: 145 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 146 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 147 | 148 | build-linux-arm64-node-20: 149 | name: Node.js 20 on Bullseye ARM64 150 | needs: create-release 151 | runs-on: ubuntu-latest 152 | continue-on-error: true 153 | 154 | steps: 155 | - uses: actions/checkout@v4 156 | with: 157 | submodules: true 158 | - name: Set up QEMU 159 | uses: docker/setup-qemu-action@v3 160 | with: 161 | platforms: arm64 162 | - name: Install, test, and create artifact 163 | uses: ./.github/actions/linux-arm64-node-20/ 164 | env: 165 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 166 | - name: Attest 167 | if: env.CREATED_ASSET_NAME != '' 168 | uses: actions/attest-build-provenance@v2 169 | with: 170 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 171 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 172 | 173 | build-linux-arm64-node-22: 174 | name: Node.js 22 on Bullseye ARM64 175 | needs: create-release 176 | runs-on: ubuntu-latest 177 | continue-on-error: true 178 | 179 | steps: 180 | - uses: actions/checkout@v4 181 | with: 182 | submodules: true 183 | - name: Set up QEMU 184 | uses: docker/setup-qemu-action@v3 185 | with: 186 | platforms: arm64 187 | - name: Install, test, and create artifact 188 | uses: ./.github/actions/linux-arm64-node-22/ 189 | env: 190 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 191 | - name: Attest 192 | if: env.CREATED_ASSET_NAME != '' 193 | uses: actions/attest-build-provenance@v2 194 | with: 195 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 196 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 197 | 198 | build-linux-arm64-alpine-node-20: 199 | name: Node.js 20 on Alpine ARM64 200 | needs: create-release 201 | runs-on: ubuntu-latest 202 | continue-on-error: true 203 | 204 | steps: 205 | - uses: actions/checkout@v4 206 | with: 207 | submodules: true 208 | - name: Set up QEMU 209 | uses: docker/setup-qemu-action@v3 210 | with: 211 | platforms: arm64 212 | - name: Install, test, and create artifact 213 | uses: ./.github/actions/linux-arm64-alpine-node-20/ 214 | env: 215 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 216 | - name: Attest 217 | if: env.CREATED_ASSET_NAME != '' 218 | uses: actions/attest-build-provenance@v2 219 | with: 220 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 221 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 222 | 223 | build-linux-arm64-alpine-node-22: 224 | name: Node.js 22 on Alpine ARM64 225 | needs: create-release 226 | runs-on: ubuntu-latest 227 | continue-on-error: true 228 | 229 | steps: 230 | - uses: actions/checkout@v4 231 | with: 232 | submodules: true 233 | - name: Set up QEMU 234 | uses: docker/setup-qemu-action@v3 235 | with: 236 | platforms: arm64 237 | - name: Install, test, and create artifact 238 | uses: ./.github/actions/linux-arm64-alpine-node-22/ 239 | env: 240 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 241 | - name: Attest 242 | if: env.CREATED_ASSET_NAME != '' 243 | uses: actions/attest-build-provenance@v2 244 | with: 245 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 246 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 247 | 248 | build-linux-node-23: 249 | name: Node.js 23 on Bullseye 250 | needs: create-release 251 | runs-on: ubuntu-latest 252 | continue-on-error: true 253 | 254 | steps: 255 | - uses: actions/checkout@v4 256 | with: 257 | submodules: true 258 | - name: Install, test, and create artifact 259 | uses: ./.github/actions/linux-node-23/ 260 | env: 261 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 262 | - name: Attest 263 | if: env.CREATED_ASSET_NAME != '' 264 | uses: actions/attest-build-provenance@v2 265 | with: 266 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 267 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 268 | 269 | build-linux-alpine-node-23: 270 | name: Node.js 23 on Alpine 271 | needs: create-release 272 | runs-on: ubuntu-latest 273 | continue-on-error: true 274 | 275 | steps: 276 | - uses: actions/checkout@v4 277 | with: 278 | submodules: true 279 | - name: Install, test, and create artifact 280 | uses: ./.github/actions/linux-alpine-node-23/ 281 | env: 282 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 283 | - name: Attest 284 | if: env.CREATED_ASSET_NAME != '' 285 | uses: actions/attest-build-provenance@v2 286 | with: 287 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 288 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 289 | 290 | build-linux-arm64-node-23: 291 | name: Node.js 23 on Bullseye ARM64 292 | needs: create-release 293 | runs-on: ubuntu-latest 294 | continue-on-error: true 295 | 296 | steps: 297 | - uses: actions/checkout@v4 298 | with: 299 | submodules: true 300 | - name: Set up QEMU 301 | uses: docker/setup-qemu-action@v3 302 | with: 303 | platforms: arm64 304 | - name: Install, test, and create artifact 305 | uses: ./.github/actions/linux-arm64-node-23/ 306 | env: 307 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 308 | - name: Attest 309 | if: env.CREATED_ASSET_NAME != '' 310 | uses: actions/attest-build-provenance@v2 311 | with: 312 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 313 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 314 | 315 | build-linux-arm64-alpine-node-23: 316 | name: Node.js 23 on Alpine ARM64 317 | needs: create-release 318 | runs-on: ubuntu-latest 319 | continue-on-error: true 320 | 321 | steps: 322 | - uses: actions/checkout@v4 323 | with: 324 | submodules: true 325 | - name: Set up QEMU 326 | uses: docker/setup-qemu-action@v3 327 | with: 328 | platforms: arm64 329 | - name: Install, test, and create artifact 330 | uses: ./.github/actions/linux-arm64-alpine-node-23/ 331 | env: 332 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 333 | - name: Attest 334 | if: env.CREATED_ASSET_NAME != '' 335 | uses: actions/attest-build-provenance@v2 336 | with: 337 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 338 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 339 | 340 | build-linux-node-24: 341 | name: Node.js 24 on Bullseye 342 | needs: create-release 343 | runs-on: ubuntu-latest 344 | continue-on-error: true 345 | 346 | steps: 347 | - uses: actions/checkout@v4 348 | with: 349 | submodules: true 350 | - name: Install, test, and create artifact 351 | uses: ./.github/actions/linux-node-24/ 352 | env: 353 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 354 | - name: Attest 355 | if: env.CREATED_ASSET_NAME != '' 356 | uses: actions/attest-build-provenance@v2 357 | with: 358 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 359 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 360 | 361 | build-linux-alpine-node-24: 362 | name: Node.js 24 on Alpine 363 | needs: create-release 364 | runs-on: ubuntu-latest 365 | continue-on-error: true 366 | 367 | steps: 368 | - uses: actions/checkout@v4 369 | with: 370 | submodules: true 371 | - name: Install, test, and create artifact 372 | uses: ./.github/actions/linux-alpine-node-24/ 373 | env: 374 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 375 | - name: Attest 376 | if: env.CREATED_ASSET_NAME != '' 377 | uses: actions/attest-build-provenance@v2 378 | with: 379 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 380 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 381 | 382 | build-linux-arm64-node-24: 383 | name: Node.js 24 on Bullseye ARM64 384 | needs: create-release 385 | runs-on: ubuntu-latest 386 | continue-on-error: true 387 | 388 | steps: 389 | - uses: actions/checkout@v4 390 | with: 391 | submodules: true 392 | - name: Set up QEMU 393 | uses: docker/setup-qemu-action@v3 394 | with: 395 | platforms: arm64 396 | - name: Install, test, and create artifact 397 | uses: ./.github/actions/linux-arm64-node-24/ 398 | env: 399 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 400 | - name: Attest 401 | if: env.CREATED_ASSET_NAME != '' 402 | uses: actions/attest-build-provenance@v2 403 | with: 404 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 405 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 406 | 407 | build-linux-arm64-alpine-node-24: 408 | name: Node.js 24 on Alpine ARM64 409 | needs: create-release 410 | runs-on: ubuntu-latest 411 | continue-on-error: true 412 | 413 | steps: 414 | - uses: actions/checkout@v4 415 | with: 416 | submodules: true 417 | - name: Set up QEMU 418 | uses: docker/setup-qemu-action@v3 419 | with: 420 | platforms: arm64 421 | - name: Install, test, and create artifact 422 | uses: ./.github/actions/linux-arm64-alpine-node-24/ 423 | env: 424 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 425 | - name: Attest 426 | if: env.CREATED_ASSET_NAME != '' 427 | uses: actions/attest-build-provenance@v2 428 | with: 429 | subject-name: '${{ env.CREATED_ASSET_NAME }}' 430 | subject-path: '${{ github.workspace }}/build/Release/re2.node' 431 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: ['*'] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | tests: 11 | name: Node.js ${{matrix.node-version}} on ${{matrix.os}} 12 | runs-on: ${{matrix.os}} 13 | 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, macOS-latest, windows-latest] 17 | node-version: [20, 22, 23, 24] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | - name: Setup Node.js ${{matrix.node-version}} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{matrix.node-version}} 27 | - name: Install the package and run tests 28 | env: 29 | DEVELOPMENT_SKIP_GETTING_ASSET: true 30 | run: | 31 | npm i 32 | npm run build --if-present 33 | npm test && npm run ts-test 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | report/ 4 | coverage/ 5 | .AppleDouble 6 | /.development 7 | /.developmentx 8 | /.xdevelopment 9 | 10 | /scripts/save-local.sh 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/re2"] 2 | path = vendor/re2 3 | url = https://github.com/google/re2 4 | [submodule "vendor/abseil-cpp"] 5 | path = vendor/abseil-cpp 6 | url = https://github.com/abseil/abseil-cpp 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "bracketSpacing": false, 5 | "arrowParens": "avoid", 6 | "trailingComma": "none" 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "/${env.NVM_INC}/**" 8 | ], 9 | "defines": [], 10 | "macFrameworkPath": [ 11 | "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks" 12 | ], 13 | "compilerPath": "/usr/bin/clang", 14 | "cStandard": "c17", 15 | "cppStandard": "c++17", 16 | "intelliSenseMode": "macos-clang-arm64" 17 | } 18 | ], 19 | "version": 4 20 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug tests", 11 | "preLaunchTask": "npm: build:dev", 12 | "program": "${env:NVM_BIN}/node", 13 | "args": ["${workspaceFolder}/tests/tests.js"], 14 | "cwd": "${workspaceFolder}" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "heya", 4 | "PCRE", 5 | "replacee", 6 | "Submatch" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "build:dev", 7 | "group": "build", 8 | "problemMatcher": [], 9 | "label": "npm: build:dev", 10 | "detail": "node-gyp -j max build --debug" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This library is available under the terms of the modified BSD license. No external contributions 2 | are allowed under licenses which are fundamentally incompatible with the BSD license that this library is distributed under. 3 | 4 | The text of the BSD license is reproduced below. 5 | 6 | ------------------------------------------------------------------------------- 7 | The "New" BSD License: 8 | ********************** 9 | 10 | Copyright (c) 2005-2025, Eugene Lazutkin 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | 16 | * Redistributions of source code must retain the above copyright notice, this 17 | list of conditions and the following disclaimer. 18 | * Redistributions in binary form must reproduce the above copyright notice, 19 | this list of conditions and the following disclaimer in the documentation 20 | and/or other materials provided with the distribution. 21 | * Neither the name of Eugene Lazutkin nor the names of other contributors 22 | may be used to endorse or promote products derived from this software 23 | without specific prior written permission. 24 | 25 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 26 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 27 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 28 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 29 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 31 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 33 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-re2 [![NPM version][npm-img]][npm-url] 2 | 3 | [npm-img]: https://img.shields.io/npm/v/re2.svg 4 | [npm-url]: https://npmjs.org/package/re2 5 | 6 | This project provides bindings for [RE2](https://github.com/google/re2): 7 | fast, safe alternative to backtracking regular expression engines written by [Russ Cox](http://swtch.com/~rsc/). 8 | To learn more about RE2, start with an overview 9 | [Regular Expression Matching in the Wild](http://swtch.com/~rsc/regexp/regexp3.html). More resources can be found 10 | at his [Implementing Regular Expressions](http://swtch.com/~rsc/regexp/) page. 11 | 12 | `RE2`'s regular expression language is almost a superset of what is provided by `RegExp` 13 | (see [Syntax](https://github.com/google/re2/wiki/Syntax)), 14 | but it lacks two features: backreferences and lookahead assertions. See below for more details. 15 | 16 | `RE2` always works in the [Unicode mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode), which means that all matches that use character codes are interpret as Unicode code points, not as binary values of UTF-16. 17 | See `RE2.unicodeWarningLevel` below for more details. 18 | 19 | 20 | `RE2` object emulates standard `RegExp` making it a practical drop-in replacement in most cases. 21 | `RE2` is extended to provide `String`-based regular expression methods as well. To help to convert 22 | `RegExp` objects to `RE2` its constructor can take `RegExp` directly honoring all properties. 23 | 24 | It can work with [node.js buffers](http://nodejs.org/api/buffer.html) directly reducing overhead 25 | on recoding and copying characters, and making processing/parsing long files fast. 26 | 27 | All documentation can be found in this README and in the [wiki](https://github.com/uhop/node-re2/wiki). 28 | 29 | ## Why use node-re2? 30 | 31 | The built-in Node.js regular expression engine can run in exponential time with a special combination: 32 | - A vulnerable regular expression 33 | - "Evil input" 34 | 35 | This can lead to what is known as a [Regular Expression Denial of Service (ReDoS)](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS). 36 | To tell if your regular expressions are vulnerable, you might try the one of these projects: 37 | - [rxxr2](http://www.cs.bham.ac.uk/~hxt/research/rxxr2/) 38 | - [safe-regex](https://github.com/substack/safe-regex) 39 | 40 | However, neither project is perfect. 41 | 42 | node-re2 can protect your Node.js application from ReDoS. 43 | node-re2 makes vulnerable regular expression patterns safe by evaluating them in `RE2` instead of the built-in Node.js regex engine. 44 | 45 | ## Standard features 46 | 47 | `RE2` object can be created just like `RegExp`: 48 | 49 | * [`new RE2(pattern[, flags])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) 50 | 51 | Supported properties: 52 | 53 | * [`re2.lastIndex`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex) 54 | * [`re2.global`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global) 55 | * [`re2.ignoreCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase) 56 | * [`re2.multiline`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline) 57 | * [`re2.dotAll`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/dotAll) — *since 1.17.6.* 58 | * [`re2.unicode`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode) 59 | * `RE2` engine always works in the Unicode mode. See details below. 60 | * [`re2.sticky`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky) — *since 1.7.0.* 61 | * [`re2.hasIndices`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/hasIndices) — *since 1.19.0.* 62 | * [`re2.source`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/source) 63 | * [`re2.flags`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags) 64 | 65 | Supported methods: 66 | 67 | * [`re2.exec(str)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec) 68 | * [`re2.test(str)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) 69 | * [`re2.toString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/toString) 70 | 71 | Starting with 1.6.0 following well-known symbol-based methods are supported (see [Symbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)): 72 | 73 | * [`re2[Symbol.match](str)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/match) 74 | * [`re2[Symbol.matchAll](str)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/matchAll) — *since 1.17.5.* 75 | * [`re2[Symbol.search](str)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/search) 76 | * [`re2[Symbol.replace](str, newSubStr|function)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/replace) 77 | * [`re2[Symbol.split](str[, limit])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/split) 78 | 79 | It allows to use `RE2` instances on strings directly, just like `RegExp` instances: 80 | 81 | ```js 82 | var re = new RE2("1"); 83 | "213".match(re); // [ '1', index: 1, input: '213' ] 84 | "213".search(re); // 1 85 | "213".replace(re, "+"); // 2+3 86 | "213".split(re); // [ '2', '3' ] 87 | 88 | Array.from("2131".matchAll(re)); // returns a generator! 89 | // [['1', index: 1, input: '2131'], ['1', index: 3, input: '2131']] 90 | ``` 91 | 92 | Starting with 1.8.0 [named groups](https://tc39.github.io/proposal-regexp-named-groups/) are supported. 93 | 94 | ## Extensions 95 | 96 | ### Shortcut construction 97 | 98 | `RE2` object can be created from a regular expression: 99 | 100 | ```js 101 | var re1 = new RE2(/ab*/ig); // from a RegExp object 102 | var re2 = new RE2(re1); // from another RE2 object 103 | ``` 104 | 105 | ### `String` methods 106 | 107 | Standard `String` defines four more methods that can use regular expressions. `RE2` provides them as methods 108 | exchanging positions of a string, and a regular expression: 109 | 110 | * `re2.match(str)` 111 | * See [`str.match(regexp)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match) 112 | * `re2.replace(str, newSubStr|function)` 113 | * See [`str.replace(regexp, newSubStr|function)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace) 114 | * `re2.search(str)` 115 | * See [`str.search(regexp)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search) 116 | * `re2.split(str[, limit])` 117 | * See [`str.split(regexp[, limit])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split) 118 | 119 | Starting with 1.6.0, these methods added as well-known symbol-based methods to be used transparently with ES6 string/regex machinery. 120 | 121 | ### `Buffer` support 122 | 123 | In order to support `Buffer` directly, most methods can accept buffers instead of strings. It speeds up all operations. 124 | Following signatures are supported: 125 | 126 | * `re2.exec(buf)` 127 | * `re2.test(buf)` 128 | * `re2.match(buf)` 129 | * `re2.search(buf)` 130 | * `re2.split(buf[, limit])` 131 | * `re2.replace(buf, replacer)` 132 | 133 | Differences with their string-based versions: 134 | 135 | * All buffers are assumed to be encoded as [UTF-8](http://en.wikipedia.org/wiki/UTF-8) 136 | (ASCII is a proper subset of UTF-8). 137 | * Instead of strings they return `Buffer` objects, even in composite objects. A buffer can be converted to a string with 138 | [`buf.toString()`](http://nodejs.org/api/buffer.html#buffer_buf_tostring_encoding_start_end). 139 | * All offsets and lengths are in bytes, rather than characters (each UTF-8 character can occupy from 1 to 4 bytes). 140 | This way users can properly slice buffers without costly recalculations from characters to bytes. 141 | 142 | When `re2.replace()` is used with a replacer function, the replacer can return a buffer, or a string. But all arguments 143 | (except for an input object) will be strings, and an offset will be in characters. If you prefer to deal 144 | with buffers and byte offsets in a replacer function, set a property `useBuffers` to `true` on the function: 145 | 146 | ```js 147 | function strReplacer(match, offset, input) { 148 | // typeof match == "string" 149 | return "<= " + offset + " characters|"; 150 | } 151 | 152 | RE2("б").replace("абв", strReplacer); 153 | // "а<= 1 characters|в" 154 | 155 | function bufReplacer(match, offset, input) { 156 | // typeof match == "string" 157 | return "<= " + offset + " bytes|"; 158 | } 159 | bufReplacer.useBuffers = true; 160 | 161 | RE2("б").replace("абв", bufReplacer); 162 | // "а<= 2 bytes|в" 163 | ``` 164 | 165 | This feature works for string and buffer inputs. If a buffer was used as an input, its output will be returned as 166 | a buffer too, otherwise a string will be returned. 167 | 168 | ### Calculate length 169 | 170 | Two functions to calculate string sizes between 171 | [UTF-8](http://en.wikipedia.org/wiki/UTF-8) and 172 | [UTF-16](http://en.wikipedia.org/wiki/UTF-16) are exposed on `RE2`: 173 | 174 | * `RE2.getUtf8Length(str)` — calculates a buffer size in bytes to encode a UTF-16 string as 175 | a UTF-8 buffer. 176 | * `RE2.getUtf16Length(buf)` — calculates a string size in characters to encode a UTF-8 buffer as 177 | a UTF-16 string. 178 | 179 | JavaScript supports UCS-2 strings with 16-bit characters, while node.js 0.11 supports full UTF-16 as 180 | a default string. 181 | 182 | ### Property: `internalSource` 183 | 184 | Starting 1.8.0 property `source` emulates the same property of `RegExp`, meaning that it can be used to create an identical `RE2` or `RegExp` instance. Sometimes, for troubleshooting purposes, a user wants to inspect a `RE2` translated source. It is available as a read-only property called `internalSource`. 185 | 186 | ### Unicode warning level 187 | 188 | `RE2` engine always works in the Unicode mode. In most cases either there is no difference or the Unicode mode is actually preferred. But sometimes a user wants a tight control over their regular expressions. For those cases, there is a static string property `RE2.unicodeWarningLevel`. 189 | 190 | Regular expressions in the Unicode mode work as usual. But if a regular expression lacks the Unicode flag, it is always added silently. 191 | 192 | ```js 193 | const x = /./; 194 | x.flags; // '' 195 | const y = new RE2(x); 196 | y.flags; // 'u' 197 | ``` 198 | 199 | In the latter case `RE2` can do following actions depending on `RE2.unicodeWarningLevel`: 200 | 201 | * `'nothing'` (the default): no warnings or notifications of any kind, a regular expression will be created with `'u'` flag. 202 | * `'warnOnce'`: warns exactly once the very first time, a regular expression will be created with `'u'` flag. 203 | * Assigning this value resets an internal flag, so `RE2` will warn once again. 204 | * `'warn'`: warns every time, a regular expression will be created with `'u'` flag. 205 | * `'throw'`: throws a `SyntaxError` every time. 206 | * All other warning level values are silently ignored on asignment leaving the previous value unchanged. 207 | 208 | Warnings and exceptions help to audit an application for stray non-Unicode regular expressions. 209 | 210 | ## How to install 211 | 212 | Installation: 213 | 214 | ``` 215 | npm install --save re2 216 | ``` 217 | 218 | While the project is known to work with other package managers, it is not guaranteed nor tested. 219 | For example, [yarn](https://yarnpkg.com/) is known to fail in some scenarios 220 | (see this [Wiki article](https://github.com/uhop/node-re2/wiki/Problem:-unusual-errors-with-yarn)). 221 | 222 | ### Precompiled artifacts 223 | 224 | When installing re2 the [install script](https://github.com/uhop/install-artifact-from-github/blob/master/bin/install-from-cache.js) attempts to download a prebuilt artifact for your system from the Github releases. The download location can be overridden by setting the `RE2_DOWNLOAD_MIRROR` environment variable as seen in the install script. 225 | 226 | If all attempts to download the prebuilt artifact for your system fails the script attempts to built re2 locally on your machine using [node-gyp](https://github.com/nodejs/node-gyp). 227 | 228 | ## How to use 229 | 230 | It is used just like a `RegExp` object. 231 | 232 | ```js 233 | var RE2 = require("re2"); 234 | 235 | // with default flags 236 | var re = new RE2("a(b*)"); 237 | var result = re.exec("abbc"); 238 | console.log(result[0]); // "abb" 239 | console.log(result[1]); // "bb" 240 | 241 | result = re.exec("aBbC"); 242 | console.log(result[0]); // "a" 243 | console.log(result[1]); // "" 244 | 245 | // with explicit flags 246 | re = new RE2("a(b*)", "i"); 247 | result = re.exec("aBbC"); 248 | console.log(result[0]); // "aBb" 249 | console.log(result[1]); // "Bb" 250 | 251 | // from regular expression object 252 | var regexp = new RegExp("a(b*)", "i"); 253 | re = new RE2(regexp); 254 | result = re.exec("aBbC"); 255 | console.log(result[0]); // "aBb" 256 | console.log(result[1]); // "Bb" 257 | 258 | // from regular expression literal 259 | re = new RE2(/a(b*)/i); 260 | result = re.exec("aBbC"); 261 | console.log(result[0]); // "aBb" 262 | console.log(result[1]); // "Bb" 263 | 264 | // from another RE2 object 265 | var rex = new RE2(re); 266 | result = rex.exec("aBbC"); 267 | console.log(result[0]); // "aBb" 268 | console.log(result[1]); // "Bb" 269 | 270 | // shortcut 271 | result = new RE2("ab*").exec("abba"); 272 | 273 | // factory 274 | result = RE2("ab*").exec("abba"); 275 | ``` 276 | 277 | ## Limitations (things RE2 does not support) 278 | 279 | `RE2` consciously avoids any regular expression features that require worst-case exponential time to evaluate. 280 | These features are essentially those that describe a Context-Free Language (CFL) rather than a Regular Expression, 281 | and are extensions to the traditional regular expression language because some people don't know when enough is enough. 282 | 283 | The most noteworthy missing features are backreferences and lookahead assertions. 284 | If your application uses these features, you should continue to use `RegExp`. 285 | But since these features are fundamentally vulnerable to 286 | [ReDoS](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS), 287 | you should strongly consider replacing them. 288 | 289 | `RE2` will throw a `SyntaxError` if you try to declare a regular expression using these features. 290 | If you are evaluating an externally-provided regular expression, wrap your `RE2` declarations in a try-catch block. It allows to use `RegExp`, when `RE2` misses a feature: 291 | 292 | ```js 293 | var re = /(a)+(b)*/; 294 | try { 295 | re = new RE2(re); 296 | // use RE2 as a drop-in replacement 297 | } catch (e) { 298 | // suppress an error, and use 299 | // the original RegExp 300 | } 301 | var result = re.exec(sample); 302 | ``` 303 | 304 | In addition to these missing features, `RE2` also behaves somewhat differently from the built-in regular expression engine in corner cases. 305 | 306 | ### Backreferences 307 | 308 | `RE2` doesn't support backreferences, which are numbered references to previously 309 | matched groups, like so: `\1`, `\2`, and so on. Example of backrefrences: 310 | 311 | ```js 312 | /(cat|dog)\1/.test("catcat"); // true 313 | /(cat|dog)\1/.test("dogdog"); // true 314 | /(cat|dog)\1/.test("catdog"); // false 315 | /(cat|dog)\1/.test("dogcat"); // false 316 | ``` 317 | 318 | ### Lookahead assertions 319 | 320 | `RE2` doesn't support lookahead assertions, which are ways to allow a matching dependent on subsequent contents. 321 | 322 | ```js 323 | /abc(?=def)/; // match abc only if it is followed by def 324 | /abc(?!def)/; // match abc only if it is not followed by def 325 | ``` 326 | 327 | ### Mismatched behavior 328 | 329 | `RE2` and the built-in regex engines disagree a bit. Before you switch to `RE2`, verify that your regular expressions continue to work as expected. They should do so in the vast majority of cases. 330 | 331 | Here is an example of a case where they may not: 332 | 333 | ```js 334 | var RE2 = require("../re2"); 335 | 336 | var pattern = '(?:(a)|(b)|(c))+'; 337 | 338 | var built_in = new RegExp(pattern); 339 | var re2 = new RE2(pattern); 340 | 341 | var input = 'abc'; 342 | 343 | var bi_res = built_in.exec(input); 344 | var re2_res = re2.exec(input); 345 | 346 | console.log('bi_res: ' + bi_res); // prints: bi_res: abc,,,c 347 | console.log('re2_res : ' + re2_res); // prints: re2_res : abc,a,b,c 348 | ``` 349 | 350 | ### Unicode 351 | 352 | `RE2` always works in the Unicode mode. See `RE2.unicodeWarningLevel` above for more details on how to control warnings about this feature. 353 | 354 | #### Unicode classes `\p{...}` and `\P{...}` 355 | 356 | `RE2` supports a subset of Unicode classes as defined in [RE2 Syntax](https://github.com/google/re2/wiki/Syntax). Native Google RE2 supports only short names, e.g., `L` for `Letter`, `N` for `Number`, etc. Like `RegExp`, `RE2` supports both short and long names, e.g., `Letter` for `L`, by translating them to short names. 357 | 358 | Generally, the extended form `\p{name=value}` is not supported. Only form `\p{name}` is supported. 359 | The exception is `Script` and `sc` names, e.g., `\p{Script=Latin}` and `\p{sc=Cyrillic}`. 360 | 361 | The same applies to `\P{...}`. 362 | 363 | ## Release history 364 | 365 | - 1.22.1 *Added support for translation of scripts as Unicode classes.* 366 | - 1.22.0 *Added support for translation of Unicode classes (thx, [John Livingston](https://github.com/JohnXLivingston)). Added [attestations](https://github.com/uhop/node-re2/attestations).* 367 | - 1.21.5 *Updated all dependencies and the list of pre-compiled targets. Fixed minor bugs. C++ style fix (thx, [Benjamin Brienen](https://github.com/BenjaminBrienen)). Added Windows 11 ARM build runner (thx, [Kagami Sascha Rosylight](https://github.com/saschanaz)).* 368 | - 1.21.4 *Fixed a regression reported by [caroline-matsec](https://github.com/caroline-matsec), thx! Added pre-compilation targets for Alpine Linux on ARM. Updated deps.* 369 | - 1.21.3 *Fixed an empty string regression reported by [Rhys Arkins](https://github.com/rarkins), thx! Updated deps.* 370 | - 1.21.2 *Fixed another memory regression reported by [matthewvalentine](https://github.com/matthewvalentine), thx! Updated deps. Added more tests and benchmarks.* 371 | - 1.21.1 *Fixed a memory regression reported by [matthewvalentine](https://github.com/matthewvalentine), thx! Updated deps.* 372 | - 1.21.0 *Fixed the performance problem reported by [matthewvalentine](https://github.com/matthewvalentine) (thx!). The change improves performance for multiple use cases.* 373 | - 1.20.12 *Updated deps. Maintenance chores. Fixes for buffer-related bugs: `exec()` index (reported by [matthewvalentine](https://github.com/matthewvalentine), thx) and `match()` index.* 374 | - 1.20.11 *Updated deps. Added support for Node 22 (thx, [Elton Leong](https://github.com/eltonkl)).* 375 | - 1.20.10 *Updated deps. Removed files the pack used for development (thx, [Haruaki OTAKE](https://github.com/aaharu)). Added arm64 Linux prebilds (thx, [Christopher M](https://github.com/cmanou)). Fixed non-`npm` `corepack` problem (thx, [Steven](https://github.com/styfle)).* 376 | - 1.20.9 *Updated deps. Added more `absail-cpp` files that manifested itself on NixOS. Thx, [Laura Hausmann](https://github.com/zotanmew).* 377 | - 1.20.8 *Updated deps: `install-artifact-from-github`. A default HTTPS agent is used for fetching precompiled artifacts avoiding unnecessary long wait times.* 378 | - 1.20.7 *Added more `absail-cpp` files that manifested itself on ARM Alpine. Thx, [Laura Hausmann](https://github.com/zotanmew).* 379 | - 1.20.6 *Updated deps, notably `node-gyp`.* 380 | - 1.20.5 *Updated deps, added Node 21 and retired Node 16 as pre-compilation targets.* 381 | - 1.20.4 *Updated deps. Fix: the 2nd argument of the constructor overrides flags. Thx, [gost-serb](https://github.com/gost-serb).* 382 | - 1.20.3 *Fix: subsequent numbers are incorporated into group if they would form a legal group reference. Thx, [Oleksii Vasyliev](https://github.com/le0pard).* 383 | - 1.20.2 *Fix: added a missing C++ file, which caused a bug on Alpine Linux. Thx, [rbitanga-manticore](https://github.com/rbitanga-manticore).* 384 | - 1.20.1 *Fix: files included in the npm package to build the C++ code.* 385 | - 1.20.0 *Updated RE2. New version uses `abseil-cpp` and required the adaptation work. Thx, [Stefano Rivera](https://github.com/stefanor).* 386 | 387 | The rest can be consulted in the project's wiki [Release history](https://github.com/uhop/node-re2/wiki/Release-history). 388 | 389 | ## License 390 | 391 | BSD-3-Clause 392 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "re2", 5 | "sources": [ 6 | "lib/addon.cc", 7 | "lib/accessors.cc", 8 | "lib/util.cc", 9 | "lib/new.cc", 10 | "lib/exec.cc", 11 | "lib/test.cc", 12 | "lib/match.cc", 13 | "lib/replace.cc", 14 | "lib/search.cc", 15 | "lib/split.cc", 16 | "lib/to_string.cc", 17 | "vendor/re2/re2/bitmap256.cc", 18 | "vendor/re2/re2/bitstate.cc", 19 | "vendor/re2/re2/compile.cc", 20 | "vendor/re2/re2/dfa.cc", 21 | "vendor/re2/re2/filtered_re2.cc", 22 | "vendor/re2/re2/mimics_pcre.cc", 23 | "vendor/re2/re2/nfa.cc", 24 | "vendor/re2/re2/onepass.cc", 25 | "vendor/re2/re2/parse.cc", 26 | "vendor/re2/re2/perl_groups.cc", 27 | "vendor/re2/re2/prefilter.cc", 28 | "vendor/re2/re2/prefilter_tree.cc", 29 | "vendor/re2/re2/prog.cc", 30 | "vendor/re2/re2/re2.cc", 31 | "vendor/re2/re2/regexp.cc", 32 | "vendor/re2/re2/set.cc", 33 | "vendor/re2/re2/simplify.cc", 34 | "vendor/re2/re2/tostring.cc", 35 | "vendor/re2/re2/unicode_casefold.cc", 36 | "vendor/re2/re2/unicode_groups.cc", 37 | "vendor/re2/util/pcre.cc", 38 | "vendor/re2/util/rune.cc", 39 | "vendor/re2/util/strutil.cc", 40 | "vendor/abseil-cpp/absl/base/internal/cycleclock.cc", 41 | "vendor/abseil-cpp/absl/base/internal/low_level_alloc.cc", 42 | "vendor/abseil-cpp/absl/base/internal/raw_logging.cc", 43 | "vendor/abseil-cpp/absl/base/internal/spinlock.cc", 44 | "vendor/abseil-cpp/absl/base/internal/spinlock_wait.cc", 45 | "vendor/abseil-cpp/absl/base/internal/strerror.cc", 46 | "vendor/abseil-cpp/absl/base/internal/sysinfo.cc", 47 | "vendor/abseil-cpp/absl/base/internal/thread_identity.cc", 48 | "vendor/abseil-cpp/absl/base/internal/throw_delegate.cc", 49 | "vendor/abseil-cpp/absl/base/internal/unscaledcycleclock.cc", 50 | "vendor/abseil-cpp/absl/container/internal/raw_hash_set.cc", 51 | "vendor/abseil-cpp/absl/debugging/internal/decode_rust_punycode.cc", 52 | "vendor/abseil-cpp/absl/debugging/internal/demangle.cc", 53 | "vendor/abseil-cpp/absl/debugging/internal/demangle_rust.cc", 54 | "vendor/abseil-cpp/absl/debugging/internal/address_is_readable.cc", 55 | "vendor/abseil-cpp/absl/debugging/internal/elf_mem_image.cc", 56 | "vendor/abseil-cpp/absl/debugging/internal/examine_stack.cc", 57 | "vendor/abseil-cpp/absl/debugging/internal/utf8_for_code_point.cc", 58 | "vendor/abseil-cpp/absl/debugging/internal/vdso_support.cc", 59 | "vendor/abseil-cpp/absl/debugging/stacktrace.cc", 60 | "vendor/abseil-cpp/absl/debugging/symbolize.cc", 61 | "vendor/abseil-cpp/absl/flags/commandlineflag.cc", 62 | "vendor/abseil-cpp/absl/flags/internal/commandlineflag.cc", 63 | "vendor/abseil-cpp/absl/flags/internal/flag.cc", 64 | "vendor/abseil-cpp/absl/flags/internal/private_handle_accessor.cc", 65 | "vendor/abseil-cpp/absl/flags/internal/program_name.cc", 66 | "vendor/abseil-cpp/absl/flags/marshalling.cc", 67 | "vendor/abseil-cpp/absl/flags/reflection.cc", 68 | "vendor/abseil-cpp/absl/flags/usage_config.cc", 69 | "vendor/abseil-cpp/absl/hash/internal/city.cc", 70 | "vendor/abseil-cpp/absl/hash/internal/hash.cc", 71 | "vendor/abseil-cpp/absl/hash/internal/low_level_hash.cc", 72 | "vendor/abseil-cpp/absl/log/internal/globals.cc", 73 | "vendor/abseil-cpp/absl/log/internal/log_format.cc", 74 | "vendor/abseil-cpp/absl/log/internal/log_message.cc", 75 | "vendor/abseil-cpp/absl/log/internal/log_sink_set.cc", 76 | "vendor/abseil-cpp/absl/log/internal/nullguard.cc", 77 | "vendor/abseil-cpp/absl/log/internal/proto.cc", 78 | "vendor/abseil-cpp/absl/log/internal/structured_proto.cc", 79 | "vendor/abseil-cpp/absl/log/globals.cc", 80 | "vendor/abseil-cpp/absl/log/log_sink.cc", 81 | "vendor/abseil-cpp/absl/numeric/int128.cc", 82 | "vendor/abseil-cpp/absl/strings/ascii.cc", 83 | "vendor/abseil-cpp/absl/strings/charconv.cc", 84 | "vendor/abseil-cpp/absl/strings/internal/charconv_bigint.cc", 85 | "vendor/abseil-cpp/absl/strings/internal/charconv_parse.cc", 86 | "vendor/abseil-cpp/absl/strings/internal/memutil.cc", 87 | "vendor/abseil-cpp/absl/strings/internal/str_format/arg.cc", 88 | "vendor/abseil-cpp/absl/strings/internal/str_format/bind.cc", 89 | "vendor/abseil-cpp/absl/strings/internal/str_format/extension.cc", 90 | "vendor/abseil-cpp/absl/strings/internal/str_format/float_conversion.cc", 91 | "vendor/abseil-cpp/absl/strings/internal/str_format/output.cc", 92 | "vendor/abseil-cpp/absl/strings/internal/str_format/parser.cc", 93 | "vendor/abseil-cpp/absl/strings/match.cc", 94 | "vendor/abseil-cpp/absl/strings/numbers.cc", 95 | "vendor/abseil-cpp/absl/strings/str_cat.cc", 96 | "vendor/abseil-cpp/absl/strings/str_split.cc", 97 | "vendor/abseil-cpp/absl/strings/string_view.cc", 98 | "vendor/abseil-cpp/absl/synchronization/internal/create_thread_identity.cc", 99 | "vendor/abseil-cpp/absl/synchronization/internal/graphcycles.cc", 100 | "vendor/abseil-cpp/absl/synchronization/internal/futex_waiter.cc", 101 | "vendor/abseil-cpp/absl/synchronization/internal/kernel_timeout.cc", 102 | "vendor/abseil-cpp/absl/synchronization/internal/per_thread_sem.cc", 103 | "vendor/abseil-cpp/absl/synchronization/internal/waiter_base.cc", 104 | "vendor/abseil-cpp/absl/synchronization/mutex.cc", 105 | "vendor/abseil-cpp/absl/time/clock.cc", 106 | "vendor/abseil-cpp/absl/time/duration.cc", 107 | "vendor/abseil-cpp/absl/time/internal/cctz/src/time_zone_fixed.cc", 108 | "vendor/abseil-cpp/absl/time/internal/cctz/src/time_zone_if.cc", 109 | "vendor/abseil-cpp/absl/time/internal/cctz/src/time_zone_impl.cc", 110 | "vendor/abseil-cpp/absl/time/internal/cctz/src/time_zone_info.cc", 111 | "vendor/abseil-cpp/absl/time/internal/cctz/src/time_zone_libc.cc", 112 | "vendor/abseil-cpp/absl/time/internal/cctz/src/time_zone_lookup.cc", 113 | "vendor/abseil-cpp/absl/time/internal/cctz/src/time_zone_posix.cc", 114 | "vendor/abseil-cpp/absl/time/internal/cctz/src/zone_info_source.cc", 115 | "vendor/abseil-cpp/absl/time/time.cc", 116 | ], 117 | "cflags": [ 118 | "-std=c++2a", 119 | "-Wall", 120 | "-Wextra", 121 | "-Wno-sign-compare", 122 | "-Wno-unused-parameter", 123 | "-Wno-missing-field-initializers", 124 | "-Wno-cast-function-type", 125 | "-O3", 126 | "-g" 127 | ], 128 | "defines": [ 129 | "NDEBUG", 130 | "NOMINMAX" 131 | ], 132 | "include_dirs": [ 133 | " 4 | #include 5 | #include 6 | 7 | NAN_GETTER(WrappedRE2::GetSource) 8 | { 9 | if (!WrappedRE2::HasInstance(info.This())) 10 | { 11 | info.GetReturnValue().Set(Nan::New("(?:)").ToLocalChecked()); 12 | return; 13 | } 14 | 15 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 16 | info.GetReturnValue().Set(Nan::New(re2->source).ToLocalChecked()); 17 | } 18 | 19 | NAN_GETTER(WrappedRE2::GetInternalSource) 20 | { 21 | if (!WrappedRE2::HasInstance(info.This())) 22 | { 23 | info.GetReturnValue().Set(Nan::New("(?:)").ToLocalChecked()); 24 | return; 25 | } 26 | 27 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 28 | info.GetReturnValue().Set(Nan::New(re2->regexp.pattern()).ToLocalChecked()); 29 | } 30 | 31 | NAN_GETTER(WrappedRE2::GetFlags) 32 | { 33 | if (!WrappedRE2::HasInstance(info.This())) 34 | { 35 | info.GetReturnValue().Set(Nan::New("").ToLocalChecked()); 36 | return; 37 | } 38 | 39 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 40 | 41 | std::string flags; 42 | if (re2->hasIndices) 43 | { 44 | flags += "d"; 45 | } 46 | if (re2->global) 47 | { 48 | flags += "g"; 49 | } 50 | if (re2->ignoreCase) 51 | { 52 | flags += "i"; 53 | } 54 | if (re2->multiline) 55 | { 56 | flags += "m"; 57 | } 58 | if (re2->dotAll) 59 | { 60 | flags += "s"; 61 | } 62 | flags += "u"; 63 | if (re2->sticky) 64 | { 65 | flags += "y"; 66 | } 67 | 68 | info.GetReturnValue().Set(Nan::New(flags).ToLocalChecked()); 69 | } 70 | 71 | NAN_GETTER(WrappedRE2::GetGlobal) 72 | { 73 | if (!WrappedRE2::HasInstance(info.This())) 74 | { 75 | info.GetReturnValue().SetUndefined(); 76 | return; 77 | } 78 | 79 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 80 | info.GetReturnValue().Set(re2->global); 81 | } 82 | 83 | NAN_GETTER(WrappedRE2::GetIgnoreCase) 84 | { 85 | if (!WrappedRE2::HasInstance(info.This())) 86 | { 87 | info.GetReturnValue().SetUndefined(); 88 | return; 89 | } 90 | 91 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 92 | info.GetReturnValue().Set(re2->ignoreCase); 93 | } 94 | 95 | NAN_GETTER(WrappedRE2::GetMultiline) 96 | { 97 | if (!WrappedRE2::HasInstance(info.This())) 98 | { 99 | info.GetReturnValue().SetUndefined(); 100 | return; 101 | } 102 | 103 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 104 | info.GetReturnValue().Set(re2->multiline); 105 | } 106 | 107 | NAN_GETTER(WrappedRE2::GetDotAll) 108 | { 109 | if (!WrappedRE2::HasInstance(info.This())) 110 | { 111 | info.GetReturnValue().SetUndefined(); 112 | return; 113 | } 114 | 115 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 116 | info.GetReturnValue().Set(re2->dotAll); 117 | } 118 | 119 | NAN_GETTER(WrappedRE2::GetUnicode) 120 | { 121 | if (!WrappedRE2::HasInstance(info.This())) 122 | { 123 | info.GetReturnValue().SetUndefined(); 124 | return; 125 | } 126 | 127 | info.GetReturnValue().Set(true); 128 | } 129 | 130 | NAN_GETTER(WrappedRE2::GetSticky) 131 | { 132 | if (!WrappedRE2::HasInstance(info.This())) 133 | { 134 | info.GetReturnValue().SetUndefined(); 135 | return; 136 | } 137 | 138 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 139 | info.GetReturnValue().Set(re2->sticky); 140 | } 141 | 142 | NAN_GETTER(WrappedRE2::GetHasIndices) 143 | { 144 | if (!WrappedRE2::HasInstance(info.This())) 145 | { 146 | info.GetReturnValue().SetUndefined(); 147 | return; 148 | } 149 | 150 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 151 | info.GetReturnValue().Set(re2->hasIndices); 152 | } 153 | 154 | NAN_GETTER(WrappedRE2::GetLastIndex) 155 | { 156 | if (!WrappedRE2::HasInstance(info.This())) 157 | { 158 | info.GetReturnValue().SetUndefined(); 159 | return; 160 | } 161 | 162 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 163 | info.GetReturnValue().Set(static_cast(re2->lastIndex)); 164 | } 165 | 166 | NAN_SETTER(WrappedRE2::SetLastIndex) 167 | { 168 | if (!WrappedRE2::HasInstance(info.This())) 169 | { 170 | return Nan::ThrowTypeError("Cannot set lastIndex of an invalid RE2 object."); 171 | } 172 | 173 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 174 | if (value->IsNumber()) 175 | { 176 | int n = value->NumberValue(Nan::GetCurrentContext()).FromMaybe(0); 177 | re2->lastIndex = n <= 0 ? 0 : n; 178 | } 179 | } 180 | 181 | WrappedRE2::UnicodeWarningLevels WrappedRE2::unicodeWarningLevel; 182 | 183 | NAN_GETTER(WrappedRE2::GetUnicodeWarningLevel) 184 | { 185 | std::string level; 186 | switch (unicodeWarningLevel) 187 | { 188 | case THROW: 189 | level = "throw"; 190 | break; 191 | case WARN: 192 | level = "warn"; 193 | break; 194 | case WARN_ONCE: 195 | level = "warnOnce"; 196 | break; 197 | default: 198 | level = "nothing"; 199 | break; 200 | } 201 | info.GetReturnValue().Set(Nan::New(level).ToLocalChecked()); 202 | } 203 | 204 | NAN_SETTER(WrappedRE2::SetUnicodeWarningLevel) 205 | { 206 | if (value->IsString()) 207 | { 208 | Nan::Utf8String s(value); 209 | if (!strcmp(*s, "throw")) 210 | { 211 | unicodeWarningLevel = THROW; 212 | return; 213 | } 214 | if (!strcmp(*s, "warn")) 215 | { 216 | unicodeWarningLevel = WARN; 217 | return; 218 | } 219 | if (!strcmp(*s, "warnOnce")) 220 | { 221 | unicodeWarningLevel = WARN_ONCE; 222 | alreadyWarnedAboutUnicode = false; 223 | return; 224 | } 225 | if (!strcmp(*s, "nothing")) 226 | { 227 | unicodeWarningLevel = NOTHING; 228 | return; 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /lib/addon.cc: -------------------------------------------------------------------------------- 1 | #include "./wrapped_re2.h" 2 | 3 | static NAN_METHOD(GetUtf8Length) 4 | { 5 | auto t = info[0]->ToString(Nan::GetCurrentContext()); 6 | if (t.IsEmpty()) 7 | { 8 | return; 9 | } 10 | auto s = t.ToLocalChecked(); 11 | info.GetReturnValue().Set(static_cast(s->Utf8Length(v8::Isolate::GetCurrent()))); 12 | } 13 | 14 | static NAN_METHOD(GetUtf16Length) 15 | { 16 | if (node::Buffer::HasInstance(info[0])) 17 | { 18 | const auto *s = node::Buffer::Data(info[0]); 19 | info.GetReturnValue().Set(static_cast(getUtf16Length(s, s + node::Buffer::Length(info[0])))); 20 | return; 21 | } 22 | info.GetReturnValue().Set(-1); 23 | } 24 | 25 | static void cleanup(void *p) 26 | { 27 | v8::Isolate *isolate = static_cast(p); 28 | auto p_tpl = Nan::GetIsolateData>(isolate); 29 | delete p_tpl; 30 | } 31 | 32 | // NAN_MODULE_INIT(WrappedRE2::Init) 33 | v8::Local WrappedRE2::Init() 34 | { 35 | Nan::EscapableHandleScope scope; 36 | 37 | // prepare constructor template 38 | 39 | auto tpl = Nan::New(New); 40 | tpl->SetClassName(Nan::New("RE2").ToLocalChecked()); 41 | auto instanceTemplate = tpl->InstanceTemplate(); 42 | instanceTemplate->SetInternalFieldCount(1); 43 | 44 | // save the template 45 | auto isolate = v8::Isolate::GetCurrent(); 46 | auto p_tpl = new Nan::Persistent(tpl); 47 | Nan::SetIsolateData(isolate, p_tpl); 48 | node::AddEnvironmentCleanupHook(isolate, cleanup, isolate); 49 | 50 | // prototype 51 | 52 | Nan::SetPrototypeMethod(tpl, "toString", ToString); 53 | 54 | Nan::SetPrototypeMethod(tpl, "exec", Exec); 55 | Nan::SetPrototypeMethod(tpl, "test", Test); 56 | 57 | Nan::SetPrototypeMethod(tpl, "match", Match); 58 | Nan::SetPrototypeMethod(tpl, "replace", Replace); 59 | Nan::SetPrototypeMethod(tpl, "search", Search); 60 | Nan::SetPrototypeMethod(tpl, "split", Split); 61 | 62 | Nan::SetPrototypeTemplate(tpl, "source", Nan::New("(?:)").ToLocalChecked()); 63 | Nan::SetPrototypeTemplate(tpl, "flags", Nan::New("").ToLocalChecked()); 64 | 65 | Nan::SetAccessor(instanceTemplate, Nan::New("source").ToLocalChecked(), GetSource); 66 | Nan::SetAccessor(instanceTemplate, Nan::New("flags").ToLocalChecked(), GetFlags); 67 | Nan::SetAccessor(instanceTemplate, Nan::New("global").ToLocalChecked(), GetGlobal); 68 | Nan::SetAccessor(instanceTemplate, Nan::New("ignoreCase").ToLocalChecked(), GetIgnoreCase); 69 | Nan::SetAccessor(instanceTemplate, Nan::New("multiline").ToLocalChecked(), GetMultiline); 70 | Nan::SetAccessor(instanceTemplate, Nan::New("dotAll").ToLocalChecked(), GetDotAll); 71 | Nan::SetAccessor(instanceTemplate, Nan::New("unicode").ToLocalChecked(), GetUnicode); 72 | Nan::SetAccessor(instanceTemplate, Nan::New("sticky").ToLocalChecked(), GetSticky); 73 | Nan::SetAccessor(instanceTemplate, Nan::New("hasIndices").ToLocalChecked(), GetHasIndices); 74 | Nan::SetAccessor(instanceTemplate, Nan::New("lastIndex").ToLocalChecked(), GetLastIndex, SetLastIndex); 75 | Nan::SetAccessor(instanceTemplate, Nan::New("internalSource").ToLocalChecked(), GetInternalSource); 76 | 77 | auto ctr = Nan::GetFunction(tpl).ToLocalChecked(); 78 | 79 | // properties 80 | 81 | Nan::Export(ctr, "getUtf8Length", GetUtf8Length); 82 | Nan::Export(ctr, "getUtf16Length", GetUtf16Length); 83 | Nan::SetAccessor(v8::Local(ctr), Nan::New("unicodeWarningLevel").ToLocalChecked(), GetUnicodeWarningLevel, SetUnicodeWarningLevel); 84 | 85 | return scope.Escape(ctr); 86 | } 87 | 88 | NODE_MODULE_INIT() 89 | { 90 | Nan::HandleScope scope; 91 | Nan::Set(module->ToObject(context).ToLocalChecked(), Nan::New("exports").ToLocalChecked(), WrappedRE2::Init()); 92 | } 93 | 94 | WrappedRE2::~WrappedRE2() 95 | { 96 | dropCache(); 97 | } 98 | 99 | // private methods 100 | 101 | void WrappedRE2::dropCache() 102 | { 103 | if (!lastString.IsEmpty()) 104 | { 105 | // lastString.ClearWeak(); 106 | lastString.Reset(); 107 | } 108 | if (!lastCache.IsEmpty()) 109 | { 110 | // lastCache.ClearWeak(); 111 | lastCache.Reset(); 112 | } 113 | lastStringValue.clear(); 114 | } 115 | 116 | const StrVal &WrappedRE2::prepareArgument(const v8::Local &arg, bool ignoreLastIndex) 117 | { 118 | size_t startFrom = ignoreLastIndex ? 0 : lastIndex; 119 | 120 | if (!lastString.IsEmpty()) 121 | { 122 | lastString.ClearWeak(); 123 | } 124 | if (!lastCache.IsEmpty()) 125 | { 126 | lastCache.ClearWeak(); 127 | } 128 | 129 | if (lastString == arg && !node::Buffer::HasInstance(arg) && !lastCache.IsEmpty()) 130 | { 131 | // we have a properly cached string 132 | lastStringValue.setIndex(startFrom); 133 | return lastStringValue; 134 | } 135 | 136 | dropCache(); 137 | 138 | if (node::Buffer::HasInstance(arg)) 139 | { 140 | // no need to cache buffers 141 | 142 | lastString.Reset(arg); 143 | 144 | auto argSize = node::Buffer::Length(arg); 145 | lastStringValue.reset(arg, argSize, argSize, startFrom, true); 146 | 147 | return lastStringValue; 148 | } 149 | 150 | // caching the string 151 | 152 | auto t = arg->ToString(Nan::GetCurrentContext()); 153 | if (t.IsEmpty()) 154 | { 155 | // do not process bad strings 156 | lastStringValue.isBad = true; 157 | return lastStringValue; 158 | } 159 | 160 | lastString.Reset(arg); 161 | 162 | auto isolate = v8::Isolate::GetCurrent(); 163 | 164 | auto s = t.ToLocalChecked(); 165 | auto argLength = s->Utf8Length(isolate); 166 | 167 | auto buffer = node::Buffer::New(isolate, s).ToLocalChecked(); 168 | lastCache.Reset(buffer); 169 | 170 | auto argSize = node::Buffer::Length(buffer); 171 | lastStringValue.reset(buffer, argSize, argLength, startFrom); 172 | 173 | return lastStringValue; 174 | }; 175 | 176 | void WrappedRE2::doneWithLastString() 177 | { 178 | if (!lastString.IsEmpty()) 179 | { 180 | static_cast &>(lastString).SetWeak(); 181 | } 182 | 183 | if (!lastCache.IsEmpty()) 184 | { 185 | static_cast &>(lastCache).SetWeak(); 186 | } 187 | } 188 | 189 | // StrVal 190 | 191 | void StrVal::setIndex(size_t newIndex) 192 | { 193 | isValidIndex = newIndex <= length; 194 | if (!isValidIndex) 195 | { 196 | index = newIndex; 197 | byteIndex = 0; 198 | return; 199 | } 200 | 201 | if (newIndex == index) 202 | return; 203 | 204 | if (isBuffer) 205 | { 206 | byteIndex = index = newIndex; 207 | return; 208 | } 209 | 210 | // String 211 | 212 | if (!newIndex) 213 | { 214 | byteIndex = index = 0; 215 | return; 216 | } 217 | 218 | if (newIndex == length) 219 | { 220 | byteIndex = size; 221 | index = length; 222 | return; 223 | } 224 | 225 | byteIndex = index < newIndex ? getUtf16PositionByCounter(data, byteIndex, newIndex - index) : getUtf16PositionByCounter(data, 0, newIndex); 226 | index = newIndex; 227 | } 228 | 229 | static char null_buffer[] = {'\0'}; 230 | 231 | void StrVal::reset(const v8::Local &arg, size_t argSize, size_t argLength, size_t newIndex, bool buffer) 232 | { 233 | clear(); 234 | isBuffer = buffer; 235 | size = argSize; 236 | length = argLength; 237 | data = size ? node::Buffer::Data(arg) : null_buffer; 238 | setIndex(newIndex); 239 | } 240 | -------------------------------------------------------------------------------- /lib/exec.cc: -------------------------------------------------------------------------------- 1 | #include "./wrapped_re2.h" 2 | 3 | #include 4 | 5 | NAN_METHOD(WrappedRE2::Exec) 6 | { 7 | 8 | // unpack arguments 9 | 10 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 11 | if (!re2) 12 | { 13 | info.GetReturnValue().SetNull(); 14 | return; 15 | } 16 | 17 | PrepareLastString prep(re2, info[0]); 18 | StrVal& str = prep; 19 | if (str.isBad) return; // throws an exception 20 | 21 | if (re2->global || re2->sticky) 22 | { 23 | if (!str.isValidIndex) 24 | { 25 | re2->lastIndex = 0; 26 | info.GetReturnValue().SetNull(); 27 | return; 28 | } 29 | } 30 | 31 | // actual work 32 | 33 | std::vector groups(re2->regexp.NumberOfCapturingGroups() + 1); 34 | 35 | if (!re2->regexp.Match(str, str.byteIndex, str.size, re2->sticky ? re2::RE2::ANCHOR_START : re2::RE2::UNANCHORED, &groups[0], groups.size())) 36 | { 37 | if (re2->global || re2->sticky) 38 | { 39 | re2->lastIndex = 0; 40 | } 41 | info.GetReturnValue().SetNull(); 42 | return; 43 | } 44 | 45 | // form a result 46 | 47 | auto result = Nan::New(), indices = Nan::New(); 48 | int indexOffset = re2->global || re2->sticky ? re2->lastIndex : 0; 49 | 50 | if (str.isBuffer) 51 | { 52 | for (size_t i = 0, n = groups.size(); i < n; ++i) 53 | { 54 | const auto &item = groups[i]; 55 | const auto data = item.data(); 56 | if (data) 57 | { 58 | Nan::Set(result, i, Nan::CopyBuffer(data, item.size()).ToLocalChecked()); 59 | if (re2->hasIndices) 60 | { 61 | auto pair = Nan::New(); 62 | auto offset = data - str.data - str.byteIndex; 63 | auto length = item.size(); 64 | Nan::Set(pair, 0, Nan::New(indexOffset + static_cast(offset))); 65 | Nan::Set(pair, 1, Nan::New(indexOffset + static_cast(offset + length))); 66 | Nan::Set(indices, i, pair); 67 | } 68 | } 69 | else 70 | { 71 | Nan::Set(result, i, Nan::Undefined()); 72 | if (re2->hasIndices) 73 | { 74 | Nan::Set(indices, i, Nan::Undefined()); 75 | } 76 | } 77 | } 78 | Nan::Set(result, Nan::New("index").ToLocalChecked(), Nan::New(indexOffset + static_cast(groups[0].data() - str.data - str.byteIndex))); 79 | } 80 | else 81 | { 82 | for (size_t i = 0, n = groups.size(); i < n; ++i) 83 | { 84 | const auto &item = groups[i]; 85 | const auto data = item.data(); 86 | if (data) 87 | { 88 | Nan::Set(result, i, Nan::New(data, item.size()).ToLocalChecked()); 89 | if (re2->hasIndices) 90 | { 91 | auto pair = Nan::New(); 92 | auto offset = getUtf16Length(str.data + str.byteIndex, data); 93 | auto length = getUtf16Length(data, data + item.size()); 94 | Nan::Set(pair, 0, Nan::New(indexOffset + static_cast(offset))); 95 | Nan::Set(pair, 1, Nan::New(indexOffset + static_cast(offset + length))); 96 | Nan::Set(indices, i, pair); 97 | } 98 | } 99 | else 100 | { 101 | Nan::Set(result, i, Nan::Undefined()); 102 | if (re2->hasIndices) 103 | { 104 | Nan::Set(indices, i, Nan::Undefined()); 105 | } 106 | } 107 | } 108 | Nan::Set( 109 | result, 110 | Nan::New("index").ToLocalChecked(), 111 | Nan::New(indexOffset + 112 | static_cast(getUtf16Length(str.data + str.byteIndex, groups[0].data())))); 113 | } 114 | 115 | if (re2->global || re2->sticky) 116 | { 117 | re2->lastIndex += 118 | str.isBuffer ? groups[0].data() - str.data + groups[0].size() - str.byteIndex : getUtf16Length(str.data + str.byteIndex, groups[0].data() + groups[0].size()); 119 | } 120 | 121 | Nan::Set(result, Nan::New("input").ToLocalChecked(), info[0]); 122 | 123 | const auto &groupNames = re2->regexp.CapturingGroupNames(); 124 | if (!groupNames.empty()) 125 | { 126 | auto groups = Nan::New(); 127 | Nan::SetPrototype(groups, Nan::Null()); 128 | 129 | for (auto group : groupNames) 130 | { 131 | auto value = Nan::Get(result, group.first); 132 | if (!value.IsEmpty()) 133 | { 134 | Nan::Set(groups, Nan::New(group.second).ToLocalChecked(), value.ToLocalChecked()); 135 | } 136 | } 137 | 138 | Nan::Set(result, Nan::New("groups").ToLocalChecked(), groups); 139 | 140 | if (re2->hasIndices) 141 | { 142 | auto indexGroups = Nan::New(); 143 | Nan::SetPrototype(indexGroups, Nan::Null()); 144 | 145 | for (auto group : groupNames) 146 | { 147 | auto value = Nan::Get(indices, group.first); 148 | if (!value.IsEmpty()) 149 | { 150 | Nan::Set(indexGroups, Nan::New(group.second).ToLocalChecked(), value.ToLocalChecked()); 151 | } 152 | } 153 | 154 | Nan::Set(indices, Nan::New("groups").ToLocalChecked(), indexGroups); 155 | } 156 | } 157 | else 158 | { 159 | Nan::Set(result, Nan::New("groups").ToLocalChecked(), Nan::Undefined()); 160 | if (re2->hasIndices) 161 | { 162 | Nan::Set(indices, Nan::New("groups").ToLocalChecked(), Nan::Undefined()); 163 | } 164 | } 165 | 166 | if (re2->hasIndices) 167 | { 168 | Nan::Set(result, Nan::New("indices").ToLocalChecked(), indices); 169 | } 170 | 171 | info.GetReturnValue().Set(result); 172 | } 173 | -------------------------------------------------------------------------------- /lib/match.cc: -------------------------------------------------------------------------------- 1 | #include "./wrapped_re2.h" 2 | 3 | #include 4 | 5 | NAN_METHOD(WrappedRE2::Match) 6 | { 7 | 8 | // unpack arguments 9 | 10 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 11 | if (!re2) 12 | { 13 | info.GetReturnValue().SetNull(); 14 | return; 15 | } 16 | 17 | PrepareLastString prep(re2, info[0]); 18 | StrVal& str = prep; 19 | if (str.isBad) return; // throws an exception 20 | 21 | if (!str.isValidIndex) 22 | { 23 | re2->lastIndex = 0; 24 | info.GetReturnValue().SetNull(); 25 | return; 26 | } 27 | 28 | std::vector groups; 29 | size_t byteIndex = 0; 30 | auto anchor = re2::RE2::UNANCHORED; 31 | 32 | // actual work 33 | 34 | if (re2->global) 35 | { 36 | // global: collect all matches 37 | 38 | re2::StringPiece match; 39 | 40 | if (re2->sticky) 41 | { 42 | anchor = re2::RE2::ANCHOR_START; 43 | } 44 | 45 | while (re2->regexp.Match(str, byteIndex, str.size, anchor, &match, 1)) 46 | { 47 | groups.push_back(match); 48 | byteIndex = match.data() - str.data + match.size(); 49 | } 50 | 51 | if (groups.empty()) 52 | { 53 | info.GetReturnValue().SetNull(); 54 | return; 55 | } 56 | } 57 | else 58 | { 59 | // non-global: just like exec() 60 | 61 | if (re2->sticky) 62 | { 63 | byteIndex = str.byteIndex; 64 | anchor = RE2::ANCHOR_START; 65 | } 66 | 67 | groups.resize(re2->regexp.NumberOfCapturingGroups() + 1); 68 | if (!re2->regexp.Match(str, byteIndex, str.size, anchor, &groups[0], groups.size())) 69 | { 70 | if (re2->sticky) 71 | re2->lastIndex = 0; 72 | info.GetReturnValue().SetNull(); 73 | return; 74 | } 75 | } 76 | 77 | // form a result 78 | 79 | auto result = Nan::New(), indices = Nan::New(); 80 | 81 | if (str.isBuffer) 82 | { 83 | for (size_t i = 0, n = groups.size(); i < n; ++i) 84 | { 85 | const auto &item = groups[i]; 86 | const auto data = item.data(); 87 | if (data) 88 | { 89 | Nan::Set(result, i, Nan::CopyBuffer(data, item.size()).ToLocalChecked()); 90 | if (!re2->global && re2->hasIndices) 91 | { 92 | auto pair = Nan::New(); 93 | auto offset = data - str.data - byteIndex; 94 | auto length = item.size(); 95 | Nan::Set(pair, 0, Nan::New(static_cast(offset))); 96 | Nan::Set(pair, 1, Nan::New(static_cast(offset + length))); 97 | Nan::Set(indices, i, pair); 98 | } 99 | } 100 | else 101 | { 102 | Nan::Set(result, i, Nan::Undefined()); 103 | if (!re2->global && re2->hasIndices) 104 | Nan::Set(indices, i, Nan::Undefined()); 105 | } 106 | } 107 | if (!re2->global) 108 | { 109 | Nan::Set(result, Nan::New("index").ToLocalChecked(), Nan::New(static_cast(groups[0].data() - str.data))); 110 | Nan::Set(result, Nan::New("input").ToLocalChecked(), info[0]); 111 | } 112 | } 113 | else 114 | { 115 | for (size_t i = 0, n = groups.size(); i < n; ++i) 116 | { 117 | const auto &item = groups[i]; 118 | const auto data = item.data(); 119 | if (data) 120 | { 121 | Nan::Set(result, i, Nan::New(data, item.size()).ToLocalChecked()); 122 | if (!re2->global && re2->hasIndices) 123 | { 124 | auto pair = Nan::New(); 125 | auto offset = getUtf16Length(str.data + byteIndex, data); 126 | auto length = getUtf16Length(data, data + item.size()); 127 | Nan::Set(pair, 0, Nan::New(static_cast(offset))); 128 | Nan::Set(pair, 1, Nan::New(static_cast(offset + length))); 129 | Nan::Set(indices, i, pair); 130 | } 131 | } 132 | else 133 | { 134 | Nan::Set(result, i, Nan::Undefined()); 135 | if (!re2->global && re2->hasIndices) 136 | { 137 | Nan::Set(indices, i, Nan::Undefined()); 138 | } 139 | } 140 | } 141 | if (!re2->global) 142 | { 143 | Nan::Set(result, Nan::New("index").ToLocalChecked(), Nan::New(static_cast(getUtf16Length(str.data, groups[0].data())))); 144 | Nan::Set(result, Nan::New("input").ToLocalChecked(), info[0]); 145 | } 146 | } 147 | 148 | if (re2->global) 149 | { 150 | re2->lastIndex = 0; 151 | } 152 | else if (re2->sticky) 153 | { 154 | re2->lastIndex += 155 | str.isBuffer ? groups[0].data() - str.data + groups[0].size() - byteIndex : getUtf16Length(str.data + byteIndex, groups[0].data() + groups[0].size()); 156 | } 157 | 158 | if (!re2->global) 159 | { 160 | const auto &groupNames = re2->regexp.CapturingGroupNames(); 161 | if (!groupNames.empty()) 162 | { 163 | auto groups = Nan::New(); 164 | Nan::SetPrototype(groups, Nan::Null()); 165 | 166 | for (auto group : groupNames) 167 | { 168 | auto value = Nan::Get(result, group.first); 169 | if (!value.IsEmpty()) 170 | { 171 | Nan::Set(groups, Nan::New(group.second).ToLocalChecked(), value.ToLocalChecked()); 172 | } 173 | } 174 | 175 | Nan::Set(result, Nan::New("groups").ToLocalChecked(), groups); 176 | 177 | if (re2->hasIndices) 178 | { 179 | auto indexGroups = Nan::New(); 180 | Nan::SetPrototype(indexGroups, Nan::Null()); 181 | 182 | for (auto group : groupNames) 183 | { 184 | auto value = Nan::Get(indices, group.first); 185 | if (!value.IsEmpty()) 186 | { 187 | Nan::Set(indexGroups, Nan::New(group.second).ToLocalChecked(), value.ToLocalChecked()); 188 | } 189 | } 190 | 191 | Nan::Set(indices, Nan::New("groups").ToLocalChecked(), indexGroups); 192 | } 193 | } 194 | else 195 | { 196 | Nan::Set(result, Nan::New("groups").ToLocalChecked(), Nan::Undefined()); 197 | if (re2->hasIndices) 198 | { 199 | Nan::Set(indices, Nan::New("groups").ToLocalChecked(), Nan::Undefined()); 200 | } 201 | } 202 | if (re2->hasIndices) 203 | { 204 | Nan::Set(result, Nan::New("indices").ToLocalChecked(), indices); 205 | } 206 | } 207 | 208 | info.GetReturnValue().Set(result); 209 | } 210 | -------------------------------------------------------------------------------- /lib/new.cc: -------------------------------------------------------------------------------- 1 | #include "./wrapped_re2.h" 2 | #include "./util.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 11 | 12 | inline bool isUpperCaseAlpha(char ch) 13 | { 14 | return 'A' <= ch && ch <= 'Z'; 15 | } 16 | 17 | inline bool isHexadecimal(char ch) 18 | { 19 | return ('0' <= ch && ch <= '9') || ('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z'); 20 | } 21 | 22 | static std::map unicodeClasses = { 23 | {"Uppercase_Letter", "Lu"}, 24 | {"Lowercase_Letter", "Ll"}, 25 | {"Titlecase_Letter", "Lt"}, 26 | {"Cased_Letter", "LC"}, 27 | {"Modifier_Letter", "Lm"}, 28 | {"Other_Letter", "Lo"}, 29 | {"Letter", "L"}, 30 | {"Nonspacing_Mark", "Mn"}, 31 | {"Spacing_Mark", "Mc"}, 32 | {"Enclosing_Mark", "Me"}, 33 | {"Mark", "M"}, 34 | {"Decimal_Number", "Nd"}, 35 | {"Letter_Number", "Nl"}, 36 | {"Other_Number", "No"}, 37 | {"Number", "N"}, 38 | {"Connector_Punctuation", "Pc"}, 39 | {"Dash_Punctuation", "Pd"}, 40 | {"Open_Punctuation", "Ps"}, 41 | {"Close_Punctuation", "Pe"}, 42 | {"Initial_Punctuation", "Pi"}, 43 | {"Final_Punctuation", "Pf"}, 44 | {"Other_Punctuation", "Po"}, 45 | {"Punctuation", "P"}, 46 | {"Math_Symbol", "Sm"}, 47 | {"Currency_Symbol", "Sc"}, 48 | {"Modifier_Symbol", "Sk"}, 49 | {"Other_Symbol", "So"}, 50 | {"Symbol", "S"}, 51 | {"Space_Separator", "Zs"}, 52 | {"Line_Separator", "Zl"}, 53 | {"Paragraph_Separator", "Zp"}, 54 | {"Separator", "Z"}, 55 | {"Control", "Cc"}, 56 | {"Format", "Cf"}, 57 | {"Surrogate", "Cs"}, 58 | {"Private_Use", "Co"}, 59 | {"Unassigned", "Cn"}, 60 | {"Other", "C"}, 61 | }; 62 | 63 | static bool translateRegExp(const char *data, size_t size, bool multiline, std::vector &buffer) 64 | { 65 | std::string result; 66 | bool changed = false; 67 | 68 | if (!size) 69 | { 70 | result = "(?:)"; 71 | changed = true; 72 | } 73 | else if (multiline) 74 | { 75 | result = "(?m)"; 76 | changed = true; 77 | } 78 | 79 | for (size_t i = 0; i < size;) 80 | { 81 | char ch = data[i]; 82 | if (ch == '\\') 83 | { 84 | if (i + 1 < size) 85 | { 86 | ch = data[i + 1]; 87 | switch (ch) 88 | { 89 | case '\\': 90 | result += "\\\\"; 91 | i += 2; 92 | continue; 93 | case 'c': 94 | if (i + 2 < size) 95 | { 96 | ch = data[i + 2]; 97 | if (isUpperCaseAlpha(ch)) 98 | { 99 | result += "\\x"; 100 | result += hex[((ch - '@') / 16) & 15]; 101 | result += hex[(ch - '@') & 15]; 102 | i += 3; 103 | changed = true; 104 | continue; 105 | } 106 | } 107 | result += "\\c"; 108 | i += 2; 109 | continue; 110 | case 'u': 111 | if (i + 2 < size) 112 | { 113 | ch = data[i + 2]; 114 | if (isHexadecimal(ch)) 115 | { 116 | result += "\\x{"; 117 | result += ch; 118 | i += 3; 119 | for (size_t j = 0; j < 3 && i < size; ++i, ++j) 120 | { 121 | ch = data[i]; 122 | if (!isHexadecimal(ch)) 123 | { 124 | break; 125 | } 126 | result += ch; 127 | } 128 | result += '}'; 129 | changed = true; 130 | continue; 131 | } 132 | else if (ch == '{') 133 | { 134 | result += "\\x"; 135 | i += 2; 136 | changed = true; 137 | continue; 138 | } 139 | } 140 | result += "\\u"; 141 | i += 2; 142 | continue; 143 | case 'p': 144 | case 'P': 145 | if (i + 2 < size) { 146 | if (data[i + 2] == '{') { 147 | size_t j = i + 3; 148 | while (j < size && data[j] != '}') ++j; 149 | if (j < size) { 150 | result += "\\"; 151 | result += data[i + 1]; 152 | std::string name(data + i + 3, j - i - 3); 153 | if (unicodeClasses.find(name) != unicodeClasses.end()) { 154 | name = unicodeClasses[name]; 155 | } else if (name.size() > 7 && !strncmp(name.c_str(), "Script=", 7)) { 156 | name = name.substr(7); 157 | } else if (name.size() > 3 && !strncmp(name.c_str(), "sc=", 3)) { 158 | name = name.substr(3); 159 | } 160 | if (name.size() == 1) { 161 | result += name; 162 | } else { 163 | result += "{"; 164 | result += name; 165 | result += "}"; 166 | } 167 | i = j + 1; 168 | changed = true; 169 | continue; 170 | } 171 | } 172 | } 173 | result += "\\"; 174 | result += data[i + 1]; 175 | i += 2; 176 | continue; 177 | default: 178 | result += "\\"; 179 | size_t sym_size = getUtf8CharSize(ch); 180 | result.append(data + i + 1, sym_size); 181 | i += sym_size + 1; 182 | continue; 183 | } 184 | } 185 | } 186 | else if (ch == '/') 187 | { 188 | result += "\\/"; 189 | i += 1; 190 | changed = true; 191 | continue; 192 | } 193 | else if (ch == '(' && i + 2 < size && data[i + 1] == '?' && data[i + 2] == '<') 194 | { 195 | if (i + 3 >= size || (data[i + 3] != '=' && data[i + 3] != '!')) 196 | { 197 | result += "(?P<"; 198 | i += 3; 199 | changed = true; 200 | continue; 201 | } 202 | } 203 | size_t sym_size = getUtf8CharSize(ch); 204 | result.append(data + i, sym_size); 205 | i += sym_size; 206 | } 207 | 208 | if (!changed) 209 | { 210 | return false; 211 | } 212 | 213 | buffer.resize(0); 214 | buffer.insert(buffer.end(), result.data(), result.data() + result.size()); 215 | buffer.push_back('\0'); 216 | 217 | return true; 218 | } 219 | 220 | static std::string escapeRegExp(const char *data, size_t size) 221 | { 222 | std::string result; 223 | 224 | if (!size) 225 | { 226 | result = "(?:)"; 227 | } 228 | 229 | size_t prevBackSlashes = 0; 230 | for (size_t i = 0; i < size;) 231 | { 232 | char ch = data[i]; 233 | if (ch == '\\') 234 | { 235 | ++prevBackSlashes; 236 | } 237 | else if (ch == '/' && !(prevBackSlashes & 1)) 238 | { 239 | result += "\\/"; 240 | i += 1; 241 | prevBackSlashes = 0; 242 | continue; 243 | } 244 | else 245 | { 246 | prevBackSlashes = 0; 247 | } 248 | size_t sym_size = getUtf8CharSize(ch); 249 | result.append(data + i, sym_size); 250 | i += sym_size; 251 | } 252 | 253 | return result; 254 | } 255 | 256 | bool WrappedRE2::alreadyWarnedAboutUnicode = false; 257 | 258 | static const char *deprecationMessage = "BMP patterns aren't supported by node-re2. An implicit \"u\" flag is assumed by the RE2 constructor. In a future major version, calling the RE2 constructor without the \"u\" flag may become forbidden, or cause a different behavior. Please see https://github.com/uhop/node-re2/issues/21 for more information."; 259 | 260 | inline bool ensureUniqueNamedGroups(const std::map &groups) 261 | { 262 | std::unordered_set names; 263 | 264 | for (auto group : groups) 265 | { 266 | if (!names.insert(group.second).second) 267 | { 268 | return false; 269 | } 270 | } 271 | 272 | return true; 273 | } 274 | 275 | NAN_METHOD(WrappedRE2::New) 276 | { 277 | 278 | if (!info.IsConstructCall()) 279 | { 280 | // call a constructor and return the result 281 | 282 | std::vector> parameters(info.Length()); 283 | for (size_t i = 0, n = info.Length(); i < n; ++i) 284 | { 285 | parameters[i] = info[i]; 286 | } 287 | auto isolate = v8::Isolate::GetCurrent(); 288 | auto p_tpl = Nan::GetIsolateData>(isolate); 289 | auto newObject = Nan::NewInstance(Nan::GetFunction(p_tpl->Get(isolate)).ToLocalChecked(), parameters.size(), ¶meters[0]); 290 | if (!newObject.IsEmpty()) 291 | { 292 | info.GetReturnValue().Set(newObject.ToLocalChecked()); 293 | } 294 | return; 295 | } 296 | 297 | // process arguments 298 | 299 | std::vector buffer; 300 | 301 | char *data = NULL; 302 | size_t size = 0; 303 | 304 | std::string source; 305 | bool global = false; 306 | bool ignoreCase = false; 307 | bool multiline = false; 308 | bool dotAll = false; 309 | bool unicode = false; 310 | bool sticky = false; 311 | bool hasIndices = false; 312 | 313 | auto context = Nan::GetCurrentContext(); 314 | bool needFlags = true; 315 | 316 | if (info.Length() > 1) 317 | { 318 | if (info[1]->IsString()) 319 | { 320 | auto isolate = v8::Isolate::GetCurrent(); 321 | auto t = info[1]->ToString(Nan::GetCurrentContext()); 322 | auto s = t.ToLocalChecked(); 323 | size = s->Utf8Length(isolate); 324 | buffer.resize(size + 1); 325 | data = &buffer[0]; 326 | s->WriteUtf8(isolate, data, buffer.size()); 327 | buffer[size] = '\0'; 328 | } 329 | else if (node::Buffer::HasInstance(info[1])) 330 | { 331 | size = node::Buffer::Length(info[1]); 332 | data = node::Buffer::Data(info[1]); 333 | } 334 | for (size_t i = 0; i < size; ++i) 335 | { 336 | switch (data[i]) 337 | { 338 | case 'g': 339 | global = true; 340 | break; 341 | case 'i': 342 | ignoreCase = true; 343 | break; 344 | case 'm': 345 | multiline = true; 346 | break; 347 | case 's': 348 | dotAll = true; 349 | break; 350 | case 'u': 351 | unicode = true; 352 | break; 353 | case 'y': 354 | sticky = true; 355 | break; 356 | case 'd': 357 | hasIndices = true; 358 | break; 359 | } 360 | } 361 | size = 0; 362 | needFlags = false; 363 | } 364 | 365 | bool needConversion = true; 366 | 367 | if (node::Buffer::HasInstance(info[0])) 368 | { 369 | size = node::Buffer::Length(info[0]); 370 | data = node::Buffer::Data(info[0]); 371 | 372 | source = escapeRegExp(data, size); 373 | } 374 | else if (info[0]->IsRegExp()) 375 | { 376 | const auto *re = v8::RegExp::Cast(*info[0]); 377 | 378 | auto isolate = v8::Isolate::GetCurrent(); 379 | auto t = re->GetSource()->ToString(Nan::GetCurrentContext()); 380 | auto s = t.ToLocalChecked(); 381 | size = s->Utf8Length(isolate); 382 | buffer.resize(size + 1); 383 | data = &buffer[0]; 384 | s->WriteUtf8(isolate, data, buffer.size()); 385 | buffer[size] = '\0'; 386 | 387 | source = escapeRegExp(data, size); 388 | 389 | if (needFlags) 390 | { 391 | v8::RegExp::Flags flags = re->GetFlags(); 392 | global = bool(flags & v8::RegExp::kGlobal); 393 | ignoreCase = bool(flags & v8::RegExp::kIgnoreCase); 394 | multiline = bool(flags & v8::RegExp::kMultiline); 395 | dotAll = bool(flags & v8::RegExp::kDotAll); 396 | unicode = bool(flags & v8::RegExp::kUnicode); 397 | sticky = bool(flags & v8::RegExp::kSticky); 398 | hasIndices = bool(flags & v8::RegExp::kHasIndices); 399 | needFlags = false; 400 | } 401 | } 402 | else if (info[0]->IsObject() && !info[0]->IsString()) 403 | { 404 | WrappedRE2 *re2 = nullptr; 405 | auto object = info[0]->ToObject(context).ToLocalChecked(); 406 | if (!object.IsEmpty() && object->InternalFieldCount() > 0) 407 | { 408 | re2 = Nan::ObjectWrap::Unwrap(object); 409 | } 410 | if (re2) 411 | { 412 | const auto &pattern = re2->regexp.pattern(); 413 | size = pattern.size(); 414 | buffer.resize(size); 415 | data = &buffer[0]; 416 | memcpy(data, pattern.data(), size); 417 | needConversion = false; 418 | 419 | source = re2->source; 420 | 421 | if (needFlags) 422 | { 423 | global = re2->global; 424 | ignoreCase = re2->ignoreCase; 425 | multiline = re2->multiline; 426 | dotAll = re2->dotAll; 427 | unicode = true; 428 | sticky = re2->sticky; 429 | hasIndices = re2->hasIndices; 430 | needFlags = false; 431 | } 432 | } 433 | } 434 | else if (info[0]->IsString()) 435 | { 436 | auto isolate = v8::Isolate::GetCurrent(); 437 | auto t = info[0]->ToString(Nan::GetCurrentContext()); 438 | auto s = t.ToLocalChecked(); 439 | size = s->Utf8Length(isolate); 440 | buffer.resize(size + 1); 441 | data = &buffer[0]; 442 | s->WriteUtf8(isolate, data, buffer.size()); 443 | buffer[size] = '\0'; 444 | 445 | source = escapeRegExp(data, size); 446 | } 447 | 448 | if (!data) 449 | { 450 | return Nan::ThrowTypeError("Expected string, Buffer, RegExp, or RE2 as the 1st argument."); 451 | } 452 | 453 | if (!unicode) 454 | { 455 | switch (unicodeWarningLevel) 456 | { 457 | case THROW: 458 | return Nan::ThrowSyntaxError(deprecationMessage); 459 | case WARN: 460 | printDeprecationWarning(deprecationMessage); 461 | break; 462 | case WARN_ONCE: 463 | if (!alreadyWarnedAboutUnicode) 464 | { 465 | printDeprecationWarning(deprecationMessage); 466 | alreadyWarnedAboutUnicode = true; 467 | } 468 | break; 469 | default: 470 | break; 471 | } 472 | } 473 | 474 | if (needConversion && translateRegExp(data, size, multiline, buffer)) 475 | { 476 | size = buffer.size() - 1; 477 | data = &buffer[0]; 478 | } 479 | 480 | // create and return an object 481 | 482 | re2::RE2::Options options; 483 | options.set_case_sensitive(!ignoreCase); 484 | options.set_one_line(!multiline); // to track this state, otherwise it is ignored 485 | options.set_dot_nl(dotAll); 486 | options.set_log_errors(false); // inappropriate when embedding 487 | 488 | std::unique_ptr re2(new WrappedRE2(re2::StringPiece(data, size), options, source, global, ignoreCase, multiline, dotAll, sticky, hasIndices)); 489 | if (!re2->regexp.ok()) 490 | { 491 | return Nan::ThrowSyntaxError(re2->regexp.error().c_str()); 492 | } 493 | if (!ensureUniqueNamedGroups(re2->regexp.CapturingGroupNames())) 494 | { 495 | return Nan::ThrowSyntaxError("duplicate capture group name"); 496 | } 497 | re2->Wrap(info.This()); 498 | re2.release(); 499 | 500 | info.GetReturnValue().Set(info.This()); 501 | } 502 | -------------------------------------------------------------------------------- /lib/replace.cc: -------------------------------------------------------------------------------- 1 | #include "./wrapped_re2.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | inline int getMaxSubmatch( 9 | const char *data, 10 | size_t size, 11 | const std::map &namedGroups) 12 | { 13 | int maxSubmatch = 0, index, index2; 14 | const char *nameBegin; 15 | const char *nameEnd; 16 | for (size_t i = 0; i < size;) 17 | { 18 | char ch = data[i]; 19 | if (ch == '$') 20 | { 21 | if (i + 1 < size) 22 | { 23 | ch = data[i + 1]; 24 | switch (ch) 25 | { 26 | case '$': 27 | case '&': 28 | case '`': 29 | case '\'': 30 | i += 2; 31 | continue; 32 | case '0': 33 | case '1': 34 | case '2': 35 | case '3': 36 | case '4': 37 | case '5': 38 | case '6': 39 | case '7': 40 | case '8': 41 | case '9': 42 | index = ch - '0'; 43 | if (i + 2 < size) 44 | { 45 | ch = data[i + 2]; 46 | if ('0' <= ch && ch <= '9') 47 | { 48 | index2 = index * 10 + (ch - '0'); 49 | if (maxSubmatch < index2) 50 | maxSubmatch = index2; 51 | i += 3; 52 | continue; 53 | } 54 | } 55 | if (maxSubmatch < index) 56 | maxSubmatch = index; 57 | i += 2; 58 | continue; 59 | case '<': 60 | nameBegin = data + i + 2; 61 | nameEnd = (const char *)memchr(nameBegin, '>', size - i - 2); 62 | if (nameEnd) 63 | { 64 | std::string name(nameBegin, nameEnd - nameBegin); 65 | auto group = namedGroups.find(name); 66 | if (group != namedGroups.end()) 67 | { 68 | index = group->second; 69 | if (maxSubmatch < index) 70 | maxSubmatch = index; 71 | } 72 | i = nameEnd + 1 - data; 73 | } 74 | else 75 | { 76 | i += 2; 77 | } 78 | continue; 79 | } 80 | } 81 | ++i; 82 | continue; 83 | } 84 | i += getUtf8CharSize(ch); 85 | } 86 | return maxSubmatch; 87 | } 88 | 89 | inline std::string replace( 90 | const char *data, 91 | size_t size, 92 | const std::vector &groups, 93 | const re2::StringPiece &str, 94 | const std::map &namedGroups) 95 | { 96 | std::string result; 97 | size_t index, index2; 98 | const char *nameBegin; 99 | const char *nameEnd; 100 | for (size_t i = 0; i < size;) 101 | { 102 | char ch = data[i]; 103 | if (ch == '$') 104 | { 105 | if (i + 1 < size) 106 | { 107 | ch = data[i + 1]; 108 | switch (ch) 109 | { 110 | case '$': 111 | result += ch; 112 | i += 2; 113 | continue; 114 | case '&': 115 | result += (std::string)groups[0]; 116 | i += 2; 117 | continue; 118 | case '`': 119 | result += std::string(str.data(), groups[0].data() - str.data()); 120 | i += 2; 121 | continue; 122 | case '\'': 123 | result += std::string(groups[0].data() + groups[0].size(), 124 | str.data() + str.size() - groups[0].data() - groups[0].size()); 125 | i += 2; 126 | continue; 127 | case '0': 128 | case '1': 129 | case '2': 130 | case '3': 131 | case '4': 132 | case '5': 133 | case '6': 134 | case '7': 135 | case '8': 136 | case '9': 137 | index = ch - '0'; 138 | if (i + 2 < size) 139 | { 140 | ch = data[i + 2]; 141 | if ('0' <= ch && ch <= '9') 142 | { 143 | i += 3; 144 | index2 = index * 10 + (ch - '0'); 145 | if (index2 && index2 < groups.size()) 146 | { 147 | result += (std::string)groups[index2]; 148 | continue; 149 | } 150 | else if (index && index < groups.size()) 151 | { 152 | result += (std::string)groups[index]; 153 | result += ch; 154 | continue; 155 | } 156 | result += '$'; 157 | result += '0' + index; 158 | result += ch; 159 | continue; 160 | } 161 | ch = '0' + index; 162 | } 163 | i += 2; 164 | if (index && index < groups.size()) 165 | { 166 | result += (std::string)groups[index]; 167 | continue; 168 | } 169 | result += '$'; 170 | result += ch; 171 | continue; 172 | case '<': 173 | if (!namedGroups.empty()) 174 | { 175 | nameBegin = data + i + 2; 176 | nameEnd = (const char *)memchr(nameBegin, '>', size - i - 2); 177 | if (nameEnd) 178 | { 179 | std::string name(nameBegin, nameEnd - nameBegin); 180 | auto group = namedGroups.find(name); 181 | if (group != namedGroups.end()) 182 | { 183 | index = group->second; 184 | result += (std::string)groups[index]; 185 | } 186 | i = nameEnd + 1 - data; 187 | } 188 | else 189 | { 190 | result += "$<"; 191 | i += 2; 192 | } 193 | } 194 | else 195 | { 196 | result += "$<"; 197 | i += 2; 198 | } 199 | continue; 200 | } 201 | } 202 | result += '$'; 203 | ++i; 204 | continue; 205 | } 206 | size_t sym_size = getUtf8CharSize(ch); 207 | result.append(data + i, sym_size); 208 | i += sym_size; 209 | } 210 | return result; 211 | } 212 | 213 | static Nan::Maybe replace( 214 | WrappedRE2 *re2, 215 | const StrVal &replacee, 216 | const char *replacer, 217 | size_t replacer_size) 218 | { 219 | const re2::StringPiece str = replacee; 220 | const char *data = str.data(); 221 | size_t size = str.size(); 222 | 223 | const auto &namedGroups = re2->regexp.NamedCapturingGroups(); 224 | 225 | std::vector groups(std::min(re2->regexp.NumberOfCapturingGroups(), getMaxSubmatch(replacer, replacer_size, namedGroups)) + 1); 226 | const auto &match = groups[0]; 227 | 228 | size_t byteIndex = 0; 229 | std::string result; 230 | auto anchor = re2::RE2::UNANCHORED; 231 | 232 | if (re2->sticky) 233 | { 234 | if (!re2->global) 235 | byteIndex = replacee.byteIndex; 236 | anchor = re2::RE2::ANCHOR_START; 237 | } 238 | 239 | if (byteIndex) 240 | { 241 | result = std::string(data, byteIndex); 242 | } 243 | 244 | bool noMatch = true; 245 | while (byteIndex <= size && re2->regexp.Match(str, byteIndex, size, anchor, &groups[0], groups.size())) 246 | { 247 | noMatch = false; 248 | auto offset = match.data() - data; 249 | if (!re2->global && re2->sticky) 250 | { 251 | re2->lastIndex += 252 | replacee.isBuffer ? offset + match.size() - byteIndex : getUtf16Length(data + byteIndex, match.data() + match.size()); 253 | } 254 | if (match.data() == data || offset > static_cast(byteIndex)) 255 | { 256 | result += std::string(data + byteIndex, offset - byteIndex); 257 | } 258 | result += replace(replacer, replacer_size, groups, str, namedGroups); 259 | if (match.size()) 260 | { 261 | byteIndex = offset + match.size(); 262 | } 263 | else if ((size_t)offset < size) 264 | { 265 | auto sym_size = getUtf8CharSize(data[offset]); 266 | result.append(data + offset, sym_size); 267 | byteIndex = offset + sym_size; 268 | } 269 | else 270 | { 271 | byteIndex = size; 272 | break; 273 | } 274 | if (!re2->global) 275 | { 276 | break; 277 | } 278 | } 279 | if (byteIndex < size) 280 | { 281 | result += std::string(data + byteIndex, size - byteIndex); 282 | } 283 | 284 | if (re2->global) 285 | { 286 | re2->lastIndex = 0; 287 | } 288 | else if (re2->sticky) 289 | { 290 | if (noMatch) 291 | re2->lastIndex = 0; 292 | } 293 | 294 | return Nan::Just(result); 295 | } 296 | 297 | inline Nan::Maybe replace( 298 | const Nan::Callback *replacer, 299 | const std::vector &groups, 300 | const re2::StringPiece &str, 301 | const v8::Local &input, 302 | bool useBuffers, 303 | const std::map &namedGroups) 304 | { 305 | std::vector> argv; 306 | 307 | auto context = Nan::GetCurrentContext(); 308 | 309 | if (useBuffers) 310 | { 311 | for (size_t i = 0, n = groups.size(); i < n; ++i) 312 | { 313 | const auto &item = groups[i]; 314 | const auto data = item.data(); 315 | if (data) 316 | { 317 | argv.push_back(Nan::CopyBuffer(data, item.size()).ToLocalChecked()); 318 | } 319 | else 320 | { 321 | argv.push_back(Nan::Undefined()); 322 | } 323 | } 324 | argv.push_back(Nan::New(static_cast(groups[0].data() - str.data()))); 325 | } 326 | else 327 | { 328 | for (size_t i = 0, n = groups.size(); i < n; ++i) 329 | { 330 | const auto &item = groups[i]; 331 | const auto data = item.data(); 332 | if (data) 333 | { 334 | argv.push_back(Nan::New(data, item.size()).ToLocalChecked()); 335 | } 336 | else 337 | { 338 | argv.push_back(Nan::Undefined()); 339 | } 340 | } 341 | argv.push_back(Nan::New(static_cast(getUtf16Length(str.data(), groups[0].data())))); 342 | } 343 | argv.push_back(input); 344 | 345 | if (!namedGroups.empty()) 346 | { 347 | auto groups = Nan::New(); 348 | Nan::SetPrototype(groups, Nan::Null()); 349 | 350 | for (std::pair group : namedGroups) 351 | { 352 | Nan::Set(groups, Nan::New(group.first).ToLocalChecked(), argv[group.second]); 353 | } 354 | 355 | argv.push_back(groups); 356 | } 357 | 358 | auto maybeResult = Nan::CallAsFunction(replacer->GetFunction(), context->Global(), static_cast(argv.size()), &argv[0]); 359 | 360 | if (maybeResult.IsEmpty()) 361 | { 362 | return Nan::Nothing(); 363 | } 364 | 365 | auto result = maybeResult.ToLocalChecked(); 366 | 367 | if (node::Buffer::HasInstance(result)) 368 | { 369 | return Nan::Just(std::string(node::Buffer::Data(result), node::Buffer::Length(result))); 370 | } 371 | 372 | auto t = result->ToString(Nan::GetCurrentContext()); 373 | if (t.IsEmpty()) 374 | { 375 | return Nan::Nothing(); 376 | } 377 | 378 | v8::String::Utf8Value s(v8::Isolate::GetCurrent(), t.ToLocalChecked()); 379 | return Nan::Just(std::string(*s)); 380 | } 381 | 382 | static Nan::Maybe replace( 383 | WrappedRE2 *re2, 384 | const StrVal &replacee, 385 | const Nan::Callback *replacer, 386 | const v8::Local &input, 387 | bool useBuffers) 388 | { 389 | const re2::StringPiece str = replacee; 390 | const char *data = str.data(); 391 | size_t size = str.size(); 392 | 393 | std::vector groups(re2->regexp.NumberOfCapturingGroups() + 1); 394 | const auto &match = groups[0]; 395 | 396 | size_t byteIndex = 0; 397 | std::string result; 398 | auto anchor = re2::RE2::UNANCHORED; 399 | 400 | if (re2->sticky) 401 | { 402 | if (!re2->global) 403 | byteIndex = replacee.byteIndex; 404 | anchor = RE2::ANCHOR_START; 405 | } 406 | 407 | if (byteIndex) 408 | { 409 | result = std::string(data, byteIndex); 410 | } 411 | 412 | const auto &namedGroups = re2->regexp.NamedCapturingGroups(); 413 | 414 | bool noMatch = true; 415 | while (byteIndex <= size && re2->regexp.Match(str, byteIndex, size, anchor, &groups[0], groups.size())) 416 | { 417 | noMatch = false; 418 | auto offset = match.data() - data; 419 | if (!re2->global && re2->sticky) 420 | { 421 | re2->lastIndex += replacee.isBuffer ? offset + match.size() - byteIndex : getUtf16Length(data + byteIndex, match.data() + match.size()); 422 | } 423 | if (match.data() == data || offset > static_cast(byteIndex)) 424 | { 425 | result += std::string(data + byteIndex, offset - byteIndex); 426 | } 427 | const auto part = replace(replacer, groups, str, input, useBuffers, namedGroups); 428 | if (part.IsNothing()) 429 | { 430 | return part; 431 | } 432 | result += part.FromJust(); 433 | if (match.size()) 434 | { 435 | byteIndex = offset + match.size(); 436 | } 437 | else if ((size_t)offset < size) 438 | { 439 | auto sym_size = getUtf8CharSize(data[offset]); 440 | result.append(data + offset, sym_size); 441 | byteIndex = offset + sym_size; 442 | } 443 | else 444 | { 445 | byteIndex = size; 446 | break; 447 | } 448 | if (!re2->global) 449 | { 450 | break; 451 | } 452 | } 453 | if (byteIndex < size) 454 | { 455 | result += std::string(data + byteIndex, size - byteIndex); 456 | } 457 | 458 | if (re2->global) 459 | { 460 | re2->lastIndex = 0; 461 | } 462 | else if (re2->sticky) 463 | { 464 | if (noMatch) 465 | { 466 | re2->lastIndex = 0; 467 | } 468 | } 469 | 470 | return Nan::Just(result); 471 | } 472 | 473 | static bool requiresBuffers(const v8::Local &f) 474 | { 475 | auto flag(Nan::Get(f, Nan::New("useBuffers").ToLocalChecked()).ToLocalChecked()); 476 | if (flag->IsUndefined() || flag->IsNull() || flag->IsFalse()) 477 | { 478 | return false; 479 | } 480 | if (flag->IsNumber()) 481 | { 482 | return flag->NumberValue(Nan::GetCurrentContext()).FromMaybe(0) != 0; 483 | } 484 | if (flag->IsString()) 485 | { 486 | return flag->ToString(Nan::GetCurrentContext()).ToLocalChecked()->Length() > 0; 487 | } 488 | return true; 489 | } 490 | 491 | NAN_METHOD(WrappedRE2::Replace) 492 | { 493 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 494 | if (!re2) 495 | { 496 | info.GetReturnValue().Set(info[0]); 497 | return; 498 | } 499 | 500 | PrepareLastString prep(re2, info[0]); 501 | StrVal& replacee = prep; 502 | if (replacee.isBad) return; // throws an exception 503 | 504 | if (!replacee.isValidIndex) 505 | { 506 | info.GetReturnValue().Set(info[0]); 507 | return; 508 | } 509 | 510 | std::string result; 511 | 512 | if (info[1]->IsFunction()) 513 | { 514 | auto fun = info[1].As(); 515 | const std::unique_ptr cb(new Nan::Callback(fun)); 516 | const auto replaced = replace(re2, replacee, cb.get(), info[0], requiresBuffers(fun)); 517 | if (replaced.IsNothing()) 518 | { 519 | info.GetReturnValue().Set(info[0]); 520 | return; 521 | } 522 | result = replaced.FromJust(); 523 | } 524 | else 525 | { 526 | v8::Local replacer; 527 | if (node::Buffer::HasInstance(info[1])) 528 | { 529 | replacer = info[1].As(); 530 | } 531 | else 532 | { 533 | auto t = info[1]->ToString(Nan::GetCurrentContext()); 534 | if (t.IsEmpty()) 535 | return; // throws an exception 536 | replacer = node::Buffer::New(v8::Isolate::GetCurrent(), t.ToLocalChecked()).ToLocalChecked(); 537 | } 538 | 539 | auto data = node::Buffer::Data(replacer); 540 | auto size = node::Buffer::Length(replacer); 541 | 542 | const auto replaced = replace(re2, replacee, data, size); 543 | if (replaced.IsNothing()) 544 | { 545 | info.GetReturnValue().Set(info[0]); 546 | return; 547 | } 548 | result = replaced.FromJust(); 549 | } 550 | 551 | if (replacee.isBuffer) 552 | { 553 | info.GetReturnValue().Set(Nan::CopyBuffer(result.data(), result.size()).ToLocalChecked()); 554 | return; 555 | } 556 | info.GetReturnValue().Set(Nan::New(result).ToLocalChecked()); 557 | } 558 | -------------------------------------------------------------------------------- /lib/search.cc: -------------------------------------------------------------------------------- 1 | #include "./wrapped_re2.h" 2 | 3 | NAN_METHOD(WrappedRE2::Search) 4 | { 5 | 6 | // unpack arguments 7 | 8 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 9 | if (!re2) 10 | { 11 | info.GetReturnValue().Set(-1); 12 | return; 13 | } 14 | 15 | PrepareLastString prep(re2, info[0]); 16 | StrVal& str = prep; 17 | if (str.isBad) return; // throws an exception 18 | 19 | if (!str.data) 20 | return; 21 | 22 | // actual work 23 | 24 | re2::StringPiece match; 25 | 26 | if (re2->regexp.Match(str, 0, str.size, re2->sticky ? re2::RE2::ANCHOR_START : re2::RE2::UNANCHORED, &match, 1)) 27 | { 28 | info.GetReturnValue().Set(static_cast(str.isBuffer ? match.data() - str.data : getUtf16Length(str.data, match.data()))); 29 | return; 30 | } 31 | 32 | info.GetReturnValue().Set(-1); 33 | } 34 | -------------------------------------------------------------------------------- /lib/split.cc: -------------------------------------------------------------------------------- 1 | #include "./wrapped_re2.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | NAN_METHOD(WrappedRE2::Split) 8 | { 9 | 10 | auto result = Nan::New(); 11 | 12 | // unpack arguments 13 | 14 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 15 | if (!re2) 16 | { 17 | Nan::Set(result, 0, info[0]); 18 | info.GetReturnValue().Set(result); 19 | return; 20 | } 21 | 22 | PrepareLastString prep(re2, info[0]); 23 | StrVal& str = prep; 24 | if (str.isBad) return; // throws an exception 25 | 26 | size_t limit = std::numeric_limits::max(); 27 | if (info.Length() > 1 && info[1]->IsNumber()) 28 | { 29 | size_t lim = info[1]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0); 30 | if (lim > 0) 31 | { 32 | limit = lim; 33 | } 34 | } 35 | 36 | // actual work 37 | 38 | std::vector groups(re2->regexp.NumberOfCapturingGroups() + 1), pieces; 39 | const auto &match = groups[0]; 40 | size_t byteIndex = 0; 41 | 42 | while (byteIndex < str.size && re2->regexp.Match(str, byteIndex, str.size, RE2::UNANCHORED, &groups[0], groups.size())) 43 | { 44 | if (match.size()) 45 | { 46 | pieces.push_back(re2::StringPiece(str.data + byteIndex, match.data() - str.data - byteIndex)); 47 | byteIndex = match.data() - str.data + match.size(); 48 | pieces.insert(pieces.end(), groups.begin() + 1, groups.end()); 49 | } 50 | else 51 | { 52 | size_t sym_size = getUtf8CharSize(str.data[byteIndex]); 53 | pieces.push_back(re2::StringPiece(str.data + byteIndex, sym_size)); 54 | byteIndex += sym_size; 55 | } 56 | if (pieces.size() >= limit) 57 | { 58 | break; 59 | } 60 | } 61 | if (pieces.size() < limit && (byteIndex < str.size || (byteIndex == str.size && match.size()))) 62 | { 63 | pieces.push_back(re2::StringPiece(str.data + byteIndex, str.size - byteIndex)); 64 | } 65 | 66 | if (pieces.empty()) 67 | { 68 | Nan::Set(result, 0, info[0]); 69 | info.GetReturnValue().Set(result); 70 | return; 71 | } 72 | 73 | // form a result 74 | 75 | if (str.isBuffer) 76 | { 77 | for (size_t i = 0, n = std::min(pieces.size(), limit); i < n; ++i) 78 | { 79 | const auto &item = pieces[i]; 80 | Nan::Set(result, i, Nan::CopyBuffer(item.data(), item.size()).ToLocalChecked()); 81 | } 82 | } 83 | else 84 | { 85 | for (size_t i = 0, n = std::min(pieces.size(), limit); i < n; ++i) 86 | { 87 | const auto &item = pieces[i]; 88 | Nan::Set(result, i, Nan::New(item.data(), item.size()).ToLocalChecked()); 89 | } 90 | } 91 | 92 | info.GetReturnValue().Set(result); 93 | } 94 | -------------------------------------------------------------------------------- /lib/test.cc: -------------------------------------------------------------------------------- 1 | #include "./wrapped_re2.h" 2 | 3 | #include 4 | 5 | NAN_METHOD(WrappedRE2::Test) 6 | { 7 | 8 | // unpack arguments 9 | 10 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 11 | if (!re2) 12 | { 13 | info.GetReturnValue().Set(false); 14 | return; 15 | } 16 | 17 | PrepareLastString prep(re2, info[0]); 18 | StrVal& str = prep; 19 | if (str.isBad) return; // throws an exception 20 | 21 | if (!re2->global && !re2->sticky) 22 | { 23 | info.GetReturnValue().Set(re2->regexp.Match(str, 0, str.size, re2::RE2::UNANCHORED, NULL, 0)); 24 | return; 25 | } 26 | 27 | if (!str.isValidIndex) 28 | { 29 | re2->lastIndex = 0; 30 | info.GetReturnValue().SetNull(); 31 | return; 32 | } 33 | 34 | // actual work 35 | 36 | re2::StringPiece match; 37 | if (re2->regexp.Match(str, str.byteIndex, str.size, re2->sticky ? re2::RE2::ANCHOR_START : re2::RE2::UNANCHORED, &match, 1)) 38 | { 39 | re2->lastIndex += 40 | str.isBuffer ? match.data() - str.data + match.size() - str.byteIndex : getUtf16Length(str.data + str.byteIndex, match.data() + match.size()); 41 | info.GetReturnValue().Set(true); 42 | return; 43 | } 44 | re2->lastIndex = 0; 45 | info.GetReturnValue().Set(false); 46 | } 47 | -------------------------------------------------------------------------------- /lib/to_string.cc: -------------------------------------------------------------------------------- 1 | #include "./wrapped_re2.h" 2 | 3 | #include 4 | 5 | NAN_METHOD(WrappedRE2::ToString) 6 | { 7 | 8 | // unpack arguments 9 | 10 | auto re2 = Nan::ObjectWrap::Unwrap(info.This()); 11 | if (!re2) 12 | { 13 | info.GetReturnValue().SetEmptyString(); 14 | return; 15 | } 16 | 17 | // actual work 18 | 19 | std::string buffer("/"); 20 | buffer += re2->source; 21 | buffer += "/"; 22 | 23 | if (re2->global) 24 | { 25 | buffer += "g"; 26 | } 27 | if (re2->ignoreCase) 28 | { 29 | buffer += "i"; 30 | } 31 | if (re2->multiline) 32 | { 33 | buffer += "m"; 34 | } 35 | if (re2->dotAll) 36 | { 37 | buffer += "s"; 38 | } 39 | buffer += "u"; 40 | if (re2->sticky) 41 | { 42 | buffer += "y"; 43 | } 44 | 45 | info.GetReturnValue().Set(Nan::New(buffer).ToLocalChecked()); 46 | } 47 | -------------------------------------------------------------------------------- /lib/util.cc: -------------------------------------------------------------------------------- 1 | #include "./util.h" 2 | 3 | void consoleCall(const v8::Local &methodName, v8::Local text) 4 | { 5 | auto context = Nan::GetCurrentContext(); 6 | 7 | auto maybeConsole = bind( 8 | Nan::Get(context->Global(), Nan::New("console").ToLocalChecked()), 9 | [context](v8::Local console) { return console->ToObject(context); }); 10 | if (maybeConsole.IsEmpty()) 11 | return; 12 | 13 | auto console = maybeConsole.ToLocalChecked(); 14 | 15 | auto maybeMethod = bind( 16 | Nan::Get(console, methodName), 17 | [context](v8::Local method) { return method->ToObject(context); }); 18 | if (maybeMethod.IsEmpty()) 19 | return; 20 | 21 | auto method = maybeMethod.ToLocalChecked(); 22 | if (!method->IsFunction()) 23 | return; 24 | 25 | Nan::CallAsFunction(method, console, 1, &text); 26 | } 27 | 28 | void printDeprecationWarning(const char *warning) 29 | { 30 | std::string prefixedWarning = "DeprecationWarning: "; 31 | prefixedWarning += warning; 32 | consoleCall(Nan::New("error").ToLocalChecked(), Nan::New(prefixedWarning).ToLocalChecked()); 33 | } 34 | 35 | v8::Local callToString(const v8::Local &object) 36 | { 37 | auto context = Nan::GetCurrentContext(); 38 | 39 | auto maybeMethod = bind( 40 | Nan::Get(object, Nan::New("toString").ToLocalChecked()), 41 | [context](v8::Local method) { return method->ToObject(context); }); 42 | if (maybeMethod.IsEmpty()) 43 | return Nan::New("No toString() is found").ToLocalChecked(); 44 | 45 | auto method = maybeMethod.ToLocalChecked(); 46 | if (!method->IsFunction()) 47 | return Nan::New("No toString() is found").ToLocalChecked(); 48 | 49 | auto maybeResult = Nan::CallAsFunction(method, object, 0, nullptr); 50 | if (maybeResult.IsEmpty()) 51 | { 52 | return Nan::New("nothing was returned").ToLocalChecked(); 53 | } 54 | 55 | auto result = maybeResult.ToLocalChecked(); 56 | 57 | if (result->IsObject()) 58 | { 59 | return callToString(result->ToObject(context).ToLocalChecked()); 60 | } 61 | 62 | Nan::Utf8String val(result->ToString(context).ToLocalChecked()); 63 | return Nan::New(std::string(*val, val.length())).ToLocalChecked(); 64 | } 65 | -------------------------------------------------------------------------------- /lib/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./wrapped_re2.h" 4 | 5 | template 6 | inline v8::MaybeLocal bind(v8::MaybeLocal

param, L lambda) 7 | { 8 | return param.IsEmpty() ? v8::MaybeLocal() : lambda(param.ToLocalChecked()); 9 | } 10 | 11 | void consoleCall(const v8::Local &methodName, v8::Local text); 12 | void printDeprecationWarning(const char *warning); 13 | 14 | v8::Local callToString(const v8::Local &object); 15 | -------------------------------------------------------------------------------- /lib/wrapped_re2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct StrVal 8 | { 9 | char *data; 10 | size_t size, length; 11 | size_t index, byteIndex; 12 | bool isBuffer, isValidIndex, isBad; 13 | 14 | StrVal() : data(NULL), size(0), length(0), index(0), byteIndex(0), isBuffer(false), isValidIndex(false), isBad(false) {} 15 | 16 | operator re2::StringPiece() const { return re2::StringPiece(data, size); } 17 | 18 | void setIndex(size_t newIndex = 0); 19 | void reset(const v8::Local &arg, size_t size, size_t length, size_t newIndex = 0, bool buffer = false); 20 | 21 | void clear() 22 | { 23 | isBad = isBuffer = isValidIndex = false; 24 | size = length = index = byteIndex = 0; 25 | data = nullptr; 26 | } 27 | }; 28 | 29 | class WrappedRE2 : public Nan::ObjectWrap 30 | { 31 | private: 32 | WrappedRE2( 33 | const re2::StringPiece &pattern, 34 | const re2::RE2::Options &options, 35 | const std::string &src, 36 | const bool &g, 37 | const bool &i, 38 | const bool &m, 39 | const bool &s, 40 | const bool &y, 41 | const bool &d) : regexp(pattern, options), 42 | source(src), 43 | global(g), 44 | ignoreCase(i), 45 | multiline(m), 46 | dotAll(s), 47 | sticky(y), 48 | hasIndices(d), 49 | lastIndex(0) {} 50 | 51 | static NAN_METHOD(New); 52 | static NAN_METHOD(ToString); 53 | 54 | static NAN_GETTER(GetSource); 55 | static NAN_GETTER(GetFlags); 56 | static NAN_GETTER(GetGlobal); 57 | static NAN_GETTER(GetIgnoreCase); 58 | static NAN_GETTER(GetMultiline); 59 | static NAN_GETTER(GetDotAll); 60 | static NAN_GETTER(GetUnicode); 61 | static NAN_GETTER(GetSticky); 62 | static NAN_GETTER(GetHasIndices); 63 | static NAN_GETTER(GetLastIndex); 64 | static NAN_SETTER(SetLastIndex); 65 | static NAN_GETTER(GetInternalSource); 66 | 67 | // RegExp methods 68 | static NAN_METHOD(Exec); 69 | static NAN_METHOD(Test); 70 | 71 | // String methods 72 | static NAN_METHOD(Match); 73 | static NAN_METHOD(Replace); 74 | static NAN_METHOD(Search); 75 | static NAN_METHOD(Split); 76 | 77 | // strict Unicode warning support 78 | static NAN_GETTER(GetUnicodeWarningLevel); 79 | static NAN_SETTER(SetUnicodeWarningLevel); 80 | 81 | public: 82 | ~WrappedRE2(); 83 | 84 | static v8::Local Init(); 85 | 86 | static inline bool HasInstance(v8::Local object) 87 | { 88 | auto isolate = v8::Isolate::GetCurrent(); 89 | auto p_tpl = Nan::GetIsolateData>(isolate); 90 | return p_tpl->Get(isolate)->HasInstance(object); 91 | } 92 | 93 | enum UnicodeWarningLevels 94 | { 95 | NOTHING, 96 | WARN_ONCE, 97 | WARN, 98 | THROW 99 | }; 100 | static UnicodeWarningLevels unicodeWarningLevel; 101 | static bool alreadyWarnedAboutUnicode; 102 | 103 | re2::RE2 regexp; 104 | std::string source; 105 | bool global; 106 | bool ignoreCase; 107 | bool multiline; 108 | bool dotAll; 109 | bool sticky; 110 | bool hasIndices; 111 | size_t lastIndex; 112 | 113 | friend struct PrepareLastString; 114 | 115 | private: 116 | Nan::Persistent lastString; // weak pointer 117 | Nan::Persistent lastCache; // weak pointer 118 | StrVal lastStringValue; 119 | 120 | void dropCache(); 121 | const StrVal &prepareArgument(const v8::Local &arg, bool ignoreLastIndex = false); 122 | void doneWithLastString(); 123 | 124 | friend struct PrepareLastString; 125 | }; 126 | 127 | struct PrepareLastString 128 | { 129 | PrepareLastString(WrappedRE2 *re2, const v8::Local &arg, bool ignoreLastIndex = false) : re2(re2) { 130 | re2->prepareArgument(arg, ignoreLastIndex); 131 | } 132 | 133 | ~PrepareLastString() { 134 | re2->doneWithLastString(); 135 | } 136 | 137 | operator const StrVal&() const { 138 | return re2->lastStringValue; 139 | } 140 | 141 | operator StrVal&() { 142 | return re2->lastStringValue; 143 | } 144 | 145 | WrappedRE2 *re2; 146 | }; 147 | 148 | // utilities 149 | 150 | inline size_t getUtf8Length(const uint16_t *from, const uint16_t *to) 151 | { 152 | size_t n = 0; 153 | while (from != to) 154 | { 155 | uint16_t ch = *from++; 156 | if (ch <= 0x7F) 157 | ++n; 158 | else if (ch <= 0x7FF) 159 | n += 2; 160 | else if (0xD800 <= ch && ch <= 0xDFFF) 161 | { 162 | n += 4; 163 | if (from == to) 164 | break; 165 | ++from; 166 | } 167 | else if (ch < 0xFFFF) 168 | n += 3; 169 | else 170 | n += 4; 171 | } 172 | return n; 173 | } 174 | 175 | inline size_t getUtf16Length(const char *from, const char *to) 176 | { 177 | size_t n = 0; 178 | while (from != to) 179 | { 180 | unsigned ch = *from & 0xFF; 181 | if (ch < 0xF0) 182 | { 183 | if (ch < 0x80) 184 | { 185 | ++from; 186 | } 187 | else 188 | { 189 | if (ch < 0xE0) 190 | { 191 | from += 2; 192 | if (from == to + 1) 193 | { 194 | ++n; 195 | break; 196 | } 197 | } 198 | else 199 | { 200 | from += 3; 201 | if (from > to && from < to + 3) 202 | { 203 | ++n; 204 | break; 205 | } 206 | } 207 | } 208 | ++n; 209 | } 210 | else 211 | { 212 | from += 4; 213 | n += 2; 214 | if (from > to && from < to + 4) 215 | break; 216 | } 217 | } 218 | return n; 219 | } 220 | 221 | inline size_t getUtf8CharSize(char ch) 222 | { 223 | return ((0xE5000000 >> ((ch >> 3) & 0x1E)) & 3) + 1; 224 | } 225 | 226 | inline size_t getUtf16PositionByCounter(const char *data, size_t from, size_t n) 227 | { 228 | for (; n > 0; --n) 229 | { 230 | size_t s = getUtf8CharSize(data[from]); 231 | from += s; 232 | if (s == 4 && n >= 2) 233 | --n; // this utf8 character will take two utf16 characters 234 | // the decrement above is protected to avoid an overflow of an unsigned integer 235 | } 236 | return from; 237 | } 238 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "re2", 3 | "version": "1.22.1", 4 | "description": "Bindings for RE2: fast, safe alternative to backtracking regular expression engines.", 5 | "homepage": "https://github.com/uhop/node-re2", 6 | "bugs": "https://github.com/uhop/node-re2/issues", 7 | "type": "commonjs", 8 | "main": "re2.js", 9 | "types": "re2.d.ts", 10 | "files": [ 11 | "binding.gyp", 12 | "lib", 13 | "re2.d.ts", 14 | "scripts/*.js", 15 | "vendor" 16 | ], 17 | "dependencies": { 18 | "install-artifact-from-github": "^1.4.0", 19 | "nan": "^2.22.2", 20 | "node-gyp": "^11.2.0" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "^22.15.17", 24 | "heya-unit": "^0.3.0", 25 | "typescript": "^5.8.3" 26 | }, 27 | "scripts": { 28 | "test": "node tests/tests.js", 29 | "ts-test": "tsc", 30 | "save-to-github": "save-to-github-cache --artifact build/Release/re2.node", 31 | "install": "install-from-cache --artifact build/Release/re2.node --host-var RE2_DOWNLOAD_MIRROR --skip-path-var RE2_DOWNLOAD_SKIP_PATH --skip-ver-var RE2_DOWNLOAD_SKIP_VER || node-gyp -j max rebuild", 32 | "verify-build": "node scripts/verify-build.js", 33 | "build:dev": "node-gyp -j max build --debug", 34 | "build": "node-gyp -j max build", 35 | "rebuild:dev": "node-gyp -j max rebuild --debug", 36 | "rebuild": "node-gyp -j max rebuild", 37 | "clean": "node-gyp clean && node-gyp configure", 38 | "clean-build": "node-gyp clean" 39 | }, 40 | "github": "https://github.com/uhop/node-re2", 41 | "repository": { 42 | "type": "git", 43 | "url": "git://github.com/uhop/node-re2.git" 44 | }, 45 | "keywords": [ 46 | "RegExp", 47 | "RegEx", 48 | "text processing", 49 | "PCRE alternative" 50 | ], 51 | "author": "Eugene Lazutkin (https://lazutkin.com/)", 52 | "license": "BSD-3-Clause" 53 | } 54 | -------------------------------------------------------------------------------- /re2.d.ts: -------------------------------------------------------------------------------- 1 | declare module 're2' { 2 | 3 | interface RE2BufferExecArray { 4 | index: number; 5 | input: Buffer; 6 | 0: Buffer; 7 | groups?: { 8 | [key: string]: Buffer 9 | } 10 | } 11 | 12 | interface RE2BufferMatchArray { 13 | index?: number; 14 | input?: Buffer; 15 | 0: Buffer; 16 | groups?: { 17 | [key: string]: Buffer 18 | } 19 | } 20 | 21 | interface RE2 extends RegExp { 22 | exec(str: string): RegExpExecArray | null; 23 | exec(str: Buffer): RE2BufferExecArray | null; 24 | 25 | match(str: string): RegExpMatchArray | null; 26 | match(str: Buffer): RE2BufferMatchArray | null; 27 | 28 | test(str: string | Buffer): boolean; 29 | 30 | replace(str: K, replaceValue: string | Buffer): K; 31 | replace(str: K, replacer: (substring: string, ...args: any[]) => string | Buffer): K; 32 | 33 | search(str: string | Buffer): number; 34 | 35 | split(str: K, limit?: number): K[]; 36 | } 37 | 38 | interface RE2Constructor extends RegExpConstructor { 39 | new(pattern: Buffer | RegExp | RE2 | string): RE2; 40 | new(pattern: Buffer | string, flags?: string | Buffer): RE2; 41 | (pattern: Buffer | RegExp | RE2 | string): RE2; 42 | (pattern: Buffer | string, flags?: string | Buffer): RE2; 43 | readonly prototype: RE2; 44 | 45 | unicodeWarningLevel: 'nothing' | 'warnOnce' | 'warn' | 'throw'; 46 | getUtf8Length(value: string): number; 47 | getUtf16Length(value: Buffer): number; 48 | } 49 | 50 | var RE2: RE2Constructor; 51 | export = RE2; 52 | } 53 | -------------------------------------------------------------------------------- /re2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RE2 = require('./build/Release/re2.node'); 4 | // const RE2 = require('./build/Debug/re2.node'); 5 | 6 | const setAliases = (object, dict) => { 7 | for (let [name, alias] of Object.entries(dict)) { 8 | Object.defineProperty( 9 | object, 10 | alias, 11 | Object.getOwnPropertyDescriptor(object, name) 12 | ); 13 | } 14 | }; 15 | 16 | setAliases(RE2.prototype, { 17 | match: Symbol.match, 18 | search: Symbol.search, 19 | replace: Symbol.replace, 20 | split: Symbol.split 21 | }); 22 | 23 | RE2.prototype[Symbol.matchAll] = function* (str) { 24 | if (!this.global) 25 | throw TypeError( 26 | 'String.prototype.matchAll() is called with a non-global RE2 argument' 27 | ); 28 | 29 | const re = new RE2(this); 30 | re.lastIndex = this.lastIndex; 31 | for (;;) { 32 | const result = re.exec(str); 33 | if (!result) break; 34 | if (result[0] === '') ++re.lastIndex; 35 | yield result; 36 | } 37 | }; 38 | 39 | module.exports = RE2; 40 | -------------------------------------------------------------------------------- /scripts/verify-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a light-weight script to make sure that the package works. 4 | 5 | const assert = require('assert').strict; 6 | 7 | const RE2 = require("../re2"); 8 | 9 | const sample = "abbcdefabh"; 10 | 11 | const re1 = new RE2("ab*", "g"); 12 | assert(re1.test(sample)); 13 | 14 | const re2 = RE2("ab*"); 15 | assert(re2.test(sample)); 16 | 17 | const re3 = new RE2("abc"); 18 | assert(!re3.test(sample)); 19 | -------------------------------------------------------------------------------- /tests/manual/matchall-bench.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RE2 = require('../../re2'); 4 | 5 | const N = 1_000_000; 6 | 7 | const s = 'a'.repeat(N), 8 | re = new RE2('a', 'g'), 9 | matches = s.matchAll(re); 10 | 11 | let n = 0; 12 | for (const _ of matches) ++n; 13 | 14 | if (n !== s.length) console.log('Wrong result.'); 15 | 16 | console.log('Done.'); 17 | -------------------------------------------------------------------------------- /tests/manual/memory-check.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RE2 = require('../../re2.js'); 4 | 5 | const L = 20 * 1024 * 1024, 6 | N = 100; 7 | 8 | if (typeof globalThis.gc != 'function') 9 | console.log( 10 | "Warning: to run it with explicit gc() calls, you should use --expose-gc as a node's argument." 11 | ); 12 | 13 | const gc = typeof globalThis.gc == 'function' ? globalThis.gc : () => {}; 14 | 15 | const s = 'a'.repeat(L), 16 | objects = []; 17 | 18 | for (let i = 0; i < N; ++i) { 19 | const re2 = new RE2('x', 'g'); 20 | objects.push(re2); 21 | const result = s.replace(re2, ''); 22 | if (result.length !== s.length) console.log('Wrong result.'); 23 | gc(); 24 | } 25 | 26 | console.log('Done. Now it is spinning: check the memory consumption! To stop it, press Ctrl+C.'); 27 | 28 | for(;;); 29 | -------------------------------------------------------------------------------- /tests/manual/memory-monitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RE2 = require('../../re2'); 4 | 5 | const N = 5_000_000; 6 | 7 | console.log('Never-ending loop: exit with Ctrl+C.'); 8 | 9 | const aCharCode = 'a'.charCodeAt(0); 10 | const randomAlpha = () => String.fromCharCode(aCharCode + Math.floor(Math.random() * 26)); 11 | 12 | const humanizeNumber = n => { 13 | const negative = n < 0; 14 | if (negative) n = -n; 15 | 16 | const s = n.toFixed(); 17 | let group1 = s.length % 3; 18 | if (!group1) group1 = 3; 19 | 20 | let result = s.substring(0, group1); 21 | for (let i = group1; i < s.length; i += 3) { 22 | result += ',' + s.substring(i, i + 3); 23 | } 24 | 25 | return (negative ? '-' : '') + result; 26 | }; 27 | 28 | const CSI = '\x1B['; 29 | const cursorUp = (n = 1) => CSI + (n > 1 ? n.toFixed() : '') + 'A'; 30 | const sgr = (cmd = '') => CSI + (Array.isArray(cmd) ? cmd.join(';') : cmd) + 'm'; 31 | const RESET = sgr(); 32 | const NOTE = sgr(91); 33 | 34 | let first = true; 35 | const maxMemory = {heapTotal: 0, heapUsed: 0, external: 0, arrayBuffers: 0, rss: 0}, 36 | labels = { 37 | heapTotal: 'heap total', 38 | heapUsed: 'heap used', 39 | external: 'external', 40 | arrayBuffers: 'array buffers', 41 | rss: 'resident set size' 42 | }, 43 | maxLabelSize = Math.max(...Array.from(Object.values(labels)).map(label => label.length)); 44 | 45 | const report = () => { 46 | const memoryUsage = process.memoryUsage(), 47 | previousMax = {...maxMemory}; 48 | 49 | console.log((first ? '' : '\r' + cursorUp(6)) + 50 | ''.padStart(maxLabelSize + 1), 'Current'.padStart(15), 'Max'.padStart(15)); 51 | for (const name in maxMemory) { 52 | const prefix = previousMax[name] && previousMax[name] < memoryUsage[name] ? NOTE : RESET; 53 | console.log( 54 | (labels[name] + ':').padStart(maxLabelSize + 1), 55 | prefix + humanizeNumber(memoryUsage[name]).padStart(15) + RESET, 56 | humanizeNumber(maxMemory[name]).padStart(15) 57 | ); 58 | } 59 | 60 | for (const [name, value] of Object.entries(maxMemory)) { 61 | maxMemory[name] = Math.max(value, memoryUsage[name]); 62 | } 63 | 64 | first = false; 65 | } 66 | 67 | for (;;) { 68 | const re2 = new RE2(randomAlpha(), 'g'); 69 | 70 | let s = ''; 71 | for (let i = 0; i < N; ++i) s += randomAlpha(); 72 | 73 | let n = 0; 74 | for (const _ of s.matchAll(re2)) ++n; 75 | 76 | re2.lastIndex = 0; 77 | const r = s.replace(re2, ''); 78 | if (r.length + n != s.length) { 79 | console.log('ERROR!', 's:', s.length, 'r:', r.length, 'n:', n, 're2:', re2.toString()); 80 | break; 81 | } 82 | 83 | report(); 84 | } 85 | -------------------------------------------------------------------------------- /tests/manual/worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {Worker, isMainThread} = require('worker_threads'); 4 | 5 | const RE2 = require('../../re2'); 6 | 7 | if (isMainThread) { 8 | // This re-loads the current file inside a Worker instance. 9 | console.log('Inside Master!'); 10 | const worker = new Worker(__filename); 11 | worker.on('exit', code => { 12 | console.log('Exit code:', code); 13 | test('#2'); 14 | }); 15 | test('#1'); 16 | } else { 17 | console.log('Inside Worker!'); 18 | test(); 19 | } 20 | 21 | function test(msg) { 22 | msg && console.log(isMainThread ? 'Main' : 'Worker', msg); 23 | 24 | const a = new RE2('^\\d+$'); 25 | console.log(isMainThread, a.test('123'), a.test('abc'), a.test('123abc'), a instanceof RE2); 26 | 27 | const b = RE2('^\\d+$'); 28 | console.log(isMainThread, b.test('123'), b.test('abc'), b.test('123abc'), b instanceof RE2); 29 | } 30 | -------------------------------------------------------------------------------- /tests/test_exec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | // These tests are copied from MDN: 10 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec 11 | 12 | function test_execBasic(t) { 13 | 'use strict'; 14 | 15 | var re = new RE2('quick\\s(brown).+?(jumps)', 'ig'); 16 | 17 | eval(t.TEST("re.source === 'quick\\\\s(brown).+?(jumps)'")); 18 | eval(t.TEST('re.ignoreCase')); 19 | eval(t.TEST('re.global')); 20 | eval(t.TEST('!re.multiline')); 21 | 22 | var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog'); 23 | 24 | eval( 25 | t.TEST("t.unify(result, ['Quick Brown Fox Jumps', 'Brown', 'Jumps'])") 26 | ); 27 | eval(t.TEST('result.index === 4')); 28 | eval( 29 | t.TEST("result.input === 'The Quick Brown Fox Jumps Over The Lazy Dog'") 30 | ); 31 | eval(t.TEST('re.lastIndex === 25')); 32 | }, 33 | function test_execSucc(t) { 34 | 'use strict'; 35 | 36 | var str = 'abbcdefabh'; 37 | 38 | var re = new RE2('ab*', 'g'); 39 | var result = re.exec(str); 40 | 41 | eval(t.TEST('!!result')); 42 | eval(t.TEST("result[0] === 'abb'")); 43 | eval(t.TEST('result.index === 0')); 44 | eval(t.TEST('re.lastIndex === 3')); 45 | 46 | result = re.exec(str); 47 | 48 | eval(t.TEST('!!result')); 49 | eval(t.TEST("result[0] === 'ab'")); 50 | eval(t.TEST('result.index === 7')); 51 | eval(t.TEST('re.lastIndex === 9')); 52 | 53 | result = re.exec(str); 54 | 55 | eval(t.TEST('!result')); 56 | }, 57 | function test_execSimple(t) { 58 | 'use strict'; 59 | 60 | var re = new RE2('(hello \\S+)'); 61 | var result = re.exec('This is a hello world!'); 62 | 63 | eval(t.TEST("result[1] === 'hello world!'")); 64 | }, 65 | function test_execFail(t) { 66 | 'use strict'; 67 | 68 | var re = new RE2('(a+)?(b+)?'); 69 | var result = re.exec('aaabb'); 70 | 71 | eval(t.TEST("result[1] === 'aaa'")); 72 | eval(t.TEST("result[2] === 'bb'")); 73 | 74 | result = re.exec('aaacbb'); 75 | 76 | eval(t.TEST("result[1] === 'aaa'")); 77 | eval(t.TEST('result[2] === undefined')); 78 | eval(t.TEST('result.length === 3')); 79 | }, 80 | function test_execAnchoredToBeginning(t) { 81 | 'use strict'; 82 | 83 | var re = RE2('^hello', 'g'); 84 | 85 | var result = re.exec('hellohello'); 86 | 87 | eval(t.TEST("t.unify(result, ['hello'])")); 88 | eval(t.TEST('result.index === 0')); 89 | eval(t.TEST('re.lastIndex === 5')); 90 | 91 | eval(t.TEST("re.exec('hellohello') === null")); 92 | }, 93 | function test_execInvalid(t) { 94 | 'use strict'; 95 | 96 | var re = RE2(''); 97 | 98 | try { 99 | re.exec({ 100 | toString() { 101 | throw 'corner'; 102 | } 103 | }); 104 | t.test(false); // shouldn't be here 105 | } catch (e) { 106 | eval(t.TEST("e === 'corner'")); 107 | } 108 | }, 109 | function test_execAnchor1(t) { 110 | 'use strict'; 111 | 112 | var re = new RE2('b|^a', 'g'); 113 | 114 | var result = re.exec('aabc'); 115 | eval(t.TEST('!!result')); 116 | eval(t.TEST('result.index === 0')); 117 | eval(t.TEST('re.lastIndex === 1')); 118 | 119 | result = re.exec('aabc'); 120 | eval(t.TEST('!!result')); 121 | eval(t.TEST('result.index === 2')); 122 | eval(t.TEST('re.lastIndex === 3')); 123 | 124 | result = re.exec('aabc'); 125 | eval(t.TEST('!result')); 126 | }, 127 | function test_execAnchor2(t) { 128 | 'use strict'; 129 | 130 | var re = new RE2('(?:^a)', 'g'); 131 | 132 | var result = re.exec('aabc'); 133 | eval(t.TEST('!!result')); 134 | eval(t.TEST('result.index === 0')); 135 | eval(t.TEST('re.lastIndex === 1')); 136 | 137 | result = re.exec('aabc'); 138 | eval(t.TEST('!result')); 139 | }, 140 | 141 | // Unicode tests 142 | 143 | function test_execUnicode(t) { 144 | 'use strict'; 145 | 146 | var re = new RE2('охотник\\s(желает).+?(где)', 'ig'); 147 | 148 | eval(t.TEST("re.source === 'охотник\\\\s(желает).+?(где)'")); 149 | eval(t.TEST('re.ignoreCase')); 150 | eval(t.TEST('re.global')); 151 | eval(t.TEST('!re.multiline')); 152 | 153 | var result = re.exec('Каждый Охотник Желает Знать Где Сидит Фазан'); 154 | 155 | eval( 156 | t.TEST("t.unify(result, ['Охотник Желает Знать Где', 'Желает', 'Где'])") 157 | ); 158 | eval(t.TEST('result.index === 7')); 159 | eval( 160 | t.TEST("result.input === 'Каждый Охотник Желает Знать Где Сидит Фазан'") 161 | ); 162 | eval(t.TEST('re.lastIndex === 31')); 163 | 164 | eval( 165 | t.TEST( 166 | "result.input.substr(result.index) === 'Охотник Желает Знать Где Сидит Фазан'" 167 | ) 168 | ); 169 | eval(t.TEST("result.input.substr(re.lastIndex) === ' Сидит Фазан'")); 170 | }, 171 | function test_execUnicodeSubsequent(t) { 172 | 'use strict'; 173 | 174 | var str = 'аббвгдеабё'; 175 | 176 | var re = new RE2('аб*', 'g'); 177 | var result = re.exec(str); 178 | 179 | eval(t.TEST('!!result')); 180 | eval(t.TEST("result[0] === 'абб'")); 181 | eval(t.TEST('result.index === 0')); 182 | eval(t.TEST('re.lastIndex === 3')); 183 | 184 | result = re.exec(str); 185 | 186 | eval(t.TEST('!!result')); 187 | eval(t.TEST("result[0] === 'аб'")); 188 | eval(t.TEST('result.index === 7')); 189 | eval(t.TEST('re.lastIndex === 9')); 190 | 191 | result = re.exec(str); 192 | 193 | eval(t.TEST('!result')); 194 | }, 195 | function test_execUnicodeSupplementary(t) { 196 | 'use strict'; 197 | 198 | var re = new RE2('\\u{1F603}', 'g'); 199 | 200 | eval(t.TEST("re.source === '\\\\u{1F603}'")); 201 | eval(t.TEST("re.internalSource === '\\\\x{1F603}'")); 202 | eval(t.TEST('!re.ignoreCase')); 203 | eval(t.TEST('re.global')); 204 | eval(t.TEST('!re.multiline')); 205 | 206 | var result = re.exec('\u{1F603}'); // 1F603 is the SMILING FACE WITH OPEN MOUTH emoji 207 | 208 | eval(t.TEST("t.unify(result, ['\\u{1F603}'])")); 209 | eval(t.TEST('result.index === 0')); 210 | eval(t.TEST("result.input === '\\u{1F603}'")); 211 | eval(t.TEST('re.lastIndex === 2')); 212 | 213 | var re2 = new RE2('.', 'g'); 214 | 215 | eval(t.TEST("re2.source === '.'")); 216 | eval(t.TEST('!re2.ignoreCase')); 217 | eval(t.TEST('re2.global')); 218 | eval(t.TEST('!re2.multiline')); 219 | 220 | var result2 = re2.exec('\u{1F603}'); 221 | 222 | eval(t.TEST("t.unify(result2, ['\\u{1F603}'])")); 223 | eval(t.TEST('result2.index === 0')); 224 | eval(t.TEST("result2.input === '\\u{1F603}'")); 225 | eval(t.TEST('re2.lastIndex === 2')); 226 | 227 | var re3 = new RE2('[\u{1F603}-\u{1F605}]', 'g'); 228 | 229 | eval(t.TEST("re3.source === '[\u{1F603}-\u{1F605}]'")); 230 | eval(t.TEST('!re3.ignoreCase')); 231 | eval(t.TEST('re3.global')); 232 | eval(t.TEST('!re3.multiline')); 233 | 234 | var result3 = re3.exec('\u{1F604}'); 235 | 236 | eval(t.TEST("t.unify(result3, ['\\u{1F604}'])")); 237 | eval(t.TEST('result3.index === 0')); 238 | eval(t.TEST("result3.input === '\\u{1F604}'")); 239 | eval(t.TEST('re3.lastIndex === 2')); 240 | }, 241 | 242 | // Buffer tests 243 | 244 | function test_execBuffer(t) { 245 | 'use strict'; 246 | 247 | var re = new RE2('охотник\\s(желает).+?(где)', 'ig'); 248 | var buf = new Buffer('Каждый Охотник Желает Знать Где Сидит Фазан'); 249 | 250 | var result = re.exec(buf); 251 | 252 | eval(t.TEST('result.length === 3')); 253 | eval(t.TEST('result[0] instanceof Buffer')); 254 | eval(t.TEST('result[1] instanceof Buffer')); 255 | eval(t.TEST('result[2] instanceof Buffer')); 256 | 257 | eval(t.TEST("result[0].toString() === 'Охотник Желает Знать Где'")); 258 | eval(t.TEST("result[1].toString() === 'Желает'")); 259 | eval(t.TEST("result[2].toString() === 'Где'")); 260 | 261 | eval(t.TEST('result.index === 13')); 262 | eval(t.TEST('result.input instanceof Buffer')); 263 | eval( 264 | t.TEST( 265 | "result.input.toString() === 'Каждый Охотник Желает Знать Где Сидит Фазан'" 266 | ) 267 | ); 268 | eval(t.TEST('re.lastIndex === 58')); 269 | 270 | eval( 271 | t.TEST( 272 | "result.input.toString('utf8', result.index) === 'Охотник Желает Знать Где Сидит Фазан'" 273 | ) 274 | ); 275 | eval( 276 | t.TEST("result.input.toString('utf8', re.lastIndex) === ' Сидит Фазан'") 277 | ); 278 | }, 279 | 280 | // Sticky tests 281 | 282 | function test_execSticky(t) { 283 | 'use strict'; 284 | 285 | var re = new RE2('\\s+', 'y'); 286 | 287 | eval(t.TEST("re.exec('Hello world, how are you?') === null")); 288 | 289 | re.lastIndex = 5; 290 | 291 | var result = re.exec('Hello world, how are you?'); 292 | 293 | eval(t.TEST("t.unify(result, [' '])")); 294 | eval(t.TEST('result.index === 5')); 295 | eval(t.TEST('re.lastIndex === 6')); 296 | 297 | var re2 = new RE2('\\s+', 'gy'); 298 | 299 | eval(t.TEST("re2.exec('Hello world, how are you?') === null")); 300 | 301 | re2.lastIndex = 5; 302 | 303 | var result2 = re2.exec('Hello world, how are you?'); 304 | 305 | eval(t.TEST("t.unify(result2, [' '])")); 306 | eval(t.TEST('result2.index === 5')); 307 | eval(t.TEST('re2.lastIndex === 6')); 308 | }, 309 | 310 | function test_execSupplemental(t) { 311 | 'use strict'; 312 | 313 | var re = new RE2('\\w+', 'g'); 314 | var testString = '🤡🤡🤡 Hello clown world!'; 315 | 316 | var result = re.exec(testString); 317 | eval(t.TEST("t.unify(result, ['Hello'])")); 318 | 319 | result = re.exec(testString); 320 | eval(t.TEST("t.unify(result, ['clown'])")); 321 | 322 | result = re.exec(testString); 323 | eval(t.TEST("t.unify(result, ['world'])")); 324 | }, 325 | 326 | // Multiline test 327 | 328 | function test_execMultiline(t) { 329 | 'use strict'; 330 | 331 | const re = new RE2('^xy', 'm'), 332 | pattern = ` xy1 333 | xy2 (at start of line) 334 | xy3`; 335 | 336 | const result = re.exec(pattern); 337 | 338 | eval(t.TEST('!!result')); 339 | eval(t.TEST("result[0] === 'xy'")); 340 | eval(t.TEST('result.index > 3')); 341 | eval(t.TEST('result.index < pattern.length - 4')); 342 | eval( 343 | t.TEST('result[0] === pattern.substr(result.index, result[0].length)') 344 | ); 345 | }, 346 | 347 | // dotAll tests 348 | 349 | function test_execDotAll(t) { 350 | 'use strict'; 351 | 352 | eval(t.TEST("new RE2('a.c').test('abc')")); 353 | eval(t.TEST("new RE2(/a.c/).test('a c')")); 354 | eval(t.TEST("!new RE2(/a.c/).test('a\\nc')")); 355 | 356 | eval(t.TEST("new RE2('a.c', 's').test('abc')")); 357 | eval(t.TEST("new RE2(/a.c/s).test('a c')")); 358 | eval(t.TEST("new RE2(/a.c/s).test('a\\nc')")); 359 | }, 360 | 361 | // hasIndices tests 362 | 363 | function test_execHasIndices(t) { 364 | 'use strict'; 365 | 366 | eval(t.TEST("!new RE2('1').hasIndices")); 367 | eval(t.TEST('!new RE2(/1/).hasIndices')); 368 | 369 | var re = new RE2('(aa)(?b)?(?ccc)', 'd'); 370 | 371 | eval(t.TEST('re.hasIndices')); 372 | 373 | var result = re.exec('1aabccc2'); 374 | 375 | eval(t.TEST('result.length === 4')); 376 | eval(t.TEST("result.input === '1aabccc2'")); 377 | eval(t.TEST('result.index === 1')); 378 | eval(t.TEST('Object.keys(result.groups).length === 2')); 379 | eval(t.TEST("result.groups.b === 'b'")); 380 | eval(t.TEST("result.groups.c === 'ccc'")); 381 | eval(t.TEST("result[0] === 'aabccc'")); 382 | eval(t.TEST("result[1] === 'aa'")); 383 | eval(t.TEST("result[2] === 'b'")); 384 | eval(t.TEST("result[3] === 'ccc'")); 385 | eval(t.TEST('result.indices.length === 4')); 386 | eval(t.TEST('t.unify(result.indices, [[1, 7], [1, 3], [3, 4], [4, 7]])')); 387 | eval(t.TEST('Object.keys(result.indices.groups).length === 2')); 388 | eval(t.TEST('t.unify(result.indices.groups.b, [3, 4])')); 389 | eval(t.TEST('t.unify(result.indices.groups.c, [4, 7])')); 390 | 391 | result = re.exec('1aaccc2'); 392 | 393 | eval(t.TEST('result.length === 4')); 394 | eval(t.TEST("result.input === '1aaccc2'")); 395 | eval(t.TEST('result.index === 1')); 396 | eval(t.TEST('Object.keys(result.groups).length === 2')); 397 | eval(t.TEST('result.groups.b === undefined')); 398 | eval(t.TEST("result.groups.c === 'ccc'")); 399 | eval(t.TEST("result[0] === 'aaccc'")); 400 | eval(t.TEST("result[1] === 'aa'")); 401 | eval(t.TEST('result[2] === undefined')); 402 | eval(t.TEST("result[3] === 'ccc'")); 403 | eval(t.TEST('result.indices.length === 4')); 404 | eval( 405 | t.TEST('t.unify(result.indices, [[1, 6], [1, 3], undefined, [3, 6]])') 406 | ); 407 | eval(t.TEST('Object.keys(result.indices.groups).length === 2')); 408 | eval(t.TEST('t.unify(result.indices.groups.b, undefined)')); 409 | eval(t.TEST('t.unify(result.indices.groups.c, [3, 6])')); 410 | 411 | try { 412 | re = new RE2(new RegExp('1', 'd')); 413 | eval(t.TEST('re.hasIndices')); 414 | } catch (e) { 415 | // squelch 416 | } 417 | }, 418 | 419 | function test_hasIndexLastIndex(t) { 420 | 'use strict'; 421 | 422 | const re2 = new RE2('a', 'dg'); 423 | 424 | eval(t.TEST('re2.lastIndex === 0')); 425 | 426 | let result = re2.exec('abca'); 427 | eval(t.TEST('re2.lastIndex === 1')); 428 | eval(t.TEST('result.index === 0')); 429 | eval(t.TEST('t.unify(result.indices, [[0, 1]])')); 430 | 431 | result = re2.exec('abca'); 432 | eval(t.TEST('re2.lastIndex === 4')); 433 | eval(t.TEST('result.index === 3')); 434 | eval(t.TEST('t.unify(result.indices, [[3, 4]])')); 435 | 436 | result = re2.exec('abca'); 437 | eval(t.TEST('re2.lastIndex === 0')); 438 | eval(t.TEST('result === null')); 439 | }, 440 | 441 | function test_bufferVsString(t) { 442 | 'use strict'; 443 | 444 | const re2 = new RE2('.', 'g'), 445 | pattern = 'abcdefg'; 446 | 447 | re2.lastIndex = 2; 448 | const result1 = re2.exec(pattern); 449 | 450 | re2.lastIndex = 2; 451 | const result2 = re2.exec(Buffer.from(pattern)); 452 | 453 | eval(t.TEST("result1[0] === 'c'")); 454 | eval(t.TEST("t.unify(result2[0], Buffer.from('c'))")); 455 | 456 | eval(t.TEST('result1.index === 2')); 457 | eval(t.TEST('result2.index === 2')); 458 | }, 459 | 460 | function test_foundEmptyString(t) { 461 | 'use strict'; 462 | 463 | const re2 = new RE2('^.*?'), 464 | match = re2.exec(''); 465 | 466 | eval(t.TEST("match[0] === ''")); 467 | eval(t.TEST('match.index === 0')); 468 | eval(t.TEST("match.input === ''")); 469 | eval(t.TEST('match.groups === undefined')); 470 | } 471 | ]); 472 | -------------------------------------------------------------------------------- /tests/test_general.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | function test_generalCtr(t) { 10 | 'use strict'; 11 | 12 | eval(t.TEST('!!RE2')); 13 | eval(t.TEST("RE2.toString() === 'function RE2() { [native code] }'")); 14 | eval(t.TEST('!!RE2.prototype')); 15 | }, 16 | function test_generalInst(t) { 17 | 'use strict'; 18 | 19 | var re1 = new RE2('\\d+'); 20 | 21 | eval(t.TEST('!!re1')); 22 | eval(t.TEST('re1 instanceof RE2')); 23 | 24 | var re2 = RE2('\\d+'); 25 | 26 | eval(t.TEST('!!re2')); 27 | eval(t.TEST('re2 instanceof RE2')); 28 | compare(re1, re2, t); 29 | 30 | re1 = new RE2('\\d+', 'm'); 31 | 32 | eval(t.TEST('!!re1')); 33 | eval(t.TEST('re1 instanceof RE2')); 34 | 35 | re2 = RE2('\\d+', 'm'); 36 | 37 | eval(t.TEST('!!re2')); 38 | eval(t.TEST('re2 instanceof RE2')); 39 | compare(re1, re2, t); 40 | }, 41 | function test_instErrors(t) { 42 | try { 43 | var re = new RE2([]); 44 | t.test(false); // shouldn't be here 45 | } catch (e) { 46 | eval(t.TEST('e instanceof TypeError')); 47 | } 48 | 49 | try { 50 | var re = new RE2({}); 51 | t.test(false); // shouldn't be here 52 | } catch (e) { 53 | eval(t.TEST('e instanceof TypeError')); 54 | } 55 | 56 | try { 57 | var re = new RE2(new Date()); 58 | t.test(false); // shouldn't be here 59 | } catch (e) { 60 | eval(t.TEST('e instanceof TypeError')); 61 | } 62 | 63 | try { 64 | var re = new RE2(null); 65 | t.test(false); // shouldn't be here 66 | } catch (e) { 67 | eval(t.TEST('e instanceof TypeError')); 68 | } 69 | 70 | try { 71 | var re = new RE2(); 72 | t.test(false); // shouldn't be here 73 | } catch (e) { 74 | eval(t.TEST('e instanceof TypeError')); 75 | } 76 | 77 | try { 78 | var re = RE2(); 79 | t.test(false); // shouldn't be here 80 | } catch (e) { 81 | eval(t.TEST('e instanceof TypeError')); 82 | } 83 | 84 | try { 85 | var re = RE2({ 86 | toString() { 87 | throw 'corner'; 88 | } 89 | }); 90 | t.test(false); // shouldn't be here 91 | } catch (e) { 92 | eval(t.TEST('e instanceof TypeError')); 93 | } 94 | }, 95 | function test_generalIn(t) { 96 | 'use strict'; 97 | 98 | var re = new RE2('\\d+'); 99 | 100 | eval(t.TEST("'exec' in re")); 101 | eval(t.TEST("'test' in re")); 102 | eval(t.TEST("'match' in re")); 103 | eval(t.TEST("'replace' in re")); 104 | eval(t.TEST("'search' in re")); 105 | eval(t.TEST("'split' in re")); 106 | eval(t.TEST("'source' in re")); 107 | eval(t.TEST("'flags' in re")); 108 | eval(t.TEST("'global' in re")); 109 | eval(t.TEST("'ignoreCase' in re")); 110 | eval(t.TEST("'multiline' in re")); 111 | eval(t.TEST("'dotAll' in re")); 112 | eval(t.TEST("'sticky' in re")); 113 | eval(t.TEST("'lastIndex' in re")); 114 | }, 115 | function test_generalPresent(t) { 116 | 'use strict'; 117 | 118 | var re = new RE2('\\d+'); 119 | 120 | eval(t.TEST("typeof re.exec == 'function'")); 121 | eval(t.TEST("typeof re.test == 'function'")); 122 | eval(t.TEST("typeof re.match == 'function'")); 123 | eval(t.TEST("typeof re.replace == 'function'")); 124 | eval(t.TEST("typeof re.search == 'function'")); 125 | eval(t.TEST("typeof re.split == 'function'")); 126 | eval(t.TEST("typeof re.source == 'string'")); 127 | eval(t.TEST("typeof re.flags == 'string'")); 128 | eval(t.TEST("typeof re.global == 'boolean'")); 129 | eval(t.TEST("typeof re.ignoreCase == 'boolean'")); 130 | eval(t.TEST("typeof re.multiline == 'boolean'")); 131 | eval(t.TEST("typeof re.dotAll == 'boolean'")); 132 | eval(t.TEST("typeof re.sticky == 'boolean'")); 133 | eval(t.TEST("typeof re.lastIndex == 'number'")); 134 | }, 135 | function test_generalLastIndex(t) { 136 | 'use strict'; 137 | 138 | var re = new RE2('\\d+'); 139 | 140 | eval(t.TEST('re.lastIndex === 0')); 141 | 142 | re.lastIndex = 5; 143 | 144 | eval(t.TEST('re.lastIndex === 5')); 145 | 146 | re.lastIndex = 0; 147 | 148 | eval(t.TEST('re.lastIndex === 0')); 149 | }, 150 | function test_generalRegExp(t) { 151 | 'use strict'; 152 | 153 | var re1 = new RegExp('\\d+'); 154 | var re2 = new RE2('\\d+'); 155 | 156 | compare(re1, re2, t); 157 | 158 | re2 = new RE2(re1); 159 | 160 | compare(re1, re2, t); 161 | 162 | re1 = new RegExp('a', 'ig'); 163 | re2 = new RE2('a', 'ig'); 164 | 165 | compare(re1, re2, t); 166 | 167 | re2 = new RE2(re1); 168 | 169 | compare(re1, re2, t); 170 | 171 | re1 = /\s/gm; 172 | re2 = new RE2('\\s', 'mg'); 173 | 174 | compare(re1, re2, t); 175 | 176 | re2 = new RE2(re1); 177 | 178 | compare(re1, re2, t); 179 | 180 | re2 = new RE2(/\s/gm); 181 | 182 | compare(/\s/gm, re2, t); 183 | 184 | re1 = new RE2('b', 'gm'); 185 | re2 = new RE2(re1); 186 | 187 | compare(re1, re2, t); 188 | 189 | re1 = new RE2('b', 'sgm'); 190 | re2 = new RE2(re1); 191 | 192 | compare(re1, re2, t); 193 | 194 | re2 = new RE2(/\s/gms); 195 | 196 | compare(/\s/gms, re2, t); 197 | }, 198 | function test_utf8(t) { 199 | 'use strict'; 200 | 201 | var s = 'Привет!'; 202 | 203 | eval(t.TEST('s.length === 7')); 204 | eval(t.TEST('RE2.getUtf8Length(s) === 13')); 205 | 206 | var b = new Buffer.from(s); 207 | eval(t.TEST('b.length === 13')); 208 | eval(t.TEST('RE2.getUtf16Length(b) === 7')); 209 | 210 | var s2 = '\u{1F603}'; 211 | 212 | eval(t.TEST('s2.length === 2')); 213 | eval(t.TEST('RE2.getUtf8Length(s2) === 4')); 214 | 215 | var b2 = new Buffer.from(s2); 216 | 217 | eval(t.TEST('b2.length === 4')); 218 | eval(t.TEST('RE2.getUtf16Length(b2) === 2')); 219 | 220 | var s3 = '\uD83D'; 221 | 222 | eval(t.TEST('s3.length === 1')); 223 | eval(t.TEST('RE2.getUtf8Length(s3) === 3')); 224 | 225 | var s4 = '🤡'; 226 | 227 | eval(t.TEST('s4.length === 2')); 228 | eval(t.TEST('RE2.getUtf8Length(s4) === 4')); 229 | eval(t.TEST("RE2.getUtf16Length(Buffer.from(s4, 'utf8')) === s4.length")); 230 | 231 | var b3 = new Buffer.from([0xf0]); 232 | 233 | eval(t.TEST('b3.length === 1')); 234 | eval(t.TEST('RE2.getUtf16Length(b3) === 2')); 235 | 236 | try { 237 | RE2.getUtf8Length({ 238 | toString() { 239 | throw 'corner'; 240 | } 241 | }); 242 | t.test(false); // shouldn't be here 243 | } catch (e) { 244 | eval(t.TEST("e === 'corner'")); 245 | } 246 | 247 | eval( 248 | t.TEST("RE2.getUtf16Length({ toString() { throw 'corner'; } }) === -1") 249 | ); 250 | }, 251 | function test_flags(t) { 252 | 'use strict'; 253 | 254 | var re = new RE2('a', 'u'); 255 | eval(t.TEST("re.flags === 'u'")); 256 | 257 | re = new RE2('a', 'iu'); 258 | eval(t.TEST("re.flags === 'iu'")); 259 | 260 | re = new RE2('a', 'mu'); 261 | eval(t.TEST("re.flags === 'mu'")); 262 | 263 | re = new RE2('a', 'gu'); 264 | eval(t.TEST("re.flags === 'gu'")); 265 | 266 | re = new RE2('a', 'yu'); 267 | eval(t.TEST("re.flags === 'uy'")); 268 | 269 | re = new RE2('a', 'yiu'); 270 | eval(t.TEST("re.flags === 'iuy'")); 271 | 272 | re = new RE2('a', 'yigu'); 273 | eval(t.TEST("re.flags === 'giuy'")); 274 | 275 | re = new RE2('a', 'miu'); 276 | eval(t.TEST("re.flags === 'imu'")); 277 | 278 | re = new RE2('a', 'ygu'); 279 | eval(t.TEST("re.flags === 'guy'")); 280 | 281 | re = new RE2('a', 'myu'); 282 | eval(t.TEST("re.flags === 'muy'")); 283 | 284 | re = new RE2('a', 'migyu'); 285 | eval(t.TEST("re.flags === 'gimuy'")); 286 | 287 | re = new RE2('a', 'smigyu'); 288 | eval(t.TEST("re.flags === 'gimsuy'")); 289 | }, 290 | function test_flags_2nd(t) { 291 | 'use strict'; 292 | 293 | var re = new RE2(/a/, 'u'); 294 | eval(t.TEST("re.flags === 'u'")); 295 | 296 | re = new RE2(/a/gm, 'iu'); 297 | eval(t.TEST("re.flags === 'iu'")); 298 | 299 | re = new RE2(/a/gi, 'mu'); 300 | eval(t.TEST("re.flags === 'mu'")); 301 | 302 | re = new RE2(/a/g, 'gu'); 303 | eval(t.TEST("re.flags === 'gu'")); 304 | 305 | re = new RE2(/a/m, 'yu'); 306 | eval(t.TEST("re.flags === 'uy'")); 307 | 308 | re = new RE2(/a/, 'yiu'); 309 | eval(t.TEST("re.flags === 'iuy'")); 310 | 311 | re = new RE2(/a/gim, 'yigu'); 312 | eval(t.TEST("re.flags === 'giuy'")); 313 | 314 | re = new RE2(/a/gm, 'miu'); 315 | eval(t.TEST("re.flags === 'imu'")); 316 | 317 | re = new RE2(/a/i, 'ygu'); 318 | eval(t.TEST("re.flags === 'guy'")); 319 | 320 | re = new RE2(/a/g, 'myu'); 321 | eval(t.TEST("re.flags === 'muy'")); 322 | 323 | re = new RE2(/a/, 'migyu'); 324 | eval(t.TEST("re.flags === 'gimuy'")); 325 | 326 | re = new RE2(/a/s, 'smigyu'); 327 | eval(t.TEST("re.flags === 'gimsuy'")); 328 | } 329 | ]); 330 | 331 | // utilitites 332 | 333 | function compare(re1, re2, t) { 334 | // compares regular expression objects 335 | eval(t.TEST('re1.source === re2.source')); 336 | eval(t.TEST('re1.global === re2.global')); 337 | eval(t.TEST('re1.ignoreCase === re2.ignoreCase')); 338 | eval(t.TEST('re1.multiline === re2.multiline')); 339 | // eval(t.TEST("re1.unicode === re2.unicode")); 340 | eval(t.TEST('re1.sticky === re2.sticky')); 341 | } 342 | -------------------------------------------------------------------------------- /tests/test_groups.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | function test_groupsNormal(t) { 10 | 'use strict'; 11 | 12 | eval(t.TEST("RE2('(?\\\\d)').test('9')")); 13 | eval(t.TEST("t.unify(RE2('(?-)', 'g').match('a-b-c'), ['-', '-'])")); 14 | eval( 15 | t.TEST( 16 | "t.unify(RE2('(?-)').split('a-b-c'), ['a', '-', 'b', '-', 'c'])" 17 | ) 18 | ); 19 | eval(t.TEST("RE2('(?-)', 'g').search('a-b-c') === 1")); 20 | }, 21 | function test_groupsExec(t) { 22 | 'use strict'; 23 | 24 | var result = new RE2('(\\d)').exec('k9'); 25 | eval(t.TEST('result')); 26 | eval(t.TEST("result[0] === '9'")); 27 | eval(t.TEST("result[1] === '9'")); 28 | eval(t.TEST('result.index === 1')); 29 | eval(t.TEST("result.input === 'k9'")); 30 | eval(t.TEST("typeof result.groups == 'undefined'")); 31 | 32 | result = new RE2('(?\\d)').exec('k9'); 33 | eval(t.TEST('result')); 34 | eval(t.TEST("result[0] === '9'")); 35 | eval(t.TEST("result[1] === '9'")); 36 | eval(t.TEST('result.index === 1')); 37 | eval(t.TEST("result.input === 'k9'")); 38 | eval(t.TEST("t.unify(result.groups, {a: '9'})")); 39 | }, 40 | function test_groupsMatch(t) { 41 | 'use strict'; 42 | 43 | var result = new RE2('(\\d)').match('k9'); 44 | eval(t.TEST('result')); 45 | eval(t.TEST("result[0] === '9'")); 46 | eval(t.TEST("result[1] === '9'")); 47 | eval(t.TEST('result.index === 1')); 48 | eval(t.TEST("result.input === 'k9'")); 49 | eval(t.TEST("typeof result.groups == 'undefined'")); 50 | 51 | result = new RE2('(?\\d)').match('k9'); 52 | eval(t.TEST('result')); 53 | eval(t.TEST("result[0] === '9'")); 54 | eval(t.TEST("result[1] === '9'")); 55 | eval(t.TEST('result.index === 1')); 56 | eval(t.TEST("result.input === 'k9'")); 57 | eval(t.TEST("t.unify(result.groups, {a: '9'})")); 58 | }, 59 | function test_groupsMatch(t) { 60 | 'use strict'; 61 | 62 | eval( 63 | t.TEST( 64 | "RE2('(?\\\\w)(?\\\\d)', 'g').replace('a1b2c', '$2$1') === '1a2bc'" 65 | ) 66 | ); 67 | eval( 68 | t.TEST( 69 | "RE2('(?\\\\w)(?\\\\d)', 'g').replace('a1b2c', '$$') === '1a2bc'" 70 | ) 71 | ); 72 | 73 | eval( 74 | t.TEST( 75 | "RE2('(?\\\\w)(?\\\\d)', 'g').replace('a1b2c', replacerByNumbers) === '1a2bc'" 76 | ) 77 | ); 78 | eval( 79 | t.TEST( 80 | "RE2('(?\\\\w)(?\\\\d)', 'g').replace('a1b2c', replacerByNames) === '1a2bc'" 81 | ) 82 | ); 83 | 84 | function replacerByNumbers(match, group1, group2, index, source, groups) { 85 | return group2 + group1; 86 | } 87 | function replacerByNames(match, group1, group2, index, source, groups) { 88 | return groups.d + groups.w; 89 | } 90 | }, 91 | function test_groupsInvalid(t) { 92 | 'use strict'; 93 | 94 | try { 95 | RE2('(?<>.)'); 96 | t.test(false); // shouldn'be here 97 | } catch (e) { 98 | eval(t.TEST('e instanceof SyntaxError')); 99 | } 100 | 101 | // TODO: do we need to enforce the correct id? 102 | // try { 103 | // RE2('(?<1>.)'); 104 | // t.test(false); // shouldn'be here 105 | // } catch(e) { 106 | // eval(t.TEST("e instanceof SyntaxError")); 107 | // } 108 | 109 | try { 110 | RE2('(?.)(?.)'); 111 | t.test(false); // shouldn'be here 112 | } catch (e) { 113 | eval(t.TEST('e instanceof SyntaxError')); 114 | } 115 | } 116 | ]); 117 | -------------------------------------------------------------------------------- /tests/test_invalid.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | function test_inval(t) { 10 | 'use strict'; 11 | 12 | var threw; 13 | 14 | // Backreferences 15 | threw = false; 16 | try { 17 | new RE2(/(a)\1/); 18 | } catch (e) { 19 | threw = true; 20 | eval(t.TEST('e instanceof SyntaxError')); 21 | eval(t.TEST("e.message === 'invalid escape sequence: \\\\1'")); 22 | } 23 | t.test(threw); 24 | 25 | // Lookahead assertions 26 | 27 | // Positive 28 | threw = false; 29 | try { 30 | new RE2(/a(?=b)/); 31 | } catch (e) { 32 | threw = true; 33 | eval(t.TEST('e instanceof SyntaxError')); 34 | eval(t.TEST("e.message === 'invalid perl operator: (?='")); 35 | } 36 | t.test(threw); 37 | 38 | // Negative 39 | threw = false; 40 | try { 41 | new RE2(/a(?!b)/); 42 | } catch (e) { 43 | threw = true; 44 | eval(t.TEST('e instanceof SyntaxError')); 45 | eval(t.TEST("e.message === 'invalid perl operator: (?!'")); 46 | } 47 | t.test(threw); 48 | } 49 | ]); 50 | -------------------------------------------------------------------------------- /tests/test_match.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | // These tests are copied from MDN: 10 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match 11 | 12 | function test_match(t) { 13 | 'use strict'; 14 | 15 | var str = 'For more information, see Chapter 3.4.5.1'; 16 | 17 | var re = new RE2(/(chapter \d+(\.\d)*)/i); 18 | var result = re.match(str); 19 | 20 | eval(t.TEST('result.input === str')); 21 | eval(t.TEST('result.index === 26')); 22 | eval(t.TEST('result.length === 3')); 23 | eval(t.TEST("result[0] === 'Chapter 3.4.5.1'")); 24 | eval(t.TEST("result[1] === 'Chapter 3.4.5.1'")); 25 | eval(t.TEST("result[2] === '.1'")); 26 | }, 27 | function test_matchGlobal(t) { 28 | 'use strict'; 29 | 30 | var re = new RE2(/[A-E]/gi); 31 | var result = re.match( 32 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' 33 | ); 34 | 35 | eval( 36 | t.TEST( 37 | "t.unify(result, ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e'])" 38 | ) 39 | ); 40 | }, 41 | function test_matchFail(t) { 42 | 'use strict'; 43 | 44 | var re = new RE2('(a+)?(b+)?'); 45 | var result = re.match('aaabb'); 46 | 47 | eval(t.TEST("result[1] === 'aaa'")); 48 | eval(t.TEST("result[2] === 'bb'")); 49 | 50 | result = re.match('aaacbb'); 51 | 52 | eval(t.TEST("result[1] === 'aaa'")); 53 | eval(t.TEST('result[2] === undefined')); 54 | }, 55 | function test_matchInvalid(t) { 56 | 'use strict'; 57 | 58 | var re = RE2(''); 59 | 60 | try { 61 | re.match({ 62 | toString() { 63 | throw 'corner'; 64 | } 65 | }); 66 | t.test(false); // shouldn't be here 67 | } catch (e) { 68 | eval(t.TEST("e === 'corner'")); 69 | } 70 | }, 71 | 72 | // Unicode tests 73 | 74 | function test_matchUnicode(t) { 75 | 'use strict'; 76 | 77 | var str = 'Это ГЛАВА 3.4.5.1'; 78 | 79 | var re = new RE2(/(глава \d+(\.\d)*)/i); 80 | var result = re.match(str); 81 | 82 | eval(t.TEST('result.input === str')); 83 | eval(t.TEST('result.index === 4')); 84 | eval(t.TEST('result.length === 3')); 85 | eval(t.TEST("result[0] === 'ГЛАВА 3.4.5.1'")); 86 | eval(t.TEST("result[1] === 'ГЛАВА 3.4.5.1'")); 87 | eval(t.TEST("result[2] === '.1'")); 88 | }, 89 | 90 | // Buffer tests 91 | 92 | function test_matchBuffer(t) { 93 | 'use strict'; 94 | 95 | var buf = new Buffer('Это ГЛАВА 3.4.5.1'); 96 | 97 | var re = new RE2(/(глава \d+(\.\d)*)/i); 98 | var result = re.match(buf); 99 | 100 | eval(t.TEST('result.input instanceof Buffer')); 101 | eval(t.TEST('result.length === 3')); 102 | eval(t.TEST('result[0] instanceof Buffer')); 103 | eval(t.TEST('result[1] instanceof Buffer')); 104 | eval(t.TEST('result[2] instanceof Buffer')); 105 | 106 | eval(t.TEST('result.input === buf')); 107 | eval(t.TEST('result.index === 7')); 108 | eval( 109 | t.TEST("result.input.toString('utf8', result.index) === 'ГЛАВА 3.4.5.1'") 110 | ); 111 | eval(t.TEST("result[0].toString() === 'ГЛАВА 3.4.5.1'")); 112 | eval(t.TEST("result[1].toString() === 'ГЛАВА 3.4.5.1'")); 113 | eval(t.TEST("result[2].toString() === '.1'")); 114 | }, 115 | 116 | // Sticky tests 117 | 118 | function test_matchSticky(t) { 119 | 'use strict'; 120 | 121 | var re = new RE2('\\s+', 'y'); 122 | 123 | eval(t.TEST("re.match('Hello world, how are you?') === null")); 124 | 125 | re.lastIndex = 5; 126 | 127 | var result = re.match('Hello world, how are you?'); 128 | 129 | eval(t.TEST("t.unify(result, [' '])")); 130 | eval(t.TEST('result.index === 5')); 131 | eval(t.TEST('re.lastIndex === 6')); 132 | 133 | var re2 = new RE2('\\s+', 'gy'); 134 | 135 | eval(t.TEST("re2.match('Hello world, how are you?') === null")); 136 | 137 | re2.lastIndex = 5; 138 | 139 | eval(t.TEST("re2.match('Hello world, how are you?') === null")); 140 | 141 | var re3 = new RE2(/[A-E]/giy); 142 | var result3 = re3.match( 143 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' 144 | ); 145 | 146 | eval(t.TEST("t.unify(result3, ['A', 'B', 'C', 'D', 'E'])")); 147 | }, 148 | 149 | // hasIndices tests 150 | 151 | function test_matchHasIndices(t) { 152 | 'use strict'; 153 | 154 | const re = new RE2('(aa)(?b)?(?ccc)', 'd'), 155 | str1 = '1aabccc2', 156 | str2 = '1aaccc2'; 157 | 158 | eval(t.TEST('t.unify(str1.match(re), re.exec(str1))')); 159 | eval(t.TEST('t.unify(str2.match(re), re.exec(str2))')); 160 | }, 161 | 162 | function test_matchHasIndicesGlobal(t) { 163 | 'use strict'; 164 | 165 | const re = new RE2('(?a)', 'dg'), 166 | result = 'abca'.match(re); 167 | 168 | eval(t.TEST("t.unify(result, ['a', 'a'])")); 169 | eval(t.TEST("!('indices' in result)")); 170 | eval(t.TEST("!('groups' in result)")); 171 | }, 172 | 173 | function test_matchLastIndex(t) { 174 | 'use strict'; 175 | 176 | const re = new RE2(/./g), 177 | pattern = 'Я123'; 178 | 179 | re.lastIndex = 2; 180 | const result1 = pattern.match(re); 181 | eval(t.TEST("t.unify(result1, ['Я', '1', '2', '3'])")); 182 | eval(t.TEST('re.lastIndex === 0')); 183 | 184 | const re2 = RE2(re); 185 | re2.lastIndex = 2; 186 | const result2 = re2.match(Buffer.from(pattern)); 187 | eval( 188 | t.TEST("t.unify(result2.map(b => b.toString()), ['Я', '1', '2', '3'])") 189 | ); 190 | eval(t.TEST('re2.lastIndex === 0')); 191 | } 192 | ]); 193 | -------------------------------------------------------------------------------- /tests/test_matchAll.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const unit = require('heya-unit'); 4 | const RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | // These tests are copied from MDN: 10 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll 11 | 12 | function test_matchAll(t) { 13 | 'use strict'; 14 | 15 | const str = 'test1test2'; 16 | const re = new RE2(/t(e)(st(\d?))/g); 17 | const result = Array.from(str.matchAll(re)); 18 | 19 | eval(t.TEST('result.length === 2')); 20 | eval(t.TEST('result[0].input === str')); 21 | eval(t.TEST('result[0].index === 0')); 22 | eval(t.TEST('result[0].length === 4')); 23 | eval(t.TEST("result[0][0] === 'test1'")); 24 | eval(t.TEST("result[0][1] === 'e'")); 25 | eval(t.TEST("result[0][2] === 'st1'")); 26 | eval(t.TEST("result[0][3] === '1'")); 27 | eval(t.TEST('result[1].input === str')); 28 | eval(t.TEST('result[1].index === 5')); 29 | eval(t.TEST('result[1].length === 4')); 30 | eval(t.TEST("result[1][0] === 'test2'")); 31 | eval(t.TEST("result[1][1] === 'e'")); 32 | eval(t.TEST("result[1][2] === 'st2'")); 33 | eval(t.TEST("result[1][3] === '2'")); 34 | }, 35 | 36 | function test_matchAll_iterator(t) { 37 | 'use strict'; 38 | 39 | const str = 'table football, foosball'; 40 | const re = new RE2('foo[a-z]*', 'g'); 41 | 42 | const expected = [ 43 | {start: 6, finish: 14}, 44 | {start: 16, finish: 24} 45 | ]; 46 | let i = 0; 47 | for (const match of str.matchAll(re)) { 48 | eval(t.TEST('match.index === expected[i].start')); 49 | eval(t.TEST('match.index + match[0].length === expected[i].finish')); 50 | ++i; 51 | } 52 | }, 53 | 54 | function test_matchAll_non_global(t) { 55 | 'use strict'; 56 | 57 | const re = RE2('b'); 58 | 59 | try { 60 | 'abc'.matchAll(re); 61 | t.test(false); // shouldn't be here 62 | } catch (e) { 63 | eval(t.TEST('e instanceof TypeError')); 64 | } 65 | }, 66 | 67 | function test_matchAll_lastIndex(t) { 68 | 'use strict'; 69 | 70 | const re = RE2('[a-c]', 'g'); 71 | re.lastIndex = 1; 72 | 73 | const expected = ['b', 'c']; 74 | let i = 0; 75 | for (const match of 'abc'.matchAll(re)) { 76 | eval(t.TEST('re.lastIndex === 1')); 77 | eval(t.TEST('match[0] === expected[i]')); 78 | ++i; 79 | } 80 | }, 81 | 82 | function test_matchAll_empty_match(t) { 83 | 'use strict'; 84 | 85 | const str = 'foo'; 86 | // Matches empty strings, but should not cause an infinite loop 87 | const re = new RE2('(?:)', 'g'); 88 | const result = Array.from(str.matchAll(re)); 89 | 90 | eval(t.TEST('result.length === str.length + 1')); 91 | for (let i = 0; i < result.length; ++i) { 92 | eval(t.TEST('result[i][0] === ""')); 93 | } 94 | } 95 | ]); 96 | -------------------------------------------------------------------------------- /tests/test_new.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const unit = require('heya-unit'); 4 | const RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | function test_newUnicodeWarnOnce(t) { 10 | 'use strict'; 11 | 12 | let errorMessage = null; 13 | 14 | const consoleError = console.error; 15 | console.error = msg => (errorMessage = msg); 16 | RE2.unicodeWarningLevel = 'warnOnce'; 17 | 18 | let a = new RE2('.*'); 19 | eval(t.TEST('errorMessage')); 20 | errorMessage = null; 21 | 22 | a = new RE2('.?'); 23 | eval(t.TEST('errorMessage === null')); 24 | 25 | RE2.unicodeWarningLevel = 'warnOnce'; 26 | a = new RE2('.+'); 27 | eval(t.TEST('errorMessage')); 28 | 29 | RE2.unicodeWarningLevel = 'nothing'; 30 | console.error = consoleError; 31 | }, 32 | function test_newUnicodeWarn(t) { 33 | 'use strict'; 34 | 35 | let errorMessage = null; 36 | 37 | const consoleError = console.error; 38 | console.error = msg => (errorMessage = msg); 39 | RE2.unicodeWarningLevel = 'warn'; 40 | 41 | let a = new RE2('.*'); 42 | eval(t.TEST('errorMessage')); 43 | errorMessage = null; 44 | 45 | a = new RE2('.?'); 46 | eval(t.TEST('errorMessage')); 47 | 48 | RE2.unicodeWarningLevel = 'nothing'; 49 | console.error = consoleError; 50 | }, 51 | function test_newUnicodeWarn(t) { 52 | 'use strict'; 53 | 54 | RE2.unicodeWarningLevel = 'throw'; 55 | 56 | try { 57 | let a = new RE2('.'); 58 | t.test(false); // shouldn't be here 59 | } catch (e) { 60 | eval(t.TEST('e instanceof SyntaxError')); 61 | } 62 | 63 | RE2.unicodeWarningLevel = 'nothing'; 64 | } 65 | ]); 66 | -------------------------------------------------------------------------------- /tests/test_prototype.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | function test_prototype(t) { 10 | 'use strict'; 11 | 12 | eval(t.TEST("RE2.prototype.source === '(?:)'")); 13 | eval(t.TEST("RE2.prototype.flags === ''")); 14 | eval(t.TEST('RE2.prototype.global === undefined')); 15 | eval(t.TEST('RE2.prototype.ignoreCase === undefined')); 16 | eval(t.TEST('RE2.prototype.multiline === undefined')); 17 | eval(t.TEST('RE2.prototype.dotAll === undefined')); 18 | eval(t.TEST('RE2.prototype.sticky === undefined')); 19 | eval(t.TEST('RE2.prototype.lastIndex === undefined')); 20 | } 21 | ]); 22 | -------------------------------------------------------------------------------- /tests/test_replace.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | // These tests are copied from MDN: 10 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace 11 | 12 | function test_replaceString(t) { 13 | 'use strict'; 14 | 15 | var re = new RE2(/apples/gi); 16 | var result = re.replace( 17 | 'Apples are round, and apples are juicy.', 18 | 'oranges' 19 | ); 20 | eval(t.TEST("result === 'oranges are round, and oranges are juicy.'")); 21 | 22 | re = new RE2(/xmas/i); 23 | result = re.replace('Twas the night before Xmas...', 'Christmas'); 24 | eval(t.TEST("result === 'Twas the night before Christmas...'")); 25 | 26 | re = new RE2(/(\w+)\s(\w+)/); 27 | result = re.replace('John Smith', '$2, $1'); 28 | eval(t.TEST("result === 'Smith, John'")); 29 | }, 30 | function test_replaceFunReplacer(t) { 31 | 'use strict'; 32 | 33 | function replacer(match, p1, p2, p3, offset, string) { 34 | // p1 is nondigits, p2 digits, and p3 non-alphanumerics 35 | return [p1, p2, p3].join(' - '); 36 | } 37 | 38 | var re = new RE2(/([^\d]*)(\d*)([^\w]*)/); 39 | var result = re.replace('abc12345#$*%', replacer); 40 | eval(t.TEST("result === 'abc - 12345 - #$*%'")); 41 | }, 42 | function test_replaceFunUpper(t) { 43 | 'use strict'; 44 | 45 | function upperToHyphenLower(match) { 46 | return '-' + match.toLowerCase(); 47 | } 48 | 49 | var re = new RE2(/[A-Z]/g); 50 | var result = re.replace('borderTop', upperToHyphenLower); 51 | eval(t.TEST("result === 'border-top'")); 52 | }, 53 | function test_replaceFunConvert(t) { 54 | 'use strict'; 55 | 56 | function convert(str, p1, offset, s) { 57 | return ((p1 - 32) * 5) / 9 + 'C'; 58 | } 59 | 60 | var re = new RE2(/(\d+(?:\.\d*)?)F\b/g); 61 | 62 | eval(t.TEST("re.replace('32F', convert) === '0C'")); 63 | eval(t.TEST("re.replace('41F', convert) === '5C'")); 64 | eval(t.TEST("re.replace('50F', convert) === '10C'")); 65 | eval(t.TEST("re.replace('59F', convert) === '15C'")); 66 | eval(t.TEST("re.replace('68F', convert) === '20C'")); 67 | eval(t.TEST("re.replace('77F', convert) === '25C'")); 68 | eval(t.TEST("re.replace('86F', convert) === '30C'")); 69 | eval(t.TEST("re.replace('95F', convert) === '35C'")); 70 | eval(t.TEST("re.replace('104F', convert) === '40C'")); 71 | eval(t.TEST("re.replace('113F', convert) === '45C'")); 72 | eval(t.TEST("re.replace('212F', convert) === '100C'")); 73 | }, 74 | { 75 | test: function test_replaceFunLoop(t) { 76 | 'use strict'; 77 | 78 | RE2(/(x_*)|(-)/g).replace('x-x_', function (match, p1, p2) { 79 | if (p1) { 80 | t.info('on: ' + p1.length); 81 | } 82 | if (p2) { 83 | t.info('off: 1'); 84 | } 85 | }); 86 | }, 87 | logs: [{text: 'on: 1'}, {text: 'off: 1'}, {text: 'on: 2'}] 88 | }, 89 | function test_replaceInvalid(t) { 90 | 'use strict'; 91 | 92 | var re = RE2(''); 93 | 94 | try { 95 | re.replace( 96 | { 97 | toString() { 98 | throw 'corner1'; 99 | } 100 | }, 101 | '' 102 | ); 103 | t.test(false); // shouldn't be here 104 | } catch (e) { 105 | eval(t.TEST("e === 'corner1'")); 106 | } 107 | 108 | try { 109 | re.replace('', { 110 | toString() { 111 | throw 'corner2'; 112 | } 113 | }); 114 | t.test(false); // shouldn't be here 115 | } catch (e) { 116 | eval(t.TEST("e === 'corner2'")); 117 | } 118 | 119 | var arg2Stringified = false; 120 | 121 | try { 122 | re.replace( 123 | { 124 | toString() { 125 | throw 'corner1'; 126 | } 127 | }, 128 | { 129 | toString() { 130 | arg2Stringified = true; 131 | throw 'corner2'; 132 | } 133 | } 134 | ); 135 | t.test(false); // shouldn't be here 136 | } catch (e) { 137 | eval(t.TEST("e === 'corner1'")); 138 | eval(t.TEST('!arg2Stringified')); 139 | } 140 | 141 | try { 142 | re.replace('', () => { 143 | throw 'corner2'; 144 | }); 145 | t.test(false); // shouldn't be here 146 | } catch (e) { 147 | eval(t.TEST("e === 'corner2'")); 148 | } 149 | 150 | try { 151 | re.replace('', () => ({ 152 | toString() { 153 | throw 'corner2'; 154 | } 155 | })); 156 | t.test(false); // shouldn't be here 157 | } catch (e) { 158 | eval(t.TEST("e === 'corner2'")); 159 | } 160 | }, 161 | 162 | // Unicode tests 163 | 164 | function test_replaceStrUnicode(t) { 165 | 'use strict'; 166 | 167 | var re = new RE2(/яблоки/gi); 168 | var result = re.replace('Яблоки красны, яблоки сочны.', 'апельсины'); 169 | eval(t.TEST("result === 'апельсины красны, апельсины сочны.'")); 170 | 171 | re = new RE2(/иван/i); 172 | result = re.replace('Могуч Иван Иванов...', 'Сидор'); 173 | eval(t.TEST("result === 'Могуч Сидор Иванов...'")); 174 | 175 | re = new RE2(/иван/gi); 176 | result = re.replace('Могуч Иван Иванов...', 'Сидор'); 177 | eval(t.TEST("result === 'Могуч Сидор Сидоров...'")); 178 | 179 | re = new RE2(/([а-яё]+)\s+([а-яё]+)/i); 180 | result = re.replace('Пётр Петров', '$2, $1'); 181 | eval(t.TEST("result === 'Петров, Пётр'")); 182 | }, 183 | function test_replaceFunUnicode(t) { 184 | 'use strict'; 185 | 186 | function replacer(match, offset, string) { 187 | t.test(typeof offset == 'number'); 188 | t.test(typeof string == 'string'); 189 | t.test(offset === 0 || offset === 7); 190 | t.test(string === 'ИВАН и пЁтр'); 191 | return match.charAt(0).toUpperCase() + match.substr(1).toLowerCase(); 192 | } 193 | 194 | var re = new RE2(/(?:иван|пётр|сидор)/gi); 195 | var result = re.replace('ИВАН и пЁтр', replacer); 196 | eval(t.TEST("result === 'Иван и Пётр'")); 197 | }, 198 | 199 | // Buffer tests 200 | 201 | function test_replaceStrBuffer(t) { 202 | 'use strict'; 203 | 204 | var re = new RE2(/яблоки/gi); 205 | var result = re.replace( 206 | new Buffer('Яблоки красны, яблоки сочны.'), 207 | 'апельсины' 208 | ); 209 | eval(t.TEST('result instanceof Buffer')); 210 | eval(t.TEST("result.toString() === 'апельсины красны, апельсины сочны.'")); 211 | 212 | result = re.replace( 213 | new Buffer('Яблоки красны, яблоки сочны.'), 214 | new Buffer('апельсины') 215 | ); 216 | eval(t.TEST('result instanceof Buffer')); 217 | eval(t.TEST("result.toString() === 'апельсины красны, апельсины сочны.'")); 218 | 219 | result = re.replace( 220 | 'Яблоки красны, яблоки сочны.', 221 | new Buffer('апельсины') 222 | ); 223 | eval(t.TEST("typeof result == 'string'")); 224 | eval(t.TEST("result === 'апельсины красны, апельсины сочны.'")); 225 | }, 226 | function test_replaceFunBuffer(t) { 227 | 'use strict'; 228 | 229 | function replacer(match, offset, string) { 230 | eval(t.TEST('match instanceof Buffer')); 231 | eval(t.TEST("typeof offset == 'number'")); 232 | eval(t.TEST("typeof string == 'string'")); 233 | eval(t.TEST('offset === 0 || offset === 12')); 234 | eval(t.TEST("string === 'ИВАН и пЁтр'")); 235 | var s = match.toString(); 236 | return s.charAt(0).toUpperCase() + s.substr(1).toLowerCase(); 237 | } 238 | replacer.useBuffers = true; 239 | 240 | var re = new RE2(/(?:иван|пётр|сидор)/gi); 241 | var result = re.replace('ИВАН и пЁтр', replacer); 242 | eval(t.TEST("typeof result == 'string'")); 243 | eval(t.TEST("result === 'Иван и Пётр'")); 244 | }, 245 | function test_replace0(t) { 246 | 'use strict'; 247 | 248 | function replacer(match) { 249 | return 'MARKER' + match; 250 | } 251 | 252 | var re = new RE2(/^/g); 253 | var result = re.replace('foo bar', 'MARKER'); 254 | eval(t.TEST("result === 'MARKERfoo bar'")); 255 | result = re.replace('foo bar', replacer); 256 | eval(t.TEST("result === 'MARKERfoo bar'")); 257 | 258 | re = new RE2(/$/g); 259 | result = re.replace('foo bar', 'MARKER'); 260 | eval(t.TEST("result === 'foo barMARKER'")); 261 | result = re.replace('foo bar', replacer); 262 | eval(t.TEST("result === 'foo barMARKER'")); 263 | 264 | re = new RE2(/\b/g); 265 | result = re.replace('foo bar', 'MARKER'); 266 | eval(t.TEST("result === 'MARKERfooMARKER MARKERbarMARKER'")); 267 | result = re.replace('foo bar', replacer); 268 | eval(t.TEST("result === 'MARKERfooMARKER MARKERbarMARKER'")); 269 | }, 270 | 271 | // Sticky tests 272 | 273 | function test_replaceSticky(t) { 274 | 'use strict'; 275 | 276 | var re = new RE2(/[A-E]/y); 277 | 278 | eval(t.TEST("re.replace('ABCDEFABCDEF', '!') === '!BCDEFABCDEF'")); 279 | eval(t.TEST("re.replace('ABCDEFABCDEF', '!') === 'A!CDEFABCDEF'")); 280 | eval(t.TEST("re.replace('ABCDEFABCDEF', '!') === 'AB!DEFABCDEF'")); 281 | eval(t.TEST("re.replace('ABCDEFABCDEF', '!') === 'ABC!EFABCDEF'")); 282 | eval(t.TEST("re.replace('ABCDEFABCDEF', '!') === 'ABCD!FABCDEF'")); 283 | eval(t.TEST("re.replace('ABCDEFABCDEF', '!') === 'ABCDEFABCDEF'")); 284 | eval(t.TEST("re.replace('ABCDEFABCDEF', '!') === '!BCDEFABCDEF'")); 285 | 286 | var re2 = new RE2(/[A-E]/gy); 287 | 288 | eval(t.TEST("re2.replace('ABCDEFABCDEF', '!') === '!!!!!FABCDEF'")); 289 | eval(t.TEST("re2.replace('FABCDEFABCDE', '!') === 'FABCDEFABCDE'")); 290 | 291 | re2.lastIndex = 3; 292 | 293 | eval(t.TEST("re2.replace('ABCDEFABCDEF', '!') === '!!!!!FABCDEF'")); 294 | eval(t.TEST('re2.lastIndex === 0')); 295 | }, 296 | 297 | // Non-matches 298 | 299 | function test_replaceOneNonMatch(t) { 300 | 'use strict'; 301 | 302 | function replacer(match, capture, offset, string) { 303 | t.test(typeof offset == 'number'); 304 | t.test(typeof match == 'string'); 305 | t.test(typeof string == 'string'); 306 | t.test(typeof capture == 'undefined'); 307 | t.test(offset === 0); 308 | t.test(string === 'hello '); 309 | return ''; 310 | } 311 | 312 | var re = new RE2(/hello (world)?/); 313 | re.replace('hello ', replacer); 314 | }, 315 | function test_replaceTwoNonMatches(t) { 316 | 'use strict'; 317 | 318 | function replacer(match, capture1, capture2, offset, string, groups) { 319 | t.test(typeof offset == 'number'); 320 | t.test(typeof match == 'string'); 321 | t.test(typeof string == 'string'); 322 | t.test(typeof capture1 == 'undefined'); 323 | t.test(typeof capture2 == 'undefined'); 324 | t.test(offset === 1); 325 | t.test(match === 'b & y'); 326 | t.test(string === 'ab & yz'); 327 | t.test(typeof groups == 'object'); 328 | t.test(Object.keys(groups).length == 2); 329 | t.test(groups.a === undefined); 330 | t.test(groups.b == undefined); 331 | return ''; 332 | } 333 | 334 | var re = new RE2(/b(?1)? & (?2)?y/); 335 | var result = re.replace('ab & yz', replacer); 336 | eval(t.TEST("result === 'az'")); 337 | }, 338 | function test_replaceGroupSimple(t) { 339 | 'use strict'; 340 | 341 | var re = new RE2(/(2)/); 342 | 343 | var result = re.replace('123', '$0'); 344 | eval(t.TEST("result === '1$03'")); 345 | result = re.replace('123', '$1'); 346 | eval(t.TEST("result === '123'")); 347 | result = re.replace('123', '$2'); 348 | eval(t.TEST("result === '1$23'")); 349 | 350 | result = re.replace('123', '$00'); 351 | eval(t.TEST("result === '1$003'")); 352 | result = re.replace('123', '$01'); 353 | eval(t.TEST("result === '123'")); 354 | result = re.replace('123', '$02'); 355 | eval(t.TEST("result === '1$023'")); 356 | }, 357 | function test_replaceGroupCases(t) { 358 | 'use strict'; 359 | 360 | var re = new RE2(/(test)/g); 361 | var result = re.replace('123', '$1$20'); 362 | eval(t.TEST("result === '123'")); 363 | 364 | re = new RE2(/(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)/g); 365 | result = re.replace('abcdefghijklmnopqrstuvwxyz123', '$10$20'); 366 | eval(t.TEST("result === 'jb0wo0123'")); 367 | 368 | re = new RE2(/(.)(.)(.)(.)(.)/g); 369 | result = re.replace('abcdefghijklmnopqrstuvwxyz123', '$10$20'); 370 | eval(t.TEST("result === 'a0b0f0g0k0l0p0q0u0v0z123'")); 371 | 372 | re = new RE2( 373 | /(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)/g 374 | ); 375 | result = re.replace('abcdefghijklmnopqrstuvwxyz123', '$10$20'); 376 | eval(t.TEST("result === 'jtvwxyz123'")); 377 | 378 | re = new RE2(/abcd/g); 379 | result = re.replace('abcd123', '$1$2'); 380 | eval(t.TEST("result === '$1$2123'")); 381 | }, 382 | function test_emptyReplacement(t) { 383 | 'use strict'; 384 | 385 | eval(t.TEST("'ac' === 'abc'.replace(RE2('b'), '')")); 386 | } 387 | ]); 388 | -------------------------------------------------------------------------------- /tests/test_search.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | function test_search(t) { 10 | 'use strict'; 11 | 12 | var str = 'Total is 42 units.'; 13 | 14 | var re = new RE2(/\d+/i); 15 | var result = re.search(str); 16 | eval(t.TEST('result === 9')); 17 | 18 | re = new RE2('\\b[a-z]+\\b'); 19 | result = re.search(str); 20 | eval(t.TEST('result === 6')); 21 | 22 | re = new RE2('\\b\\w+\\b'); 23 | result = re.search(str); 24 | eval(t.TEST('result === 0')); 25 | 26 | re = new RE2('z', 'gm'); 27 | result = re.search(str); 28 | eval(t.TEST('result === -1')); 29 | }, 30 | function test_searchInvalid(t) { 31 | 'use strict'; 32 | 33 | var re = RE2(''); 34 | 35 | try { 36 | re.search({ 37 | toString() { 38 | throw 'corner'; 39 | } 40 | }); 41 | t.test(false); // shouldn't be here 42 | } catch (e) { 43 | eval(t.TEST("e === 'corner'")); 44 | } 45 | }, 46 | function test_searchUnicode(t) { 47 | 'use strict'; 48 | 49 | var str = 'Всего 42 штуки.'; 50 | 51 | var re = new RE2(/\d+/i); 52 | var result = re.search(str); 53 | eval(t.TEST('result === 6')); 54 | 55 | re = new RE2('\\s[а-я]+'); 56 | result = re.search(str); 57 | eval(t.TEST('result === 8')); 58 | 59 | re = new RE2('[а-яА-Я]+'); 60 | result = re.search(str); 61 | eval(t.TEST('result === 0')); 62 | 63 | re = new RE2('z', 'gm'); 64 | result = re.search(str); 65 | eval(t.TEST('result === -1')); 66 | }, 67 | function test_searchBuffer(t) { 68 | 'use strict'; 69 | 70 | var buf = new Buffer('Всего 42 штуки.'); 71 | 72 | var re = new RE2(/\d+/i); 73 | var result = re.search(buf); 74 | eval(t.TEST('result === 11')); 75 | 76 | re = new RE2('\\s[а-я]+'); 77 | result = re.search(buf); 78 | eval(t.TEST('result === 13')); 79 | 80 | re = new RE2('[а-яА-Я]+'); 81 | result = re.search(buf); 82 | eval(t.TEST('result === 0')); 83 | 84 | re = new RE2('z', 'gm'); 85 | result = re.search(buf); 86 | eval(t.TEST('result === -1')); 87 | }, 88 | function test_searchSticky(t) { 89 | 'use strict'; 90 | 91 | var str = 'Total is 42 units.'; 92 | 93 | var re = new RE2(/\d+/y); 94 | var result = re.search(str); 95 | eval(t.TEST('result === -1')); 96 | 97 | re = new RE2('\\b[a-z]+\\b', 'y'); 98 | result = re.search(str); 99 | eval(t.TEST('result === -1')); 100 | 101 | re = new RE2('\\b\\w+\\b', 'y'); 102 | result = re.search(str); 103 | eval(t.TEST('result === 0')); 104 | 105 | re = new RE2('z', 'gmy'); 106 | result = re.search(str); 107 | eval(t.TEST('result === -1')); 108 | } 109 | ]); 110 | -------------------------------------------------------------------------------- /tests/test_source.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | function test_sourceIdentity(t) { 10 | 'use strict'; 11 | 12 | var re = new RE2('a\\cM\\u34\\u1234\\u10abcdz'); 13 | eval(t.TEST("re.source === 'a\\\\cM\\\\u34\\\\u1234\\\\u10abcdz'")); 14 | 15 | re = new RE2('a\\cM\\u34\\u1234\\u{10abcd}z'); 16 | eval(t.TEST("re.source === 'a\\\\cM\\\\u34\\\\u1234\\\\u{10abcd}z'")); 17 | 18 | re = new RE2(''); 19 | eval(t.TEST("re.source === '(?:)'")); 20 | 21 | re = new RE2('foo/bar'); 22 | eval(t.TEST("re.source === 'foo\\\\/bar'")); 23 | 24 | re = new RE2('foo\\/bar'); 25 | eval(t.TEST("re.source === 'foo\\\\/bar'")); 26 | 27 | re = new RE2('(?bar)', 'u'); 28 | eval(t.TEST("re.source === '(?bar)'")); 29 | }, 30 | function test_sourceTranslation(t) { 31 | 'use strict'; 32 | 33 | var re = new RE2('a\\cM\\u34\\u1234\\u10abcdz'); 34 | eval( 35 | t.TEST( 36 | "re.internalSource === 'a\\\\x0D\\\\x{34}\\\\x{1234}\\\\x{10ab}cdz'" 37 | ) 38 | ); 39 | 40 | re = new RE2('a\\cM\\u34\\u1234\\u{10abcd}z'); 41 | eval( 42 | t.TEST( 43 | "re.internalSource === 'a\\\\x0D\\\\x{34}\\\\x{1234}\\\\x{10abcd}z'" 44 | ) 45 | ); 46 | 47 | re = new RE2(''); 48 | eval(t.TEST("re.internalSource === '(?:)'")); 49 | 50 | re = new RE2('foo/bar'); 51 | eval(t.TEST("re.internalSource === 'foo\\\\/bar'")); 52 | 53 | re = new RE2('foo\\/bar'); 54 | eval(t.TEST("re.internalSource === 'foo\\\\/bar'")); 55 | 56 | re = new RE2('(?bar)', 'u'); 57 | eval(t.TEST("re.internalSource === '(?Pbar)'")); 58 | 59 | re = new RE2('foo\\/bar', 'm'); 60 | eval(t.TEST("re.internalSource === '(?m)foo\\\\/bar'")); 61 | }, 62 | function test_sourceBackSlashes(t) { 63 | 'use strict'; 64 | 65 | function compare(source, expected) { 66 | var s = new RE2(source).source; 67 | eval(t.TEST('s === expected')); 68 | } 69 | 70 | compare('a/b', 'a\\/b'); 71 | compare('a\/b', 'a\\/b'); 72 | compare('a\\/b', 'a\\/b'); 73 | compare('a\\\/b', 'a\\/b'); 74 | compare('a\\\\/b', 'a\\\\\\/b'); 75 | compare('a\\\\\/b', 'a\\\\\\/b'); 76 | 77 | compare('/a/b', '\\/a\\/b'); 78 | compare('\\/a/b', '\\/a\\/b'); 79 | compare('\\/a\\/b', '\\/a\\/b'); 80 | compare('\\/a\\\\/b', '\\/a\\\\\\/b'); 81 | } 82 | ]); 83 | -------------------------------------------------------------------------------- /tests/test_split.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | // These tests are copied from MDN: 10 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split 11 | 12 | function test_split(t) { 13 | 'use strict'; 14 | 15 | var re = new RE2(/\s+/); 16 | var result = re.split('Oh brave new world that has such people in it.'); 17 | eval( 18 | t.TEST( 19 | "t.unify(result, ['Oh', 'brave', 'new', 'world', 'that', 'has', 'such', 'people', 'in', 'it.'])" 20 | ) 21 | ); 22 | 23 | re = new RE2(','); 24 | result = re.split('Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'); 25 | eval( 26 | t.TEST( 27 | "t.unify(result, ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'])" 28 | ) 29 | ); 30 | 31 | re = new RE2(','); 32 | result = re.split(',Jan,Feb,Mar,Apr,May,Jun,,Jul,Aug,Sep,Oct,Nov,Dec,'); 33 | eval( 34 | t.TEST( 35 | "t.unify(result, ['','Jan','Feb','Mar','Apr','May','Jun','','Jul','Aug','Sep','Oct','Nov','Dec',''])" 36 | ) 37 | ); 38 | 39 | re = new RE2(/\s*;\s*/); 40 | result = re.split( 41 | 'Harry Trump ;Fred Barney; Helen Rigby ; Bill Abel ;Chris Hand ' 42 | ); 43 | eval( 44 | t.TEST( 45 | "t.unify(result, ['Harry Trump', 'Fred Barney', 'Helen Rigby', 'Bill Abel', 'Chris Hand '])" 46 | ) 47 | ); 48 | 49 | re = new RE2(/\s+/); 50 | result = re.split('Hello World. How are you doing?', 3); 51 | eval(t.TEST("t.unify(result, ['Hello', 'World.', 'How'])")); 52 | 53 | re = new RE2(/(\d)/); 54 | result = re.split('Hello 1 word. Sentence number 2.'); 55 | eval( 56 | t.TEST( 57 | "t.unify(result, ['Hello ', '1', ' word. Sentence number ', '2', '.'])" 58 | ) 59 | ); 60 | 61 | eval( 62 | t.TEST( 63 | "RE2(/[x-z]*/).split('asdfghjkl').reverse().join('') === 'lkjhgfdsa'" 64 | ) 65 | ); 66 | }, 67 | function test_splitInvalid(t) { 68 | 'use strict'; 69 | 70 | var re = RE2(''); 71 | 72 | try { 73 | re.split({ 74 | toString() { 75 | throw 'corner'; 76 | } 77 | }); 78 | t.test(false); // shouldn't be here 79 | } catch (e) { 80 | eval(t.TEST("e === 'corner'")); 81 | } 82 | }, 83 | 84 | function test_cornerCases(t) { 85 | 'use strict'; 86 | 87 | var re = new RE2(/1/); 88 | var result = re.split('23456'); 89 | eval(t.TEST("t.unify(result, ['23456'])")); 90 | }, 91 | 92 | // Unicode tests 93 | 94 | function test_splitUnicode(t) { 95 | 'use strict'; 96 | 97 | var re = new RE2(/\s+/); 98 | var result = re.split('Она не понимает, что этим убивает меня.'); 99 | eval( 100 | t.TEST( 101 | "t.unify(result, ['Она', 'не', 'понимает,', 'что', 'этим', 'убивает', 'меня.'])" 102 | ) 103 | ); 104 | 105 | re = new RE2(','); 106 | result = re.split('Пн,Вт,Ср,Чт,Пт,Сб,Вс'); 107 | eval(t.TEST("t.unify(result, ['Пн','Вт','Ср','Чт','Пт','Сб','Вс'])")); 108 | 109 | re = new RE2(/\s*;\s*/); 110 | result = re.split('Ваня Иванов ;Петро Петренко; Саша Машин ; Маша Сашина'); 111 | eval( 112 | t.TEST( 113 | "t.unify(result, ['Ваня Иванов', 'Петро Петренко', 'Саша Машин', 'Маша Сашина'])" 114 | ) 115 | ); 116 | 117 | re = new RE2(/\s+/); 118 | result = re.split('Привет мир. Как дела?', 3); 119 | eval(t.TEST("t.unify(result, ['Привет', 'мир.', 'Как'])")); 120 | 121 | re = new RE2(/(\d)/); 122 | result = re.split('Привет 1 слово. Предложение номер 2.'); 123 | eval( 124 | t.TEST( 125 | "t.unify(result, ['Привет ', '1', ' слово. Предложение номер ', '2', '.'])" 126 | ) 127 | ); 128 | 129 | eval( 130 | t.TEST( 131 | "RE2(/[э-я]*/).split('фывапролд').reverse().join('') === 'длорпавыф'" 132 | ) 133 | ); 134 | }, 135 | 136 | // Buffer tests 137 | 138 | function test_splitBuffer(t) { 139 | 'use strict'; 140 | 141 | var re = new RE2(/\s+/); 142 | var result = re.split( 143 | new Buffer('Она не понимает, что этим убивает меня.') 144 | ); 145 | eval( 146 | t.TEST( 147 | "t.unify(verifyBuffer(result, t), ['Она', 'не', 'понимает,', 'что', 'этим', 'убивает', 'меня.'])" 148 | ) 149 | ); 150 | 151 | re = new RE2(','); 152 | result = re.split(new Buffer('Пн,Вт,Ср,Чт,Пт,Сб,Вс')); 153 | eval( 154 | t.TEST( 155 | "t.unify(verifyBuffer(result, t), ['Пн','Вт','Ср','Чт','Пт','Сб','Вс'])" 156 | ) 157 | ); 158 | 159 | re = new RE2(/\s*;\s*/); 160 | result = re.split( 161 | new Buffer('Ваня Иванов ;Петро Петренко; Саша Машин ; Маша Сашина') 162 | ); 163 | eval( 164 | t.TEST( 165 | "t.unify(verifyBuffer(result, t), ['Ваня Иванов', 'Петро Петренко', 'Саша Машин', 'Маша Сашина'])" 166 | ) 167 | ); 168 | 169 | re = new RE2(/\s+/); 170 | result = re.split(new Buffer('Привет мир. Как дела?'), 3); 171 | eval(t.TEST("t.unify(verifyBuffer(result, t), ['Привет', 'мир.', 'Как'])")); 172 | 173 | re = new RE2(/(\d)/); 174 | result = re.split(new Buffer('Привет 1 слово. Предложение номер 2.')); 175 | eval( 176 | t.TEST( 177 | "t.unify(verifyBuffer(result, t), ['Привет ', '1', ' слово. Предложение номер ', '2', '.'])" 178 | ) 179 | ); 180 | 181 | eval( 182 | t.TEST( 183 | "RE2(/[э-я]*/).split(new Buffer('фывапролд')).map(function(x) { return x.toString(); }).reverse().join('') === 'длорпавыф'" 184 | ) 185 | ); 186 | }, 187 | 188 | // Sticky tests 189 | 190 | function test_splitSticky(t) { 191 | 'use strict'; 192 | 193 | var re = new RE2(/\s+/y); // sticky is ignored 194 | 195 | var result = re.split('Oh brave new world that has such people in it.'); 196 | eval( 197 | t.TEST( 198 | "t.unify(result, ['Oh', 'brave', 'new', 'world', 'that', 'has', 'such', 'people', 'in', 'it.'])" 199 | ) 200 | ); 201 | 202 | var result2 = re.split(' Oh brave new world that has such people in it.'); 203 | eval( 204 | t.TEST( 205 | "t.unify(result2, ['', 'Oh', 'brave', 'new', 'world', 'that', 'has', 'such', 'people', 'in', 'it.'])" 206 | ) 207 | ); 208 | } 209 | ]); 210 | 211 | function verifyBuffer(buf, t) { 212 | return buf.map(function (x) { 213 | t.test(x instanceof Buffer); 214 | return x.toString(); 215 | }); 216 | } 217 | -------------------------------------------------------------------------------- /tests/test_symbols.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | function test_match_symbol(t) { 10 | 'use strict'; 11 | 12 | if (typeof Symbol == 'undefined' || !Symbol.match) return; 13 | 14 | var str = 'For more information, see Chapter 3.4.5.1'; 15 | 16 | var re = new RE2(/(chapter \d+(\.\d)*)/i); 17 | var result = str.match(re); 18 | 19 | eval(t.TEST('result.input === str')); 20 | eval(t.TEST('result.index === 26')); 21 | eval(t.TEST('result.length === 3')); 22 | eval(t.TEST("result[0] === 'Chapter 3.4.5.1'")); 23 | eval(t.TEST("result[1] === 'Chapter 3.4.5.1'")); 24 | eval(t.TEST("result[2] === '.1'")); 25 | }, 26 | function test_search_symbol(t) { 27 | 'use strict'; 28 | 29 | if (typeof Symbol == 'undefined' || !Symbol.search) return; 30 | 31 | var str = 'Total is 42 units.'; 32 | 33 | var re = new RE2(/\d+/i); 34 | var result = str.search(re); 35 | eval(t.TEST('result === 9')); 36 | 37 | re = new RE2('\\b[a-z]+\\b'); 38 | result = str.search(re); 39 | eval(t.TEST('result === 6')); 40 | 41 | re = new RE2('\\b\\w+\\b'); 42 | result = str.search(re); 43 | eval(t.TEST('result === 0')); 44 | 45 | re = new RE2('z', 'gm'); 46 | result = str.search(re); 47 | eval(t.TEST('result === -1')); 48 | }, 49 | function test_replace_symbol(t) { 50 | 'use strict'; 51 | 52 | if (typeof Symbol == 'undefined' || !Symbol.replace) return; 53 | 54 | var re = new RE2(/apples/gi); 55 | var result = 'Apples are round, and apples are juicy.'.replace( 56 | re, 57 | 'oranges' 58 | ); 59 | eval(t.TEST("result === 'oranges are round, and oranges are juicy.'")); 60 | 61 | re = new RE2(/xmas/i); 62 | result = 'Twas the night before Xmas...'.replace(re, 'Christmas'); 63 | eval(t.TEST("result === 'Twas the night before Christmas...'")); 64 | 65 | re = new RE2(/(\w+)\s(\w+)/); 66 | result = 'John Smith'.replace(re, '$2, $1'); 67 | eval(t.TEST("result === 'Smith, John'")); 68 | }, 69 | function test_split(t) { 70 | 'use strict'; 71 | 72 | if (typeof Symbol == 'undefined' || !Symbol.split) return; 73 | 74 | var re = new RE2(/\s+/); 75 | var result = 'Oh brave new world that has such people in it.'.split(re); 76 | eval( 77 | t.TEST( 78 | "t.unify(result, ['Oh', 'brave', 'new', 'world', 'that', 'has', 'such', 'people', 'in', 'it.'])" 79 | ) 80 | ); 81 | 82 | re = new RE2(','); 83 | result = 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(re); 84 | eval( 85 | t.TEST( 86 | "t.unify(result, ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'])" 87 | ) 88 | ); 89 | 90 | re = new RE2(/\s*;\s*/); 91 | result = 92 | 'Harry Trump ;Fred Barney; Helen Rigby ; Bill Abel ;Chris Hand '.split( 93 | re 94 | ); 95 | eval( 96 | t.TEST( 97 | "t.unify(result, ['Harry Trump', 'Fred Barney', 'Helen Rigby', 'Bill Abel', 'Chris Hand '])" 98 | ) 99 | ); 100 | 101 | re = new RE2(/\s+/); 102 | result = 'Hello World. How are you doing?'.split(re, 3); 103 | eval(t.TEST("t.unify(result, ['Hello', 'World.', 'How'])")); 104 | 105 | re = new RE2(/(\d)/); 106 | result = 'Hello 1 word. Sentence number 2.'.split(re); 107 | eval( 108 | t.TEST( 109 | "t.unify(result, ['Hello ', '1', ' word. Sentence number ', '2', '.'])" 110 | ) 111 | ); 112 | 113 | eval( 114 | t.TEST( 115 | "'asdfghjkl'.split(RE2(/[x-z]*/)).reverse().join('') === 'lkjhgfdsa'" 116 | ) 117 | ); 118 | } 119 | ]); 120 | -------------------------------------------------------------------------------- /tests/test_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | // These tests are copied from MDN: 10 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test 11 | 12 | function test_testFromExec(t) { 13 | 'use strict'; 14 | 15 | var re = new RE2('quick\\s(brown).+?(jumps)', 'i'); 16 | 17 | eval(t.TEST("re.test('The Quick Brown Fox Jumps Over The Lazy Dog')")); 18 | eval(t.TEST("re.test('tHE qUICK bROWN fOX jUMPS oVER tHE lAZY dOG')")); 19 | eval(t.TEST("re.test('the quick brown fox jumps over the lazy dog')")); 20 | eval(t.TEST("re.test('THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG')")); 21 | eval(t.TEST("!re.test('THE KWIK BROWN FOX JUMPS OVER THE LAZY DOG')")); 22 | 23 | re = new RE2('ab*', 'g'); 24 | 25 | eval(t.TEST("re.test('abbcdefabh')")); 26 | eval(t.TEST("!re.test('qwerty')")); 27 | 28 | re = new RE2('(hello \\S+)'); 29 | 30 | eval(t.TEST("re.test('This is a hello world!')")); 31 | eval(t.TEST("!re.test('This is a Hello world!')")); 32 | }, 33 | function test_testSucc(t) { 34 | 'use strict'; 35 | 36 | var str = 'abbcdefabh'; 37 | 38 | var re = new RE2('ab*', 'g'); 39 | var result = re.test(str); 40 | 41 | eval(t.TEST('result')); 42 | eval(t.TEST('re.lastIndex === 3')); 43 | 44 | result = re.test(str); 45 | 46 | eval(t.TEST('result')); 47 | eval(t.TEST('re.lastIndex === 9')); 48 | 49 | result = re.test(str); 50 | 51 | eval(t.TEST('!result')); 52 | }, 53 | function test_testSimple(t) { 54 | 'use strict'; 55 | 56 | var str = 'abbcdefabh'; 57 | 58 | var re1 = new RE2('ab*', 'g'); 59 | 60 | eval(t.TEST('re1.test(str)')); 61 | 62 | var re2 = new RE2('ab*'); 63 | 64 | eval(t.TEST('re2.test(str)')); 65 | 66 | var re3 = new RE2('abc'); 67 | 68 | eval(t.TEST('!re3.test(str)')); 69 | }, 70 | function test_testAnchoredToBeginning(t) { 71 | 'use strict'; 72 | 73 | var re = RE2('^hello', 'g'); 74 | 75 | eval(t.TEST("re.test('hellohello')")); 76 | eval(t.TEST("!re.test('hellohello')")); 77 | }, 78 | function test_testInvalid(t) { 79 | 'use strict'; 80 | 81 | var re = RE2(''); 82 | 83 | try { 84 | re.test({ 85 | toString() { 86 | throw 'corner'; 87 | } 88 | }); 89 | t.test(false); // shouldn't be here 90 | } catch (e) { 91 | eval(t.TEST("e === 'corner'")); 92 | } 93 | }, 94 | function test_testAnchor1(t) { 95 | 'use strict'; 96 | 97 | var re = new RE2('b|^a', 'g'); 98 | 99 | var result = re.test('aabc'); 100 | eval(t.TEST('result')); 101 | eval(t.TEST('re.lastIndex === 1')); 102 | 103 | result = re.test('aabc'); 104 | eval(t.TEST('result')); 105 | eval(t.TEST('re.lastIndex === 3')); 106 | 107 | result = re.test('aabc'); 108 | eval(t.TEST('!result')); 109 | }, 110 | function test_testAnchor2(t) { 111 | 'use strict'; 112 | 113 | var re = new RE2('(?:^a)', 'g'); 114 | 115 | var result = re.test('aabc'); 116 | eval(t.TEST('result')); 117 | eval(t.TEST('re.lastIndex === 1')); 118 | 119 | result = re.test('aabc'); 120 | eval(t.TEST('!result')); 121 | }, 122 | 123 | // Unicode tests 124 | 125 | function test_testUnicode(t) { 126 | 'use strict'; 127 | 128 | var re = new RE2('охотник\\s(желает).+?(где)', 'i'); 129 | 130 | eval(t.TEST("re.test('Каждый Охотник Желает Знать Где Сидит Фазан')")); 131 | eval(t.TEST("re.test('кАЖДЫЙ оХОТНИК жЕЛАЕТ зНАТЬ гДЕ сИДИТ фАЗАН')")); 132 | eval(t.TEST("re.test('каждый охотник желает знать где сидит фазан')")); 133 | eval(t.TEST("re.test('КАЖДЫЙ ОХОТНИК ЖЕЛАЕТ ЗНАТЬ ГДЕ СИДИТ ФАЗАН')")); 134 | eval(t.TEST("!re.test('Кажный Стрелок Хочет Найти Иде Прячется Птица')")); 135 | 136 | re = new RE2('аб*', 'g'); 137 | 138 | eval(t.TEST("re.test('аббвгдеабё')")); 139 | eval(t.TEST("!re.test('йцукен')")); 140 | 141 | re = new RE2('(привет \\S+)'); 142 | 143 | eval(t.TEST("re.test('Это просто привет всем.')")); 144 | eval(t.TEST("!re.test('Это просто Привет всем.')")); 145 | }, 146 | function test_testUnicodeSubsequent(t) { 147 | 'use strict'; 148 | 149 | var str = 'аббвгдеабё'; 150 | 151 | var re = new RE2('аб*', 'g'); 152 | var result = re.test(str); 153 | 154 | eval(t.TEST('result')); 155 | eval(t.TEST('re.lastIndex === 3')); 156 | 157 | result = re.test(str); 158 | 159 | eval(t.TEST('result')); 160 | eval(t.TEST('re.lastIndex === 9')); 161 | 162 | result = re.test(str); 163 | 164 | eval(t.TEST('!result')); 165 | }, 166 | 167 | // Buffer tests 168 | 169 | function test_testBuffer(t) { 170 | 'use strict'; 171 | 172 | var re = new RE2('охотник\\s(желает).+?(где)', 'i'); 173 | 174 | eval( 175 | t.TEST( 176 | "re.test(new Buffer('Каждый Охотник Желает Знать Где Сидит Фазан'))" 177 | ) 178 | ); 179 | eval( 180 | t.TEST( 181 | "re.test(new Buffer('кАЖДЫЙ оХОТНИК жЕЛАЕТ зНАТЬ гДЕ сИДИТ фАЗАН'))" 182 | ) 183 | ); 184 | eval( 185 | t.TEST( 186 | "re.test(new Buffer('каждый охотник желает знать где сидит фазан'))" 187 | ) 188 | ); 189 | eval( 190 | t.TEST( 191 | "re.test(new Buffer('КАЖДЫЙ ОХОТНИК ЖЕЛАЕТ ЗНАТЬ ГДЕ СИДИТ ФАЗАН'))" 192 | ) 193 | ); 194 | eval( 195 | t.TEST( 196 | "!re.test(new Buffer('Кажный Стрелок Хочет Найти Иде Прячется Птица'))" 197 | ) 198 | ); 199 | 200 | re = new RE2('аб*', 'g'); 201 | 202 | eval(t.TEST("re.test(new Buffer('аббвгдеабё'))")); 203 | eval(t.TEST("!re.test(new Buffer('йцукен'))")); 204 | 205 | re = new RE2('(привет \\S+)'); 206 | 207 | eval(t.TEST("re.test(new Buffer('Это просто привет всем.'))")); 208 | eval(t.TEST("!re.test(new Buffer('Это просто Привет всем.'))")); 209 | }, 210 | 211 | // Sticky tests 212 | 213 | function test_testSticky(t) { 214 | 'use strict'; 215 | 216 | var re = new RE2('\\s+', 'y'); 217 | 218 | eval(t.TEST("!re.test('Hello world, how are you?')")); 219 | 220 | re.lastIndex = 5; 221 | 222 | eval(t.TEST("re.test('Hello world, how are you?')")); 223 | eval(t.TEST('re.lastIndex === 6')); 224 | 225 | var re2 = new RE2('\\s+', 'gy'); 226 | 227 | eval(t.TEST("!re2.test('Hello world, how are you?')")); 228 | 229 | re2.lastIndex = 5; 230 | 231 | eval(t.TEST("re2.test('Hello world, how are you?')")); 232 | eval(t.TEST('re2.lastIndex === 6')); 233 | } 234 | ]); 235 | -------------------------------------------------------------------------------- /tests/test_toString.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | function test_toString(t) { 10 | 'use strict'; 11 | 12 | eval(t.TEST("RE2('').toString() === '/(?:)/u'")); 13 | eval(t.TEST("RE2('a').toString() === '/a/u'")); 14 | eval(t.TEST("RE2('b', 'i').toString() === '/b/iu'")); 15 | eval(t.TEST("RE2('c', 'g').toString() === '/c/gu'")); 16 | eval(t.TEST("RE2('d', 'm').toString() === '/d/mu'")); 17 | eval(t.TEST("RE2('\\\\d+', 'gi') + '' === '/\\\\d+/giu'")); 18 | eval(t.TEST("RE2('\\\\s*', 'gm') + '' === '/\\\\s*/gmu'")); 19 | eval(t.TEST("RE2('\\\\S{1,3}', 'ig') + '' === '/\\\\S{1,3}/giu'")); 20 | eval(t.TEST("RE2('\\\\D{,2}', 'mig') + '' === '/\\\\D{,2}/gimu'")); 21 | eval(t.TEST("RE2('^a{2,}', 'mi') + '' === '/^a{2,}/imu'")); 22 | eval(t.TEST("RE2('^a{5}$', 'gim') + '' === '/^a{5}$/gimu'")); 23 | eval( 24 | t.TEST("RE2('\\\\u{1F603}/', 'iy') + '' === '/\\\\u{1F603}\\\\//iuy'") 25 | ); 26 | eval(t.TEST("RE2('^a{2,}', 'smi') + '' === '/^a{2,}/imsu'")); 27 | 28 | eval(t.TEST("RE2('c', 'ug').toString() === '/c/gu'")); 29 | eval(t.TEST("RE2('d', 'um').toString() === '/d/mu'")); 30 | } 31 | ]); 32 | -------------------------------------------------------------------------------- /tests/test_unicode_classes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | var RE2 = require('../re2'); 5 | 6 | // tests 7 | 8 | unit.add(module, [ 9 | function test_unicodeClasses(t) { 10 | 'use strict'; 11 | 12 | let re2 = new RE2(/\p{L}/u); 13 | eval(t.TEST("re2.test('a') === true")); 14 | eval(t.TEST("re2.test('1') === false")); 15 | 16 | re2 = new RE2(/\p{Letter}/u); 17 | eval(t.TEST("re2.test('a') === true")); 18 | eval(t.TEST("re2.test('1') === false")); 19 | 20 | re2 = new RE2(/\p{Lu}/u); 21 | eval(t.TEST("re2.test('A') === true")); 22 | eval(t.TEST("re2.test('a') === false")); 23 | 24 | re2 = new RE2(/\p{Uppercase_Letter}/u); 25 | eval(t.TEST("re2.test('A') === true")); 26 | eval(t.TEST("re2.test('a') === false")); 27 | 28 | re2 = new RE2(/\p{Script=Latin}/u); 29 | eval(t.TEST("re2.test('a') === true")); 30 | eval(t.TEST("re2.test('ф') === false")); 31 | 32 | re2 = new RE2(/\p{sc=Cyrillic}/u); 33 | eval(t.TEST("re2.test('a') === false")); 34 | eval(t.TEST("re2.test('ф') === true")); 35 | } 36 | ]); 37 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var unit = require('heya-unit'); 4 | 5 | require('./test_general'); 6 | require('./test_source'); 7 | require('./test_exec'); 8 | require('./test_test'); 9 | require('./test_toString'); 10 | require('./test_match'); 11 | require('./test_matchAll'); 12 | require('./test_replace'); 13 | require('./test_search'); 14 | require('./test_split'); 15 | require('./test_invalid'); 16 | require('./test_symbols'); 17 | require('./test_prototype'); 18 | require('./test_new'); 19 | require('./test_groups'); 20 | require('./test_unicode_classes'); 21 | 22 | unit.run(); 23 | -------------------------------------------------------------------------------- /ts-tests/test-types.ts: -------------------------------------------------------------------------------- 1 | import RE2 from 're2'; 2 | 3 | function assertType(_val: T) {} 4 | 5 | function test_execTypes() { 6 | const re = new RE2('quick\\s(brown).+?(?jumps)', 'ig'); 7 | const result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog') 8 | if (!(result && result.groups)) { 9 | throw 'Unexpected Result' 10 | } 11 | assertType(result.index) 12 | assertType(result.input) 13 | assertType(result.groups['verb']) 14 | } 15 | 16 | function test_matchTypes() { 17 | const re = new RE2('quick\\s(brown).+?(?jumps)', 'ig'); 18 | const result = re.match('The Quick Brown Fox Jumps Over The Lazy Dog') 19 | if (!(result && result.index && result.input && result.groups)) { 20 | throw 'Unexpected Result' 21 | } 22 | assertType(result.index) 23 | assertType(result.input) 24 | assertType(result.groups['verb']) 25 | } 26 | 27 | test_execTypes() 28 | test_matchTypes() 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "declaration": true, 5 | "esModuleInterop": true, 6 | "strict": true, 7 | "allowUnusedLabels": false, 8 | "allowUnreachableCode": false, 9 | "exactOptionalPropertyTypes": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitOverride": true, 12 | "noImplicitReturns": true, 13 | "noPropertyAccessFromIndexSignature": true, 14 | "noUncheckedIndexedAccess": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "forceConsistentCasingInFileNames": true 18 | }, 19 | "include": ["**/*.ts"], 20 | "exclude": ["vendor/re2/app/**"] 21 | } 22 | --------------------------------------------------------------------------------