Skip to content

Commit 906aff6

Browse files
fix: remove lodash dependency and replace with native utilities (#339)
* chore: remove lodash dependency Replace lodash usage with native equivalents (structuredClone, isDeepStrictEqual, String#trimEnd/repeat, Object.assign/entries, array spread/flatMap) and small local helpers for upperFirst, lowerFirst, and camelCase. * test: add coverage for upperFirst, lowerFirst and camelCase helpers --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 606fc7d commit 906aff6

10 files changed

Lines changed: 150 additions & 98 deletions

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
"devDependencies": {
3030
"@electron/docs-parser": "^2.0.0",
3131
"@types/debug": "^4.1.12",
32-
"@types/lodash": "^4.17.7",
3332
"husky": "^9.1.6",
3433
"lint-staged": "^16.2.7",
3534
"prettier": "^3.3.3",
@@ -40,7 +39,6 @@
4039
"@types/node": "^20.11.25",
4140
"chalk": "^5.3.0",
4241
"debug": "^4.3.7",
43-
"lodash": "^4.17.11",
4442
"ora": "^8.1.0",
4543
"pretty-ms": "^9.1.0"
4644
},

src/dynamic-param-interfaces.ts

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { isDeepStrictEqual } from 'node:util';
2+
13
import {
24
EventParameterDocumentation,
35
DetailedObjectType,
@@ -7,9 +9,9 @@ import {
79
} from '@electron/docs-parser';
810
import chalk from 'chalk';
911
import d from 'debug';
10-
import _ from 'lodash';
1112

1213
import * as utils from './utils.js';
14+
import { upperFirst, lowerFirst, camelCase } from './utils.js';
1315

1416
const debug = d('dynamic-param');
1517

@@ -39,11 +41,13 @@ const polite = (s: string): string => {
3941
const ignoreDescriptions = <T extends EventParameterDocumentation>(
4042
props: T[],
4143
): Pick<T, Exclude<keyof T, 'description'>>[] =>
42-
_.map(props, (p) => {
43-
const { description, ...toReturn } = p;
44+
props
45+
.map((p) => {
46+
const { description, ...toReturn } = p;
4447

45-
return toReturn;
46-
}).sort((a, b) => a.name.localeCompare(b.name));
48+
return toReturn;
49+
})
50+
.sort((a, b) => a.name.localeCompare(b.name));
4751

4852
const noDescriptionCache = new WeakMap();
4953
const unsetDescriptions = (o: any): any => {
@@ -71,48 +75,48 @@ const createParamInterface = (
7175
): string => {
7276
const maybeArray = (type: string) => (param.collection ? `Array<${type}>` : type);
7377
const potentialExistingArgType = polite(IName);
74-
const potentialExistingArgName = _.lowerFirst(polite(IName));
75-
let argType = polite(IName) + _.upperFirst(_.camelCase(param.name));
78+
const potentialExistingArgName = lowerFirst(polite(IName));
79+
let argType = polite(IName) + upperFirst(camelCase(param.name));
7680
let argName = param.name;
7781
// TODO: Note. It is still possible for even backupIName to be already used
7882
let usingExistingParamInterface = false;
79-
_.forIn(paramInterfacesToDeclare, (value, key) => {
83+
for (const [key, value] of Object.entries(paramInterfacesToDeclare)) {
8084
const test = unsetDescriptions(
81-
_.assign({}, param, {
85+
Object.assign({}, param, {
8286
name: argName,
8387
tName: argType,
8488
required: value.required,
8589
additionalTags: (param as any).additionalTags || [],
8690
}),
8791
);
8892
const potentialTest = unsetDescriptions(
89-
_.assign({}, param, {
93+
Object.assign({}, param, {
9094
name: potentialExistingArgName,
9195
tName: potentialExistingArgType,
9296
required: value.required,
9397
additionalTags: (param as any).additionalTags || [],
9498
}),
9599
);
96100
const unsetValue = unsetDescriptions(value);
97-
if (_.isEqual(test, unsetValue) || _.isEqual(potentialTest, unsetValue)) {
101+
if (isDeepStrictEqual(test, unsetValue) || isDeepStrictEqual(potentialTest, unsetValue)) {
98102
usingExistingParamInterface = true;
99103
debug(
100104
chalk.cyan(
101-
`Using existing type for param name ${argType} --> ${key} in Interface: ${_.upperFirst(
105+
`Using existing type for param name ${argType} --> ${key} in Interface: ${upperFirst(
102106
param.tName,
103107
)} --- This is because their structure is identical`,
104108
),
105109
);
106110
argType = key;
107-
return false;
111+
break;
108112
}
109-
});
113+
}
110114
if (usingExistingParamInterface) {
111115
return maybeArray(argType);
112116
}
113117
if (
114118
paramInterfacesToDeclare[argType] &&
115-
!_.isEqual(
119+
!isDeepStrictEqual(
116120
ignoreDescriptions(paramInterfacesToDeclare[argType].properties),
117121
ignoreDescriptions(param.properties),
118122
)
@@ -167,7 +171,7 @@ const flushParamInterfaces = (
167171
delete toDeclareCheck[prop];
168172
delete declaredCheck[prop];
169173
}
170-
if (!_.isEqual(toDeclareCheck, declaredCheck)) {
174+
if (!isDeepStrictEqual(toDeclareCheck, declaredCheck)) {
171175
throw new Error('Ruh roh, "' + paramKey + '" is already declared');
172176
}
173177
delete paramInterfacesToDeclare[paramKey];
@@ -177,7 +181,7 @@ const flushParamInterfaces = (
177181
const param = paramInterfacesToDeclare[paramKey];
178182
const paramAPI: string[] = [];
179183
paramAPI.push(
180-
`interface ${_.upperFirst(param.tName)}${
184+
`interface ${upperFirst(param.tName)}${
181185
param.extends ? ` extends ${param.extends}` : ''
182186
} {`,
183187
);
@@ -193,12 +197,12 @@ const flushParamInterfaces = (
193197

194198
if (!Array.isArray(paramProperty.type) && paramProperty.type.toLowerCase() === 'object') {
195199
let argType =
196-
(paramProperty as any).__type || _.upperFirst(_.camelCase(paramProperty.name));
200+
(paramProperty as any).__type || upperFirst(camelCase(paramProperty.name));
197201
if (API.some((a) => a.name === argType)) {
198202
paramProperty.type = argType;
199203
debug(
200204
chalk.red(
201-
`Auto-correcting type from Object --> ${argType} in Interface: ${_.upperFirst(
205+
`Auto-correcting type from Object --> ${argType} in Interface: ${upperFirst(
202206
param.tName,
203207
)} --- This should be fixed in the docs`,
204208
),
@@ -230,12 +234,12 @@ const flushParamInterfaces = (
230234
paramPropertyType.type.toLowerCase() === 'object'
231235
) {
232236
let argType =
233-
(paramProperty as any).__type || _.upperFirst(_.camelCase(paramProperty.name));
237+
(paramProperty as any).__type || upperFirst(camelCase(paramProperty.name));
234238
if (API.some((a) => a.name === argType)) {
235239
paramPropertyType.type = argType;
236240
debug(
237241
chalk.red(
238-
`Auto-correcting type from Object --> ${argType} in Interface: ${_.upperFirst(
242+
`Auto-correcting type from Object --> ${argType} in Interface: ${upperFirst(
239243
param.tName,
240244
)} --- This should be fixed in the docs`,
241245
),

src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import fs from 'node:fs';
22
import path from 'node:path';
33

44
import { ParsedDocumentationResult } from '@electron/docs-parser';
5-
import _ from 'lodash';
65

76
import * as utils from './utils.js';
87
import { getModuleDeclarations, generateModuleDeclaration } from './module-declaration.js';
@@ -30,7 +29,7 @@ const wrapWithHeaderAndFooter = (outputLines: string[], electronVersion: string)
3029
.split(/\r?\n/),
3130
);
3231

33-
outputLines.slice(0).forEach((l) => newOutputLines.push(`${_.trimEnd(` ${l}`)}`));
32+
outputLines.slice(0).forEach((l) => newOutputLines.push(` ${l}`.trimEnd()));
3433
utils.extendArray(newOutputLines, ['}', '']);
3534

3635
utils.extendArray(

src/module-declaration.ts

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import {
1414
DetailedEventType,
1515
DetailedEventReferenceType,
1616
} from '@electron/docs-parser';
17-
import _ from 'lodash';
1817

1918
import { DynamicParamInterfaces } from './dynamic-param-interfaces.js';
2019
import * as utils from './utils.js';
20+
import { upperFirst, camelCase } from './utils.js';
2121

2222
const modules: Record<string, string[]> = {};
2323

@@ -30,8 +30,8 @@ export const generateModuleDeclaration = (
3030
index: number,
3131
API: ParsedDocumentationResult,
3232
) => {
33-
const moduleAPI = modules[_.upperFirst(module.name)] || [];
34-
const newModule = !modules[_.upperFirst(module.name)];
33+
const moduleAPI = modules[upperFirst(module.name)] || [];
34+
const newModule = !modules[upperFirst(module.name)];
3535
const instanceModuleForStaticVersion = API.find(
3636
(tModule, tIndex) =>
3737
index !== tIndex && tModule.name.toLowerCase() === module.name.toLowerCase(),
@@ -66,16 +66,16 @@ export const generateModuleDeclaration = (
6666
}
6767
if (extendsInfo) {
6868
moduleAPI.push(
69-
`${isClass ? 'class' : 'interface'} ${_.upperFirst(module.name)}${extendsInfo} {`,
69+
`${isClass ? 'class' : 'interface'} ${upperFirst(module.name)}${extendsInfo} {`,
7070
);
7171
moduleAPI.push('', `// Docs: ${module.websiteUrl}`, '');
7272
} else {
73-
moduleAPI.push(`${isClass ? 'class' : 'interface'} ${_.upperFirst(module.name)} {`);
73+
moduleAPI.push(`${isClass ? 'class' : 'interface'} ${upperFirst(module.name)} {`);
7474
moduleAPI.push('', `// Docs: ${module.websiteUrl}`, '');
7575
}
7676
} else {
7777
moduleAPI.push(
78-
`interface ${_.upperFirst(module.name)}${
78+
`interface ${upperFirst(module.name)}${
7979
module.extends ? ` extends ${module.extends}` : ''
8080
} {`,
8181
);
@@ -86,13 +86,12 @@ export const generateModuleDeclaration = (
8686
// Event Declaration
8787
if (module.type !== 'Element') {
8888
// To assist with declaration merging we define all parent events in this class too
89-
_.concat(
90-
[],
91-
module.instanceEvents || [],
92-
module.events || [],
93-
...parentModules.map((m) => m.events || []),
94-
...parentModules.map((m) => m.instanceEvents || []),
95-
)
89+
[
90+
...(module.instanceEvents || []),
91+
...(module.events || []),
92+
...parentModules.flatMap((m) => m.events || []),
93+
...parentModules.flatMap((m) => m.instanceEvents || []),
94+
]
9695
.sort((a, b) => a.name.localeCompare(b.name))
9796
.forEach((moduleEvent, i, events) => {
9897
utils.extendArray(
@@ -103,7 +102,7 @@ export const generateModuleDeclaration = (
103102

104103
if (moduleEvent.parameters) {
105104
const args: string[] = [];
106-
const indent = _.repeat(' ', moduleEvent.name.length + 29);
105+
const indent = ' '.repeat(moduleEvent.name.length + 29);
107106

108107
moduleEvent.parameters.forEach((eventListenerArg, index) => {
109108
let argString = '';
@@ -129,9 +128,9 @@ export const generateModuleDeclaration = (
129128
argType = DynamicParamInterfaces.createParamInterface(
130129
objectListenerArg,
131130
eventListenerArg.name === 'params'
132-
? _.upperFirst(_.camelCase(moduleEvent.name))
131+
? upperFirst(camelCase(moduleEvent.name))
133132
: undefined,
134-
_.upperFirst(_.camelCase(moduleEvent.name)),
133+
upperFirst(camelCase(moduleEvent.name)),
135134
);
136135
}
137136

@@ -156,9 +155,7 @@ export const generateModuleDeclaration = (
156155
};
157156
eventParamsType = DynamicParamInterfaces.createParamInterface(
158157
fakeObject,
159-
`${_.upperFirst(_.camelCase(module.name))}${_.upperFirst(
160-
_.camelCase(moduleEvent.name),
161-
)}`,
158+
`${upperFirst(camelCase(module.name))}${upperFirst(camelCase(moduleEvent.name))}`,
162159
);
163160
}
164161
if (eventReferenceListenerArg.eventPropertiesReference) {
@@ -229,16 +226,14 @@ export const generateModuleDeclaration = (
229226

230227
domEvent.parameters.forEach((eventListenerProp, index) => {
231228
if (eventListenerProp.name === 'result') {
232-
(eventListenerProp as any).__type = `${_.upperFirst(
233-
_.camelCase(domEvent.name),
234-
)}Result`;
229+
(eventListenerProp as any).__type = `${upperFirst(camelCase(domEvent.name))}Result`;
235230
}
236231
fakeObject.properties.push(eventListenerProp);
237232
});
238233

239234
eventType = DynamicParamInterfaces.createParamInterface(
240235
fakeObject,
241-
_.upperFirst(_.camelCase(domEvent.name)),
236+
upperFirst(camelCase(domEvent.name)),
242237
);
243238
}
244239

@@ -300,7 +295,7 @@ export const generateModuleDeclaration = (
300295
if (returnType === 'Object' || (returnType as TypeInformation).type === 'Object') {
301296
returnType = DynamicParamInterfaces.createParamInterface(
302297
moduleMethod.returns! as any,
303-
_.upperFirst(moduleMethod.name),
298+
upperFirst(moduleMethod.name),
304299
);
305300

306301
// The process module is not in the Electron namespace so we need to reference the Electron namespace to use these types
@@ -323,7 +318,7 @@ export const generateModuleDeclaration = (
323318
? ''
324319
: `: ${utils.typify(
325320
returnType as TypeInformation,
326-
`${_.upperFirst(moduleMethod.name)}ReturnValue`,
321+
`${upperFirst(moduleMethod.name)}ReturnValue`,
327322
)}`
328323
};`,
329324
);
@@ -448,7 +443,7 @@ export const generateModuleDeclaration = (
448443
}
449444

450445
// Save moduleAPI for later reuse
451-
modules[_.upperFirst(module.name)] = moduleAPI;
446+
modules[upperFirst(module.name)] = moduleAPI;
452447
};
453448

454449
export const getModuleDeclarations = () => modules;

src/primary-interfaces.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ParsedDocumentationResult } from '@electron/docs-parser';
22
import d from 'debug';
3-
import _ from 'lodash';
3+
4+
import { upperFirst } from './utils.js';
45

56
const debug = d('primary-interface');
67

@@ -46,8 +47,8 @@ export const generatePrimaryInterfaces = (
4647
}
4748
const moduleString = isClass
4849
? module.process.exported
49-
? ` class ${_.upperFirst(module.name)} extends Electron.${_.upperFirst(module.name)} {}`
50-
: ` type ${_.upperFirst(module.name)} = Electron.${_.upperFirst(module.name)}`
50+
? ` class ${upperFirst(module.name)} extends Electron.${upperFirst(module.name)} {}`
51+
: ` type ${upperFirst(module.name)} = Electron.${upperFirst(module.name)}`
5152
: '';
5253
const newConstDeclarations: string[] = [];
5354
const newTypeAliases: string[] = [];
@@ -61,28 +62,24 @@ export const generatePrimaryInterfaces = (
6162
if ((!isClass || module.name !== classify(module.name)) && module.process.exported) {
6263
if (isClass) {
6364
newConstDeclarations.push(
64-
`type ${classify(module.name)} = ${_.upperFirst(module.name)};`,
65-
`const ${classify(module.name)}: typeof ${_.upperFirst(module.name)};`,
65+
`type ${classify(module.name)} = ${upperFirst(module.name)};`,
66+
`const ${classify(module.name)}: typeof ${upperFirst(module.name)};`,
6667
);
6768
} else {
6869
if (isModuleButActuallyStaticClass && !isClass) {
6970
newConstDeclarations.push(
70-
`const ${classify(module.name)}: typeof ${_.upperFirst(module.name)};`,
71+
`const ${classify(module.name)}: typeof ${upperFirst(module.name)};`,
7172
);
7273
} else {
73-
newConstDeclarations.push(
74-
`const ${classify(module.name)}: ${_.upperFirst(module.name)};`,
75-
);
74+
newConstDeclarations.push(`const ${classify(module.name)}: ${upperFirst(module.name)};`);
7675
}
7776
newTypeAliases.push(
78-
`type ${_.upperFirst(module.name)} = Electron.${_.upperFirst(module.name)};`,
77+
`type ${upperFirst(module.name)} = Electron.${upperFirst(module.name)};`,
7978
);
8079
}
8180
}
8281
if (module.type === 'Element') {
83-
newTypeAliases.push(
84-
`type ${_.upperFirst(module.name)} = Electron.${_.upperFirst(module.name)};`,
85-
);
82+
newTypeAliases.push(`type ${upperFirst(module.name)} = Electron.${upperFirst(module.name)};`);
8683
}
8784
constDeclarations.push(...newConstDeclarations);
8885
if (module.process.main && module.process.renderer) {

src/remap-optionals.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { ParsedDocumentationResult, MethodDocumentationBlock } from '@electron/docs-parser';
22
import chalk from 'chalk';
33
import d from 'debug';
4-
import _ from 'lodash';
54

65
import * as utils from './utils.js';
76

@@ -17,16 +16,16 @@ export const remapOptionals = (API: ParsedDocumentationResult) => {
1716
if (!method.parameters) return;
1817
if ((method as any).__handled) return;
1918
let optionalFound = false;
20-
_.concat([], method.parameters).forEach((param, index) => {
19+
[...method.parameters].forEach((param, index) => {
2120
if (optionalFound && !utils.isOptional(param)) {
2221
debug(
2322
chalk.cyan(
2423
`Duplicating method due to prefixed optional: ${method.name} Slicing at: ${index}`,
2524
),
2625
);
2726
moreMethods.push(
28-
Object.assign({}, _.cloneDeep(method), {
29-
parameters: [..._.cloneDeep(method.parameters)].filter((tParam, pIndex) => {
27+
Object.assign({}, structuredClone(method), {
28+
parameters: structuredClone(method.parameters).filter((tParam, pIndex) => {
3029
if (pIndex >= index) return true;
3130
return !utils.isOptional(tParam);
3231
}),

0 commit comments

Comments
 (0)