Repository: eslint/doctrine (js)

Issue:
Support type expression for @this tag
See #143 

The current implementation only supports one name in the expression:
```js
/**
 * @this {Widget}
 */
```

UnionType of NameExpression should be supported as well:
```js
/**
 * @this {Widget|Button}
 */
```

Pull request description:
Fix: support type expression for @this tag (fixes #181)
comments welcome

Patch:
diff --git a/lib/doctrine.js b/lib/doctrine.js
index 095eeeb..a5804df 100644
--- a/lib/doctrine.js
+++ b/lib/doctrine.js
@@ -10,13 +10,11 @@
 
     var typed,
         utility,
-        isArray,
         jsdoc,
         esutils,
         hasOwnProperty;
 
     esutils = require('esutils');
-    isArray = require('isarray');
     typed = require('./typed');
     utility = require('./utility');
 
@@ -268,7 +266,7 @@
 
         function scanIdentifier(last) {
             var identifier;
-            if (!esutils.code.isIdentifierStartES5(source.charCodeAt(index))) {
+            if (!esutils.code.isIdentifierStartES5(source.charCodeAt(index)) && !source[index].match(/[0-9]/)) {
                 return null;
             }
             identifier = advance();
@@ -296,13 +294,13 @@
                 return null;
             }
 
-            if (allowBrackets && source.charCodeAt(index) === 0x5B  /* '[' */) {
-                useBrackets = true;
-                name = advance();
-            }
-
-            if (!esutils.code.isIdentifierStartES5(source.charCodeAt(index))) {
-                return null;
+            if (source.charCodeAt(index) === 0x5B  /* '[' */) {
+                if (allowBrackets) {
+                    useBrackets = true;
+                    name = advance();
+                } else {
+                    return null;
+                }
             }
 
             name += scanIdentifier(last);
@@ -525,8 +523,8 @@
                         // extract the default value if there is one
                         // example: @param {string} [somebody=John Doe] description
                         assign = name.substring(1, name.length - 1).split('=');
-                        if (assign[1]) {
-                            this._tag['default'] = assign[1];
+                        if (assign.length > 1) {
+                            this._tag['default'] = assign.slice(1).join('=');
                         }
                         this._tag.name = assign[0];
 
@@ -609,13 +607,13 @@
             return true;
         };
 
-        TagParser.prototype.parseThis = function parseAccess() {
-            // this name may be a name expression (e.g. {foo.bar})
-            // or a name path (e.g. foo.bar)
+        TagParser.prototype.parseThis = function parseThis() {
+            // this name may be a name expression (e.g. {foo.bar}),
+            // an union (e.g. {foo.bar|foo.baz}) or a name path (e.g. foo.bar)
             var value = trim(sliceSource(source, index, this._last));
             if (value && value.charAt(0) === '{') {
                 var gotType = this.parseType();
-                if (gotType && this._tag.type.type === 'NameExpression') {
+                if (gotType && this._tag.type.type === 'NameExpression' || this._tag.type.type === 'UnionType') {
                     this._tag.name = this._tag.type.name;
                     return true;
                 } else {
@@ -839,7 +837,7 @@
 
             // array of relevant tags
             if (options.tags) {
-                if (isArray(options.tags)) {
+                if (Array.isArray(options.tags)) {
                     interestingTags = { };
                     for (i = 0, iz = options.tags.length; i < iz; i++) {
                         if (typeof options.tags[i] === 'string') {
diff --git a/package.json b/package.json
index 4616b44..71eec5c 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,6 @@
     "betarelease": "eslint-prerelease beta"
   },
   "dependencies": {
-    "esutils": "^2.0.2",
-    "isarray": "^1.0.0"
+    "esutils": "^2.0.2"
   }
 }


Test patch:
diff --git a/test/parse.js b/test/parse.js
index abcde95..51886db 100644
--- a/test/parse.js
+++ b/test/parse.js
@@ -498,6 +498,41 @@ describe('parse', function () {
         });
     });
 
+    it('param with array indexes', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {String} user.0",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'user.0');
+        res.tags[0].should.have.property('type');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'String'
+        });
+    });
+
+    it('param with array indexes and descriptions', function () {
+        var res = doctrine.parse(
+            [
+                "/**",
+                " * @param {String} user.0 The first element",
+                "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'param');
+        res.tags[0].should.have.property('name', 'user.0');
+        res.tags[0].should.have.property('type');
+        res.tags[0].should.have.property('description', 'The first element');
+        res.tags[0].type.should.eql({
+            type: 'NameExpression',
+            name: 'String'
+        });
+    });
+
     it('arg with properties', function () {
         var res = doctrine.parse(
             [
@@ -1172,9 +1207,25 @@ describe('parse', function () {
             ].join('\n'), { unwrap: true });
         res.tags.should.have.length(1);
         res.tags[0].should.have.property('title', 'this');
+        res.tags[0].type.should.have.property('type', 'NameExpression');
         res.tags[0].should.have.property('name', 'thingName.name');
     });
 
+    it('this with UnionType expression', function () {
+        var res = doctrine.parse(
+            [
+              "/**",
+              " * @this {thingName.name|FooBar}",
+              "*/"
+            ].join('\n'), { unwrap: true });
+        res.tags.should.have.length(1);
+        res.tags[0].should.have.property('title', 'this');
+        res.tags[0].type.should.have.property('type', 'UnionType');
+        res.tags[0].type.elements.should.have.length(2);
+        res.tags[0].type.elements.should.containEql({type: 'NameExpression', name: 'thingName.name'});
+        res.tags[0].type.elements.should.containEql({type: 'NameExpression', name: 'FooBar'});
+    });
+
     it('this error with type application', function () {
         var res = doctrine.parse(
             [
@@ -2271,6 +2322,28 @@ describe('optional params', function() {
         });
     });
 
+    it('default arrow functions', function() {
+        doctrine.parse(
+            ["/**", " * @param {Function} [fn=()=>{}] some description", " */"].join('\n'),
+            {unwrap: true, sloppy: true}
+        ).should.eql({
+            description: "",
+            tags: [{
+                "title": "param",
+                "description": "some description",
+                "type": {
+                    "type": "OptionalType",
+                    "expression": {
+                        "type": "NameExpression",
+                        "name": "Function"
+                    }
+                },
+                "name": "fn",
+                "default": "()=>{}"
+            }]
+        });
+    });
+
     it('line numbers', function() {
         var res = doctrine.parse(
             [