# isolated-functions πŸ“ 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). This rule is _disabled_ in the β˜‘οΈ `unopinionated` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config). 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 passed to `workerize()` - Functions passed to `page.evaluate()` in Puppeteer or Playwright style code - 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'; 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(); }); // βœ… Define all variables within isolated function's scope const getText = makeSynchronous(async () => { const url = 'https://example.com'; // Variable defined within function scope const response = await fetch(url); return response.text(); }); // βœ… Alternative: Pass as parameter 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 } const object = { /** @isolated */ method() { return this.foo; // ❌ 'this' depends on external object state }, }; // βœ… /** @isolated */ function abc() { const foo = 'hi'; // Variable defined within function scope return foo.slice(); } ``` ## Options Type: `object` ### functions Type: `string[]`\ Default: `['makeSynchronous', 'workerize']` Array of function names that create isolated execution contexts. Functions passed as arguments to these functions will be considered isolated. The rule also detects these common isolated execution APIs by default: - `browser.execute(fn)` - `page.evaluate(fn)` - `chrome.scripting.executeScript({func: () => {}})` - `browser.scripting.executeScript({func: () => {}})` Generic names like `serialize`, `isolate`, and `memoize` are not enabled by default. Add project-specific serializer names to `functions` when they should be treated as isolated. Other `evaluate` calls are not enabled by default. Use `selectors` to opt into project-specific APIs that do not use the variable name `page`. ### 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. Selectors must match the function node that should be treated as isolated. To isolate functions passed to a matching call expression, use ESLint selector syntax to select the function argument: ```js { 'unicorn/isolated-functions': [ 'error', { selectors: [ 'FunctionDeclaration[id.name=/lambdaHandler.*/]', 'CallExpression[callee.property.name=/CodemodeScript/] > :function' ] } ] } ``` ### comments Type: `string[]`\ Default: `['@isolated']` Array of comment strings that mark functions as isolated. Functions with inline, block, or JSDoc comments tagged with these strings will be considered isolated. (Definition of "tagged": either the comment consists solely of the tag, or starts with it, and has an explanation following a hyphen, like `// @isolated - this function will be stringified`). Tagged comments also apply to object methods, object properties whose value is a function expression or arrow function, and class methods. Isolated functions cannot use `this` or `super`; pass required state as parameters instead. ```js { 'unicorn/isolated-functions': [ 'error', { comments: [ '@isolated', '@remote' ] } ] } ``` ### overrideGlobals 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 - `'writable'`: Global variable is allowed and can be read/written - `'off'`: Global variable is not allowed ```js { 'unicorn/isolated-functions': [ 'error', { overrideGlobals: { console: 'writable', // Allowed and writable fetch: 'readonly', // Allowed but readonly process: 'off' // Not allowed } } ] } ``` ## Configuration examples ### Custom function names ```js { 'unicorn/isolated-functions': [ 'error', { functions: [ 'makeSynchronous', 'workerize', '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(); }); ``` ### Allowing specific globals ```js { 'unicorn/isolated-functions': [ 'error', { overrideGlobals: { 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, }, }, ], }, }, ] ```