eslint-plugin-unicorn no-using-resource-escape:拦截 `using` 资源经 return/export 逃逸所有权
eslint-plugin-unicorn no-using-resource-escape拦截using资源经 return/export 逃逸所有权【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn本指南深入解析 eslint-plugin-unicorn 中的no-using-resource-escape规则。该规则禁止将using/await using声明的资源通过return、export或捕获该资源的函数泄露到所有权作用域之外从而避免调用方拿到已被释放disposed的资源。读完本文你将掌握该规则的检测范围、底层实现原理、支持与刻意不支持的边界场景以及它与prefer-dispose、no-invalid-well-known-symbol-methods等规则的协同用法。规则背景Explicit Resource Management 与资源逃逸JavaScript 的 Explicit Resource Managementusing与await using声明会在声明所在的块级作用域退出时自动按逆序释放dispose所声明的资源取代了手写try/finally的经典模式。其语义要点是using foo …要求值实现Symbol.disposeawait using则要求实现Symbol.asyncDispose否则运行时抛出TypeError当作用域退出时资源立即被释放。若把资源本身、或一个捕获了该资源的函数返回/导出到作用域之外调用方后续再访问它时资源已经被释放从而产生隐蔽的运行时错误。no-using-resource-escape正是针对这一逃逸escape问题设计的静态检查规则。规则元数据在 rules/no-using-resource-escape.js 中定义type: problem即报告的是实质性 bug 而非风格问题schema: []即没有任何配置选项同时不提供自动修复neither autofix nor suggestion。启用状态与使用前提该规则默认包含在recommended与unopinionated两套配置中规则头部见 docs/rules/no-using-resource-escape.md以 ✅/☑️ 标注启用状态readme.md 的规则索引表中也可见其行。它在 rules/index.js 中作为no-using-resource-escape导出随插件统一注册。规则同时支持 JavaScript 与 TypeScript且不需要类型信息type information因此无需开启 type-aware linting 即可生效。不提供自动修复的原因在文档中写明资源的所有权必须由应用自身决定规则无法替你改写代码语义。核心规则禁止返回或导出资源本身最基本的情形是直接把using声明的资源作为返回值或导出值// ❌ 资源在函数返回前就被释放调用方拿到的是已释放对象 function openResource() { using resource acquire(); return resource; } // ✅ 只返回从资源读取的结果 function readResource() { using resource acquire(); return resource.read(); }// ❌ 导出后模块外任何使用方拿到的都是已释放资源 using resource acquire(); export {resource}; // ✅ 异步资源在函数体内完成使用后再返回查询结果 export async function query() { await using connection await connect(); return await connection.query(); }从源码看规则通过context.on(ReturnStatement, …)与context.on([ExportNamedDeclaration, ExportDefaultDeclaration, TSExportAssignment], …)两条监听路径分别处理返回与导出见 rules/no-using-resource-escape.js。在 TypeScript 中export resourceTSExportAssignment同样会被拦截。关键检测维度捕获资源的函数同样逃逸比直接返回资源更隐蔽的是“捕获函数”capturing function——返回或导出一个闭包而该闭包内部引用了外层using声明的资源// ❌ 返回的箭头函数捕获了 resource调用它时资源已释放 function createDisposedReader() { using resource acquire(); return () resource.read(); } // ✅ 在返回的函数内部自行声明并拥有资源 function createReader() { return () { using resource acquire(); return resource.read(); }; }捕获函数的识别逻辑在getReferencedFunction与getCapturedResources中实现见 rules/no-using-resource-escape.js 与 L110-L123。规则支持两种“间接引用函数”的方式未重新赋值的局部函数声明FunctionDeclaration且其变量没有任何写入引用直接以函数表达式初始化的const声明初始值经unwrapTypeScriptExpression解包后确认是函数如const read () resource.read();。而对let声明的函数变量例如let read () resource.read(); return read;由于无法静态保证它未被改写规则故意不检测这一点在测试文件 test/no-using-resource-escape.js 的 “Deliberately unsupported escape paths” 注释组中可以看到对应用例。捕获检测基于 ESLint 的 scope 分析遍历函数作用域的through引用即未在函数内声明的自由变量对每个运行时引用isRuntimeReference若其解析到的变量满足“由本函数作用域拥有的using声明”条件则判定逃逸。isOwnedResource通过getUniqueDefinition确认变量唯一的定义是kind using或kind await using的Variable定义且该定义所在的作用域归属getOwner向上找最近的函数或Program与当前逃逸点一致见 rules/no-using-resource-escape.js。容器追踪数组、对象、条件/逻辑/序列表达式逃逸不一定发生在“裸资源”上——资源可以藏在复合表达式里。规则的getEscapingResources生成器会递归解构以下容器见 rules/no-using-resource-escape.js表达式类型检查策略关键细节ArrayExpression递归检查每个数组元素空洞元素如[,, resource]同样处理ObjectExpression递归检查每个Property的value包括普通属性值、方法含 getter/setterConditionalExpression检查consequent与alternate若资源本身作为test由于可释放值恒为 truthy资源不可能从alternate分支逃逸故对alternate中的同资源做豁免LogicalExpression按运算符分派可释放值恒为 truthy因此资源不可能从的左侧逃逸\|\|、??两侧都检查SequenceExpression只检查最后一个操作数序列表达式整体值等于最后一项对应测试覆盖了大量组合例如// ❌ 条件、逻辑、序列表达式中的逃逸 function f() { using resource acquire(); return condition ? resource : other; } function f() { using resource acquire(); return other resource; } function f() { using resource acquire(); return (other, resource); } // ✅ 资源作为条件本身、或值不逃逸时 function f() { using resource acquire(); return resource ? resource.read() : resource; } function f() { using resource acquire(); return (resource, other); }以上用例分别见 test/no-using-resource-escape.js 与 L27。当return的参数为标识符时getEscapingResources会先尝试解析其指向的资源变量若解析结果是上述两类可跟踪函数则继续按函数捕获分析。TypeScript 与 JSX 处理规则借助unwrapTypeScriptExpression先剥离as、satisfies、非空断言!、尖括号类型断言等包装再进入递归分析因此这些写法不会绕过检查// ❌ 类型断言不能掩盖逃逸事实 function f() { using resource acquire(); return resource as Resource; } function f() { using resource acquire(); return resource satisfies Resource; } async function f() { await using resource acquire(); return resource!; }同时规则会识别非运行时引用isNonRuntimeReference见 rules/no-using-resource-escape.js并予以豁免包括typeof resource类型查询、JSX 命名空间名如resource:tag /的标签部分类型专用计算键type-only computed keys涵盖TSAbstractMethodDefinition、TSMethodSignature、TSPropertySignature等节点类型见 rules/no-using-resource-escape.js 的typeOnlyComputedKeyNodeTypesdeclare修饰的属性、抽象类成员、带装饰器的成员除外装饰器意味着运行时确实会读取该键。对应的 TS/JSX 有效与无效用例集中列在 test/no-using-resource-escape.js例如export type {resource}、export {type resource}、return (): typeof resource other均为合法而return Resourceresource;、export resource;、类中的运行时计算键[resource]() {}等均为非法。getExportedValues对export语句的解析也严格排除了export type、export {type x}以及带source的 re-export见 rules/no-using-resource-escape.js。局限性与刻意不支持的逃逸路径规则文档明确列出以下不支持的场景理解这些边界有助于避免误用对应实现见 docs/rules/no-using-resource-escape.md 与测试注释组别名const alias resource; return alias;—— 不跟踪中间绑定解构与可变绑定export const {name} resource;、export let value resource;、let read () resource.read(); return read;对外部状态的赋值outer resource;写入外层状态而非 return/export类返回的类方法捕获资源return class { read() { return resource.read(); } };属性派生资源return resource.value;这是安全的返回的是属性值调用与 awaited 表达式return wrap(resource);、return await resource;展开return {...resource};、return [...resource];计算对象键return {[resource.read()]: other};yield生成器函数中的yield resource;re-export 与类型专用导出/引用export {resource} from other;。此外传给定时器、事件监听器、Promise 等回调中的资源引用也被忽略因为其生命周期未知setTimeout(() resource.read(), 0)不会报错。两个 TypeScript 专项边界同样刻意不支持函数实例化表达式return readResource与重载函数引用先声明重载签名再实现的函数相关用例见 test/no-using-resource-escape.js 的注释。相关规则与配合建议prefer-disposedocs/rules/prefer-dispose.md反向互补——它鼓励把只用于释放资源的try/finally改写为using声明。先用prefer-dispose引入资源管理再用no-using-resource-escape保证资源不逃逸两者构成“引入声明 守住所有权”的完整闭环。no-invalid-well-known-symbol-methodsdocs/rules/no-invalid-well-known-symbol-methods.md检查Symbol.dispose/Symbol.asyncDispose等方法的实现是否合法例如Symbol.dispose必须同步、不能返回 Promise从 disposer 一侧保证using语义正确。typescript-eslint/return-await若希望确保 Promise 在资源释放前被 await文档建议配合该规则使用。但要注意await只能修复 Promise 场景无法修复返回普通已释放资源或捕获它的闭包的问题——这正是本规则存在的意义。测试验证与快照规则的完整行为由 test/no-using-resource-escape.js 通过三组快照测试锁定普通 JS 用例、TypeScript 用例、JSX 用例。快照输出存放在 test/snapshots/no-using-resource-escape.js.md每次改动后通过快照比对即可确认错误消息与报告位置未发生意外变化。规则报告的消息模板为Do not {{action}} resource {{name}} or a value that contains or captures it. The resource is disposed when its owning scope exits.其中action为return或exportname为资源变量名见 rules/no-using-resource-escape.js。规则还正确处理了前向引用如export {read}; using resource acquire(); function read() { return resource.read(); }与文件后部的写入其注释“Scope analysis already includes forward references and writes later in the file”rules/no-using-resource-escape.js说明了 scope 分析带来的稳健性。小结no-using-resource-escape是 Explicit Resource Management 生态中不可或缺的所有权守卫它覆盖 return/export 两条逃逸通道既能识别裸资源逃逸也能穿透数组、对象、条件/逻辑/序列表达式以及捕获函数层层追踪同时它保持克制——明确不支持别名、可变绑定、类、yield、回调等无法静态定论的路径也不提供自动修复。将它与prefer-dispose、no-invalid-well-known-symbol-methods及类型层面的return-await配合使用可以在不依赖类型信息的前提下系统性地杜绝“返回已释放资源”这一类隐蔽 bug。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考