diff --git a/lib/jsdoc/src/astbuilder.js b/lib/jsdoc/src/astbuilder.js index fc7a0edb..deededa9 100644 --- a/lib/jsdoc/src/astbuilder.js +++ b/lib/jsdoc/src/astbuilder.js @@ -22,6 +22,7 @@ var acceptsLeadingComments = (function() { // these nodes always accept leading comments var commentable = [ + Syntax.ArrowFunctionExpression, Syntax.AssignmentExpression, Syntax.CallExpression, Syntax.ClassDeclaration, @@ -46,17 +47,20 @@ var acceptsLeadingComments = (function() { // these nodes accept leading comments if they have specific types of parent nodes // like: function foo(/** @type {string} */ bar) {} accepts[Syntax.Identifier] = [ + Syntax.ArrowFunctionExpression, Syntax.CatchClause, Syntax.FunctionDeclaration, Syntax.FunctionExpression ]; // like: function foo(/** @type {string} */ bar='baz') {} accepts[Syntax.AssignmentPattern] = [ + Syntax.ArrowFunctionExpression, Syntax.FunctionDeclaration, Syntax.FunctionExpression ]; // like: function foo(/** @type {string} */ ...bar) {} accepts[Syntax.RestElement] = [ + Syntax.ArrowFunctionExpression, Syntax.FunctionDeclaration, Syntax.FunctionExpression ]; diff --git a/lib/jsdoc/src/astnode.js b/lib/jsdoc/src/astnode.js index 101b9ca6..e5dbdd1b 100644 --- a/lib/jsdoc/src/astnode.js +++ b/lib/jsdoc/src/astnode.js @@ -33,7 +33,7 @@ var isFunction = exports.isFunction = function(node) { } return type === Syntax.FunctionDeclaration || type === Syntax.FunctionExpression || - type === Syntax.MethodDefinition; + type === Syntax.MethodDefinition || type === Syntax.ArrowFunctionExpression; }; /** @@ -160,6 +160,9 @@ var nodeToValue = exports.nodeToValue = function(node) { str = 'exports.' + nodeToValue(node.exported); break; + case Syntax.ArrowFunctionExpression: + // falls through + case Syntax.FunctionDeclaration: // falls through @@ -295,6 +298,14 @@ var getInfo = exports.getInfo = function(node) { var info = {}; switch (node.type) { + // like the function in: "var foo = () => {}" + case Syntax.ArrowFunctionExpression: + info.node = node; + info.name = ''; + info.type = info.node.type; + info.paramnames = getParamNames(node); + break; + // like: "foo = 'bar'" (after declaring foo) // like: "MyClass.prototype.myMethod = function() {}" (after declaring MyClass) case Syntax.AssignmentExpression: diff --git a/lib/jsdoc/src/parser.js b/lib/jsdoc/src/parser.js index e0c4eb77..7a3c0bac 100644 --- a/lib/jsdoc/src/parser.js +++ b/lib/jsdoc/src/parser.js @@ -282,8 +282,9 @@ Parser.prototype.addDocletRef = function(e) { } // keep references to undocumented anonymous functions, too, as they might have scoped vars else if ( - (node.type === Syntax.FunctionDeclaration || node.type === Syntax.FunctionExpression) && - !this._getDocletById(node.nodeId) ) { + (node.type === Syntax.FunctionDeclaration || node.type === Syntax.FunctionExpression || + node.type === Syntax.ArrowFunctionExpression) && + !this._getDocletById(node.nodeId) ) { fakeDoclet = { longname: jsdoc.name.LONGNAMES.ANONYMOUS, meta: { @@ -343,7 +344,8 @@ Parser.prototype.astnodeToMemberof = function(node) { var type = node.type; if ( (type === Syntax.FunctionDeclaration || type === Syntax.FunctionExpression || - type === Syntax.VariableDeclarator) && node.enclosingScope ) { + type === Syntax.ArrowFunctionExpression || type === Syntax.VariableDeclarator) && + node.enclosingScope ) { doclet = this._getDocletById(node.enclosingScope.nodeId); if (!doclet) { diff --git a/lib/jsdoc/src/visitor.js b/lib/jsdoc/src/visitor.js index 25c9bdce..9ec169b3 100644 --- a/lib/jsdoc/src/visitor.js +++ b/lib/jsdoc/src/visitor.js @@ -566,6 +566,10 @@ Visitor.prototype.makeSymbolFoundEvent = function(node, parser, filename) { break; + // like: var foo = () => {}; + case Syntax.ArrowFunctionExpression: + // falls through + // like: function foo() {} case Syntax.FunctionDeclaration: // falls through @@ -671,13 +675,6 @@ Visitor.prototype.makeSymbolFoundEvent = function(node, parser, filename) { break; - // log a warning for ES 2015 nodes that are not currently handled - case Syntax.ArrowFunctionExpression: - logger.warn('JSDoc does not currently handle %s nodes. Source file: %s, line %s', - node.type, filename, (node.loc && node.loc.start) ? node.loc.start.line : '??'); - - break; - default: // ignore } diff --git a/lib/jsdoc/src/walker.js b/lib/jsdoc/src/walker.js index fbca5218..cbde904f 100644 --- a/lib/jsdoc/src/walker.js +++ b/lib/jsdoc/src/walker.js @@ -21,7 +21,8 @@ var Syntax = require('jsdoc/src/syntax').Syntax; function isScopeNode(node) { // TODO: handle blocks with "let" declarations return node && typeof node === 'object' && (node.type === Syntax.CatchClause || - node.type === Syntax.FunctionDeclaration || node.type === Syntax.FunctionExpression); + node.type === Syntax.FunctionDeclaration || node.type === Syntax.FunctionExpression || + node.type === Syntax.ArrowFunctionExpression); } // TODO: docs @@ -66,7 +67,6 @@ walkers[Syntax.ArrowFunctionExpression] = function(node, parent, state, cb) { var i; var l; - // used for function declarations, so we include it here if (node.id) { cb(node.id, node, state); } diff --git a/test/fixtures/arrowfunction.js b/test/fixtures/arrowfunction.js new file mode 100644 index 00000000..c2357e11 --- /dev/null +++ b/test/fixtures/arrowfunction.js @@ -0,0 +1,11 @@ +/** + * Increment a number by 1. + * + * @param {number} n - The number to increment. + */ +var increment = n => n + 1; + +/** + * Print a value to the console. + */ +var print = (/** @type {*} */ val) => console.log(val); diff --git a/test/specs/documentation/arrowfunction.js b/test/specs/documentation/arrowfunction.js new file mode 100644 index 00000000..bedb5f18 --- /dev/null +++ b/test/specs/documentation/arrowfunction.js @@ -0,0 +1,24 @@ +'use strict'; + +if (jasmine.jsParser !== 'rhino') { + describe('arrow functions', function() { + var docSet = jasmine.getDocSetFromFile('test/fixtures/arrowfunction.js'); + var increment = docSet.getByLongname('increment')[0]; + var print = docSet.getByLongname('print')[0]; + + it('should use the correct name and longname', function() { + expect(increment).toBeDefined(); + expect(increment.name).toBe('increment'); + }); + + it('should allow function parameters to be documented', function() { + expect(increment.params.length).toBe(1); + expect(increment.params[0].name).toBe('n'); + }); + + it('should support inline comments on parameters', function() { + expect(print.params.length).toBe(1); + expect(print.params[0].type.names[0]).toBe('*'); + }); + }); +} diff --git a/test/specs/jsdoc/src/astnode.js b/test/specs/jsdoc/src/astnode.js index 7c6fdaba..1d9831ad 100644 --- a/test/specs/jsdoc/src/astnode.js +++ b/test/specs/jsdoc/src/astnode.js @@ -14,6 +14,7 @@ describe('jsdoc/src/astNode', function() { // create the AST nodes we'll be testing var arrayExpression = parse('[,]').body[0].expression; + var arrowFunctionExpression = parse('var foo = () => {};').body[0].declarations[0].init; var assignmentExpression = parse('foo = 1;').body[0].expression; var binaryExpression = parse('foo & foo;').body[0].expression; var functionDeclaration1 = parse('function foo() {}').body[0]; @@ -28,6 +29,7 @@ describe('jsdoc/src/astNode', function() { var memberExpression = parse('foo.bar;').body[0].expression; var memberExpressionComputed1 = parse('foo["bar"];').body[0].expression; var memberExpressionComputed2 = parse('foo[\'bar\'];').body[0].expression; + var methodDefinition = parse('class Foo { bar() {} }').body[0].body.body[0]; var propertyGet = parse('var foo = { get bar() {} };').body[0].declarations[0].init .properties[0]; var propertyInit = parse('var foo = { bar: {} };').body[0].declarations[0].init.properties[0]; @@ -65,6 +67,10 @@ describe('jsdoc/src/astNode', function() { expect(typeof astNode.isAssignment).toBe('function'); }); + it('should export an isFunction method', function() { + expect(typeof astNode.isFunction).toBe('function'); + }); + it('should export an isScope method', function() { expect(typeof astNode.isScope).toBe('function'); }); @@ -489,6 +495,28 @@ describe('jsdoc/src/astNode', function() { }); }); + describe('isFunction', function() { + it('should recognize function declarations as functions', function() { + expect( astNode.isFunction(functionDeclaration1) ).toBe(true); + }); + + it('should recognize function expressions as functions', function() { + expect( astNode.isFunction(functionExpression1) ).toBe(true); + }); + + it('should recognize method definitions as functions', function() { + expect( astNode.isFunction(methodDefinition) ).toBe(true); + }); + + it('should recognize arrow function expressions as functions', function() { + expect( astNode.isFunction(arrowFunctionExpression) ).toBe(true); + }); + + it('should recognize non-functions', function() { + expect( astNode.isFunction(arrayExpression) ).toBe(false); + }); + }); + describe('isScope', function() { it('should return false for undefined values', function() { expect( astNode.isScope() ).toBe(false);