![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/dceprojects.corals.io/vendor/nesk/puphpeteer/src/ |
import * as ts from 'typescript'; const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); type ObjectMemberAsJson = { [key: string]: string; } type ObjectMembersAsJson = { properties: ObjectMemberAsJson, getters: ObjectMemberAsJson, methods: ObjectMemberAsJson, } type ClassAsJson = { name: string } & ObjectMembersAsJson type MemberContext = 'class'|'literal' type TypeContext = 'methodReturn' class TypeNotSupportedError extends Error { constructor(message?: string) { super(message || 'This type is currently not supported.'); } } interface SupportChecker { supportsMethodName(methodName: string): boolean; } class JsSupportChecker { supportsMethodName(methodName: string): boolean { return true; } } class PhpSupportChecker { supportsMethodName(methodName: string): boolean { return !methodName.includes('$'); } } interface DocumentationFormatter { formatProperty(name: string, type: string, context: MemberContext): string formatGetter(name: string, type: string): string formatAnonymousFunction(parameters: string, returnType: string): string formatFunction(name: string, parameters: string, returnType: string): string formatParameter(name: string, type: string, isVariadic: boolean, isOptional: boolean): string formatTypeAny(): string formatTypeUnknown(): string formatTypeVoid(): string formatTypeUndefined(): string formatTypeNull(): string formatTypeBoolean(): string formatTypeNumber(): string formatTypeString(): string formatTypeReference(type: string): string formatGeneric(parentType: string, argumentTypes: string[], context?: TypeContext): string formatQualifiedName(left: string, right: string): string formatIndexedAccessType(object: string, index: string): string formatLiteralType(value: string): string formatUnion(types: string[]): string formatIntersection(types: string[]): string formatObject(members: string[]): string formatArray(type: string): string } class JsDocumentationFormatter implements DocumentationFormatter { formatProperty(name: string, type: string, context: MemberContext): string { return `${name}: ${type}`; } formatGetter(name: string, type: string): string { return `${name}: ${type}`; } formatAnonymousFunction(parameters: string, returnType: string): string { return `(${parameters}) => ${returnType}`; } formatFunction(name: string, parameters: string, returnType: string): string { return `${name}(${parameters}): ${returnType}`; } formatParameter(name: string, type: string, isVariadic: boolean, isOptional: boolean): string { return `${isVariadic ? '...' : ''}${name}${isOptional ? '?' : ''}: ${type}`; } formatTypeAny(): string { return 'any'; } formatTypeUnknown(): string { return 'unknown'; } formatTypeVoid(): string { return 'void'; } formatTypeUndefined(): string { return 'undefined'; } formatTypeNull(): string { return 'null'; } formatTypeBoolean(): string { return 'boolean'; } formatTypeNumber(): string { return 'number'; } formatTypeString(): string { return 'string'; } formatTypeReference(type: string): string { return type; } formatGeneric(parentType: string, argumentTypes: string[], context?: TypeContext): string { return `${parentType}<${argumentTypes.join(', ')}>`; } formatQualifiedName(left: string, right: string): string { return `${left}.${right}`; } formatIndexedAccessType(object: string, index: string): string { return `${object}[${index}]`; } formatLiteralType(value: string): string { return `'${value}'`; } formatUnion(types: string[]): string { return types.join(' | '); } formatIntersection(types: string[]): string { return types.join(' & '); } formatObject(members: string[]): string { return `{ ${members.join(', ')} }`; } formatArray(type: string): string { return `${type}[]`; } } class PhpDocumentationFormatter implements DocumentationFormatter { static readonly allowedJsClasses = ['Promise', 'Record', 'Map']; constructor( private readonly resourcesNamespace: string, private readonly resources: string[], ) {} formatProperty(name: string, type: string, context: MemberContext): string { return context === 'class' ? `${type} ${name}` : `${name}: ${type}`; } formatGetter(name: string, type: string): string { return `${type} ${name}`; } formatAnonymousFunction(parameters: string, returnType: string): string { return `callable(${parameters}): ${returnType}`; } formatFunction(name: string, parameters: string, returnType: string): string { return `${returnType} ${name}(${parameters})`; } formatParameter(name: string, type: string, isVariadic: boolean, isOptional: boolean): string { if (isVariadic && type.endsWith('[]')) { type = type.slice(0, -2); } const defaultValue = isOptional ? ' = null' : ''; return `${type} ${isVariadic ? '...' : ''}\$${name}${defaultValue}`; } formatTypeAny(): string { return 'mixed'; } formatTypeUnknown(): string { return 'mixed'; } formatTypeVoid(): string { return 'void'; } formatTypeUndefined(): string { return 'null'; } formatTypeNull(): string { return 'null'; } formatTypeBoolean(): string { return 'bool'; } formatTypeNumber(): string { return 'float'; } formatTypeString(): string { return 'string'; } formatTypeReference(type: string): string { // Allow some specific JS classes to be used in phpDoc if (PhpDocumentationFormatter.allowedJsClasses.includes(type)) { return type; } // Prefix PHP resources with their namespace if (this.resources.includes(type)) { return `\\${this.resourcesNamespace}\\${type}`; } // If the type ends with "options" then convert it to an associative array if (/options$/i.test(type)) { return 'array<string, mixed>'; } // Types ending with "Fn" are always callables or strings if (type.endsWith('Fn')) { return this.formatUnion(['callable', 'string']); } if (type === 'Function') { return 'callable'; } if (type === 'PuppeteerLifeCycleEvent') { return 'string'; } if (type === 'Serializable') { return this.formatUnion(['int', 'float', 'string', 'bool', 'null', 'array']); } if (type === 'SerializableOrJSHandle') { return this.formatUnion([this.formatTypeReference('Serializable'), this.formatTypeReference('JSHandle')]); } if (type === 'HandleType') { return this.formatUnion([this.formatTypeReference('JSHandle'), this.formatTypeReference('ElementHandle')]); } return 'mixed'; } formatGeneric(parentType: string, argumentTypes: string[], context?: TypeContext): string { // Avoid generics with "mixed" as parent type if (parentType === 'mixed') { return 'mixed'; } // Unwrap promises for method return types if (context === 'methodReturn' && parentType === 'Promise' && argumentTypes.length === 1) { return argumentTypes[0]; } // Transform Record and Map types to associative arrays if (['Record', 'Map'].includes(parentType) && argumentTypes.length === 2) { parentType = 'array'; } return `${parentType}<${argumentTypes.join(', ')}>`; } formatQualifiedName(left: string, right: string): string { return `mixed`; } formatIndexedAccessType(object: string, index: string): string { return `mixed`; } formatLiteralType(value: string): string { return `'${value}'`; } private prepareUnionOrIntersectionTypes(types: string[]): string[] { // Replace "void" type by "null" types = types.map(type => type === 'void' ? 'null' : type) // Remove duplicates const uniqueTypes = new Set(types); return Array.from(uniqueTypes.values()); } formatUnion(types: string[]): string { const result = this.prepareUnionOrIntersectionTypes(types).join('|'); // Convert enums to string type if (/^('\w+'\|)*'\w+'$/.test(result)) { return 'string'; } return result; } formatIntersection(types: string[]): string { return this.prepareUnionOrIntersectionTypes(types).join('&'); } formatObject(members: string[]): string { return `array{ ${members.join(', ')} }`; } formatArray(type: string): string { return `${type}[]`; } } class DocumentationGenerator { constructor( private readonly supportChecker: SupportChecker, private readonly formatter: DocumentationFormatter, ) {} private hasModifierForNode( node: ts.Node, modifier: ts.KeywordSyntaxKind ): boolean { if (!node.modifiers) { return false; } return node.modifiers.some((node) => node.kind === modifier); } private isNodeAccessible(node: ts.Node): boolean { // @ts-ignore if (node.name && this.getNamedDeclarationAsString(node).startsWith('_')) { return false; } return ( this.hasModifierForNode(node, ts.SyntaxKind.PublicKeyword) || (!this.hasModifierForNode(node, ts.SyntaxKind.ProtectedKeyword) && !this.hasModifierForNode(node, ts.SyntaxKind.PrivateKeyword)) ); } private isNodeStatic(node: ts.Node): boolean { return this.hasModifierForNode(node, ts.SyntaxKind.StaticKeyword); } public getClassDeclarationAsJson(node: ts.ClassDeclaration): ClassAsJson { return Object.assign( { name: this.getNamedDeclarationAsString(node) }, this.getMembersAsJson(node.members, 'class'), ); } private getMembersAsJson(members: ts.NodeArray<ts.NamedDeclaration>, context: MemberContext): ObjectMembersAsJson { const json: ObjectMembersAsJson = { properties: {}, getters: {}, methods: {}, }; for (const member of members) { if (!this.isNodeAccessible(member) || this.isNodeStatic(member)) { continue; } const name = member.name ? this.getNamedDeclarationAsString(member) : null; if (ts.isPropertySignature(member) || ts.isPropertyDeclaration(member)) { json.properties[name] = this.getPropertySignatureOrDeclarationAsString(member, context); } else if (ts.isGetAccessorDeclaration(member)) { json.getters[name] = this.getGetAccessorDeclarationAsString(member); } else if (ts.isMethodDeclaration(member)) { if (!this.supportChecker.supportsMethodName(name)) { continue; } json.methods[name] = this.getSignatureDeclarationBaseAsString(member); } } return json; } private getPropertySignatureOrDeclarationAsString( node: ts.PropertySignature | ts.PropertyDeclaration, context: MemberContext ): string { const type = this.getTypeNodeAsString(node.type); const name = this.getNamedDeclarationAsString(node); return this.formatter.formatProperty(name, type, context); } private getGetAccessorDeclarationAsString( node: ts.GetAccessorDeclaration ): string { const type = this.getTypeNodeAsString(node.type); const name = this.getNamedDeclarationAsString(node); return this.formatter.formatGetter(name, type); } private getSignatureDeclarationBaseAsString( node: ts.SignatureDeclarationBase ): string { const name = node.name && this.getNamedDeclarationAsString(node); const parameters = node.parameters .map(parameter => this.getParameterDeclarationAsString(parameter)) .join(', '); const returnType = this.getTypeNodeAsString(node.type, name ? 'methodReturn' : undefined); return name ? this.formatter.formatFunction(name, parameters, returnType) : this.formatter.formatAnonymousFunction(parameters, returnType); } private getParameterDeclarationAsString(node: ts.ParameterDeclaration): string { const name = this.getNamedDeclarationAsString(node); const type = this.getTypeNodeAsString(node.type); const isVariadic = node.dotDotDotToken !== undefined; const isOptional = node.questionToken !== undefined; return this.formatter.formatParameter(name, type, isVariadic, isOptional); } private getTypeNodeAsString(node: ts.TypeNode, context?: TypeContext): string { if (node.kind === ts.SyntaxKind.AnyKeyword) { return this.formatter.formatTypeAny(); } else if (node.kind === ts.SyntaxKind.UnknownKeyword) { return this.formatter.formatTypeUnknown(); } else if (node.kind === ts.SyntaxKind.VoidKeyword) { return this.formatter.formatTypeVoid(); } else if (node.kind === ts.SyntaxKind.UndefinedKeyword) { return this.formatter.formatTypeUndefined(); } else if (node.kind === ts.SyntaxKind.NullKeyword) { return this.formatter.formatTypeNull(); } else if (node.kind === ts.SyntaxKind.BooleanKeyword) { return this.formatter.formatTypeBoolean(); } else if (node.kind === ts.SyntaxKind.NumberKeyword) { return this.formatter.formatTypeNumber(); } else if (node.kind === ts.SyntaxKind.StringKeyword) { return this.formatter.formatTypeString(); } else if (ts.isTypeReferenceNode(node)) { return this.getTypeReferenceNodeAsString(node, context); } else if (ts.isIndexedAccessTypeNode(node)) { return this.getIndexedAccessTypeNodeAsString(node); } else if (ts.isLiteralTypeNode(node)) { return this.getLiteralTypeNodeAsString(node); } else if (ts.isUnionTypeNode(node)) { return this.getUnionTypeNodeAsString(node, context); } else if (ts.isIntersectionTypeNode(node)) { return this.getIntersectionTypeNodeAsString(node, context); } else if (ts.isTypeLiteralNode(node)) { return this.getTypeLiteralNodeAsString(node); } else if (ts.isArrayTypeNode(node)) { return this.getArrayTypeNodeAsString(node, context); } else if (ts.isFunctionTypeNode(node)) { return this.getSignatureDeclarationBaseAsString(node); } else { throw new TypeNotSupportedError(); } } private getTypeReferenceNodeAsString(node: ts.TypeReferenceNode, context?: TypeContext): string { return this.getGenericTypeReferenceNodeAsString(node, context) || this.getSimpleTypeReferenceNodeAsString(node); } private getGenericTypeReferenceNodeAsString(node: ts.TypeReferenceNode, context?: TypeContext): string | null { if (!node.typeArguments || node.typeArguments.length === 0) { return null; } const parentType = this.getSimpleTypeReferenceNodeAsString(node); const argumentTypes = node.typeArguments.map((node) => this.getTypeNodeAsString(node)); return this.formatter.formatGeneric(parentType, argumentTypes, context); } private getSimpleTypeReferenceNodeAsString(node: ts.TypeReferenceNode): string { return ts.isIdentifier(node.typeName) ? this.formatter.formatTypeReference(this.getIdentifierAsString(node.typeName)) : this.getQualifiedNameAsString(node.typeName); } private getQualifiedNameAsString(node: ts.QualifiedName): string { const right = this.getIdentifierAsString(node.right); const left = ts.isIdentifier(node.left) ? this.getIdentifierAsString(node.left) : this.getQualifiedNameAsString(node.left); return this.formatter.formatQualifiedName(left, right); } private getIndexedAccessTypeNodeAsString( node: ts.IndexedAccessTypeNode ): string { const object = this.getTypeNodeAsString(node.objectType); const index = this.getTypeNodeAsString(node.indexType); return this.formatter.formatIndexedAccessType(object, index); } private getLiteralTypeNodeAsString(node: ts.LiteralTypeNode): string { if (node.literal.kind === ts.SyntaxKind.NullKeyword) { return this.formatter.formatTypeNull(); } else if (node.literal.kind === ts.SyntaxKind.BooleanKeyword) { return this.formatter.formatTypeBoolean(); } else if (ts.isLiteralExpression(node.literal)) { return this.formatter.formatLiteralType(node.literal.text); } throw new TypeNotSupportedError(); } private getUnionTypeNodeAsString(node: ts.UnionTypeNode, context?: TypeContext): string { const types = node.types.map(typeNode => this.getTypeNodeAsString(typeNode, context)); return this.formatter.formatUnion(types); } private getIntersectionTypeNodeAsString(node: ts.IntersectionTypeNode, context?: TypeContext): string { const types = node.types.map(typeNode => this.getTypeNodeAsString(typeNode, context)); return this.formatter.formatIntersection(types); } private getTypeLiteralNodeAsString(node: ts.TypeLiteralNode): string { const members = this.getMembersAsJson(node.members, 'literal'); const stringMembers = Object.values(members).map(Object.values); const flattenMembers = stringMembers.reduce((acc, val) => acc.concat(val), []); return this.formatter.formatObject(flattenMembers); } private getArrayTypeNodeAsString(node: ts.ArrayTypeNode, context?: TypeContext): string { const type = this.getTypeNodeAsString(node.elementType, context); return this.formatter.formatArray(type); } private getNamedDeclarationAsString(node: ts.NamedDeclaration): string { if (!ts.isIdentifier(node.name)) { throw new TypeNotSupportedError(); } return this.getIdentifierAsString(node.name); } private getIdentifierAsString(node: ts.Identifier): string { return String(node.escapedText); } } const { argv } = yargs(hideBin(process.argv)) .command('$0 <language> <definition-files...>') .option('resources-namespace', { type: 'string', default: '' }) .option('resources', { type: 'array', default: [] }) .option('pretty', { type: 'boolean', default: false }) let supportChecker, formatter; switch (argv.language.toUpperCase()) { case 'JS': supportChecker = new JsSupportChecker(); formatter = new JsDocumentationFormatter(); break; case 'PHP': supportChecker = new PhpSupportChecker(); formatter = new PhpDocumentationFormatter(argv.resourcesNamespace, argv.resources); break; default: console.error(`Unsupported "${argv.language}" language.`); process.exit(1); } const docGenerator = new DocumentationGenerator(supportChecker, formatter); const program = ts.createProgram(argv.definitionFiles, {}); const classes = {}; for (const fileName of argv.definitionFiles) { const sourceFile = program.getSourceFile(fileName); ts.forEachChild(sourceFile, node => { if (ts.isClassDeclaration(node)) { const classAsJson = docGenerator.getClassDeclarationAsJson(node); classes[classAsJson.name] = classAsJson; } }); } process.stdout.write(JSON.stringify(classes, null, argv.pretty ? 2 : null));