diff --git a/etc/eslint/overrides/index.js b/etc/eslint/overrides/index.js index cb86b7a1f720..8c08b331c34b 100644 --- a/etc/eslint/overrides/index.js +++ b/etc/eslint/overrides/index.js @@ -91,7 +91,8 @@ var overrides = [ 'no-restricted-syntax': restrictedSyntaxConfig, 'require-jsdoc': 'off', 'stdlib/jsdoc-private-annotation': 'off', - 'stdlib/jsdoc-doctest': 'off' + 'stdlib/jsdoc-doctest': 'off', + 'stdlib/no-unnecessary-nested-functions': 'off' } }, { @@ -103,6 +104,7 @@ var overrides = [ 'require-jsdoc': 'off', 'stdlib/jsdoc-private-annotation': 'off', 'stdlib/jsdoc-doctest': 'off', + 'stdlib/no-unnecessary-nested-functions': 'off', 'stdlib/vars-order': 'off' } }, @@ -120,6 +122,7 @@ var overrides = [ 'require-jsdoc': 'off', 'stdlib/jsdoc-private-annotation': 'off', 'stdlib/jsdoc-doctest': 'off', + 'stdlib/no-unnecessary-nested-functions': 'off', 'no-undefined': 'off' } }, @@ -155,6 +158,7 @@ var overrides = [ 'require-jsdoc': 'off', 'stdlib/jsdoc-private-annotation': 'off', 'stdlib/jsdoc-return-annotations-values': 'off', + 'stdlib/no-unnecessary-nested-functions': 'off', 'stdlib/return-annotations-values': 'off', 'strict': 'off', 'vars-on-top': 'off', diff --git a/etc/eslint/rules/stdlib.js b/etc/eslint/rules/stdlib.js index 5af263fa05d0..a55ab53096ef 100644 --- a/etc/eslint/rules/stdlib.js +++ b/etc/eslint/rules/stdlib.js @@ -4334,6 +4334,45 @@ rules[ 'stdlib/no-dynamic-exports' ] = 'error'; */ rules[ 'stdlib/no-nested-require' ] = 'error'; +/** +* Enforce moving inner function declarations to the highest possible scope. +* +* @name no-unnecessary-nested-functions +* @memberof rules +* @type {string} +* @default 'error' +* +* @example +* // Bad... +* function outer() { +* function inner() { +* return 42; +* } +* return inner(); +* } +* +* @example +* // Good... +* function inner() { +* return 42; +* } +* +* function outer() { +* return inner(); +* } +* +* @example +* // Good (uses outer scope variable)... +* function outer( x ) { +* var multiplier = 2; +* function inner() { +* return x * multiplier; +* } +* return inner(); +* } +*/ +rules[ 'stdlib/no-unnecessary-nested-functions' ] = 'error'; + /** * Disallow the use of the `new Array()` constructor. * diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js b/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js index e7a2c4f7ce7d..a83887850934 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js @@ -972,6 +972,15 @@ setReadOnly( rules, 'no-self-require', require( '@stdlib/_tools/eslint/rules/no- */ setReadOnly( rules, 'no-unassigned-require', require( '@stdlib/_tools/eslint/rules/no-unassigned-require' ) ); +/** +* @name no-unnecessary-nested-functions +* @memberof rules +* @readonly +* @type {Function} +* @see {@link module:@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions} +*/ +setReadOnly( rules, 'no-unnecessary-nested-functions', require( '@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions' ) ); + /** * @name repl-namespace-order * @memberof rules diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/README.md b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/README.md new file mode 100644 index 000000000000..0a4599f62c57 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/README.md @@ -0,0 +1,183 @@ + + +# no-unnecessary-nested-functions + +> [ESLint rule][eslint-rules] to prevent unnecessary function nesting when inner functions don't depend on outer scope variables. + +
+ +This rule enforces a best practice to only have functions defined inside of other functions if they need access to variables defined in the outer function scope. Otherwise, functions should always be elevated to the highest level (ideally module scope) to avoid redefining the functions per invocation. + +
+ + + +
+ +## Usage + +```javascript +var rule = require( '@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions' ); +``` + +#### rule + +[ESLint rule][eslint-rules] to prevent unnecessary function nesting when inner functions don't depend on outer scope variables. + +**Bad**: + + + +```javascript +var PI = require( '@stdlib/constants/float64/pi' ); + +function outer( x ) { + var result; + + function helper() { + return PI; // doesn't use any variables from outer scope... + } + + result = x * helper(); + return result; +} +``` + +**Good**: + +```javascript +var PI = require( '@stdlib/constants/float64/pi' ); + +// Helper moved to module scope... +function helper() { + return PI; +} + +function outer( x ) { + var result; + result = x * helper(); + return result; +} +``` + +**Good** (function uses outer scope variables): + +```javascript +function outer( x ) { + var multiplier = 2; + + function helper() { + return x * multiplier; // uses 'multiplier' from outer scope... + } + + return helper(); +} +``` + +
+ + + +
+ +## Notes + +- The rule only checks function **declarations**, not function expressions or arrow functions. +- Functions already at module or global scope are ignored. +- Functions that reference variables from outer scopes are not flagged (closures are preserved). +- Functions inside IIFEs (Immediately Invoked Function Expressions) are not flagged, as IIFEs are intentionally used for scope isolation. + +
+ + + +
+ +## Examples + + + +```javascript +var Linter = require( 'eslint' ).Linter; +var rule = require( '@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions' ); + +var linter = new Linter(); + +// Generate source code containing nested function without dependencies: +var code = [ + 'function outer() {', + ' function inner() {', + ' return 42;', + ' }', + ' return inner();', + '}' +].join( '\n' ); + +// Define the ESLint configuration: +var config = { + 'rules': { + 'no-unnecessary-nested-functions': 'error' + } +}; + +// Register the rule: +linter.defineRule( 'no-unnecessary-nested-functions', rule ); + +// Lint the code: +var out = linter.verify( code, config ); +console.log( out ); +/* => + [ + { + 'ruleId': 'no-unnecessary-nested-functions', + 'severity': 2, + 'message': 'Function \'inner\' should be moved to module scope.', + 'line': 2, + 'column': 3, + 'nodeType': 'FunctionDeclaration', + 'endLine': 4, + 'endColumn': 4 + } + ] +*/ +``` + +
+ + + + + + + + + + + + + + diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/examples/index.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/examples/index.js new file mode 100644 index 000000000000..6cc9156bcbad --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/examples/index.js @@ -0,0 +1,61 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +var Linter = require( 'eslint' ).Linter; +var rule = require( './../lib' ); + +var linter = new Linter(); + +// Generate source code containing nested function without dependencies: +var code = [ + 'function outer() {', + ' function inner() {', + ' return 42;', + ' }', + ' return inner();', + '}' +].join( '\n' ); + +// Define the ESLint configuration: +var config = { + 'rules': { + 'no-unnecessary-nested-functions': 'error' + } +}; + +// Register the rule: +linter.defineRule( 'no-unnecessary-nested-functions', rule ); + +// Lint the code: +var out = linter.verify( code, config ); +console.log( out ); +/* => + [ + { + 'ruleId': 'no-unnecessary-nested-functions', + 'severity': 2, + 'message': 'Function declaration \'inner\' does not use outer scope variables and should be moved to module scope.', + 'line': 2, + 'column': 3, + 'nodeType': 'FunctionDeclaration', + ... + } + ] +*/ diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/lib/index.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/lib/index.js new file mode 100644 index 000000000000..27969e3bd7a0 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/lib/index.js @@ -0,0 +1,39 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +/** +* ESLint rule to prevent unnecessary function nesting when inner functions don't depend on outer scope variables. +* +* @module @stdlib/_tools/eslint/rules/no-unnecessary-nested-functions +* +* @example +* var rule = require( '@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions' ); +* +* console.log( rule ); +*/ + +// MODULES // + +var main = require( './main.js' ); + + +// EXPORTS // + +module.exports = main; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/lib/main.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/lib/main.js new file mode 100644 index 000000000000..02906f9dfb29 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/lib/main.js @@ -0,0 +1,323 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// VARIABLES // + +var rule; + + +// FUNCTIONS // + +/** +* Checks if a function is inside an IIFE (Immediately Invoked Function Expression). +* +* @private +* @param {ASTNode} node - function declaration node +* @returns {boolean} true if function is inside an IIFE +*/ +function isInsideIIFE( node ) { + var parent = node.parent; + + // Walk up the AST to check if we're inside an IIFE... + while ( parent ) { + // Check if parent is a function expression or arrow function: + if ( parent.type === 'FunctionExpression' || parent.type === 'ArrowFunctionExpression' ) { + // Check if the function's parent is a CallExpression (making it an IIFE): + if ( parent.parent && parent.parent.type === 'CallExpression' && parent.parent.callee === parent ) { + return true; + } + } + parent = parent.parent; + } + + return false; +} + +/** +* Determines if a function or any of its nested functions reference variables from outer scopes. +* +* @private +* @param {ASTNode} node - function declaration node +* @param {Object} context - ESLint context +* @returns {boolean} true if function or nested functions reference outer scope variables +*/ +function referencesOuterVariables( node, context ) { + var identifierName; + var sourceCode; + var checkScope; + var reference; + var variables; + var variable; + var found; + var scope; + var stmt; + var i; + var j; + + sourceCode = context.getSourceCode(); + scope = sourceCode.getScope( node ); + + // Check all references in this scope... + for ( i = 0; i < scope.references.length; i++ ) { + reference = scope.references[ i ]; + variable = reference.resolved; + identifierName = reference.identifier.name; + + // If variable is resolved and not in current scope, it's from outer scope... + if ( variable && variable.scope !== scope ) { + // Make sure it's not a global or built-in: + if ( variable.scope.type !== 'global' ) { + return true; + } + } + + // Handle unresolved references that might be from outer scope... + if ( !variable ) { + // Check if this identifier exists in parent scopes... + checkScope = scope.upper; + while ( checkScope && checkScope.type !== 'global' ) { + variables = checkScope.variables; + found = false; + for ( j = 0; j < variables.length; j++ ) { + if ( variables[ j ].name === identifierName ) { + found = true; + break; + } + } + if ( found ) { + return true; + } + checkScope = checkScope.upper; + } + } + } + + // Also check nested functions within this function... + if ( node.body && node.body.body ) { + for ( i = 0; i < node.body.body.length; i++ ) { + stmt = node.body.body[i]; + if ( stmt.type === 'FunctionDeclaration' ) { + if ( referencesOuterVariables( stmt, context ) ) { + return true; + } + } + } + } + + return false; +} + +/** +* Finds the scope where a variable is defined. +* +* @private +* @param {string} varName - variable name +* @param {Object} startScope - scope to start searching from +* @returns {(Object|null)} scope where variable is defined or null if not found +*/ +function findVariableScope( varName, startScope ) { + var currentScope; + var variables; + var i; + + currentScope = startScope; + + while ( currentScope && currentScope.type !== 'global' ) { + variables = currentScope.variables; + for ( i = 0; i < variables.length; i++ ) { + if ( variables[i].name === varName ) { + return currentScope; + } + } + currentScope = currentScope.upper; + } + + return null; +} + +/** +* Determines the highest scope where a function can be moved based on its variable references. +* +* @private +* @param {ASTNode} node - function declaration node +* @param {Object} context - ESLint context +* @returns {(Object|null)} highest scope object or null if cannot be moved +*/ +function getHighestPossibleScope( node, context ) { + var definingScope; + var highestScope; + var currentScope; + var sourceCode; + var checkScope; + var reference; + var variable; + var varScope; + var i; + + sourceCode = context.getSourceCode(); + currentScope = sourceCode.getScope( node ); + + // Start with the assumption we can move all the way to module level (just below global)... + highestScope = currentScope.upper; + while ( highestScope && highestScope.upper && highestScope.upper.type !== 'global' ) { + highestScope = highestScope.upper; + } + + // If we're already at module level, we still want to check if we can move there (note: this handles the case where the function is directly nested in a top-level function)... + + // Check all references in this function's scope... + for ( i = 0; i < currentScope.references.length; i++ ) { + reference = currentScope.references[ i ]; + variable = reference.resolved; + + // If variable is resolved and not in current scope, find where it's defined... + if ( variable && variable.scope !== currentScope && variable.scope.type !== 'global' ) { + // Can't move higher than the scope where this variable is defined... + varScope = variable.scope; + + // Walk up the scope chain to see if the variable scope is an ancestor... + checkScope = currentScope.upper; + while ( checkScope && checkScope !== varScope && checkScope.type !== 'global' ) { + checkScope = checkScope.upper; + } + + // If we found the variable scope in our ancestor chain, restrict to that level: + if ( checkScope === varScope ) { + highestScope = varScope; + } + } + + // Handle unresolved references... + if ( !variable ) { + definingScope = findVariableScope( reference.identifier.name, currentScope.upper ); // eslint-disable-line max-len + if ( definingScope ) { + // Can't move higher than where this variable is defined... + if ( definingScope === currentScope.upper ) { + highestScope = currentScope.upper; + } + } + } + } + + // Always return the highest scope - let the caller decide if it should report: + return highestScope; +} + +/** +* Determines the scope type name for error messages. +* +* @private +* @param {Object} scope - ESLint scope object +* @returns {string} scope type name for messages +*/ +function getScopeTypeName( scope ) { + if ( scope.type === 'module' ) { + return 'module scope'; + } + if ( scope.type === 'function' ) { + return 'outer function scope'; + } + return 'higher scope'; +} + +/** +* Rule for enforcing that inner function declarations are moved to higher scope when possible. +* +* @param {Object} context - ESLint context +* @returns {Object} validators +*/ +function main( context ) { + /** + * Checks if a function declaration can be moved to a higher scope. + * + * @private + * @param {ASTNode} node - function declaration node + */ + function checkFunctionDeclaration( node ) { + var scopeTypeName; + var highestScope; + var sourceCode; + var canMove; + var scope; + + // Skip functions inside IIFEs as they are intentionally isolated: + if ( isInsideIIFE( node ) ) { + return; + } + + sourceCode = context.getSourceCode(); + scope = sourceCode.getScope( node ); + + // Skip if already at module/global scope: + if ( !scope.upper || scope.upper.type === 'global' || scope.upper.type === 'module' ) { + return; + } + + highestScope = getHighestPossibleScope( node, context ); + if ( highestScope ) { + canMove = false; + + // Check if the function can be moved to a higher scope... + if ( !referencesOuterVariables( node, context ) ) { + // Function doesn't reference outer variables, can move to module scope... + canMove = true; + scopeTypeName = ( highestScope.upper && highestScope.upper.type === 'global' ) ? 'module scope' : getScopeTypeName( highestScope ); + } else if ( highestScope !== scope.upper ) { + // References outer variables but can still move higher than current level... + canMove = true; + scopeTypeName = getScopeTypeName( highestScope ); + } + + if ( canMove ) { + context.report({ + 'node': node, + 'message': 'Function \'{{name}}\' should be moved to {{scope}}.', + 'data': { + 'name': ( node.id ) ? node.id.name : '', + 'scope': scopeTypeName + } + }); + } + } + } + + return { + 'FunctionDeclaration': checkFunctionDeclaration + }; +} + + +// MAIN // + +rule = { + 'meta': { + 'type': 'suggestion', + 'docs': { + 'description': 'enforce moving inner function declarations to higher scope when they do not depend on outer scope variables' + }, + 'schema': [] + }, + 'create': main +}; + + +// EXPORTS // + +module.exports = rule; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/package.json b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/package.json new file mode 100644 index 000000000000..e991ca2b3d71 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/package.json @@ -0,0 +1,59 @@ +{ + "name": "@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions", + "version": "0.0.0", + "description": "ESLint rule to prevent unnecessary function nesting when inner functions don't depend on outer scope variables.", + "license": "Apache-2.0", + "author": { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + }, + "contributors": [ + { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + } + ], + "main": "./lib", + "directories": { + "example": "./examples", + "lib": "./lib", + "test": "./test" + }, + "scripts": {}, + "homepage": "https://github.com/stdlib-js/stdlib", + "repository": { + "type": "git", + "url": "git://github.com/stdlib-js/stdlib.git" + }, + "bugs": { + "url": "https://github.com/stdlib-js/stdlib/issues" + }, + "dependencies": {}, + "devDependencies": {}, + "engines": { + "node": ">=0.10.0", + "npm": ">2.7.0" + }, + "os": [ + "aix", + "darwin", + "freebsd", + "linux", + "macos", + "openbsd", + "sunos", + "win32", + "windows" + ], + "keywords": [ + "stdlib", + "tools", + "tool", + "eslint", + "lint", + "rule", + "function", + "nested", + "scope" + ] +} diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/test/fixtures/invalid.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/test/fixtures/invalid.js new file mode 100644 index 000000000000..b116dcf47df1 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/test/fixtures/invalid.js @@ -0,0 +1,454 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +var invalid = []; + +// Simple nested function without dependencies: +var test = { + 'code': [ + 'function outer() {', + ' function inner() {', + ' return 42;', + ' }', + ' return inner();', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'inner\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Nested function that only uses global variables: +test = { + 'code': [ + 'var globalVar = 10;', + 'function outer() {', + ' function inner() {', + ' return globalVar * 2;', + ' }', + ' return inner();', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'inner\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Three levels deep, innermost can move to module: +test = { + 'code': [ + 'function outer() {', + ' function middle() {', + ' function inner() {', + ' return Math.PI;', + ' }', + ' return inner();', + ' }', + ' return middle();', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'middle\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + }, + { + 'message': 'Function \'inner\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Three levels deep, innermost can move to outer function: +test = { + 'code': [ + 'function outer() {', + ' var outerVar = 10;', + ' function middle() {', + ' function inner() {', + ' return outerVar * 2;', + ' }', + ' return inner();', + ' }', + ' return middle();', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'inner\' should be moved to outer function scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Multiple nested functions without dependencies: +test = { + 'code': [ + 'function outer() {', + ' function helper1() {', + ' return 1;', + ' }', + ' function helper2() {', + ' return 2;', + ' }', + ' return helper1() + helper2();', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'helper1\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + }, + { + 'message': 'Function \'helper2\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Nested function with only internal variables: +test = { + 'code': [ + 'function outer( x ) {', + ' function calculate() {', + ' var y = 10;', + ' var z = 20;', + ' return y + z;', + ' }', + ' return x * calculate();', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'calculate\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Named function without dependencies: +test = { + 'code': [ + 'function outer() {', + ' function namedHelper() {', + ' return "helper";', + ' }', + ' return namedHelper();', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'namedHelper\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Function in block scope without dependencies: +test = { + 'code': [ + 'function outer() {', + ' if ( true ) {', + ' function blockScoped() {', + ' return 42;', + ' }', + ' blockScoped();', + ' }', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'blockScoped\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Functions where only some can be moved: +test = { + 'code': [ + 'function outer() {', + ' function first() {', + ' return 1;', + ' }', + ' function second() {', + ' return 2;', + ' }', + ' function third() {', + ' return first() + second();', + ' }', + ' return third();', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'first\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + }, + { + 'message': 'Function \'second\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + // Note: third() cannot be moved because it references first() and second() which are sibling function declarations + ] +}; +invalid.push( test ); + +// Function using only built-in objects: +test = { + 'code': [ + 'function outer() {', + ' function useBuiltins() {', + ' return Math.PI * Date.now();', + ' }', + ' return useBuiltins();', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'useBuiltins\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Function declaration inside function expression (assignment): +test = { + 'code': [ + 'var obj = {};', + 'obj.method = function() {', + ' function inner() {', + ' return 42;', + ' }', + ' return inner();', + '};' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'inner\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Function declaration inside function expression (variable): +test = { + 'code': [ + 'var outer = function() {', + ' function inner() {', + ' return 42;', + ' }', + ' return inner();', + '};' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'inner\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Function declaration inside object method: +test = { + 'code': [ + 'var obj = {', + ' method: function() {', + ' function inner() {', + ' return 42;', + ' }', + ' return inner();', + ' }', + '};' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'inner\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Function declaration inside function expression returned from factory: +test = { + 'code': [ + 'function factory() {', + ' return function() {', + ' function inner() {', + ' return 42;', + ' }', + ' return inner();', + ' };', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'inner\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Function declaration inside array method callback: +test = { + 'code': [ + '[1, 2, 3].map(function() {', + ' function inner() {', + ' return 42;', + ' }', + ' return inner();', + '});' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'inner\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Function declaration inside class method (ES6): +test = { + 'code': [ + 'class MyClass {', + ' method() {', + ' function inner() {', + ' return 42;', + ' }', + ' return inner();', + ' }', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'inner\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ], + 'parserOptions': { + 'ecmaVersion': 6 + } +}; +invalid.push( test ); + +// Function uses 'arguments' directly (can be moved since it refers to its own arguments): +test = { + 'code': [ + 'function outer() {', + ' function helper() {', + ' return arguments.length;', + ' }', + ' return helper(1, 2, 3);', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'helper\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Function uses 'this' directly (can be moved since strict mode makes this undefined anyway): +test = { + 'code': [ + 'function outer() {', + ' function helper() {', + ' return this.value * 2;', + ' }', + ' return helper.call({ value: 5 });', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'helper\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Object method with nested function using 'this': +test = { + 'code': [ + 'var obj = {', + ' value: 10,', + ' method: function() {', + ' function helper() {', + ' return this.value * 2;', + ' }', + ' return helper.call(this);', + ' }', + '};' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'helper\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + +// Constructor with nested function using 'this': +test = { + 'code': [ + 'function MyClass() {', + ' this.value = 42;', + ' function initialize() {', + ' this.initialized = true;', + ' }', + ' initialize.call(this);', + '}' + ].join( '\n' ), + 'errors': [ + { + 'message': 'Function \'initialize\' should be moved to module scope.', + 'type': 'FunctionDeclaration' + } + ] +}; +invalid.push( test ); + + +// EXPORTS // + +module.exports = invalid; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/test/fixtures/unvalidated.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/test/fixtures/unvalidated.js new file mode 100644 index 000000000000..3544adf14ca6 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/test/fixtures/unvalidated.js @@ -0,0 +1,39 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +var unvalidated = []; + +// Function declaration inside IIFE (intentionally isolated): +var test = { + 'code': [ + '(function() {', + ' function inner() {', + ' return 42;', + ' }', + ' return inner();', + '})();' + ].join( '\n' ) +}; +unvalidated.push( test ); + + +// EXPORTS // + +module.exports = unvalidated; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/test/fixtures/valid.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/test/fixtures/valid.js new file mode 100644 index 000000000000..a8cb1fd4a808 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/test/fixtures/valid.js @@ -0,0 +1,204 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +var valid = []; + +// Top-level function (no nesting): +var test = { + 'code': [ + 'function topLevel() {', + ' return 42;', + '}' + ].join( '\n' ) +}; +valid.push( test ); + +// Function expression (not a declaration): +test = { + 'code': [ + 'function outer() {', + ' var helper = function() {', + ' return 42;', + ' };', + ' return helper();', + '}' + ].join( '\n' ) +}; +valid.push( test ); + +// Arrow function (not a declaration): +test = { + 'code': [ + 'function outer() {', + ' var helper = () => 42;', + ' return helper();', + '}' + ].join( '\n' ), + 'parserOptions': { + 'ecmaVersion': 6 + } +}; +valid.push( test ); + +// Function uses outer scope variable: +test = { + 'code': [ + 'function outer( x ) {', + ' var multiplier = 2;', + ' function helper() {', + ' return x * multiplier;', + ' }', + ' return helper();', + '}' + ].join( '\n' ) +}; +valid.push( test ); + +// Function uses parameter from outer function: +test = { + 'code': [ + 'function outer( x, y ) {', + ' function helper() {', + ' return x + y;', + ' }', + ' return helper();', + '}' + ].join( '\n' ) +}; +valid.push( test ); + +// Function uses 'this' from outer scope: +test = { + 'code': [ + 'function outer() {', + ' var self = this;', + ' function helper() {', + ' return self.value;', + ' }', + ' return helper();', + '}' + ].join( '\n' ) +}; +valid.push( test ); + +// Function uses 'arguments' from outer scope: +test = { + 'code': [ + 'function outer() {', + ' var args = arguments;', + ' function helper() {', + ' return args.length;', + ' }', + ' return helper();', + '}' + ].join( '\n' ) +}; +valid.push( test ); + +// Closure pattern (uses outer variable): +test = { + 'code': [ + 'function createCounter() {', + ' var count = 0;', + ' function increment() {', + ' count += 1;', + ' return count;', + ' }', + ' return increment;', + '}' + ].join( '\n' ) +}; +valid.push( test ); + +// Recursive function that references itself: +test = { + 'code': [ + 'function outer() {', + ' var n = 10;', + ' function factorial() {', + ' if ( n <= 1 ) {', + ' return 1;', + ' }', + ' n -= 1;', + ' return n * factorial();', + ' }', + ' return factorial();', + '}' + ].join( '\n' ) +}; +valid.push( test ); + +// Module-level function: +test = { + 'code': [ + 'var x = 10;', + 'function moduleLevel() {', + ' return x * 2;', + '}' + ].join( '\n' ) +}; +valid.push( test ); + +// Mutually recursive functions: +test = { + 'code': [ + 'function outer() {', + ' var count = 0;', + ' function even() {', + ' if ( count === 10 ) {', + ' return true;', + ' }', + ' count += 1;', + ' return odd();', + ' }', + ' function odd() {', + ' if ( count === 10 ) {', + ' return false;', + ' }', + ' count += 1;', + ' return even();', + ' }', + ' return even();', + '}' + ].join( '\n' ) +}; +valid.push( test ); + +// Function references outer function before it's defined (hoisting): +test = { + 'code': [ + 'function outer() {', + ' var x = 10;', + ' function helper() {', + ' return x + later();', + ' }', + ' function later() {', + ' return x * 2;', + ' }', + ' return helper();', + '}' + ].join( '\n' ) +}; +valid.push( test ); + + +// EXPORTS // + +module.exports = valid; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/test/test.js b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/test/test.js new file mode 100644 index 000000000000..2bb99b368bb4 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/no-unnecessary-nested-functions/test/test.js @@ -0,0 +1,86 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var tape = require( 'tape' ); +var RuleTester = require( 'eslint' ).RuleTester; +var rule = require( './../lib' ); + + +// FIXTURES // + +var valid = require( './fixtures/valid.js' ); +var invalid = require( './fixtures/invalid.js' ); +var unvalidated = require( './fixtures/unvalidated.js' ); + + +// TESTS // + +tape( 'main export is an object', function test( t ) { + t.ok( true, __filename ); + t.strictEqual( typeof rule, 'object', 'main export is an object' ); + t.end(); +}); + +tape( 'the function positively validates code with properly scoped functions', function test( t ) { + var tester = new RuleTester(); + + try { + tester.run( 'no-unnecessary-nested-functions', rule, { + 'valid': valid, + 'invalid': [] + }); + t.pass( 'passed without errors' ); + } catch ( err ) { + t.fail( 'encountered an error: ' + err.message ); + } + t.end(); +}); + +tape( 'the function negatively validates code with unnecessarily nested functions', function test( t ) { + var tester = new RuleTester(); + + try { + tester.run( 'no-unnecessary-nested-functions', rule, { + 'valid': [], + 'invalid': invalid + }); + t.pass( 'passed without errors' ); + } catch ( err ) { + t.fail( 'encountered an error: ' + err.message ); + } + t.end(); +}); + +tape( 'the function does not validate edge cases and special patterns', function test( t ) { + var tester = new RuleTester(); + + try { + tester.run( 'no-unnecessary-nested-functions', rule, { + 'valid': unvalidated, + 'invalid': [] + }); + t.pass( 'passed without errors' ); + } catch ( err ) { + t.fail( 'encountered an error: ' + err.message ); + } + t.end(); +});