docs: support interfaces in doclint (#420)

This commit is contained in:
Dmitry Gozman 2020-01-08 14:04:33 -08:00 committed by GitHub
parent 73b148a0c6
commit 57c3916b0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 40 additions and 36 deletions

View file

@ -15,14 +15,14 @@
*/
import { BrowserContext, BrowserContextOptions } from './browserContext';
import { EventEmitter } from './platform';
import * as platform from './platform';
export class Browser extends EventEmitter {
newContext(options?: BrowserContextOptions): Promise<BrowserContext> { throw new Error('Not implemented'); }
browserContexts(): BrowserContext[] { throw new Error('Not implemented'); }
defaultContext(): BrowserContext { throw new Error('Not implemented'); }
export interface Browser extends platform.EventEmitterType {
newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
browserContexts(): BrowserContext[];
defaultContext(): BrowserContext;
disconnect(): void { throw new Error('Not implemented'); }
isConnected(): boolean { throw new Error('Not implemented'); }
close(): Promise<void> { throw new Error('Not implemented'); }
disconnect(): void;
isConnected(): boolean;
close(): Promise<void>;
}

View file

@ -24,7 +24,7 @@ import { Page, Worker } from '../page';
import { CRTarget } from './crTarget';
import { Protocol } from './protocol';
import { CRPage } from './crPage';
import * as browser from '../browser';
import { Browser } from '../browser';
import * as network from '../network';
import * as types from '../types';
import * as platform from '../platform';
@ -38,7 +38,7 @@ export type CRConnectOptions = {
transport?: ConnectionTransport;
};
export class CRBrowser extends browser.Browser {
export class CRBrowser extends platform.EventEmitter implements Browser {
_connection: CRConnection;
_client: CRSession;
private _defaultContext: BrowserContext;

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
import * as browser from '../browser';
import { Browser } from '../browser';
import { BrowserContext, BrowserContextOptions } from '../browserContext';
import { Events } from '../events';
import { assert, helper, RegisteredListener } from '../helper';
@ -33,7 +33,7 @@ export type FFConnectOptions = {
transport?: ConnectionTransport;
};
export class FFBrowser extends browser.Browser {
export class FFBrowser extends platform.EventEmitter implements Browser {
_connection: FFConnection;
_targets: Map<string, Target>;
private _defaultContext: BrowserContext;

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
import * as browser from '../browser';
import { Browser } from '../browser';
import { BrowserContext, BrowserContextOptions } from '../browserContext';
import { assert, helper, RegisteredListener } from '../helper';
import * as network from '../network';
@ -26,13 +26,14 @@ import { Events } from '../events';
import { Protocol } from './protocol';
import { WKConnection, WKConnectionEvents, WKPageProxySession } from './wkConnection';
import { WKPageProxy } from './wkPageProxy';
import * as platform from '../platform';
export type WKConnectOptions = {
slowMo?: number,
transport: ConnectionTransport;
};
export class WKBrowser extends browser.Browser {
export class WKBrowser extends platform.EventEmitter implements Browser {
readonly _connection: WKConnection;
private readonly _defaultContext: BrowserContext;
private readonly _contexts = new Map<string, BrowserContext>();

View file

@ -126,6 +126,7 @@ Documentation.Member = class {
* @param {!Array<!Documentation.Member>} argsArray
*/
constructor(kind, name, type, argsArray, comment = '', returnComment = '', required = true) {
if (name === 'code') debugger;
this.kind = kind;
this.name = name;
this.type = type;

View file

@ -45,33 +45,40 @@ function checkSources(sources) {
const checker = program.getTypeChecker();
const sourceFiles = program.getSourceFiles();
const errors = [];
const apiClassNames = new Set();
/** @type {!Array<!Documentation.Class>} */
const classes = [];
/** @type {!Map<string, string>} */
/** @type {!Map<string, string[]>} */
const inheritance = new Map();
sourceFiles.filter(x => !x.fileName.includes('node_modules')).map(x => visit(x));
const documentation = new Documentation(recreateClassesWithInheritance(classes, inheritance));
const documentation = new Documentation(recreateClassesWithInheritance(classes, inheritance).filter(cls => apiClassNames.has(cls.name)));
return {errors, documentation};
/**
* @param {!Array<!Documentation.Class>} classes
* @param {!Map<string, string>} inheritance
* @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) {
const visit = cls => {
if (!cls)
return;
for (const member of cls.membersArray) {
// Member was overridden.
const memberId = member.kind + ':' + member.name;
if (membersMap.has(memberId))
continue;
membersMap.set(memberId, member);
}
}
const parents = inheritance.get(cls.name) || [];
for (const parent of parents)
visit(classesByName.get(parent));
};
visit(cls);
return new Documentation.Class(expandPrefix(cls.name), Array.from(membersMap.values()));
});
}
@ -80,7 +87,8 @@ function checkSources(sources) {
* @param {!ts.Node} node
*/
function visit(node) {
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
const fileName = node.getSourceFile().fileName;
if (ts.isClassDeclaration(node) || ts.isClassExpression(node) || ts.isInterfaceDeclaration(node)) {
const symbol = node.name ? checker.getSymbolAtLocation(node.name) : node.symbol;
let className = symbol.getName();
@ -92,13 +100,12 @@ function checkSources(sources) {
}
if (className && !excludeClasses.has(className)) {
classes.push(serializeClass(className, symbol, node));
const parentClassName = parentClass(node);
if (parentClassName)
inheritance.set(className, parentClassName);
inheritance.set(className, parentClasses(node));
excludeClasses.add(className);
}
}
const fileName = node.getSourceFile().fileName;
if (fileName.endsWith('/api.ts') && ts.isExportSpecifier(node))
apiClassNames.add(expandPrefix((node.propertyName || node.name).text));
if (!fileName.endsWith('platform.ts') && !fileName.includes('src/server/')) {
// Only relative imports.
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
@ -126,17 +133,18 @@ function checkSources(sources) {
ts.forEachChild(node, visit);
}
function parentClass(classNode) {
function parentClasses(classNode) {
const parents = [];
for (const herigateClause of classNode.heritageClauses || []) {
for (const heritageType of herigateClause.types) {
let expression = heritageType.expression;
if (expression.kind === ts.SyntaxKind.PropertyAccessExpression)
expression = expression.name;
if (classNode.name.escapedText !== expression.escapedText)
return expression.escapedText;
parents.push(expression.escapedText);
}
}
return null;
return parents;
}
function serializeSymbol(symbol, circular = []) {
@ -225,6 +233,8 @@ function checkSources(sources) {
/** @type {!Array<!Documentation.Member>} */
const members = classEvents.get(className) || [];
for (const [name, member] of symbol.members || []) {
if (className === 'Error')
continue;
if (name.startsWith('_'))
continue;
if (EventEmitter.prototype.hasOwnProperty(name))

View file

@ -58,17 +58,9 @@ module.exports = async function lint(page, mdSources, jsSources) {
* @return {!Documentation}
*/
function filterJSDocumentation(jsSources, jsDocumentation) {
const apijs = jsSources.find(source => source.name() === 'api.ts');
let includedClasses = null;
if (apijs) {
const api = require(path.join(apijs.filePath(), '..', '..', 'lib', 'api.js'));
includedClasses = new Set(Object.keys(api).map(c => jsBuilder.expandPrefix(c)));
}
// Filter private classes and methods.
const classes = [];
for (const cls of jsDocumentation.classesArray) {
if (includedClasses && !includedClasses.has(cls.name))
continue;
const members = cls.membersArray.filter(member => !EXCLUDE_PROPERTIES.has(`${cls.name}.${member.name}`));
classes.push(new Documentation.Class(cls.name, members));
}