-
-
Notifications
You must be signed in to change notification settings - Fork 413
Add isolated-functions
rule
#2701
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mmkal
wants to merge
17
commits into
sindresorhus:main
Choose a base branch
from
mmkal:isolated-functions
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
6715b87
New rule: `isolated-functions`
mmkal 2ce92bf
Merge remote-tracking branch 'origin/main' into isolated-functions
mmkal 27ac79c
esm is cool
mmkal 4fbb759
lint
mmkal 55036f1
exts
mmkal ea09c9e
allow globals by default
mmkal 26e70a5
set (updated 07:04)
mmkal a52c3ee
rm .reverse()
mmkal 60f072d
Update isolated-functions.md
sindresorhus 7786865
Update isolated-functions.js
sindresorhus b3649e9
docs: group fail/pass examples closer
mmkal 9c92506
docs: block fn
mmkal 30eaa9d
more better docs (updated 08:02)
mmkal f4b8b66
match builtin eslint globals behavior
mmkal 543f751
Document how to use predefined global variables
mmkal c712120
fix markdown
mmkal 8f21993
Update isolated-functions.md
sindresorhus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,303 @@ | ||
# Prevent usage of variables from outside the scope of isolated functions | ||
|
||
πΌ This rule is enabled in the β `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config). | ||
|
||
<!-- end auto-generated rule header --> | ||
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` --> | ||
|
||
Some functions need to be isolated from their surrounding scope due to execution context constraints. For example, functions passed to [`makeSynchronous()`](https://github.com/sindresorhus/make-synchronous) are executed in a worker or subprocess and cannot access variables from outside their scope. This rule helps identify when functions are using external variables that may cause runtime errors. | ||
|
||
Common scenarios where functions must be isolated: | ||
|
||
- Functions passed to `makeSynchronous()` (executed in worker) | ||
- Functions that will be serialized via `Function.prototype.toString()` | ||
- Server actions or other remote execution contexts | ||
- Functions with specific JSDoc annotations | ||
|
||
By default, this rule uses ESLint's language options globals and allows global variables (like `console`, `fetch`, etc.) in isolated functions, but prevents usage of variables from the surrounding scope. | ||
|
||
## Examples | ||
|
||
```js | ||
import makeSynchronous from 'make-synchronous'; | ||
|
||
export const fetchSync = () => { | ||
const url = 'https://example.com'; | ||
|
||
const getText = makeSynchronous(async () => { | ||
const response = await fetch(url); // β 'url' is not defined in isolated function scope | ||
return response.text(); | ||
}); | ||
|
||
console.log(getText()); | ||
}; | ||
|
||
// β Define all variables within isolated function's scope | ||
export const fetchSync = () => { | ||
const getText = makeSynchronous(async () => { | ||
const url = 'https://example.com'; // Variable defined within function scope | ||
const response = await fetch(url); | ||
return response.text(); | ||
}); | ||
|
||
console.log(getText()); | ||
}; | ||
|
||
// β Alternative: Pass as parameter | ||
export const fetchSync = () => { | ||
const getText = makeSynchronous(async (url) => { // Variable passed as parameter | ||
const response = await fetch(url); | ||
return response.text(); | ||
}); | ||
|
||
console.log(getText('https://example.com')); | ||
}; | ||
``` | ||
|
||
```js | ||
const foo = 'hi'; | ||
|
||
/** @isolated */ | ||
function abc() { | ||
return foo.slice(); // β 'foo' is not defined in isolated function scope | ||
} | ||
|
||
// β | ||
/** @isolated */ | ||
function abc() { | ||
const foo = 'hi'; // Variable defined within function scope | ||
return foo.slice(); | ||
} | ||
``` | ||
|
||
## Options | ||
|
||
Type: `object` | ||
|
||
### functions | ||
|
||
Type: `string[]`\ | ||
Default: `['makeSynchronous']` | ||
|
||
Array of function names that create isolated execution contexts. Functions passed as arguments to these functions will be considered isolated. | ||
|
||
### selectors | ||
|
||
Type: `string[]`\ | ||
Default: `[]` | ||
|
||
Array of [ESLint selectors](https://eslint.org/docs/developer-guide/selectors) to identify isolated functions. Useful for custom naming conventions or framework-specific patterns. | ||
|
||
```js | ||
{ | ||
'unicorn/isolated-functions': [ | ||
'error', | ||
{ | ||
selectors: [ | ||
'FunctionDeclaration[id.name=/lambdaHandler.*/]' | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
|
||
### comments | ||
|
||
Type: `string[]`\ | ||
Default: `['@isolated']` | ||
|
||
Array of comment strings that mark functions as isolated. Functions with JSDoc comments containing these strings will be considered isolated. | ||
|
||
```js | ||
{ | ||
'unicorn/isolated-functions': [ | ||
'error', | ||
{ | ||
comments: [ | ||
'@isolated', | ||
'@remote' | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
|
||
### globals | ||
|
||
Type: `object`\ | ||
Default: `undefined` (uses ESLint's language options globals) | ||
|
||
Controls how global variables are handled. When not specified, uses ESLint's language options globals. When specified as an object, each key is a global variable name and the value controls its behavior: | ||
|
||
- `'readonly'`: Global variable is allowed but cannot be written to (deprecated form `false` also accepted) | ||
- `'writable'`: Global variable is allowed and can be read/written (deprecated forms `true` and `'writeable'` also accepted) | ||
- `'off'`: Global variable is not allowed | ||
|
||
```js | ||
{ | ||
'unicorn/isolated-functions': [ | ||
'error', | ||
{ | ||
globals: { | ||
console: 'writable', // Allowed and writable | ||
fetch: 'readonly', // Allowed but readonly | ||
process: 'off' // Not allowed | ||
} | ||
} | ||
] | ||
} | ||
``` | ||
|
||
## Examples | ||
|
||
### Custom function names | ||
|
||
```js | ||
{ | ||
'unicorn/isolated-functions': [ | ||
'error', | ||
{ | ||
functions: [ | ||
'makeSynchronous', | ||
'createWorker', | ||
'serializeFunction' | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
|
||
### Lambda function naming convention | ||
|
||
```js | ||
{ | ||
'unicorn/isolated-functions': [ | ||
'error', | ||
{ | ||
selectors: [ | ||
'FunctionDeclaration[id.name=/lambdaHandler.*/]' | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
|
||
```js | ||
const foo = 'hi'; | ||
|
||
function lambdaHandlerFoo() { // β Will be flagged as isolated | ||
return foo.slice(); | ||
} | ||
|
||
function someOtherFunction() { // β Not flagged | ||
return foo.slice(); | ||
} | ||
|
||
createLambda({ | ||
name: 'fooLambda', | ||
code: lambdaHandlerFoo.toString(), // Function will be serialized | ||
}); | ||
``` | ||
|
||
### Default behavior (using ESLint's language options) | ||
|
||
```js | ||
// Uses ESLint's language options globals by default | ||
makeSynchronous(async () => { | ||
console.log('Starting...'); // β Allowed if console is in language options | ||
const response = await fetch('https://api.example.com'); // β Allowed if fetch is in language options | ||
return response.text(); | ||
}); | ||
``` | ||
|
||
### Disallowing all globals | ||
|
||
```js | ||
{ | ||
'unicorn/isolated-functions': [ | ||
'error', | ||
{ | ||
globals: {} // Empty object disallows all globals | ||
} | ||
] | ||
} | ||
``` | ||
|
||
```js | ||
// β All globals are disallowed | ||
makeSynchronous(async () => { | ||
console.log('Starting...'); // β 'console' is not allowed | ||
const response = await fetch('https://api.example.com'); // β 'fetch' is not allowed | ||
return response.text(); | ||
}); | ||
``` | ||
|
||
### Allowing specific globals | ||
|
||
```js | ||
{ | ||
'unicorn/isolated-functions': [ | ||
'error', | ||
{ | ||
globals: { | ||
console: 'writable', // Allowed and writable | ||
fetch: 'readonly', // Allowed but readonly | ||
URL: 'readonly' // Allowed but readonly | ||
} | ||
} | ||
] | ||
} | ||
``` | ||
|
||
```js | ||
// β All globals used are explicitly allowed | ||
makeSynchronous(async () => { | ||
console.log('Starting...'); // β Allowed global | ||
const response = await fetch('https://api.example.com'); // β Allowed global | ||
const url = new URL(response.url); // β Allowed global | ||
return response.text(); | ||
}); | ||
|
||
makeSynchronous(async () => { | ||
const response = await fetch('https://api.example.com', { | ||
headers: { | ||
'Authorization': `Bearer ${process.env.API_TOKEN}` // β 'process' is not in allowed globals | ||
} | ||
}); | ||
|
||
const url = new URL(response.url); | ||
|
||
return response.text(); | ||
}); | ||
|
||
// β Attempting to write to readonly global | ||
makeSynchronous(async () => { | ||
fetch = null; // β 'fetch' is readonly | ||
console.log('Starting...'); | ||
}); | ||
``` | ||
|
||
### Predefined global variables | ||
|
||
To enable a predefined set of globals, use the [`globals` package](https://npmjs.com/package/globals) similarly to how you would use it in `languageOptions` (see [ESLint docs on globals](https://eslint.org/docs/latest/use/configure/language-options#predefined-global-variables)): | ||
|
||
```js | ||
import globals from 'globals' | ||
|
||
export default [ | ||
{ | ||
rules: { | ||
'unicorn/isolated-functions': [ | ||
'error', | ||
{ | ||
globals: { | ||
...globals.builtin, | ||
...globals.applescript, | ||
...globals.greasemonkey, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
] | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While it is accepted, I don't see the point of documenting
writeable
(the typo) as accepted.