mirror of
https://github.com/photonstorm/phaser
synced 2024-11-16 09:48:18 +00:00
510 lines
No EOL
22 KiB
JavaScript
510 lines
No EOL
22 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
const dom = require("dts-dom");
|
||
const regexEndLine = /^(.*)\r\n|\n|\r/gm;
|
||
class Parser {
|
||
constructor(docs) {
|
||
this.topLevel = [];
|
||
this.objects = {};
|
||
this.namespaces = {};
|
||
// parse doclets and create corresponding dom objects
|
||
this.parseObjects(docs);
|
||
this.resolveObjects(docs);
|
||
// removes members inherited from classes
|
||
// possibly could be avoided if mixins were defined as such before JSDoc parses them and then we could globally remove all inherited (not
|
||
// overridden) members globally from the parsed DB
|
||
this.resolveInheritance(docs);
|
||
this.resolveParents(docs);
|
||
// add integer alias
|
||
this.topLevel.push(dom.create.alias('integer', dom.type.number));
|
||
// add declare module
|
||
const phaserPkgModuleDOM = dom.create.module('phaser');
|
||
phaserPkgModuleDOM.members.push(dom.create.exportEquals('Phaser'));
|
||
this.topLevel.push(phaserPkgModuleDOM);
|
||
}
|
||
emit() {
|
||
let ignored = [];
|
||
let result = '/// <reference types="./matter" />\n\n';
|
||
result = result.concat(this.topLevel.reduce((out, obj) => {
|
||
return out + dom.emit(obj);
|
||
}, ''));
|
||
if (ignored.length > 0) {
|
||
console.log('ignored top level properties:');
|
||
console.log(ignored);
|
||
}
|
||
return result;
|
||
}
|
||
parseObjects(docs) {
|
||
for (let i = 0; i < docs.length; i++) {
|
||
let doclet = docs[i];
|
||
// TODO: Custom temporary rules
|
||
switch (doclet.longname) {
|
||
case 'Phaser.GameObjects.Components.Alpha':
|
||
case 'Phaser.GameObjects.Components.AlphaSingle':
|
||
case 'Phaser.GameObjects.Components.Animation':
|
||
case 'Phaser.GameObjects.Components.BlendMode':
|
||
case 'Phaser.GameObjects.Components.ComputedSize':
|
||
case 'Phaser.GameObjects.Components.Crop':
|
||
case 'Phaser.GameObjects.Components.Depth':
|
||
case 'Phaser.GameObjects.Components.Flip':
|
||
case 'Phaser.GameObjects.Components.GetBounds':
|
||
case 'Phaser.GameObjects.Components.Mask':
|
||
case 'Phaser.GameObjects.Components.Origin':
|
||
case 'Phaser.GameObjects.Components.PathFollower':
|
||
case 'Phaser.GameObjects.Components.Pipeline':
|
||
case 'Phaser.GameObjects.Components.ScrollFactor':
|
||
case 'Phaser.GameObjects.Components.Size':
|
||
case 'Phaser.GameObjects.Components.Texture':
|
||
case 'Phaser.GameObjects.Components.TextureCrop':
|
||
case 'Phaser.GameObjects.Components.Tint':
|
||
case 'Phaser.GameObjects.Components.ToJSON':
|
||
case 'Phaser.GameObjects.Components.Transform':
|
||
case 'Phaser.GameObjects.Components.Visible':
|
||
case 'Phaser.Renderer.WebGL.Pipelines.ModelViewProjection':
|
||
doclet.kind = 'mixin';
|
||
break;
|
||
// Because, sod you TypeScript
|
||
case 'Phaser.BlendModes':
|
||
case 'Phaser.ScaleModes':
|
||
case 'Phaser.Physics.Impact.TYPE':
|
||
case 'Phaser.Physics.Impact.COLLIDES':
|
||
case 'Phaser.Scale.Center':
|
||
case 'Phaser.Scale.Orientation':
|
||
case 'Phaser.Scale.ScaleModes':
|
||
case 'Phaser.Scale.Zoom':
|
||
case 'Phaser.Textures.FilterMode':
|
||
// console.log('Forcing enum for ' + doclet.longname);
|
||
doclet.kind = 'member';
|
||
doclet.isEnum = true;
|
||
break;
|
||
}
|
||
if ((doclet.longname.indexOf('Phaser.Physics.Arcade.Components.') == 0 || doclet.longname.indexOf('Phaser.Physics.Impact.Components.') == 0 || doclet.longname.indexOf('Phaser.Physics.Matter.Components.') == 0) && doclet.longname.indexOf('#') == -1) {
|
||
doclet.kind = 'mixin';
|
||
}
|
||
let obj;
|
||
let container = this.objects;
|
||
switch (doclet.kind) {
|
||
case 'namespace':
|
||
obj = this.createNamespace(doclet);
|
||
container = this.namespaces;
|
||
break;
|
||
case 'class':
|
||
obj = this.createClass(doclet);
|
||
break;
|
||
case 'mixin':
|
||
obj = this.createInterface(doclet);
|
||
break;
|
||
case 'member':
|
||
if (doclet.isEnum === true) {
|
||
obj = this.createEnum(doclet);
|
||
break;
|
||
}
|
||
case 'constant':
|
||
obj = this.createMember(doclet);
|
||
break;
|
||
case 'function':
|
||
obj = this.createFunction(doclet);
|
||
break;
|
||
case 'typedef':
|
||
obj = this.createTypedef(doclet);
|
||
break;
|
||
case 'event':
|
||
obj = this.createEvent(doclet);
|
||
break;
|
||
default:
|
||
console.log('Ignored doclet kind: ' + doclet.kind);
|
||
break;
|
||
}
|
||
if (obj) {
|
||
if (container[doclet.longname]) {
|
||
console.log('Warning: ignoring duplicate doc name: ' + doclet.longname);
|
||
docs.splice(i--, 1);
|
||
continue;
|
||
}
|
||
container[doclet.longname] = obj;
|
||
if (doclet.description) {
|
||
let otherDocs = obj.jsDocComment || '';
|
||
obj.jsDocComment = doclet.description.replace(regexEndLine, '$1\n') + otherDocs;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
resolveObjects(docs) {
|
||
let allTypes = new Set();
|
||
for (let doclet of docs) {
|
||
let obj = doclet.kind === 'namespace' ? this.namespaces[doclet.longname] : this.objects[doclet.longname];
|
||
if (!obj) {
|
||
// TODO
|
||
console.log(`Warning: Didn't find object for ${doclet.longname}`);
|
||
continue;
|
||
}
|
||
if (!doclet.memberof) {
|
||
this.topLevel.push(obj);
|
||
}
|
||
else {
|
||
let isNamespaceMember = doclet.kind === 'class' || doclet.kind === 'typedef' || doclet.kind == 'namespace' || doclet.isEnum;
|
||
let parent = isNamespaceMember ? this.namespaces[doclet.memberof] : (this.objects[doclet.memberof] || this.namespaces[doclet.memberof]);
|
||
//TODO: this whole section should be removed once stable
|
||
if (!parent) {
|
||
console.log(`${doclet.longname} in ${doclet.meta.filename}@${doclet.meta.lineno} has parent '${doclet.memberof}' that is not defined.`);
|
||
let parts = doclet.memberof.split('.');
|
||
let newParts = [parts.pop()];
|
||
while (parts.length > 0 && this.objects[parts.join('.')] == null)
|
||
newParts.unshift(parts.pop());
|
||
parent = this.objects[parts.join('.')];
|
||
if (parent == null) {
|
||
parent = dom.create.namespace(doclet.memberof);
|
||
this.namespaces[doclet.memberof] = parent;
|
||
this.topLevel.push(parent);
|
||
}
|
||
else {
|
||
while (newParts.length > 0) {
|
||
let oldParent = parent;
|
||
parent = dom.create.namespace(newParts.shift());
|
||
parts.push(parent.name);
|
||
this.namespaces[parts.join('.')] = parent;
|
||
oldParent.members.push(parent);
|
||
parent._parent = oldParent;
|
||
}
|
||
}
|
||
}
|
||
///////////////////////////////////////////////////////
|
||
if (parent.members) {
|
||
parent.members.push(obj);
|
||
}
|
||
else {
|
||
console.log('Cannot find members array for:');
|
||
console.log(parent);
|
||
}
|
||
obj._parent = parent;
|
||
// class/interface members have methods, not functions
|
||
if ((parent.kind === 'class' || parent.kind === 'interface')
|
||
&& obj.kind === 'function')
|
||
obj.kind = 'method';
|
||
// namespace members are vars or consts, not properties
|
||
if (parent.kind === 'namespace' && obj.kind === 'property') {
|
||
if (doclet.kind == 'constant')
|
||
obj.kind = 'const';
|
||
else
|
||
obj.kind = 'var';
|
||
}
|
||
}
|
||
}
|
||
}
|
||
resolveInheritance(docs) {
|
||
for (let doclet of docs) {
|
||
let obj = doclet.kind === 'namespace' ? this.namespaces[doclet.longname] : this.objects[doclet.longname];
|
||
if (!obj) {
|
||
// TODO
|
||
console.log(`Didn't find type ${doclet.longname} ???`);
|
||
continue;
|
||
}
|
||
if (!obj._parent)
|
||
continue;
|
||
if (doclet.inherited) { // remove inherited members if they aren't from an interface
|
||
let from = this.objects[doclet.inherits];
|
||
if (!from || !from._parent)
|
||
throw `'${doclet.longname}' should inherit from '${doclet.inherits}', which is not defined.`;
|
||
if (from._parent.kind != 'interface') {
|
||
obj._parent.members.splice(obj._parent.members.indexOf(obj), 1);
|
||
obj._parent = null;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
resolveParents(docs) {
|
||
for (let doclet of docs) {
|
||
let obj = this.objects[doclet.longname];
|
||
if (!obj || doclet.kind !== 'class')
|
||
continue;
|
||
let o = obj;
|
||
// resolve augments
|
||
if (doclet.augments && doclet.augments.length) {
|
||
for (let augment of doclet.augments) {
|
||
let name = this.prepareTypeName(augment);
|
||
let wrappingName = name.match(/[^<]+/s)[0]; //gets everything up to a first < (to handle augments with type parameters)
|
||
let baseType = this.objects[wrappingName];
|
||
if (!baseType) {
|
||
console.log(`ERROR: Did not find base type: ${augment} for ${doclet.longname}`);
|
||
}
|
||
else {
|
||
if (baseType.kind == 'class') {
|
||
o.baseType = dom.create.class(name);
|
||
}
|
||
else {
|
||
o.implements.push(dom.create.interface(name));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
createNamespace(doclet) {
|
||
/**
|
||
namespace: { comment: '',
|
||
meta:
|
||
{ filename: 'index.js',
|
||
lineno: 10,
|
||
columnno: 0,
|
||
path: '/Users/rich/Documents/GitHub/phaser/src/tweens',
|
||
code: {} },
|
||
kind: 'namespace',
|
||
name: 'Tweens',
|
||
memberof: 'Phaser',
|
||
longname: 'Phaser.Tweens',
|
||
scope: 'static',
|
||
___id: 'T000002R034468',
|
||
___s: true }
|
||
*/
|
||
// console.log('namespace:', doclet.longname);
|
||
let obj = dom.create.namespace(doclet.name);
|
||
return obj;
|
||
}
|
||
createClass(doclet) {
|
||
let obj = dom.create.class(doclet.name);
|
||
let params = null;
|
||
if (doclet.params) {
|
||
let ctor = dom.create.constructor(null);
|
||
this.setParams(doclet, ctor);
|
||
params = ctor.parameters;
|
||
obj.members.push(ctor);
|
||
ctor._parent = obj;
|
||
}
|
||
this.processGeneric(doclet, obj, params);
|
||
if (doclet.classdesc)
|
||
doclet.description = doclet.classdesc.replace(regexEndLine, '$1\n'); // make sure docs will be added
|
||
return obj;
|
||
}
|
||
createInterface(doclet) {
|
||
return dom.create.interface(doclet.name);
|
||
}
|
||
createMember(doclet) {
|
||
let type = this.parseType(doclet);
|
||
let obj = dom.create.property(doclet.name, type);
|
||
this.processGeneric(doclet, obj, null);
|
||
this.processFlags(doclet, obj);
|
||
return obj;
|
||
}
|
||
createEvent(doclet) {
|
||
let type = this.parseType(doclet);
|
||
let obj = dom.create.const(doclet.name, type);
|
||
this.processFlags(doclet, obj);
|
||
return obj;
|
||
}
|
||
createEnum(doclet) {
|
||
let obj = dom.create.enum(doclet.name, false);
|
||
this.processFlags(doclet, obj);
|
||
return obj;
|
||
}
|
||
createFunction(doclet) {
|
||
let returnType = dom.type.void;
|
||
if (doclet.returns) {
|
||
returnType = this.parseType(doclet.returns[0]);
|
||
}
|
||
let obj = dom.create.function(doclet.name, null, returnType);
|
||
this.setParams(doclet, obj);
|
||
this.processGeneric(doclet, obj, obj.parameters);
|
||
this.processFlags(doclet, obj);
|
||
return obj;
|
||
}
|
||
createTypedef(doclet) {
|
||
const typeName = doclet.type.names[0];
|
||
let type = null;
|
||
if (doclet.type.names[0] === 'object') {
|
||
let properties = [];
|
||
for (let propDoc of doclet.properties) {
|
||
let prop = this.createMember(propDoc);
|
||
properties.push(prop);
|
||
if (propDoc.description)
|
||
prop.jsDocComment = propDoc.description.replace(regexEndLine, '$1\n');
|
||
}
|
||
type = dom.create.objectType(properties);
|
||
if (doclet.augments && doclet.augments.length) {
|
||
let intersectionTypes = [];
|
||
for (let i = 0; i < doclet.augments.length; i++) {
|
||
intersectionTypes.push(dom.create.namedTypeReference(doclet.augments[i]));
|
||
}
|
||
intersectionTypes.push(type);
|
||
type = dom.create.intersection(intersectionTypes);
|
||
}
|
||
}
|
||
else {
|
||
if (doclet.type.names[0] == "function") {
|
||
type = dom.create.functionType(null, dom.type.void);
|
||
this.setParams(doclet, type);
|
||
}
|
||
else {
|
||
type = this.parseType(doclet);
|
||
}
|
||
}
|
||
let alias = dom.create.alias(doclet.name, type);
|
||
this.processGeneric(doclet, alias, null);
|
||
return alias;
|
||
}
|
||
setParams(doclet, obj) {
|
||
let parameters = [];
|
||
if (doclet.params) {
|
||
let optional = false;
|
||
obj.jsDocComment = '';
|
||
for (let paramDoc of doclet.params) {
|
||
// TODO REMOVE TEMP FIX
|
||
if (paramDoc.name.indexOf('.') != -1) {
|
||
console.log(`Warning: ignoring param with '.' for '${doclet.longname}' in ${doclet.meta.filename}@${doclet.meta.lineno}`);
|
||
let defaultVal = paramDoc.defaultvalue !== undefined ? ` Default ${String(paramDoc.defaultvalue)}.` : '';
|
||
if (paramDoc.description)
|
||
obj.jsDocComment += `\n@param ${paramDoc.name} ${paramDoc.description.replace(regexEndLine, '$1\n')}` + defaultVal;
|
||
else if (defaultVal.length)
|
||
obj.jsDocComment += `\n@param ${paramDoc.name} ` + defaultVal;
|
||
continue;
|
||
}
|
||
///////////////////////
|
||
let param = dom.create.parameter(paramDoc.name, this.parseType(paramDoc));
|
||
parameters.push(param);
|
||
if (optional && paramDoc.optional != true) {
|
||
console.log(`Warning: correcting to optional: parameter '${paramDoc.name}' for '${doclet.longname}' in ${doclet.meta.filename}@${doclet.meta.lineno}`);
|
||
paramDoc.optional = true;
|
||
}
|
||
this.processFlags(paramDoc, param);
|
||
optional = optional || paramDoc.optional === true;
|
||
let defaultVal = paramDoc.defaultvalue !== undefined ? ` Default ${String(paramDoc.defaultvalue)}.` : '';
|
||
if (paramDoc.description)
|
||
obj.jsDocComment += `\n@param ${paramDoc.name} ${paramDoc.description.replace(regexEndLine, '$1\n')}` + defaultVal;
|
||
else if (defaultVal.length)
|
||
obj.jsDocComment += `\n@param ${paramDoc.name} ` + defaultVal;
|
||
}
|
||
}
|
||
obj.parameters = parameters;
|
||
}
|
||
parseType(typeDoc) {
|
||
if (!typeDoc || !typeDoc.type) {
|
||
return dom.type.any;
|
||
}
|
||
else {
|
||
let types = [];
|
||
for (let name of typeDoc.type.names) {
|
||
name = this.prepareTypeName(name);
|
||
let type = dom.create.namedTypeReference(this.processTypeName(name));
|
||
types.push(type);
|
||
}
|
||
if (types.length == 1)
|
||
return types[0];
|
||
else
|
||
return dom.create.union(types);
|
||
}
|
||
}
|
||
prepareTypeName(name) {
|
||
if (name.indexOf('*') != -1) {
|
||
name = name.split('*').join('any');
|
||
}
|
||
if (name.indexOf('.<') != -1 && name !== 'Array.<function()>') {
|
||
name = name.split('.<').join('<');
|
||
}
|
||
return name;
|
||
}
|
||
processTypeName(name) {
|
||
if (name === 'float')
|
||
return 'number';
|
||
if (name === 'function')
|
||
return 'Function';
|
||
if (name === 'Array.<function()>')
|
||
return 'Function[]';
|
||
if (name === 'array')
|
||
return 'any[]';
|
||
if (name.startsWith('Array<')) {
|
||
let matches = name.match(/^Array<(.*)>$/);
|
||
if (matches && matches[1]) {
|
||
return this.processTypeName(matches[1]) + '[]';
|
||
}
|
||
}
|
||
else if (name.startsWith('Object<')) {
|
||
let matches = name.match(/^Object<(.*)>$/);
|
||
if (matches && matches[1]) {
|
||
if (matches[1].indexOf(',') != -1) {
|
||
let parts = matches[1].split(',');
|
||
return `{[key: ${this.processTypeName(parts[0])}]: ${this.processTypeName(parts[1])}}`;
|
||
}
|
||
else {
|
||
return `{[key: string]: ${this.processTypeName(matches[1])}}`;
|
||
}
|
||
}
|
||
}
|
||
return name;
|
||
}
|
||
processFlags(doclet, obj) {
|
||
obj.flags = dom.DeclarationFlags.None;
|
||
if (doclet.variable === true) {
|
||
obj.flags |= dom.ParameterFlags.Rest;
|
||
let type = obj.type;
|
||
if (!type.name.endsWith('[]')) {
|
||
if (type.name != 'any')
|
||
console.log(`Warning: rest parameter should be an array type for ${doclet.longname}`);
|
||
type.name = type.name + '[]'; // Must be an array
|
||
}
|
||
}
|
||
else if (doclet.optional === true) { // Rest implies Optional – no need to flag it as such
|
||
if (obj['kind'] === 'parameter')
|
||
obj.flags |= dom.ParameterFlags.Optional;
|
||
else
|
||
obj.flags |= dom.DeclarationFlags.Optional;
|
||
}
|
||
switch (doclet.access) {
|
||
case 'protected':
|
||
obj.flags |= dom.DeclarationFlags.Protected;
|
||
break;
|
||
case 'private':
|
||
obj.flags |= dom.DeclarationFlags.Private;
|
||
break;
|
||
}
|
||
if (doclet.readonly || doclet.kind === 'constant')
|
||
obj.flags |= dom.DeclarationFlags.ReadOnly;
|
||
if (doclet.scope === 'static')
|
||
obj.flags |= dom.DeclarationFlags.Static;
|
||
}
|
||
processGeneric(doclet, obj, params) {
|
||
if (doclet.tags)
|
||
for (let tag of doclet.tags) {
|
||
if (tag.originalTitle === 'generic') {
|
||
/**
|
||
* {string} K - [key]
|
||
* 1 = string | 2 = null | 3 = K | 4 = key
|
||
*
|
||
* {string=string} K - [key]
|
||
* 1 = string | 2 = string | 3 = K | 4 = key
|
||
*/
|
||
const matches = tag.value.match(/(?:(?:{)([^}=]+)(?:=)?([^}=]+)?(?:}))?\s?([^\s]+)(?:\s?-\s?(?:\[)(.+)(?:\]))?/);
|
||
const [_, _type, _defaultType, _name, _paramsNames] = matches;
|
||
const typeParam = dom.create.typeParameter(_name, _type == null ? null : dom.create.typeParameter(_type));
|
||
if (_defaultType != null) {
|
||
typeParam.defaultType = dom.create.typeParameter(_defaultType);
|
||
}
|
||
obj.typeParameters.push(typeParam);
|
||
handleOverrides(_paramsNames, _name);
|
||
}
|
||
else if (tag.originalTitle === 'genericUse') {
|
||
let matches = tag.value.match(/(?:(?:{)([^}]+)(?:}))(?:\s?-\s?(?:\[)(.+)(?:\]))?/);
|
||
let overrideType = this.prepareTypeName(matches[1]);
|
||
handleOverrides(matches[2], this.processTypeName(overrideType));
|
||
}
|
||
}
|
||
function handleOverrides(matchedString, overrideType) {
|
||
if (matchedString != null) {
|
||
let overrides = matchedString.split(',');
|
||
if (params != null) {
|
||
for (let param of params) {
|
||
if (overrides.indexOf(param.name) != -1) {
|
||
param.type = dom.create.namedTypeReference(overrideType);
|
||
}
|
||
}
|
||
}
|
||
if (overrides.indexOf('$return') != -1) { // has $return, must be a function
|
||
obj.returnType = dom.create.namedTypeReference(overrideType);
|
||
}
|
||
if (overrides.indexOf('$type') != -1) { // has $type, must be a property
|
||
obj.type = dom.create.namedTypeReference(overrideType);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
exports.Parser = Parser;
|
||
//# sourceMappingURL=Parser.js.map
|