diff --git a/docs/changelog.txt b/docs/changelog.txt index 7bffa7d..e1d572f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -1,3 +1,4 @@ changed - Support string.test(/regexp/) format (formerly required regexp in a quoted string). feature - Added regexp test and samples. +feature - Allow quoted property names in type statements. fixed - Parser failed when empty string used. diff --git a/src/rules-generator.ts b/src/rules-generator.ts index cf36339..b4fe7e7 100644 --- a/src/rules-generator.ts +++ b/src/rules-generator.ts @@ -34,8 +34,11 @@ var errors = { invalidGeneric: "Invalid generic schema usage: ", invalidMapKey: "Map - Key must derive from String type.", invalidWildChildren: "Types can have at most one $wild property and cannot mix with other properties.", + invalidPropertyName: "Property names cannot contain any of: . $ # [ ] / or control characters: ", }; +let INVALID_KEY_REGEX = /[\[\].#$\/\u0000-\u001F\u007F]/; + /* A Validator is a JSON heriarchical structure. The "leaves" are "dot-properties" (see below). The intermediate nodes in the tree are "prop" or "$prop" @@ -418,6 +421,13 @@ export class Generator { Object.keys(schema.properties).forEach((propName) => { if (propName[0] === '$') { wildProperties += 1; + if (INVALID_KEY_REGEX.test(propName.slice(1))) { + this.fatal(errors.invalidPropertyName + propName); + } + } else { + if (INVALID_KEY_REGEX.test(propName)) { + this.fatal(errors.invalidPropertyName + propName); + } } if (!validator[propName]) { validator[propName] = {}; diff --git a/src/rules-parser.pegjs b/src/rules-parser.pegjs index 5cf310c..2463a7c 100644 --- a/src/rules-parser.pegjs +++ b/src/rules-parser.pegjs @@ -194,7 +194,7 @@ Properties = head:PropertyDefinition tail:(_ PropSep part:PropertyDefinition { r PropSep = ("," / ";")? _ -PropertyDefinition = name:Identifier _ ":" _ type:TypeExpression { +PropertyDefinition = name:(Identifier / String) _ ":" _ type:TypeExpression { return { name: name, type: type @@ -473,9 +473,11 @@ RegExpCharacters = chars:( [^\\/] / RegExpEscaped )+ { return chars.join(""); } RegExpEscaped = "\\" char_:. { return "\\" + char_; } -StringLiteral "string" +StringLiteral "string" = s:String { return ast.string(s); } + +String = parts:('"' DoubleStringCharacters '"' / "'" SingleStringCharacters "'") { - return ast.string(parts[1]); + return parts[1]; } DoubleStringCharacters @@ -536,7 +538,7 @@ UnicodeEscapeSequence return String.fromCharCode(parseInt(digits, 16)); } -Identifier "identifier" = start:[a-zA-Z_$] rest:[a-zA-Z0-9_]* { +Identifier "identifier" = start:[a-zA-Z_$] rest:[a-zA-Z_$0-9]* { return start + rest.join(""); } diff --git a/src/test/generator-test.ts b/src/test/generator-test.ts index 54a4498..81eb5d4 100644 --- a/src/test/generator-test.ts +++ b/src/test/generator-test.ts @@ -242,6 +242,11 @@ suite("Rules Generator Tests", function() { expect: {'.validate': "newData.hasChildren()", '$key': {'.validate': "newData.isNumber()"}} }, + { data: "type T { 'a b': Number }", + expect: {'.validate': "newData.hasChildren(['a b'])", + 'a b': {'.validate': "newData.isNumber()"}, + '$other': {'.validate': 'false'}} }, + { data: "type T {a: Number, b: String}", expect: {'.validate': "newData.hasChildren(['a', 'b'])", a: {'.validate': "newData.isNumber()"}, @@ -393,6 +398,10 @@ suite("Rules Generator Tests", function() { expect: /recursive/i }, { data: "type X { $n: Number, $s: String } path / is X;", expect: /wild property/ }, + { data: "type X { $$n: Number } path / is X;", + expect: /property names/i }, + { data: "type X { '\x01': Number } path / is X;", + expect: /property names/i }, { data: "path / is Map;", expect: /No type.*non-generic/ }, { data: "type Pair {a: X, b: Y} path / is Pair;", diff --git a/src/test/parser-test.ts b/src/test/parser-test.ts index c8b3717..fbb8356 100644 --- a/src/test/parser-test.ts +++ b/src/test/parser-test.ts @@ -312,6 +312,13 @@ suite("Rules Parser Tests", function() { methods: {}, params: [], }}, + + { data: "type Foo { 'hyphen-prop': String }", + expect: { derivedFrom: ast.typeType('Object'), + properties: {"hyphen-prop": ast.typeType('String')}, + methods: {}, + params: [], + }}, ]; helper.dataDrivenTest(tests, function(data, expect) {