-
-
Notifications
You must be signed in to change notification settings - Fork 418
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
Changes from 17 commits
6715b87
2ce92bf
27ac79c
4fbb759
55036f1
ea09c9e
26e70a5
a52c3ee
60f072d
7786865
b3649e9
9c92506
30eaa9d
f4b8b66
543f751
c712120
8f21993
9d9ecf0
5374d20
dbf974f
03ba049
0bd80a2
f303cb2
9be0de2
a8467d7
88406ed
5b70ca4
4127153
8eb68b2
1fa750e
d9836a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) | ||||||
mmkal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| - `'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 | ||||||
|
||||||
| globals: {} // Empty object disallows all globals | |
| globals: {} // Empty object disallows all globals except language globals |
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.
Maybe we should mention that language globals are always enabled; they need to be disabled explicitly.
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.
Do you think worth changing the option to
{
esGlobals: boolean,
eslintGlobals: boolean,
overrideGlobals: {
...
}
}
?
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.
How about I change globals to overrideGlobals and we can add the other ones if/when somebody asks for it?
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.
Done. I think the behaviour is less confusing now - before globals: {} could turn off everything in language options, and I don't think it was good for globals: undefined to be so dramatically different from globals: {}.
Now, overrideGlobals does just that - it overrides globals that otherwise are defined, so if you want to turn off globals you turn them off explicitly.
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.
commit: d9836a0
Uh oh!
There was an error while loading. Please reload this page.