-
-
Notifications
You must be signed in to change notification settings - Fork 6
/
index.js
148 lines (124 loc) · 4.92 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
const chalk = require('chalk');
const { parse } = require('./src/json.pjs');
const { psw, removeLinebreak, verboseLog } = require('./src/utils');
const fixer = require('./src/fixer');
let fixRounds = 0;
let roundThreshold = 20;
const setFixThreshold = (data) => {
const lineCount = data.split('\n').length;
roundThreshold = Math.max(data.length / lineCount, lineCount);
};
const doubleCheck = (data, options = {}) => {
/* eslint-disable no-console */
const verbose = options.verbose;
try {
const res = parse(data);
if (verbose) psw(`\n${chalk.cyan('The JSON data was fixed!')}`);
if (res) return options.parse ? res : data;
} catch (err) {
if (verbose) {
psw('Nearly fixed data:');
data.split('\n').forEach((l, i) => psw(`${chalk.yellow(i)} ${l}`));
}
// eslint-disable-next-line no-use-before-define
if (fixRounds < roundThreshold) return fixJson(err, data, options);
console.error(chalk.red("There's still an error!"));
throw new Error(err.message);
}
/* eslint-enable no-console */
};
const extraChar = (err) => err.expected[0].type === 'other' && ['}', ']'].includes(err.found);
const trailingChar = (err) => {
const literal = err.expected[0].type === 'literal' && err.expected[0].text !== ':';
return ['.', ',', 'x', 'b', 'o'].includes(err.found) && literal;
};
const missingChar = (err) => err.expected[0].text === ',' && ['"', '[', '{'].includes(err.found);
const singleQuotes = (err) => err.found === "'";
const missingQuotes = (err) =>
/\w/.test(err.found) && err.expected.find((el) => el.description === 'string');
const notSquare = (err) => err.found === ':' && [',', ']'].includes(err.expected[0].text);
const notCurly = (err) => err.found === ',' && err.expected[0].text === ':';
const comment = (err) => err.found === '/';
const ops = (err) => ['+', '-', '*', '/', '>', '<', '~', '|', '&', '^'].includes(err.found);
const extraBrackets = (err) => err.found === '}';
const specialChar = (err) => err.found === '"';
const runFixer = ({ verbose, lines, start, err }) => {
/* eslint-disable security/detect-object-injection */
let fixedData = [...lines];
const targetLine = start.line - 2;
if (extraChar(err)) {
fixedData = fixer.fixExtraChar({ fixedData, verbose, targetLine });
} else if (trailingChar(err)) {
fixedData = fixer.fixTrailingChar({ start, fixedData, verbose });
} else if (missingChar(err)) {
if (verbose) psw(chalk.magenta('Missing character'));
const brokenLine = removeLinebreak(lines[targetLine]);
fixedData[targetLine] = `${brokenLine},`;
} else if (singleQuotes(err)) {
fixedData = fixer.fixSingleQuotes({ start, fixedData, verbose });
} else if (missingQuotes(err)) {
fixedData = fixer.fixMissingQuotes({ start, fixedData, verbose });
} else if (notSquare(err)) {
fixedData = fixer.fixSquareBrackets({ start, fixedData, verbose, targetLine });
} else if (notCurly(err)) {
fixedData = fixer.fixCurlyBrackets({ fixedData, verbose, targetLine });
} else if (comment(err)) {
fixedData = fixer.fixComment({ start, fixedData, verbose });
} else if (ops(err)) {
fixedData = fixer.fixOpConcat({ start, fixedData, verbose });
} else if (extraBrackets(err)) {
fixedData = fixer.fixExtraCurlyBrackets({ start, fixedData, verbose });
} else if (specialChar(err)) {
fixedData = fixer.fixSpecialChar({ start, fixedData, verbose });
} else throw new Error(`Unsupported issue: ${err.message} (please open an issue at the repo)`);
return fixedData;
};
/*eslint-disable no-console */
const fixJson = (err, data, options) => {
++fixRounds;
const lines = data.split('\n');
const verbose = options.verbose;
verboseLog({ verbose, lines, err });
const start = err.location.start;
const fixedData = runFixer({ verbose, lines, start, err });
return doubleCheck(fixedData.join('\n'), options);
};
/*eslint-enable no-console */
const fixingTime = ({ data, err, optionsCopy }) => {
fixRounds = 0;
setFixThreshold(data);
return {
data: fixJson(err, data, optionsCopy),
changed: true
};
};
/**
* @param {string} data JSON string data to check (and fix).
* @param {{verbose:boolean, parse:boolean}} options configuration object which specifies verbosity and whether the object should be parsed or returned as fixed string
* @returns {{data: (Object|string|Array), changed: boolean}} Result
*/
const checkJson = (data, options) => {
//inspired by https://jsontuneup.com/
let optionsCopy;
if (!options || typeof options === 'boolean') {
optionsCopy = {};
optionsCopy.verbose = options;
} else {
optionsCopy = JSON.parse(JSON.stringify(options));
}
if (optionsCopy.parse === undefined || optionsCopy.parse === null) {
optionsCopy.parse = true;
}
try {
const res = parse(data);
if (res) {
return {
data: optionsCopy.parse ? res : data,
changed: false
};
}
} catch (err) {
return fixingTime({ data, err, optionsCopy });
}
};
module.exports = checkJson;