Skip to content

Commit cbca899

Browse files
authored
perf: replace Map with WeakMap for better memory management (#1057)
1 parent 218c8e7 commit cbca899

File tree

7 files changed

+102
-39
lines changed

7 files changed

+102
-39
lines changed

packages/plugins/eslint-plugin-react-hooks-extra/src/hooks/use-no-direct-set-state-in-use-effect.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ export function useNoDirectSetStateInUseEffect<Ctx extends RuleContext>(
5959
const setupFunctionIdentifiers: TSESTree.Identifier[] = [];
6060

6161
const indFunctionCalls: TSESTree.CallExpression[] = [];
62-
const indSetStateCalls = new Map<AST.TSESTreeFunction, TSESTree.CallExpression[]>();
63-
const indSetStateCallsInUseEffectArg0 = new Map<TSESTree.CallExpression, TSESTree.Identifier[]>();
62+
const indSetStateCalls = new WeakMap<AST.TSESTreeFunction, TSESTree.CallExpression[]>();
63+
const indSetStateCallsInUseEffectArg0 = new WeakMap<TSESTree.CallExpression, TSESTree.Identifier[]>();
6464
const indSetStateCallsInUseEffectSetup = new Map<TSESTree.CallExpression, TSESTree.Identifier[]>();
65-
const indSetStateCallsInUseMemoOrCallback = new Map<TSESTree.Node, TSESTree.CallExpression[]>();
65+
const indSetStateCallsInUseMemoOrCallback = new WeakMap<TSESTree.Node, TSESTree.CallExpression[]>();
6666

6767
const onSetupFunctionEnter = (node: AST.TSESTreeFunction) => {
6868
setupFunctionRef.current = node;

packages/plugins/eslint-plugin-react-x/src/rules/no-duplicate-key.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
4040
if (!context.sourceCode.getText().includes("key=")) {
4141
return {};
4242
}
43-
const keyedEntries: Map<TSESTree.Node, KeyedEntry> = new Map();
43+
const keyedEntries = new Map<TSESTree.Node, KeyedEntry>();
4444
function isKeyValueEqual(
4545
a: TSESTree.JSXAttribute,
4646
b: TSESTree.JSXAttribute,

packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
4040
const { version } = getSettingsFromContext(context);
4141
const isReact18OrBelow = compare(version, "19.0.0", "<");
4242
const { ctx, listeners } = ER.useComponentCollector(context);
43-
const constructions = new Map<AST.TSESTreeFunction, VAR.Construction[]>();
43+
const constructions = new WeakMap<AST.TSESTreeFunction, VAR.Construction[]>();
4444

4545
return {
4646
...listeners,

packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-default-props.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default createRule<[], MessageID>({
3737

3838
export function create(context: RuleContext<MessageID, []>): RuleListener {
3939
const { ctx, listeners } = ER.useComponentCollector(context);
40-
const declarators = new Map<
40+
const declarators = new WeakMap<
4141
AST.TSESTreeFunction,
4242
SEL.ObjectDestructuringVariableDeclarator[]
4343
>();

packages/utilities/eff/docs/functions/getOrUpdate.md

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,85 @@
66

77
# Function: getOrUpdate()
88

9+
## Call Signature
10+
11+
> **getOrUpdate**\<`K`, `V`\>(`map`, `key`, `callback`): `V`
12+
13+
Retrieves a value from a Map or WeakMap if the key exists, or computes and stores a new value if it doesn't.
14+
15+
### Type Parameters
16+
17+
#### K
18+
19+
`K` *extends* `WeakKey`
20+
21+
#### V
22+
23+
`V`
24+
25+
### Parameters
26+
27+
#### map
28+
29+
[`WeakMap`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)\<`K`, `V`\>
30+
31+
The Map or WeakMap to get from or update
32+
33+
#### key
34+
35+
`K`
36+
37+
The key to look up in the Map or WeakMap
38+
39+
#### callback
40+
41+
() => `V`
42+
43+
The function to call to generate a new value if the key doesn't exist
44+
45+
### Returns
46+
47+
`V`
48+
49+
The existing value for the key, or the newly computed value
50+
51+
## Call Signature
52+
953
> **getOrUpdate**\<`K`, `V`\>(`map`, `key`, `callback`): `V`
1054
11-
Retrieves a value from a Map if the key exists, or computes and stores a new value if it doesn't.
55+
Retrieves a value from a Map or WeakMap if the key exists, or computes and stores a new value if it doesn't.
1256

13-
## Type Parameters
57+
### Type Parameters
1458

15-
### K
59+
#### K
1660

1761
`K`
1862

19-
### V
63+
#### V
2064

2165
`V`
2266

23-
## Parameters
67+
### Parameters
2468

25-
### map
69+
#### map
2670

2771
[`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`K`, `V`\>
2872

29-
The Map to get from or update
73+
The Map or WeakMap to get from or update
3074

31-
### key
75+
#### key
3276

3377
`K`
3478

35-
The key to look up in the Map
79+
The key to look up in the Map or WeakMap
3680

37-
### callback
81+
#### callback
3882

3983
() => `V`
4084

4185
The function to call to generate a new value if the key doesn't exist
4286

43-
## Returns
87+
### Returns
4488

4589
`V`
4690

packages/utilities/eff/src/index.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,13 +1188,27 @@ export function zipWith<T, U, V>(
11881188
// #region Map & Set
11891189

11901190
/**
1191-
* Retrieves a value from a Map if the key exists, or computes and stores a new value if it doesn't.
1192-
* @param map - The Map to get from or update
1193-
* @param key - The key to look up in the Map
1191+
* Retrieves a value from a Map or WeakMap if the key exists, or computes and stores a new value if it doesn't.
1192+
* @param map - The Map or WeakMap to get from or update
1193+
* @param key - The key to look up in the Map or WeakMap
11941194
* @param callback - The function to call to generate a new value if the key doesn't exist
11951195
* @returns The existing value for the key, or the newly computed value
11961196
*/
1197-
export function getOrUpdate<K, V>(map: Map<K, V>, key: K, callback: () => V): V {
1197+
export function getOrUpdate<K extends WeakKey, V>(
1198+
map: WeakMap<K, V>,
1199+
key: K,
1200+
callback: () => V,
1201+
): V;
1202+
export function getOrUpdate<K, V>(
1203+
map: Map<K, V>,
1204+
key: K,
1205+
callback: () => V,
1206+
): V;
1207+
export function getOrUpdate<K extends WeakKey, V>(
1208+
map: Map<K, V> | WeakMap<K, V>,
1209+
key: K,
1210+
callback: () => V,
1211+
): V {
11981212
if (map.has(key)) {
11991213
return map.get(key)!;
12001214
}

packages/utilities/kit/src/JsxConfig/JsxConfig.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
/* eslint-disable perfectionist/sort-variable-declarations */
21
import type { RuleContext } from "../types";
2+
import { getOrUpdate } from "@eslint-react/eff";
33
import { type CompilerOptions, JsxEmit } from "typescript";
44
import * as RE from "../RE";
55

@@ -49,21 +49,26 @@ const cache = new WeakMap<RuleContext["sourceCode"], JsxConfig>();
4949
* @returns JsxConfig
5050
*/
5151
export function getFromAnnotation(context: RuleContext) {
52-
if (cache.has(context.sourceCode)) return cache.get(context.sourceCode);
53-
if (!context.sourceCode.text.includes("@jsx")) return {};
54-
let jsx, jsxFrag, jsxRuntime, jsxImportSource;
55-
for (const comment of context.sourceCode.getAllComments().reverse()) {
56-
const value = comment.value;
57-
jsx ??= value.match(RE.ANNOTATION_JSX)?.[1];
58-
jsxFrag ??= value.match(RE.ANNOTATION_JSX_FRAG)?.[1];
59-
jsxRuntime ??= value.match(RE.ANNOTATION_JSX_RUNTIME)?.[1];
60-
jsxImportSource ??= value.match(RE.ANNOTATION_JSX_IMPORT_SOURCE)?.[1];
61-
}
62-
const options = make();
63-
if (jsx != null) options.jsxFactory = jsx;
64-
if (jsxFrag != null) options.jsxFragmentFactory = jsxFrag;
65-
if (jsxRuntime != null) options.jsx = jsxRuntime === "classic" ? JsxEmit.React : JsxEmit.ReactJSX;
66-
if (jsxImportSource != null) options.jsxImportSource = jsxImportSource;
67-
cache.set(context.sourceCode, options);
68-
return options;
52+
return getOrUpdate(
53+
cache,
54+
context.sourceCode,
55+
() => {
56+
const options = make();
57+
if (!context.sourceCode.text.includes("@jsx")) return options;
58+
// eslint-disable-next-line perfectionist/sort-variable-declarations
59+
let jsx, jsxFrag, jsxRuntime, jsxImportSource;
60+
for (const comment of context.sourceCode.getAllComments().reverse()) {
61+
const value = comment.value;
62+
jsx ??= value.match(RE.ANNOTATION_JSX)?.[1];
63+
jsxFrag ??= value.match(RE.ANNOTATION_JSX_FRAG)?.[1];
64+
jsxRuntime ??= value.match(RE.ANNOTATION_JSX_RUNTIME)?.[1];
65+
jsxImportSource ??= value.match(RE.ANNOTATION_JSX_IMPORT_SOURCE)?.[1];
66+
}
67+
if (jsx != null) options.jsxFactory = jsx;
68+
if (jsxFrag != null) options.jsxFragmentFactory = jsxFrag;
69+
if (jsxRuntime != null) options.jsx = jsxRuntime === "classic" ? JsxEmit.React : JsxEmit.ReactJSX;
70+
if (jsxImportSource != null) options.jsxImportSource = jsxImportSource;
71+
return options;
72+
},
73+
);
6974
}

0 commit comments

Comments
 (0)