support class properties, including private properties

This commit is contained in:
Jeff Williams 2017-07-04 18:01:34 -07:00
parent fb37938b77
commit 47005e9646
8 changed files with 112 additions and 15 deletions

View File

@ -8,6 +8,8 @@ var parserOptions = exports.parserOptions = {
ranges: true,
sourceType: 'module',
plugins: [
'classPrivateProperties',
'classProperties',
'decorators2',
'doExpressions',
'estree',

View File

@ -46,6 +46,7 @@ var isFunction = exports.isFunction = function(node) {
var isScope = exports.isScope = function(node) {
// TODO: handle blocks with "let" declarations
return !!node && typeof node === 'object' && ( node.type === Syntax.CatchClause ||
node.type === Syntax.ClassDeclaration || node.type === Syntax.ClassExpression ||
isFunction(node) );
};
@ -138,6 +139,20 @@ var nodeToValue = exports.nodeToValue = function(node) {
str = nodeToValue(node.id);
break;
case Syntax.ClassPrivateProperty:
// TODO: Strictly speaking, the name should be '#' plus node.key, but because we
// already use '#' as scope punctuation, that causes JSDoc to get extremely confused.
// The solution probably involves quoting part or all of the name, but JSDoc doesn't
// deal with quoted names very nicely right now, and most people probably won't want to
// document class private properties anyhow. So for now, we'll just cheat and omit the
// leading '#'.
str = nodeToValue(node.key);
break;
case Syntax.ClassProperty:
str = nodeToValue(node.key);
break;
case Syntax.ExportAllDeclaration:
// falls through
@ -382,6 +397,20 @@ var getInfo = exports.getInfo = function(node) {
break;
// like "#b = 1;" in: "class A { #b = 1; }"
case Syntax.ClassPrivateProperty:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
break;
// like "b = 1;" in: "class A { b = 1; }"
case Syntax.ClassProperty:
info.node = node;
info.name = nodeToValue(info.node);
info.type = info.node.type;
break;
// like: "export * from 'foo'"
case Syntax.ExportAllDeclaration:
info.node = node;

View File

@ -354,6 +354,11 @@ Parser.prototype.astnodeToMemberof = function(node) {
result.memberof = doclet.longname + jsdoc.name.SCOPE.PUNC.INNER;
}
}
else if (type === Syntax.ClassPrivateProperty || type === Syntax.ClassProperty) {
doclet = this._getDocletById(node.enclosingScope.nodeId);
result.memberof = doclet.longname + jsdoc.name.SCOPE.PUNC.INSTANCE;
}
else {
// check local references for aliases
scope = node;

View File

@ -16,6 +16,8 @@ exports.Syntax = {
ClassBody: 'ClassBody',
ClassDeclaration: 'ClassDeclaration',
ClassExpression: 'ClassExpression',
ClassPrivateProperty: 'ClassPrivateProperty',
ClassProperty: 'ClassProperty',
ComprehensionBlock: 'ComprehensionBlock',
ComprehensionExpression: 'ComprehensionExpression',
ConditionalExpression: 'ConditionalExpression',
@ -65,6 +67,7 @@ exports.Syntax = {
NewExpression: 'NewExpression',
ObjectExpression: 'ObjectExpression',
ObjectPattern: 'ObjectPattern',
PrivateName: 'PrivateName',
Program: 'Program',
Property: 'Property',
RestElement: 'RestElement',

View File

@ -346,6 +346,19 @@ function makeAsyncFunctionFinisher(parser) {
};
}
/**
* Create a function that will mark a doclet as private.
*
* @private
* @param {module:jsdoc/src/parser.Parser} parser - The JSDoc parser.
* @return {function} A function that marks a doclet as private.
*/
function makePrivatePropertyFinisher(parser) {
return function(e) {
e.doclet.access = 'private';
};
}
// TODO: docs
function SymbolFound(node, filename, extras) {
var self = this;
@ -433,7 +446,6 @@ Visitor.prototype.visit = function(node, filename) {
return true;
};
// TODO: docs
/**
* Verify that a block comment exists and that its leading delimiter does not contain three or more
* asterisks.
@ -613,6 +625,25 @@ Visitor.prototype.makeSymbolFoundEvent = function(node, parser, filename) {
break;
// like `#b = 1` in: class A { #b = 1; }
case Syntax.ClassPrivateProperty:
extras.finishers = [
parser.resolveEnum,
makePrivatePropertyFinisher(parser)
];
e = new SymbolFound(node, filename, extras);
break;
// like `b = 1` in: class A { b = 1; }
case Syntax.ClassProperty:
extras.finishers = [parser.resolveEnum];
e = new SymbolFound(node, filename, extras);
break;
// like: export * from 'foo'
case Syntax.ExportAllDeclaration:
e = new SymbolFound(node, filename, extras);

View File

@ -12,20 +12,6 @@ var doclet = require('jsdoc/doclet');
var logger = require('jsdoc/util/logger');
var Syntax = require('jsdoc/src/syntax').Syntax;
/**
* Check whether an AST node creates a new scope.
*
* @private
* @param {Object} node - The AST node to check.
* @return {Boolean} Set to `true` if the node creates a new scope, or `false` in all other cases.
*/
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.ArrowFunctionExpression);
}
// TODO: docs
function getCurrentScope(scopes) {
return scopes[scopes.length - 1] || null;
@ -159,6 +145,10 @@ walkers[Syntax.ClassDeclaration] = function(node, parent, state, cb) {
walkers[Syntax.ClassExpression] = walkers[Syntax.ClassDeclaration];
// walkers[Syntax.ClassPrivateProperty] is defined later
// walkers[Syntax.ClassProperty] is defined later
// TODO: verify correctness
walkers[Syntax.ComprehensionBlock] = walkers[Syntax.AssignmentExpression];
@ -448,6 +438,10 @@ walkers[Syntax.ObjectExpression] = function(node, parent, state, cb) {
walkers[Syntax.ObjectPattern] = walkers[Syntax.ObjectExpression];
walkers[Syntax.PrivateName] = function(node, parent, state, cb) {
cb(node.name, node, state);
};
walkers[Syntax.Program] = function(node, parent, state, cb) {
// if the first item in the body has multiple leading comments, move all but the last one to
// this node. this happens, for example, when a file has a /** @module */ standalone comment
@ -479,6 +473,10 @@ walkers[Syntax.Property] = function(node, parent, state, cb) {
}
};
walkers[Syntax.ClassPrivateProperty] = walkers[Syntax.Property];
walkers[Syntax.ClassProperty] = walkers[Syntax.Property];
walkers[Syntax.RestElement] = function(node, parent, state, cb) {
if (node.argument) {
cb(node.argument, node, state);

8
test/fixtures/classproperties.js vendored Normal file
View File

@ -0,0 +1,8 @@
/** Sample class. */
class A {
/** Public property. */
b = 1;
/** Private property. */
#c = 2;
}

View File

@ -0,0 +1,21 @@
'use strict';
describe('class properties', function() {
var docSet = jasmine.getDocSetFromFile('test/fixtures/classproperties.js');
var b = docSet.getByLongname('A#b')[0];
var c = docSet.getByLongname('A#c')[0];
it('should assign the correct name, memberof, and scope to class properties', function() {
expect(b.name).toBe('b');
expect(b.memberof).toBe('A');
expect(b.scope).toBe('instance');
});
it('should assign the correct name, memberof, scope, and access type to class private ' +
'properties', function() {
expect(c.name).toBe('c');
expect(c.memberof).toBe('A');
expect(c.scope).toBe('instance');
expect(c.access).toBe('private');
});
});