-
Notifications
You must be signed in to change notification settings - Fork 333
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Replace json-parse-helpfulerror with jsonc-parser (#1493)
- Loading branch information
Showing
8 changed files
with
751 additions
and
549 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { ParseError, ParseErrorCode, parse, stripComments } from 'jsonc-parser' | ||
|
||
const stdoutColumns = process.stdout.columns || 80 | ||
|
||
/** | ||
* Ensures the code line or a hint is always displayed for the code snippet. | ||
* If the line is empty, it outputs `<empty>`. | ||
* If the line is larger than a line of the terminal windows, it will cut it off. This also prevents too much | ||
* garbage data from being displayed. | ||
* | ||
* @param line - target line to check. | ||
* @returns either the hint or the actual line for the code snippet. | ||
*/ | ||
function ensureLineDisplay(line: string): string { | ||
return `${line.length ? line.slice(0, Math.min(line.length, stdoutColumns)) : '<empty>'}\n` | ||
} | ||
|
||
/** | ||
* Builds a marker line to point to the position of the found error. | ||
* | ||
* @param length - positions to the right of the error line. | ||
* @returns the marker line. | ||
*/ | ||
function getMarker(length: number): string { | ||
return length > stdoutColumns ? '' : `${' '.repeat(length - 1)}^\n` | ||
} | ||
|
||
/** | ||
* Builds a json code snippet to mark and contextualize the found error. | ||
* This snippet consists of 5 lines with the erroneous line in the middle. | ||
* | ||
* @param lines - all lines of the json file. | ||
* @param errorLine - erroneous line. | ||
* @param columnNumber - the error position inside the line. | ||
* @returns the entire code snippet. | ||
*/ | ||
function showSnippet(lines: string[], errorLine: number, columnNumber: number): string { | ||
const len = lines.length | ||
if (len === 0) return '<empty>' | ||
if (len === 1) return `${ensureLineDisplay(lines[0])}${getMarker(columnNumber)}` | ||
// Show an area of lines around the error line for a more detailed snippet. | ||
const snippetEnd = Math.min(errorLine + 2, len) | ||
let snippet = '' | ||
for (let i = Math.max(errorLine - 2, 1); i <= snippetEnd; i++) { | ||
// Lines in the output are counted starting from one, so choose the previous line | ||
snippet += ensureLineDisplay(lines[i - 1]) | ||
if (i === errorLine) snippet += getMarker(columnNumber) | ||
} | ||
return `${snippet}\n` | ||
} | ||
|
||
/** | ||
* Parses a json string, while also handling errors and comments. | ||
* | ||
* @param jsonString - target json string. | ||
* @returns the parsed json object. | ||
*/ | ||
export default function parseJson(jsonString: string) { | ||
jsonString = stripComments(jsonString) | ||
try { | ||
return JSON.parse(jsonString) | ||
} catch { | ||
const errors: ParseError[] = [] | ||
const json = parse(jsonString, errors) | ||
|
||
// If no errors were found, just return the parsed json file | ||
if (errors.length === 0) return json | ||
let errorString = '' | ||
const lines = jsonString.split('\n') | ||
for (const error of errors) { | ||
const offset = error.offset | ||
let lineNumber = 1 | ||
let columnNumber = 1 | ||
let currentOffset = 0 | ||
// Calculate line and column from the offset | ||
for (const line of lines) { | ||
if (currentOffset + line.length >= offset) { | ||
columnNumber = offset - currentOffset + 1 | ||
break | ||
} | ||
currentOffset += line.length + 1 // +1 for the newline character | ||
lineNumber++ | ||
} | ||
// @ts-expect-error due to --isolatedModules forbidding to implement ambient constant enums. | ||
errorString += `Error at line ${lineNumber}, column ${columnNumber}: ${ParseErrorCode[error.error]}\n${showSnippet(lines, lineNumber, columnNumber)}\n` | ||
} | ||
throw new SyntaxError(errorString) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { Index } from './IndexType' | ||
|
||
export interface DependencyGroup { | ||
heading: string | ||
groupName: string | ||
packages: Index<string> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
import fs from 'fs/promises' | ||
import jph from 'json-parse-helpfulerror' | ||
import os from 'os' | ||
import path from 'path' | ||
import fs from 'node:fs/promises' | ||
import os from 'node:os' | ||
import path from 'node:path' | ||
import spawn from 'spawn-please' | ||
import parseJson from '../../../src/lib/utils/parseJson' | ||
import chaiSetup from '../../helpers/chaiSetup' | ||
|
||
chaiSetup() | ||
|
@@ -20,12 +20,15 @@ describe('deno', async function () { | |
} | ||
await fs.writeFile(pkgFile, JSON.stringify(pkg)) | ||
try { | ||
const { stdout } = await spawn( | ||
'node', | ||
[bin, '--jsonUpgraded', '--packageManager', 'deno', '--packageFile', pkgFile], | ||
undefined, | ||
) | ||
const pkg = jph.parse(stdout) | ||
const { stdout } = await spawn('node', [ | ||
bin, | ||
'--jsonUpgraded', | ||
'--packageManager', | ||
'deno', | ||
'--packageFile', | ||
pkgFile, | ||
]) | ||
const pkg = parseJson(stdout) | ||
pkg.should.have.property('ncu-test-v2') | ||
} finally { | ||
await fs.rm(tempDir, { recursive: true, force: true }) | ||
|
@@ -45,7 +48,7 @@ describe('deno', async function () { | |
const { stdout } = await spawn('node', [bin, '--jsonUpgraded'], undefined, { | ||
cwd: tempDir, | ||
}) | ||
const pkg = jph.parse(stdout) | ||
const pkg = parseJson(stdout) | ||
pkg.should.have.property('ncu-test-v2') | ||
} finally { | ||
await fs.rm(tempDir, { recursive: true, force: true }) | ||
|
@@ -64,7 +67,7 @@ describe('deno', async function () { | |
try { | ||
await spawn('node', [bin, '-u'], undefined, { cwd: tempDir }) | ||
const pkgDataNew = await fs.readFile(pkgFile, 'utf-8') | ||
const pkg = jph.parse(pkgDataNew) | ||
const pkg = parseJson(pkgDataNew) | ||
pkg.should.deep.equal({ | ||
imports: { | ||
'ncu-test-v2': 'npm:[email protected]', | ||
|
@@ -89,7 +92,7 @@ describe('deno', async function () { | |
const { stdout } = await spawn('node', [bin, '--jsonUpgraded'], undefined, { | ||
cwd: tempDir, | ||
}) | ||
const pkg = jph.parse(stdout) | ||
const pkg = parseJson(stdout) | ||
pkg.should.have.property('ncu-test-v2') | ||
} finally { | ||
await fs.rm(tempDir, { recursive: true, force: true }) | ||
|
@@ -108,7 +111,7 @@ describe('deno', async function () { | |
try { | ||
await spawn('node', [bin, '-u'], undefined, { cwd: tempDir }) | ||
const pkgDataNew = await fs.readFile(pkgFile, 'utf-8') | ||
const pkg = jph.parse(pkgDataNew) | ||
const pkg = parseJson(pkgDataNew) | ||
pkg.should.deep.equal({ | ||
imports: { | ||
'ncu-test-v2': 'npm:[email protected]', | ||
|
Oops, something went wrong.