eslint-plugin-unicorn 规则快照解析:prefer-location-assign 的检测与自动修复行为
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
本篇文章以eslint-plugin-unicorn仓库中的 AVA 测试快照test/snapshots/prefer-location-assign.js.md为主线,深入剖析prefer-location-assign规则的检测范围、报错信息、自动修复边界以及对应的源码实现。读者读完可以掌握该规则的完整行为模型,理解 ESLint 快照测试的解读方法,并能在自己的项目中正确启用、配置与迁移使用该规则。
快照文档是什么:一条规则的"行为契约"
test/snapshots/prefer-location-assign.js.md是由测试框架 AVA 自动生成的快照报告,对应的二进制快照文件为prefer-location-assign.js.snap(位于同一目录)。它的作用不是给人写的手册,而是把规则在非法代码(invalid)用例上的表现——包括输入源码、报错位置(用^^^^标注)、错误消息文本和自动修复后的输出——逐字固化下来,防止后续改动悄悄改变规则行为。
该快照对应 rules/prefer-location-assign.js 的实现和 test/prefer-location-assign.js 的测试定义。规则的核心主张是:用location.assign()方法调用替代对location.href的赋值,其meta.type为suggestion,meta.fixable为code(支持--fix),meta.recommended为true,且声明languages: ['js/js'],即仅针对 JavaScript/JSX 代码生效。
规则背景:为什么优先 assign() 而不是 href 赋值
官方规则文档 docs/rules/prefer-location-assign.md 给出了设计依据:Location#assign()比给.href赋值在语义上更显式、更清晰。两者在行为上的关键区别在于浏览器历史记录:
location.assign(url):保留当前页面在历史记录中,用户仍然可以点击"返回"回到原页面;location.replace(url):替换当前历史条目,用户无法返回,适合登录跳转等场景。
因此该规则给出如下推荐用法:
// ❌ - 属性赋值,语义较弱 location.href = url; // ✅ - 方法调用更显式 location.assign(url); // ✅ - 正常导航,用户可返回 const loginUrl = 'https://example.com/login'; location.assign(loginUrl); // ✅ - 重定向场景用 replace(),用户不应返回 if (isLoggedOut) { location.replace('/login'); // 无法返回受保护页面 } // ✅ - 用户主动导航用 assign() button.onclick = () => location.assign('/next-page'); // ✅ - 认证重定向用 replace() if (!hasValidToken) { location.replace('/login'); }该规则同时被纳入 ✅recommended配置(在 ☑️unopinionated配置中禁用),并可通过 ESLint 的--fix命令行选项自动修复。
快照逐例解读:13 个非法用例的行为全景
快照中的每个invalid(n)用例都包含三段信息:Input(输入代码)、Error(报错位置与消息)、Output(修复后的代码)。所有用例共享同一条错误消息:
Prefer `Location#assign()` over assigning to `Location#href`.下面按行为特征把 13 个用例分组解读。
组 1:三种全局访问形式的直接修复(invalid 1-3)
| 用例 | 输入 | 自动修复输出 |
|---|---|---|
| invalid(1) | location.href = url; | location.assign(url); |
| invalid(2) | window.location.href = url; | window.location.assign(url); |
| invalid(3) | globalThis.location.href = url; | globalThis.location.assign(url); |
这三个用例验证规则能识别location、window.location、globalThis.location三种等价的全局 Location 对象访问形式,并且都能被自动修复。修复逻辑位于 rules/prefer-location-assign.js:使用 fixer 将整个赋值表达式替换为${对象文本}.assign(${右侧表达式文本})。
组 2:计算属性访问的识别(invalid 4-5)
| 用例 | 输入 | 自动修复输出 |
|---|---|---|
| invalid(4) | location["href"] = url; | location.assign(url); |
| invalid(5) | location[\href`] = url;|location.assign(url);` |
规则不仅匹配点号属性location.href,还匹配计算属性location["href"]与模板字符串形式的location[\href`]。源码中的getStaticPropertyName([rules/prefer-location-assign.js](https://link.gitcode.com/i/9a7e1692801b30b43c29dbc256f8d76d#L17-L25))先判断非计算成员表达式的Identifier属性名,否则回退到getStaticStringValue(property)(见 [rules/ast/literal.js](https://link.gitcode.com/i/adff9d3223f77d903426196f5353b186))求取静态字符串值,从而把"href"和 ``href`` 都归一化为href` 进行比对。
组 3:const 别名场景——只报告、不修复(invalid 6-9)
| 用例 | 输入 | 是否自动修复 |
|---|---|---|
| invalid(6) | const target = location; target.href = url; | ❌ 仅报告 |
| invalid(7) | const target = location; target["href"] = url; | ❌ 仅报告 |
| invalid(8) | const target = window.location; target.href = url; | ❌ 仅报告 |
| invalid(9) | const target = globalThis.location; target.href = url; | ❌ 仅报告 |
快照显示这四例只有报错,没有Output修复结果。这与源码的修复守卫条件一一对应:getProblem中要求自动修复必须同时满足三个条件(rules/prefer-location-assign.js):
assignmentExpression.operator === '='(纯赋值,非复合赋值);isValueNotUsable(assignmentExpression),即赋值表达式直接作为表达式语句、返回值未被使用(见 rules/utils/is-value-not-usable.js);isDirectLocationObject(node.object, sourceCode)——必须是直接访问的 Location 对象,通过 const 别名间接引用时不修复。
之所以不修复别名场景,是因为target可能被重新赋值或复用,贸然改写为target.assign(url)无法保证target一定指向 Location 对象。规则的变量追踪逻辑体现为三层函数:
isDirectLocationObject(rules/prefer-location-assign.js):识别裸location、window.location、globalThis.location,且通过isUnshadowedGlobalIdentifier确认未被局部变量遮蔽(利用@eslint-community/eslint-utils的findVariable判断变量未定义或定义在全局作用域且无defs);getConstantInitializer(第 55-69 行):仅当标识符对应的defs[0]是const变量时,才取回其初始化表达式;isConstantLocationAlias(第 71-74 行):初始化表达式本身是直接 Location 对象,则视为别名。
组 4:赋值结果被使用——只报告、不修复(invalid 10)
const result = location.href = url;该例中赋值表达式是const result = ...的初始化器,其返回值被变量result使用,因此isValueNotUsable为假,触发"仅报告不修复"的保守策略——强行改为location.assign(url)会丢失赋值表达式的返回值语义。
组 5:复合赋值——只报告、不修复(invalid 11)
location.href += hash;+=复合赋值存在取值-运算-赋值三步语义,直接替换成location.assign(location.href + hash)并不等价,故规则只报告(operator 不等于=的守卫条件生效),引导开发者手动改写。
组 6:带注释的赋值——只报告、不修复(invalid 12-13)
location.href = /* comment */ url; location.href = url /* comment */;快照显示这两例同样只报告、不修复。源码通过hasComments(rules/prefer-location-assign.js)调用sourceCode.getCommentsInside(node)检测注释,若赋值语句内部存在注释则放弃自动修复,以免在自动改写时丢失开发者注释。
从快照反推源码:检测流程与修复链路
综合快照行为与 rules/prefer-location-assign.js 的实现,规则的完整工作链路如下:
- 监听
AssignmentExpression节点(第 117-126 行); - 判断
left是否为MemberExpression,且静态属性名为href、对象是 Location 对象(isLocationHref,第 80-83 行); - 命中后进入
getProblem:默认报告指向node.property(即被高亮的href部分,与快照中^^^^标注位置一致); - 只有同时满足"纯等号赋值、结果未被使用、直接 Location 对象、无注释"四个条件才附加
fix,否则仅报告; - 修复时保留对象原样文本与右侧表达式原样文本,拼装成方法调用。
值得一提的是,invalid(10)中const result = location.href = url;的报错位置快照显示为^^^^指向location.href的href部分,说明规则即使不修复也会在属性节点上精准定位,方便开发者阅读。
测试配套与运行方式
快照由 test/prefer-location-assign.js 中的ruleTest.snapshot({...})生成,其中还包含一组valid用例,从反面圈定规则边界,例如:
- 只读访问不报告:
location.href;、const url = location.href; - 已使用
assign()不报告:location.assign(url); - 非 Location 对象不报告:
element.href = url; - 动态计算属性不报告:
location[path] = url;、location[href] = url; - 非赋值操作不报告:
delete location.href;、location.href++; - 被局部变量遮蔽的全局名不报告:
const location = {}; location.href = url;(含window、globalThis同名遮蔽) let声明的别名不报告:let target = location; target.href = url;
这些 valid 用例与快照中的 invalid 用例互为印证,共同构成规则的完整行为边界。此外测试文件还包含一个独立用例works with the recommended config without browser globals,验证在没有声明浏览器全局变量的环境下,规则在unicorn.configs.recommended配置中依然能工作;其中const target = window.location; target.href = url;被修复为const target = globalThis.location; target.href = url;(注意这里仅是变量初始化器被改写,别名赋值本身仍不修复)。
在仓库中运行该规则的测试可使用 AVA:npx ava test/prefer-location-assign.js(快照变更后运行npx ava --update-snapshots可更新prefer-location-assign.js.snap与对应的.md快照报告)。若需要更新规则文档中的自动生成头部,可执行npm run fix:eslint-docs(见 docs/rules/prefer-location-assign.md 的说明)。
总结:快照带给使用者的启示
通过这份快照,可以提炼出prefer-location-assign规则在实际项目中的落地要点:
- 常见写法可直接一键修复:
location.href = url、window.location.href = url、globalThis.location.href = url以及"href"/`href`计算属性形式,--fix都能安全改写为location.assign(...); - 保守场景只提示不自动改:const 别名、复合赋值(
+=)、赋值结果被复用、以及含有注释的赋值,都需要开发者手动确认; - 历史记录语义要靠开发者自行把握:规则只推荐
assign(),若场景是需要覆盖历史条目的重定向,应遵循 docs/rules/prefer-location-assign.md 的建议改用手写location.replace(...); - 全局遮蔽防护完善:任何把
location/window/globalThis声明为局部变量的代码都不会被误报。
快照文档不仅是测试产物,更是一份可读性极高的"行为规格说明书"——读懂它,就等于完整理解了这条规则在真实代码库中的全部表现。
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考