Skip to content

Commit

Permalink
Translate JSON syntax errors to other languages
Browse files Browse the repository at this point in the history
See jsonLint field of translation files
Also fix bug with down arrow on last row of error form
  • Loading branch information
molsonkiko committed Jul 5, 2024
1 parent 3a9be8a commit 5441e6f
Show file tree
Hide file tree
Showing 14 changed files with 805 additions and 254 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1. [Progress reporting](/docs/README.md#reporting-progress-when-parsing-large-amounts-of-json) with the JSON from files and APIs form (henceforth the `grepper form`).
2. In the `grepper form`, pressing `Enter` inside the `Previously viewed directories...` box causes the current text of the box to be searched, assuming that it is a valid directory.
3. [Translation](/README.md#translating-jsontools-to-another-language) of settings in the [Settings form](/docs/README.md#customizing-settings).
4. Translation of JSON syntax errors (under the `jsonLint` field of the [translation file](/translation/english.json5))

### Changed

Expand All @@ -64,6 +65,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed

1. If there would be an `OutOfMemoryException` due to running out of memory while formatting JSON (a likely occurrence when using the `grepper form`), that error is caught and reported with a message box, rather than potentially causing Notepad++ to crash.
2. Ensure that hitting the down key does nothing when the last row of the [error form](/docs/README.md#error-form-and-status-bar) is selected.

## [8.0.0] - 2024-06-29

Expand Down
18 changes: 12 additions & 6 deletions JsonToolsNppPlugin/Forms/ErrorForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public void Reload(string fname, List<JsonLint> lints, bool onStartup = false)
var row = new DataGridViewRow();
row.CreateCells(ErrorGrid);
row.Cells[0].Value = lint.severity;
row.Cells[1].Value = lint.message;
row.Cells[1].Value = lint.TranslateMessageIfDesired(true);
row.Cells[2].Value = lint.pos;
ErrorGrid.Rows.Add(row);
if (interval == 1)
Expand Down Expand Up @@ -167,7 +167,7 @@ private void ExportLintsToJsonMenuItem_Click(object sender, EventArgs e)
var lintArrChildren = new List<JNode>();
foreach (JsonLint lint in lints)
{
var lintObj = lint.ToJson();
var lintObj = lint.ToJson(true);
lintArrChildren.Add(lintObj);
}
var lintArr = new JArray(0, lintArrChildren);
Expand Down Expand Up @@ -217,10 +217,16 @@ private void ErrorForm_KeyUp(object sender, KeyEventArgs e)
var selRowIndex = cells[0].RowIndex;
if (!IsValidRowIndex(selRowIndex))
return;
if (e.KeyCode == Keys.Up && selRowIndex > 0) // move up
ChangeSelectedRow(selRowIndex, selRowIndex - 1);
else if (e.KeyCode == Keys.Down && selRowIndex < ErrorGrid.RowCount - 1)
ChangeSelectedRow(selRowIndex, selRowIndex + 1); // move down
if (e.KeyCode == Keys.Up) // move up (unless on first row)
{
if (selRowIndex > 0)
ChangeSelectedRow(selRowIndex, selRowIndex - 1);
}
else if (e.KeyCode == Keys.Down) // move down (unless on last row)
{
if (selRowIndex < ErrorGrid.RowCount - 1)
ChangeSelectedRow(selRowIndex, selRowIndex + 1);
}
// for most keys, seek first row after current row
// whose description start with that key's char
else if (e.KeyValue >= ' ' && e.KeyValue <= 126)
Expand Down
2 changes: 1 addition & 1 deletion JsonToolsNppPlugin/JSONTools/IniFileParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public override string ToString()

public JsonLint ToJsonLint()
{
return new JsonLint(Message, Position, CurChar, ParserState.FATAL);
return new JsonLint(JsonLintType.FATAL_UNSPECIFIED_ERROR, Position, CurChar, Message);
}
}

Expand Down
555 changes: 395 additions & 160 deletions JsonToolsNppPlugin/JSONTools/JsonParser.cs

Large diffs are not rendered by default.

12 changes: 9 additions & 3 deletions JsonToolsNppPlugin/JSONTools/JsonSchemaValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ private static JsonLint ValidationProblemToLint(ValidationProblemType problemTyp
break;
default: throw new ArgumentException($"Unknown validation problem type {problemType}");
}
return new JsonLint(msg, position, '\x00', ParserState.SCHEMA);
return new JsonLint(JsonLintType.SCHEMA_FIRST, position, '\x00', msg);
}

/// <summary>
Expand All @@ -149,9 +149,15 @@ private static bool AppendValidationProblem(ValidationProblemType problemType, D
return lints.Count <= maxLintCount;
}

public static string LintsAsJArrayString(List<JsonLint> lints)
/// <summary>
///
/// </summary>
/// <param name="lints"></param>
/// <param name="translated">currently does nothing, as schema validation errors are not yet translated</param>
/// <returns></returns>
public static string LintsAsJArrayString(List<JsonLint> lints, bool translated = false)
{
return new JArray(0, lints.Select(x => x.ToJson()).ToList()).ToString();
return new JArray(0, lints.Select(x => x.ToJson(false)).ToList()).ToString();
}

public delegate bool ValidationFunc(JNode x, out List<JsonLint> lints);
Expand Down
8 changes: 4 additions & 4 deletions JsonToolsNppPlugin/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ public static (ParserState parserState, JNode node, bool usesSelections, Documen
json = jsonParser.Parse(text);
lints = jsonParser.lint.ToList();
fatalErrors.Add(jsonParser.fatal);
errorMessage = jsonParser.fatalError?.ToString();
errorMessage = jsonParser.fatalError?.TranslatedToString();
}
catch (Exception ex)
{
Expand Down Expand Up @@ -643,12 +643,12 @@ public static (ParserState parserState, JNode node, bool usesSelections, Documen
try
{
subJson = jsonParser.Parse(selRange);
lints.AddRange(jsonParser.lint.Select(x => new JsonLint(x.message, x.pos + start, x.curChar, x.severity)));
lints.AddRange(jsonParser.lint.Select(x => x.Copy()));
fatalErrors.Add(jsonParser.fatal);
if (jsonParser.fatal)
{
if (errorMessage == null)
errorMessage = jsonParser.fatalError?.ToString();
errorMessage = jsonParser.fatalError?.TranslatedToString();
}
}
catch (Exception ex)
Expand Down Expand Up @@ -737,7 +737,7 @@ private static string HandleJsonParserError(string text, List<bool> fatalErrors,
errorChar = text[errorPos];
}
string errorMessage = RemesParser.PrettifyException(ex);
lints.Add(new JsonLint(errorMessage, errorPos, errorChar, ParserState.FATAL));
lints.Add(new JsonLint(JsonLintType.FATAL_UNSPECIFIED_ERROR, errorPos, errorChar, errorMessage));
return errorMessage;
}

Expand Down
4 changes: 2 additions & 2 deletions JsonToolsNppPlugin/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@
// Build Number
// Revision
//
[assembly: AssemblyVersion("8.0.0.4")]
[assembly: AssemblyFileVersion("8.0.0.4")]
[assembly: AssemblyVersion("8.0.0.5")]
[assembly: AssemblyFileVersion("8.0.0.5")]
91 changes: 56 additions & 35 deletions JsonToolsNppPlugin/Tests/Benchmarker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,39 +37,8 @@ public static bool BenchmarkParserAndRemesPath(string[][] queriesAndDescriptions
return true;
}
string jsonstr = File.ReadAllText(fname);
int len = jsonstr.Length;
long[] loadTimes = new long[numParseTrials];
JNode json = new JNode();
// benchmark time to load json
for (int ii = 0; ii < numParseTrials; ii++)
{
watch.Reset();
watch.Start();
try
{
json = jsonParser.Parse(jsonstr);
}
catch (Exception ex)
{
Npp.AddLine($"Couldn't run benchmark tests because parsing error occurred:\n{ex}");
return true;
}
watch.Stop();
long t = watch.Elapsed.Ticks;
loadTimes[ii] = t;
}
// display loading results
string jsonPreview = json.ToString().Slice(":300") + "\n...";
Npp.AddLine($"Preview of json: {jsonPreview}");
(double mean, double sd) = GetMeanAndSd(loadTimes);
Npp.AddLine($"To convert JSON string of size {len} into JNode took {ConvertTicks(mean)} +/- {ConvertTicks(sd)} " +
$"ms over {loadTimes.Length} trials");
var loadTimesStr = new string[loadTimes.Length];
for (int ii = 0; ii < loadTimes.Length; ii++)
{
loadTimesStr[ii] = (loadTimes[ii] / 10000).ToString();
}
Npp.AddLine($"Load times (ms): {String.Join(", ", loadTimesStr)}");
if (!BenchmarkParser(jsonstr, numParseTrials, jsonParser, watch, out JNode json))
return true;
// time query compiling
RemesParser parser = new RemesParser();
foreach (string[] queryAndDesc in queriesAndDescriptions)
Expand Down Expand Up @@ -148,8 +117,8 @@ public static bool BenchmarkParserAndRemesPath(string[][] queriesAndDescriptions
}
}
// display querying results
(mean, sd) = GetMeanAndSd(queryTimes);
Npp.AddLine($"To run pre-compiled query \"{query}\" on JNode from JSON of size {len} into took {ConvertTicks(mean)} +/- {ConvertTicks(sd)} ms over {numQueryTrials} trials");
(double mean, double sd) = GetMeanAndSd(queryTimes);
Npp.AddLine($"To run pre-compiled query \"{query}\" on JNode from JSON of size {jsonstr.Length} into took {ConvertTicks(mean)} +/- {ConvertTicks(sd)} ms over {numQueryTrials} trials");
var queryTimesStr = new string[queryTimes.Length];
for (int ii = 0; ii < queryTimes.Length; ii++)
{
Expand All @@ -162,6 +131,58 @@ public static bool BenchmarkParserAndRemesPath(string[][] queriesAndDescriptions
return false;
}

//public static bool BenchmarkParsingAndLintingJsonWithErrors(int numTrials)
//{
// JsonParser jsonParser = new JsonParser();
// Stopwatch watch = new Stopwatch();
// // this is just the text of testfiles/small/bad_json.json, but with some closing brackets and a comma at the end, so we can chain them together to make an array
// string singleBadJson = "{ // this file has ALL the bad things\r\n\\u0348z草 false, # yes, all of them\r\n\"b\": None /* so much bad */\r\n'9' :False, \r\n\"😀\"\r\n { \r\n \"u\\\r\ny\":\r\n [\r\n 1\r\n \"FO\\x4fBAR\r\n \" +2, \r\n NaN,\r\n .75 \r\n-0xF +0xabcde\r\n0xABCDE\r\n , // missing ']' here\r\n \"y\r\nu\":\r\n [\r\n [\r\n -.5,\r\n {\r\n \"y\" : 'b\\9\\x0a':\r\n \"m8\": 9.,\r\n }\r\n ],\r\n Infinity\r\n -Infinity\r\n ],\r\n $unquồted_‍key\\u00df \"are *very naughty*\"\r\n _st\\u1eb2tus‌_ェ : \"jubaЯ\"\r\n ],\r\n\"9\\\"a\\\"\t\" \r\n:null,\r\n\"\\u0042lutentharst\":\r\n [\r\n \"\\n'\\\"DOOM\\\" BOOM\b, AND I CONSUME', said Bludd, the mighty Blood God.\\n\\t\",\r\n True,\r\n undefined\r\n [ // some unclosed things too\r\n { /* \r\n and finally an unclosed multiline comment\r\n */\r\n }]]},\r\n";
// string jsonstr = "[" + ArgFunction.StrMulHelper(singleBadJson, 100);
// BenchmarkParser(jsonstr, numTrials, jsonParser, watch, out _);
// for (int ii = 0; ii < numTrials; ii++)
// {

// }
//}

private static bool BenchmarkParser(string jsonstr, int numTrials, JsonParser jsonParser, Stopwatch watch, out JNode json)
{
json = new JNode();
int len = jsonstr.Length;
long[] loadTimes = new long[numTrials];
// benchmark time to load json
for (int ii = 0; ii < numTrials; ii++)
{
watch.Reset();
watch.Start();
try
{
json = jsonParser.Parse(jsonstr);
}
catch (Exception ex)
{
Npp.AddLine($"Couldn't run benchmark tests because parsing error occurred:\n{ex}");
return false;
}
watch.Stop();
long t = watch.Elapsed.Ticks;
loadTimes[ii] = t;
}
// display loading results
string jsonPreview = json.ToString().Slice(":300") + "\n...";
Npp.AddLine($"Preview of json: {jsonPreview}");
(double mean, double sd) = GetMeanAndSd(loadTimes);
Npp.AddLine($"To convert JSON string of size {len} into JNode took {ConvertTicks(mean)} +/- {ConvertTicks(sd)} " +
$"ms over {loadTimes.Length} trials");
var loadTimesStr = new string[loadTimes.Length];
for (int ii = 0; ii < loadTimes.Length; ii++)
{
loadTimesStr[ii] = (loadTimes[ii] / 10000).ToString();
}
Npp.AddLine($"Load times (ms): {String.Join(", ", loadTimesStr)}");
return true;
}

public static bool BenchmarkJNodeToString(int numTrials, string fname)
{
// setup
Expand Down
1 change: 1 addition & 0 deletions JsonToolsNppPlugin/Tests/TestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public static async Task RunAll()
"JsonParser performance",
true, false
),
//(() => Benchmarker.BenchmarkParsingAndLintingJsonWithErrors(30), "JsonParser performance and performance of JsonLint.message"),
(() => Benchmarker.BenchmarkJNodeToString(64, bigRandomFname),
"performance of JSON compression and pretty-printing",
true, false
Expand Down
102 changes: 102 additions & 0 deletions JsonToolsNppPlugin/Utils/Translator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,107 @@ public static string TranslateSettingsDescription(PropertyInfo propertyInfo)
return "";
}

#region message box and error translation

public static string TranslateJsonLint(JsonLint lint)
{
JsonLintType lintType = lint.lintType;
string lintTypeStr = lintType.ToString();
if (!(translations is JObject jobj && jobj.children is Dictionary<string, JNode> dict
&& dict.TryGetValue("jsonLint", out JNode lintTranslations) && lintTranslations is JObject lintTransObj
&& lintTransObj.children.TryGetValue(lintTypeStr, out JNode lintTypeTrans)
&& lintTypeTrans.value is string newMessage))
return lint.message;
switch (lintType)
{
case JsonLintType.FATAL_INVALID_STARTSWITH_I:
case JsonLintType.FATAL_INVALID_STARTSWITH_N:
case JsonLintType.FATAL_INVALID_STARTSWITH_i:
case JsonLintType.FATAL_HEX_INT_OVERFLOW:
case JsonLintType.FATAL_SECOND_DECIMAL_POINT:
case JsonLintType.FATAL_NUM_TRAILING_e_OR_E:
case JsonLintType.FATAL_MAX_RECURSION_DEPTH:
case JsonLintType.FATAL_UNEXPECTED_EOF:
case JsonLintType.FATAL_NO_VALID_LITERAL_POSSIBLE:
case JsonLintType.FATAL_INVALID_STARTSWITH_t:
case JsonLintType.FATAL_INVALID_STARTSWITH_f:
case JsonLintType.FATAL_INVALID_STARTSWITH_T:
case JsonLintType.FATAL_INVALID_STARTSWITH_F:
case JsonLintType.FATAL_INVALID_STARTSWITH_u:
case JsonLintType.FATAL_NO_INPUT:
case JsonLintType.FATAL_ONLY_WHITESPACE_COMMENTS:
case JsonLintType.FATAL_JSONL_NOT_ONE_DOC_PER_LINE:
case JsonLintType.OK_CONTROL_CHAR:
case JsonLintType.NAN_INF_Infinity:
case JsonLintType.NAN_INF_NaN:
case JsonLintType.JSONC_JAVASCRIPT_COMMENT:
case JsonLintType.JSON5_WHITESPACE_CHAR:
case JsonLintType.JSON5_SINGLEQUOTED_STRING:
case JsonLintType.JSON5_ESCAPED_NEWLINE:
case JsonLintType.JSON5_X_ESCAPE:
case JsonLintType.JSON5_UNQUOTED_KEY:
case JsonLintType.JSON5_NUM_LEADING_PLUS:
case JsonLintType.JSON5_HEX_NUM:
case JsonLintType.JSON5_NUM_LEADING_DECIMAL_POINT:
case JsonLintType.JSON5_COMMA_AFTER_LAST_ELEMENT_ARRAY:
case JsonLintType.JSON5_COMMA_AFTER_LAST_ELEMENT_OBJECT:
case JsonLintType.JSON5_NUM_TRAILING_DECIMAL_POINT:
case JsonLintType.BAD_UNTERMINATED_MULTILINE_COMMENT:
case JsonLintType.BAD_PYTHON_COMMENT:
case JsonLintType.BAD_STRING_CONTAINS_NEWLINE:
case JsonLintType.BAD_KEY_CONTAINS_NEWLINE:
case JsonLintType.BAD_PYTHON_nan:
case JsonLintType.BAD_PYTHON_None:
case JsonLintType.BAD_PYTHON_inf:
case JsonLintType.BAD_UNNECESSARY_LEADING_0:
case JsonLintType.BAD_SLASH_FRACTION:
case JsonLintType.BAD_COMMA_BEFORE_FIRST_ELEMENT_ARRAY:
case JsonLintType.BAD_ARRAY_ENDSWITH_CURLYBRACE:
case JsonLintType.BAD_NO_COMMA_BETWEEN_ARRAY_ITEMS:
case JsonLintType.BAD_COLON_BETWEEN_ARRAY_ITEMS:
case JsonLintType.BAD_UNTERMINATED_ARRAY:
case JsonLintType.BAD_COMMA_BEFORE_FIRST_PAIR_OBJECT:
case JsonLintType.BAD_UNTERMINATED_OBJECT:
case JsonLintType.BAD_OBJECT_ENDSWITH_SQUAREBRACE:
case JsonLintType.BAD_COLON_BETWEEN_OBJECT_PAIRS:
case JsonLintType.BAD_PYTHON_True:
case JsonLintType.FATAL_NUL_CHAR:
case JsonLintType.BAD_PYTHON_False:
case JsonLintType.BAD_JAVASCRIPT_undefined:
case JsonLintType.FATAL_UNTERMINATED_KEY:
case JsonLintType.FATAL_INVALID_STARTSWITH_n:
case JsonLintType.FATAL_EXPECTED_JAVASCRIPT_COMMENT:
return newMessage;
case JsonLintType.BAD_CHAR_INSTEAD_OF_EOF:
case JsonLintType.FATAL_HEXADECIMAL_TOO_SHORT:
case JsonLintType.JSON5_ESCAPED_CHAR:
case JsonLintType.BAD_UNTERMINATED_STRING:
case JsonLintType.BAD_INVALID_UNQUOTED_KEY:
case JsonLintType.BAD_NUMBER_INVALID_FORMAT:
case JsonLintType.BAD_TWO_CONSECUTIVE_COMMAS_ARRAY:
case JsonLintType.BAD_TWO_CONSECUTIVE_COMMAS_OBJECT:
case JsonLintType.BAD_NO_COMMA_BETWEEN_OBJECT_PAIRS:
case JsonLintType.BAD_NO_COLON_BETWEEN_OBJECT_KEY_VALUE:
case JsonLintType.BAD_DUPLICATE_KEY:
case JsonLintType.FATAL_PLUS_OR_MINUS_AT_EOF:
case JsonLintType.FATAL_BADLY_LOCATED_CHAR:
return string.Format(newMessage, lint.param1);
case JsonLintType.BAD_CHAR_WHERE_COLON_EXPECTED:
return string.Format(newMessage, lint.param1, lint.param2);

// FATAL messages that wrap an exception
//case JsonLintType.FATAL_INI_FILE_EXCEPTION:
case JsonLintType.FATAL_UNSPECIFIED_ERROR: return (string)lint.param1;
//// SCHEMA messages
//case JsonLintType.SCHEMA_FIRST:
default: return $"No translated message was found for JsonLintType " + lintTypeStr;
}
}

#endregion

#region Form translation

/// <summary>
/// This assumes that each character in Size 7.8 font is 7 pixels across.<br></br>
/// In practice, this generally returns a number that is greater than the width of the text.
Expand Down Expand Up @@ -290,5 +391,6 @@ private static bool Overlap(Control ctrl1, Control ctrl2)
{
return HorizontalOverlap(ctrl1, ctrl2) && VerticalOverlap(ctrl1, ctrl2);
}
#endregion
}
}
Loading

0 comments on commit 5441e6f

Please sign in to comment.