From 1bfe08b1ecf0d8e0b10cc3a689182eb0e740371d Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Thu, 22 Feb 2024 23:41:38 -0800 Subject: [PATCH 1/2] fix: infer EventEmitter extensions from API --- src/module-declaration.ts | 31 +++++++++++++++-------- src/utils.ts | 53 ++++++++++++++++++--------------------- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/src/module-declaration.ts b/src/module-declaration.ts index 489fc99..b9522e9 100644 --- a/src/module-declaration.ts +++ b/src/module-declaration.ts @@ -31,12 +31,11 @@ export const generateModuleDeclaration = ( ) => { const moduleAPI = modules[_.upperFirst(module.name)] || []; const newModule = !modules[_.upperFirst(module.name)]; - const isStaticVersion = - module.type === 'Module' && - API.some( - (tModule, tIndex) => - index !== tIndex && tModule.name.toLowerCase() === module.name.toLowerCase(), - ); + const instanceModuleForStaticVersion = API.find( + (tModule, tIndex) => + index !== tIndex && tModule.name.toLowerCase() === module.name.toLowerCase(), + ); + const isStaticVersion = module.type === 'Module' && !!instanceModuleForStaticVersion; const isClass = module.type === 'Class' || isStaticVersion; const parentModules: ParsedDocumentationResult = []; let parentModule: @@ -53,11 +52,23 @@ export const generateModuleDeclaration = ( // Interface Declaration if (newModule) { if (module.type !== 'Structure') { - if (utils.isEmitter(module)) { + let extendsInfo = ''; + if (module.extends) { + extendsInfo = ` extends ${module.extends}`; + } else if ( + utils.isEmitter(module) || + (isStaticVersion && + instanceModuleForStaticVersion && + utils.isEmitter(instanceModuleForStaticVersion)) + ) { + extendsInfo = ` extends ${isClass ? 'NodeEventEmitter' : 'NodeJS.EventEmitter'}`; + } + if (module.name.toLowerCase() === 'session' && isStaticVersion) { + console.log({ isStaticVersion, instanceModuleForStaticVersion, extendsInfo }); + } + if (extendsInfo) { moduleAPI.push( - `${isClass ? 'class' : 'interface'} ${_.upperFirst( - module.name, - )} extends ${module.extends || (isClass ? 'NodeEventEmitter' : 'NodeJS.EventEmitter')} {`, + `${isClass ? 'class' : 'interface'} ${_.upperFirst(module.name)}${extendsInfo} {`, ); moduleAPI.push('', `// Docs: ${module.websiteUrl}`, ''); } else { diff --git a/src/utils.ts b/src/utils.ts index 08c245d..0220f39 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -8,6 +8,7 @@ import { DocumentationBlock, DetailedFunctionType, DocumentationTag, + ParsedDocumentationResult, } from '@electron/docs-parser'; import _ from 'lodash'; import d from 'debug'; @@ -257,34 +258,30 @@ export const paramify = (paramName: string) => { } return paramName; }; -// TODO: Infer through electron-docs-linter/parser -export const isEmitter = (module: Pick) => { - const nonEventEmitters = [ - 'menuitem', - 'nativeimage', - 'shell', - 'browserview', - 'webrequest', - 'crashreporter', - 'dock', - 'commandline', - 'browserwindowproxy', - 'clipboard', - 'contenttracing', - 'desktopcapturer', - 'dialog', - 'globalshortcut', - 'powersaveblocker', - 'touchbar', - 'touchbarbutton', - 'net', - 'netlog', - 'protocol', - 'contextbridge', - 'webframe', - 'messagechannelmain', - ]; - return !nonEventEmitters.includes(module.name.toLowerCase()); +export const isEmitter = (doc: ParsedDocumentationResult[0]) => { + // Is a module, has events, is an eventemitter + if (doc.type === 'Module' && doc.events.length) { + return true; + } + + // Is a class, has instance events, is an eventemitter + if (doc.type === 'Class' && doc.instanceEvents.length) { + return true; + } + + // Implements the on and removeListener methods normally means + // it's an EventEmitter wrapper like ipcMain or ipcRenderer + const relevantMethods = + doc.type === 'Class' ? doc.instanceMethods : doc.type === 'Module' ? doc.methods : []; + if ( + relevantMethods.find(m => m.name === 'on') && + relevantMethods.find(m => m.name === 'removeListener') + ) { + return true; + } + + // Structure and Elements are not eventemitters, so bail here + return false; }; export const isPrimitive = (type: string) => { const primitives = ['boolean', 'number', 'any', 'string', 'void', 'unknown']; From 4d9037208143905280defb77a3701a1787da8d97 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 5 Mar 2024 10:37:54 -0800 Subject: [PATCH 2/2] chore: update tests --- test/utils_spec.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/utils_spec.js b/test/utils_spec.js index 02e3607..dab0331 100644 --- a/test/utils_spec.js +++ b/test/utils_spec.js @@ -116,11 +116,18 @@ describe('utils', () => { describe('isEmitter', () => { it('should return true on most modules', () => { - expect(utils.isEmitter({ name: 'app' })).to.eq(true); + expect(utils.isEmitter({ name: 'app', type: 'Module', events: [1] })).to.eq(true); }); it('should return false for specific non-emitter modules', () => { - expect(utils.isEmitter({ name: 'menuitem' })).to.eq(false); + expect( + utils.isEmitter({ + name: 'menuitem', + type: 'Class', + instanceEvents: [], + instanceMethods: [], + }), + ).to.eq(false); }); });