Repository: eslint/doctrine (js)

Issue:
Support string/numeric/boolean literal types
JSDoc supports, e.g.:

```
/**
 * @typedef {Object} Comment
 * @property {('public'|'protected'|'private')} access
 */
```

`jsdoc -X` output looks like:

```
...
        "properties": [
            {
                "type": {
                    "names": [
                        "'public'",
                        "'protected'",
                        "'private'"
                    ]
                },
                "name": "access"
            }
        ],
...
```

This is useful to support enumerated types. See, for instance, https://github.com/jsdoc3/jsdoc/issues/629, https://github.com/jsdoc3/jsdoc/issues/1065, and https://github.com/documentationjs/documentation/issues/373.

Pull request description:
Added support for numeric and string literal types (fixes #156)
Added support to typed.js for string and numeric literals in types.

Patch:
diff --git a/lib/typed.js b/lib/typed.js
index cf7a212..a4c282f 100644
--- a/lib/typed.js
+++ b/lib/typed.js
@@ -41,7 +41,9 @@
         OptionalType: 'OptionalType',
         NullableType: 'NullableType',
         NameExpression: 'NameExpression',
-        TypeApplication: 'TypeApplication'
+        TypeApplication: 'TypeApplication',
+        StringLiteralType: 'StringLiteralType',
+        NumericLiteralType: 'NumericLiteralType'
     };
 
     Token = {
@@ -462,6 +464,10 @@
             token = Token.EQUAL;
             return token;
 
+        case 0x2D: /* '-' */
+            token = scanNumber();
+            return token;
+
         default:
             if (esutils.code.isDecimalDigit(ch)) {
                 token = scanNumber();
@@ -888,6 +894,20 @@
 
             return parseTypeName();
 
+        case Token.STRING:
+            next();
+            return {
+                type: Syntax.StringLiteralType,
+                value: value
+            };
+
+        case Token.NUMBER:
+            next();
+            return {
+                type: Syntax.NumericLiteralType,
+                value: value
+            };
+
         default:
             utility.throwError('unexpected token');
         }
@@ -1220,6 +1240,14 @@
             result += '>';
             break;
 
+        case Syntax.StringLiteralType:
+            result = '"' + node.value + '"';
+            break;
+
+        case Syntax.NumericLiteralType:
+            result = String(node.value);
+            break;
+
         default:
             utility.throwError('Unknown type ' + node.type);
         }


Test patch:
diff --git a/test/parse.js b/test/parse.js
index 047cc8b..438ed6d 100644
--- a/test/parse.js
+++ b/test/parse.js
@@ -1227,6 +1227,25 @@ describe('parse', function () {
             ]
         });
     });
+
+    it('string literal property', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @typedef {Object} comment",
+                " * @property {('public'|'protected'|'private')} access",
+                "*/"
+            ].join('\n'), { unwrap: true });
+
+        res.tags.should.have.length(2);
+        res.tags[1].should.have.property('title', 'property');
+        res.tags[1].should.have.property('name', 'access');
+        res.tags[1].type.should.have.property('type', 'UnionType');
+        res.tags[1].type.elements.should.have.length(3);
+        res.tags[1].type.elements.should.containEql({type: 'StringLiteralType', value: 'public'});
+        res.tags[1].type.elements.should.containEql({type: 'StringLiteralType', value: 'private'});
+        res.tags[1].type.elements.should.containEql({type: 'StringLiteralType', value: 'protected'});
+    });
 });
 
 describe('parseType', function () {
@@ -1744,6 +1763,29 @@ describe('parseType', function () {
         });
     });
 
+    it('string literal type', function () {
+        var type;
+        type = doctrine.parseType('"Hello, World"');
+        type.should.eql({
+            type: 'StringLiteralType',
+            value: 'Hello, World'
+        });
+    });
+
+    it('numeric literal type', function () {
+        var type;
+        type = doctrine.parseType('32');
+        type.should.eql({
+            type: 'NumericLiteralType',
+            value: 32
+        });
+        type = doctrine.parseType('-142.42');
+        type.should.eql({
+            type: 'NumericLiteralType',
+            value: -142.42
+        });
+    });
+
     it('illegal tokens', function () {
         doctrine.parseType.bind(doctrine, ".").should.throw('unexpected token');
         doctrine.parseType.bind(doctrine, ".d").should.throw('unexpected token');
@@ -2405,7 +2447,9 @@ describe('exported Syntax', function() {
             OptionalType: 'OptionalType',
             NullableType: 'NullableType',
             NameExpression: 'NameExpression',
-            TypeApplication: 'TypeApplication'
+            TypeApplication: 'TypeApplication',
+            StringLiteralType: 'StringLiteralType',
+            NumericLiteralType: 'NumericLiteralType'
         });
     });
 });
diff --git a/test/stringify.js b/test/stringify.js
index 2db6d69..2531458 100644
--- a/test/stringify.js
+++ b/test/stringify.js
@@ -76,6 +76,10 @@ describe('stringify', function () {
     testStringify("function(a:number,callback:function(a:Array.<(String|Number|Object)>):boolean):String");
     testStringify("function(a:(string|number),this:string,new:true):function():number");
     testStringify("function(a:(string|number),this:string,new:true):function(a:function(val):result):number");
+
+    // literal types
+    testStringify('"Hello, World!"');
+    testStringify("-142.35");
 });
 
 describe('literals', function() {
@@ -102,6 +106,20 @@ describe('literals', function() {
             type: doctrine.Syntax.UndefinedLiteral
         }).should.equal('undefined');
     });
+
+    it('StringLiteralType', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.StringLiteralType,
+            value: 'Hello, World!'
+        }).should.equal('"Hello, World!"');
+    });
+
+    it('NumericLiteralType', function () {
+        doctrine.type.stringify({
+            type: doctrine.Syntax.NumericLiteralType,
+            value: '-142.35'
+        }).should.equal('-142.35');
+    });
 });
 
 describe('Expression', function () {