eslint-plugin-unicorn 之 consistent-function-scoping:将函数定义提升到最高可能作用域
eslint-plugin-unicorn 之 consistent-function-scoping将函数定义提升到最高可能作用域【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn本篇文章讲解 eslint-plugin-unicorn 中consistent-function-scoping规则的设计理念、检测逻辑与配置方式。该规则的核心目标是把函数定义移动到尽可能高的作用域在捕获值不被破坏的前提下从而提升代码可读性、改善运行时性能并帮助 JavaScript 引擎更好地做优化。阅读本文后你将掌握该规则的适用场景、误报边界IIFE、React Hooks、jest.mock工厂等豁免情形以及如何通过checkArrowFunctions选项定制检测行为。规则概述为什么函数要尽量放在外层作用域consistent-function-scoping的规则描述是一句话Move function definitions to the highest possible scope把函数定义移动到最高可能的作用域。它要求函数定义在不破坏其捕获值的前提下尽量靠近顶层作用域。该规则在仓库中的定位如下在 readme.md 的规则列表中标注为 ✅即默认在recommended配置中启用同时在 ☑️unopinionated配置中被禁用。规则实现位于 rules/consistent-function-scoping.js并被注册到 rules/index.js。规则类型为suggestion建议型消息模板为Move {{functionNameWithKind}} to the outer scope.即在提示中动态填充函数种类与名称如function doBar、async arrow function等定位信息使用getFunctionHeadLocation指向函数头部方便开发者快速定位到被判定应该外移的函数。为什么要把函数外移文档给出的理由有两层可读性当函数没有捕获任何外层作用域的变量时把它定义在内部只会增加阅读者的认知负担——读者需要确认它到底依赖了哪些外层状态才能判断能否安全移动。性能外层作用域的函数可以被复用而不是每次执行时重新创建同时文档引用了社区普遍认可的观点减少嵌套层级能够让 JavaScript 引擎如 V8的优化器更轻松地对函数进行内联、去虚拟化等优化避免因为函数过于动态化而落入优化限制的陷阱。规则触发示例哪些写法会被判定违规函数声明嵌套在函数体内以下代码中doBar完全没有捕获doFoo作用域内的任何东西因此可以被安全地移动到外层// ❌ 违规 export function doFoo(foo) { // Does not capture anything from the scope, can be moved to the outer scope function doBar(bar) { return bar bar; } return doBar; }// ✅ 符合规则 function doBar(bar) { return bar bar; } export function doFoo(foo) { return doBar; }箭头函数同样会被检测默认开启// ❌ 违规 function doFoo() { // Does not capture anything from the scope, can be moved to the outer scope return bar bar bar; }// ✅ 符合规则 const doBar bar bar bar; function doFoo() { return doBar; }值得注意的是return 语句中的箭头函数也会被标记。例如 Redux 风格的 action creator// ❌ 违规 export function someAction() { return dispatch dispatch({type: SOME_TYPE}); }// ✅ 符合规则 const handleDispatch dispatch dispatch({type: SOME_TYPE}); export function someAction() { return handleDispatch; }这一行为在 test/consistent-function-scoping.js 中有对应的测试用例注释#931 - arrow functions returned from function declarations should be flagged并且多层嵌套箭头函数如return next action { ... }的柯里化中间件写法在每次嵌套处都可能分别报告一次错误测试中还覆盖了类方法、对象方法与默认导出函数中 return 箭头函数的情况。变量声明形式const箭头函数 / 函数表达式// ❌ 违规 function doFoo(foo) { const doBar bar { return bar bar; }; }// ✅ 符合规则 const doBar bar { return bar bar; }; function doFoo(foo) {}同理const doBar function (bar) { ... }的函数表达式形式也会被检测。从源码看shouldSkipFunction会先跳过VariableDeclarator、VariableDeclaration等包装节点再基于真正的父节点确定相关父作用域因此const/let声明的函数表达式与箭头函数都会被纳入同一套判定逻辑。捕获了外层变量的函数不会被标记规则只针对可以安全外移的函数。一旦函数捕获了外层作用域的变量比如foo移动它就会破坏闭包语义因此不报告// ✅ 符合规则 export function doFoo(foo) { function doBar(bar) { return bar bar foo.doBar(bar); } return doBar; } // ✅ 符合规则 const doBar bar bar bar; export function doFoo() { return doBar; }测试文件也验证了大量合法捕获场景函数捕获参数、捕获函数体内const变量、多层嵌套函数各自捕获不同层级变量、递归引用自身函数名等均不报错。特别地源码中的checkReferences对递归函数名做了专门跳过处理——当引用解析到同名FunctionName定义时直接忽略避免把递归函数误判为可以外移。源码实现原理作用域引用分析规则的核心实现位于 rules/consistent-function-scoping.js整体思路可以概括为对每个函数节点判断把它上移到父作用域后它的所有变量引用是否仍然成立。检测的函数类型规则监听三类函数节点定义于 rules/ast/function-types.jsFunctionDeclaration函数声明FunctionExpression函数表达式ArrowFunctionExpression箭头函数引用检查算法checkReferences是核心判定函数它通过 ESLint 的scopeManager做三类检查引用references检查遍历函数作用域内的所有引用判断其from作用域或解析后的resolved.scope是否落在候选父作用域集合parentScopes中如果是说明函数依赖外层变量不能外移。定义definitions检查对变量的定义节点调用scopeManager.acquire判断定义所在作用域是否在父作用域集合中。相邻函数定义标识符检查处理邻居函数声明这种边界情况——查找存在于FunctionDeclaration中的标识符通过scopeManager.acquire(identifier.parent)定位其上层作用域判断它是否与被检查的引用同处一层。测试中function doFoo(foo) { function doBar() {...} function doZaz() { doBar(); } }这类兄弟函数互相调用的合法场景正是靠这个分支正确放行的。候选父作用域的计算getRelevantParentScopes负责收集函数被外移后所处的父作用域候选集。除直接父作用域外它还会处理循环体块的特殊性getLoopBodyBlockChain会沿着BlockStatement链向上收集循环体DoWhileStatement、ForInStatement、ForOfStatement、ForStatement、WhileStatement对应的作用域。这意味着循环体内声明的函数/箭头函数如果捕获的是循环迭代变量如for循环的value会因依赖循环作用域而被视为合法这在测试中有大量对应用例。配置项checkArrowFunctions该规则只有一个选项定义在规则的schema中additionalProperties: false不允许未知键配置项类型默认值说明checkArrowFunctionsbooleantrue是否检测箭头函数设为false后仅检测普通函数声明/函数表达式在 eslint.config.js 的项目自检dogfooding配置中该规则被显式关闭unicorn/consistent-function-scoping: off以符合项目自身的代码风格。配置示例// eslint.config.jsflat config 写法 import unicorn from eslint-plugin-unicorn; export default [ { plugins: {unicorn}, rules: { // 开启并自定义箭头函数检测 unicorn/consistent-function-scoping: [error, {checkArrowFunctions: true}], }, }, ];当checkArrowFunctions为false时源码中context.onExit回调会直接跳过所有ArrowFunctionExpression节点但测试表明箭头函数内部嵌套的普通函数声明仍然会被检查const outer () { function inner() {} }在关闭箭头函数检测后仍会报告function inner因为内层函数声明本身属于FunctionDeclaration不受该选项影响。Limitations规则刻意忽略的边界情况文档明确列出了几类不会触发报告的代码形态理解这些边界能帮助你判断为什么这里没报错1. 函数体内的多余代码块规则不会检测或清理函数内部的额外块级作用域function doFoo(foo) { { function doBar(bar) { return bar; } } return foo; }不过测试用例显示规则依然会穿透多余的{}块报告function doFoo() { { { function doBar() {...} } } }这种双重嵌套块中的doBar仍会被标记甚至顶层模块中的裸块{ { function doBar() {...} } }也会被报告。需要区分的是规则不主动删除这些多余的块无自动修复但函数定义本身若可外移仍会被指出。2. 立即调用函数表达式IIFEIIFE 内部的函数被忽略因为 IIFE 本身就是一种局部作用域隔离的惯用法(function () { function doFoo(bar) { return bar; } })();源码中isIife的实现要求节点类型为FunctionExpression或ArrowFunctionExpression且其父节点是CallExpression且callee就是该节点本身。测试覆盖了多种 IIFE 形态(function(){})()、(function(){}())、!function(){}()、(() {})()、async function(){}、async function*(){}等。同时IIFE 内部更深层嵌套的函数仍会被检查——例如(function() { function foo() { function bar() {} } })()中bar仍被报告foo(function() { ... })这种作为参数传入、并非 IIFE的函数体内部同样会被检查测试中有专门的对照用例。3. React 内置 HooksReact 官方内置 Hooks 的回调函数被忽略useEffect(() { async function getItems() {} getItems(); }, [])源码中维护了一份完整的 Hooks 名单useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef、useImperativeHandle、useLayoutEffect、useDebugValue并且同时匹配useXxx与React.useXxx两种调用形态借助 utils 中的isNodeMatches。判定逻辑要求scope.block.parent.callee与名单匹配。测试确认useEffect(() { function foo() {} })合法而NotReact.useEffect(() { function foo() {} })会被报告——说明只有名单内的 Hooks 才受豁免此外 Hooks 回调内部嵌套的更深层函数如useEffect回调里的foo内再定义bar仍会被正常检查。4.jest.mock()工厂函数Jest 会限制 mock 工厂函数引用作用域外的变量因此工厂内部的函数被忽略jest.mock(module, () { function createMock() { return mock; } return createMock; });源码中isInsideJestMockFactory会沿 AST 父链向上查找判断是否存在CallExpression满足第二个参数arguments[1]是当前函数节点、callee匹配jest.mock、且函数类型在三种函数节点之内。测试进一步验证了边界jest.mock(module, () () mock)这种直接内联返回的箭头函数不会被豁免因为工厂参数本身不是函数声明/表达式的嵌套体而jest.doMock不在豁免名单内其工厂内部函数会被报告。5. 词法this与arguments的捕获虽然文档未展开但源码与测试补充了重要细节箭头函数如果捕获了外层词法this或arguments外移会改变其语义因此不会被报告。判定借助 rules/utils/is-node-contains-lexical-this.js 做 AST 级词法扫描刻意不使用scope.thisFound因为该标记在不同解析器间行为不一致this表达式、类计算键、superClass等都会触发词法this命中而普通函数/类体内部的this会被正确跳过。测试覆盖了() this、() () this多层嵌套、参数默认值引用thisconst doBar (value this) value、类计算键[this.x]()以及() arguments等场景均判定为合法。消息格式与定位规则通过getFunctionNameWithKind来自eslint-community/eslint-utils生成精确的函数种类描述测试验证了以下消息形态代码形态报告消息中的函数名function bar() {}function barasync function bar() {}async function barfunction* bar() {}generator function barasync function* bar() {}async generator function barconst bar () {}arrow function barconst bar async () {}async arrow function bar匿名return bar bararrow function无名称完整消息示例Move async generator function baz to the outer scope.。报告位置指向函数头部getFunctionHeadLocation例如function foo() { function bar() {} }的报错范围覆盖整个bar的声明头。与其他生态的配合TypeScript 与 JSX规则通过meta.languages: [js/js]声明只针对 JavaScript。不过测试test.typescript块表明规则在 TypeScript 与 JSX 场景下也能正常工作泛型箭头函数const log T extends Method(method: T) (data: DataT) {...}如果捕获外层name等变量不会被误报类字段中的FinalizationRegistry回调引用this时不会被误报词法this豁免JSX 引用同样参与作用域分析function doFoo(FooComponent) { return FooComponent /; }合法组件内部定义且未被 JSX 引用的函数如function Foo() { const Bar div /; function doBaz() {...} }会被报告小写 JSX 标签如foo /被视为内建元素而非标识符引用因此内部函数仍会被标记。总结如何用好 consistent-function-scopingconsistent-function-scoping是一条低误报、高收益的代码组织型规则。它的设计哲学是让函数定义位置忠实反映其依赖关系——不捕获外层状态的函数理应被提升到外层避免每次调用外层函数时都重新创建内部函数也避免引擎对动态函数做优化时打折扣。实践建议保持recommended配置默认启用即可获得全量检测如果项目中有大量依赖闭包捕获的柯里化/工厂函数风格代码如 Redux middleware可评估checkArrowFunctions: false仅保留对普通函数声明的检测理解豁免清单IIFE、React Hooks、jest.mock工厂、词法this/arguments捕获能帮助你解释为什么某些嵌套函数没有被报告避免误以为规则漏检。如果你想深入验证行为可以直接阅读 test/consistent-function-scoping.js含大量有效/无效用例与快照测试 test/snapshots/consistent-function-scoping.js.md并结合 rules/consistent-function-scoping.js 的实现逐步对照分析。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →