chore(types): lint the api docs with typescript (#3577)
This commit is contained in:
parent
fb5b0800ec
commit
a0cbaf39ab
12
docs/api.md
12
docs/api.md
@ -1102,7 +1102,7 @@ Get the browser the page belongs to.
|
|||||||
#### page.click(selector[, options])
|
#### page.click(selector[, options])
|
||||||
- `selector` <[string]> A [selector] to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
|
- `selector` <[string]> A [selector] to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
|
||||||
- `options` <[Object]>
|
- `options` <[Object]>
|
||||||
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
- `button` <"left"|"right"|"middle"> Defaults to `left`.
|
||||||
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
|
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
|
||||||
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
|
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
|
||||||
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully clicked. The Promise will be rejected if there is no element matching `selector`.
|
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully clicked. The Promise will be rejected if there is no element matching `selector`.
|
||||||
@ -2183,7 +2183,7 @@ await page.mouse.up();
|
|||||||
- `x` <[number]>
|
- `x` <[number]>
|
||||||
- `y` <[number]>
|
- `y` <[number]>
|
||||||
- `options` <[Object]>
|
- `options` <[Object]>
|
||||||
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
- `button` <"left"|"right"|"middle"> Defaults to `left`.
|
||||||
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
|
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
|
||||||
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
|
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
|
||||||
- returns: <[Promise]>
|
- returns: <[Promise]>
|
||||||
@ -2192,7 +2192,7 @@ Shortcut for [`mouse.move`](#mousemovex-y-options), [`mouse.down`](#mousedownopt
|
|||||||
|
|
||||||
#### mouse.down([options])
|
#### mouse.down([options])
|
||||||
- `options` <[Object]>
|
- `options` <[Object]>
|
||||||
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
- `button` <"left"|"right"|"middle"> Defaults to `left`.
|
||||||
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
|
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
|
||||||
- returns: <[Promise]>
|
- returns: <[Promise]>
|
||||||
|
|
||||||
@ -2209,7 +2209,7 @@ Dispatches a `mousemove` event.
|
|||||||
|
|
||||||
#### mouse.up([options])
|
#### mouse.up([options])
|
||||||
- `options` <[Object]>
|
- `options` <[Object]>
|
||||||
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
- `button` <"left"|"right"|"middle"> Defaults to `left`.
|
||||||
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
|
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
|
||||||
- returns: <[Promise]>
|
- returns: <[Promise]>
|
||||||
|
|
||||||
@ -2407,7 +2407,7 @@ Adds a `<link rel="stylesheet">` tag into the page with the desired url or a `<s
|
|||||||
#### frame.click(selector[, options])
|
#### frame.click(selector[, options])
|
||||||
- `selector` <[string]> A [selector] to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
|
- `selector` <[string]> A [selector] to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
|
||||||
- `options` <[Object]>
|
- `options` <[Object]>
|
||||||
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
- `button` <"left"|"right"|"middle"> Defaults to `left`.
|
||||||
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
|
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
|
||||||
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
|
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
|
||||||
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully clicked. The Promise will be rejected if there is no element matching `selector`.
|
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully clicked. The Promise will be rejected if there is no element matching `selector`.
|
||||||
@ -3000,7 +3000,7 @@ This method returns boxes of the element, or `null` if the element is not visibl
|
|||||||
|
|
||||||
#### elementHandle.click([options])
|
#### elementHandle.click([options])
|
||||||
- `options` <[Object]>
|
- `options` <[Object]>
|
||||||
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
- `button` <"left"|"right"|"middle"> Defaults to `left`.
|
||||||
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
|
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
|
||||||
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
|
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
|
||||||
- returns: <[Promise]> Promise which resolves when the element is successfully clicked. Promise gets rejected if the element is detached from DOM.
|
- returns: <[Promise]> Promise which resolves when the element is successfully clicked. Promise gets rejected if the element is detached from DOM.
|
||||||
|
@ -20,6 +20,7 @@ class Documentation {
|
|||||||
*/
|
*/
|
||||||
constructor(classesArray) {
|
constructor(classesArray) {
|
||||||
this.classesArray = classesArray;
|
this.classesArray = classesArray;
|
||||||
|
/** @type {!Map<string, !Documentation.Class>} */
|
||||||
this.classes = new Map();
|
this.classes = new Map();
|
||||||
for (const cls of classesArray)
|
for (const cls of classesArray)
|
||||||
this.classes.set(cls.name, cls);
|
this.classes.set(cls.name, cls);
|
||||||
@ -34,17 +35,21 @@ Documentation.Class = class {
|
|||||||
constructor(name, membersArray) {
|
constructor(name, membersArray) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.membersArray = membersArray;
|
this.membersArray = membersArray;
|
||||||
|
/** @type {!Map<string, !Documentation.Member>} */
|
||||||
this.members = new Map();
|
this.members = new Map();
|
||||||
|
/** @type {!Map<string, !Documentation.Member>} */
|
||||||
this.properties = new Map();
|
this.properties = new Map();
|
||||||
|
/** @type {!Map<string, !Documentation.Member>} */
|
||||||
this.methods = new Map();
|
this.methods = new Map();
|
||||||
|
/** @type {!Map<string, !Documentation.Member>} */
|
||||||
this.events = new Map();
|
this.events = new Map();
|
||||||
for (const member of membersArray) {
|
for (const member of membersArray) {
|
||||||
this.members.set(member.name, member);
|
this.members.set(member.name, member);
|
||||||
if (member.type === 'method')
|
if (member.kind === 'method')
|
||||||
this.methods.set(member.name, member);
|
this.methods.set(member.name, member);
|
||||||
else if (member.type === 'property')
|
else if (member.kind === 'property')
|
||||||
this.properties.set(member.name, member);
|
this.properties.set(member.name, member);
|
||||||
else if (member.type === 'event')
|
else if (member.kind === 'event')
|
||||||
this.events.set(member.name, member);
|
this.events.set(member.name, member);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,39 +57,39 @@ Documentation.Class = class {
|
|||||||
|
|
||||||
Documentation.Member = class {
|
Documentation.Member = class {
|
||||||
/**
|
/**
|
||||||
* @param {string} type
|
* @param {string} kind
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {!Array<!Documentation.Argument>} argsArray
|
* @param {!Documentation.Type} type
|
||||||
* @param {boolean} hasReturn
|
* @param {!Array<!Documentation.Member>} argsArray
|
||||||
* @param {boolean} async
|
|
||||||
*/
|
*/
|
||||||
constructor(type, name, argsArray, hasReturn, async) {
|
constructor(kind, name, type, argsArray) {
|
||||||
this.type = type;
|
this.kind = kind;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
this.argsArray = argsArray;
|
this.argsArray = argsArray;
|
||||||
|
/** @type {!Map<string, !Documentation.Member>} */
|
||||||
this.args = new Map();
|
this.args = new Map();
|
||||||
this.hasReturn = hasReturn;
|
|
||||||
this.async = async;
|
|
||||||
for (const arg of argsArray)
|
for (const arg of argsArray)
|
||||||
this.args.set(arg.name, arg);
|
this.args.set(arg.name, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {!Array<!Documentation.Argument>} argsArray
|
* @param {!Array<!Documentation.Member>} argsArray
|
||||||
* @param {boolean} hasReturn
|
* @param {?Documentation.Type} returnType
|
||||||
* @return {!Documentation.Member}
|
* @return {!Documentation.Member}
|
||||||
*/
|
*/
|
||||||
static createMethod(name, argsArray, hasReturn, async) {
|
static createMethod(name, argsArray, returnType) {
|
||||||
return new Documentation.Member('method', name, argsArray, hasReturn, async);
|
return new Documentation.Member('method', name, returnType, argsArray,);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
|
* @param {!Documentation.Type}
|
||||||
* @return {!Documentation.Member}
|
* @return {!Documentation.Member}
|
||||||
*/
|
*/
|
||||||
static createProperty(name) {
|
static createProperty(name, type) {
|
||||||
return new Documentation.Member('property', name, [], false, false);
|
return new Documentation.Member('property', name, type, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,16 +97,18 @@ Documentation.Member = class {
|
|||||||
* @return {!Documentation.Member}
|
* @return {!Documentation.Member}
|
||||||
*/
|
*/
|
||||||
static createEvent(name) {
|
static createEvent(name) {
|
||||||
return new Documentation.Member('event', name, [], false, false);
|
return new Documentation.Member('event', name, null, []);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Documentation.Argument = class {
|
Documentation.Type = class {
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
|
* @param {!Array<!Documentation.Member>=} properties
|
||||||
*/
|
*/
|
||||||
constructor(name) {
|
constructor(name, properties = []) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,199 +1,204 @@
|
|||||||
/**
|
const ts = require('typescript');
|
||||||
* Copyright 2017 Google Inc. All rights reserved.
|
const path = require('path');
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const assert = require('assert');
|
|
||||||
const esprima = require('esprima');
|
|
||||||
const ESTreeWalker = require('../../ESTreeWalker');
|
|
||||||
const Documentation = require('./Documentation');
|
const Documentation = require('./Documentation');
|
||||||
|
module.exports = checkSources;
|
||||||
class JSOutline {
|
|
||||||
/**
|
|
||||||
* @param {string} text
|
|
||||||
* @param {string} name
|
|
||||||
*/
|
|
||||||
constructor(text, name) {
|
|
||||||
this.classes = [];
|
|
||||||
/** @type {!Map<string, string>} */
|
|
||||||
this.inheritance = new Map();
|
|
||||||
this.errors = [];
|
|
||||||
this._eventsByClassName = new Map();
|
|
||||||
this._currentClassName = null;
|
|
||||||
this._currentClassMembers = [];
|
|
||||||
this._name = name;
|
|
||||||
|
|
||||||
this._text = text;
|
|
||||||
const ast = esprima.parseScript(this._text, {loc: true, range: true});
|
|
||||||
const walker = new ESTreeWalker(node => {
|
|
||||||
if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression')
|
|
||||||
this._onClassDeclaration(node);
|
|
||||||
else if (node.type === 'MethodDefinition')
|
|
||||||
this._onMethodDefinition(node);
|
|
||||||
else if (node.type === 'AssignmentExpression')
|
|
||||||
this._onAssignmentExpression(node);
|
|
||||||
});
|
|
||||||
walker.walk(ast);
|
|
||||||
this._flushClassIfNeeded();
|
|
||||||
this._recreateClassesWithEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
_onClassDeclaration(node) {
|
|
||||||
this._flushClassIfNeeded();
|
|
||||||
this._currentClassName = this._extractText(node.id);
|
|
||||||
if (!this._currentClassName)
|
|
||||||
this._currentClassName = this._name.substring(0, this._name.indexOf('.'));
|
|
||||||
const superClass = this._extractText(node.superClass);
|
|
||||||
if (superClass)
|
|
||||||
this.inheritance.set(this._currentClassName, superClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onMethodDefinition(node) {
|
|
||||||
assert(this._currentClassName !== null);
|
|
||||||
assert(node.value.type === 'FunctionExpression');
|
|
||||||
const methodName = this._extractText(node.key);
|
|
||||||
if (node.kind === 'get') {
|
|
||||||
const property = Documentation.Member.createProperty(methodName);
|
|
||||||
this._currentClassMembers.push(property);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Async functions have return value.
|
|
||||||
let hasReturn = node.value.async;
|
|
||||||
// Extract properties from constructor.
|
|
||||||
if (node.kind === 'constructor') {
|
|
||||||
// Extract properties from constructor.
|
|
||||||
const walker = new ESTreeWalker(node => {
|
|
||||||
if (node.type !== 'AssignmentExpression')
|
|
||||||
return;
|
|
||||||
node = node.left;
|
|
||||||
if (node.type === 'MemberExpression' && node.object &&
|
|
||||||
node.object.type === 'ThisExpression' && node.property &&
|
|
||||||
node.property.type === 'Identifier')
|
|
||||||
this._currentClassMembers.push(Documentation.Member.createProperty(node.property.name));
|
|
||||||
});
|
|
||||||
walker.walk(node);
|
|
||||||
} else if (!hasReturn) {
|
|
||||||
const walker = new ESTreeWalker(node => {
|
|
||||||
if (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression')
|
|
||||||
return ESTreeWalker.SkipSubtree;
|
|
||||||
if (node.type === 'ReturnStatement')
|
|
||||||
hasReturn = hasReturn || !!node.argument;
|
|
||||||
});
|
|
||||||
walker.walk(node.value.body);
|
|
||||||
}
|
|
||||||
const args = [];
|
|
||||||
for (const param of node.value.params) {
|
|
||||||
if (param.type === 'AssignmentPattern' && param.left.name)
|
|
||||||
args.push(new Documentation.Argument(param.left.name));
|
|
||||||
else if (param.type === 'RestElement')
|
|
||||||
args.push(new Documentation.Argument('...' + param.argument.name));
|
|
||||||
else if (param.type === 'Identifier')
|
|
||||||
args.push(new Documentation.Argument(param.name));
|
|
||||||
else if (param.type === 'ObjectPattern' || param.type === 'AssignmentPattern')
|
|
||||||
args.push(new Documentation.Argument('options'));
|
|
||||||
else
|
|
||||||
this.errors.push(`JS Parsing issue: unsupported syntax to define parameter in ${this._currentClassName}.${methodName}(): ${this._extractText(param)}`);
|
|
||||||
}
|
|
||||||
const method = Documentation.Member.createMethod(methodName, args, hasReturn, node.value.async);
|
|
||||||
this._currentClassMembers.push(method);
|
|
||||||
return ESTreeWalker.SkipSubtree;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onAssignmentExpression(node) {
|
|
||||||
if (node.left.type !== 'MemberExpression' || node.right.type !== 'ObjectExpression')
|
|
||||||
return;
|
|
||||||
if (node.left.object.type !== 'Identifier' || node.left.property.type !== 'Identifier' || node.left.property.name !== 'Events')
|
|
||||||
return;
|
|
||||||
const className = node.left.object.name;
|
|
||||||
let events = this._eventsByClassName.get(className);
|
|
||||||
if (!events) {
|
|
||||||
events = [];
|
|
||||||
this._eventsByClassName.set(className, events);
|
|
||||||
}
|
|
||||||
for (const property of node.right.properties) {
|
|
||||||
if (property.type !== 'Property' || property.key.type !== 'Identifier' || property.value.type !== 'Literal')
|
|
||||||
continue;
|
|
||||||
events.push(Documentation.Member.createEvent(property.value.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_flushClassIfNeeded() {
|
|
||||||
if (this._currentClassName === null)
|
|
||||||
return;
|
|
||||||
const jsClass = new Documentation.Class(this._currentClassName, this._currentClassMembers);
|
|
||||||
this.classes.push(jsClass);
|
|
||||||
this._currentClassName = null;
|
|
||||||
this._currentClassMembers = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
_recreateClassesWithEvents() {
|
|
||||||
this.classes = this.classes.map(cls => {
|
|
||||||
const events = this._eventsByClassName.get(cls.name) || [];
|
|
||||||
const members = cls.membersArray.concat(events);
|
|
||||||
return new Documentation.Class(cls.name, members);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_extractText(node) {
|
|
||||||
if (!node)
|
|
||||||
return null;
|
|
||||||
const text = this._text.substring(node.range[0], node.range[1]).trim();
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!Array<!Documentation.Class>} classes
|
* @param {!Array<!import('../Source')>} sources
|
||||||
* @param {!Map<string, string>} inheritance
|
|
||||||
* @return {!Array<!Documentation.Class>}
|
|
||||||
*/
|
*/
|
||||||
function recreateClassesWithInheritance(classes, inheritance) {
|
function checkSources(sources) {
|
||||||
const classesByName = new Map(classes.map(cls => [cls.name, cls]));
|
const excludeClasses = new Set([]);
|
||||||
return classes.map(cls => {
|
const program = ts.createProgram({
|
||||||
const membersMap = new Map();
|
options: {
|
||||||
for (let wp = cls; wp; wp = classesByName.get(inheritance.get(wp.name))) {
|
allowJs: true,
|
||||||
for (const member of wp.membersArray) {
|
target: ts.ScriptTarget.ES2017
|
||||||
// Member was overridden.
|
},
|
||||||
const memberId = member.type + ':' + member.name;
|
rootNames: sources.map(source => source.filePath())
|
||||||
if (membersMap.has(memberId))
|
});
|
||||||
continue;
|
const checker = program.getTypeChecker();
|
||||||
// Do not inherit constructors
|
const sourceFiles = program.getSourceFiles();
|
||||||
if (wp !== cls && member.name === 'constructor' && member.type === 'method')
|
/** @type {!Array<!Documentation.Class>} */
|
||||||
continue;
|
const classes = [];
|
||||||
membersMap.set(memberId, member);
|
/** @type {!Map<string, string>} */
|
||||||
|
const inheritance = new Map();
|
||||||
|
sourceFiles.filter(x => !x.fileName.includes('node_modules')).map(x => visit(x));
|
||||||
|
const errors = [];
|
||||||
|
const documentation = new Documentation(recreateClassesWithInheritance(classes, inheritance));
|
||||||
|
return {errors, documentation};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.kind + ':' + member.name;
|
||||||
|
if (membersMap.has(memberId))
|
||||||
|
continue;
|
||||||
|
membersMap.set(memberId, member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Documentation.Class(cls.name, Array.from(membersMap.values()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!ts.Node} node
|
||||||
|
*/
|
||||||
|
function visit(node) {
|
||||||
|
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
|
||||||
|
const symbol = node.name ? checker.getSymbolAtLocation(node.name) : node.symbol;
|
||||||
|
let className = symbol.getName();
|
||||||
|
|
||||||
|
if (className === '__class') {
|
||||||
|
let parent = node;
|
||||||
|
while (parent.parent)
|
||||||
|
parent = parent.parent;
|
||||||
|
className = path.basename(parent.fileName, '.js');
|
||||||
|
}
|
||||||
|
if (className && !excludeClasses.has(className)) {
|
||||||
|
classes.push(serializeClass(className, symbol, node));
|
||||||
|
const parentClassName = parentClass(node);
|
||||||
|
if (parentClassName)
|
||||||
|
inheritance.set(className, parentClassName);
|
||||||
|
excludeClasses.add(className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Documentation.Class(cls.name, Array.from(membersMap.values()));
|
ts.forEachChild(node, visit);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {!Array<!Source>} sources
|
|
||||||
* @return {!Promise<{documentation: !Documentation, errors: !Array<string>}>}
|
|
||||||
*/
|
|
||||||
module.exports = async function(sources) {
|
|
||||||
const classes = [];
|
|
||||||
const errors = [];
|
|
||||||
const inheritance = new Map();
|
|
||||||
for (const source of sources) {
|
|
||||||
const outline = new JSOutline(source.text(), source.name());
|
|
||||||
classes.push(...outline.classes);
|
|
||||||
errors.push(...outline.errors);
|
|
||||||
for (const entry of outline.inheritance)
|
|
||||||
inheritance.set(entry[0], entry[1]);
|
|
||||||
}
|
}
|
||||||
const documentation = new Documentation(recreateClassesWithInheritance(classes, inheritance));
|
|
||||||
return { documentation, errors };
|
|
||||||
};
|
|
||||||
|
|
||||||
|
function parentClass(classNode) {
|
||||||
|
for (const herigateClause of classNode.heritageClauses || []) {
|
||||||
|
for (const heritageType of herigateClause.types) {
|
||||||
|
const parentClassName = heritageType.expression.escapedText;
|
||||||
|
return parentClassName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeSymbol(symbol, circular = []) {
|
||||||
|
const type = checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration);
|
||||||
|
const name = symbol.getName();
|
||||||
|
if (symbol.valueDeclaration.dotDotDotToken) {
|
||||||
|
const innerType = serializeType(type.typeArguments[0], circular);
|
||||||
|
innerType.name = '...' + innerType.name;
|
||||||
|
return Documentation.Member.createProperty('...' + name, innerType);
|
||||||
|
}
|
||||||
|
return Documentation.Member.createProperty(name, serializeType(type, circular));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!ts.ObjectType} type
|
||||||
|
*/
|
||||||
|
function isRegularObject(type) {
|
||||||
|
if (type.isIntersection())
|
||||||
|
return true;
|
||||||
|
if (!type.objectFlags)
|
||||||
|
return false;
|
||||||
|
if (!('aliasSymbol' in type))
|
||||||
|
return false;
|
||||||
|
if (type.getConstructSignatures().length)
|
||||||
|
return false;
|
||||||
|
if (type.getCallSignatures().length)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!ts.Type} type
|
||||||
|
* @return {!Documentation.Type}
|
||||||
|
*/
|
||||||
|
function serializeType(type, circular = []) {
|
||||||
|
let typeName = checker.typeToString(type);
|
||||||
|
if (typeName === 'any' || typeName === '{ [x: string]: string; }')
|
||||||
|
typeName = 'Object';
|
||||||
|
const nextCircular = [typeName].concat(circular);
|
||||||
|
|
||||||
|
if (isRegularObject(type)) {
|
||||||
|
let properties = undefined;
|
||||||
|
if (!circular.includes(typeName))
|
||||||
|
properties = type.getProperties().map(property => serializeSymbol(property, nextCircular));
|
||||||
|
return new Documentation.Type('Object', properties);
|
||||||
|
}
|
||||||
|
if (type.isUnion() && typeName.includes('|')) {
|
||||||
|
const types = type.types.map(type => serializeType(type, circular));
|
||||||
|
const name = types.map(type => type.name).join('|');
|
||||||
|
const properties = [].concat(...types.map(type => type.properties));
|
||||||
|
return new Documentation.Type(name.replace(/false\|true/g, 'boolean'), properties);
|
||||||
|
}
|
||||||
|
if (type.typeArguments) {
|
||||||
|
const properties = [];
|
||||||
|
const innerTypeNames = [];
|
||||||
|
for (const typeArgument of type.typeArguments) {
|
||||||
|
const innerType = serializeType(typeArgument, nextCircular);
|
||||||
|
if (innerType.properties)
|
||||||
|
properties.push(...innerType.properties);
|
||||||
|
innerTypeNames.push(innerType.name);
|
||||||
|
}
|
||||||
|
if (innerTypeNames.length === 1 && innerTypeNames[0] === 'void')
|
||||||
|
return new Documentation.Type(type.symbol.name);
|
||||||
|
return new Documentation.Type(`${type.symbol.name}<${innerTypeNames.join(', ')}>`, properties);
|
||||||
|
}
|
||||||
|
return new Documentation.Type(typeName, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} className
|
||||||
|
* @param {!ts.Symbol} symbol
|
||||||
|
* @return {}
|
||||||
|
*/
|
||||||
|
function serializeClass(className, symbol, node) {
|
||||||
|
/** @type {!Array<!Documentation.Member>} */
|
||||||
|
const members = [];
|
||||||
|
|
||||||
|
const type = checker.getTypeOfSymbolAtLocation(symbol, node);
|
||||||
|
const events = type.getProperty('Events');
|
||||||
|
if (events) {
|
||||||
|
const eventType = checker.getTypeAtLocation(events.valueDeclaration);
|
||||||
|
for (const property of eventType.getProperties()) {
|
||||||
|
if (property.valueDeclaration.initializer.text)
|
||||||
|
members.push(Documentation.Member.createEvent(property.valueDeclaration.initializer.text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [name, member] of symbol.members || []) {
|
||||||
|
if (name.startsWith('_'))
|
||||||
|
continue;
|
||||||
|
const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration);
|
||||||
|
const signature = memberType.getCallSignatures()[0];
|
||||||
|
if (signature)
|
||||||
|
members.push(serializeSignature(name, signature));
|
||||||
|
else
|
||||||
|
members.push(serializeProperty(name, memberType));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Documentation.Class(className, members);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {!ts.Signature} signature
|
||||||
|
*/
|
||||||
|
function serializeSignature(name, signature) {
|
||||||
|
const parameters = signature.parameters.map(s => serializeSymbol(s));
|
||||||
|
const returnType = serializeType(signature.getReturnType());
|
||||||
|
return Documentation.Member.createMethod(name, parameters, returnType.name !== 'void' ? returnType : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {!ts.Type} type
|
||||||
|
*/
|
||||||
|
function serializeProperty(name, type) {
|
||||||
|
return Documentation.Member.createProperty(name, serializeType(type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -48,13 +48,13 @@ class MDOutline {
|
|||||||
member = {
|
member = {
|
||||||
name: element.textContent,
|
name: element.textContent,
|
||||||
args: [],
|
args: [],
|
||||||
hasReturn: false
|
returnType: null
|
||||||
};
|
};
|
||||||
currentClass.members.push(member);
|
currentClass.members.push(member);
|
||||||
} else if (element.matches('li') && element.firstChild.matches && element.firstChild.matches('code')) {
|
} else if (element.matches('li') && element.firstChild.matches && element.firstChild.matches('code')) {
|
||||||
member.args.push(element.firstChild.textContent);
|
member.args.push(parseProperty(element));
|
||||||
} else if (element.matches('li') && element.firstChild.nodeType === Element.TEXT_NODE && element.firstChild.textContent.toLowerCase().startsWith('return')) {
|
} else if (element.matches('li') && element.firstChild.nodeType === Element.TEXT_NODE && element.firstChild.textContent.toLowerCase().startsWith('return')) {
|
||||||
member.hasReturn = true;
|
member.returnType = parseProperty(element);
|
||||||
const expectedText = 'returns: ';
|
const expectedText = 'returns: ';
|
||||||
let actualText = element.firstChild.textContent;
|
let actualText = element.firstChild.textContent;
|
||||||
let angleIndex = actualText.indexOf('<');
|
let angleIndex = actualText.indexOf('<');
|
||||||
@ -67,6 +67,39 @@ class MDOutline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {classes, errors};
|
return {classes, errors};
|
||||||
|
|
||||||
|
function parseProperty(element) {
|
||||||
|
const str = element.textContent;
|
||||||
|
const name = str.substring(0, str.indexOf('<')).trim();
|
||||||
|
const type = findType(str);
|
||||||
|
const properties = [];
|
||||||
|
// Strings have enum values instead of properties
|
||||||
|
if (!type.includes('string')) {
|
||||||
|
for (const childElement of element.querySelectorAll(':scope > ul > li'))
|
||||||
|
properties.push(parseProperty(childElement));
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
properties
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
function findType(str) {
|
||||||
|
const start = str.indexOf('<') + 1;
|
||||||
|
let count = 1;
|
||||||
|
for (let i = start; i < str.length; i++) {
|
||||||
|
if (str[i] === '<') count++;
|
||||||
|
if (str[i] === '>') count--;
|
||||||
|
if (!count)
|
||||||
|
return str.substring(start, i);
|
||||||
|
}
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return new MDOutline(classes, errors);
|
return new MDOutline(classes, errors);
|
||||||
}
|
}
|
||||||
@ -110,13 +143,21 @@ class MDOutline {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
parameters = parameters.trim().replace(/[\[\]]/g, '');
|
parameters = parameters.trim().replace(/[\[\]]/g, '');
|
||||||
if (parameters !== member.args.join(', '))
|
if (parameters !== member.args.map(arg => arg.name).join(', '))
|
||||||
this.errors.push(`Heading arguments for "${member.name}" do not match described ones, i.e. "${parameters}" != "${member.args.join(', ')}"`);
|
this.errors.push(`Heading arguments for "${member.name}" do not match described ones, i.e. "${parameters}" != "${member.args.map(a => a.name).join(', ')}"`);
|
||||||
const args = member.args.map(arg => new Documentation.Argument(arg));
|
const args = member.args.map(createPropertyFromJSON);
|
||||||
const method = Documentation.Member.createMethod(methodName, args, member.hasReturn, false);
|
let returnType = null;
|
||||||
|
if (member.returnType)
|
||||||
|
returnType = createPropertyFromJSON(member.returnType).type;
|
||||||
|
const method = Documentation.Member.createMethod(methodName, args, returnType);
|
||||||
currentClassMembers.push(method);
|
currentClassMembers.push(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createPropertyFromJSON(payload) {
|
||||||
|
const type = new Documentation.Type(payload.type, payload.properties.map(createPropertyFromJSON));
|
||||||
|
return Documentation.Member.createProperty(payload.name, type);
|
||||||
|
}
|
||||||
|
|
||||||
function handleProperty(member, className, propertyName) {
|
function handleProperty(member, className, propertyName) {
|
||||||
if (!currentClassName || !className || !propertyName || className.toLowerCase() !== currentClassName.toLowerCase()) {
|
if (!currentClassName || !className || !propertyName || className.toLowerCase() !== currentClassName.toLowerCase()) {
|
||||||
this.errors.push(`Failed to process header as property: ${member.name}`);
|
this.errors.push(`Failed to process header as property: ${member.name}`);
|
||||||
|
@ -49,7 +49,6 @@ const EXCLUDE_PROPERTIES = new Set([
|
|||||||
/**
|
/**
|
||||||
* @param {!Page} page
|
* @param {!Page} page
|
||||||
* @param {!Array<!Source>} mdSources
|
* @param {!Array<!Source>} mdSources
|
||||||
* @param {!Array<!Source>} jsSources
|
|
||||||
* @return {!Promise<!Array<!Message>>}
|
* @return {!Promise<!Array<!Message>>}
|
||||||
*/
|
*/
|
||||||
module.exports = async function lint(page, mdSources, jsSources) {
|
module.exports = async function lint(page, mdSources, jsSources) {
|
||||||
@ -83,21 +82,21 @@ function checkSorting(doc) {
|
|||||||
|
|
||||||
// Events should go first.
|
// Events should go first.
|
||||||
let eventIndex = 0;
|
let eventIndex = 0;
|
||||||
for (; eventIndex < members.length && members[eventIndex].type === 'event'; ++eventIndex);
|
for (; eventIndex < members.length && members[eventIndex].kind === 'event'; ++eventIndex);
|
||||||
for (; eventIndex < members.length && members[eventIndex].type !== 'event'; ++eventIndex);
|
for (; eventIndex < members.length && members[eventIndex].kind !== 'event'; ++eventIndex);
|
||||||
if (eventIndex < members.length)
|
if (eventIndex < members.length)
|
||||||
errors.push(`Events should go first. Event '${members[eventIndex].name}' in class ${cls.name} breaks order`);
|
errors.push(`Events should go first. Event '${members[eventIndex].name}' in class ${cls.name} breaks order`);
|
||||||
|
|
||||||
// Constructor should be right after events and before all other members.
|
// Constructor should be right after events and before all other members.
|
||||||
const constructorIndex = members.findIndex(member => member.type === 'method' && member.name === 'constructor');
|
const constructorIndex = members.findIndex(member => member.kind === 'method' && member.name === 'constructor');
|
||||||
if (constructorIndex > 0 && members[constructorIndex - 1].type !== 'event')
|
if (constructorIndex > 0 && members[constructorIndex - 1].kind !== 'event')
|
||||||
errors.push(`Constructor of ${cls.name} should go before other methods`);
|
errors.push(`Constructor of ${cls.name} should go before other methods`);
|
||||||
|
|
||||||
// Events should be sorted alphabetically.
|
// Events should be sorted alphabetically.
|
||||||
for (let i = 0; i < members.length - 1; ++i) {
|
for (let i = 0; i < members.length - 1; ++i) {
|
||||||
const member1 = cls.membersArray[i];
|
const member1 = cls.membersArray[i];
|
||||||
const member2 = cls.membersArray[i + 1];
|
const member2 = cls.membersArray[i + 1];
|
||||||
if (member1.type !== 'event' || member2.type !== 'event')
|
if (member1.kind !== 'event' || member2.kind !== 'event')
|
||||||
continue;
|
continue;
|
||||||
if (member1.name > member2.name)
|
if (member1.name > member2.name)
|
||||||
errors.push(`Event '${member1.name}' in class ${cls.name} breaks alphabetic ordering of events`);
|
errors.push(`Event '${member1.name}' in class ${cls.name} breaks alphabetic ordering of events`);
|
||||||
@ -107,16 +106,16 @@ function checkSorting(doc) {
|
|||||||
for (let i = 0; i < members.length - 1; ++i) {
|
for (let i = 0; i < members.length - 1; ++i) {
|
||||||
const member1 = cls.membersArray[i];
|
const member1 = cls.membersArray[i];
|
||||||
const member2 = cls.membersArray[i + 1];
|
const member2 = cls.membersArray[i + 1];
|
||||||
if (member1.type === 'event' || member2.type === 'event')
|
if (member1.kind === 'event' || member2.kind === 'event')
|
||||||
continue;
|
continue;
|
||||||
if (member1.type === 'method' && member1.name === 'constructor')
|
if (member1.kind === 'method' && member1.name === 'constructor')
|
||||||
continue;
|
continue;
|
||||||
if (member1.name > member2.name) {
|
if (member1.name > member2.name) {
|
||||||
let memberName1 = `${cls.name}.${member1.name}`;
|
let memberName1 = `${cls.name}.${member1.name}`;
|
||||||
if (member1.type === 'method')
|
if (member1.kind === 'method')
|
||||||
memberName1 += '()';
|
memberName1 += '()';
|
||||||
let memberName2 = `${cls.name}.${member2.name}`;
|
let memberName2 = `${cls.name}.${member2.name}`;
|
||||||
if (member2.type === 'method')
|
if (member2.kind === 'method')
|
||||||
memberName2 += '()';
|
memberName2 += '()';
|
||||||
errors.push(`Bad alphabetic ordering of ${cls.name} members: ${memberName1} should go after ${memberName2}`);
|
errors.push(`Bad alphabetic ordering of ${cls.name} members: ${memberName1} should go after ${memberName2}`);
|
||||||
}
|
}
|
||||||
@ -135,14 +134,7 @@ function filterJSDocumentation(jsDocumentation) {
|
|||||||
for (const cls of jsDocumentation.classesArray) {
|
for (const cls of jsDocumentation.classesArray) {
|
||||||
if (EXCLUDE_CLASSES.has(cls.name))
|
if (EXCLUDE_CLASSES.has(cls.name))
|
||||||
continue;
|
continue;
|
||||||
const members = cls.membersArray.filter(member => {
|
const members = cls.membersArray.filter(member => !EXCLUDE_PROPERTIES.has(`${cls.name}.${member.name}`));
|
||||||
if (member.name.startsWith('_'))
|
|
||||||
return false;
|
|
||||||
// Exclude all constructors by default.
|
|
||||||
if (member.name === 'constructor' && member.type === 'method')
|
|
||||||
return false;
|
|
||||||
return !EXCLUDE_PROPERTIES.has(`${cls.name}.${member.name}`);
|
|
||||||
});
|
|
||||||
classes.push(new Documentation.Class(cls.name, members));
|
classes.push(new Documentation.Class(cls.name, members));
|
||||||
}
|
}
|
||||||
return new Documentation(classes);
|
return new Documentation(classes);
|
||||||
@ -162,9 +154,9 @@ function checkDuplicates(doc) {
|
|||||||
classes.add(cls.name);
|
classes.add(cls.name);
|
||||||
const members = new Set();
|
const members = new Set();
|
||||||
for (const member of cls.membersArray) {
|
for (const member of cls.membersArray) {
|
||||||
if (members.has(member.type + ' ' + member.name))
|
if (members.has(member.kind + ' ' + member.name))
|
||||||
errors.push(`Duplicate declaration of ${member.type} ${cls.name}.${member.name}()`);
|
errors.push(`Duplicate declaration of ${member.kind} ${cls.name}.${member.name}()`);
|
||||||
members.add(member.type + ' ' + member.name);
|
members.add(member.kind + ' ' + member.name);
|
||||||
const args = new Set();
|
const args = new Set();
|
||||||
for (const arg of member.argsArray) {
|
for (const arg of member.argsArray) {
|
||||||
if (args.has(arg.name))
|
if (args.has(arg.name))
|
||||||
@ -206,25 +198,28 @@ function compareDocumentations(actual, expected) {
|
|||||||
for (const methodName of methodDiff.equal) {
|
for (const methodName of methodDiff.equal) {
|
||||||
const actualMethod = actualClass.methods.get(methodName);
|
const actualMethod = actualClass.methods.get(methodName);
|
||||||
const expectedMethod = expectedClass.methods.get(methodName);
|
const expectedMethod = expectedClass.methods.get(methodName);
|
||||||
if (actualMethod.hasReturn !== expectedMethod.hasReturn) {
|
if (!actualMethod.type !== !expectedMethod.type) {
|
||||||
if (actualMethod.hasReturn)
|
if (actualMethod.type)
|
||||||
errors.push(`Method ${className}.${methodName} has unneeded description of return type`);
|
errors.push(`Method ${className}.${methodName} has unneeded description of return type`);
|
||||||
else if (!expectedMethod.async)
|
|
||||||
errors.push(`Method ${className}.${methodName} is missing return type description`);
|
|
||||||
else
|
else
|
||||||
errors.push(`Async method ${className}.${methodName} should describe return type Promise`);
|
errors.push(`Method ${className}.${methodName} is missing return type description`);
|
||||||
|
} else if (actualMethod.hasReturn) {
|
||||||
|
checkType(`Method ${className}.${methodName} has the wrong return type: `, actualMethod.type, expectedMethod.type);
|
||||||
}
|
}
|
||||||
const actualArgs = Array.from(actualMethod.args.keys());
|
const actualArgs = Array.from(actualMethod.args.keys());
|
||||||
const expectedArgs = Array.from(expectedMethod.args.keys());
|
const expectedArgs = Array.from(expectedMethod.args.keys());
|
||||||
const argDiff = diff(actualArgs, expectedArgs);
|
const argsDiff = diff(actualArgs, expectedArgs);
|
||||||
if (argDiff.extra.length || argDiff.missing.length) {
|
if (argsDiff.extra.length || argsDiff.missing.length) {
|
||||||
const text = [`Method ${className}.${methodName}() fails to describe its parameters:`];
|
const text = [`Method ${className}.${methodName}() fails to describe its parameters:`];
|
||||||
for (const arg of argDiff.missing)
|
for (const arg of argsDiff.missing)
|
||||||
text.push(`- Argument not found: ${arg}`);
|
text.push(`- Argument not found: ${arg}`);
|
||||||
for (const arg of argDiff.extra)
|
for (const arg of argsDiff.extra)
|
||||||
text.push(`- Non-existing argument found: ${arg}`);
|
text.push(`- Non-existing argument found: ${arg}`);
|
||||||
errors.push(text.join('\n'));
|
errors.push(text.join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const arg of argsDiff.equal)
|
||||||
|
checkProperty(`Method ${className}.${methodName}()`, actualMethod.args.get(arg), expectedMethod.args.get(arg));
|
||||||
}
|
}
|
||||||
const actualProperties = Array.from(actualClass.properties.keys()).sort();
|
const actualProperties = Array.from(actualClass.properties.keys()).sort();
|
||||||
const expectedProperties = Array.from(expectedClass.properties.keys()).sort();
|
const expectedProperties = Array.from(expectedClass.properties.keys()).sort();
|
||||||
@ -242,6 +237,43 @@ function compareDocumentations(actual, expected) {
|
|||||||
for (const eventName of eventsDiff.missing)
|
for (const eventName of eventsDiff.missing)
|
||||||
errors.push(`Event not found in class ${className}: '${eventName}'`);
|
errors.push(`Event not found in class ${className}: '${eventName}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} source
|
||||||
|
* @param {!Documentation.Member} actual
|
||||||
|
* @param {!Documentation.Member} expected
|
||||||
|
*/
|
||||||
|
function checkProperty(source, actual, expected) {
|
||||||
|
checkType(source + ' ' + actual.name, actual.type, expected.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} source
|
||||||
|
* @param {!Documentation.Type} actual
|
||||||
|
* @param {!Documentation.Type} expected
|
||||||
|
*/
|
||||||
|
function checkType(source, actual, expected) {
|
||||||
|
// TODO(@JoelEinbinder): check functions and Serializable
|
||||||
|
if (actual.name.includes('unction') || actual.name.includes('Serializable'))
|
||||||
|
return;
|
||||||
|
// We don't have nullchecks on for TypeScript
|
||||||
|
const actualName = actual.name.replace(/[\? ]/g, '');
|
||||||
|
// TypeScript likes to add some spaces
|
||||||
|
const expectedName = expected.name.replace(/\ /g, '');
|
||||||
|
if (expectedName !== actualName)
|
||||||
|
errors.push(`${source} ${actualName} != ${expectedName}`);
|
||||||
|
const actualPropertiesMap = new Map(actual.properties.map(property => [property.name, property.type]));
|
||||||
|
const expectedPropertiesMap = new Map(expected.properties.map(property => [property.name, property.type]));
|
||||||
|
const propertiesDiff = diff(Array.from(actualPropertiesMap.keys()).sort(), Array.from(expectedPropertiesMap.keys()).sort());
|
||||||
|
for (const propertyName of propertiesDiff.extra)
|
||||||
|
errors.push(`${source} has unexpected property ${propertyName}`);
|
||||||
|
for (const propertyName of propertiesDiff.missing)
|
||||||
|
errors.push(`${source} is missing property ${propertyName}`);
|
||||||
|
for (const propertyName of propertiesDiff.equal)
|
||||||
|
checkType(source + '.' + propertyName, actualPropertiesMap.get(propertyName), expectedPropertiesMap.get(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,3 +12,4 @@
|
|||||||
|
|
||||||
### class: Bar
|
### class: Bar
|
||||||
|
|
||||||
|
[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type "Number"
|
||||||
|
@ -2,6 +2,9 @@ class Foo {
|
|||||||
test() {
|
test() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} arg
|
||||||
|
*/
|
||||||
title(arg) {
|
title(arg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,3 +9,6 @@
|
|||||||
|
|
||||||
#### foo.www()
|
#### foo.www()
|
||||||
- returns <[string]>
|
- returns <[string]>
|
||||||
|
|
||||||
|
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"
|
||||||
|
[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type "Number"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[MarkDown] foo.www() has mistyped 'return' type declaration: expected exactly 'returns: ', found 'returns '.
|
[MarkDown] foo.www() has mistyped 'return' type declaration: expected exactly 'returns: ', found 'returns '.
|
||||||
[MarkDown] Async method Foo.asyncFunction should describe return type Promise
|
[MarkDown] Method Foo.asyncFunction is missing return type description
|
||||||
[MarkDown] Method Foo.return42 is missing return type description
|
[MarkDown] Method Foo.return42 is missing return type description
|
||||||
[MarkDown] Method Foo.returnNothing has unneeded description of return type
|
[MarkDown] Method Foo.returnNothing has unneeded description of return type
|
@ -1,11 +1,14 @@
|
|||||||
### class: Foo
|
### class: Foo
|
||||||
#### foo.bar(options)
|
#### foo.bar(options)
|
||||||
- `options` <[Object]>
|
- `options` <[Object]>
|
||||||
|
- `visibility` <[boolean]>
|
||||||
#### foo.foo(arg1, arg2)
|
#### foo.foo(arg1, arg2)
|
||||||
- `arg1` <[string]>
|
- `arg1` <[string]>
|
||||||
- `arg2` <[string]>
|
- `arg2` <[string]>
|
||||||
|
|
||||||
#### foo.test(...files)
|
#### foo.test(...files)
|
||||||
- `...filePaths` <[string]>
|
- `...filePaths` <...[string]>
|
||||||
|
|
||||||
|
[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object "Object"
|
||||||
|
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"
|
||||||
|
[boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type "Boolean"
|
||||||
|
@ -1,10 +1,19 @@
|
|||||||
class Foo {
|
class Foo {
|
||||||
|
/**
|
||||||
|
* @param {string} arg1
|
||||||
|
*/
|
||||||
foo(arg1, arg3 = {}) {
|
foo(arg1, arg3 = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {...string} filePaths
|
||||||
|
*/
|
||||||
test(...filePaths) {
|
test(...filePaths) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bar({visibility}) {
|
/**
|
||||||
|
* @param {{visibility?: boolean}} options
|
||||||
|
*/
|
||||||
|
bar(options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,52 +3,46 @@
|
|||||||
{
|
{
|
||||||
"name": "A",
|
"name": "A",
|
||||||
"members": [
|
"members": [
|
||||||
|
{
|
||||||
|
"name": "anevent",
|
||||||
|
"kind": "event"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "property1",
|
"name": "property1",
|
||||||
"type": "property",
|
"type": {
|
||||||
"hasReturn": false,
|
"name": "number"
|
||||||
"async": false,
|
},
|
||||||
"args": []
|
"kind": "property"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "_property2",
|
|
||||||
"type": "property",
|
|
||||||
"hasReturn": false,
|
|
||||||
"async": false,
|
|
||||||
"args": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constructor",
|
|
||||||
"type": "method",
|
|
||||||
"hasReturn": false,
|
|
||||||
"async": false,
|
|
||||||
"args": [
|
|
||||||
"delegate"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getter",
|
"name": "getter",
|
||||||
"type": "property",
|
"type": {
|
||||||
"hasReturn": false,
|
"name": "Object"
|
||||||
"async": false,
|
},
|
||||||
"args": []
|
"kind": "property"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "method",
|
"name": "method",
|
||||||
"type": "method",
|
"type": {
|
||||||
"hasReturn": true,
|
"name": "Promise"
|
||||||
"async": true,
|
},
|
||||||
|
"kind": "method",
|
||||||
"args": [
|
"args": [
|
||||||
"foo",
|
{
|
||||||
"bar"
|
"name": "foo",
|
||||||
|
"type": {
|
||||||
|
"name": "Object"
|
||||||
|
},
|
||||||
|
"kind": "property"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bar",
|
||||||
|
"type": {
|
||||||
|
"name": "Object"
|
||||||
|
},
|
||||||
|
"kind": "property"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "anevent",
|
|
||||||
"type": "event",
|
|
||||||
"hasReturn": false,
|
|
||||||
"async": false,
|
|
||||||
"args": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -3,57 +3,56 @@
|
|||||||
{
|
{
|
||||||
"name": "A",
|
"name": "A",
|
||||||
"members": [
|
"members": [
|
||||||
{
|
|
||||||
"name": "constructor",
|
|
||||||
"type": "method",
|
|
||||||
"hasReturn": false,
|
|
||||||
"async": false,
|
|
||||||
"args": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
"type": "method",
|
"kind": "method",
|
||||||
"hasReturn": false,
|
|
||||||
"async": false,
|
|
||||||
"args": [
|
"args": [
|
||||||
"a"
|
{
|
||||||
|
"name": "a",
|
||||||
|
"type": {
|
||||||
|
"name": "Object"
|
||||||
|
},
|
||||||
|
"kind": "property"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bar",
|
"name": "bar",
|
||||||
"type": "method",
|
"kind": "method"
|
||||||
"hasReturn": false,
|
|
||||||
"async": false,
|
|
||||||
"args": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "B",
|
"name": "B",
|
||||||
"members": [
|
"members": [
|
||||||
|
{
|
||||||
|
"name": "foo",
|
||||||
|
"kind": "event"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "bar",
|
"name": "bar",
|
||||||
"type": "method",
|
"kind": "method",
|
||||||
"hasReturn": false,
|
|
||||||
"async": false,
|
|
||||||
"args": [
|
"args": [
|
||||||
"override"
|
{
|
||||||
|
"name": "override",
|
||||||
|
"type": {
|
||||||
|
"name": "Object"
|
||||||
|
},
|
||||||
|
"kind": "property"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
"type": "event",
|
"kind": "method",
|
||||||
"hasReturn": false,
|
|
||||||
"async": false,
|
|
||||||
"args": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "foo",
|
|
||||||
"type": "method",
|
|
||||||
"hasReturn": false,
|
|
||||||
"async": false,
|
|
||||||
"args": [
|
"args": [
|
||||||
"a"
|
{
|
||||||
|
"name": "a",
|
||||||
|
"type": {
|
||||||
|
"name": "Object"
|
||||||
|
},
|
||||||
|
"kind": "property"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
This is a class.
|
This is a class.
|
||||||
|
|
||||||
#### event: 'frame'
|
#### event: 'frame'
|
||||||
- <[Frame]>
|
- <[Frame]>
|
||||||
|
|
||||||
This event is dispatched.
|
This event is dispatched.
|
||||||
|
|
||||||
@ -17,3 +17,8 @@ The method runs document.querySelector.
|
|||||||
- <[string]>
|
- <[string]>
|
||||||
|
|
||||||
Contains the URL of the request.
|
Contains the URL of the request.
|
||||||
|
|
||||||
|
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"
|
||||||
|
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise"
|
||||||
|
[ElementHandle]: # "ElementHandle"
|
||||||
|
[ElementHandle]: # "Frame"
|
||||||
|
@ -5,26 +5,27 @@
|
|||||||
"members": [
|
"members": [
|
||||||
{
|
{
|
||||||
"name": "frame",
|
"name": "frame",
|
||||||
"type": "event",
|
"kind": "event"
|
||||||
"hasReturn": false,
|
|
||||||
"async": false,
|
|
||||||
"args": []
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "$",
|
"name": "$",
|
||||||
"type": "method",
|
"type": {
|
||||||
"hasReturn": true,
|
"name": "Promise<ElementHandle>"
|
||||||
"async": false,
|
},
|
||||||
|
"kind": "method",
|
||||||
"args": [
|
"args": [
|
||||||
"selector"
|
{
|
||||||
|
"name": "selector",
|
||||||
|
"type": {
|
||||||
|
"name": "string"
|
||||||
|
},
|
||||||
|
"kind": "property"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "url",
|
"name": "url",
|
||||||
"type": "property",
|
"kind": "property"
|
||||||
"hasReturn": false,
|
|
||||||
"async": false,
|
|
||||||
"args": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -91,24 +91,37 @@ async function testJSBuilder(state, test) {
|
|||||||
expect(serialize(documentation)).toBeGolden('result.txt');
|
expect(serialize(documentation)).toBeGolden('result.txt');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('../Documentation')} doc
|
||||||
|
*/
|
||||||
function serialize(doc) {
|
function serialize(doc) {
|
||||||
const result = {classes: []};
|
const result = {
|
||||||
for (let cls of doc.classesArray) {
|
classes: doc.classesArray.map(cls => ({
|
||||||
const classJSON = {
|
|
||||||
name: cls.name,
|
name: cls.name,
|
||||||
members: []
|
members: cls.membersArray.map(serializeMember)
|
||||||
};
|
}))
|
||||||
result.classes.push(classJSON);
|
};
|
||||||
for (let member of cls.membersArray) {
|
|
||||||
classJSON.members.push({
|
|
||||||
name: member.name,
|
|
||||||
type: member.type,
|
|
||||||
hasReturn: member.hasReturn,
|
|
||||||
async: member.async,
|
|
||||||
args: member.argsArray.map(arg => arg.name)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JSON.stringify(result, null, 2);
|
return JSON.stringify(result, null, 2);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @param {import('../Documentation').Member} member
|
||||||
|
*/
|
||||||
|
function serializeMember(member) {
|
||||||
|
return {
|
||||||
|
name: member.name,
|
||||||
|
type: serializeType(member.type),
|
||||||
|
kind: member.kind,
|
||||||
|
args: member.argsArray.length ? member.argsArray.map(serializeMember) : undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {import('../Documentation').Type} type
|
||||||
|
*/
|
||||||
|
function serializeType(type) {
|
||||||
|
if (!type)
|
||||||
|
return undefined;
|
||||||
|
return {
|
||||||
|
name: type.name,
|
||||||
|
properties: type.properties.length ? type.properties.map(serializeMember) : undefined
|
||||||
|
}
|
||||||
|
}
|
@ -48,8 +48,9 @@ async function run() {
|
|||||||
const browser = await puppeteer.launch();
|
const browser = await puppeteer.launch();
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
const checkPublicAPI = require('./check_public_api');
|
const checkPublicAPI = require('./check_public_api');
|
||||||
const jsSources = await Source.readdir(path.join(PROJECT_DIR, 'lib'), '.js');
|
const jsSources = await Source.readdir(path.join(PROJECT_DIR, 'lib'));
|
||||||
messages.push(...await checkPublicAPI(page, mdSources, jsSources));
|
messages.push(...await checkPublicAPI(page, mdSources, jsSources));
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
|
|
||||||
for (const source of mdSources) {
|
for (const source of mdSources) {
|
||||||
|
Loading…
Reference in New Issue
Block a user