├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── src ├── cli.ts ├── execute.ts ├── index.ts ├── opcode-function.ts ├── parser.pegjs └── types.ts ├── test ├── execute.test.ts └── parser.test.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package.json 3 | src/parser.ts 4 | dist 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | # set:ft=yaml: 3 | 4 | env: 5 | browser: true 6 | commonjs: true 7 | es6: true 8 | node: true 9 | 10 | plugins: 11 | - "@typescript-eslint" 12 | - jest 13 | - prettier 14 | 15 | extends: 16 | - eslint:recommended 17 | - plugin:jest/recommended 18 | - plugin:prettier/recommended 19 | - plugin:@typescript-eslint/recommended 20 | 21 | parser: "@typescript-eslint/parser" 22 | 23 | parserOptions: 24 | ecmaVersion: 2018 25 | 26 | ignorePatterns: 27 | - "src/parser.ts" 28 | 29 | rules: 30 | indent: 31 | - error 32 | - 2 33 | - SwitchCase: 1 34 | 35 | linebreak-style: 36 | - error 37 | - unix 38 | 39 | quotes: 40 | - error 41 | - single 42 | - avoid-escape 43 | 44 | semi: 45 | - error 46 | - never 47 | 48 | comma-dangle: 49 | - error 50 | - arrays: always-multiline 51 | objects: always-multiline 52 | imports: always-multiline 53 | exports: always-multiline 54 | functions: only-multiline 55 | 56 | prettier/prettier: 57 | - error 58 | 59 | "@typescript-eslint/ban-ts-comment": 'off' 60 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [16.x, 17.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: yarn 29 | - run: yarn test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src/parser.ts 3 | dist 4 | .eslintcache 5 | .idea 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .eslintcache 2 | .github 3 | .idea 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | --- 2 | trailingComma: "es5" 3 | tabWidth: 2 4 | semi: false 5 | singleQuote: true 6 | printWidth: 100 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [`1.8.0`](https://github.com/elastic/micro-jq/tree/v1.8.0) 2 | 3 | Adds support for calling zero argument and single argument functions, along 4 | with support for the following functions. 5 | 6 | Whitespace removal e.g. `.foo | trim`: 7 | 8 | - `trim` 9 | - `ltrim` 10 | - `rtrim` 11 | 12 | Trim specific characters e.g. `.foo | ltrimstr("f")` 13 | 14 | - `ltrimstr` 15 | - `rtrimstr` 16 | 17 | Filters e.g. `.foo | startswith("f")` 18 | 19 | - `startswith` 20 | - `endswith` 21 | 22 | `split` - breaks a string into an array using the specified delimiter e.g. 23 | `.aStringField | split(",")` 24 | 25 | `join` - turn an array into a string by concatenating using the specified 26 | delimiter e.g. `.anArrayField | join(",")` 27 | 28 | ## [`1.7.0`](https://github.com/elastic/micro-jq/tree/v1.7.0) 29 | 30 | - Improve handling of bools, empty strings and falsy values 31 | 32 | ## [`1.6.1`](https://github.com/elastic/micro-jq/tree/v1.6.1) 33 | 34 | - Convert project to TypeScript 35 | - Switch to `peggy` instead of PegJS 36 | - Update dependencies 37 | 38 | ## `1.5.0` 39 | 40 | - Update dependencies (note that this release was not correctly tracked in 41 | GitHub) 42 | 43 | ## [`1.4.3`](https://github.com/elastic/micro-jq/tree/v1.4.3) 44 | 45 | - Update dependencies 46 | 47 | ## [`1.4.2`](https://github.com/elastic/micro-jq/tree/v1.4.2) 48 | 49 | - Just a release to Fix up the out-of-date changelog 50 | 51 | ## [`1.4.1`](https://github.com/elastic/micro-jq/tree/v1.4.1) 52 | 53 | - Bugfix 54 | 55 | ## [`1.4.0`](https://github.com/elastic/micro-jq/tree/v1.4.0) 56 | 57 | - Ensures that micro-jq conforms to jq's "spec" of constructing multiple objects when a field in an object constructor produces "multiple results". 58 | 59 | ## [`1.3.0`](https://github.com/elastic/micro-jq/tree/v1.3.0) 60 | 61 | - Add the ability to iterate over objects with the iterator operator as described in the jq manual, in the same fashion as arrays can be iterated over. 62 | 63 | ## [`1.2.0`](https://github.com/elastic/micro-jq/tree/v1.2.0) 64 | 65 | - Better support for slices, indexing, and improved composability #3 66 | - Update dependencies 67 | 68 | ## [`1.1.0`](https://github.com/elastic/micro-jq/tree/v1.1.0) 69 | 70 | - Update dependencies 71 | 72 | ## [`1.0.2`](https://github.com/elastic/micro-jq/tree/v1.0.2) 73 | 74 | - Bugfixes 75 | 76 | ## [`1.0.1`](https://github.com/elastic/micro-jq/tree/v1.0.1) 77 | 78 | - Bugfixes 79 | 80 | ## [`1.0.0`](https://github.com/elastic/micro-jq/tree/v1.0.0) Initial Release 81 | 82 | - Initial release 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Elasticsearch BV 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # micro-jq 2 | 3 | A small implementation of JQ. 4 | 5 | ## What is it? 6 | 7 | [jq](https://stedolan.github.io/jq/) is a fantastic tool for wrangling 8 | JSON, but it's written in C and so cannot be used in a browser. This 9 | project implements a subset of the JQ filter language, and is intended for 10 | simple filtering jobs in JavaScript where the filter is supplied e.g. by 11 | a user. 12 | 13 | ## How does it work? 14 | 15 | It uses a [parsed expression grammar][peg] (via [Peggy][peggy]) to transform JQ expressions 16 | into a series of "op codes", with each one representing a JQ filter 17 | operation. Each op code is executed with a context, which is initialised 18 | with the input object. As each op code executes, the context is updated. 19 | 20 | ## Goals 21 | 22 | * Implement enough of the JQ syntax to be useful 23 | * Implement it the same as JQ 24 | 25 | ## Non-goals 26 | 27 | * Complete implementation of JQ (but the level of completeness may 28 | increase over time). 29 | 30 | [peg]: https://en.wikipedia.org/wiki/Parsing_expression_grammar 31 | [peggy]: https://peggyjs.org/ 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@elastic/micro-jq", 3 | "version": "1.8.0", 4 | "description": "Small implementation of jq", 5 | "main": "./dist/index.js", 6 | "license": "Apache-2.0", 7 | "bin": "./dist/cli.js", 8 | "types": "./dist/index.d.ts", 9 | "scripts": { 10 | "build": "peggy --plugin ./node_modules/ts-pegjs --cache -o ./src/parser.ts ./src/parser.pegjs && tsc --declaration --outdir dist src/*.ts", 11 | "lint": "eslint --cache src", 12 | "test": "yarn lint && yarn build && jest" 13 | }, 14 | "devDependencies": { 15 | "@types/jest": "^27.4.0", 16 | "@typescript-eslint/eslint-plugin": "^5.12.0", 17 | "@typescript-eslint/parser": "^5.12.0", 18 | "eslint": "^8.9.0", 19 | "eslint-config-prettier": "^8.3.0", 20 | "eslint-plugin-jest": "^26.1.0", 21 | "eslint-plugin-prettier": "^4.0.0", 22 | "jest": "^27.5.1", 23 | "peggy": "^1.2.0", 24 | "prettier": "^2.5.1", 25 | "ts-jest": "^27.1.3", 26 | "ts-pegjs": "^1.2.1", 27 | "typescript": "^4.5.5" 28 | }, 29 | "jest": { 30 | "preset": "ts-jest", 31 | "testEnvironment": "node", 32 | "globals": { 33 | "ts-jest": { 34 | "tsconfig": { 35 | "noImplicitAny": false 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { readFileSync } from 'fs' 4 | import executeScript from './execute' 5 | 6 | const script = process.argv[2] || '.' 7 | 8 | const input = JSON.parse(readFileSync(0, 'utf8')) 9 | 10 | const result = executeScript(input, script) 11 | 12 | if (Array.isArray(result)) { 13 | for (const eachResult of result) { 14 | console.log(JSON.stringify(eachResult)) 15 | } 16 | } else { 17 | console.log(JSON.stringify(result)) 18 | } 19 | -------------------------------------------------------------------------------- /src/execute.ts: -------------------------------------------------------------------------------- 1 | import { evaluateOpCode_function } from './opcode-function' 2 | // @ts-ignore 3 | import { parse } from './parser' 4 | import { 5 | Context, 6 | Exploder, 7 | ExploderCallback, 8 | JSONValue, 9 | OpCode, 10 | OpCreateArray, 11 | OpCreateObject, 12 | OpExplode, 13 | OpIndex, 14 | OpPick, 15 | OpPipe, 16 | OpSlice, 17 | } from './types' 18 | 19 | export default function executeScript(input: JSONValue, script: string) { 20 | const opCodes = parse(script) 21 | 22 | return evaluateOpCodes([input], opCodes) 23 | } 24 | 25 | function evaluateOpCodes( 26 | context: Context, 27 | opCodes: OpCode[], 28 | callback?: ExploderCallback 29 | ): JSONValue { 30 | do { 31 | const opCode = opCodes.shift() 32 | 33 | if (opCode == null) { 34 | throw new Error('null opcode, cannot continue') 35 | } 36 | 37 | switch (opCode.op) { 38 | case 'current_context': 39 | break 40 | 41 | case 'literal': 42 | context = [opCode.value] 43 | break 44 | 45 | case 'pick': 46 | context = evaluateOpCode_pick(context, opCode) 47 | break 48 | 49 | case 'index': 50 | context = evaluateOpCode_index(context, opCode) 51 | break 52 | 53 | case 'slice': 54 | context = evaluateOpCode_slice(context, opCode) 55 | break 56 | 57 | case 'explode': 58 | context = evaluateOpCode_explode(context, opCode, callback) 59 | break 60 | 61 | case 'create_array': 62 | context = evaluateOpCode_create_array(context, opCode) 63 | break 64 | 65 | case 'create_object': 66 | context = evaluateOpCode_create_object(context, opCode, callback) 67 | break 68 | 69 | case 'pipe': 70 | context = evaluateOpCode_pipe(context, opCode, callback) 71 | break 72 | 73 | case 'function': 74 | context = evaluateOpCode_function(context, opCode) 75 | break 76 | 77 | default: 78 | // @ts-expect-error if this doesn't error, then some opcode isn't handled 79 | throw new Error('Unknown op code: ' + opCode.op) 80 | } 81 | } while (opCodes.length > 0) 82 | 83 | return context.length > 1 ? context : context[0] 84 | } 85 | 86 | function evaluateOpCode_pick(context: Context, opCode: OpPick): Context { 87 | return context.reduce((result, each) => { 88 | if (typeof each !== 'object' || Array.isArray(each)) { 89 | if (opCode.strict) { 90 | throw new Error(`Cannot index ${typeof each} with ${opCode.key}`) 91 | } 92 | // Skip this value entirely 93 | return result 94 | } 95 | const picked = each?.[opCode.key] ?? null 96 | result.push(picked) 97 | return result 98 | }, []) 99 | } 100 | 101 | function evaluateOpCode_index(context: Context, opCode: OpIndex): Context { 102 | return context.reduce((result, each) => { 103 | if (!Array.isArray(each)) { 104 | if (opCode.strict) { 105 | throw new Error('Can only index into arrays') 106 | } 107 | return result 108 | } 109 | let indexed 110 | if (Math.abs(opCode.index) > each.length || opCode.index === each.length) { 111 | indexed = null 112 | } else if (opCode.index < 0) { 113 | indexed = each.slice(opCode.index)[0] 114 | } else { 115 | indexed = each[opCode.index] 116 | } 117 | result.push(indexed) 118 | return result 119 | }, []) 120 | } 121 | 122 | function evaluateOpCode_slice(context: Context, opCode: OpSlice): Context { 123 | return context.reduce((result, each) => { 124 | if ('string' !== typeof each && Array.isArray(each) == false) { 125 | if (opCode.strict) { 126 | throw new Error('Cannot slice ' + typeof each) 127 | } 128 | return result 129 | } else { 130 | if (undefined === opCode.start && undefined === opCode.end) { 131 | throw new Error('Cannot slice with no offsets') 132 | } 133 | if (each == null) { 134 | result.push(null) 135 | } else { 136 | result.push((each as string | JSONValue[]).slice(opCode.start, opCode.end)) 137 | } 138 | return result 139 | } 140 | }, []) 141 | } 142 | 143 | function evaluateOpCode_explode( 144 | context: Context, 145 | opCode: OpExplode, 146 | callback?: ExploderCallback 147 | ): Context { 148 | context = context.reduce((result, each) => { 149 | if (Array.isArray(each)) { 150 | return result.concat(each) 151 | } else if (typeof each === 'object' && each != null) { 152 | return result.concat(Object.values(each)) 153 | } 154 | if (opCode.strict) { 155 | // jq throws an error specifically for `null`, so let's 156 | // distinguish that from `object`. 157 | const type = each === null ? 'null' : typeof each 158 | throw new Error('Cannot iterate over ' + type) 159 | } 160 | return result 161 | }, []) 162 | if (callback) { 163 | callback(context.length) 164 | } 165 | return context 166 | } 167 | 168 | function evaluateOpCode_create_array(context: Context, opCode: OpCreateArray): Context { 169 | return [ 170 | opCode.values.reduce((result, opCodesForIndex) => { 171 | const exploder = makeExploder() 172 | // Array creation terminates an explode chain - all generated 173 | // results will get collected in the singular array. As such, 174 | // no parent is called to makeExploderCb, and none is taken by 175 | // this function. 176 | const values = evaluateOpCodes(context, [...opCodesForIndex], makeExploderCb(exploder)) 177 | if (!exploder.exploded) { 178 | result.push(values) 179 | } else { 180 | switch (exploder.length) { 181 | case 0: 182 | break 183 | 184 | case 1: 185 | result.push(values) 186 | break 187 | 188 | default: 189 | result = result.concat(values) 190 | } 191 | } 192 | 193 | return result 194 | }, [] as Context), 195 | ] 196 | } 197 | 198 | function evaluateOpCode_create_object( 199 | context: Context, 200 | opCode: OpCreateObject, 201 | callback?: ExploderCallback 202 | ): Context { 203 | const jsonObject = opCode.entries.reduce((result, each) => { 204 | const exploder = makeExploder() 205 | const values: JSONValue = evaluateOpCodes( 206 | context, 207 | [...each.value], 208 | makeExploderCb(exploder, callback) 209 | ) 210 | 211 | if (!exploder.exploded) { 212 | result[each.key] = [values] 213 | } else { 214 | switch (exploder.length) { 215 | case 0: 216 | result[each.key] = [] 217 | break 218 | 219 | case 1: 220 | result[each.key] = [values] 221 | break 222 | 223 | default: 224 | result[each.key] = values as Context 225 | break 226 | } 227 | } 228 | return result 229 | }, {} as { [key: string]: Context }) 230 | 231 | const ops: [string, Context][] = Object.entries(jsonObject) 232 | 233 | return evaluateOpCode_create_object_build({} as { [key: string]: JSONValue }, ops) 234 | } 235 | 236 | function evaluateOpCode_create_object_build( 237 | current: { [key: string]: JSONValue }, 238 | ops: Array<[string, Context]> 239 | ) { 240 | let result: Context = [] 241 | const op = ops.shift() 242 | if (op == null) { 243 | throw new Error('Unexpectedly ran out of ops') 244 | } 245 | const [key, values] = op 246 | values.forEach((value) => { 247 | current[key] = value 248 | if (ops.length > 0) { 249 | result = result.concat(evaluateOpCode_create_object_build({ ...current }, [...ops])) 250 | } else { 251 | result.push({ ...current }) 252 | } 253 | }) 254 | return result 255 | } 256 | 257 | function evaluateOpCode_pipe( 258 | context: Context, 259 | opCode: OpPipe, 260 | callback?: ExploderCallback 261 | ): Context { 262 | const exploder = makeExploder() 263 | let result = evaluateOpCodes(context, [...opCode.in], makeExploderCb(exploder, callback)) 264 | if (!exploder.exploded) { 265 | result = evaluateOpCodes([result], [...opCode.out]) 266 | } else { 267 | let explodedResult: JSONValue[] 268 | switch (exploder.length) { 269 | case 0: 270 | explodedResult = [] 271 | break 272 | 273 | case 1: 274 | explodedResult = [result] 275 | break 276 | 277 | default: 278 | explodedResult = result as JSONValue[] 279 | break 280 | } 281 | result = 282 | explodedResult == null 283 | ? null 284 | : explodedResult.map((each) => evaluateOpCodes([each], [...opCode.out])) 285 | } 286 | return Array.isArray(result) ? result : [result] 287 | } 288 | 289 | function makeExploder(): Exploder { 290 | return { exploded: false, length: 0 } 291 | } 292 | 293 | function makeExploderCb(exploder: Exploder, callback?: ExploderCallback): ExploderCallback { 294 | return (length) => { 295 | exploder.exploded = true 296 | exploder.length = length 297 | if (callback) { 298 | callback(length) 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { parse } from './parser' 2 | 3 | export function checkScript(script: string): boolean { 4 | try { 5 | parse(script) 6 | return true 7 | } catch (e) { 8 | return false 9 | } 10 | } 11 | 12 | export { default as executeScript } from './execute' 13 | export { parse } from './parser' 14 | -------------------------------------------------------------------------------- /src/opcode-function.ts: -------------------------------------------------------------------------------- 1 | import { Context, NoArgFunctioName, OpFunction, StringArgFunctionName } from './types' 2 | 3 | export function evaluateOpCode_function(context: Context, { name, value }: OpFunction): Context { 4 | const result: Context = [] 5 | 6 | const stringArgCallback = 7 | name in stringArgCallbacks ? stringArgCallbacks[name as StringArgFunctionName] : undefined 8 | 9 | const noArgCallback = 10 | !stringArgCallback && name in noArgCallbacks 11 | ? noArgCallbacks[name as NoArgFunctioName] 12 | : undefined 13 | 14 | if (stringArgCallback) { 15 | if (value === undefined) { 16 | throw new Error('Unexpected value for string arg callback') 17 | } 18 | for (const each of context) { 19 | result.push(stringArgCallback(each, value)) 20 | } 21 | } else if (noArgCallback) { 22 | if (value !== undefined) { 23 | throw new Error('Unexpected value for no arg callback') 24 | } 25 | for (const each of context) { 26 | result.push(noArgCallback(each)) 27 | } 28 | } else { 29 | throw new Error('Unknown function name: ' + name) 30 | } 31 | 32 | return result 33 | } 34 | 35 | const noArgCallbacks = { 36 | ltrim(each: Context[number]): string { 37 | if (typeof each !== 'string') throw new Error(`Cannot ltrim ${typeof each}`) 38 | return each.trimStart() 39 | }, 40 | rtrim(each: Context[number]): string { 41 | if (typeof each !== 'string') throw new Error(`Cannot rtrim ${typeof each}`) 42 | return each.trimEnd() 43 | }, 44 | trim(each: Context[number]): string { 45 | if (typeof each !== 'string') throw new Error(`Cannot trim ${typeof each}`) 46 | return each.trim() 47 | }, 48 | } as const 49 | 50 | const stringArgCallbacks = { 51 | startswith( 52 | each: Context[number], 53 | value: Value 54 | ): each is `${Value}${string}` { 55 | if (typeof each !== 'string') throw new Error(`Cannot startswith ${typeof each}`) 56 | return each.startsWith(value) 57 | }, 58 | 59 | endswith(each: Context[number], value: Value): each is `${string}${Value}` { 60 | if (typeof each !== 'string') throw new Error(`Cannot endswith ${typeof each}`) 61 | return each.endsWith(value) 62 | }, 63 | 64 | ltrimstr(each: Context[number], value: string): string { 65 | return stringArgCallbacks.startswith(each, value) ? each.slice(value.length) : value 66 | }, 67 | 68 | rtrimstr(each: Context[number], value: string): string { 69 | return stringArgCallbacks.endswith(each, value) ? each.slice(0, 0 - value.length) : value 70 | }, 71 | 72 | split(each: Context[number], value: string): string[] { 73 | if (typeof each !== 'string') throw new Error(`Cannot split ${typeof each}`) 74 | return each.split(value) 75 | }, 76 | 77 | join(each: Context[number], value: string): string { 78 | if (!Array.isArray(each)) throw new Error(`Cannot join ${typeof each}`) 79 | return each.join(value) 80 | }, 81 | } as const 82 | -------------------------------------------------------------------------------- /src/parser.pegjs: -------------------------------------------------------------------------------- 1 | // vim:nowrap: 2 | 3 | { 4 | function toNumber(stringArray, negative) { 5 | return parseInt(stringArray.join(''), 10) * ( negative ? -1 : 1 ) 6 | } 7 | 8 | function literal(value) { 9 | return { 10 | op: 'literal', 11 | value 12 | } 13 | } 14 | } 15 | 16 | Script 17 | = Expression 18 | 19 | Expression 20 | = head:Operation tail:(_ Pipe* _ Operation)* { 21 | return tail.reduce(function(result, element) { 22 | return [{ 23 | op: 'pipe', 24 | in: result, 25 | out: Array.isArray(element[3]) ? element[3] : [element[3]], 26 | }] 27 | }, Array.isArray(head) ? head : [head]) 28 | } 29 | 30 | _ "whitespace" 31 | = [ \t\n\r]* 32 | 33 | Pipe 34 | = "|" 35 | 36 | Filter 37 | = Traversal+ 38 | 39 | Traversal 40 | = Index 41 | / Slice 42 | / Explode 43 | / Pick 44 | / Context 45 | 46 | Functions 47 | = StringArgFunctions 48 | / NoArgFunctions 49 | 50 | Index 51 | = "[" _ index:Number _ "]" strict:"?"? { 52 | return { 53 | op: 'index', 54 | index: index, 55 | strict: strict == null, 56 | } 57 | } 58 | 59 | Slice 60 | = "[" start:Number? ":" end:Number? "]" strict:"?"? { 61 | return { 62 | op: 'slice', 63 | start: null === start ? undefined : start, 64 | end: null === end ? undefined : end, 65 | strict: strict == null, 66 | } 67 | } 68 | 69 | Explode 70 | = "[]" strict:"?"? { 71 | return { 72 | op: 'explode', 73 | strict: strict == null, 74 | } 75 | } 76 | 77 | Pick 78 | = "." name: Identifier strict:"?"? { 79 | return { 80 | op: 'pick', 81 | key: name, 82 | strict: strict == null, 83 | } 84 | } 85 | / "[" _ name:String _ "]" strict:"?"? { 86 | return { 87 | op: 'pick', 88 | key: name, 89 | strict: strict == null, 90 | } 91 | } 92 | 93 | Context 94 | = "." { 95 | return { 96 | op: 'current_context' 97 | } 98 | } 99 | 100 | NoArgFunctions 101 | = name:NoArgFunctionsNames _ { 102 | return { 103 | op: 'function', 104 | name 105 | } 106 | } 107 | 108 | NoArgFunctionsNames 109 | = 'trim' 110 | / 'ltrim' 111 | / 'rtrim' 112 | 113 | StringArgFunctions 114 | = name:StringArgFunctionNames _ "(" _ value:String _ ")" { 115 | return { 116 | op: 'function', 117 | name, 118 | value 119 | } 120 | } 121 | 122 | StringArgFunctionNames 123 | = 'startswith' 124 | / 'endswith' 125 | / 'ltrimstr' 126 | / 'rtrimstr' 127 | / 'split' 128 | / 'join' 129 | 130 | Operation 131 | = Literal // Must be bare 132 | / CreateArray // Must be bare 133 | / CreateObject // Must be bare 134 | / Filter // Must have context 135 | / Functions // Must have context 136 | 137 | Literal 138 | = number:Number { return literal(number) } 139 | / "null" { return literal(null) } 140 | / "undefined" { return literal(undefined) } 141 | / string:String { return literal(string) } 142 | / boolean:Boolean { return literal(boolean) } 143 | 144 | Number 145 | = negative:"-"? number:[0-9]+ { return toNumber(number, negative) } 146 | 147 | String 148 | = "'" string:[^']* "'" { return string.join('') } 149 | / '"' string:[^"]* '"' { return string.join('') } 150 | 151 | Boolean 152 | = 'true' { return true } 153 | / 'false' { return false } 154 | 155 | CreateArray 156 | = "[" _ head:Expression tail:(_ "," _ Expression)* _ "]" { 157 | return { 158 | op: 'create_array', 159 | values: tail.reduce((result, element) => result.concat([element[3]]), [head]) 160 | } 161 | } 162 | 163 | CreateObject 164 | = "{" _ head:KeyValue tail:(_ "," _ KeyValue)* _ "}" { 165 | return { 166 | op: 'create_object', 167 | entries: tail.reduce(function(result, element) { 168 | result.push(element[3]) 169 | return result 170 | }, [head]) 171 | } 172 | } 173 | 174 | KeyValue 175 | = key:Key _ ":" _ value:Expression { 176 | return { key, value } 177 | } 178 | 179 | Key 180 | = key:Identifier { return key } 181 | / "'" key:[^']+ "'" { return key.join('') } 182 | / '"' key:[^"]+ '"' { return key.join('') } 183 | 184 | // The patterns from here are largely pinched from the PegJS JavaScript 185 | // parser example: 186 | // https://github.com/pegjs/pegjs/blob/master/examples/javascript.pegjs 187 | 188 | Identifier 189 | = name:IdentifierName { return name } 190 | 191 | IdentifierName "identifier" 192 | = head:IdentifierStart tail:IdentifierPart* { return head + tail.join("") } 193 | 194 | IdentifierStart 195 | = UnicodeLetter 196 | / "$" 197 | / "_" 198 | / "\\" sequence:UnicodeEscapeSequence { return sequence } 199 | 200 | IdentifierPart 201 | = IdentifierStart 202 | / UnicodeCombiningMark 203 | / UnicodeDigit 204 | / UnicodeConnectorPunctuation 205 | / "\u200C" 206 | / "\u200D" 207 | 208 | UnicodeLetter 209 | = Lu 210 | / Ll 211 | / Lt 212 | / Lm 213 | / Lo 214 | / Nl 215 | 216 | UnicodeCombiningMark 217 | = Mn 218 | / Mc 219 | 220 | UnicodeDigit 221 | = Nd 222 | 223 | UnicodeConnectorPunctuation 224 | = Pc 225 | 226 | UnicodeEscapeSequence 227 | = "u" digits:$(HexDigit HexDigit HexDigit HexDigit) { 228 | return String.fromCharCode(parseInt(digits, 16)) 229 | } 230 | 231 | // Letter, Lowercase 232 | Ll = [\u0061-\u007A\u00B5\u00DF-\u00F6\u00F8-\u00FF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137-\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148-\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C-\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA-\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9-\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC-\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF-\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F-\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0-\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB-\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE-\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6-\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FC7\u1FD0-\u1FD3\u1FD6-\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6-\u1FF7\u210A\u210E-\u210F\u2113\u212F\u2134\u2139\u213C-\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61\u2C65-\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73-\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3-\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7FA\uAB30-\uAB5A\uAB60-\uAB65\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A] 233 | 234 | // Letter, Modifier 235 | Lm = [\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0374\u037A\u0559\u0640\u06E5-\u06E6\u07F4-\u07F5\u07FA\u081A\u0824\u0828\u0971\u0E46\u0EC6\u10FC\u17D7\u1843\u1AA7\u1C78-\u1C7D\u1D2C-\u1D6A\u1D78\u1D9B-\u1DBF\u2071\u207F\u2090-\u209C\u2C7C-\u2C7D\u2D6F\u2E2F\u3005\u3031-\u3035\u303B\u309D-\u309E\u30FC-\u30FE\uA015\uA4F8-\uA4FD\uA60C\uA67F\uA69C-\uA69D\uA717-\uA71F\uA770\uA788\uA7F8-\uA7F9\uA9CF\uA9E6\uAA70\uAADD\uAAF3-\uAAF4\uAB5C-\uAB5F\uFF70\uFF9E-\uFF9F] 236 | 237 | // Letter, Other 238 | Lo = [\u00AA\u00BA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA\u05EF-\u05F2\u0620-\u063F\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u09FC\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E45\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-\u30FA\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEF\uA000-\uA014\uA016-\uA48C\uA4D0-\uA4F7\uA500-\uA60B\uA610-\uA61F\uA62A-\uA62B\uA66E\uA6A0-\uA6E5\uA78F\uA7F7\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9E0-\uA9E4\uA9E7-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F\uAA71-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC] 239 | 240 | // Letter, Titlecase 241 | Lt = [\u01C5\u01C8\u01CB\u01F2\u1F88-\u1F8F\u1F98-\u1F9F\u1FA8-\u1FAF\u1FBC\u1FCC\u1FFC] 242 | 243 | // Letter, Uppercase 244 | Lu = [\u0041-\u005A\u00C0-\u00D6\u00D8-\u00DE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178-\u0179\u017B\u017D\u0181-\u0182\u0184\u0186-\u0187\u0189-\u018B\u018E-\u0191\u0193-\u0194\u0196-\u0198\u019C-\u019D\u019F-\u01A0\u01A2\u01A4\u01A6-\u01A7\u01A9\u01AC\u01AE-\u01AF\u01B1-\u01B3\u01B5\u01B7-\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A-\u023B\u023D-\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E-\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9-\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0-\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1C90-\u1CBA\u1CBD-\u1CBF\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E-\u213F\u2145\u2183\u2C00-\u2C2E\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D-\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AE\uA7B0-\uA7B4\uA7B6\uA7B8\uFF21-\uFF3A] 245 | 246 | // Mark, Spacing Combining 247 | Mc = [\u0903\u093B\u093E-\u0940\u0949-\u094C\u094E-\u094F\u0982-\u0983\u09BE-\u09C0\u09C7-\u09C8\u09CB-\u09CC\u09D7\u0A03\u0A3E-\u0A40\u0A83\u0ABE-\u0AC0\u0AC9\u0ACB-\u0ACC\u0B02-\u0B03\u0B3E\u0B40\u0B47-\u0B48\u0B4B-\u0B4C\u0B57\u0BBE-\u0BBF\u0BC1-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD7\u0C01-\u0C03\u0C41-\u0C44\u0C82-\u0C83\u0CBE\u0CC0-\u0CC4\u0CC7-\u0CC8\u0CCA-\u0CCB\u0CD5-\u0CD6\u0D02-\u0D03\u0D3E-\u0D40\u0D46-\u0D48\u0D4A-\u0D4C\u0D57\u0D82-\u0D83\u0DCF-\u0DD1\u0DD8-\u0DDF\u0DF2-\u0DF3\u0F3E-\u0F3F\u0F7F\u102B-\u102C\u1031\u1038\u103B-\u103C\u1056-\u1057\u1062-\u1064\u1067-\u106D\u1083-\u1084\u1087-\u108C\u108F\u109A-\u109C\u17B6\u17BE-\u17C5\u17C7-\u17C8\u1923-\u1926\u1929-\u192B\u1930-\u1931\u1933-\u1938\u1A19-\u1A1A\u1A55\u1A57\u1A61\u1A63-\u1A64\u1A6D-\u1A72\u1B04\u1B35\u1B3B\u1B3D-\u1B41\u1B43-\u1B44\u1B82\u1BA1\u1BA6-\u1BA7\u1BAA\u1BE7\u1BEA-\u1BEC\u1BEE\u1BF2-\u1BF3\u1C24-\u1C2B\u1C34-\u1C35\u1CE1\u1CF2-\u1CF3\u1CF7\u302E-\u302F\uA823-\uA824\uA827\uA880-\uA881\uA8B4-\uA8C3\uA952-\uA953\uA983\uA9B4-\uA9B5\uA9BA-\uA9BB\uA9BD-\uA9C0\uAA2F-\uAA30\uAA33-\uAA34\uAA4D\uAA7B\uAA7D\uAAEB\uAAEE-\uAAEF\uAAF5\uABE3-\uABE4\uABE6-\uABE7\uABE9-\uABEA\uABEC] 248 | 249 | // Mark, Nonspacing 250 | Mn = [\u0300-\u036F\u0483-\u0487\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08D3-\u08E1\u08E3-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962-\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2-\u09E3\u09FE\u0A01-\u0A02\u0A3C\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A51\u0A70-\u0A71\u0A75\u0A81-\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7-\u0AC8\u0ACD\u0AE2-\u0AE3\u0AFA-\u0AFF\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B56\u0B62-\u0B63\u0B82\u0BC0\u0BCD\u0C00\u0C04\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C62-\u0C63\u0C81\u0CBC\u0CBF\u0CC6\u0CCC-\u0CCD\u0CE2-\u0CE3\u0D00-\u0D01\u0D3B-\u0D3C\u0D41-\u0D44\u0D4D\u0D62-\u0D63\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EC8-\u0ECD\u0F18-\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86-\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039-\u103A\u103D-\u103E\u1058-\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085-\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752-\u1753\u1772-\u1773\u17B4-\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u1885-\u1886\u18A9\u1920-\u1922\u1927-\u1928\u1932\u1939-\u193B\u1A17-\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ABD\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80-\u1B81\u1BA2-\u1BA5\u1BA8-\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8-\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8-\u1CF9\u1DC0-\u1DF9\u1DFB-\u1DFF\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u3099-\u309A\uA66F\uA674-\uA67D\uA69E-\uA69F\uA6F0-\uA6F1\uA802\uA806\uA80B\uA825-\uA826\uA8C4-\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9E5\uAA29-\uAA2E\uAA31-\uAA32\uAA35-\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7-\uAAB8\uAABE-\uAABF\uAAC1\uAAEC-\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F] 251 | 252 | // Number, Decimal Digit 253 | Nd = [\u0030-\u0039\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0BE6-\u0BEF\u0C66-\u0C6F\u0CE6-\u0CEF\u0D66-\u0D6F\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F29\u1040-\u1049\u1090-\u1099\u17E0-\u17E9\u1810-\u1819\u1946-\u194F\u19D0-\u19D9\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\uA620-\uA629\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19] 254 | 255 | // Number, Letter 256 | Nl = [\u16EE-\u16F0\u2160-\u2182\u2185-\u2188\u3007\u3021-\u3029\u3038-\u303A\uA6E6-\uA6EF] 257 | 258 | // Punctuation, Connector 259 | Pc = [\u005F\u203F-\u2040\u2054\uFE33-\uFE34\uFE4D-\uFE4F\uFF3F] 260 | 261 | HexDigit = [0-9a-f]i 262 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type JSONValue = 2 | | string 3 | | number 4 | | boolean 5 | | null 6 | | undefined // not a JSON type, but included for interoperability with defined types that allow undefined 7 | | JSONValue[] 8 | | { [key: string]: JSONValue } 9 | 10 | export type Context = JSONValue[] 11 | 12 | export interface OpCreateArray { 13 | op: 'create_array' 14 | values: OpCode[][] 15 | } 16 | 17 | export interface OpCreateObject { 18 | op: 'create_object' 19 | entries: Array<{ key: string; value: OpCode[] }> 20 | } 21 | 22 | interface OpCurrentContext { 23 | op: 'current_context' 24 | } 25 | 26 | export interface OpExplode { 27 | op: 'explode' 28 | strict: boolean 29 | } 30 | 31 | export interface OpIndex { 32 | op: 'index' 33 | index: number 34 | strict: boolean 35 | } 36 | 37 | interface OpLiteral { 38 | op: 'literal' 39 | value: string | number | null 40 | } 41 | 42 | export interface OpPick { 43 | op: 'pick' 44 | key: string 45 | strict: boolean 46 | } 47 | 48 | export interface OpPipe { 49 | op: 'pipe' 50 | in: OpCode[] 51 | out: OpCode[] 52 | } 53 | 54 | export interface OpSlice { 55 | op: 'slice' 56 | start: number | undefined 57 | end: number | undefined 58 | strict: boolean 59 | } 60 | 61 | export type StringArgFunctionName = 62 | | 'startswith' 63 | | 'endswith' 64 | | 'ltrimstr' 65 | | 'rtrimstr' 66 | | 'split' 67 | | 'join' 68 | 69 | export type NoArgFunctioName = 'trim' | 'ltrim' | 'rtrim' 70 | 71 | export type AnyArgFunctionName = StringArgFunctionName | NoArgFunctioName 72 | 73 | export interface OpFunction { 74 | op: 'function' 75 | name: AnyArgFunctionName 76 | value?: string 77 | } 78 | 79 | export interface OpStringArgFunction 80 | extends OpFunction { 81 | name: Name 82 | value: string 83 | } 84 | 85 | export type OpCode = 86 | | OpCreateArray 87 | | OpCreateObject 88 | | OpCurrentContext 89 | | OpExplode 90 | | OpIndex 91 | | OpLiteral 92 | | OpPick 93 | | OpPipe 94 | | OpSlice 95 | | OpFunction 96 | 97 | export interface Exploder { 98 | exploded: boolean 99 | length: number 100 | } 101 | 102 | export type ExploderCallback = (length: number) => void 103 | -------------------------------------------------------------------------------- /test/execute.test.ts: -------------------------------------------------------------------------------- 1 | import { executeScript } from '../src' 2 | 3 | describe('literals', () => { 4 | test('number', () => { 5 | expect(executeScript(null, '42')).toEqual(42) 6 | }) 7 | 8 | test('strings', () => { 9 | expect(executeScript(null, '"foo"')).toEqual('foo') 10 | expect(executeScript(null, "'foo'")).toEqual('foo') 11 | expect(executeScript(null, '""')).toEqual('') 12 | }) 13 | 14 | test('null', () => { 15 | expect(executeScript(null, 'null')).toEqual(null) 16 | }) 17 | 18 | test('bools', () => { 19 | expect(executeScript(null, 'false')).toEqual(false) 20 | expect(executeScript(null, 'true')).toEqual(true) 21 | }) 22 | }) 23 | 24 | describe('pick values', () => { 25 | test('missing', () => { 26 | const input = {} 27 | const script = '.foo' 28 | expect(executeScript(input, script)).toEqual(null) 29 | }) 30 | 31 | test('one deep', () => { 32 | const input = { foo: 'bar' } 33 | const script = '.foo' 34 | expect(executeScript(input, script)).toEqual('bar') 35 | }) 36 | 37 | test('two deep', () => { 38 | const input = { foo: { bar: 'baz' } } 39 | const script = '.foo.bar' 40 | expect(executeScript(input, script)).toEqual('baz') 41 | }) 42 | 43 | test('pick index', () => { 44 | const input = [1, 1, 2, 3, 5, 8, 13] 45 | const script = '.[4]' 46 | expect(executeScript(input, script)).toEqual(5) 47 | }) 48 | 49 | test('pick negative index', () => { 50 | const input = [1, 1, 2, 3, 5, 8, 13] 51 | const script = '.[-4]' 52 | expect(executeScript(input, script)).toEqual(3) 53 | }) 54 | 55 | test('pick first index by negative length', () => { 56 | const input = [1, 1, 2, 3, 5, 8, 13] 57 | const script = '.[-7]' 58 | expect(executeScript(input, script)).toEqual(1) 59 | }) 60 | 61 | test('pick out of bounds index', () => { 62 | const input = [1, 1, 2, 3, 5, 8, 13] 63 | const script = '.[7]' 64 | expect(executeScript(input, script)).toEqual(null) 65 | }) 66 | 67 | test('pick out of bounds negative index', () => { 68 | const input = [1, 1, 2, 3, 5, 8, 13] 69 | const script = '.[-8]' 70 | expect(executeScript(input, script)).toEqual(null) 71 | }) 72 | 73 | test('slice', () => { 74 | const input = [1, 1, 2, 3, 5, 8, 13] 75 | const script = '.[2:5]' 76 | expect(executeScript(input, script)).toEqual([2, 3, 5]) 77 | }) 78 | 79 | test('slice with no start', () => { 80 | const input = [1, 1, 2, 3, 5, 8, 13] 81 | const script = '.[:5]' 82 | expect(executeScript(input, script)).toEqual([1, 1, 2, 3, 5]) 83 | }) 84 | 85 | test('slice with no end', () => { 86 | const input = [1, 1, 2, 3, 5, 8, 13] 87 | const script = '.[2:]' 88 | expect(executeScript(input, script)).toEqual([2, 3, 5, 8, 13]) 89 | }) 90 | 91 | test('slice with no offsets', () => { 92 | const input = [1, 1, 2, 3, 5, 8, 13] 93 | const script = '.[:]' 94 | expect(() => executeScript(input, script)).toThrowErrorMatchingInlineSnapshot( 95 | '"Cannot slice with no offsets"' 96 | ) 97 | }) 98 | 99 | test('slice with negative offsets', () => { 100 | const input = [1, 1, 2, 3, 5, 8, 13] 101 | const script = '.[-5:-2]' 102 | expect(executeScript(input, script)).toEqual([2, 3, 5]) 103 | }) 104 | 105 | test('slice with end offset greater than start', () => { 106 | const input = [1, 1, 2, 3, 5, 8, 13] 107 | const script = '.[5:2]' 108 | expect(executeScript(input, script)).toEqual([]) 109 | }) 110 | 111 | test('pick by identifier and then index', () => { 112 | const input = { foo: [1, 1, 2, 3, 5, 8, 13] } 113 | const script = '.foo[4]' 114 | expect(executeScript(input, script)).toEqual(5) 115 | }) 116 | 117 | test('pick by identifier and then index by negative number', () => { 118 | const input = { foo: [1, 1, 2, 3, 5, 8, 13] } 119 | const script = '.foo[-4]' 120 | expect(executeScript(input, script)).toEqual(3) 121 | }) 122 | 123 | test('pick by identifier, index, then identifier again', () => { 124 | const input = { foo: [{ bar: 1 }, { bar: 2 }] } 125 | const script = '.foo[1].bar' 126 | expect(executeScript(input, script)).toEqual(2) 127 | }) 128 | 129 | test('pick 0 value', () => { 130 | const input = { foo: 0 } 131 | const script = '.foo' 132 | expect(executeScript(input, script)).toEqual(0) 133 | }) 134 | 135 | test('pick empty string value', () => { 136 | const input = { foo: '' } 137 | const script = '.foo' 138 | expect(executeScript(input, script)).toEqual('') 139 | }) 140 | 141 | test('pick false value', () => { 142 | const input = { foo: false } 143 | const script = '.foo' 144 | expect(executeScript(input, script)).toEqual(false) 145 | }) 146 | }) 147 | 148 | describe('functions', () => { 149 | test('trim', () => { 150 | expect(executeScript(' a-b ', 'trim')).toEqual('a-b') 151 | }) 152 | test('ltrim', () => { 153 | expect(executeScript(' a-b ', 'ltrim')).toEqual('a-b ') 154 | }) 155 | test('rtrim', () => { 156 | expect(executeScript(' a-b ', 'rtrim')).toEqual(' a-b') 157 | }) 158 | test('startswith', () => { 159 | expect(executeScript('a-b--', 'startswith("a")')).toEqual(true) 160 | expect(executeScript('a-b--', 'startswith("-")')).toEqual(false) 161 | }) 162 | test('endswith', () => { 163 | expect(executeScript('a-b--', 'endswith("-")')).toEqual(true) 164 | expect(executeScript('a-b--', 'endswith("a")')).toEqual(false) 165 | }) 166 | test('ltrimstr', () => { 167 | expect(executeScript('--a-b--', 'ltrimstr("-")')).toEqual('-a-b--') 168 | }) 169 | test('rtrimstr', () => { 170 | expect(executeScript('--a-b--', 'rtrimstr("-")')).toEqual('--a-b-') 171 | }) 172 | test('split', () => { 173 | expect(executeScript('a-b-c', 'split("-")')).toEqual(['a', 'b', 'c']) 174 | }) 175 | test('join', () => { 176 | expect(executeScript(['a', 'b', 'c'], 'join("-")')).toEqual('a-b-c') 177 | }) 178 | }) 179 | 180 | describe('lazy operator', () => { 181 | test('cannot pick a key from a number', () => { 182 | const input = 1 183 | const script = '.foo' 184 | expect(() => executeScript(input, script)).toThrowErrorMatchingInlineSnapshot( 185 | '"Cannot index number with foo"' 186 | ) 187 | }) 188 | 189 | test('can suppress errors', () => { 190 | const input = 1 191 | const script = '.foo?' 192 | expect(executeScript(input, script)).toEqual(undefined) 193 | }) 194 | 195 | test('cannot expand a literal as an array', () => { 196 | const input = 1 197 | const script = '.[]' 198 | expect(() => executeScript(input, script)).toThrowErrorMatchingInlineSnapshot( 199 | '"Cannot iterate over number"' 200 | ) 201 | }) 202 | 203 | test('can suppress iteration errors', () => { 204 | const input = 1 205 | const script = '.[]?' 206 | expect(executeScript(input, script)).toEqual(undefined) 207 | }) 208 | }) 209 | 210 | describe('pipes', () => { 211 | test('simple pipe', () => { 212 | const input = { foo: { bar: 'baz' } } 213 | const script = '.foo | .bar' 214 | expect(executeScript(input, script)).toEqual('baz') 215 | }) 216 | }) 217 | 218 | describe('create array', () => { 219 | test('with literal', () => { 220 | expect(executeScript(null, '[ 1 ]')).toEqual([1]) 221 | }) 222 | 223 | test('with multiple literal', () => { 224 | expect(executeScript(null, '[ 1, "2", \'3\', null ]')).toEqual([1, '2', '3', null]) 225 | }) 226 | 227 | test('with filter', () => { 228 | expect(executeScript({ foo: 'bar' }, '[ .foo ]')).toEqual(['bar']) 229 | }) 230 | 231 | test('with complex filter', () => { 232 | const input = { foo: [{ bar: 1 }, { bar: 2 }] } 233 | const script = '[ .foo[1].bar ]' 234 | expect(executeScript(input, script)).toEqual([2]) 235 | }) 236 | }) 237 | 238 | describe('create object', () => { 239 | test('with literal', () => { 240 | expect(executeScript(null, '{ foo: 42 }')).toEqual({ foo: 42 }) 241 | }) 242 | 243 | test('with multiple literal', () => { 244 | expect(executeScript(null, '{ foo: 42, bar: "baz", quux: { schmee: null } }')).toEqual({ 245 | foo: 42, 246 | bar: 'baz', 247 | quux: { schmee: null }, 248 | }) 249 | }) 250 | 251 | test('with filters', () => { 252 | expect(executeScript({ foo: 'bar' }, '{ bar: .foo }')).toEqual({ 253 | bar: 'bar', 254 | }) 255 | }) 256 | 257 | test('with iterators', () => { 258 | const input = { foo: ['a', 'b'], bar: [1, 2] } 259 | const script = '{ foo: .foo[], bar: .bar[] }' 260 | expect(executeScript(input, script)).toEqual([ 261 | { foo: 'a', bar: 1 }, 262 | { foo: 'a', bar: 2 }, 263 | { foo: 'b', bar: 1 }, 264 | { foo: 'b', bar: 2 }, 265 | ]) 266 | }) 267 | }) 268 | 269 | describe('iterator', () => { 270 | test('array', () => { 271 | const input = ['foo', 'bar'] 272 | const script = '.[]' 273 | expect(executeScript(input, script)).toEqual(['foo', 'bar']) 274 | }) 275 | 276 | test('object', () => { 277 | const input = { foo: 'a', bar: 'b' } 278 | const script = '.[]' 279 | expect(executeScript(input, script)).toEqual(['a', 'b']) 280 | }) 281 | 282 | test('nested', () => { 283 | const input = { foo: [{ a: 1, b: 2 }], bar: [{ a: 3, b: 4 }] } 284 | const script = '.[].[].b' 285 | expect(executeScript(input, script)).toEqual([2, 4]) 286 | }) 287 | 288 | test('object, nested', () => { 289 | const input = { 290 | foo: [ 291 | { a: 1, b: 2 }, 292 | { a: 3, b: 4 }, 293 | ], 294 | bar: [ 295 | { a: 5, b: 6 }, 296 | { a: 7, b: 8 }, 297 | ], 298 | } 299 | const script = '{foo: .[].[].a, bar: .[].[].b}' 300 | expect(executeScript(input, script)).toEqual([ 301 | { foo: 1, bar: 2 }, 302 | { foo: 1, bar: 4 }, 303 | { foo: 1, bar: 6 }, 304 | { foo: 1, bar: 8 }, 305 | { foo: 3, bar: 2 }, 306 | { foo: 3, bar: 4 }, 307 | { foo: 3, bar: 6 }, 308 | { foo: 3, bar: 8 }, 309 | { foo: 5, bar: 2 }, 310 | { foo: 5, bar: 4 }, 311 | { foo: 5, bar: 6 }, 312 | { foo: 5, bar: 8 }, 313 | { foo: 7, bar: 2 }, 314 | { foo: 7, bar: 4 }, 315 | { foo: 7, bar: 6 }, 316 | { foo: 7, bar: 8 }, 317 | ]) 318 | }) 319 | 320 | test('should not double-promote double-nested single elment array', () => { 321 | const input = [[1]] 322 | const script = '{foo: .[]}' 323 | expect(executeScript(input, script)).toEqual({ foo: [1] }) 324 | }) 325 | 326 | test('cannot iterate over null', () => { 327 | const input = null 328 | const script = '.[]' 329 | expect(() => executeScript(input, script)).toThrowErrorMatchingInlineSnapshot( 330 | '"Cannot iterate over null"' 331 | ) 332 | }) 333 | }) 334 | 335 | describe('nested structures', () => { 336 | test('#1', () => { 337 | const input = { 338 | foo: [ 339 | { 340 | bar: 'baz', 341 | }, 342 | { 343 | bar: 'quux', 344 | }, 345 | ], 346 | } 347 | 348 | expect(executeScript(input, '.foo[] | .bar')).toEqual(['baz', 'quux']) 349 | }) 350 | 351 | test('#2', () => { 352 | const input = { 353 | data: { 354 | foo: { 355 | entries: [ 356 | { name: 'foo-a', a: { b: { c: { d: 1 } } } }, 357 | { name: 'foo-b', a: { b: { c: { d: 2 } } } }, 358 | ], 359 | }, 360 | bar: { 361 | entries: [ 362 | { name: 'bar-a', a: { b: { c: { d: 3 } } } }, 363 | { name: 'bar-b', a: { b: { c: { d: 4 } } } }, 364 | { name: 'bar-c', a: {} }, 365 | ], 366 | }, 367 | }, 368 | } 369 | 370 | expect(executeScript(input, '[.data[].entries[] | {name: .name, value: .a.b.c.d}]')).toEqual([ 371 | { name: 'foo-a', value: 1 }, 372 | { name: 'foo-b', value: 2 }, 373 | { name: 'bar-a', value: 3 }, 374 | { name: 'bar-b', value: 4 }, 375 | { name: 'bar-c', value: null }, 376 | ]) 377 | }) 378 | 379 | test('#3', () => { 380 | const input = { 381 | data: { 382 | foo: { 383 | entries: [{ name: 'foo-a', a: { b: { c: { d: [{ e: 1 }, { e: 2 }] } } } }], 384 | }, 385 | }, 386 | } 387 | 388 | expect( 389 | executeScript(input, '[.data[].entries[] | {name: .name, values: [ .a.b.c.d[] | .e ]}]') 390 | ).toEqual([{ name: 'foo-a', values: [1, 2] }]) 391 | }) 392 | }) 393 | -------------------------------------------------------------------------------- /test/parser.test.ts: -------------------------------------------------------------------------------- 1 | import { parse } from '../src' 2 | 3 | test('current context', () => { 4 | expect(parse('.')).toMatchInlineSnapshot(` 5 | Array [ 6 | Object { 7 | "op": "current_context", 8 | }, 9 | ] 10 | `) 11 | }) 12 | 13 | test('bare identifier', () => { 14 | expect(parse('.foo')).toMatchInlineSnapshot(` 15 | Array [ 16 | Object { 17 | "key": "foo", 18 | "op": "pick", 19 | "strict": true, 20 | }, 21 | ] 22 | `) 23 | }) 24 | 25 | test('bare identifier with punctuation', () => { 26 | expect(parse('.$foo')).toMatchInlineSnapshot(` 27 | Array [ 28 | Object { 29 | "key": "$foo", 30 | "op": "pick", 31 | "strict": true, 32 | }, 33 | ] 34 | `) 35 | }) 36 | 37 | test('optional bare identifier', () => { 38 | expect(parse('.foo?')).toMatchInlineSnapshot(` 39 | Array [ 40 | Object { 41 | "key": "foo", 42 | "op": "pick", 43 | "strict": false, 44 | }, 45 | ] 46 | `) 47 | }) 48 | 49 | test('pick with object index', () => { 50 | expect(parse('.["foo"]')).toMatchInlineSnapshot(` 51 | Array [ 52 | Object { 53 | "op": "current_context", 54 | }, 55 | Object { 56 | "key": "foo", 57 | "op": "pick", 58 | "strict": true, 59 | }, 60 | ] 61 | `) 62 | }) 63 | 64 | test('nested keys', () => { 65 | expect(parse('.foo.bar')).toMatchInlineSnapshot(` 66 | Array [ 67 | Object { 68 | "key": "foo", 69 | "op": "pick", 70 | "strict": true, 71 | }, 72 | Object { 73 | "key": "bar", 74 | "op": "pick", 75 | "strict": true, 76 | }, 77 | ] 78 | `) 79 | }) 80 | 81 | test('index an array', () => { 82 | expect(parse('.[1]')).toMatchInlineSnapshot(` 83 | Array [ 84 | Object { 85 | "op": "current_context", 86 | }, 87 | Object { 88 | "index": 1, 89 | "op": "index", 90 | "strict": true, 91 | }, 92 | ] 93 | `) 94 | }) 95 | 96 | test('index an array with negative index', () => { 97 | expect(parse('.[-1]')).toMatchInlineSnapshot(` 98 | Array [ 99 | Object { 100 | "op": "current_context", 101 | }, 102 | Object { 103 | "index": -1, 104 | "op": "index", 105 | "strict": true, 106 | }, 107 | ] 108 | `) 109 | }) 110 | 111 | test('index non-strictly', () => { 112 | expect(parse('.[1]?')).toMatchInlineSnapshot(` 113 | Array [ 114 | Object { 115 | "op": "current_context", 116 | }, 117 | Object { 118 | "index": 1, 119 | "op": "index", 120 | "strict": false, 121 | }, 122 | ] 123 | `) 124 | }) 125 | 126 | test('slice an array', () => { 127 | expect(parse('.[1:3]')).toMatchInlineSnapshot(` 128 | Array [ 129 | Object { 130 | "op": "current_context", 131 | }, 132 | Object { 133 | "end": 3, 134 | "op": "slice", 135 | "start": 1, 136 | "strict": true, 137 | }, 138 | ] 139 | `) 140 | }) 141 | 142 | test('slice an array with negative offsets', () => { 143 | expect(parse('.[-3:-1]')).toMatchInlineSnapshot(` 144 | Array [ 145 | Object { 146 | "op": "current_context", 147 | }, 148 | Object { 149 | "end": -1, 150 | "op": "slice", 151 | "start": -3, 152 | "strict": true, 153 | }, 154 | ] 155 | `) 156 | }) 157 | 158 | test('slice an array with no start', () => { 159 | expect(parse('.[:3]')).toMatchInlineSnapshot(` 160 | Array [ 161 | Object { 162 | "op": "current_context", 163 | }, 164 | Object { 165 | "end": 3, 166 | "op": "slice", 167 | "start": undefined, 168 | "strict": true, 169 | }, 170 | ] 171 | `) 172 | }) 173 | 174 | test('slice an array with no end', () => { 175 | expect(parse('.[1:]')).toMatchInlineSnapshot(` 176 | Array [ 177 | Object { 178 | "op": "current_context", 179 | }, 180 | Object { 181 | "end": undefined, 182 | "op": "slice", 183 | "start": 1, 184 | "strict": true, 185 | }, 186 | ] 187 | `) 188 | }) 189 | 190 | test('get all values from an array', () => { 191 | expect(parse('.[]')).toMatchInlineSnapshot(` 192 | Array [ 193 | Object { 194 | "op": "current_context", 195 | }, 196 | Object { 197 | "op": "explode", 198 | "strict": true, 199 | }, 200 | ] 201 | `) 202 | }) 203 | 204 | test('pick a value and index into it', () => { 205 | expect(parse('.foo[1]')).toMatchInlineSnapshot(` 206 | Array [ 207 | Object { 208 | "key": "foo", 209 | "op": "pick", 210 | "strict": true, 211 | }, 212 | Object { 213 | "index": 1, 214 | "op": "index", 215 | "strict": true, 216 | }, 217 | ] 218 | `) 219 | }) 220 | 221 | test('pick value from an array', () => { 222 | expect(parse('.foo[].bar')).toMatchInlineSnapshot(` 223 | Array [ 224 | Object { 225 | "key": "foo", 226 | "op": "pick", 227 | "strict": true, 228 | }, 229 | Object { 230 | "op": "explode", 231 | "strict": true, 232 | }, 233 | Object { 234 | "key": "bar", 235 | "op": "pick", 236 | "strict": true, 237 | }, 238 | ] 239 | `) 240 | }) 241 | 242 | test('pick a value from an array and pick from that', () => { 243 | expect(parse('.foo[1].bar')).toMatchInlineSnapshot(` 244 | Array [ 245 | Object { 246 | "key": "foo", 247 | "op": "pick", 248 | "strict": true, 249 | }, 250 | Object { 251 | "index": 1, 252 | "op": "index", 253 | "strict": true, 254 | }, 255 | Object { 256 | "key": "bar", 257 | "op": "pick", 258 | "strict": true, 259 | }, 260 | ] 261 | `) 262 | }) 263 | 264 | test('pipe commands together', () => { 265 | expect(parse('.foo | .bar')).toMatchInlineSnapshot(` 266 | Array [ 267 | Object { 268 | "in": Array [ 269 | Object { 270 | "key": "foo", 271 | "op": "pick", 272 | "strict": true, 273 | }, 274 | ], 275 | "op": "pipe", 276 | "out": Array [ 277 | Object { 278 | "key": "bar", 279 | "op": "pick", 280 | "strict": true, 281 | }, 282 | ], 283 | }, 284 | ] 285 | `) 286 | }) 287 | 288 | test('multiple pipe', () => { 289 | expect(parse('.foo | .bar | .baz')).toMatchInlineSnapshot(` 290 | Array [ 291 | Object { 292 | "in": Array [ 293 | Object { 294 | "in": Array [ 295 | Object { 296 | "key": "foo", 297 | "op": "pick", 298 | "strict": true, 299 | }, 300 | ], 301 | "op": "pipe", 302 | "out": Array [ 303 | Object { 304 | "key": "bar", 305 | "op": "pick", 306 | "strict": true, 307 | }, 308 | ], 309 | }, 310 | ], 311 | "op": "pipe", 312 | "out": Array [ 313 | Object { 314 | "key": "baz", 315 | "op": "pick", 316 | "strict": true, 317 | }, 318 | ], 319 | }, 320 | ] 321 | `) 322 | }) 323 | 324 | test('pipe into create object', () => { 325 | expect(parse('.[] | {foo: .bar}')).toMatchInlineSnapshot(` 326 | Array [ 327 | Object { 328 | "in": Array [ 329 | Object { 330 | "op": "current_context", 331 | }, 332 | Object { 333 | "op": "explode", 334 | "strict": true, 335 | }, 336 | ], 337 | "op": "pipe", 338 | "out": Array [ 339 | Object { 340 | "entries": Array [ 341 | Object { 342 | "key": "foo", 343 | "value": Array [ 344 | Object { 345 | "key": "bar", 346 | "op": "pick", 347 | "strict": true, 348 | }, 349 | ], 350 | }, 351 | ], 352 | "op": "create_object", 353 | }, 354 | ], 355 | }, 356 | ] 357 | `) 358 | }) 359 | 360 | describe('create arrays', () => { 361 | test('create an array', () => { 362 | expect(parse('[ 1 ]')).toMatchInlineSnapshot(` 363 | Array [ 364 | Object { 365 | "op": "create_array", 366 | "values": Array [ 367 | Array [ 368 | Object { 369 | "op": "literal", 370 | "value": 1, 371 | }, 372 | ], 373 | ], 374 | }, 375 | ] 376 | `) 377 | }) 378 | 379 | test('create an array with multiple values', () => { 380 | expect(parse('[ 1, 2 ]')).toMatchInlineSnapshot(` 381 | Array [ 382 | Object { 383 | "op": "create_array", 384 | "values": Array [ 385 | Array [ 386 | Object { 387 | "op": "literal", 388 | "value": 1, 389 | }, 390 | ], 391 | Array [ 392 | Object { 393 | "op": "literal", 394 | "value": 2, 395 | }, 396 | ], 397 | ], 398 | }, 399 | ] 400 | `) 401 | }) 402 | 403 | test('create an array with filters', () => { 404 | expect(parse('[ .foo, .bar ]')).toMatchInlineSnapshot(` 405 | Array [ 406 | Object { 407 | "op": "create_array", 408 | "values": Array [ 409 | Array [ 410 | Object { 411 | "key": "foo", 412 | "op": "pick", 413 | "strict": true, 414 | }, 415 | ], 416 | Array [ 417 | Object { 418 | "key": "bar", 419 | "op": "pick", 420 | "strict": true, 421 | }, 422 | ], 423 | ], 424 | }, 425 | ] 426 | `) 427 | }) 428 | 429 | test('create an array by picking a value from an array and picking from that', () => { 430 | expect(parse('[ .foo[1].bar, .foo[1].bar ]')).toMatchInlineSnapshot(` 431 | Array [ 432 | Object { 433 | "op": "create_array", 434 | "values": Array [ 435 | Array [ 436 | Object { 437 | "key": "foo", 438 | "op": "pick", 439 | "strict": true, 440 | }, 441 | Object { 442 | "index": 1, 443 | "op": "index", 444 | "strict": true, 445 | }, 446 | Object { 447 | "key": "bar", 448 | "op": "pick", 449 | "strict": true, 450 | }, 451 | ], 452 | Array [ 453 | Object { 454 | "key": "foo", 455 | "op": "pick", 456 | "strict": true, 457 | }, 458 | Object { 459 | "index": 1, 460 | "op": "index", 461 | "strict": true, 462 | }, 463 | Object { 464 | "key": "bar", 465 | "op": "pick", 466 | "strict": true, 467 | }, 468 | ], 469 | ], 470 | }, 471 | ] 472 | `) 473 | }) 474 | }) 475 | 476 | describe('create objects', () => { 477 | test('one key', () => { 478 | expect(parse('{ foo: 1 }')).toMatchInlineSnapshot(` 479 | Array [ 480 | Object { 481 | "entries": Array [ 482 | Object { 483 | "key": "foo", 484 | "value": Array [ 485 | Object { 486 | "op": "literal", 487 | "value": 1, 488 | }, 489 | ], 490 | }, 491 | ], 492 | "op": "create_object", 493 | }, 494 | ] 495 | `) 496 | }) 497 | 498 | test('multiple keys', () => { 499 | expect(parse('{ foo: 1, bar: \'baz\', quux: "schmee" }')).toMatchInlineSnapshot(` 500 | Array [ 501 | Object { 502 | "entries": Array [ 503 | Object { 504 | "key": "foo", 505 | "value": Array [ 506 | Object { 507 | "op": "literal", 508 | "value": 1, 509 | }, 510 | ], 511 | }, 512 | Object { 513 | "key": "bar", 514 | "value": Array [ 515 | Object { 516 | "op": "literal", 517 | "value": "baz", 518 | }, 519 | ], 520 | }, 521 | Object { 522 | "key": "quux", 523 | "value": Array [ 524 | Object { 525 | "op": "literal", 526 | "value": "schmee", 527 | }, 528 | ], 529 | }, 530 | ], 531 | "op": "create_object", 532 | }, 533 | ] 534 | `) 535 | }) 536 | 537 | test('filters for values', () => { 538 | expect(parse('{ foo: .bar, baz: .quux.schmee }')).toMatchInlineSnapshot(` 539 | Array [ 540 | Object { 541 | "entries": Array [ 542 | Object { 543 | "key": "foo", 544 | "value": Array [ 545 | Object { 546 | "key": "bar", 547 | "op": "pick", 548 | "strict": true, 549 | }, 550 | ], 551 | }, 552 | Object { 553 | "key": "baz", 554 | "value": Array [ 555 | Object { 556 | "key": "quux", 557 | "op": "pick", 558 | "strict": true, 559 | }, 560 | Object { 561 | "key": "schmee", 562 | "op": "pick", 563 | "strict": true, 564 | }, 565 | ], 566 | }, 567 | ], 568 | "op": "create_object", 569 | }, 570 | ] 571 | `) 572 | }) 573 | }) 574 | 575 | describe('functions', () => { 576 | test('trim', () => { 577 | expect(parse('trim')).toMatchInlineSnapshot(` 578 | Array [ 579 | Object { 580 | "name": "trim", 581 | "op": "function", 582 | }, 583 | ] 584 | `) 585 | }) 586 | test('ltrim', () => { 587 | expect(parse('ltrim')).toMatchInlineSnapshot(` 588 | Array [ 589 | Object { 590 | "name": "ltrim", 591 | "op": "function", 592 | }, 593 | ] 594 | `) 595 | }) 596 | test('rtrim', () => { 597 | expect(parse('rtrim')).toMatchInlineSnapshot(` 598 | Array [ 599 | Object { 600 | "name": "rtrim", 601 | "op": "function", 602 | }, 603 | ] 604 | `) 605 | }) 606 | test('startswith', () => { 607 | expect(parse('startswith("foo")')).toMatchInlineSnapshot(` 608 | Array [ 609 | Object { 610 | "name": "startswith", 611 | "op": "function", 612 | "value": "foo", 613 | }, 614 | ] 615 | `) 616 | }) 617 | test('endswith', () => { 618 | expect(parse('endswith("foo")')).toMatchInlineSnapshot(` 619 | Array [ 620 | Object { 621 | "name": "endswith", 622 | "op": "function", 623 | "value": "foo", 624 | }, 625 | ] 626 | `) 627 | }) 628 | test('ltrimstr', () => { 629 | expect(parse('ltrimstr("foo")')).toMatchInlineSnapshot(` 630 | Array [ 631 | Object { 632 | "name": "ltrimstr", 633 | "op": "function", 634 | "value": "foo", 635 | }, 636 | ] 637 | `) 638 | }) 639 | test('rtrimstr', () => { 640 | expect(parse('rtrimstr("foo")')).toMatchInlineSnapshot(` 641 | Array [ 642 | Object { 643 | "name": "rtrimstr", 644 | "op": "function", 645 | "value": "foo", 646 | }, 647 | ] 648 | `) 649 | }) 650 | test('split', () => { 651 | expect(parse('split("foo")')).toMatchInlineSnapshot(` 652 | Array [ 653 | Object { 654 | "name": "split", 655 | "op": "function", 656 | "value": "foo", 657 | }, 658 | ] 659 | `) 660 | }) 661 | test('join', () => { 662 | expect(parse('join("foo")')).toMatchInlineSnapshot(` 663 | Array [ 664 | Object { 665 | "name": "join", 666 | "op": "function", 667 | "value": "foo", 668 | }, 669 | ] 670 | `) 671 | }) 672 | }) 673 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | 26 | /* Modules */ 27 | "module": "commonjs", /* Specify what module code is generated. */ 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | // "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 50 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 68 | 69 | /* Interop Constraints */ 70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 75 | 76 | /* Type Checking */ 77 | "strict": true, /* Enable all strict type-checking options. */ 78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 96 | 97 | /* Completeness */ 98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 100 | } 101 | } 102 | --------------------------------------------------------------------------------