Skip to content

Commit 478d24b

Browse files
fix: infer EventEmitter extensions from API (#259)
* fix: infer EventEmitter extensions from API * chore: update tests
1 parent a54887c commit 478d24b

File tree

3 files changed

+55
-40
lines changed

3 files changed

+55
-40
lines changed

src/module-declaration.ts

+21-10
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,11 @@ export const generateModuleDeclaration = (
3131
) => {
3232
const moduleAPI = modules[_.upperFirst(module.name)] || [];
3333
const newModule = !modules[_.upperFirst(module.name)];
34-
const isStaticVersion =
35-
module.type === 'Module' &&
36-
API.some(
37-
(tModule, tIndex) =>
38-
index !== tIndex && tModule.name.toLowerCase() === module.name.toLowerCase(),
39-
);
34+
const instanceModuleForStaticVersion = API.find(
35+
(tModule, tIndex) =>
36+
index !== tIndex && tModule.name.toLowerCase() === module.name.toLowerCase(),
37+
);
38+
const isStaticVersion = module.type === 'Module' && !!instanceModuleForStaticVersion;
4039
const isClass = module.type === 'Class' || isStaticVersion;
4140
const parentModules: ParsedDocumentationResult = [];
4241
let parentModule:
@@ -53,11 +52,23 @@ export const generateModuleDeclaration = (
5352
// Interface Declaration
5453
if (newModule) {
5554
if (module.type !== 'Structure') {
56-
if (utils.isEmitter(module)) {
55+
let extendsInfo = '';
56+
if (module.extends) {
57+
extendsInfo = ` extends ${module.extends}`;
58+
} else if (
59+
utils.isEmitter(module) ||
60+
(isStaticVersion &&
61+
instanceModuleForStaticVersion &&
62+
utils.isEmitter(instanceModuleForStaticVersion))
63+
) {
64+
extendsInfo = ` extends ${isClass ? 'NodeEventEmitter' : 'NodeJS.EventEmitter'}`;
65+
}
66+
if (module.name.toLowerCase() === 'session' && isStaticVersion) {
67+
console.log({ isStaticVersion, instanceModuleForStaticVersion, extendsInfo });
68+
}
69+
if (extendsInfo) {
5770
moduleAPI.push(
58-
`${isClass ? 'class' : 'interface'} ${_.upperFirst(
59-
module.name,
60-
)} extends ${module.extends || (isClass ? 'NodeEventEmitter' : 'NodeJS.EventEmitter')} {`,
71+
`${isClass ? 'class' : 'interface'} ${_.upperFirst(module.name)}${extendsInfo} {`,
6172
);
6273
moduleAPI.push('', `// Docs: ${module.websiteUrl}`, '');
6374
} else {

src/utils.ts

+25-28
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
DocumentationBlock,
99
DetailedFunctionType,
1010
DocumentationTag,
11+
ParsedDocumentationResult,
1112
} from '@electron/docs-parser';
1213
import _ from 'lodash';
1314
import d from 'debug';
@@ -257,34 +258,30 @@ export const paramify = (paramName: string) => {
257258
}
258259
return paramName;
259260
};
260-
// TODO: Infer through electron-docs-linter/parser
261-
export const isEmitter = (module: Pick<ModuleDocumentationContainer, 'name'>) => {
262-
const nonEventEmitters = [
263-
'menuitem',
264-
'nativeimage',
265-
'shell',
266-
'browserview',
267-
'webrequest',
268-
'crashreporter',
269-
'dock',
270-
'commandline',
271-
'browserwindowproxy',
272-
'clipboard',
273-
'contenttracing',
274-
'desktopcapturer',
275-
'dialog',
276-
'globalshortcut',
277-
'powersaveblocker',
278-
'touchbar',
279-
'touchbarbutton',
280-
'net',
281-
'netlog',
282-
'protocol',
283-
'contextbridge',
284-
'webframe',
285-
'messagechannelmain',
286-
];
287-
return !nonEventEmitters.includes(module.name.toLowerCase());
261+
export const isEmitter = (doc: ParsedDocumentationResult[0]) => {
262+
// Is a module, has events, is an eventemitter
263+
if (doc.type === 'Module' && doc.events.length) {
264+
return true;
265+
}
266+
267+
// Is a class, has instance events, is an eventemitter
268+
if (doc.type === 'Class' && doc.instanceEvents.length) {
269+
return true;
270+
}
271+
272+
// Implements the on and removeListener methods normally means
273+
// it's an EventEmitter wrapper like ipcMain or ipcRenderer
274+
const relevantMethods =
275+
doc.type === 'Class' ? doc.instanceMethods : doc.type === 'Module' ? doc.methods : [];
276+
if (
277+
relevantMethods.find(m => m.name === 'on') &&
278+
relevantMethods.find(m => m.name === 'removeListener')
279+
) {
280+
return true;
281+
}
282+
283+
// Structure and Elements are not eventemitters, so bail here
284+
return false;
288285
};
289286
export const isPrimitive = (type: string) => {
290287
const primitives = ['boolean', 'number', 'any', 'string', 'void', 'unknown'];

test/utils_spec.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,18 @@ describe('utils', () => {
116116

117117
describe('isEmitter', () => {
118118
it('should return true on most modules', () => {
119-
expect(utils.isEmitter({ name: 'app' })).to.eq(true);
119+
expect(utils.isEmitter({ name: 'app', type: 'Module', events: [1] })).to.eq(true);
120120
});
121121

122122
it('should return false for specific non-emitter modules', () => {
123-
expect(utils.isEmitter({ name: 'menuitem' })).to.eq(false);
123+
expect(
124+
utils.isEmitter({
125+
name: 'menuitem',
126+
type: 'Class',
127+
instanceEvents: [],
128+
instanceMethods: [],
129+
}),
130+
).to.eq(false);
124131
});
125132
});
126133

0 commit comments

Comments
 (0)