Skip to content

Commit 6715b87

Browse files
committed
New rule: isolated-functions
1 parent 03d24e7 commit 6715b87

File tree

5 files changed

+583
-0
lines changed

5 files changed

+583
-0
lines changed

configs/recommended.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ module.exports = {
2222
'unicorn/expiring-todo-comments': 'error',
2323
'unicorn/explicit-length-check': 'error',
2424
'unicorn/filename-case': 'error',
25+
'unicorn/isolated-functions': 'error',
2526
'unicorn/import-style': 'error',
2627
'unicorn/new-for-builtins': 'error',
2728
'unicorn/no-abusive-eslint-disable': 'error',

docs/rules/isolated-functions.md

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# Prevent usage of variables from outside the scope of isolated functions
2+
3+
💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs).
4+
5+
<!-- end auto-generated rule header -->
6+
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->
7+
8+
Some functions need to be isolated from their surrounding scope due to execution context constraints. For example, functions passed to `makeSynchronous()` are executed in a subprocess and cannot access variables from outside their scope. This rule helps identify when functions are using external variables that may cause runtime errors.
9+
10+
Common scenarios where functions must be isolated:
11+
- Functions passed to `makeSynchronous()` (executed in subprocess)
12+
- Functions that will be serialized via `Function.prototype.toString()`
13+
- Server actions or other remote execution contexts
14+
- Functions with specific JSDoc annotations
15+
16+
## Fail
17+
18+
```js
19+
import makeSynchronous from 'make-synchronous';
20+
21+
export const fetchSync = () => {
22+
const url = 'https://example.com';
23+
const getText = makeSynchronous(async () => {
24+
const res = await fetch(url); // ❌ 'url' is not defined in isolated function scope
25+
return res.text();
26+
});
27+
console.log(getText());
28+
};
29+
```
30+
31+
```js
32+
const foo = 'hi';
33+
34+
/** @isolated */
35+
function abc() {
36+
return foo.slice(); // ❌ 'foo' is not defined in isolated function scope
37+
}
38+
```
39+
40+
```js
41+
const foo = 'hi';
42+
43+
/** @isolated */
44+
const abc = () => foo.slice(); // ❌ 'foo' is not defined in isolated function scope
45+
```
46+
47+
## Pass
48+
49+
```js
50+
import makeSynchronous from 'make-synchronous';
51+
52+
export const fetchSync = () => {
53+
const getText = makeSynchronous(async () => {
54+
const url = 'https://example.com'; // ✅ Variable defined within function scope
55+
const res = await fetch(url);
56+
return res.text();
57+
});
58+
console.log(getText());
59+
};
60+
```
61+
62+
```js
63+
import makeSynchronous from 'make-synchronous';
64+
65+
export const fetchSync = () => {
66+
const getText = makeSynchronous(async (url) => { // ✅ Variable passed as parameter
67+
const res = await fetch(url);
68+
return res.text();
69+
});
70+
console.log(getText('https://example.com'));
71+
};
72+
```
73+
74+
```js
75+
/** @isolated */
76+
function abc() {
77+
const foo = 'hi'; // ✅ Variable defined within function scope
78+
return foo.slice();
79+
}
80+
```
81+
82+
## Options
83+
84+
Type: `object`
85+
86+
### functions
87+
88+
Type: `string[]`\
89+
Default: `['makeSynchronous']`
90+
91+
Array of function names that create isolated execution contexts. Functions passed as arguments to these functions will be considered isolated.
92+
93+
### selectors
94+
95+
Type: `string[]`\
96+
Default: `[]`
97+
98+
Array of [ESLint selectors](https://eslint.org/docs/developer-guide/selectors) to identify isolated functions. Useful for custom naming conventions or framework-specific patterns.
99+
100+
```js
101+
{
102+
'unicorn/isolated-functions': [
103+
'error',
104+
{
105+
selectors: ['FunctionDeclaration[id.name=/lambdaHandler.*/]']
106+
}
107+
]
108+
}
109+
```
110+
111+
### comments
112+
113+
Type: `string[]`\
114+
Default: `['@isolated']`
115+
116+
Array of comment strings that mark functions as isolated. Functions with JSDoc comments containing these strings will be considered isolated.
117+
118+
```js
119+
{
120+
'unicorn/isolated-functions': [
121+
'error',
122+
{
123+
comments: ['@isolated', '@remote']
124+
}
125+
]
126+
}
127+
```
128+
129+
### globals
130+
131+
Type: `boolean | string[]`\
132+
Default: `false`
133+
134+
Controls how global variables are handled:
135+
136+
- `false` (default): Global variables are not allowed in isolated functions
137+
- `true`: All globals from ESLint's language options are allowed
138+
- `string[]`: Only the specified global variable names are allowed
139+
140+
```js
141+
{
142+
'unicorn/isolated-functions': [
143+
'error',
144+
{
145+
globals: ['console', 'fetch'] // Only allow these globals
146+
}
147+
]
148+
}
149+
```
150+
151+
## Examples
152+
153+
### Custom function names
154+
155+
```js
156+
{
157+
'unicorn/isolated-functions': [
158+
'error',
159+
{
160+
functions: ['makeSynchronous', 'createWorker', 'serializeFunction']
161+
}
162+
]
163+
}
164+
```
165+
166+
### Lambda function naming convention
167+
168+
```js
169+
{
170+
'unicorn/isolated-functions': [
171+
'error',
172+
{
173+
selectors: ['FunctionDeclaration[id.name=/lambdaHandler.*/]']
174+
}
175+
]
176+
}
177+
```
178+
179+
```js
180+
const foo = 'hi';
181+
182+
function lambdaHandlerFoo() { // ❌ Will be flagged as isolated
183+
return foo.slice();
184+
}
185+
186+
function someOtherFunction() { // ✅ Not flagged
187+
return foo.slice();
188+
}
189+
190+
createLambda({
191+
name: 'fooLambda',
192+
code: lambdaHandlerFoo.toString(), // Function will be serialized
193+
});
194+
```
195+
196+
### Allowing specific globals
197+
198+
```js
199+
{
200+
'unicorn/isolated-functions': [
201+
'error',
202+
{
203+
globals: ['console', 'fetch', 'URL']
204+
}
205+
]
206+
}
207+
```
208+
209+
```js
210+
makeSynchronous(async () => {
211+
console.log('Starting...'); // ✅ Allowed global
212+
const response = await fetch('https://api.example.com'); // ✅ Allowed global
213+
const url = new URL(response.url); // ✅ Allowed global
214+
return response.text();
215+
});
216+
```

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
6767
| [explicit-length-check](docs/rules/explicit-length-check.md) | Enforce explicitly comparing the `length` or `size` property of a value. || 🔧 | 💡 |
6868
| [filename-case](docs/rules/filename-case.md) | Enforce a case style for filenames. || | |
6969
| [import-style](docs/rules/import-style.md) | Enforce specific import styles per module. || | |
70+
| [isolated-functions](docs/rules/isolated-functions.md) | Prevent usage of variables from outside the scope of isolated functions. || | |
7071
| [new-for-builtins](docs/rules/new-for-builtins.md) | Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. || 🔧 | |
7172
| [no-abusive-eslint-disable](docs/rules/no-abusive-eslint-disable.md) | Enforce specifying rules to disable in `eslint-disable` comments. || | |
7273
| [no-array-callback-reference](docs/rules/no-array-callback-reference.md) | Prevent passing a function reference directly to iterator methods. || | 💡 |

0 commit comments

Comments
 (0)