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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -58,17 +58,9 @@ module.exports = async function lint(page, mdSources, jsSources) {
* @return {!Documentation} * @return {!Documentation}
*/ */
function filterJSDocumentation(jsSources, jsDocumentation) { 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. // Filter private classes and methods.
const classes = []; const classes = [];
for (const cls of jsDocumentation.classesArray) { 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}`)); const members = cls.membersArray.filter(member => !EXCLUDE_PROPERTIES.has(`${cls.name}.${member.name}`));
classes.push(new Documentation.Class(cls.name, members)); classes.push(new Documentation.Class(cls.name, members));
} }