@sheng/no-native-string-user-text-ops
禁止在用户可见文本里直接使用原生字符串长度 / 截断 API,避免 UTF-16 code unit 计数和切片误伤 emoji、国旗和组合字符。
所属 config
unicode-user-text:用户可见文本的 Unicode 字符数和截断护栏。
背景
JavaScript 的 String.length 统计的是 UTF-16 code unit,不是用户眼里的“一个字”。这会让用户可见文本出现明显错误:
- 一个 emoji 可能被算成 2。
- 国旗由两个 regional indicator 组成,
length可能是 4。 - ZWJ 家庭 emoji、肤色修饰 emoji、组合音标、Indic conjunct 等会被拆成多个 code unit 或 code point。
slice()/substring()可能把一个用户感知字符切断,导致显示乱码或半个 emoji。
项目里资料昵称、签名、举报原因、输入框计数等都应该按用户感知字符处理,而不是按 UTF-16 code unit 处理。
这条规则就是为了防止新增代码继续使用原生字符串长度 / 截断能力处理用户可见文本。
检查范围
规则主要检查三类场景:
- 对用户可见文本读
.length。 - 对用户可见文本调用
charAt、slice、substring、substr。 - 在
<input>/<textarea>上使用原生maxlength限制用户可见文本。
规则不是“所有字符串都不能用原生 API”。它会结合变量名、函数名、调用链、TypeScript 类型和 Vue ref 类型判断目标是不是用户可见文本。
会提示
const nickname = ref('👨👩👧👦')
const count = nickname.value.length
const preview = nickname.value.slice(0, 10)<template>
<input v-model="nickname" maxlength="20" />
</template>推荐替代
统一使用 shared/utils/unicodeText/ 里的工具:
- 统计长度:
countUnicodeCharacters(value) - 判断空白:
isUnicodeTextBlank(value) - 判断超限:
isUnicodeTextOverLimit(value, max) - 截断:
truncateUnicodeCharacters(value, max) - 切片:
sliceUnicodeCharacters(value, start, end) - 保留
substring()起止交换语义:substringUnicodeCharacters(value, start, end) - 取单个用户感知字符:
getUnicodeCharacterAt(value, index)
如果只是限制输入最大长度,不要依赖原生 maxlength:
<script setup lang="ts">
function handleInput(event: Event) {
const input = event.target as HTMLInputElement
model.value = truncateUnicodeCharacters(input.value, 20)
}
</script>
<template>
<input :value="model" @input="handleInput" />
</template>技术字符串例外
以下语义通常是技术字符串,可以继续使用原生长度或切片:
uid/id/did- token / cookie / hash / md5 / uuid
- path / url / href
- phone / password / otp / code
- byte / buffer / base64 / hex
默认名单在 constants.js 里维护。项目特殊技术字段可以通过 technicalNamePatterns 扩展,例如 sku、serial、checksum。
用户文本识别
规则会优先识别这些用户可见文本语义:
- nickname
- remark
- description
- signature
- title
- content
- message
- label
- placeholder
- search / keyword / query
- reason / feedback
默认名单也在 constants.js 里维护。新增业务领域如果有稳定的用户文本命名,可以通过 userTextNamePatterns 扩展。
误报处理
如果某个场景确实需要原生 UTF-16 长度或原生字符串下标语义,允许局部禁用,但必须写清楚原因:
// eslint-disable-next-line @sheng/no-native-string-user-text-ops -- token 按协议固定字节/ASCII 长度校验,不是用户可见文本。
if (token.length !== TOKEN_LENGTH) return false不要为了省事在文件头关闭整条规则。
维护方式
- 新增被禁止的原生方法时,同时在
constants.js里补替代建议。 - 新增技术词 / 用户文本词时,优先补单测,确认不会造成大面积误报。
- 如果某个新输入组件不是
<input>/<textarea>,但也有原生最大长度限制,先补规则测试再扩展 AST 检查。
对应测试
测试文件是 tests/unit/architecture/no-native-string-user-text-ops-rule.test.ts。这条规则涉及 Unicode 边界,新增 case 时尽量把 emoji、国旗、组合字符和技术字符串例外都覆盖到。
相关阅读
这条规则对应中文文章 别再用 length 统计用户文本:一次 Unicode 字符数和 ESLint 护栏复盘。文章里的核心结论是:产品里的“一个字”更接近 Unicode grapheme cluster;JavaScript 原生字符串 API 统计的是 UTF-16 code unit,容易数错 emoji、国旗、组合字符,也可能把一个用户感知字符切断。
接入方式
@sheng/eslint-plugin 的内置 config 会以 warn 开启这条规则。需要单独配置时,可以这样写:
import sheng from '@sheng/eslint-plugin'
export default [
{
plugins: {
'@sheng': sheng,
},
rules: {
'@sheng/no-native-string-user-text-ops': 'warn',
},
},
]选项
这条规则支持 options,具体 schema 以规则实现和测试用例为准。补充或调整选项时,同步更新本页示例。
维护入口
- 规则源码:
src/rules/no-native-string-user-text-ops/index.mjs - 规则短说明:
src/rules/no-native-string-user-text-ops/README.md - 测试用例:
tests/unit/architecture/