├── .editorconfig ├── .gitattributes ├── .github ├── security.md └── workflows │ └── main.yml ├── .gitignore ├── .npmrc ├── index.d.ts ├── index.js ├── index.test-d.ts ├── license ├── package.json ├── readme.md └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/security.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. 4 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node.js ${{ matrix.node-version }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | node-version: 13 | - 22 14 | - 20 15 | - 18 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: npm install 22 | - run: npm test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | .nyc_output 4 | coverage 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import type {JsonObject} from 'type-fest'; 2 | 3 | /** 4 | Exposed for `instanceof` checking. 5 | */ 6 | export class JSONError extends Error { // eslint-disable-line @typescript-eslint/naming-convention 7 | /** 8 | The filename displayed in the error message, if any. 9 | */ 10 | fileName: string; 11 | 12 | /** 13 | The printable section of the JSON which produces the error. 14 | */ 15 | readonly codeFrame: string; 16 | 17 | /** 18 | The raw version of `codeFrame` without colors. 19 | */ 20 | readonly rawCodeFrame: string; 21 | } 22 | 23 | // Get `reviver`` parameter from `JSON.parse()`. 24 | export type Reviver = Parameters['1']; 25 | 26 | /** 27 | Parse JSON with more helpful errors. 28 | 29 | @param string - A valid JSON string. 30 | @param reviver - Prescribes how the value originally produced by parsing is transformed, before being returned. See [`JSON.parse` docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter 31 | ) for more. 32 | @param filename - The filename displayed in the error message. 33 | @returns A parsed JSON object. 34 | @throws A {@link JSONError} when there is a parsing error. 35 | 36 | @example 37 | ``` 38 | import parseJson, {JSONError} from 'parse-json'; 39 | 40 | const json = '{\n\t"foo": true,\n}'; 41 | 42 | parseJson(json); 43 | // JSONError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) 44 | 45 | // 1 | { 46 | // 2 | "foo": true, 47 | // > 3 | } 48 | // | ^ 49 | 50 | parseJson(json, 'foo.json'); 51 | // JSONError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) in foo.json 52 | 53 | // 1 | { 54 | // 2 | "foo": true, 55 | // > 3 | } 56 | // | ^ 57 | // fileName: 'foo.json', 58 | // [cause]: SyntaxError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) 59 | // at JSON.parse () 60 | // at ... 61 | 62 | // You can also add the filename at a later point 63 | try { 64 | parseJson(json); 65 | } catch (error) { 66 | if (error instanceof JSONError) { 67 | error.fileName = 'foo.json'; 68 | } 69 | 70 | throw error; 71 | } 72 | // JSONError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) in foo.json 73 | 74 | // 1 | { 75 | // 2 | "foo": true, 76 | // > 3 | } 77 | // | ^ 78 | 79 | // fileName: 'foo.json', 80 | // [cause]: SyntaxError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) 81 | // at JSON.parse () 82 | // at ... 83 | ``` 84 | */ 85 | export default function parseJson(string: string, reviver?: Reviver, filename?: string): JsonObject; 86 | 87 | /** 88 | Parse JSON with more helpful errors. 89 | 90 | @param string - A valid JSON string. 91 | @param filename - The filename displayed in the error message. 92 | @returns A parsed JSON object. 93 | @throws A {@link JSONError} when there is a parsing error. 94 | 95 | @example 96 | ``` 97 | import parseJson, {JSONError} from 'parse-json'; 98 | 99 | const json = '{\n\t"foo": true,\n}'; 100 | 101 | parseJson(json); 102 | // JSONError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) 103 | 104 | // 1 | { 105 | // 2 | "foo": true, 106 | // > 3 | } 107 | // | ^ 108 | 109 | parseJson(json, 'foo.json'); 110 | // JSONError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) in foo.json 111 | 112 | // 1 | { 113 | // 2 | "foo": true, 114 | // > 3 | } 115 | // | ^ 116 | // fileName: 'foo.json', 117 | // [cause]: SyntaxError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) 118 | // at JSON.parse () 119 | // at ... 120 | 121 | // You can also add the filename at a later point 122 | try { 123 | parseJson(json); 124 | } catch (error) { 125 | if (error instanceof JSONError) { 126 | error.fileName = 'foo.json'; 127 | } 128 | 129 | throw error; 130 | } 131 | // JSONError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) in foo.json 132 | 133 | // 1 | { 134 | // 2 | "foo": true, 135 | // > 3 | } 136 | // | ^ 137 | 138 | // fileName: 'foo.json', 139 | // [cause]: SyntaxError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) 140 | // at JSON.parse () 141 | // at ... 142 | ``` 143 | */ 144 | export default function parseJson(string: string, filename?: string): JsonObject; 145 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import {codeFrameColumns} from '@babel/code-frame'; 2 | import indexToPosition from 'index-to-position'; 3 | 4 | const getCodePoint = character => `\\u{${character.codePointAt(0).toString(16)}}`; 5 | 6 | export class JSONError extends Error { 7 | name = 'JSONError'; 8 | fileName; 9 | #input; 10 | #jsonParseError; 11 | #message; 12 | #codeFrame; 13 | #rawCodeFrame; 14 | 15 | constructor(messageOrOptions) { 16 | // JSONError constructor used accept string 17 | // TODO[>=9]: Remove this `if` on next major version 18 | if (typeof messageOrOptions === 'string') { 19 | super(); 20 | this.#message = messageOrOptions; 21 | } else { 22 | const {jsonParseError, fileName, input} = messageOrOptions; 23 | // We cannot pass message to `super()`, otherwise the message accessor will be overridden. 24 | // https://262.ecma-international.org/14.0/#sec-error-message 25 | super(undefined, {cause: jsonParseError}); 26 | 27 | this.#input = input; 28 | this.#jsonParseError = jsonParseError; 29 | this.fileName = fileName; 30 | } 31 | 32 | Error.captureStackTrace?.(this, JSONError); 33 | } 34 | 35 | get message() { 36 | this.#message ??= `${addCodePointToUnexpectedToken(this.#jsonParseError.message)}${this.#input === '' ? ' while parsing empty string' : ''}`; 37 | 38 | const {codeFrame} = this; 39 | return `${this.#message}${this.fileName ? ` in ${this.fileName}` : ''}${codeFrame ? `\n\n${codeFrame}\n` : ''}`; 40 | } 41 | 42 | set message(message) { 43 | this.#message = message; 44 | } 45 | 46 | #getCodeFrame(highlightCode) { 47 | // TODO[>=9]: Remove this `if` on next major version 48 | if (!this.#jsonParseError) { 49 | return; 50 | } 51 | 52 | const input = this.#input; 53 | 54 | const location = getErrorLocation(input, this.#jsonParseError.message); 55 | if (!location) { 56 | return; 57 | } 58 | 59 | return codeFrameColumns(input, {start: location}, {highlightCode}); 60 | } 61 | 62 | get codeFrame() { 63 | this.#codeFrame ??= this.#getCodeFrame(/* highlightCode */ true); 64 | return this.#codeFrame; 65 | } 66 | 67 | get rawCodeFrame() { 68 | this.#rawCodeFrame ??= this.#getCodeFrame(/* highlightCode */ false); 69 | return this.#rawCodeFrame; 70 | } 71 | } 72 | 73 | const getErrorLocation = (string, message) => { 74 | const match = message.match(/in JSON at position (?\d+)(?: \(line (?\d+) column (?\d+)\))?$/); 75 | 76 | if (!match) { 77 | return; 78 | } 79 | 80 | const {index, line, column} = match.groups; 81 | 82 | if (line && column) { 83 | return {line: Number(line), column: Number(column)}; 84 | } 85 | 86 | return indexToPosition(string, Number(index), {oneBased: true}); 87 | }; 88 | 89 | const addCodePointToUnexpectedToken = message => message.replace( 90 | // TODO[engine:node@>=20]: The token always quoted after Node.js 20 91 | /(?<=^Unexpected token )(?')?(.)\k/, 92 | (_, _quote, token) => `"${token}"(${getCodePoint(token)})`, 93 | ); 94 | 95 | export default function parseJson(string, reviver, fileName) { 96 | if (typeof reviver === 'string') { 97 | fileName = reviver; 98 | reviver = undefined; 99 | } 100 | 101 | try { 102 | return JSON.parse(string, reviver); 103 | } catch (error) { 104 | throw new JSONError({ 105 | jsonParseError: error, 106 | fileName, 107 | input: string, 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /index.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectType, expectError} from 'tsd'; 2 | import type {JsonObject} from 'type-fest'; 3 | import parseJson, {type JSONError} from './index.js'; 4 | 5 | expectError(parseJson()); 6 | expectError(parseJson({foo: true})); 7 | expectType(parseJson('{"foo": true}')); 8 | expectType(parseJson('{"foo": true}', 'foo.json')); 9 | expectType(parseJson('{"foo": true}', (key, value) => String(value))); 10 | expectType(parseJson('{"foo": true}', (key, value) => String(value), 'foo.json')); 11 | 12 | expectType((() => { 13 | let x: string; 14 | parseJson('{"foo": true}', (key, value) => x = key); // eslint-disable-line no-return-assign 15 | return x!; 16 | })()); 17 | 18 | /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return */ 19 | expectType((() => { 20 | let x: any; 21 | parseJson('{"foo": true}', (key, value) => x = value); // eslint-disable-line no-return-assign 22 | return x; 23 | })()); 24 | /* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return */ 25 | 26 | const jsonError: JSONError = { 27 | name: 'JSONError', 28 | message: 'Unexpected token } in JSON at position 16 while parsing near \'{ "foo": true,}\'', 29 | fileName: 'foo.json', 30 | codeFrame: ` 31 | 1 | { 32 | 2 | "foo": true, 33 | > 3 | } 34 | | ^ 35 | `, 36 | rawCodeFrame: ` 37 | 1 | { 38 | 2 | "foo": true, 39 | > 3 | } 40 | | ^ 41 | `, 42 | }; 43 | 44 | expectError(jsonError.codeFrame = ''); 45 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parse-json", 3 | "version": "8.3.0", 4 | "description": "Parse JSON with more helpful errors", 5 | "license": "MIT", 6 | "repository": "sindresorhus/parse-json", 7 | "funding": "https://github.com/sponsors/sindresorhus", 8 | "author": { 9 | "name": "Sindre Sorhus", 10 | "email": "sindresorhus@gmail.com", 11 | "url": "https://sindresorhus.com" 12 | }, 13 | "type": "module", 14 | "exports": { 15 | "types": "./index.d.ts", 16 | "default": "./index.js" 17 | }, 18 | "sideEffects": false, 19 | "engines": { 20 | "node": ">=18" 21 | }, 22 | "scripts": { 23 | "test": "xo && c8 ava && tsd" 24 | }, 25 | "files": [ 26 | "index.js", 27 | "index.d.ts" 28 | ], 29 | "keywords": [ 30 | "parse", 31 | "json", 32 | "graceful", 33 | "error", 34 | "message", 35 | "humanize", 36 | "friendly", 37 | "helpful", 38 | "string" 39 | ], 40 | "dependencies": { 41 | "@babel/code-frame": "^7.26.2", 42 | "index-to-position": "^1.1.0", 43 | "type-fest": "^4.39.1" 44 | }, 45 | "devDependencies": { 46 | "ava": "^6.2.0", 47 | "c8": "^10.1.3", 48 | "outdent": "^0.8.0", 49 | "strip-ansi": "^7.1.0", 50 | "tsd": "^0.31.2", 51 | "xo": "^0.60.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # parse-json 2 | 3 | > Parse JSON with more helpful errors 4 | 5 | ## Install 6 | 7 | ```sh 8 | npm install parse-json 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | import parseJson, {JSONError} from 'parse-json'; 15 | 16 | const json = '{\n\t"foo": true,\n}'; 17 | 18 | 19 | JSON.parse(json); 20 | /* 21 | SyntaxError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) 22 | */ 23 | 24 | 25 | parseJson(json); 26 | /* 27 | JSONError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) 28 | 29 | 1 | { 30 | 2 | "foo": true, 31 | > 3 | } 32 | | ^ 33 | */ 34 | 35 | 36 | parseJson(json, 'foo.json'); 37 | /* 38 | JSONError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) in foo.json 39 | 40 | 1 | { 41 | 2 | "foo": true, 42 | > 3 | } 43 | | ^ 44 | fileName: 'foo.json', 45 | [cause]: SyntaxError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) 46 | at JSON.parse () 47 | at ... 48 | */ 49 | 50 | 51 | // You can also add the filename at a later point 52 | try { 53 | parseJson(json); 54 | } catch (error) { 55 | if (error instanceof JSONError) { 56 | error.fileName = 'foo.json'; 57 | } 58 | 59 | throw error; 60 | } 61 | /* 62 | JSONError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) in foo.json 63 | 64 | 1 | { 65 | 2 | "foo": true, 66 | > 3 | } 67 | | ^ 68 | 69 | fileName: 'foo.json', 70 | [cause]: SyntaxError: Expected double-quoted property name in JSON at position 16 (line 3 column 1) 71 | at JSON.parse () 72 | at ... 73 | */ 74 | ``` 75 | 76 | ## API 77 | 78 | ### parseJson(string, reviver?, filename?) 79 | 80 | Throws a `JSONError` when there is a parsing error. 81 | 82 | #### string 83 | 84 | Type: `string` 85 | 86 | #### reviver 87 | 88 | Type: `Function` 89 | 90 | Prescribes how the value originally produced by parsing is transformed, before being returned. See [`JSON.parse` docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter 91 | ) for more. 92 | 93 | #### filename 94 | 95 | Type: `string` 96 | 97 | The filename displayed in the error message. 98 | 99 | ### JSONError 100 | 101 | Exposed for `instanceof` checking. 102 | 103 | #### fileName 104 | 105 | Type: `string` 106 | 107 | The filename displayed in the error message. 108 | 109 | #### codeFrame 110 | 111 | Type: `string` 112 | 113 | The printable section of the JSON which produces the error. 114 | 115 | #### rawCodeFrame 116 | 117 | Type: `string` 118 | 119 | The raw version of `codeFrame` without colors. 120 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import process from 'node:process'; 2 | import test from 'ava'; 3 | import {outdent} from 'outdent'; 4 | import stripAnsi from 'strip-ansi'; 5 | import parseJson, {JSONError} from './index.js'; 6 | 7 | const NODE_JS_VERSION = Number(process.versions.node.split('.')[0]); 8 | 9 | const errorMessageRegex = (() => { 10 | if (NODE_JS_VERSION < 20) { 11 | return /Unexpected token "}"\(\\u{7d}\) in JSON at position 16/; 12 | } 13 | 14 | if (NODE_JS_VERSION < 21) { 15 | return /Expected double-quoted property name in JSON at position 16/; 16 | } 17 | 18 | return /Expected double-quoted property name in JSON at position 16 \(line 3 column 1\)/; 19 | })(); 20 | const errorMessageRegexWithFileName = new RegExp(errorMessageRegex.source + '.*in foo\\.json'); 21 | const INVALID_JSON_STRING = outdent` 22 | { 23 | "foo": true, 24 | } 25 | `; 26 | const EXPECTED_CODE_FRAME = ` 27 | 1 | { 28 | 2 | "foo": true, 29 | > 3 | } 30 | | ^ 31 | `.slice(1, -1); 32 | 33 | test('main', t => { 34 | t.deepEqual(parseJson('{"foo": true}'), {foo: true}); 35 | 36 | t.throws(() => { 37 | parseJson(INVALID_JSON_STRING); 38 | }, { 39 | name: 'JSONError', 40 | message: errorMessageRegex, 41 | }); 42 | 43 | t.throws(() => { 44 | try { 45 | parseJson(INVALID_JSON_STRING); 46 | } catch (error) { 47 | error.fileName = 'foo.json'; 48 | throw error; 49 | } 50 | }, { 51 | message: errorMessageRegexWithFileName, 52 | }); 53 | 54 | t.throws(() => { 55 | parseJson(INVALID_JSON_STRING, 'foo.json'); 56 | }, { 57 | message: errorMessageRegexWithFileName, 58 | }); 59 | 60 | t.throws(() => { 61 | try { 62 | parseJson(INVALID_JSON_STRING, 'bar.json'); 63 | } catch (error) { 64 | error.fileName = 'foo.json'; 65 | throw error; 66 | } 67 | }, { 68 | message: errorMessageRegexWithFileName, 69 | }); 70 | 71 | { 72 | let jsonError; 73 | try { 74 | parseJson(INVALID_JSON_STRING, 'foo.json'); 75 | } catch (error) { 76 | jsonError = error; 77 | } 78 | 79 | jsonError.message = 'custom error message'; 80 | t.true(jsonError.message.startsWith('custom error message in foo.json')); 81 | // Still have code frame in message. 82 | t.true(stripAnsi(jsonError.message).includes('> 3 | }')); 83 | } 84 | 85 | { 86 | let nativeJsonParseError; 87 | try { 88 | JSON.parse(INVALID_JSON_STRING); 89 | } catch (error) { 90 | nativeJsonParseError = error; 91 | } 92 | 93 | let jsonError; 94 | try { 95 | parseJson(INVALID_JSON_STRING); 96 | } catch (error) { 97 | jsonError = error; 98 | } 99 | 100 | t.is(nativeJsonParseError.name, 'SyntaxError'); 101 | t.deepEqual(nativeJsonParseError, jsonError.cause); 102 | } 103 | }); 104 | 105 | test('throws exported error error', t => { 106 | t.throws(() => { 107 | parseJson('asdf'); 108 | }, { 109 | instanceOf: JSONError, 110 | }); 111 | }); 112 | 113 | test('has error frame properties', t => { 114 | try { 115 | parseJson(INVALID_JSON_STRING, 'foo.json'); 116 | } catch (error) { 117 | t.is(error.rawCodeFrame, EXPECTED_CODE_FRAME); 118 | t.is(stripAnsi(error.codeFrame), EXPECTED_CODE_FRAME); 119 | } 120 | }); 121 | 122 | test('allow error location out of bounds', t => { 123 | try { 124 | parseJson('{'); 125 | } catch (error) { 126 | t.true(error instanceof JSONError); 127 | t.is(error.rawCodeFrame, NODE_JS_VERSION === 18 ? undefined : outdent` 128 | > 1 | { 129 | | ^ 130 | `); 131 | } 132 | }); 133 | 134 | test('empty string', t => { 135 | try { 136 | parseJson(''); 137 | } catch (error) { 138 | t.true(error instanceof JSONError); 139 | t.is(error.message, 'Unexpected end of JSON input while parsing empty string'); 140 | t.is(error.rawCodeFrame, undefined); 141 | } 142 | 143 | try { 144 | parseJson(' '); 145 | } catch (error) { 146 | t.true(error instanceof JSONError); 147 | t.is(error.message, 'Unexpected end of JSON input'); 148 | t.is(error.rawCodeFrame, undefined); 149 | } 150 | }); 151 | 152 | test('Unexpected tokens', t => { 153 | try { 154 | parseJson('a'); 155 | } catch (error) { 156 | t.true(error instanceof JSONError); 157 | const firstLine = error.message.split('\n')[0]; 158 | if (NODE_JS_VERSION === 18) { 159 | t.is(firstLine, 'Unexpected token "a"(\\u{61}) in JSON at position 0'); 160 | } else { 161 | t.is(firstLine, 'Unexpected token "a"(\\u{61}), "a" is not valid JSON'); 162 | } 163 | } 164 | }); 165 | 166 | test('JSONError legacy interface', t => { 167 | { 168 | const error = new JSONError('Error message'); 169 | t.is(error.message, 'Error message'); 170 | } 171 | 172 | { 173 | const error = new JSONError('Error message'); 174 | error.message = 'New error message'; 175 | t.is(error.message, 'New error message'); 176 | } 177 | 178 | { 179 | const error = new JSONError('Error message'); 180 | error.fileName = 'foo.json'; 181 | t.is(error.message, 'Error message in foo.json'); 182 | error.message = 'New error message'; 183 | t.is(error.message, 'New error message in foo.json'); 184 | } 185 | }); 186 | --------------------------------------------------------------------------------