chore(doclint): support classes inheritance (#935)

This patch:
- gives meaningful names to doclint tests
- supports classes inheritance in documentation linter. When class A
  extends class B, all methods of class B are added to documentation of
  class A.

This is a prerequisite for Object Handles: ElementHandle will be
extending ObjectHandle.

References #382
This commit is contained in:
Andrey Lushnikov 2017-10-02 13:38:44 -07:00 committed by GitHub
parent 8bcf550bb6
commit 6c9a99477b
33 changed files with 127 additions and 11 deletions

View File

@ -21,6 +21,8 @@ const Documentation = require('./Documentation');
class JSOutline { class JSOutline {
constructor(text) { constructor(text) {
this.classes = []; this.classes = [];
/** @type {!Map<string, string>} */
this.inheritance = new Map();
this.errors = []; this.errors = [];
this._eventsByClassName = new Map(); this._eventsByClassName = new Map();
this._currentClassName = null; this._currentClassName = null;
@ -44,6 +46,9 @@ class JSOutline {
_onClassDeclaration(node) { _onClassDeclaration(node) {
this._flushClassIfNeeded(); this._flushClassIfNeeded();
this._currentClassName = this._extractText(node.id); this._currentClassName = this._extractText(node.id);
const superClass = this._extractText(node.superClass);
if (superClass)
this.inheritance.set(this._currentClassName, superClass);
} }
_onMethodDefinition(node) { _onMethodDefinition(node) {
@ -140,6 +145,31 @@ class JSOutline {
} }
} }
/**
* @param {!Array<!Documentation.Class>} classes
* @param {!Map<string, string>} inheritance
* @return {!Array<!Documentation.Class>}
*/
function recreateClassesWithInheritance(classes, inheritance) {
const classesByName = new Map(classes.map(cls => [cls.name, cls]));
return classes.map(cls => {
const membersMap = new Map();
for (let wp = cls; wp; wp = classesByName.get(inheritance.get(wp.name))) {
for (const member of wp.membersArray) {
// Member was overridden.
const memberId = member.type + ':' + member.name;
if (membersMap.has(memberId))
continue;
// Do not inherit constructors
if (wp !== cls && member.name === 'constructor' && member.type === 'method')
continue;
membersMap.set(memberId, member);
}
}
return new Documentation.Class(cls.name, Array.from(membersMap.values()));
});
}
/** /**
* @param {!Array<!Source>} sources * @param {!Array<!Source>} sources
* @return {!Promise<{documentation: !Documentation, errors: !Array<string>}>} * @return {!Promise<{documentation: !Documentation, errors: !Array<string>}>}
@ -147,12 +177,15 @@ class JSOutline {
module.exports = async function(sources) { module.exports = async function(sources) {
const classes = []; const classes = [];
const errors = []; const errors = [];
const inheritance = new Map();
for (const source of sources) { for (const source of sources) {
const outline = new JSOutline(source.text()); const outline = new JSOutline(source.text());
classes.push(...outline.classes); classes.push(...outline.classes);
errors.push(...outline.errors); errors.push(...outline.errors);
for (const entry of outline.inheritance)
inheritance.set(entry[0], entry[1]);
} }
const documentation = new Documentation(classes); const documentation = new Documentation(recreateClassesWithInheritance(classes, inheritance));
return { documentation, errors }; return { documentation, errors };
}; };

View File

@ -0,0 +1,20 @@
class A {
constructor() {
}
foo(a) {
}
bar() {
}
}
class B extends A {
bar(override) {
}
}
B.Events = {
// Event with the same name as a super class method.
foo: 'foo'
};

View File

@ -0,0 +1,62 @@
{
"classes": [
{
"name": "A",
"members": [
{
"name": "constructor",
"type": "method",
"hasReturn": false,
"async": false,
"args": []
},
{
"name": "foo",
"type": "method",
"hasReturn": false,
"async": false,
"args": [
"a"
]
},
{
"name": "bar",
"type": "method",
"hasReturn": false,
"async": false,
"args": []
}
]
},
{
"name": "B",
"members": [
{
"name": "bar",
"type": "method",
"hasReturn": false,
"async": false,
"args": [
"override"
]
},
{
"name": "foo",
"type": "event",
"hasReturn": false,
"async": false,
"args": []
},
{
"name": "foo",
"type": "method",
"hasReturn": false,
"async": false,
"args": [
"a"
]
}
]
}
]
}

View File

@ -47,16 +47,17 @@ afterAll(SX(async function() {
})); }));
describe('checkPublicAPI', function() { describe('checkPublicAPI', function() {
it('01-class-errors', SX(testLint)); it('diff-classes', SX(testLint));
it('02-method-errors', SX(testLint)); it('diff-methods', SX(testLint));
it('03-property-errors', SX(testLint)); it('diff-properties', SX(testLint));
it('04-bad-arguments', SX(testLint)); it('diff-arguments', SX(testLint));
it('05-event-errors', SX(testLint)); it('diff-events', SX(testLint));
it('06-duplicates', SX(testLint)); it('check-duplicates', SX(testLint));
it('07-sorting', SX(testLint)); it('check-sorting', SX(testLint));
it('08-return', SX(testLint)); it('check-returns', SX(testLint));
it('09-js-builder', SX(testJSBuilder)); it('js-builder-common', SX(testJSBuilder));
it('10-md-builder', SX(testMDBuilder)); it('js-builder-inheritance', SX(testJSBuilder));
it('md-builder-common', SX(testMDBuilder));
}); });
async function testLint() { async function testLint() {