├── .release-please-manifest.json ├── renovate.json ├── js ├── tests │ ├── types │ │ ├── commonjs.cts │ │ ├── tsconfig.json │ │ └── typedefs.test.ts │ └── print.test.js ├── tsconfig.json ├── src │ ├── index.js │ ├── char-codes.js │ ├── evaluate.js │ ├── errors.js │ ├── syntax.js │ ├── traversal.js │ └── char-code-reader.js ├── tsconfig.build.json ├── rollup.config.js ├── tools │ ├── update-cts-references.js │ ├── strip-typedef-aliases.js │ ├── perf2.js │ ├── regenerate-test-data.js │ └── perf.js ├── .eslintrc.cjs └── package.json ├── rust ├── src │ ├── mode.rs │ ├── errors.rs │ ├── lib.rs │ ├── ast.rs │ └── location.rs ├── Cargo.toml └── README.md ├── decisions ├── 000-template.md ├── 002-json-jsonc-trailing-commas.md └── 001-json5-numbers.md ├── release-please-config.json ├── package.json ├── .github ├── ISSUE_TEMPLATE │ └── BUG_REPORT.md └── workflows │ ├── types-integration.yml │ ├── ci.yml │ ├── manual-publish.yml │ └── release-please.yml ├── fixtures ├── asts │ ├── number-1.txt │ ├── number-2.txt │ ├── number-6.txt │ ├── string-1.txt │ ├── string-5.txt │ ├── string-6.txt │ ├── number-3.txt │ ├── number-4.txt │ ├── number-5.txt │ ├── number-7.txt │ ├── number-9.txt │ ├── number-nan-1-json5.txt │ ├── string-3.txt │ ├── string-4.txt │ ├── string-7.txt │ ├── boolean-true.txt │ ├── number-8.txt │ ├── string-2.txt │ ├── boolean-false.txt │ ├── number-infinity-1-json5.txt │ ├── number-nan-2-plus-sign-json5.txt │ ├── number-nan-3-minus-sign-json5.txt │ ├── string-8-single-quote-json5.txt │ ├── number-leading-plus-1-json5.txt │ ├── number-infinity-2-plus-sign-json5.txt │ ├── number-infinity-3-minus-sign-json5.txt │ ├── string-9-single-quote-with-double-quote-json5.txt │ ├── string-11-multiline-json5.txt │ ├── string-10-single-quote-escapes-json5.txt │ ├── array-2.txt │ ├── array-3.txt │ ├── array-5.txt │ ├── array-6.txt │ ├── array-7.txt │ ├── object-1.txt │ ├── object-7-identifier-nan-json5.txt │ ├── object-2.txt │ ├── object-7-identifier-infinity-json5.txt │ └── array-4.txt └── asts-with-range │ ├── number-1.txt │ ├── number-6.txt │ ├── number-2.txt │ ├── number-4.txt │ ├── number-5.txt │ ├── string-1.txt │ ├── string-5.txt │ ├── string-6.txt │ ├── string-7.txt │ ├── number-3.txt │ ├── number-7.txt │ ├── number-8.txt │ ├── number-9.txt │ ├── number-nan-1-json5.txt │ ├── string-2.txt │ ├── string-3.txt │ ├── string-4.txt │ ├── boolean-false.txt │ ├── boolean-true.txt │ ├── number-nan-2-plus-sign-json5.txt │ ├── string-8-single-quote-json5.txt │ ├── string-single-quote-1-json5.txt │ ├── number-infinity-1-json5.txt │ ├── number-leading-plus-1-json5.txt │ ├── number-nan-3-minus-sign-json5.txt │ ├── number-infinity-2-plus-sign-json5.txt │ ├── number-infinity-3-minus-sign-json5.txt │ ├── string-9-single-quote-with-double-quote-json5.txt │ ├── string-11-multiline-json5.txt │ ├── string-10-single-quote-escapes-json5.txt │ ├── array-2.txt │ ├── array-3.txt │ ├── array-5.txt │ ├── array-6.txt │ ├── array-7.txt │ ├── object-1.txt │ └── object-7-identifier-nan-json5.txt ├── .gitignore └── README.md /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "js": "3.3.10", 3 | "rust": "3.2.5" 4 | } 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":rebaseStalePrs" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /js/tests/types/commonjs.cts: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests of TypeScript types in CommonJS 3 | * @author Nicholas C. Zakas 4 | */ 5 | 6 | import "@humanwhocodes/momoa"; 7 | -------------------------------------------------------------------------------- /rust/src/mode.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::wasm_bindgen; 2 | 3 | #[wasm_bindgen] 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 5 | pub enum Mode { 6 | Json, 7 | Jsonc, 8 | } 9 | -------------------------------------------------------------------------------- /decisions/000-template.md: -------------------------------------------------------------------------------- 1 | # Title 2 | 3 | Date: YYYY-MM-DD 4 | 5 | Status: proposed | rejected | accepted | deprecated | … | superseded by 6 | [005](005-example.md) 7 | 8 | ## Context 9 | 10 | ## Decision 11 | 12 | ## Consequences 13 | -------------------------------------------------------------------------------- /js/tests/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "rootDir": "../../", 5 | "target": "ESNext", 6 | "moduleResolution": "bundler", 7 | "types": [] 8 | }, 9 | "files": ["../../dist/momoa.d.ts", "typedefs.test.ts", "commonjs.cts"] 10 | } 11 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bootstrap-sha": "a0b3e378027f53a1660ff014c69c6f00f4c5fb3d", 3 | "packages": { 4 | "js": { 5 | "release-type": "node", 6 | "package-name": "momoa-js" 7 | }, 8 | "rust": { 9 | "release-type": "rust", 10 | "package-name": "momoa-rs" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src/index.js", 4 | "src/typedefs.ts" 5 | ], 6 | "compilerOptions": { 7 | "noEmit": true, 8 | "checkJs": true, 9 | "target": "ES2022", 10 | "moduleResolution": "NodeNext", 11 | "module": "NodeNext", 12 | "types": [] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /js/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview File defining the interface of the package. 3 | * @author Nicholas C. Zakas 4 | */ 5 | 6 | export { tokenize } from "./tokens.js"; 7 | export { parse } from "./parse.js"; 8 | export { types } from "./types.js"; 9 | export { traverse, iterator, childKeys as visitorKeys } from "./traversal.js"; 10 | export { evaluate } from "./evaluate.js"; 11 | export { print } from "./print.js"; 12 | -------------------------------------------------------------------------------- /js/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "dist/momoa.js", 4 | "dist/typedefs.ts" 5 | ], 6 | "compilerOptions": { 7 | "declaration": true, 8 | "emitDeclarationOnly": true, 9 | "allowImportingTsExtensions": true, 10 | "checkJs": true, 11 | "target": "ES2022", 12 | "moduleResolution": "NodeNext", 13 | "module": "NodeNext", 14 | "lib": ["ESNext", "DOM"], 15 | "types": [] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "momoa", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/humanwhocodes/momoa.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/humanwhocodes/momoa/issues" 12 | }, 13 | "homepage": "https://github.com/humanwhocodes/momoa#readme", 14 | "keywords": [ 15 | "json", 16 | "ast", 17 | "json tree", 18 | "abstract syntax tree" 19 | ], 20 | "license": "Apache-2.0", 21 | "devDependencies": { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /js/rollup.config.js: -------------------------------------------------------------------------------- 1 | import copy from "rollup-plugin-copy"; 2 | 3 | export default [ 4 | { 5 | input: "src/index.js", 6 | output: [ 7 | { 8 | file: "dist/momoa.js", 9 | format: "esm" 10 | }, 11 | { 12 | file: "dist/momoa.cjs", 13 | format: "commonjs" 14 | } 15 | ], 16 | plugins: [ 17 | copy({ 18 | targets: [ 19 | { src: "src/typedefs.ts", dest: "dist/" }, 20 | ] 21 | }) 22 | ] 23 | } 24 | ]; 25 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "momoa" 3 | version = "3.2.5" 4 | description = "A JSON parsing library suitable for static analysis" 5 | authors = ["Nicholas C. Zakas"] 6 | license = "Apache-2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/humanwhocodes/momoa" 9 | edition = "2021" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [dependencies] 17 | thiserror = "2.0.0" 18 | serde = { version = "1.0", features = ["derive"] } 19 | wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } 20 | serde-wasm-bindgen = "0.6" 21 | 22 | [dev-dependencies] 23 | test-case = "3.0.0" 24 | serde_json = "1" 25 | glob = "0" 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41E Bug report" 3 | about: Report a problem with Momoa 4 | title: '' 5 | labels: bug, needs repro 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Tell us about your environment** 11 | 12 | * **Momoa Version:** 13 | * **Node Version:** 14 | * **npm Version:** 15 | 16 | **Which function(s) is causing a problem?** 17 | 18 | [ ] `interpret()` 19 | [ ] `iterator()` 20 | [ ] `parse()` 21 | [ ] `print()` 22 | [ ] `traverse()` 23 | [ ] `tokenize()` 24 | 25 | **Example JSON code that demonstrates the problem:** 26 | 27 | ```json 28 | 29 | ``` 30 | 31 | **What did you do?** 32 | 33 | 34 | **What did you expect to happen?** 35 | 36 | 37 | **What actually happened?** 38 | 39 | 40 | **What do you think the solution is?** 41 | -------------------------------------------------------------------------------- /js/tools/update-cts-references.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @fileoverview Updates types.js references to types.cjs in the rolled-up 4 | * .d.ts file. 5 | * @author Nicholas C. Zakas 6 | */ 7 | 8 | //----------------------------------------------------------------------------- 9 | // Imports 10 | //----------------------------------------------------------------------------- 11 | 12 | import fs from "node:fs"; 13 | 14 | //----------------------------------------------------------------------------- 15 | // Main 16 | //----------------------------------------------------------------------------- 17 | 18 | const filePath = "./dist/momoa.d.cts"; 19 | const code = fs.readFileSync(filePath, "utf8"); 20 | const fixedCode = code.replace(/typedefs\.js/g, "typedefs.cjs"); 21 | 22 | fs.writeFileSync(filePath, fixedCode, "utf8"); 23 | fs.copyFileSync("./dist/typedefs.d.ts", "./dist/typedefs.d.cts"); 24 | -------------------------------------------------------------------------------- /js/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | /* global module */ 5 | 6 | module.exports = { 7 | "env": { 8 | "es6": true, 9 | }, 10 | "extends": "eslint:recommended", 11 | "parserOptions": { 12 | "ecmaVersion": "latest", 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | "indent": [ 17 | "error", 18 | 4, 19 | { SwitchCase: 1 } 20 | ], 21 | "linebreak-style": [ 22 | "error", 23 | "unix" 24 | ], 25 | "quotes": [ 26 | "error", 27 | "double" 28 | ], 29 | "semi": [ 30 | "error", 31 | "always" 32 | ] 33 | }, 34 | overrides: [ 35 | { 36 | files: ["tests/*.js"], 37 | env: { 38 | mocha: true, 39 | node: true 40 | } 41 | } 42 | ] 43 | }; 44 | -------------------------------------------------------------------------------- /js/tools/strip-typedef-aliases.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @fileoverview Strips typedef aliases from the rolled-up file. 4 | * @author Nicholas C. Zakas 5 | */ 6 | 7 | //----------------------------------------------------------------------------- 8 | // Imports 9 | //----------------------------------------------------------------------------- 10 | 11 | import fs from "node:fs"; 12 | 13 | //----------------------------------------------------------------------------- 14 | // Main 15 | //----------------------------------------------------------------------------- 16 | 17 | const filePath = "./dist/momoa.js"; 18 | const lines = fs.readFileSync(filePath, "utf8").split(/\r?\n/g); 19 | const typedefs = new Set(); 20 | 21 | const remainingLines = lines.filter(line => { 22 | 23 | if (!line.startsWith("/** @typedef {import")) { 24 | return true; 25 | } 26 | 27 | if (typedefs.has(line)) { 28 | return false; 29 | } 30 | 31 | typedefs.add(line); 32 | return true; 33 | }); 34 | 35 | 36 | 37 | fs.writeFileSync(filePath, remainingLines.join("\n"), "utf8"); 38 | -------------------------------------------------------------------------------- /.github/workflows/types-integration.yml: -------------------------------------------------------------------------------- 1 | name: Types Integration Tests 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | eslint_json: 13 | name: Types (@eslint/json) 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout momoa 17 | uses: actions/checkout@v4 18 | with: 19 | path: momoa 20 | 21 | - name: Checkout @eslint/json 22 | uses: actions/checkout@v4 23 | with: 24 | repository: eslint/json 25 | path: json 26 | 27 | - uses: actions/setup-node@v4 28 | with: 29 | node-version: "lts/*" 30 | 31 | - name: Install Packages (momoa/js) 32 | working-directory: momoa/js 33 | run: npm install 34 | 35 | - name: Install Packages (eslint/json) 36 | working-directory: json 37 | run: | 38 | npm install 39 | npm run build 40 | npm install ../momoa/js 41 | 42 | - name: Run TSC 43 | working-directory: json 44 | run: npm run test:types 45 | -------------------------------------------------------------------------------- /fixtures/asts/number-1.txt: -------------------------------------------------------------------------------- 1 | 1 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 1, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 2, 30 | "offset": 1 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 2, 45 | "offset": 1 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-2.txt: -------------------------------------------------------------------------------- 1 | 1.5 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 1.5, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 4, 17 | "offset": 3 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 4, 30 | "offset": 3 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 4, 45 | "offset": 3 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-6.txt: -------------------------------------------------------------------------------- 1 | 0 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 0, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 2, 30 | "offset": 1 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 2, 45 | "offset": 1 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/string-1.txt: -------------------------------------------------------------------------------- 1 | "" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 3, 17 | "offset": 2 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 3, 30 | "offset": 2 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "String", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 3, 45 | "offset": 2 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/string-5.txt: -------------------------------------------------------------------------------- 1 | "/" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "/", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 4, 17 | "offset": 3 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 4, 30 | "offset": 3 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "String", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 4, 45 | "offset": 3 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/string-6.txt: -------------------------------------------------------------------------------- 1 | "/" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "/", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 4, 17 | "offset": 3 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 4, 30 | "offset": 3 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "String", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 4, 45 | "offset": 3 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-3.txt: -------------------------------------------------------------------------------- 1 | -1.52 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": -1.52, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 6, 17 | "offset": 5 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 6, 30 | "offset": 5 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 6, 45 | "offset": 5 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-4.txt: -------------------------------------------------------------------------------- 1 | -0.1 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": -0.1, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 5, 17 | "offset": 4 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 5, 30 | "offset": 4 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 5, 45 | "offset": 4 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-5.txt: -------------------------------------------------------------------------------- 1 | 0.17 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 0.17, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 5, 17 | "offset": 4 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 5, 30 | "offset": 4 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 5, 45 | "offset": 4 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-7.txt: -------------------------------------------------------------------------------- 1 | 1e5 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 100000, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 4, 17 | "offset": 3 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 4, 30 | "offset": 3 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 4, 45 | "offset": 3 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-9.txt: -------------------------------------------------------------------------------- 1 | 4e+50 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 4e+50, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 6, 17 | "offset": 5 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 6, 30 | "offset": 5 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 6, 45 | "offset": 5 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-nan-1-json5.txt: -------------------------------------------------------------------------------- 1 | NaN 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "NaN", 7 | "sign": "", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 4, 17 | "offset": 3 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 4, 30 | "offset": 3 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 4, 45 | "offset": 3 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/string-3.txt: -------------------------------------------------------------------------------- 1 | "\u002F" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "/", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 9, 17 | "offset": 8 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 9, 30 | "offset": 8 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "String", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 9, 45 | "offset": 8 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/string-4.txt: -------------------------------------------------------------------------------- 1 | "\u002f" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "/", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 9, 17 | "offset": 8 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 9, 30 | "offset": 8 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "String", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 9, 45 | "offset": 8 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/string-7.txt: -------------------------------------------------------------------------------- 1 | "\b" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "\b", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 5, 17 | "offset": 4 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 5, 30 | "offset": 4 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "String", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 5, 45 | "offset": 4 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/boolean-true.txt: -------------------------------------------------------------------------------- 1 | true 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Boolean", 7 | "value": true, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 5, 17 | "offset": 4 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 5, 30 | "offset": 4 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Boolean", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 5, 45 | "offset": 4 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-8.txt: -------------------------------------------------------------------------------- 1 | 21e-51 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 2.1e-50, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 7, 17 | "offset": 6 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 7, 30 | "offset": 6 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 7, 45 | "offset": 6 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/string-2.txt: -------------------------------------------------------------------------------- 1 | "\u005C" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "\\", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 9, 17 | "offset": 8 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 9, 30 | "offset": 8 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "String", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 9, 45 | "offset": 8 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/boolean-false.txt: -------------------------------------------------------------------------------- 1 | false 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Boolean", 7 | "value": false, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 6, 17 | "offset": 5 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 6, 30 | "offset": 5 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Boolean", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 6, 45 | "offset": 5 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-infinity-1-json5.txt: -------------------------------------------------------------------------------- 1 | Infinity 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Infinity", 7 | "sign": "", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 9, 17 | "offset": 8 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 9, 30 | "offset": 8 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 9, 45 | "offset": 8 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-nan-2-plus-sign-json5.txt: -------------------------------------------------------------------------------- 1 | +NaN 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "NaN", 7 | "sign": "+", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 5, 17 | "offset": 4 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 5, 30 | "offset": 4 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 5, 45 | "offset": 4 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-nan-3-minus-sign-json5.txt: -------------------------------------------------------------------------------- 1 | -NaN 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "NaN", 7 | "sign": "-", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 5, 17 | "offset": 4 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 5, 30 | "offset": 4 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 5, 45 | "offset": 4 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/string-8-single-quote-json5.txt: -------------------------------------------------------------------------------- 1 | '' 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 3, 17 | "offset": 2 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 3, 30 | "offset": 2 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "String", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 3, 45 | "offset": 2 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-leading-plus-1-json5.txt: -------------------------------------------------------------------------------- 1 | +1.52 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 1.52, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 6, 17 | "offset": 5 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 6, 30 | "offset": 5 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 6, 45 | "offset": 5 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-infinity-2-plus-sign-json5.txt: -------------------------------------------------------------------------------- 1 | +Infinity 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Infinity", 7 | "sign": "+", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 10, 17 | "offset": 9 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 10, 30 | "offset": 9 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 10, 45 | "offset": 9 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/number-infinity-3-minus-sign-json5.txt: -------------------------------------------------------------------------------- 1 | -Infinity 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Infinity", 7 | "sign": "-", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 10, 17 | "offset": 9 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 10, 30 | "offset": 9 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "Number", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 10, 45 | "offset": 9 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /fixtures/asts/string-9-single-quote-with-double-quote-json5.txt: -------------------------------------------------------------------------------- 1 | '"' 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "\"", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 4, 17 | "offset": 3 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 4, 30 | "offset": 3 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "String", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 4, 45 | "offset": 3 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /rust/src/errors.rs: -------------------------------------------------------------------------------- 1 | use crate::tokens::TokenKind; 2 | use serde::Serialize; 3 | use std::fmt; 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Clone, Copy, Serialize)] 7 | pub enum MomoaError { 8 | #[error("Unexpected character {c:?} found. ({line:?}:{column:?})")] 9 | UnexpectedCharacter { c: char, line: usize, column: usize }, 10 | 11 | #[error("Unexpected end of input found. ({line:?}:{column:?})")] 12 | UnexpectedEndOfInput { line: usize, column: usize }, 13 | 14 | #[error("Unexpected element found. ({line:?}:{column:?})")] 15 | UnexpectedElement { line: usize, column: usize }, 16 | 17 | #[error("Unexpected token {unexpected:?} found. ({line:?}:{column:?})")] 18 | UnexpectedToken { 19 | unexpected: TokenKind, 20 | line: usize, 21 | column: usize, 22 | }, 23 | 24 | #[error("Expected token {expected:?} but found {unexpected:?}. ({line:?}:{column:?})")] 25 | MissingExpectedToken { 26 | expected: TokenKind, 27 | unexpected: TokenKind, 28 | line: usize, 29 | column: usize, 30 | }, 31 | } 32 | 33 | impl fmt::Debug for MomoaError { 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 35 | f.write_str(&self.to_string()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fixtures/asts/string-11-multiline-json5.txt: -------------------------------------------------------------------------------- 1 | "Hello, \ 2 | world!" 3 | --- 4 | { 5 | "type": "Document", 6 | "body": { 7 | "type": "String", 8 | "value": "Hello, world!", 9 | "loc": { 10 | "start": { 11 | "line": 1, 12 | "column": 1, 13 | "offset": 0 14 | }, 15 | "end": { 16 | "line": 2, 17 | "column": 8, 18 | "offset": 17 19 | } 20 | } 21 | }, 22 | "loc": { 23 | "start": { 24 | "line": 1, 25 | "column": 1, 26 | "offset": 0 27 | }, 28 | "end": { 29 | "line": 2, 30 | "column": 8, 31 | "offset": 17 32 | } 33 | }, 34 | "tokens": [ 35 | { 36 | "type": "String", 37 | "loc": { 38 | "start": { 39 | "line": 1, 40 | "column": 1, 41 | "offset": 0 42 | }, 43 | "end": { 44 | "line": 2, 45 | "column": 8, 46 | "offset": 17 47 | } 48 | } 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /decisions/002-json-jsonc-trailing-commas.md: -------------------------------------------------------------------------------- 1 | # JSON and JSONC Will Allow Trailing Commas via an Option 2 | 3 | Date: 2024-10-15 4 | 5 | Status: accepted 6 | 7 | ## Context 8 | 9 | JSONC parsing was meant to be compatible with Microsoft's [JSONC parser](https://github.com/microsoft/node-jsonc-parser) and initially the JSONC parsing was simply JSON with JavaScript-style comments. It was [pointed out](https://github.com/humanwhocodes/momoa/issues/135) that files like `tsconfig.json` and `settings.json` actually allow dangling commas in addition to comments. Upon further investigation, the Microsoft JSONC parser defaults to [disallowing dangling commas](https://github.com/microsoft/node-jsonc-parser/blob/3c9b4203d663061d87d4d34dd0004690aef94db5/src/impl/parser.ts#L22) and is enabled as an exception of VS Code- and TypeScript-related files. 10 | 11 | ## Decision 12 | 13 | Both JSON and JSONC parsing modes will have an `allowTrailingCommas` option that will default to `false`. This will allow users to determine whether they want to allow comments, trailing commas, or both. 14 | 15 | ## Consequences 16 | 17 | For JSON and JSON5 users, there will be no immediate action necessary. 18 | 19 | For JSONC users, they will need to update their options to allowing trailing commas when parsing VS Code- and TypeScript-related files. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # distribution files 64 | js/dist 65 | 66 | # temporary files 67 | js/temp 68 | 69 | # Rust builds 70 | rust/target 71 | -------------------------------------------------------------------------------- /fixtures/asts/string-10-single-quote-escapes-json5.txt: -------------------------------------------------------------------------------- 1 | '\\b\\f\\n\\r\\t\\v\\0\\x0f\\u01fF\\\n\\\r\n\\\r\\\u2028\\\u2029\\a\\\'\\"' 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "\\b\\f\\n\\r\\t\\v\\0\\x0f\\u01fF\\\n\\\r\n\\\r\\
\\
\\a\\'\\\"", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 76, 17 | "offset": 75 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 76, 30 | "offset": 75 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "String", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 76, 45 | "offset": 75 46 | } 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /decisions/001-json5-numbers.md: -------------------------------------------------------------------------------- 1 | # JSON5 Tokens and Nodes for Hexadecimal, NaN, Infinity 2 | 3 | Date: 2024-07-09 4 | 5 | Status: accepted 6 | 7 | ## Context 8 | 9 | In JSON parsing mode, numbers are simply numeric strings optionally preceded by a minus sign. In JSON5, however, numbers can be in hexadecimal, have a leading plus sign, or be `NaN` or `Infinity`. Because the AST is designed to be JSON-serializable, this prevents using `Number` AST nodes to represent `NaN` and `Infinity` because those values are not valid JSON values. 10 | 11 | ## Decision 12 | 13 | In JSON5 parsing mode: 14 | 15 | * all of the number values will be represented by the `Number` token. 16 | * only numeric values will be represented by the `Number` node. 17 | * `Infinity`, `-Infinity`, and `+Infinity` will be represented by an `Infinity` node. 18 | * `NaN`, `-NaN`, `+NaN`, will be represented by an `NaN` node. 19 | 20 | ## Consequences 21 | 22 | Inside of Momoa, we will need to watch for `Infinity` and `NaN` being used as identifiers (keys in object properties), in which case a `Number` token that is exactly equal to `Infinity` or `NaN` are allowed. 23 | 24 | For token consumers, this means they cannot assume that the substring representing the `Number` token is always numeric. 25 | 26 | For AST consumers, this means they will need to check for `Number`, `Infinity`, and `NaN` nodes in locations where numbers are allowed. 27 | -------------------------------------------------------------------------------- /rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | mod errors; 3 | mod location; 4 | mod mode; 5 | mod parse; 6 | mod readers; 7 | mod tokens; 8 | 9 | pub use errors::MomoaError; 10 | pub use location::{Location, LocationRange}; 11 | pub use mode::Mode; 12 | pub use tokens::{Token, TokenKind}; 13 | 14 | pub mod json { 15 | use crate::*; 16 | use parse::ParserOptions; 17 | 18 | pub fn tokenize(text: &str) -> Result, MomoaError> { 19 | tokens::tokenize(text, Mode::Json) 20 | } 21 | 22 | pub fn parse(text: &str) -> Result { 23 | parse::parse(text, Mode::Json, None) 24 | } 25 | 26 | pub fn parse_with_trailing_commas(text: &str) -> Result { 27 | parse::parse(text, Mode::Json, Some(ParserOptions { allow_trailing_commas: true })) 28 | } 29 | } 30 | 31 | pub mod jsonc { 32 | use crate::*; 33 | use parse::ParserOptions; 34 | 35 | pub fn tokenize(text: &str) -> Result, MomoaError> { 36 | tokens::tokenize(text, Mode::Jsonc) 37 | } 38 | 39 | pub fn parse(text: &str) -> Result { 40 | parse::parse(text, Mode::Jsonc, None) 41 | } 42 | 43 | pub fn parse_with_trailing_commas(text: &str) -> Result { 44 | parse::parse(text, Mode::Jsonc, Some(ParserOptions { allow_trailing_commas: true })) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-1.txt: -------------------------------------------------------------------------------- 1 | 1 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 1, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 1 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 2, 34 | "offset": 1 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 2, 49 | "offset": 1 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 1 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 1 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-6.txt: -------------------------------------------------------------------------------- 1 | 0 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 0, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 1 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 2, 34 | "offset": 1 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 2, 49 | "offset": 1 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 1 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 1 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-2.txt: -------------------------------------------------------------------------------- 1 | 1.5 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 1.5, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 4, 17 | "offset": 3 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 3 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 4, 34 | "offset": 3 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 4, 49 | "offset": 3 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 3 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 3 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-4.txt: -------------------------------------------------------------------------------- 1 | -0.1 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": -0.1, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 5, 17 | "offset": 4 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 4 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 5, 34 | "offset": 4 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 5, 49 | "offset": 4 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 4 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 4 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-5.txt: -------------------------------------------------------------------------------- 1 | 0.17 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 0.17, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 5, 17 | "offset": 4 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 4 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 5, 34 | "offset": 4 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 5, 49 | "offset": 4 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 4 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 4 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/string-1.txt: -------------------------------------------------------------------------------- 1 | "" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 3, 17 | "offset": 2 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 2 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 3, 34 | "offset": 2 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "String", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 3, 49 | "offset": 2 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 2 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 2 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/string-5.txt: -------------------------------------------------------------------------------- 1 | "/" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "/", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 4, 17 | "offset": 3 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 3 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 4, 34 | "offset": 3 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "String", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 4, 49 | "offset": 3 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 3 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 3 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/string-6.txt: -------------------------------------------------------------------------------- 1 | "/" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "/", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 4, 17 | "offset": 3 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 3 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 4, 34 | "offset": 3 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "String", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 4, 49 | "offset": 3 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 3 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 3 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/string-7.txt: -------------------------------------------------------------------------------- 1 | "\b" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "\b", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 5, 17 | "offset": 4 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 4 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 5, 34 | "offset": 4 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "String", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 5, 49 | "offset": 4 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 4 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 4 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-3.txt: -------------------------------------------------------------------------------- 1 | -1.52 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": -1.52, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 6, 17 | "offset": 5 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 5 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 6, 34 | "offset": 5 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 6, 49 | "offset": 5 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 5 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 5 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-7.txt: -------------------------------------------------------------------------------- 1 | 1e5 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 100000, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 4, 17 | "offset": 3 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 3 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 4, 34 | "offset": 3 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 4, 49 | "offset": 3 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 3 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 3 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-8.txt: -------------------------------------------------------------------------------- 1 | 21e-51 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 2.1e-50, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 7, 17 | "offset": 6 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 6 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 7, 34 | "offset": 6 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 7, 49 | "offset": 6 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 6 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 6 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-9.txt: -------------------------------------------------------------------------------- 1 | 4e+50 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 4e+50, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 6, 17 | "offset": 5 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 5 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 6, 34 | "offset": 5 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 6, 49 | "offset": 5 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 5 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 5 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-nan-1-json5.txt: -------------------------------------------------------------------------------- 1 | NaN 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "NaN", 7 | "sign": "", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 4, 17 | "offset": 3 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 3 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 4, 34 | "offset": 3 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 4, 49 | "offset": 3 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 3 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 3 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/string-2.txt: -------------------------------------------------------------------------------- 1 | "\u005C" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "\\", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 9, 17 | "offset": 8 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 8 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 9, 34 | "offset": 8 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "String", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 9, 49 | "offset": 8 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 8 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 8 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/string-3.txt: -------------------------------------------------------------------------------- 1 | "\u002F" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "/", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 9, 17 | "offset": 8 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 8 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 9, 34 | "offset": 8 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "String", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 9, 49 | "offset": 8 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 8 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 8 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/string-4.txt: -------------------------------------------------------------------------------- 1 | "\u002f" 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "/", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 9, 17 | "offset": 8 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 8 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 9, 34 | "offset": 8 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "String", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 9, 49 | "offset": 8 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 8 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 8 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/boolean-false.txt: -------------------------------------------------------------------------------- 1 | false 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Boolean", 7 | "value": false, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 6, 17 | "offset": 5 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 5 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 6, 34 | "offset": 5 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Boolean", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 6, 49 | "offset": 5 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 5 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 5 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/boolean-true.txt: -------------------------------------------------------------------------------- 1 | true 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Boolean", 7 | "value": true, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 5, 17 | "offset": 4 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 4 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 5, 34 | "offset": 4 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Boolean", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 5, 49 | "offset": 4 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 4 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 4 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-nan-2-plus-sign-json5.txt: -------------------------------------------------------------------------------- 1 | +NaN 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "NaN", 7 | "sign": "+", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 5, 17 | "offset": 4 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 4 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 5, 34 | "offset": 4 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 5, 49 | "offset": 4 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 4 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 4 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/string-8-single-quote-json5.txt: -------------------------------------------------------------------------------- 1 | '' 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 3, 17 | "offset": 2 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 2 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 3, 34 | "offset": 2 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "String", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 3, 49 | "offset": 2 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 2 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 2 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/string-single-quote-1-json5.txt: -------------------------------------------------------------------------------- 1 | '' 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 3, 17 | "offset": 2 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 2 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 3, 34 | "offset": 2 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "String", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 3, 49 | "offset": 2 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 2 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 2 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-infinity-1-json5.txt: -------------------------------------------------------------------------------- 1 | Infinity 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Infinity", 7 | "sign": "", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 9, 17 | "offset": 8 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 8 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 9, 34 | "offset": 8 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 9, 49 | "offset": 8 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 8 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 8 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-leading-plus-1-json5.txt: -------------------------------------------------------------------------------- 1 | +1.52 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Number", 7 | "value": 1.52, 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 6, 17 | "offset": 5 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 5 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 6, 34 | "offset": 5 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 6, 49 | "offset": 5 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 5 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 5 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-nan-3-minus-sign-json5.txt: -------------------------------------------------------------------------------- 1 | -NaN 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "NaN", 7 | "sign": "-", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 5, 17 | "offset": 4 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 4 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 5, 34 | "offset": 4 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 5, 49 | "offset": 4 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 4 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 4 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-infinity-2-plus-sign-json5.txt: -------------------------------------------------------------------------------- 1 | +Infinity 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Infinity", 7 | "sign": "+", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 10, 17 | "offset": 9 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 9 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 10, 34 | "offset": 9 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 10, 49 | "offset": 9 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 9 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 9 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/number-infinity-3-minus-sign-json5.txt: -------------------------------------------------------------------------------- 1 | -Infinity 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Infinity", 7 | "sign": "-", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 10, 17 | "offset": 9 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 9 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 10, 34 | "offset": 9 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "Number", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 10, 49 | "offset": 9 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 9 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 9 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/string-9-single-quote-with-double-quote-json5.txt: -------------------------------------------------------------------------------- 1 | '"' 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "\"", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 4, 17 | "offset": 3 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 3 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 4, 34 | "offset": 3 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "String", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 4, 49 | "offset": 3 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 3 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 3 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/string-11-multiline-json5.txt: -------------------------------------------------------------------------------- 1 | "Hello, \ 2 | world!" 3 | --- 4 | { 5 | "type": "Document", 6 | "body": { 7 | "type": "String", 8 | "value": "Hello, world!", 9 | "loc": { 10 | "start": { 11 | "line": 1, 12 | "column": 1, 13 | "offset": 0 14 | }, 15 | "end": { 16 | "line": 2, 17 | "column": 8, 18 | "offset": 17 19 | } 20 | }, 21 | "range": [ 22 | 0, 23 | 17 24 | ] 25 | }, 26 | "loc": { 27 | "start": { 28 | "line": 1, 29 | "column": 1, 30 | "offset": 0 31 | }, 32 | "end": { 33 | "line": 2, 34 | "column": 8, 35 | "offset": 17 36 | } 37 | }, 38 | "tokens": [ 39 | { 40 | "type": "String", 41 | "loc": { 42 | "start": { 43 | "line": 1, 44 | "column": 1, 45 | "offset": 0 46 | }, 47 | "end": { 48 | "line": 2, 49 | "column": 8, 50 | "offset": 17 51 | } 52 | }, 53 | "range": [ 54 | 0, 55 | 17 56 | ] 57 | } 58 | ], 59 | "range": [ 60 | 0, 61 | 17 62 | ] 63 | } -------------------------------------------------------------------------------- /js/tools/perf2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Performance tests 3 | * @author Nicholas C. Zakas 4 | */ 5 | 6 | //----------------------------------------------------------------------------- 7 | // Imports 8 | //----------------------------------------------------------------------------- 9 | 10 | import Benchmark from "benchmark"; 11 | import benchmarks from "beautify-benchmark"; 12 | import * as momoa_esm from "../dist/momoa.js"; 13 | import momoa_cjs from "../dist/momoa.cjs"; 14 | import fs from "fs"; 15 | 16 | //----------------------------------------------------------------------------- 17 | // Data 18 | //----------------------------------------------------------------------------- 19 | 20 | const vuePkgLock = fs.readFileSync("./tests/fixtures/big/vue-package-lock.json", "utf8"); 21 | 22 | //----------------------------------------------------------------------------- 23 | // Tests 24 | //----------------------------------------------------------------------------- 25 | 26 | const suite = new Benchmark.Suite(); 27 | 28 | // add tests 29 | suite 30 | .add("tokenize JS", () => { 31 | const result = momoa_cjs.tokenize(vuePkgLock); 32 | }) 33 | .add("tokenize WASM", () => { 34 | const result = momoa_esm.tokenize(vuePkgLock); 35 | }) 36 | .on("cycle", (event) => { 37 | benchmarks.add(event.target); 38 | }) 39 | .on("complete", function() { 40 | console.log('Fastest is ' + this.filter('fastest').map('name')); 41 | benchmarks.log(); 42 | }) 43 | .run({ 'async': true }); 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Momoa JSON 2 | 3 | by [Nicholas C. Zakas](https://humanwhocodes.com) 4 | 5 | If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate). 6 | 7 | ## About 8 | 9 | Momoa is a general purpose JSON utility toolkit for JavaScript and Rust. There are two different packages in this repository: 10 | 11 | * `js` - the JavaScript package 12 | * `rust` - the Rust crate 13 | 14 | These two packages are not directly linked but they do produce the same AST and so they are kept in sync using a monorepo. 15 | 16 | ## Development 17 | 18 | To work on Momoa, you'll need: 19 | 20 | * [Git](https://git-scm.com/) 21 | * [Node.js](https://nodejs.org) 22 | * [Rust](https://rustup.rs) 23 | 24 | Make sure all three are installed by visiting the links and following the instructions to install. 25 | 26 | Now you're ready to clone the repository: 27 | 28 | ```bash 29 | git clone https://github.com/humanwhocodes/momoa.git 30 | ``` 31 | 32 | Follow the instructions in the README in each directory for how to work on that package. 33 | 34 | ## Acknowledgements 35 | 36 | This project takes inspiration (but not code) from a number of other projects: 37 | 38 | * [`Esprima`](https://esprima.org) inspired the package interface and AST format. 39 | * [`json-to-ast`](https://github.com/vtrushin/json-to-ast) inspired the AST format. 40 | * [`parseJson.js`](https://gist.github.com/rgrove/5cc64db4b9ae8c946401b230ba9d2451) inspired me by showing writing a parser isn't all that hard. 41 | 42 | ## License 43 | 44 | Apache 2.0 45 | -------------------------------------------------------------------------------- /fixtures/asts-with-range/string-10-single-quote-escapes-json5.txt: -------------------------------------------------------------------------------- 1 | '\\b\\f\\n\\r\\t\\v\\0\\x0f\\u01fF\\\n\\\r\n\\\r\\\u2028\\\u2029\\a\\\'\\"' 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "String", 7 | "value": "\\b\\f\\n\\r\\t\\v\\0\\x0f\\u01fF\\\n\\\r\n\\\r\\
\\
\\a\\'\\\"", 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 76, 17 | "offset": 75 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 75 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 76, 34 | "offset": 75 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "String", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 76, 49 | "offset": 75 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 75 55 | ] 56 | } 57 | ], 58 | "range": [ 59 | 0, 60 | 75 61 | ] 62 | } -------------------------------------------------------------------------------- /fixtures/asts/array-2.txt: -------------------------------------------------------------------------------- 1 | [] 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Array", 7 | "elements": [], 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 3, 17 | "offset": 2 18 | } 19 | } 20 | }, 21 | "loc": { 22 | "start": { 23 | "line": 1, 24 | "column": 1, 25 | "offset": 0 26 | }, 27 | "end": { 28 | "line": 1, 29 | "column": 3, 30 | "offset": 2 31 | } 32 | }, 33 | "tokens": [ 34 | { 35 | "type": "LBracket", 36 | "loc": { 37 | "start": { 38 | "line": 1, 39 | "column": 1, 40 | "offset": 0 41 | }, 42 | "end": { 43 | "line": 1, 44 | "column": 2, 45 | "offset": 1 46 | } 47 | } 48 | }, 49 | { 50 | "type": "RBracket", 51 | "loc": { 52 | "start": { 53 | "line": 1, 54 | "column": 2, 55 | "offset": 1 56 | }, 57 | "end": { 58 | "line": 1, 59 | "column": 3, 60 | "offset": 2 61 | } 62 | } 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /js/tests/types/typedefs.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests of TypeScript types. 3 | * @author Nicholas C. Zakas 4 | */ 5 | 6 | import { 7 | parse, 8 | tokenize, 9 | print, 10 | traverse, 11 | iterator, 12 | evaluate, 13 | type Location, 14 | type LocationRange 15 | } from "@humanwhocodes/momoa"; 16 | 17 | parse("foo"); 18 | parse("foo", {}); 19 | parse("foo", { ranges: true }); 20 | parse("foo", { tokens: true }); 21 | parse("foo", { mode: "json" }); 22 | parse("foo", { mode: "jsonc" }); 23 | parse("foo", { mode: "json5" }); 24 | parse("foo", { ranges: true, tokens: true, allowTrailingCommas: true, mode: "jsonc" }); 25 | 26 | tokenize("foo"); 27 | tokenize("foo", {}); 28 | tokenize("foo", { ranges: true }); 29 | tokenize("foo", { mode: "json" }); 30 | tokenize("foo", { mode: "jsonc" }); 31 | tokenize("foo", { mode: "json5" }); 32 | tokenize("foo", { ranges: true, mode: "jsonc" }); 33 | 34 | const node = parse("foo"); 35 | print(node); 36 | print(node, {}); 37 | print(node, { indent: 2 }); 38 | 39 | const ast = parse("foo"); 40 | 41 | traverse(ast, { 42 | enter(node) { 43 | console.log("Entering node:", node.type); 44 | }, 45 | exit(node) { 46 | console.log("Leaving node:", node.type); 47 | } 48 | }); 49 | 50 | const iter = iterator(ast); 51 | 52 | for (const { node, phase, parent } of iter) { 53 | console.log("Iterating node:", node.type); 54 | console.log("Iterating phase:", phase); 55 | console.log("Iterating parent:", parent?.type); 56 | } 57 | 58 | evaluate(ast); 59 | 60 | ({}) as Location satisfies {line: number, column: number, offset: number}; 61 | ({}) as LocationRange satisfies {start: Location, end: Location}; 62 | -------------------------------------------------------------------------------- /rust/src/ast.rs: -------------------------------------------------------------------------------- 1 | use crate::location::*; 2 | use crate::tokens::Token; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 6 | #[serde(tag = "type")] 7 | pub enum Node { 8 | Document(Box), 9 | String(Box>), 10 | Number(Box>), 11 | Boolean(Box>), 12 | Null(Box), 13 | Array(Box), 14 | Object(Box), 15 | Member(Box), 16 | Element(Box>), 17 | } 18 | 19 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 20 | pub struct ValueNode { 21 | pub value: T, 22 | pub loc: LocationRange, 23 | } 24 | 25 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 26 | pub struct ObjectNode { 27 | pub members: Vec, 28 | pub loc: LocationRange, 29 | } 30 | 31 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 32 | pub struct ArrayNode { 33 | pub elements: Vec, 34 | pub loc: LocationRange, 35 | } 36 | 37 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 38 | pub struct MemberNode { 39 | pub name: Node, 40 | pub value: Node, 41 | pub loc: LocationRange, 42 | } 43 | 44 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 45 | pub struct ElementNode { 46 | pub value: Node, 47 | pub loc: LocationRange, 48 | } 49 | 50 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 51 | pub struct NullNode { 52 | pub loc: LocationRange, 53 | } 54 | 55 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 56 | pub struct DocumentNode { 57 | pub body: Node, 58 | pub loc: LocationRange, 59 | pub tokens: Vec, 60 | } 61 | -------------------------------------------------------------------------------- /js/tools/regenerate-test-data.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @fileoverview Tool to generate test data files 4 | * @author Nicholas C. Zakas 5 | */ 6 | 7 | //----------------------------------------------------------------------------- 8 | // Imports 9 | //----------------------------------------------------------------------------- 10 | 11 | import { parse } from "../src/index.js"; 12 | import fs from "node:fs"; 13 | import path from "node:path"; 14 | 15 | //----------------------------------------------------------------------------- 16 | // Main 17 | //----------------------------------------------------------------------------- 18 | 19 | const astsPath = "../fixtures/asts"; 20 | const astsWithRangePath = "../fixtures/asts-with-range"; 21 | 22 | fs.readdirSync(astsPath).forEach(fileName => { 23 | 24 | const filePath = path.join(astsPath, fileName); 25 | const contents = fs.readFileSync(filePath, "utf8").replace(/\r/g, ""); 26 | const separatorIndex = contents.indexOf("---"); 27 | 28 | // Note there is a \n before the separator, so chop it off 29 | const text = contents.slice(0, separatorIndex - 1); 30 | // const json = contents.slice(separatorIndex + 4).trim(); 31 | 32 | let mode = "json"; 33 | if (fileName.includes("jsonc")) { 34 | mode = "jsonc"; 35 | } else if (fileName.includes("json5")) { 36 | mode = "json5"; 37 | } 38 | 39 | const allowTrailingCommas = fileName.includes("trailing-comma"); 40 | 41 | // with ranges 42 | let result = parse(text, { mode, ranges: true, tokens: true, allowTrailingCommas }); 43 | fs.writeFileSync(path.join(astsWithRangePath, fileName), text + "\n---\n" + JSON.stringify(result, null, " "), "utf8"); 44 | 45 | // without ranges 46 | result = parse(text, { mode, tokens: true, allowTrailingCommas }); 47 | fs.writeFileSync(filePath, text + "\n---\n" + JSON.stringify(result, null, " "), "utf8"); 48 | }); 49 | -------------------------------------------------------------------------------- /fixtures/asts-with-range/array-2.txt: -------------------------------------------------------------------------------- 1 | [] 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Array", 7 | "elements": [], 8 | "loc": { 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 3, 17 | "offset": 2 18 | } 19 | }, 20 | "range": [ 21 | 0, 22 | 2 23 | ] 24 | }, 25 | "loc": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 3, 34 | "offset": 2 35 | } 36 | }, 37 | "tokens": [ 38 | { 39 | "type": "LBracket", 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 2, 49 | "offset": 1 50 | } 51 | }, 52 | "range": [ 53 | 0, 54 | 1 55 | ] 56 | }, 57 | { 58 | "type": "RBracket", 59 | "loc": { 60 | "start": { 61 | "line": 1, 62 | "column": 2, 63 | "offset": 1 64 | }, 65 | "end": { 66 | "line": 1, 67 | "column": 3, 68 | "offset": 2 69 | } 70 | }, 71 | "range": [ 72 | 1, 73 | 2 74 | ] 75 | } 76 | ], 77 | "range": [ 78 | 0, 79 | 2 80 | ] 81 | } -------------------------------------------------------------------------------- /rust/src/location.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | //----------------------------------------------------------------------------- 4 | // Location 5 | //----------------------------------------------------------------------------- 6 | 7 | /// Represents the line, column, and character offset in text. 8 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 9 | pub struct Location { 10 | pub line: usize, 11 | pub column: usize, 12 | pub offset: usize, 13 | } 14 | 15 | impl Location { 16 | pub(crate) fn new(line: usize, column: usize, offset: usize) -> Location { 17 | Location { 18 | line, 19 | column, 20 | offset, 21 | } 22 | } 23 | 24 | pub(crate) fn advance(&self, char_count: usize) -> Location { 25 | Location { 26 | line: self.line, 27 | column: self.column + char_count, 28 | offset: self.offset + char_count, 29 | } 30 | } 31 | 32 | pub(crate) fn advance_and_new_line(&self, char_count: usize) -> Location { 33 | Location { 34 | line: self.line + 1, 35 | column: 1, 36 | offset: self.offset + char_count, 37 | } 38 | } 39 | 40 | pub(crate) fn advance_new_line(&self) -> Location { 41 | Location { 42 | line: self.line + 1, 43 | column: 1, 44 | offset: self.offset + 1, 45 | } 46 | } 47 | } 48 | 49 | // impl fmt::Debug for Location { 50 | // fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 51 | // f.write_fmt(format_args!("({:?}:{:?})", self.line, self.column)) 52 | // } 53 | // } 54 | 55 | //----------------------------------------------------------------------------- 56 | // LocationRange 57 | //----------------------------------------------------------------------------- 58 | 59 | /// Represents the start and end location inside the text. 60 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 61 | pub struct LocationRange { 62 | pub start: Location, 63 | pub end: Location, 64 | } 65 | -------------------------------------------------------------------------------- /js/tools/perf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Performance tests 3 | * @author Nicholas C. Zakas 4 | */ 5 | 6 | //----------------------------------------------------------------------------- 7 | // Imports 8 | //----------------------------------------------------------------------------- 9 | 10 | import Benchmark from "benchmark"; 11 | import benchmarks from "beautify-benchmark"; 12 | import fs from "node:fs"; 13 | import * as momoa_esm from "../dist/momoa.js"; 14 | import momoa_cjs from "../dist/momoa.cjs"; 15 | import parse2 from "json-to-ast"; 16 | import parse3 from "./json-parse.cjs"; 17 | // const parse2 = require("json-to-ast"); 18 | // const parse3 = require("./json-parse.js"); 19 | 20 | //----------------------------------------------------------------------------- 21 | // Data 22 | //----------------------------------------------------------------------------- 23 | 24 | const vuePkgLock = fs.readFileSync("../fixtures/big/vue-package-lock.json", "utf8"); 25 | 26 | //----------------------------------------------------------------------------- 27 | // Tests 28 | //----------------------------------------------------------------------------- 29 | 30 | const suite = new Benchmark.Suite(); 31 | 32 | // add tests 33 | suite 34 | .add("Momoa JS", () => { 35 | momoa_cjs.parse(vuePkgLock); 36 | }) 37 | .add("Momoa WASM", () => { 38 | momoa_esm.parse(vuePkgLock); 39 | }) 40 | .add("json-to-ast", () => { 41 | parse2(vuePkgLock); 42 | }) 43 | .add("parseJson.js", () => { 44 | parse3(vuePkgLock); 45 | }) 46 | .on("cycle", (event) => { 47 | benchmarks.add(event.target); 48 | }) 49 | .on("complete", () => { 50 | benchmarks.log(); 51 | }) 52 | .run({ 'async': true }); 53 | 54 | 55 | // suite 56 | // .add("tokens()", () => { 57 | // const result = [...tokens(vuePkgLock)]; 58 | // }) 59 | // .add("tokenize()", () => { 60 | // const result = tokenize(vuePkgLock); 61 | // }) 62 | // .on("cycle", (event) => { 63 | // benchmarks.add(event.target); 64 | // }) 65 | // .on("complete", () => { 66 | // benchmarks.log(); 67 | // }) 68 | // .run({ 'async': true }); 69 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | 13 | node_lint: 14 | 15 | name: Node.js Linting 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Use Node.js LTS 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: lts/* 23 | - name: npm install, build, and test 24 | working-directory: js 25 | run: | 26 | npm install 27 | npm run lint 28 | env: 29 | CI: true 30 | 31 | node_test: 32 | 33 | name: Node.js CI Tests 34 | runs-on: ${{ matrix.os }} 35 | 36 | strategy: 37 | matrix: 38 | os: [windows-latest, macOS-latest, ubuntu-latest] 39 | node: [18.x, 20.x, 21.x, 22.x] 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | - name: Use Node.js ${{ matrix.node }} 44 | uses: actions/setup-node@v4 45 | with: 46 | node-version: ${{ matrix.node }} 47 | - name: npm install, build, and test 48 | working-directory: js 49 | run: | 50 | npm install 51 | npm run build --if-present 52 | npm test 53 | env: 54 | CI: true 55 | 56 | node_types_test: 57 | 58 | name: Node.js CI Tests 59 | runs-on: ubuntu-latest 60 | 61 | steps: 62 | - uses: actions/checkout@v4 63 | - name: Use Node.js 64 | uses: actions/setup-node@v4 65 | with: 66 | node-version: lts/* 67 | - name: npm install, build, and test 68 | working-directory: js 69 | run: | 70 | npm install 71 | npm run build --if-present 72 | npm run test:types 73 | npm run test:attw 74 | env: 75 | CI: true 76 | 77 | rust_test: 78 | 79 | name: Rust CI Tests 80 | runs-on: ubuntu-latest 81 | steps: 82 | - uses: actions/checkout@v4 83 | - name: Install Rust toolchain 84 | uses: actions-rs/toolchain@v1 85 | with: 86 | toolchain: stable 87 | 88 | - name: Test Rust 89 | run: cargo test 90 | working-directory: rust 91 | env: 92 | CI: true 93 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@humanwhocodes/momoa", 3 | "version": "3.3.10", 4 | "description": "JSON AST parser, tokenizer, printer, traverser.", 5 | "author": "Nicholas C. Zakas", 6 | "type": "module", 7 | "main": "dist/momoa.cjs", 8 | "module": "dist/momoa.js", 9 | "types": "dist/momoa.d.ts", 10 | "exports": { 11 | ".": { 12 | "require": { 13 | "types": "./dist/momoa.d.cts", 14 | "default": "./dist/momoa.cjs" 15 | }, 16 | "import": { 17 | "types": "./dist/momoa.d.ts", 18 | "default": "./dist/momoa.js" 19 | } 20 | } 21 | }, 22 | "files": [ 23 | "dist" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/humanwhocodes/momoa.git" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/humanwhocodes/momoa/issues" 31 | }, 32 | "homepage": "https://github.com/humanwhocodes/momoa#readme", 33 | "scripts": { 34 | "build": "rollup -c && npm run fixup && tsc -p tsconfig.build.json && npm run copy-dts && npm run build-dcts", 35 | "copy-dts": "node -e \"fs.copyFileSync('dist/momoa.d.ts', 'dist/momoa.d.cts')\"", 36 | "build-dcts": "node tools/update-cts-references.js", 37 | "fixup": "node tools/strip-typedef-aliases.js", 38 | "lint": "eslint *.js src/*.js tests/*.js", 39 | "perf": "npm run build && node tools/perf.js", 40 | "regen": "npm run build && node tools/regenerate-test-data.js", 41 | "prepare": "npm run build", 42 | "pretest": "npm run build", 43 | "test": "mocha tests/*.test.js && npm run test:types", 44 | "test:types": "tsc --noEmit --project tests/types/tsconfig.json", 45 | "test:attw": "attw --pack" 46 | }, 47 | "keywords": [ 48 | "json", 49 | "ast", 50 | "json tree", 51 | "abstract syntax tree" 52 | ], 53 | "license": "Apache-2.0", 54 | "engines": { 55 | "node": ">=18" 56 | }, 57 | "devDependencies": { 58 | "@arethetypeswrong/cli": "^0.17.4", 59 | "beautify-benchmark": "0.2.4", 60 | "benchmark": "2.1.4", 61 | "chai": "^4.3.7", 62 | "eslint": "8.57.1", 63 | "esm": "3.2.25", 64 | "json-to-ast": "2.1.0", 65 | "json5": "^2.2.3", 66 | "mocha": "^11.0.0", 67 | "npm-run-all2": "^7.0.0", 68 | "rollup": "^4.19.0", 69 | "rollup-plugin-copy": "^3.4.0", 70 | "rollup-plugin-dts": "^6.1.1", 71 | "sinon": "^19.0.0", 72 | "typescript": "^5.7.2" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.github/workflows/manual-publish.yml: -------------------------------------------------------------------------------- 1 | name: Manual JS Package Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: read 8 | id-token: write 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version: lts/* 19 | registry-url: "https://registry.npmjs.org" 20 | 21 | - name: Get latest release tag 22 | id: get-latest-release 23 | run: | 24 | LATEST_TAG=$(git tag -l "momoa-js*" --sort=-v:refname | head -n 1) 25 | echo "latest_tag=${LATEST_TAG}" >> $GITHUB_OUTPUT 26 | echo "Latest tag is $LATEST_TAG" 27 | 28 | - name: Check out latest release 29 | id: checkout-latest-release 30 | run: | 31 | echo 'Checking out release ${{ steps.get-latest-release.outputs.latest_tag }}' 32 | git checkout ${{ steps.get-latest-release.outputs.latest_tag }} 33 | 34 | - name: Install dependencies 35 | run: npm install 36 | working-directory: js 37 | 38 | - name: Build 39 | run: npm run build 40 | working-directory: js 41 | 42 | - name: Publish to npm 43 | run: npm publish --provenance 44 | working-directory: js 45 | env: 46 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 47 | 48 | # Generates the social media post 49 | - run: npx @humanwhocodes/social-changelog --org humanwhocodes --repo momoa --name Momoa --tag ${{ steps.get-latest-release.outputs.latest_tag }} > social-post.txt 50 | env: 51 | OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} 52 | 53 | # Tweets out release announcement 54 | - run: 'npx @humanwhocodes/crosspost -t -b -m -l --file social-post.txt' 55 | env: 56 | TWITTER_API_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} 57 | TWITTER_API_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} 58 | TWITTER_ACCESS_TOKEN_KEY: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }} 59 | TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} 60 | MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }} 61 | MASTODON_HOST: ${{ secrets.MASTODON_HOST }} 62 | BLUESKY_HOST: ${{ vars.BLUESKY_HOST }} 63 | BLUESKY_IDENTIFIER: ${{ vars.BLUESKY_IDENTIFIER }} 64 | BLUESKY_PASSWORD: ${{ secrets.BLUESKY_PASSWORD }} 65 | LINKEDIN_ACCESS_TOKEN: ${{ secrets.LINKEDIN_ACCESS_TOKEN }} 66 | -------------------------------------------------------------------------------- /js/src/char-codes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Character codes. 3 | * @author Nicholas C. Zakas 4 | */ 5 | 6 | export const CHAR_0 = 48; // 0 7 | export const CHAR_1 = 49; // 1 8 | export const CHAR_9 = 57; // 9 9 | export const CHAR_BACKSLASH = 92; // \ 10 | export const CHAR_DOLLAR = 36; // $ 11 | export const CHAR_DOT = 46; // . 12 | export const CHAR_DOUBLE_QUOTE = 34; // " 13 | export const CHAR_LOWER_A = 97; // a 14 | export const CHAR_LOWER_E = 101; // e 15 | export const CHAR_LOWER_F = 102; // f 16 | export const CHAR_LOWER_N = 110; // n 17 | export const CHAR_LOWER_T = 116; // t 18 | export const CHAR_LOWER_U = 117; // u 19 | export const CHAR_LOWER_X = 120; // x 20 | export const CHAR_LOWER_Z = 122; // z 21 | export const CHAR_MINUS = 45; // - 22 | export const CHAR_NEWLINE = 10; // newline 23 | export const CHAR_PLUS = 43; // + 24 | export const CHAR_RETURN = 13; // return 25 | export const CHAR_SINGLE_QUOTE = 39; // ' 26 | export const CHAR_SLASH = 47; // / 27 | export const CHAR_SPACE = 32; // space 28 | export const CHAR_TAB = 9; // tab 29 | export const CHAR_UNDERSCORE = 95; // _ 30 | export const CHAR_UPPER_A = 65; // A 31 | export const CHAR_UPPER_E = 69; // E 32 | export const CHAR_UPPER_F = 70; // F 33 | export const CHAR_UPPER_N = 78; // N 34 | export const CHAR_UPPER_X = 88; // X 35 | export const CHAR_UPPER_Z = 90; // Z 36 | export const CHAR_LOWER_B = 98; // b 37 | export const CHAR_LOWER_R = 114; // r 38 | export const CHAR_LOWER_V = 118; // v 39 | export const CHAR_LINE_SEPARATOR = 0x2028; 40 | export const CHAR_PARAGRAPH_SEPARATOR = 0x2029; 41 | export const CHAR_LOWER_L = 108; // l 42 | export const CHAR_LOWER_S = 115; // s 43 | export const CHAR_LOWER_C = 99; // c 44 | export const CHAR_UPPER_I = 73; // I 45 | export const CHAR_STAR = 42; // * 46 | export const CHAR_VTAB = 11; // U+000B Vertical tab 47 | export const CHAR_FORM_FEED = 12; // U+000C Form feed 48 | export const CHAR_NBSP = 160; // U+00A0 Non-breaking space 49 | export const CHAR_BOM = 65279; // U+FEFF 50 | export const CHAR_NON_BREAKING_SPACE = 160; 51 | export const CHAR_EN_QUAD = 8192; 52 | export const CHAR_EM_QUAD = 8193; 53 | export const CHAR_EN_SPACE = 8194; 54 | export const CHAR_EM_SPACE = 8195; 55 | export const CHAR_THREE_PER_EM_SPACE = 8196; 56 | export const CHAR_FOUR_PER_EM_SPACE = 8197; 57 | export const CHAR_SIX_PER_EM_SPACE = 8198; 58 | export const CHAR_FIGURE_SPACE = 8199; 59 | export const CHAR_PUNCTUATION_SPACE = 8200; 60 | export const CHAR_THIN_SPACE = 8201; 61 | export const CHAR_HAIR_SPACE = 8202; 62 | export const CHAR_NARROW_NO_BREAK_SPACE = 8239; 63 | export const CHAR_MEDIUM_MATHEMATICAL_SPACE = 8287; 64 | export const CHAR_IDEOGRAPHIC_SPACE = 12288; 65 | -------------------------------------------------------------------------------- /js/src/evaluate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Evaluator for Momoa AST. 3 | * @author Nicholas C. Zakas 4 | */ 5 | 6 | //----------------------------------------------------------------------------- 7 | // Typedefs 8 | //----------------------------------------------------------------------------- 9 | 10 | /** @typedef {import("./typedefs.js").Node} Node */ 11 | /** @typedef {import("./typedefs.js").NodeParts} NodeParts */ 12 | /** @typedef {import("./typedefs.js").DocumentNode} DocumentNode */ 13 | /** @typedef {import("./typedefs.js").StringNode} StringNode */ 14 | /** @typedef {import("./typedefs.js").NumberNode} NumberNode */ 15 | /** @typedef {import("./typedefs.js").BooleanNode} BooleanNode */ 16 | /** @typedef {import("./typedefs.js").MemberNode} MemberNode */ 17 | /** @typedef {import("./typedefs.js").ObjectNode} ObjectNode */ 18 | /** @typedef {import("./typedefs.js").ElementNode} ElementNode */ 19 | /** @typedef {import("./typedefs.js").ArrayNode} ArrayNode */ 20 | /** @typedef {import("./typedefs.js").NullNode} NullNode */ 21 | /** @typedef {import("./typedefs.js").AnyNode} AnyNode */ 22 | /** @typedef {import("./typedefs.js").JSONValue} JSONValue */ 23 | 24 | //----------------------------------------------------------------------------- 25 | // Exports 26 | //----------------------------------------------------------------------------- 27 | 28 | /** 29 | * Evaluates a Momoa AST node into a JavaScript value. 30 | * @param {AnyNode} node The node to interpet. 31 | * @returns {JSONValue} The JavaScript value for the node. 32 | */ 33 | export function evaluate(node) { 34 | switch (node.type) { 35 | case "String": 36 | return node.value; 37 | 38 | case "Number": 39 | return node.value; 40 | 41 | case "Boolean": 42 | return node.value; 43 | 44 | case "Null": 45 | return null; 46 | 47 | case "NaN": 48 | return NaN; 49 | 50 | case "Infinity": 51 | return node.sign === "-" ? -Infinity : Infinity; 52 | 53 | case "Identifier": 54 | return node.name; 55 | 56 | case "Array": { 57 | // const arrayNode = /** @type {ArrayNode} */ (node); 58 | return node.elements.map(element => evaluate(element.value)); 59 | } 60 | 61 | case "Object": { 62 | 63 | /** @type {{[property: string]: JSONValue}} */ 64 | const object = {}; 65 | 66 | node.members.forEach(member => { 67 | object[/** @type {string} */ (evaluate(member.name))] = evaluate(member.value); 68 | }); 69 | 70 | return object; 71 | } 72 | 73 | case "Document": { 74 | return evaluate(node.body); 75 | } 76 | 77 | case "Element": 78 | throw new Error("Cannot evaluate array element outside of an array."); 79 | 80 | case "Member": 81 | throw new Error("Cannot evaluate object member outside of an object."); 82 | 83 | default: 84 | // @ts-ignore tsc doesn't know about the type property here? 85 | throw new Error(`Unknown node type ${ node.type }.`); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /fixtures/asts/array-3.txt: -------------------------------------------------------------------------------- 1 | [1] 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Array", 7 | "elements": [ 8 | { 9 | "type": "Element", 10 | "value": { 11 | "type": "Number", 12 | "value": 1, 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 3, 22 | "offset": 2 23 | } 24 | } 25 | }, 26 | "loc": { 27 | "start": { 28 | "line": 1, 29 | "column": 2, 30 | "offset": 1 31 | }, 32 | "end": { 33 | "line": 1, 34 | "column": 3, 35 | "offset": 2 36 | } 37 | } 38 | } 39 | ], 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 4, 49 | "offset": 3 50 | } 51 | } 52 | }, 53 | "loc": { 54 | "start": { 55 | "line": 1, 56 | "column": 1, 57 | "offset": 0 58 | }, 59 | "end": { 60 | "line": 1, 61 | "column": 4, 62 | "offset": 3 63 | } 64 | }, 65 | "tokens": [ 66 | { 67 | "type": "LBracket", 68 | "loc": { 69 | "start": { 70 | "line": 1, 71 | "column": 1, 72 | "offset": 0 73 | }, 74 | "end": { 75 | "line": 1, 76 | "column": 2, 77 | "offset": 1 78 | } 79 | } 80 | }, 81 | { 82 | "type": "Number", 83 | "loc": { 84 | "start": { 85 | "line": 1, 86 | "column": 2, 87 | "offset": 1 88 | }, 89 | "end": { 90 | "line": 1, 91 | "column": 3, 92 | "offset": 2 93 | } 94 | } 95 | }, 96 | { 97 | "type": "RBracket", 98 | "loc": { 99 | "start": { 100 | "line": 1, 101 | "column": 3, 102 | "offset": 2 103 | }, 104 | "end": { 105 | "line": 1, 106 | "column": 4, 107 | "offset": 3 108 | } 109 | } 110 | } 111 | ] 112 | } -------------------------------------------------------------------------------- /fixtures/asts/array-5.txt: -------------------------------------------------------------------------------- 1 | [ true ] 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Array", 7 | "elements": [ 8 | { 9 | "type": "Element", 10 | "value": { 11 | "type": "Boolean", 12 | "value": true, 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 3, 17 | "offset": 2 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 7, 22 | "offset": 6 23 | } 24 | } 25 | }, 26 | "loc": { 27 | "start": { 28 | "line": 1, 29 | "column": 3, 30 | "offset": 2 31 | }, 32 | "end": { 33 | "line": 1, 34 | "column": 7, 35 | "offset": 6 36 | } 37 | } 38 | } 39 | ], 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 9, 49 | "offset": 8 50 | } 51 | } 52 | }, 53 | "loc": { 54 | "start": { 55 | "line": 1, 56 | "column": 1, 57 | "offset": 0 58 | }, 59 | "end": { 60 | "line": 1, 61 | "column": 9, 62 | "offset": 8 63 | } 64 | }, 65 | "tokens": [ 66 | { 67 | "type": "LBracket", 68 | "loc": { 69 | "start": { 70 | "line": 1, 71 | "column": 1, 72 | "offset": 0 73 | }, 74 | "end": { 75 | "line": 1, 76 | "column": 2, 77 | "offset": 1 78 | } 79 | } 80 | }, 81 | { 82 | "type": "Boolean", 83 | "loc": { 84 | "start": { 85 | "line": 1, 86 | "column": 3, 87 | "offset": 2 88 | }, 89 | "end": { 90 | "line": 1, 91 | "column": 7, 92 | "offset": 6 93 | } 94 | } 95 | }, 96 | { 97 | "type": "RBracket", 98 | "loc": { 99 | "start": { 100 | "line": 1, 101 | "column": 8, 102 | "offset": 7 103 | }, 104 | "end": { 105 | "line": 1, 106 | "column": 9, 107 | "offset": 8 108 | } 109 | } 110 | } 111 | ] 112 | } -------------------------------------------------------------------------------- /fixtures/asts/array-6.txt: -------------------------------------------------------------------------------- 1 | [ "foo "] 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Array", 7 | "elements": [ 8 | { 9 | "type": "Element", 10 | "value": { 11 | "type": "String", 12 | "value": "foo ", 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 3, 17 | "offset": 2 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 9, 22 | "offset": 8 23 | } 24 | } 25 | }, 26 | "loc": { 27 | "start": { 28 | "line": 1, 29 | "column": 3, 30 | "offset": 2 31 | }, 32 | "end": { 33 | "line": 1, 34 | "column": 9, 35 | "offset": 8 36 | } 37 | } 38 | } 39 | ], 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 10, 49 | "offset": 9 50 | } 51 | } 52 | }, 53 | "loc": { 54 | "start": { 55 | "line": 1, 56 | "column": 1, 57 | "offset": 0 58 | }, 59 | "end": { 60 | "line": 1, 61 | "column": 10, 62 | "offset": 9 63 | } 64 | }, 65 | "tokens": [ 66 | { 67 | "type": "LBracket", 68 | "loc": { 69 | "start": { 70 | "line": 1, 71 | "column": 1, 72 | "offset": 0 73 | }, 74 | "end": { 75 | "line": 1, 76 | "column": 2, 77 | "offset": 1 78 | } 79 | } 80 | }, 81 | { 82 | "type": "String", 83 | "loc": { 84 | "start": { 85 | "line": 1, 86 | "column": 3, 87 | "offset": 2 88 | }, 89 | "end": { 90 | "line": 1, 91 | "column": 9, 92 | "offset": 8 93 | } 94 | } 95 | }, 96 | { 97 | "type": "RBracket", 98 | "loc": { 99 | "start": { 100 | "line": 1, 101 | "column": 9, 102 | "offset": 8 103 | }, 104 | "end": { 105 | "line": 1, 106 | "column": 10, 107 | "offset": 9 108 | } 109 | } 110 | } 111 | ] 112 | } -------------------------------------------------------------------------------- /js/src/errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview JSON tokenization/parsing errors 3 | * @author Nicholas C. Zakas 4 | */ 5 | 6 | //----------------------------------------------------------------------------- 7 | // Typedefs 8 | //----------------------------------------------------------------------------- 9 | 10 | /** @typedef {import("./typedefs.js").Location} Location */ 11 | /** @typedef {import("./typedefs.js").Token} Token */ 12 | 13 | //----------------------------------------------------------------------------- 14 | // Errors 15 | //----------------------------------------------------------------------------- 16 | 17 | /** 18 | * Base class that attaches location to an error. 19 | */ 20 | export class ErrorWithLocation extends Error { 21 | 22 | /** 23 | * Creates a new instance. 24 | * @param {string} message The error message to report. 25 | * @param {Location} loc The location information for the error. 26 | */ 27 | constructor(message, { line, column, offset }) { 28 | super(`${ message } (${ line }:${ column})`); 29 | 30 | /** 31 | * The line on which the error occurred. 32 | * @type {number} 33 | */ 34 | this.line = line; 35 | 36 | /** 37 | * The column on which the error occurred. 38 | * @type {number} 39 | */ 40 | this.column = column; 41 | 42 | /** 43 | * The index into the string where the error occurred. 44 | * @type {number} 45 | */ 46 | this.offset = offset; 47 | } 48 | 49 | } 50 | 51 | /** 52 | * Error thrown when an unexpected character is found during tokenizing. 53 | */ 54 | export class UnexpectedChar extends ErrorWithLocation { 55 | 56 | /** 57 | * Creates a new instance. 58 | * @param {number} unexpected The character that was found. 59 | * @param {Location} loc The location information for the found character. 60 | */ 61 | constructor(unexpected, loc) { 62 | super(`Unexpected character '${ String.fromCharCode(unexpected) }' found.`, loc); 63 | } 64 | } 65 | 66 | /** 67 | * Error thrown when an unexpected identifier is found during tokenizing. 68 | */ 69 | export class UnexpectedIdentifier extends ErrorWithLocation { 70 | 71 | /** 72 | * Creates a new instance. 73 | * @param {string} unexpected The character that was found. 74 | * @param {Location} loc The location information for the found character. 75 | */ 76 | constructor(unexpected, loc) { 77 | super(`Unexpected identifier '${ unexpected }' found.`, loc); 78 | } 79 | } 80 | 81 | /** 82 | * Error thrown when an unexpected token is found during parsing. 83 | */ 84 | export class UnexpectedToken extends ErrorWithLocation { 85 | 86 | /** 87 | * Creates a new instance. 88 | * @param {Token} token The token that was found. 89 | */ 90 | constructor(token) { 91 | super(`Unexpected token ${ token.type } found.`, token.loc.start); 92 | } 93 | } 94 | 95 | /** 96 | * Error thrown when the end of input is found where it isn't expected. 97 | */ 98 | export class UnexpectedEOF extends ErrorWithLocation { 99 | 100 | /** 101 | * Creates a new instance. 102 | * @param {Location} loc The location information for the found character. 103 | */ 104 | constructor(loc) { 105 | super("Unexpected end of input found.", loc); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /fixtures/asts/array-7.txt: -------------------------------------------------------------------------------- 1 | [{}] 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Array", 7 | "elements": [ 8 | { 9 | "type": "Element", 10 | "value": { 11 | "type": "Object", 12 | "members": [], 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 4, 22 | "offset": 3 23 | } 24 | } 25 | }, 26 | "loc": { 27 | "start": { 28 | "line": 1, 29 | "column": 2, 30 | "offset": 1 31 | }, 32 | "end": { 33 | "line": 1, 34 | "column": 4, 35 | "offset": 3 36 | } 37 | } 38 | } 39 | ], 40 | "loc": { 41 | "start": { 42 | "line": 1, 43 | "column": 1, 44 | "offset": 0 45 | }, 46 | "end": { 47 | "line": 1, 48 | "column": 5, 49 | "offset": 4 50 | } 51 | } 52 | }, 53 | "loc": { 54 | "start": { 55 | "line": 1, 56 | "column": 1, 57 | "offset": 0 58 | }, 59 | "end": { 60 | "line": 1, 61 | "column": 5, 62 | "offset": 4 63 | } 64 | }, 65 | "tokens": [ 66 | { 67 | "type": "LBracket", 68 | "loc": { 69 | "start": { 70 | "line": 1, 71 | "column": 1, 72 | "offset": 0 73 | }, 74 | "end": { 75 | "line": 1, 76 | "column": 2, 77 | "offset": 1 78 | } 79 | } 80 | }, 81 | { 82 | "type": "LBrace", 83 | "loc": { 84 | "start": { 85 | "line": 1, 86 | "column": 2, 87 | "offset": 1 88 | }, 89 | "end": { 90 | "line": 1, 91 | "column": 3, 92 | "offset": 2 93 | } 94 | } 95 | }, 96 | { 97 | "type": "RBrace", 98 | "loc": { 99 | "start": { 100 | "line": 1, 101 | "column": 3, 102 | "offset": 2 103 | }, 104 | "end": { 105 | "line": 1, 106 | "column": 4, 107 | "offset": 3 108 | } 109 | } 110 | }, 111 | { 112 | "type": "RBracket", 113 | "loc": { 114 | "start": { 115 | "line": 1, 116 | "column": 4, 117 | "offset": 3 118 | }, 119 | "end": { 120 | "line": 1, 121 | "column": 5, 122 | "offset": 4 123 | } 124 | } 125 | } 126 | ] 127 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/array-3.txt: -------------------------------------------------------------------------------- 1 | [1] 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Array", 7 | "elements": [ 8 | { 9 | "type": "Element", 10 | "value": { 11 | "type": "Number", 12 | "value": 1, 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 3, 22 | "offset": 2 23 | } 24 | }, 25 | "range": [ 26 | 1, 27 | 2 28 | ] 29 | }, 30 | "loc": { 31 | "start": { 32 | "line": 1, 33 | "column": 2, 34 | "offset": 1 35 | }, 36 | "end": { 37 | "line": 1, 38 | "column": 3, 39 | "offset": 2 40 | } 41 | } 42 | } 43 | ], 44 | "loc": { 45 | "start": { 46 | "line": 1, 47 | "column": 1, 48 | "offset": 0 49 | }, 50 | "end": { 51 | "line": 1, 52 | "column": 4, 53 | "offset": 3 54 | } 55 | }, 56 | "range": [ 57 | 0, 58 | 3 59 | ] 60 | }, 61 | "loc": { 62 | "start": { 63 | "line": 1, 64 | "column": 1, 65 | "offset": 0 66 | }, 67 | "end": { 68 | "line": 1, 69 | "column": 4, 70 | "offset": 3 71 | } 72 | }, 73 | "tokens": [ 74 | { 75 | "type": "LBracket", 76 | "loc": { 77 | "start": { 78 | "line": 1, 79 | "column": 1, 80 | "offset": 0 81 | }, 82 | "end": { 83 | "line": 1, 84 | "column": 2, 85 | "offset": 1 86 | } 87 | }, 88 | "range": [ 89 | 0, 90 | 1 91 | ] 92 | }, 93 | { 94 | "type": "Number", 95 | "loc": { 96 | "start": { 97 | "line": 1, 98 | "column": 2, 99 | "offset": 1 100 | }, 101 | "end": { 102 | "line": 1, 103 | "column": 3, 104 | "offset": 2 105 | } 106 | }, 107 | "range": [ 108 | 1, 109 | 2 110 | ] 111 | }, 112 | { 113 | "type": "RBracket", 114 | "loc": { 115 | "start": { 116 | "line": 1, 117 | "column": 3, 118 | "offset": 2 119 | }, 120 | "end": { 121 | "line": 1, 122 | "column": 4, 123 | "offset": 3 124 | } 125 | }, 126 | "range": [ 127 | 2, 128 | 3 129 | ] 130 | } 131 | ], 132 | "range": [ 133 | 0, 134 | 3 135 | ] 136 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/array-5.txt: -------------------------------------------------------------------------------- 1 | [ true ] 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Array", 7 | "elements": [ 8 | { 9 | "type": "Element", 10 | "value": { 11 | "type": "Boolean", 12 | "value": true, 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 3, 17 | "offset": 2 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 7, 22 | "offset": 6 23 | } 24 | }, 25 | "range": [ 26 | 2, 27 | 6 28 | ] 29 | }, 30 | "loc": { 31 | "start": { 32 | "line": 1, 33 | "column": 3, 34 | "offset": 2 35 | }, 36 | "end": { 37 | "line": 1, 38 | "column": 7, 39 | "offset": 6 40 | } 41 | } 42 | } 43 | ], 44 | "loc": { 45 | "start": { 46 | "line": 1, 47 | "column": 1, 48 | "offset": 0 49 | }, 50 | "end": { 51 | "line": 1, 52 | "column": 9, 53 | "offset": 8 54 | } 55 | }, 56 | "range": [ 57 | 0, 58 | 8 59 | ] 60 | }, 61 | "loc": { 62 | "start": { 63 | "line": 1, 64 | "column": 1, 65 | "offset": 0 66 | }, 67 | "end": { 68 | "line": 1, 69 | "column": 9, 70 | "offset": 8 71 | } 72 | }, 73 | "tokens": [ 74 | { 75 | "type": "LBracket", 76 | "loc": { 77 | "start": { 78 | "line": 1, 79 | "column": 1, 80 | "offset": 0 81 | }, 82 | "end": { 83 | "line": 1, 84 | "column": 2, 85 | "offset": 1 86 | } 87 | }, 88 | "range": [ 89 | 0, 90 | 1 91 | ] 92 | }, 93 | { 94 | "type": "Boolean", 95 | "loc": { 96 | "start": { 97 | "line": 1, 98 | "column": 3, 99 | "offset": 2 100 | }, 101 | "end": { 102 | "line": 1, 103 | "column": 7, 104 | "offset": 6 105 | } 106 | }, 107 | "range": [ 108 | 2, 109 | 6 110 | ] 111 | }, 112 | { 113 | "type": "RBracket", 114 | "loc": { 115 | "start": { 116 | "line": 1, 117 | "column": 8, 118 | "offset": 7 119 | }, 120 | "end": { 121 | "line": 1, 122 | "column": 9, 123 | "offset": 8 124 | } 125 | }, 126 | "range": [ 127 | 7, 128 | 8 129 | ] 130 | } 131 | ], 132 | "range": [ 133 | 0, 134 | 8 135 | ] 136 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/array-6.txt: -------------------------------------------------------------------------------- 1 | [ "foo "] 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Array", 7 | "elements": [ 8 | { 9 | "type": "Element", 10 | "value": { 11 | "type": "String", 12 | "value": "foo ", 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 3, 17 | "offset": 2 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 9, 22 | "offset": 8 23 | } 24 | }, 25 | "range": [ 26 | 2, 27 | 8 28 | ] 29 | }, 30 | "loc": { 31 | "start": { 32 | "line": 1, 33 | "column": 3, 34 | "offset": 2 35 | }, 36 | "end": { 37 | "line": 1, 38 | "column": 9, 39 | "offset": 8 40 | } 41 | } 42 | } 43 | ], 44 | "loc": { 45 | "start": { 46 | "line": 1, 47 | "column": 1, 48 | "offset": 0 49 | }, 50 | "end": { 51 | "line": 1, 52 | "column": 10, 53 | "offset": 9 54 | } 55 | }, 56 | "range": [ 57 | 0, 58 | 9 59 | ] 60 | }, 61 | "loc": { 62 | "start": { 63 | "line": 1, 64 | "column": 1, 65 | "offset": 0 66 | }, 67 | "end": { 68 | "line": 1, 69 | "column": 10, 70 | "offset": 9 71 | } 72 | }, 73 | "tokens": [ 74 | { 75 | "type": "LBracket", 76 | "loc": { 77 | "start": { 78 | "line": 1, 79 | "column": 1, 80 | "offset": 0 81 | }, 82 | "end": { 83 | "line": 1, 84 | "column": 2, 85 | "offset": 1 86 | } 87 | }, 88 | "range": [ 89 | 0, 90 | 1 91 | ] 92 | }, 93 | { 94 | "type": "String", 95 | "loc": { 96 | "start": { 97 | "line": 1, 98 | "column": 3, 99 | "offset": 2 100 | }, 101 | "end": { 102 | "line": 1, 103 | "column": 9, 104 | "offset": 8 105 | } 106 | }, 107 | "range": [ 108 | 2, 109 | 8 110 | ] 111 | }, 112 | { 113 | "type": "RBracket", 114 | "loc": { 115 | "start": { 116 | "line": 1, 117 | "column": 9, 118 | "offset": 8 119 | }, 120 | "end": { 121 | "line": 1, 122 | "column": 10, 123 | "offset": 9 124 | } 125 | }, 126 | "range": [ 127 | 8, 128 | 9 129 | ] 130 | } 131 | ], 132 | "range": [ 133 | 0, 134 | 9 135 | ] 136 | } -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | name: release-please 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | id-token: write 12 | 13 | jobs: 14 | release-please: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: googleapis/release-please-action@e5c2aa4f6acd6194e00632c8f262cff67ae17873 18 | id: release 19 | with: 20 | token: ${{secrets.GITHUB_TOKEN}} 21 | 22 | # Output which releases were created 23 | - name: Output release info 24 | run: | 25 | echo "releases_created:" ${{ steps.release.outputs.releases_created }} 26 | echo "js--release_created:" ${{ steps.release.outputs['js--release_created'] }} 27 | echo "momoa-js--release_created:" ${{ steps.release.outputs['momoa-js--release_created'] }} 28 | echo "rust--release_created:" ${{ steps.release.outputs['rust--release_created'] }} 29 | echo "momoa-rs--release_created:" ${{ steps.release.outputs['momoa-rs--release_created'] }} 30 | 31 | - run: echo "A JavaScript release was created." 32 | if: ${{ steps.release.outputs['js--release_created'] }} 33 | 34 | - run: echo "A Rust release was created." 35 | if: ${{ steps.release.outputs['rust--release_created'] }} 36 | 37 | # Check to see if we need to do any releases and if so check out the repo 38 | - uses: actions/checkout@v4 39 | if: ${{ steps.release.outputs.releases_created }} 40 | 41 | # Node.js release 42 | - uses: actions/setup-node@v4 43 | if: ${{ steps.release.outputs['js--release_created'] }} 44 | with: 45 | node-version: 20 46 | registry-url: 'https://registry.npmjs.org' 47 | - run: npm ci 48 | if: ${{ steps.release.outputs['js--release_created'] }} 49 | working-directory: js 50 | - run: npm publish --provenance 51 | if: ${{ steps.release.outputs['js--release_created'] }} 52 | env: 53 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 54 | working-directory: js 55 | 56 | # Rust release 57 | - uses: actions-rs/toolchain@v1 58 | if: ${{ steps.release.outputs['rust--release_created'] }} 59 | with: 60 | profile: minimal 61 | toolchain: stable 62 | override: true 63 | - run: "cargo publish --token $CARGO_TOKEN" 64 | if: ${{ steps.release.outputs['rust--release_created'] }} 65 | working-directory: rust 66 | env: 67 | CARGO_TOKEN: ${{secrets.CARGO_TOKEN}} 68 | 69 | # Generates the social media post 70 | - run: npx @humanwhocodes/social-changelog --org humanwhocodes --repo momoa --name Momoa --tag ${{ steps.release.outputs['js--tag_name'] }} > social-post.txt 71 | if: ${{ steps.release.outputs.release_created }} 72 | env: 73 | OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} 74 | 75 | # Tweets out release announcement 76 | - run: 'npx @humanwhocodes/crosspost -t -b -m -l --file social-post.txt' 77 | if: ${{ steps.release.outputs.release_created }} 78 | env: 79 | TWITTER_API_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} 80 | TWITTER_API_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} 81 | TWITTER_ACCESS_TOKEN_KEY: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }} 82 | TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} 83 | MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }} 84 | MASTODON_HOST: ${{ secrets.MASTODON_HOST }} 85 | BLUESKY_HOST: ${{ vars.BLUESKY_HOST }} 86 | BLUESKY_IDENTIFIER: ${{ vars.BLUESKY_IDENTIFIER }} 87 | BLUESKY_PASSWORD: ${{ secrets.BLUESKY_PASSWORD }} 88 | LINKEDIN_ACCESS_TOKEN: ${{ secrets.LINKEDIN_ACCESS_TOKEN }} 89 | 90 | 91 | # No tweets for Rust for now 92 | -------------------------------------------------------------------------------- /js/src/syntax.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview JSON syntax helpers 3 | * @author Nicholas C. Zakas 4 | */ 5 | 6 | //----------------------------------------------------------------------------- 7 | // Imports 8 | //----------------------------------------------------------------------------- 9 | 10 | import * as charCodes from "./char-codes.js"; 11 | 12 | //----------------------------------------------------------------------------- 13 | // Types 14 | //----------------------------------------------------------------------------- 15 | 16 | /** @typedef {import("./typedefs.js").TokenType} TokenType */ 17 | 18 | //----------------------------------------------------------------------------- 19 | // Predefined Tokens 20 | //----------------------------------------------------------------------------- 21 | 22 | const LBRACKET = "["; 23 | const RBRACKET = "]"; 24 | const LBRACE = "{"; 25 | const RBRACE = "}"; 26 | const COLON = ":"; 27 | const COMMA = ","; 28 | 29 | const TRUE = "true"; 30 | const FALSE = "false"; 31 | const NULL = "null"; 32 | const NAN = "NaN"; 33 | const INFINITY = "Infinity"; 34 | const QUOTE = "\""; 35 | 36 | 37 | //----------------------------------------------------------------------------- 38 | // Token Collections 39 | //----------------------------------------------------------------------------- 40 | 41 | export const keywords = [ 42 | TRUE, 43 | FALSE, 44 | NULL 45 | ]; 46 | 47 | export const json5Keywords = [ 48 | "Infinity", 49 | "NaN", 50 | "undefined", 51 | ...keywords 52 | ]; 53 | 54 | export const json5HexDigits = new Set([ 55 | "0", 56 | "1", 57 | "2", 58 | "3", 59 | "4", 60 | "5", 61 | "6", 62 | "7", 63 | "8", 64 | "9", 65 | "a", 66 | "b", 67 | "c", 68 | "d", 69 | "e", 70 | "f", 71 | "A", 72 | "B", 73 | "C", 74 | "D", 75 | "E", 76 | "F" 77 | ]); 78 | 79 | export const expectedKeywords = new Map([ 80 | [charCodes.CHAR_LOWER_T, [charCodes.CHAR_LOWER_R, charCodes.CHAR_LOWER_U, charCodes.CHAR_LOWER_E]], 81 | [charCodes.CHAR_LOWER_F, [charCodes.CHAR_LOWER_A, charCodes.CHAR_LOWER_L, charCodes.CHAR_LOWER_S, charCodes.CHAR_LOWER_E]], 82 | [charCodes.CHAR_LOWER_N, [charCodes.CHAR_LOWER_U, charCodes.CHAR_LOWER_L, charCodes.CHAR_LOWER_L]] 83 | ]); 84 | 85 | export const escapeToChar = new Map([ 86 | [charCodes.CHAR_DOUBLE_QUOTE, QUOTE], 87 | [charCodes.CHAR_BACKSLASH, "\\"], 88 | [charCodes.CHAR_SLASH, "/"], 89 | [charCodes.CHAR_LOWER_B, "\b"], 90 | [charCodes.CHAR_LOWER_N, "\n"], 91 | [charCodes.CHAR_LOWER_F, "\f"], 92 | [charCodes.CHAR_LOWER_R, "\r"], 93 | [charCodes.CHAR_LOWER_T, "\t"] 94 | ]); 95 | 96 | export const json5EscapeToChar = new Map([ 97 | ...escapeToChar, 98 | [charCodes.CHAR_LOWER_V, "\v"], 99 | [charCodes.CHAR_0, "\0"] 100 | ]); 101 | 102 | export const charToEscape = new Map([ 103 | [QUOTE, QUOTE], 104 | ["\\", "\\"], 105 | ["/", "/"], 106 | ["\b", "b"], 107 | ["\n", "n"], 108 | ["\f", "f"], 109 | ["\r", "r"], 110 | ["\t", "t"] 111 | ]); 112 | 113 | export const json5CharToEscape = new Map([ 114 | ...charToEscape, 115 | ["\v", "v"], 116 | ["\0", "0"], 117 | ["\u2028", "u2028"], 118 | ["\u2029", "u2029"] 119 | ]); 120 | 121 | /** @type {Map} */ 122 | export const knownTokenTypes = new Map([ 123 | [LBRACKET, "LBracket"], 124 | [RBRACKET, "RBracket"], 125 | [LBRACE, "LBrace"], 126 | [RBRACE, "RBrace"], 127 | [COLON, "Colon"], 128 | [COMMA, "Comma"], 129 | [TRUE, "Boolean"], 130 | [FALSE, "Boolean"], 131 | [NULL, "Null"] 132 | ]); 133 | 134 | /** @type {Map} */ 135 | export const knownJSON5TokenTypes = new Map([ 136 | ...knownTokenTypes, 137 | [NAN, "Number"], 138 | [INFINITY, "Number"] 139 | ]); 140 | 141 | // JSON5 142 | export const json5LineTerminators = new Set([ 143 | charCodes.CHAR_NEWLINE, 144 | charCodes.CHAR_RETURN, 145 | charCodes.CHAR_LINE_SEPARATOR, 146 | charCodes.CHAR_PARAGRAPH_SEPARATOR 147 | ]); 148 | -------------------------------------------------------------------------------- /fixtures/asts-with-range/array-7.txt: -------------------------------------------------------------------------------- 1 | [{}] 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Array", 7 | "elements": [ 8 | { 9 | "type": "Element", 10 | "value": { 11 | "type": "Object", 12 | "members": [], 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 4, 22 | "offset": 3 23 | } 24 | }, 25 | "range": [ 26 | 1, 27 | 3 28 | ] 29 | }, 30 | "loc": { 31 | "start": { 32 | "line": 1, 33 | "column": 2, 34 | "offset": 1 35 | }, 36 | "end": { 37 | "line": 1, 38 | "column": 4, 39 | "offset": 3 40 | } 41 | } 42 | } 43 | ], 44 | "loc": { 45 | "start": { 46 | "line": 1, 47 | "column": 1, 48 | "offset": 0 49 | }, 50 | "end": { 51 | "line": 1, 52 | "column": 5, 53 | "offset": 4 54 | } 55 | }, 56 | "range": [ 57 | 0, 58 | 4 59 | ] 60 | }, 61 | "loc": { 62 | "start": { 63 | "line": 1, 64 | "column": 1, 65 | "offset": 0 66 | }, 67 | "end": { 68 | "line": 1, 69 | "column": 5, 70 | "offset": 4 71 | } 72 | }, 73 | "tokens": [ 74 | { 75 | "type": "LBracket", 76 | "loc": { 77 | "start": { 78 | "line": 1, 79 | "column": 1, 80 | "offset": 0 81 | }, 82 | "end": { 83 | "line": 1, 84 | "column": 2, 85 | "offset": 1 86 | } 87 | }, 88 | "range": [ 89 | 0, 90 | 1 91 | ] 92 | }, 93 | { 94 | "type": "LBrace", 95 | "loc": { 96 | "start": { 97 | "line": 1, 98 | "column": 2, 99 | "offset": 1 100 | }, 101 | "end": { 102 | "line": 1, 103 | "column": 3, 104 | "offset": 2 105 | } 106 | }, 107 | "range": [ 108 | 1, 109 | 2 110 | ] 111 | }, 112 | { 113 | "type": "RBrace", 114 | "loc": { 115 | "start": { 116 | "line": 1, 117 | "column": 3, 118 | "offset": 2 119 | }, 120 | "end": { 121 | "line": 1, 122 | "column": 4, 123 | "offset": 3 124 | } 125 | }, 126 | "range": [ 127 | 2, 128 | 3 129 | ] 130 | }, 131 | { 132 | "type": "RBracket", 133 | "loc": { 134 | "start": { 135 | "line": 1, 136 | "column": 4, 137 | "offset": 3 138 | }, 139 | "end": { 140 | "line": 1, 141 | "column": 5, 142 | "offset": 4 143 | } 144 | }, 145 | "range": [ 146 | 3, 147 | 4 148 | ] 149 | } 150 | ], 151 | "range": [ 152 | 0, 153 | 4 154 | ] 155 | } -------------------------------------------------------------------------------- /rust/README.md: -------------------------------------------------------------------------------- 1 | # Momoa JSON 2 | 3 | by [Nicholas C. Zakas](https://humanwhocodes.com) 4 | 5 | If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate). 6 | 7 | ## About 8 | 9 | Momoa is a general purpose JSON utility toolkit, containing: 10 | 11 | * A **tokenizer** that allows you to separate a JSON string into its component parts. 12 | * A ECMA-404 compliant **parser** that produces an abstract syntax tree (AST) representing everything inside of a JSON string. 13 | 14 | ## Background 15 | 16 | A tool like Momoa comes in handy when you want to know not just the result of JSON parsing, but exactly what is contained in the original JSON string. 17 | 18 | ## Usage 19 | 20 | ### Parsing 21 | 22 | There are two parsing methods: one for JSON and one for JSONC. 23 | 24 | To parse a JSON string into an AST, use the `json::parse()` function: 25 | 26 | ```rs 27 | use momoa::ast::*; 28 | use momoa::json; 29 | 30 | fn do_parse(code) -> Node { 31 | let ast = json::parse(code).unwrap(); 32 | 33 | // do something with ast 34 | 35 | ast 36 | } 37 | ``` 38 | 39 | To allow trailing commas in JSON, use the `json::parse_with_trailing_commas()` function: 40 | 41 | ```rs 42 | use momoa::ast::*; 43 | use momoa::json; 44 | 45 | fn do_parse(code) -> Node { 46 | let ast = json::parse_with_trailing_commas(code).unwrap(); 47 | 48 | // do something with ast 49 | 50 | ast 51 | } 52 | ``` 53 | 54 | To parse a JSONC string into an AST, use the `jsonc::parse()` function: 55 | 56 | ```rs 57 | use momoa::ast::*; 58 | use momoa::jsonc; 59 | 60 | fn do_parse(code) -> Node { 61 | let ast = jsonc::parse(code).unwrap(); 62 | 63 | // do something with ast 64 | 65 | ast 66 | } 67 | ``` 68 | 69 | To allow trailing commas in JSONC, use the `jsonc::parse_with_trailing_commas()` function: 70 | 71 | ```rs 72 | use momoa::ast::*; 73 | use momoa::jsonc; 74 | 75 | fn do_parse(code) -> Node { 76 | let ast = jsonc::parse_with_trailing_commas(code).unwrap(); 77 | 78 | // do something with ast 79 | 80 | ast 81 | } 82 | ``` 83 | 84 | ### Tokenizing 85 | 86 | To produce JSON tokens from a string, use the `json::tokenize()` function: 87 | 88 | ```rs 89 | use momoa::*; 90 | 91 | fn do_parse(code) -> Vec { 92 | let result = json::tokenize(code).unwrap(); 93 | 94 | 95 | // do something with ast 96 | 97 | result 98 | } 99 | ``` 100 | 101 | To produce JSON tokens from a string, use the `jsonc::tokenize()` function: 102 | 103 | ```rs 104 | use momoa::*; 105 | 106 | fn do_parse(code) -> Vec { 107 | let result = jsonc::tokenize(code).unwrap(); 108 | 109 | 110 | // do something with ast 111 | 112 | result 113 | } 114 | ``` 115 | 116 | ## Development 117 | 118 | To work on Momoa, you'll need: 119 | 120 | * [Git](https://git-scm.com/) 121 | * [Rust](https://rustup.rs) 122 | 123 | Make sure both are installed by visiting the links and following the instructions to install. 124 | 125 | Now you're ready to clone the repository: 126 | 127 | ```bash 128 | git clone https://github.com/humanwhocodes/momoa.git 129 | ``` 130 | 131 | Then, enter the directory and install the dependencies: 132 | 133 | ```bash 134 | cd momoa/rust 135 | cargo build 136 | ``` 137 | 138 | After that, you can run the tests via: 139 | 140 | ```bash 141 | cargo test 142 | ``` 143 | 144 | ## License 145 | 146 | Apache 2.0 147 | 148 | ## Frequently Asked Questions 149 | 150 | ### What does "Momoa" even mean? 151 | 152 | Momoa is the last name of American actor [Jason Momoa](https://en.wikipedia.org/wiki/Jason_Momoa). Because "JSON" is pronounced "Jason", I wanted a name that played off of this fact. The most obvious choice would have been something related to [Jason and the Argonauts](https://en.wikipedia.org/wiki/Jason_and_the_Argonauts_(1963_film)), as this movie is referenced in the [JSON specification](https://ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) directly. However, both "Argo" and "Argonaut" were already used for open source projects. When I did a search for "Jason" online, Jason Momoa was the first result that came up. He always plays badass characters so it seemed to fit. 153 | 154 | ### Why support comments in JSON? 155 | 156 | There are a number of programs that allow C-style comments in JSON files, most notably, configuration files for [Visual Studio Code](https://code.visualstudio.com). As there seems to be a need for this functionality, I decided to add it out-of-the-box. 157 | -------------------------------------------------------------------------------- /js/src/traversal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Traversal approaches for Momoa JSON AST. 3 | * @author Nicholas C. Zakas 4 | */ 5 | 6 | //----------------------------------------------------------------------------- 7 | // Typedefs 8 | //----------------------------------------------------------------------------- 9 | 10 | /** @typedef {import("./typedefs.js").Node} Node */ 11 | /** @typedef {import("./typedefs.js").TraversalPhase} TraversalPhase */ 12 | /** 13 | * @typedef {Object} TraversalVisitor 14 | * @property {(node: Node, parent?: Node) => void} [enter] 15 | * @property {(node: Node, parent?: Node) => void} [exit] 16 | */ 17 | 18 | //----------------------------------------------------------------------------- 19 | // Data 20 | //----------------------------------------------------------------------------- 21 | 22 | export const childKeys = new Map([ 23 | ["Document", ["body"]], 24 | ["Object", ["members"]], 25 | ["Member", ["name", "value"]], 26 | ["Element", ["value"]], 27 | ["Array", ["elements"]], 28 | ["String", []], 29 | ["Number", []], 30 | ["Boolean", []], 31 | ["Null", []], 32 | ["NaN", []], 33 | ["Infinity", []], 34 | ["Identifier", []], 35 | ]); 36 | 37 | //----------------------------------------------------------------------------- 38 | // Helpers 39 | //----------------------------------------------------------------------------- 40 | 41 | /** 42 | * Determines if a given value is an object. 43 | * @param {*} value The value to check. 44 | * @returns {boolean} True if the value is an object, false if not. 45 | */ 46 | function isObject(value) { 47 | return value && (typeof value === "object"); 48 | } 49 | 50 | /** 51 | * Determines if a given value is an AST node. 52 | * @param {*} value The value to check. 53 | * @returns {boolean} True if the value is a node, false if not. 54 | */ 55 | export function isNode(value) { 56 | return isObject(value) && (typeof value.type === "string"); 57 | } 58 | 59 | //----------------------------------------------------------------------------- 60 | // Exports 61 | //----------------------------------------------------------------------------- 62 | 63 | /** 64 | * Traverses an AST from the given node. 65 | * @param {Node} root The node to traverse from 66 | * @param {TraversalVisitor} visitor An object with an `enter` and `exit` method. 67 | */ 68 | export function traverse(root, visitor) { 69 | 70 | /** 71 | * Recursively visits a node. 72 | * @param {Node} node The node to visit. 73 | * @param {Node} [parent] The parent of the node to visit. 74 | * @returns {void} 75 | */ 76 | function visitNode(node, parent) { 77 | 78 | if (typeof visitor.enter === "function") { 79 | visitor.enter(node, parent); 80 | } 81 | 82 | for (const key of childKeys.get(node.type)) { 83 | const value = node[key]; 84 | 85 | if (isObject(value)) { 86 | if (Array.isArray(value)) { 87 | value.forEach(child => visitNode(child, node)); 88 | } else if (isNode(value)) { 89 | visitNode(value, node); 90 | } 91 | } 92 | } 93 | 94 | if (typeof visitor.exit === "function") { 95 | visitor.exit(node, parent); 96 | } 97 | } 98 | 99 | visitNode(root); 100 | } 101 | 102 | /** 103 | * @callback FilterPredicate 104 | * @param {{node: Node, parent?: Node, phase: TraversalPhase}} item 105 | * @param {number} index 106 | * @param {Array<{node: Node, parent?: Node, phase: TraversalPhase}>} array 107 | * @returns {boolean} 108 | */ 109 | 110 | /** 111 | * Creates an iterator over the given AST. 112 | * @param {Node} root The root AST node to traverse. 113 | * @param {FilterPredicate} [filter] A filter function to determine which steps to 114 | * return; 115 | * @returns {IterableIterator<{node: Node, parent?: Node, phase: TraversalPhase}>} An iterator over the AST. 116 | */ 117 | export function iterator(root, filter = () => true) { 118 | 119 | /** @type {Array<{node: Node, parent?: Node, phase: TraversalPhase}>} */ 120 | const traversal = []; 121 | 122 | traverse(root, { 123 | enter(node, parent) { 124 | traversal.push({ node, parent, phase: "enter" }); 125 | }, 126 | exit(node, parent) { 127 | traversal.push({ node, parent, phase: "exit" }); 128 | } 129 | }); 130 | 131 | return traversal.filter(filter).values(); 132 | } 133 | -------------------------------------------------------------------------------- /js/tests/print.test.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @fileoverview Tests for printer 4 | * @author Nicholas C. Zakas 5 | */ 6 | 7 | //----------------------------------------------------------------------------- 8 | // Imports 9 | //----------------------------------------------------------------------------- 10 | 11 | import * as momoa_esm from "../dist/momoa.js"; 12 | import momoa_cjs from "../dist/momoa.cjs"; 13 | import { expect } from "chai"; 14 | import json5 from "json5"; 15 | 16 | //----------------------------------------------------------------------------- 17 | // Data 18 | //----------------------------------------------------------------------------- 19 | 20 | const pkgs = { 21 | cjs: momoa_cjs, 22 | esm: momoa_esm, 23 | }; 24 | 25 | const data = [ 26 | true, 27 | false, 28 | null, 29 | 15, 30 | -12, 31 | 0.1, 32 | "Hello world", 33 | "", 34 | { a: "b", c: 2, d: true, e: null }, 35 | ["a", "b", "c", "d"], 36 | [1, 2, 3, 4], 37 | { items: [1, 2, 3], name: "foo", flag: false }, 38 | [{ name: "foo", "a b": "c", found: true }, 2, false] 39 | ]; 40 | 41 | const json5Data = [ 42 | ...data, 43 | NaN, 44 | Infinity, 45 | -Infinity, 46 | { a: NaN, b: Infinity, c: -Infinity }, 47 | [NaN, Infinity, -Infinity], 48 | [{ a: NaN, b: Infinity, c: -Infinity }, 2, false], 49 | "NaN", 50 | "Foo\\\nbar", 51 | "Foo\\bar", 52 | "foo\\o", 53 | "foo\\\u2028bar", 54 | "foo\\\u2029bar", 55 | ]; 56 | 57 | //----------------------------------------------------------------------------- 58 | // Helpers 59 | //----------------------------------------------------------------------------- 60 | 61 | /** 62 | * The JSON5 utility frustratingly adds dangling commas on every array and object 63 | * when using an indent. This function removes those dangling commas so we can 64 | * compare output. 65 | * @param {string} str The string to process. 66 | * @returns {string} The string with dangling commas removed 67 | */ 68 | function removeDanglingCommas(str) { 69 | return str.replace(/,(\s*)]/g, "$1]").replace(/,(\s*)}/g, "$1}"); 70 | } 71 | 72 | //----------------------------------------------------------------------------- 73 | // Tests 74 | //----------------------------------------------------------------------------- 75 | 76 | 77 | describe("print()", () => { 78 | 79 | Object.entries(pkgs).forEach(([name, { parse, print }]) => { 80 | 81 | describe(name, () => { 82 | 83 | describe("JSON data", () => { 84 | 85 | for (const value of data) { 86 | 87 | it(`should print ${value} the same as JSON.stringify() when called with no indent`, () => { 88 | const nativeResult = JSON.stringify(value); 89 | const result = print(parse(nativeResult)); 90 | expect(result).to.equal(nativeResult); 91 | }); 92 | 93 | it(`should print ${value} the same as JSON.stringify() when called with indent`, () => { 94 | const nativeResult = JSON.stringify(value, null, 4); 95 | const result = print(parse(nativeResult), { indent: 4 }); 96 | expect(result).to.equal(nativeResult); 97 | }); 98 | } 99 | 100 | }); 101 | 102 | describe("JSON5 data", () => { 103 | 104 | for (const value of json5Data) { 105 | 106 | it(`should print ${value} the same as json5.stringify() when called with no indent`, () => { 107 | const nativeResult = json5.stringify(value, { quote: "\"" }); 108 | const result = print(parse(nativeResult, { mode: "json5" })); 109 | const expected = json5.stringify(json5.parse(nativeResult), { quote: "\"" }); 110 | 111 | expect(result).to.equal(expected); 112 | }); 113 | 114 | it(`should print ${value} the same as json5.stringify() when called with indent`, () => { 115 | const nativeResult = json5.stringify(value, { quote: "\"" }); 116 | const result = print(parse(nativeResult, { mode: "json5" }), { indent: 4 }); 117 | const expected = removeDanglingCommas(json5.stringify(json5.parse(nativeResult), { quote: "\"", space: 4 })); 118 | 119 | expect(result).to.equal(expected); 120 | }); 121 | } 122 | 123 | }); 124 | 125 | }); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /fixtures/asts/object-1.txt: -------------------------------------------------------------------------------- 1 | {"foo":1} 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Object", 7 | "members": [ 8 | { 9 | "type": "Member", 10 | "name": { 11 | "type": "String", 12 | "value": "foo", 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 7, 22 | "offset": 6 23 | } 24 | } 25 | }, 26 | "value": { 27 | "type": "Number", 28 | "value": 1, 29 | "loc": { 30 | "start": { 31 | "line": 1, 32 | "column": 8, 33 | "offset": 7 34 | }, 35 | "end": { 36 | "line": 1, 37 | "column": 9, 38 | "offset": 8 39 | } 40 | } 41 | }, 42 | "loc": { 43 | "start": { 44 | "line": 1, 45 | "column": 2, 46 | "offset": 1 47 | }, 48 | "end": { 49 | "line": 1, 50 | "column": 9, 51 | "offset": 8 52 | } 53 | } 54 | } 55 | ], 56 | "loc": { 57 | "start": { 58 | "line": 1, 59 | "column": 1, 60 | "offset": 0 61 | }, 62 | "end": { 63 | "line": 1, 64 | "column": 10, 65 | "offset": 9 66 | } 67 | } 68 | }, 69 | "loc": { 70 | "start": { 71 | "line": 1, 72 | "column": 1, 73 | "offset": 0 74 | }, 75 | "end": { 76 | "line": 1, 77 | "column": 10, 78 | "offset": 9 79 | } 80 | }, 81 | "tokens": [ 82 | { 83 | "type": "LBrace", 84 | "loc": { 85 | "start": { 86 | "line": 1, 87 | "column": 1, 88 | "offset": 0 89 | }, 90 | "end": { 91 | "line": 1, 92 | "column": 2, 93 | "offset": 1 94 | } 95 | } 96 | }, 97 | { 98 | "type": "String", 99 | "loc": { 100 | "start": { 101 | "line": 1, 102 | "column": 2, 103 | "offset": 1 104 | }, 105 | "end": { 106 | "line": 1, 107 | "column": 7, 108 | "offset": 6 109 | } 110 | } 111 | }, 112 | { 113 | "type": "Colon", 114 | "loc": { 115 | "start": { 116 | "line": 1, 117 | "column": 7, 118 | "offset": 6 119 | }, 120 | "end": { 121 | "line": 1, 122 | "column": 8, 123 | "offset": 7 124 | } 125 | } 126 | }, 127 | { 128 | "type": "Number", 129 | "loc": { 130 | "start": { 131 | "line": 1, 132 | "column": 8, 133 | "offset": 7 134 | }, 135 | "end": { 136 | "line": 1, 137 | "column": 9, 138 | "offset": 8 139 | } 140 | } 141 | }, 142 | { 143 | "type": "RBrace", 144 | "loc": { 145 | "start": { 146 | "line": 1, 147 | "column": 9, 148 | "offset": 8 149 | }, 150 | "end": { 151 | "line": 1, 152 | "column": 10, 153 | "offset": 9 154 | } 155 | } 156 | } 157 | ] 158 | } -------------------------------------------------------------------------------- /fixtures/asts/object-7-identifier-nan-json5.txt: -------------------------------------------------------------------------------- 1 | {NaN:1} 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Object", 7 | "members": [ 8 | { 9 | "type": "Member", 10 | "name": { 11 | "type": "Identifier", 12 | "name": "NaN", 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 5, 22 | "offset": 4 23 | } 24 | } 25 | }, 26 | "value": { 27 | "type": "Number", 28 | "value": 1, 29 | "loc": { 30 | "start": { 31 | "line": 1, 32 | "column": 6, 33 | "offset": 5 34 | }, 35 | "end": { 36 | "line": 1, 37 | "column": 7, 38 | "offset": 6 39 | } 40 | } 41 | }, 42 | "loc": { 43 | "start": { 44 | "line": 1, 45 | "column": 2, 46 | "offset": 1 47 | }, 48 | "end": { 49 | "line": 1, 50 | "column": 7, 51 | "offset": 6 52 | } 53 | } 54 | } 55 | ], 56 | "loc": { 57 | "start": { 58 | "line": 1, 59 | "column": 1, 60 | "offset": 0 61 | }, 62 | "end": { 63 | "line": 1, 64 | "column": 8, 65 | "offset": 7 66 | } 67 | } 68 | }, 69 | "loc": { 70 | "start": { 71 | "line": 1, 72 | "column": 1, 73 | "offset": 0 74 | }, 75 | "end": { 76 | "line": 1, 77 | "column": 8, 78 | "offset": 7 79 | } 80 | }, 81 | "tokens": [ 82 | { 83 | "type": "LBrace", 84 | "loc": { 85 | "start": { 86 | "line": 1, 87 | "column": 1, 88 | "offset": 0 89 | }, 90 | "end": { 91 | "line": 1, 92 | "column": 2, 93 | "offset": 1 94 | } 95 | } 96 | }, 97 | { 98 | "type": "Number", 99 | "loc": { 100 | "start": { 101 | "line": 1, 102 | "column": 2, 103 | "offset": 1 104 | }, 105 | "end": { 106 | "line": 1, 107 | "column": 5, 108 | "offset": 4 109 | } 110 | } 111 | }, 112 | { 113 | "type": "Colon", 114 | "loc": { 115 | "start": { 116 | "line": 1, 117 | "column": 5, 118 | "offset": 4 119 | }, 120 | "end": { 121 | "line": 1, 122 | "column": 6, 123 | "offset": 5 124 | } 125 | } 126 | }, 127 | { 128 | "type": "Number", 129 | "loc": { 130 | "start": { 131 | "line": 1, 132 | "column": 6, 133 | "offset": 5 134 | }, 135 | "end": { 136 | "line": 1, 137 | "column": 7, 138 | "offset": 6 139 | } 140 | } 141 | }, 142 | { 143 | "type": "RBrace", 144 | "loc": { 145 | "start": { 146 | "line": 1, 147 | "column": 7, 148 | "offset": 6 149 | }, 150 | "end": { 151 | "line": 1, 152 | "column": 8, 153 | "offset": 7 154 | } 155 | } 156 | } 157 | ] 158 | } -------------------------------------------------------------------------------- /fixtures/asts/object-2.txt: -------------------------------------------------------------------------------- 1 | { "message": "Hello world!" } 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Object", 7 | "members": [ 8 | { 9 | "type": "Member", 10 | "name": { 11 | "type": "String", 12 | "value": "message", 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 3, 17 | "offset": 2 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 12, 22 | "offset": 11 23 | } 24 | } 25 | }, 26 | "value": { 27 | "type": "String", 28 | "value": "Hello world!", 29 | "loc": { 30 | "start": { 31 | "line": 1, 32 | "column": 14, 33 | "offset": 13 34 | }, 35 | "end": { 36 | "line": 1, 37 | "column": 28, 38 | "offset": 27 39 | } 40 | } 41 | }, 42 | "loc": { 43 | "start": { 44 | "line": 1, 45 | "column": 3, 46 | "offset": 2 47 | }, 48 | "end": { 49 | "line": 1, 50 | "column": 28, 51 | "offset": 27 52 | } 53 | } 54 | } 55 | ], 56 | "loc": { 57 | "start": { 58 | "line": 1, 59 | "column": 1, 60 | "offset": 0 61 | }, 62 | "end": { 63 | "line": 1, 64 | "column": 30, 65 | "offset": 29 66 | } 67 | } 68 | }, 69 | "loc": { 70 | "start": { 71 | "line": 1, 72 | "column": 1, 73 | "offset": 0 74 | }, 75 | "end": { 76 | "line": 1, 77 | "column": 30, 78 | "offset": 29 79 | } 80 | }, 81 | "tokens": [ 82 | { 83 | "type": "LBrace", 84 | "loc": { 85 | "start": { 86 | "line": 1, 87 | "column": 1, 88 | "offset": 0 89 | }, 90 | "end": { 91 | "line": 1, 92 | "column": 2, 93 | "offset": 1 94 | } 95 | } 96 | }, 97 | { 98 | "type": "String", 99 | "loc": { 100 | "start": { 101 | "line": 1, 102 | "column": 3, 103 | "offset": 2 104 | }, 105 | "end": { 106 | "line": 1, 107 | "column": 12, 108 | "offset": 11 109 | } 110 | } 111 | }, 112 | { 113 | "type": "Colon", 114 | "loc": { 115 | "start": { 116 | "line": 1, 117 | "column": 12, 118 | "offset": 11 119 | }, 120 | "end": { 121 | "line": 1, 122 | "column": 13, 123 | "offset": 12 124 | } 125 | } 126 | }, 127 | { 128 | "type": "String", 129 | "loc": { 130 | "start": { 131 | "line": 1, 132 | "column": 14, 133 | "offset": 13 134 | }, 135 | "end": { 136 | "line": 1, 137 | "column": 28, 138 | "offset": 27 139 | } 140 | } 141 | }, 142 | { 143 | "type": "RBrace", 144 | "loc": { 145 | "start": { 146 | "line": 1, 147 | "column": 29, 148 | "offset": 28 149 | }, 150 | "end": { 151 | "line": 1, 152 | "column": 30, 153 | "offset": 29 154 | } 155 | } 156 | } 157 | ] 158 | } -------------------------------------------------------------------------------- /fixtures/asts/object-7-identifier-infinity-json5.txt: -------------------------------------------------------------------------------- 1 | {Infinity:1} 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Object", 7 | "members": [ 8 | { 9 | "type": "Member", 10 | "name": { 11 | "type": "Identifier", 12 | "name": "Infinity", 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 10, 22 | "offset": 9 23 | } 24 | } 25 | }, 26 | "value": { 27 | "type": "Number", 28 | "value": 1, 29 | "loc": { 30 | "start": { 31 | "line": 1, 32 | "column": 11, 33 | "offset": 10 34 | }, 35 | "end": { 36 | "line": 1, 37 | "column": 12, 38 | "offset": 11 39 | } 40 | } 41 | }, 42 | "loc": { 43 | "start": { 44 | "line": 1, 45 | "column": 2, 46 | "offset": 1 47 | }, 48 | "end": { 49 | "line": 1, 50 | "column": 12, 51 | "offset": 11 52 | } 53 | } 54 | } 55 | ], 56 | "loc": { 57 | "start": { 58 | "line": 1, 59 | "column": 1, 60 | "offset": 0 61 | }, 62 | "end": { 63 | "line": 1, 64 | "column": 13, 65 | "offset": 12 66 | } 67 | } 68 | }, 69 | "loc": { 70 | "start": { 71 | "line": 1, 72 | "column": 1, 73 | "offset": 0 74 | }, 75 | "end": { 76 | "line": 1, 77 | "column": 13, 78 | "offset": 12 79 | } 80 | }, 81 | "tokens": [ 82 | { 83 | "type": "LBrace", 84 | "loc": { 85 | "start": { 86 | "line": 1, 87 | "column": 1, 88 | "offset": 0 89 | }, 90 | "end": { 91 | "line": 1, 92 | "column": 2, 93 | "offset": 1 94 | } 95 | } 96 | }, 97 | { 98 | "type": "Number", 99 | "loc": { 100 | "start": { 101 | "line": 1, 102 | "column": 2, 103 | "offset": 1 104 | }, 105 | "end": { 106 | "line": 1, 107 | "column": 10, 108 | "offset": 9 109 | } 110 | } 111 | }, 112 | { 113 | "type": "Colon", 114 | "loc": { 115 | "start": { 116 | "line": 1, 117 | "column": 10, 118 | "offset": 9 119 | }, 120 | "end": { 121 | "line": 1, 122 | "column": 11, 123 | "offset": 10 124 | } 125 | } 126 | }, 127 | { 128 | "type": "Number", 129 | "loc": { 130 | "start": { 131 | "line": 1, 132 | "column": 11, 133 | "offset": 10 134 | }, 135 | "end": { 136 | "line": 1, 137 | "column": 12, 138 | "offset": 11 139 | } 140 | } 141 | }, 142 | { 143 | "type": "RBrace", 144 | "loc": { 145 | "start": { 146 | "line": 1, 147 | "column": 12, 148 | "offset": 11 149 | }, 150 | "end": { 151 | "line": 1, 152 | "column": 13, 153 | "offset": 12 154 | } 155 | } 156 | } 157 | ] 158 | } -------------------------------------------------------------------------------- /js/src/char-code-reader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview A charactor code reader. 3 | * @author Nicholas C. Zakas 4 | */ 5 | 6 | //----------------------------------------------------------------------------- 7 | // Type Definitions 8 | //----------------------------------------------------------------------------- 9 | 10 | /** @typedef {import("./typedefs.js").Location} Location */ 11 | 12 | //----------------------------------------------------------------------------- 13 | // Data 14 | //----------------------------------------------------------------------------- 15 | 16 | const CHAR_CR = 13; // \r 17 | const CHAR_LF = 10; // \n 18 | 19 | //----------------------------------------------------------------------------- 20 | // CharCodeReader 21 | //----------------------------------------------------------------------------- 22 | 23 | /** 24 | * A reader that reads character codes from a string. 25 | */ 26 | export class CharCodeReader { 27 | 28 | /** 29 | * The text to read from. 30 | * @type {string} 31 | */ 32 | #text = ""; 33 | 34 | /** 35 | * The current line number. 36 | * @type {number} 37 | */ 38 | #line = 1; 39 | 40 | /** 41 | * The current column number. 42 | * @type {number} 43 | */ 44 | #column = 0; 45 | 46 | /** 47 | * The current offset in the text. 48 | * @type {number} 49 | */ 50 | #offset = -1; 51 | 52 | /** 53 | * Whether the last character read was a new line. 54 | * @type {boolean} 55 | */ 56 | #newLine = false; 57 | 58 | /** 59 | * The last character code read. 60 | * @type {number} 61 | */ 62 | #last = -1; 63 | 64 | /** 65 | * Whether the reader has ended. 66 | * @type {boolean} 67 | */ 68 | #ended = false; 69 | 70 | /** 71 | * Creates a new instance. 72 | * @param {string} text The text to read from 73 | */ 74 | constructor(text) { 75 | this.#text = text; 76 | } 77 | 78 | /** 79 | * Ends the reader. 80 | * @returns {void} 81 | */ 82 | #end() { 83 | if (this.#ended) { 84 | return; 85 | } 86 | 87 | this.#column++; 88 | this.#offset++; 89 | this.#last = -1; 90 | this.#ended = true; 91 | } 92 | 93 | /** 94 | * Returns the current position of the reader. 95 | * @returns {Location} An object with line, column, and offset properties. 96 | */ 97 | locate() { 98 | return { 99 | line: this.#line, 100 | column: this.#column, 101 | offset: this.#offset 102 | }; 103 | } 104 | 105 | /** 106 | * Reads the next character code in the text. 107 | * @returns {number} The next character code, or -1 if there are no more characters. 108 | */ 109 | next() { 110 | if (this.#offset >= this.#text.length - 1) { 111 | this.#end(); 112 | return -1; 113 | } 114 | 115 | this.#offset++; 116 | const charCode = this.#text.charCodeAt(this.#offset); 117 | 118 | if (this.#newLine) { 119 | this.#line++; 120 | this.#column = 1; 121 | this.#newLine = false; 122 | } else { 123 | this.#column++; 124 | } 125 | 126 | if (charCode === CHAR_CR) { 127 | this.#newLine = true; 128 | 129 | // if we already see a \r, just ignore upcoming \n 130 | if (this.peek() === CHAR_LF) { 131 | this.#offset++; 132 | } 133 | } else if (charCode === CHAR_LF) { 134 | this.#newLine = true; 135 | } 136 | 137 | this.#last = charCode; 138 | 139 | return charCode; 140 | } 141 | 142 | /** 143 | * Peeks at the next character code in the text. 144 | * @returns {number} The next character code, or -1 if there are no more characters. 145 | */ 146 | peek() { 147 | if (this.#offset === this.#text.length - 1) { 148 | return -1; 149 | } 150 | 151 | return this.#text.charCodeAt(this.#offset + 1); 152 | } 153 | 154 | /** 155 | * Determines if the next character code in the text matches a specific character code. 156 | * @param {(number) => boolean} fn A function to call on the next character. 157 | * @returns {boolean} True if the next character code matches, false if not. 158 | */ 159 | match(fn) { 160 | if (fn(this.peek())) { 161 | this.next(); 162 | return true; 163 | } 164 | 165 | return false; 166 | } 167 | 168 | /** 169 | * Returns the last character code read. 170 | * @returns {number} The last character code read. 171 | */ 172 | current() { 173 | return this.#last; 174 | } 175 | 176 | 177 | } 178 | -------------------------------------------------------------------------------- /fixtures/asts/array-4.txt: -------------------------------------------------------------------------------- 1 | [1, 2] 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Array", 7 | "elements": [ 8 | { 9 | "type": "Element", 10 | "value": { 11 | "type": "Number", 12 | "value": 1, 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 3, 22 | "offset": 2 23 | } 24 | } 25 | }, 26 | "loc": { 27 | "start": { 28 | "line": 1, 29 | "column": 2, 30 | "offset": 1 31 | }, 32 | "end": { 33 | "line": 1, 34 | "column": 3, 35 | "offset": 2 36 | } 37 | } 38 | }, 39 | { 40 | "type": "Element", 41 | "value": { 42 | "type": "Number", 43 | "value": 2, 44 | "loc": { 45 | "start": { 46 | "line": 1, 47 | "column": 5, 48 | "offset": 4 49 | }, 50 | "end": { 51 | "line": 1, 52 | "column": 6, 53 | "offset": 5 54 | } 55 | } 56 | }, 57 | "loc": { 58 | "start": { 59 | "line": 1, 60 | "column": 5, 61 | "offset": 4 62 | }, 63 | "end": { 64 | "line": 1, 65 | "column": 6, 66 | "offset": 5 67 | } 68 | } 69 | } 70 | ], 71 | "loc": { 72 | "start": { 73 | "line": 1, 74 | "column": 1, 75 | "offset": 0 76 | }, 77 | "end": { 78 | "line": 1, 79 | "column": 7, 80 | "offset": 6 81 | } 82 | } 83 | }, 84 | "loc": { 85 | "start": { 86 | "line": 1, 87 | "column": 1, 88 | "offset": 0 89 | }, 90 | "end": { 91 | "line": 1, 92 | "column": 7, 93 | "offset": 6 94 | } 95 | }, 96 | "tokens": [ 97 | { 98 | "type": "LBracket", 99 | "loc": { 100 | "start": { 101 | "line": 1, 102 | "column": 1, 103 | "offset": 0 104 | }, 105 | "end": { 106 | "line": 1, 107 | "column": 2, 108 | "offset": 1 109 | } 110 | } 111 | }, 112 | { 113 | "type": "Number", 114 | "loc": { 115 | "start": { 116 | "line": 1, 117 | "column": 2, 118 | "offset": 1 119 | }, 120 | "end": { 121 | "line": 1, 122 | "column": 3, 123 | "offset": 2 124 | } 125 | } 126 | }, 127 | { 128 | "type": "Comma", 129 | "loc": { 130 | "start": { 131 | "line": 1, 132 | "column": 3, 133 | "offset": 2 134 | }, 135 | "end": { 136 | "line": 1, 137 | "column": 4, 138 | "offset": 3 139 | } 140 | } 141 | }, 142 | { 143 | "type": "Number", 144 | "loc": { 145 | "start": { 146 | "line": 1, 147 | "column": 5, 148 | "offset": 4 149 | }, 150 | "end": { 151 | "line": 1, 152 | "column": 6, 153 | "offset": 5 154 | } 155 | } 156 | }, 157 | { 158 | "type": "RBracket", 159 | "loc": { 160 | "start": { 161 | "line": 1, 162 | "column": 6, 163 | "offset": 5 164 | }, 165 | "end": { 166 | "line": 1, 167 | "column": 7, 168 | "offset": 6 169 | } 170 | } 171 | } 172 | ] 173 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/object-1.txt: -------------------------------------------------------------------------------- 1 | {"foo":1} 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Object", 7 | "members": [ 8 | { 9 | "type": "Member", 10 | "name": { 11 | "type": "String", 12 | "value": "foo", 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 7, 22 | "offset": 6 23 | } 24 | }, 25 | "range": [ 26 | 1, 27 | 6 28 | ] 29 | }, 30 | "value": { 31 | "type": "Number", 32 | "value": 1, 33 | "loc": { 34 | "start": { 35 | "line": 1, 36 | "column": 8, 37 | "offset": 7 38 | }, 39 | "end": { 40 | "line": 1, 41 | "column": 9, 42 | "offset": 8 43 | } 44 | }, 45 | "range": [ 46 | 7, 47 | 8 48 | ] 49 | }, 50 | "loc": { 51 | "start": { 52 | "line": 1, 53 | "column": 2, 54 | "offset": 1 55 | }, 56 | "end": { 57 | "line": 1, 58 | "column": 9, 59 | "offset": 8 60 | } 61 | }, 62 | "range": [ 63 | 1, 64 | 8 65 | ] 66 | } 67 | ], 68 | "loc": { 69 | "start": { 70 | "line": 1, 71 | "column": 1, 72 | "offset": 0 73 | }, 74 | "end": { 75 | "line": 1, 76 | "column": 10, 77 | "offset": 9 78 | } 79 | }, 80 | "range": [ 81 | 0, 82 | 9 83 | ] 84 | }, 85 | "loc": { 86 | "start": { 87 | "line": 1, 88 | "column": 1, 89 | "offset": 0 90 | }, 91 | "end": { 92 | "line": 1, 93 | "column": 10, 94 | "offset": 9 95 | } 96 | }, 97 | "tokens": [ 98 | { 99 | "type": "LBrace", 100 | "loc": { 101 | "start": { 102 | "line": 1, 103 | "column": 1, 104 | "offset": 0 105 | }, 106 | "end": { 107 | "line": 1, 108 | "column": 2, 109 | "offset": 1 110 | } 111 | }, 112 | "range": [ 113 | 0, 114 | 1 115 | ] 116 | }, 117 | { 118 | "type": "String", 119 | "loc": { 120 | "start": { 121 | "line": 1, 122 | "column": 2, 123 | "offset": 1 124 | }, 125 | "end": { 126 | "line": 1, 127 | "column": 7, 128 | "offset": 6 129 | } 130 | }, 131 | "range": [ 132 | 1, 133 | 6 134 | ] 135 | }, 136 | { 137 | "type": "Colon", 138 | "loc": { 139 | "start": { 140 | "line": 1, 141 | "column": 7, 142 | "offset": 6 143 | }, 144 | "end": { 145 | "line": 1, 146 | "column": 8, 147 | "offset": 7 148 | } 149 | }, 150 | "range": [ 151 | 6, 152 | 7 153 | ] 154 | }, 155 | { 156 | "type": "Number", 157 | "loc": { 158 | "start": { 159 | "line": 1, 160 | "column": 8, 161 | "offset": 7 162 | }, 163 | "end": { 164 | "line": 1, 165 | "column": 9, 166 | "offset": 8 167 | } 168 | }, 169 | "range": [ 170 | 7, 171 | 8 172 | ] 173 | }, 174 | { 175 | "type": "RBrace", 176 | "loc": { 177 | "start": { 178 | "line": 1, 179 | "column": 9, 180 | "offset": 8 181 | }, 182 | "end": { 183 | "line": 1, 184 | "column": 10, 185 | "offset": 9 186 | } 187 | }, 188 | "range": [ 189 | 8, 190 | 9 191 | ] 192 | } 193 | ], 194 | "range": [ 195 | 0, 196 | 9 197 | ] 198 | } -------------------------------------------------------------------------------- /fixtures/asts-with-range/object-7-identifier-nan-json5.txt: -------------------------------------------------------------------------------- 1 | {NaN:1} 2 | --- 3 | { 4 | "type": "Document", 5 | "body": { 6 | "type": "Object", 7 | "members": [ 8 | { 9 | "type": "Member", 10 | "name": { 11 | "type": "Identifier", 12 | "name": "NaN", 13 | "loc": { 14 | "start": { 15 | "line": 1, 16 | "column": 2, 17 | "offset": 1 18 | }, 19 | "end": { 20 | "line": 1, 21 | "column": 5, 22 | "offset": 4 23 | } 24 | }, 25 | "range": [ 26 | 1, 27 | 4 28 | ] 29 | }, 30 | "value": { 31 | "type": "Number", 32 | "value": 1, 33 | "loc": { 34 | "start": { 35 | "line": 1, 36 | "column": 6, 37 | "offset": 5 38 | }, 39 | "end": { 40 | "line": 1, 41 | "column": 7, 42 | "offset": 6 43 | } 44 | }, 45 | "range": [ 46 | 5, 47 | 6 48 | ] 49 | }, 50 | "loc": { 51 | "start": { 52 | "line": 1, 53 | "column": 2, 54 | "offset": 1 55 | }, 56 | "end": { 57 | "line": 1, 58 | "column": 7, 59 | "offset": 6 60 | } 61 | }, 62 | "range": [ 63 | 1, 64 | 6 65 | ] 66 | } 67 | ], 68 | "loc": { 69 | "start": { 70 | "line": 1, 71 | "column": 1, 72 | "offset": 0 73 | }, 74 | "end": { 75 | "line": 1, 76 | "column": 8, 77 | "offset": 7 78 | } 79 | }, 80 | "range": [ 81 | 0, 82 | 7 83 | ] 84 | }, 85 | "loc": { 86 | "start": { 87 | "line": 1, 88 | "column": 1, 89 | "offset": 0 90 | }, 91 | "end": { 92 | "line": 1, 93 | "column": 8, 94 | "offset": 7 95 | } 96 | }, 97 | "tokens": [ 98 | { 99 | "type": "LBrace", 100 | "loc": { 101 | "start": { 102 | "line": 1, 103 | "column": 1, 104 | "offset": 0 105 | }, 106 | "end": { 107 | "line": 1, 108 | "column": 2, 109 | "offset": 1 110 | } 111 | }, 112 | "range": [ 113 | 0, 114 | 1 115 | ] 116 | }, 117 | { 118 | "type": "Number", 119 | "loc": { 120 | "start": { 121 | "line": 1, 122 | "column": 2, 123 | "offset": 1 124 | }, 125 | "end": { 126 | "line": 1, 127 | "column": 5, 128 | "offset": 4 129 | } 130 | }, 131 | "range": [ 132 | 1, 133 | 4 134 | ] 135 | }, 136 | { 137 | "type": "Colon", 138 | "loc": { 139 | "start": { 140 | "line": 1, 141 | "column": 5, 142 | "offset": 4 143 | }, 144 | "end": { 145 | "line": 1, 146 | "column": 6, 147 | "offset": 5 148 | } 149 | }, 150 | "range": [ 151 | 4, 152 | 5 153 | ] 154 | }, 155 | { 156 | "type": "Number", 157 | "loc": { 158 | "start": { 159 | "line": 1, 160 | "column": 6, 161 | "offset": 5 162 | }, 163 | "end": { 164 | "line": 1, 165 | "column": 7, 166 | "offset": 6 167 | } 168 | }, 169 | "range": [ 170 | 5, 171 | 6 172 | ] 173 | }, 174 | { 175 | "type": "RBrace", 176 | "loc": { 177 | "start": { 178 | "line": 1, 179 | "column": 7, 180 | "offset": 6 181 | }, 182 | "end": { 183 | "line": 1, 184 | "column": 8, 185 | "offset": 7 186 | } 187 | }, 188 | "range": [ 189 | 6, 190 | 7 191 | ] 192 | } 193 | ], 194 | "range": [ 195 | 0, 196 | 7 197 | ] 198 | } --------------------------------------------------------------------------------