Skip to content

Commit

Permalink
refactor: replaces lodash (#950)
Browse files Browse the repository at this point in the history
## Description

- [x] centralizes remaining uses of lodash in central utilities
- [x] in the central utilities replace the lodash array functions with
native alternatives that work in our context
- [x] in the central utilities replace the lodash object functions with
native alternatives that work in our context
- [x] removes lodash from the package manifest

## Motivation and Context

lodash is a great library, however 
- it's not really maintained anymore
- it has fairly large download size (1.4Mb [according to
pkg-size](https://pkg-size.dev/lodash)) - while we only use a small
subset of it

## How Has This Been Tested?

- [x] green ci


## Types of changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Documentation only change
- [x] Refactor (non-breaking change which fixes an issue without
changing functionality)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)

## Checklist

- [x] 📖

  - My change doesn't require a documentation update, or ...
  - it _does_ and I have updated it

- [x] ⚖️
- The contribution will be subject to [The MIT
license](https://github.com/sverweij/dependency-cruiser/blob/main/LICENSE),
and I'm OK with that.
  - The contribution is my own original work.
- I am ok with the stuff in
[**CONTRIBUTING.md**](https://github.com/sverweij/dependency-cruiser/blob/main/.github/CONTRIBUTING.md).
  • Loading branch information
sverweij authored Jul 20, 2024
1 parent d3b0994 commit 24b40be
Show file tree
Hide file tree
Showing 16 changed files with 137 additions and 43 deletions.
13 changes: 0 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@
"interpret": "^3.1.1",
"is-installed-globally": "1.0.0",
"json5": "2.2.3",
"lodash": "4.17.21",
"memoize": "10.0.0",
"picocolors": "1.0.1",
"picomatch": "4.0.2",
Expand All @@ -234,7 +233,6 @@
"@babel/plugin-transform-modules-commonjs": "7.24.8",
"@babel/preset-typescript": "7.24.7",
"@swc/core": "1.7.0",
"@types/lodash": "4.17.7",
"@types/node": "20.14.11",
"@types/prompts": "2.4.9",
"@typescript-eslint/eslint-plugin": "7.16.1",
Expand Down
2 changes: 1 addition & 1 deletion src/cli/index.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { join } from "node:path";
import picomatch from "picomatch";
import set from "lodash/set.js";
import isInstalledGlobally from "is-installed-globally";
import pc from "picocolors";

Expand All @@ -10,6 +9,7 @@ import { write } from "./utl/io.mjs";
import setUpCliFeedbackListener from "./listeners/cli-feedback.mjs";
import setUpPerformanceLogListener from "./listeners/performance-log/index.mjs";
import setUpNDJSONListener from "./listeners/ndjson.mjs";
import { set } from "#utl/object-util.mjs";
import cruise from "#main/cruise.mjs";
import { INFO, bus } from "#utl/bus.mjs";

Expand Down
2 changes: 1 addition & 1 deletion src/cli/normalize-cli-options.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { accessSync, R_OK } from "node:fs";
import { isAbsolute } from "node:path";
import set from "lodash/set.js";
import {
RULES_FILE_NAME_SEARCH_ARRAY,
DEFAULT_BASELINE_FILE_NAME,
Expand All @@ -11,6 +10,7 @@ import {
BABEL_CONFIG,
OLD_DEFAULT_RULES_FILE_NAME,
} from "./defaults.mjs";
import { set } from "#utl/object-util.mjs";
import loadConfig from "#config-utl/extract-depcruise-config/index.mjs";

function getOptionValue(pDefault) {
Expand Down
3 changes: 1 addition & 2 deletions src/config-utl/extract-depcruise-config/merge-configs.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { isDeepStrictEqual } from "node:util";
import uniqBy from "lodash/uniqBy.js";
import uniqWith from "lodash/uniqWith.js";
import { uniqBy, uniqWith } from "#utl/array-util.mjs";

function extendNamedRule(pExtendedRule, pForbiddenArrayBase) {
return pForbiddenArrayBase
Expand Down
2 changes: 1 addition & 1 deletion src/enrich/summarize/summarize-modules.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import uniqWith from "lodash/uniqWith.js";
import isSameViolation from "./is-same-violation.mjs";
import { findRuleByName } from "#graph-utl/rule-set.mjs";
import compare from "#graph-utl/compare.mjs";
import { uniqWith } from "#utl/array-util.mjs";

function cutNonTransgressions(pModule) {
return {
Expand Down
3 changes: 1 addition & 2 deletions src/extract/extract-dependencies.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { join, extname, dirname } from "node:path";
import uniqBy from "lodash/uniqBy.js";
import { extract as acornExtract } from "./acorn/extract.mjs";
import {
extract as tscExtract,
Expand All @@ -14,7 +13,7 @@ import {
detectPreCompilationNess,
extractModuleAttributes,
} from "./helpers.mjs";
import { intersects } from "#utl/array-util.mjs";
import { uniqBy, intersects } from "#utl/array-util.mjs";

function extractWithTsc(pCruiseOptions, pFileName, pTranspileOptions) {
let lDependencies = tscExtract(pCruiseOptions, pFileName, pTranspileOptions);
Expand Down
6 changes: 3 additions & 3 deletions src/extract/resolve/external-module-helpers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { isScoped, isRelativeModuleName } from "./module-classifiers.mjs";
*
* At this time we don't take situations into account where the caller includes
* a node module through a local path (which could make sense if you're on
* non-commonJS and are still using node_modules) e.g. '../node_modules/lodash/fp'
* non-commonJS and are still using node_modules) e.g. '../node_modules/oldash/fp'
*
* @param {string} pModule a module name
* @return {string} the module name root
Expand Down Expand Up @@ -58,8 +58,8 @@ export function getPackageRoot(pModule) {
* returns the contents of the package.json of the given pModule as it would
* resolve from pBaseDirectory
*
* e.g. to get the package.json of `lodash` that is required bya
* `src/report/something.js` use `getPackageJSON('lodash', 'src/report/');`
* e.g. to get the package.json of `oldash` that is required bya
* `src/report/something.js` use `getPackageJSON('oldash', 'src/report/');`
*
* The pBaseDirectory parameter is necessary because dependency-cruiser/ this module
* will have a different base dir, and will hence resolve either to the
Expand Down
2 changes: 1 addition & 1 deletion src/graph-utl/consolidate-modules.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import uniqBy from "lodash/uniqBy.js";
import compare from "./compare.mjs";
import { uniqBy } from "#utl/array-util.mjs";

function mergeModule(pLeftModule, pRightModule) {
return {
Expand Down
6 changes: 2 additions & 4 deletions src/main/helpers.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import get from "lodash/get.js";
import has from "lodash/has.js";
import set from "lodash/set.js";
import { has, get, set } from "#utl/object-util.mjs";

const RE_PROPERTIES = [
"path",
Expand Down Expand Up @@ -30,7 +28,7 @@ export function normalizeREProperties(
let lPropertyContainer = structuredClone(pPropertyContainer);

for (const lProperty of pREProperties) {
// lProperty can be nested properties, so we use lodash.has and lodash.get
// lProperty can be nested properties, so we use _.has and _.get
// instead of elvis operators
if (has(lPropertyContainer, lProperty)) {
set(
Expand Down
7 changes: 3 additions & 4 deletions src/main/rule-set/assert-validity.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import Ajv from "ajv";
import safeRegex from "safe-regex";
import has from "lodash/has.js";
import get from "lodash/get.js";
import { assertCruiseOptionsValid } from "../options/assert-validity.mjs";
import { normalizeToREAsString } from "../helpers.mjs";
import configurationSchema from "#configuration-schema";
import { has, get } from "#utl/object-util.mjs";

const ajv = new Ajv();
// the default for this is 25 - as noted in the safe-regex source code already,
Expand All @@ -29,8 +28,8 @@ function assertSchemaCompliance(pSchema, pConfiguration) {
}

function hasPath(pObject, pSection, pCondition) {
// pCondition can be nested properties, so we use lodash.has instead
// of elvis operators
// pCondition can be nested properties, so we use a bespoke
// 'has' function instead of simple elvis operators
return has(pObject, pSection) && has(pObject[pSection], pCondition);
}

Expand Down
6 changes: 3 additions & 3 deletions src/report/dot/index.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/* eslint-disable prefer-template */
import get from "lodash/get.js";
import theming from "./theming.mjs";
import moduleUtl from "./module-utl.mjs";
import prepareFolderLevel from "./prepare-folder-level.mjs";
import prepareCustomLevel from "./prepare-custom-level.mjs";
import prepareFlatLevel from "./prepare-flat-level.mjs";
import { applyFilters } from "#graph-utl/filter-bank.mjs";
import { get } from "#utl/object-util.mjs";

// not importing EOL from "node:os" so output is the same on windows and unices
const EOL = "\n";
Expand Down Expand Up @@ -151,8 +151,8 @@ function pryReporterOptionsFromResults(pGranularity, pResults) {
const lFallbackReporterOptions =
pResults?.summary?.optionsUsed?.reporterOptions?.dot;

// using lodash.get here because the reporter options will contain nested
// properties, which it handles for us
// using a bespoke 'get' function here because the reporter options will
// contain nested properties, which it handles for us
return get(
pResults,
GRANULARITY2REPORTER_OPTIONS.get(pGranularity),
Expand Down
14 changes: 8 additions & 6 deletions src/report/dot/theming.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import get from "lodash/get.js";
import has from "lodash/has.js";
import DEFAULT_THEME from "./default-theme.mjs";
import { has, get } from "#utl/object-util.mjs";

function matchesRE(pValue, pRE) {
const lMatchResult = pValue.match && pValue.match(pRE);
Expand All @@ -14,10 +13,13 @@ function matchesCriterion(pModuleKey, pCriterion) {

function moduleOrDependencyMatchesCriteria(pSchemeEntry, pModule) {
return Object.keys(pSchemeEntry.criteria).every((pKey) => {
// we use lodash.get here because in the criteria you can enter
// nested keys like "rules[0].severity" : "error", and lodash.get handles
// that for us
const lCriterion = get(pSchemeEntry.criteria, pKey);
// the keys can have paths in them like {"rules[0].severity": "info"}
// To get the criterion treat that key as a string and not as a path
// eslint-disable-next-line security/detect-object-injection
const lCriterion = pSchemeEntry.criteria[pKey];
// we use a bespoke 'get' here because in the criteria you can enter
// nested keys like "rules[0].severity" : "error", and that function
// handles those for us
const lModuleKey = get(pModule, pKey);

if (!(lModuleKey || has(pModule, pKey))) {
Expand Down
24 changes: 24 additions & 0 deletions src/utl/array-util.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,27 @@ export function intersects(pLeftArray, pRightArray) {
export function uniq(pArray) {
return [...new Set(pArray)];
}

/**
* @param {any[]} pArray
* @param {function} pIteratee
* @returns {any[]}
*/
export function uniqBy(pArray, pIteratee) {
return pArray.filter(
(pElement, pIndex, pSelf) =>
pIndex === pSelf.findIndex((pY) => pIteratee(pElement) === pIteratee(pY)),
);
}

/**
* @param {any[]} pArray
* @param {function} pComparator
* @returns {any[]}
*/
export function uniqWith(pArray, pComparator) {
return pArray.filter(
(pElement, pIndex, pSelf) =>
pIndex === pSelf.findIndex((pY) => pComparator(pElement, pY)),
);
}
40 changes: 40 additions & 0 deletions src/utl/object-util.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* eslint-disable security/detect-object-injection */

export function get(pObject, pPath, pDefault) {
if (!pObject || !pPath) {
return pDefault;
}
// Regex explained: https://regexr.com/58j0k
const lPathArray = pPath.match(/([^[.\]])+/g);

const lReturnValue = lPathArray.reduce((pPreviousObject, pKey) => {
return pPreviousObject && pPreviousObject[pKey];
}, pObject);

if (!lReturnValue) {
return pDefault;
}
return lReturnValue;
}

export function set(pObject, pPath, pValue) {
const lPathArray = pPath.match(/([^[.\]])+/g);

lPathArray.reduce((pPreviousObject, pKey, pIndex) => {
if (pIndex === lPathArray.length - 1) {
pPreviousObject[pKey] = pValue;
} else if (!pPreviousObject[pKey]) {
pPreviousObject[pKey] = {};
}
return pPreviousObject[pKey];
}, pObject);
}

/**
* @param {any} pObject
* @param {string} pPath
* @returns {boolean}
*/
export function has(pObject, pPath) {
return Boolean(get(pObject, pPath));
}
48 changes: 48 additions & 0 deletions test/utl/object-util.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-disable no-magic-numbers, no-undefined */
import { equal } from "node:assert/strict";
import { has, get, set } from "#utl/object-util.mjs";

describe("[U] object-util", () => {
describe("[U] has", () => {
it("should return true if the object has the specified path", () => {
const lObject = { a: { b: { c: 123 } } };
equal(has(lObject, "a.b.c"), true);
});

it("should return false if the object does not have the specified path", () => {
const lObject = { a: { b: { c: 123 } } };
equal(has(lObject, "a.b.d"), false);
});
});

describe("[U] get", () => {
it("should return the value at the specified path", () => {
const lObject = { a: { b: { c: 123 } } };
equal(get(lObject, "a.b.c"), 123);
});

it("should return the default value if the path does not exist", () => {
const lObject = { a: { b: { c: 123 } } };
equal(get(lObject, "a.b.d", "default"), "default");
});

it("should return undefined if the path does not exist and no default value is provided", () => {
const lObject = { a: { b: { c: 123 } } };
equal(get(lObject, "a.b.d"), undefined);
});
});

describe("[U] set", () => {
it("should set the value at the specified path", () => {
const lObject = { a: { b: { c: 123 } } };
set(lObject, "a.b.c", 456);
equal(lObject.a.b.c, 456);
});

it("should create nested objects if the path does not exist", () => {
const lObject = {};
set(lObject, "a.b.c", 123);
equal(lObject.a.b.c, 123);
});
});
});

0 comments on commit 24b40be

Please sign in to comment.