skills/check-test-code-quality/rules/R002/SKILL.md
# R002: 错误码断言必须是number类型 ## 规则元信息 | 字段 | 值 | |------|------| | 规则编号 | R002 | | 规则名称 | 错误码断言必须是number类型 | | 严重级别 | Critical | | 规则分类 | simple rule(简单规则,可用grep/正则直接检测) | | 扫描范围 | 所有源代码文件(`.ets`, `.ts`, `.js`) | ## 问题描述 `error.code` 的类型为 `number`,但测试代码中经常使用 **string字面量** 对其进行断言或比较。这属于类型不匹配的低级错误。 **核心原因**: 错误码是数字类型(如 `401`、`14000011`),使用字符串(如 `"401"`、`'14000011'`)进行断言/比较是类型错误。 ## 修复方法 将所有 `error.code` 相关的 string 断言/比较改为 number 类型。 ## 检测模式 ### Pattern 1: `assertEqual` 中的 string 字面量(最常见) ```
npx skillsauth add openharmonyinsight/openharmony-skills skills/check-test-code-quality/rules/R002Install this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| 字段 | 值 |
|------|------|
| 规则编号 | R002 |
| 规则名称 | 错误码断言必须是number类型 |
| 严重级别 | Critical |
| 规则分类 | simple rule(简单规则,可用grep/正则直接检测) |
| 扫描范围 | 所有源代码文件(.ets, .ts, .js) |
error.code 的类型为 number,但测试代码中经常使用 string字面量 对其进行断言或比较。这属于类型不匹配的低级错误。
核心原因: 错误码是数字类型(如 401、14000011),使用字符串(如 "401"、'14000011')进行断言/比较是类型错误。
将所有 error.code 相关的 string 断言/比较改为 number 类型。
assertEqual 中的 string 字面量(最常见)// 错误
expect(error.code).assertEqual("401");
expect(error.code).assertEqual('14000011');
// 正确
expect(error.code).assertEqual(401);
expect(error.code).assertEqual(14000011);
expect 中的 string 参数// 错误
expect(error.code, "401");
// 正确
expect(error.code, 401);
assertTrue 中的 string 比较// 错误
expect(error.code === '14000011').assertTrue();
// 正确
expect(error.code === 14000011).assertTrue();
if 条件中的 string 比较(== 和 ===)// 错误
if (error.code == "801") { ... } // 宽松相等
if (error.code === "401") { ... } // 严格相等
// 正确
if (error.code == 801) { ... }
if (error.code === 401) { ... }
// 错误 - 变量定义为string类型
let codeNum = "401";
expect(error.code).assertEqual(codeNum);
// 正确 - 变量定义为number类型
let codeNum = 401;
expect(error.code).assertEqual(codeNum);
// 错误 - 函数参数类型标注为string
const requestVideoDestUriError = async (testNum: string, done: Function, destUri: string, errCode: string) => {
try {
// ...
} catch (error) {
expect(error.code).assertEqual(errCode); // errCode是string类型
done();
}
};
// 正确 - 函数参数类型标注为number
const requestVideoDestUriError = async (testNum: string, done: Function, destUri: string, errCode: number) => {
try {
// ...
} catch (error) {
expect(error.code).assertEqual(errCode); // errCode是number类型
done();
}
};
const 定义的 string 常量// 错误
const ERROR_PArameter = '23800151';
expect(error.code).assertEqual(ERROR_PArameter);
// 正确
const ERROR_PArameter = 23800151;
expect(error.code).assertEqual(ERROR_PArameter);
import re
# 精确模式1: expect(xxx.code).assertEqual('string') - string字面量
r002_assertEqual_pattern = re.compile(
r'expect\s*\(\s*\w+\.code\s*\)\s*\.\s*assertEqual\s*\(\s*[\'"]'
)
# 精确模式2: expect(xxx.code, "string") - expect中的string参数
r002_expect_pattern = re.compile(
r'expect\s*\(\s*\w+\.code\s*,\s*[\'"]'
)
# 精确模式3: expect(xxx.code === 'string').assertTrue() - string比较在assertTrue中
r002_assertTrue_pattern = re.compile(
r'expect\s*\(\s*\w+\.code\s*===?\s*[\'"]'
)
# 精确模式4: if (xxx.code == 'string') 或 if (xxx.code === 'string') - if条件中的string比较
# 使用 ===? 匹配 == 和 ===,避免同一行被两个正则重复报告
r002_if_pattern = re.compile(
r'if\s*\(\s*\w+\.code\s*===?\s*[\'"]'
)
# 追踪string类型变量定义
r002_var_def_pattern = re.compile(
r'(?:let|const|var)\s+(\w+)\s*=\s*[\'"]\d+[\'"]'
)
# 追踪string类型函数参数
r002_param_pattern = re.compile(
r'(\w+)\s*:\s*string'
)
# 追踪string类型常量(文件顶部const定义)
r002_const_pattern = re.compile(
r'const\s+(\w+)\s*=\s*[\'"]\d+[\'"]'
)
err.codeerr.code 只有在 err 是 error 的别名时才应报告。需要通过上下文判断:
// 情况A: err是error的别名(应报告)
} catch (err) {
expect(err.code).assertEqual("401"); // 报告
}
// 情况B: err是其他变量(不应报告)
let err = getSomeObject();
expect(err.code).assertEqual("401"); // err.code 不是 error.code,不报告
处理策略: 如果 catch 块的参数名为 err,则 err 是 error 的别名,应报告。
console.log 中的用法// 不应报告
console.log(`error.code: ${error.code}`); // 日志输出,不是断言
console.log("error.code is " + error.code);
// 不应报告
let code = error.code; // 赋值,不是断言
const myCode = error.code.toString(); // 转换,不是断言
如果 string 字面量不是纯数字,则不是错误码断言,不应报告:
// 不应报告 - "PARAM_ERROR" 不是错误码数字
expect(error.code).assertEqual("PARAM_ERROR");
expect(error.code).assertEqual("business_error");
处理策略: 只匹配 assertEqual 后面的引号内容为纯数字的情况。可以用正则进一步验证:
r002_assertEqual_with_number = re.compile(
r'expect\s*\(\s*\w+\.code\s*\)\s*\.\s*assertEqual\s*\(\s*[\'"](\d+)[\'"]'
)
import os
def is_test_file(filepath):
"""检查是否为测试文件"""
test_extensions = ('.test.ets', '.test.ts', '.test.js')
return any(filepath.endswith(ext) for ext in test_extensions)
def scan_r002_line(line, line_num, string_vars=None):
"""
扫描单行代码中的R002问题。
Args:
line: 当前行内容
line_num: 行号
string_vars: 已知的string类型变量集合(来自变量追踪)
Returns:
list[dict]: 发现的问题列表
"""
issues = []
# 跳过注释行
stripped = line.strip()
if stripped.startswith('//') or stripped.startswith('*') or stripped.startswith('/*'):
return issues
# 跳过console.log
if 'console.log' in line or 'console.info' in line or 'console.warn' in line or 'console.error' in line:
return issues
# 跳过赋值语句(左侧有 = 且不是 ==)
if re.match(r'\s*(?:let|const|var)\s+', line):
return issues
# Pattern 1: expect(xxx.code).assertEqual('...')
m = r002_assertEqual_with_number.search(line)
if m:
issues.append({
'rule': 'R002',
'type': '错误码断言必须是number类型',
'severity': 'Critical',
'line': line_num,
'snippet': stripped,
'suggestion': f'error.code 是 number 类型,断言值 "{m.group(1)}" 应改为数字: {m.group(1)}'
})
return issues
# Pattern 2: expect(xxx.code, '...')
m = r002_expect_pattern.search(line)
if m:
issues.append({
'rule': 'R002',
'type': '错误码断言必须是number类型',
'severity': 'Critical',
'line': line_num,
'snippet': stripped,
'suggestion': 'error.code 是 number 类型,expect() 中的比较值应使用数字类型'
})
return issues
# Pattern 3: expect(xxx.code === '...').assertTrue()
m = r002_assertTrue_pattern.search(line)
if m:
issues.append({
'rule': 'R002',
'type': '错误码断言必须是number类型',
'severity': 'Critical',
'line': line_num,
'snippet': stripped,
'suggestion': 'error.code 是 number 类型,比较值应使用数字类型'
})
return issues
# Pattern 4: if (xxx.code == '...') 或 if (xxx.code === '...')
m = r002_if_pattern.search(line)
if m:
issues.append({
'rule': 'R002',
'type': '错误码断言必须是number类型',
'severity': 'Critical',
'line': line_num,
'snippet': stripped,
'suggestion': 'error.code 是 number 类型,if 条件中的比较值应使用数字类型'
})
return issues
return issues
def scan_r002_file(filepath, content):
"""
扫描整个文件中的R002问题,包含变量追踪。
Args:
filepath: 文件路径
content: 文件内容
Returns:
list[dict]: 发现的问题列表
"""
issues = []
lines = content.split('\n')
# 第一遍:收集string类型变量和常量定义
string_vars = set()
for line in lines:
# let/const/var xxx = "401"
m = re.search(r'(?:let|const|var)\s+(\w+)\s*=\s*[\'"](\d+)[\'"]', line)
if m:
string_vars.add(m.group(1))
# 第二遍:收集string类型函数参数
for line in lines:
params = re.findall(r'(\w+)\s*:\s*string', line)
string_vars.update(params)
# 第三遍:逐行检测
for i, line in enumerate(lines, 1):
line_issues = scan_r002_line(line, i, string_vars)
issues.extend(line_issues)
# 额外检测:变量追踪模式
# expect(error.code).assertEqual(codeNum) 其中 codeNum 是 string 类型
if string_vars:
m = re.search(r'expect\s*\(\s*(\w+)\.code\s*\)\s*\.\s*assertEqual\s*\(\s*(\w+)\s*\)', line)
if m and m.group(2) in string_vars:
issues.append({
'rule': 'R002',
'type': '错误码断言必须是number类型',
'severity': 'Critical',
'line': i,
'snippet': line.strip(),
'suggestion': f'变量 "{m.group(2)}" 是 string 类型,应改为 number 类型'
})
# if (error.code == codeNum) 其中 codeNum 是 string 类型
m = re.search(r'if\s*\(\s*(\w+)\.code\s*==\s*(\w+)\s*\)', line)
if m and m.group(2) in string_vars:
issues.append({
'rule': 'R002',
'type': '错误码断言必须是number类型',
'severity': 'Critical',
'line': i,
'snippet': line.strip(),
'suggestion': f'变量 "{m.group(2)}" 是 string 类型,应改为 number 类型'
})
return issues
每条问题必须包含以下字段:
{
'rule': 'R002',
'type': '错误码断言必须是number类型',
'severity': 'Critical',
'file': 'path/to/file.test.ets',
'line': 25,
'testcase': 'testFuncName', # 所属用例名称(it()的第一个参数),无对应用例时为 '-'
'snippet': 'expect(error.code).assertEqual("401")',
'suggestion': 'error.code 是 number 类型,断言值 "401" 应改为数字: 401'
}
it() 块,取 it(' 后面的第一个字符串参数testcase 为 -it() 块内的问题:testcase 为 -// 错误1: assertEqual中使用string字面量
it('test001', Level.LEVEL0, () => {
try {
// ...
} catch (error) {
expect(error.code).assertEqual("401"); // R002
}
});
// 错误2: assertTrue中的string比较
it('test002', Level.LEVEL0, () => {
expect(error.code === '14000011').assertTrue(); // R002
});
// 错误3: if条件中的宽松string比较
it('test003', Level.LEVEL0, () => {
if (error.code == "801") { // R002
// ...
}
});
// 错误4: if条件中的严格string比较
it('test004', Level.LEVEL0, () => {
if (error.code === "401") { // R002
// ...
}
});
// 错误5: string变量间接断言
it('test005', Level.LEVEL0, () => {
let codeNum = "401";
if (error.code == codeNum) { // R002
expect(error.code).assertEqual(codeNum); // R002
}
});
// 错误6: string类型函数参数
const requestVideoDestUriError = async (testNum: string, done: Function, destUri: string, errCode: string) => {
try {
// ...
} catch (error) {
expect(error.code).assertEqual(errCode); // R002 - errCode是string类型
done();
}
};
// 错误7: const定义的string常量
const ERROR_PArameter = '23800151';
it('test007', Level.LEVEL0, () => {
expect(error.code).assertEqual(ERROR_PArameter); // R002
});
// 错误8: err是error的别名
it('test008', Level.LEVEL0, () => {
try {
// ...
} catch (err) {
expect(err.code).assertEqual("401"); // R002 - err是error的别名
}
});
// 正确1: 使用number字面量
it('test001', Level.LEVEL0, () => {
try {
// ...
} catch (error) {
expect(error.code).assertEqual(401); // number类型
}
});
// 正确2: number比较
it('test002', Level.LEVEL0, () => {
expect(error.code === 14000011).assertTrue(); // number比较
});
// 正确3: number条件判断
it('test003', Level.LEVEL0, () => {
if (error.code == 801) { // number比较
// ...
}
});
// 正确4: number变量
it('test004', Level.LEVEL0, () => {
let codeNum = 401;
if (error.code == codeNum) {
expect(error.code).assertEqual(codeNum);
}
});
// 正确5: number类型函数参数
const requestVideoDestUriError = async (testNum: string, done: Function, destUri: string, errCode: number) => {
try {
// ...
} catch (error) {
expect(error.code).assertEqual(errCode); // errCode是number类型
done();
}
};
// 正确6: number常量
const ERROR_PArameter = 23800151;
it('test006', Level.LEVEL0, () => {
expect(error.code).assertEqual(ERROR_PArameter); // number类型
});
// 场景1: console.log - 日志输出不是断言
console.log(`error.code: ${error.code}`);
console.log("error code is " + error.code);
// 场景2: 赋值语句
let code = error.code;
const myCode = error.code.toString();
// 场景3: err不是error的别名
let err = getSomeObject();
expect(err.code).assertEqual("some-code"); // err.code 不是 error.code
// 场景4: 非纯数字的string字面量
expect(error.code).assertEqual("PARAM_ERROR"); // 不是数字错误码
// 场景5: 注释行
// expect(error.code).assertEqual("401"); // 注释中的代码
development
Run local code quality checks covering a subset of OpenHarmony gate CI (copyright, CodeArts C/C++) plus additional local checks (pylint/flake8, shellcheck/bashate, gn format). Use before committing to reduce gate failures. Triggers on: /oh-precommit-codecheck, "门禁检查", "门禁预检", "检查代码", "run codecheck", "check code quality", "lint my code", "代码检查", or after completing code implementation. WHEN to use: before git commit, before creating PR, after modifying C/C++/Python/Shell/GN files, when gate CI fails with codecheck defects, or when you want to preview what gate will flag.
development
OpenHarmony PR full lifecycle workflow. Five modes: - Commit: standardized commit with DCO sign-off and Issue linking - Create PR: commit + push to fork + create Issue + create PR on upstream - Fix Codecheck: fetch gate CI codecheck defects from a PR and auto-fix them - Review PR: fetch a PR's changes to local for code review - Fix Review: fetch unresolved review comments from a PR and auto-fix them Triggers on: /oh-pr-workflow, "提交代码", "创建PR", "提个PR", "commit", "修复告警", "修复门禁", "修复codecheck", "fix codecheck", "review pr", "review这个pr", "看下这个pr", "检视pr", "修复review", "修复检视意见", "fix review", or a GitCode PR URL with fix/review intent.
testing
分析 HM Desktop PRD 文档,提取需求信息、验证完整性、检查章节顺序(需求来源→需求背景→需求价值分析→竞品分析→需求描述)、检查 KEP 定义、检测需求冲突并生成结构化分析报告。适用于用户请求:(1) 分析或审查 PRD 文档, (2) 从需求中提取 KEP 列表, (3) 检查 PRD 完整性或一致性, (4) 将需求映射到模块架构, (5) 验证 PRD 格式合规性, (6) 验证竞品分析章节完整性。关键词:PRD分析, requirement extraction, KEP验证, completeness check, chapter order validation, 竞品分析检查, analyze PRD, 需求提取, 完整性检查, 章节顺序验证
development
基于 PRD 文档自动生成鸿蒙系统设计文档,包括架构设计文档和功能设计文档。生成前会分析 OpenHarmony 存量代码结构,确保与现有架构兼容。架构设计文档第2章必须为竞品方案分析,位于需求背景之后。适用于用户请求:(1) 生成架构设计文档, (2) 生成功能设计文档, (3) 从 PRD 生成设计文档, (4) 创建系统架构设计, (5) 编写功能规格说明, (6) 分析 OH 代码结构。关键词:architecture design, functional design, design doc, 竞品方案分析, OpenHarmony code analysis, 架构设计, 功能设计, 设计文档生成, OH代码分析, analyze codebase, competitor analysis