From 2e7845af8024a7c64270f27fff9156e982de34bb Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Mon, 12 Aug 2013 15:31:07 -0700 Subject: [PATCH] Path of error is escaped where needed and looks more like actual JS. - Escape keys looking like something complex - Check simple keys with simple regex, full story: http://stackoverflow.com/questions/1661197/valid-characters-for-javascript-variable-names - Tests --- packages/check/match.js | 33 +++++++++++++++++++++++-------- packages/check/match_test.js | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/packages/check/match.js b/packages/check/match.js index 90c9c7df1c..60783c6c97 100644 --- a/packages/check/match.js +++ b/packages/check/match.js @@ -162,9 +162,7 @@ var checkSubtree = function (value, pattern) { checkSubtree(valueElement, pattern[0]); } catch (err) { if (err instanceof Match.Error) { - if (err.path && err.path[0] !== '[') - err.path = "." + err.path; - err.path = "[" + index + "]" + err.path; + err.path = _prependPath(index, err.path); } throw err; } @@ -251,11 +249,8 @@ var checkSubtree = function (value, pattern) { throw new Match.Error("Unknown key"); } } catch (err) { - if (err instanceof Match.Error) { - if (err.path && err.path[0] !== "[") - err.path = "." + err.path; - err.path = key + err.path; - } + if (err instanceof Match.Error) + err.path = _prependPath(key, err.path); throw err; } }); @@ -309,3 +304,25 @@ _.extend(ArgumentChecker.prototype, { self.description); } }); + +var _jsKeywords = ["do", "if", "in", "for", "let", "new", "try", "var", "case", + "else", "enum", "eval", "false", "null", "this", "true", "void", "with", + "break", "catch", "class", "const", "super", "throw", "while", "yield", + "delete", "export", "import", "public", "return", "static", "switch", + "typeof", "default", "extends", "finally", "package", "private", "continue", + "debugger", "function", "arguments", "interface", "protected", "implements", + "instanceof"]; + +// Assumes the base of path is already escaped properly +// returns key + base +var _prependPath = function (key, base) { + if ((typeof key) === "number" || key.match(/^[0-9]+$/)) + key = "[" + key + "]"; + else if (!key.match(/^[a-z_$][0-9a-z_$]*$/i) || _.contains(_jsKeywords, key)) + key = JSON.stringify([key]); + + if (base && base[0] !== "[") + return key + '.' + base; + return key + base; +}; + diff --git a/packages/check/match_test.js b/packages/check/match_test.js index ea6465ce5a..bf807e6e4f 100644 --- a/packages/check/match_test.js +++ b/packages/check/match_test.js @@ -217,3 +217,41 @@ Tinytest.add("check - argument checker", function (test) { check(x, Boolean); }, true, true); }); + +Tinytest.add("check - Match error path", function (test) { + var match = function (value, pattern, expectedPath) { + try { + check(value, pattern); + } catch (err) { + if (err.path != expectedPath) + test.fail({ + type: "match-error-path", + message: "The path of Match.Error doesn't match.", + pattern: JSON.stringify(pattern), + value: JSON.stringify(value), + path: err.path, + expectedPath: expectedPath + }); + } + }; + + match({ foo: [ { bar: 3 }, {bar: "something"} ] }, { foo: [ { bar: Number } ] }, "foo[1].bar"); + // Complicated case with arrays, $, whitespace and quotes! + match([{ $FoO: { "bar baz\n\"'": 3 } }], [{ $FoO: { "bar baz\n\"'": String } }], "[0].$FoO[\"bar baz\\n\\\"'\"]"); + // Numbers only, can be accessed w/o quotes + match({ "1231": 123 }, { "1231": String }, "[1231]"); + match({ "1234abcd": 123 }, { "1234abcd": String }, "[\"1234abcd\"]"); + match({ $set: { people: "nice" } }, { $set: { people: [String] } }, "$set.people"); + match({ _underscore: "should work" }, { _underscore: Number }, "_underscore"); + // Nested array looks nice + match([[["something", "here"], []], [["string", 123]]], [[[String]]], "[1][0][1]"); + // Object nested in arrays should look nice, too! + match([[[{ foo: "something" }, { foo: "here"}], + [{ foo: "asdf" }]], + [[{ foo: 123 }]]], + [[[{ foo: String }]]], "[1][0][0].foo"); + + // JS keyword + match({ "return": 0 }, { "return": String }, "[\"return\"]"); +}); +