skills/check-test-code-quality/rules/R006/SKILL.md
# R006: 禁止基于设备类型差异化 ## 规则信息 | 属性 | 值 | |------|------| | 规则编号 | R006 | | 问题类型 | 禁止基于设备类型差异化 | | 严重级别 | Critical | | 规则复杂度 | simple | ## 问题描述 在条件判断中使用 `deviceInfo.deviceType` 或从其赋值的变量进行设备类型判断,导致XTS测试无法在所有设备上正确执行。应使用 `SystemCapability` 和 `canIUse` 进行能力判断。 **规范来源**: 用例低级问题.md 第8条 — "禁止基于deviceInfo.deviceType差异化" ## 扫描范围 **⚠️ 关键陷阱(陷阱2)**: R006必须扫描**所有源代码文件**,不是测试文件! | 应扫描 | 文件扩展名 | |--------|-----------| | **所有源代码文件** | `.ets`, `.ts`, `.js` | **错误做法**: 只扫描 `.test.ets` 文件 → 漏报部分问题 **正确做法**:
npx skillsauth add openharmonyinsight/openharmony-skills skills/check-test-code-quality/rules/R006Install 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.
| 属性 | 值 | |------|------| | 规则编号 | R006 | | 问题类型 | 禁止基于设备类型差异化 | | 严重级别 | Critical | | 规则复杂度 | simple |
在条件判断中使用 deviceInfo.deviceType 或从其赋值的变量进行设备类型判断,导致XTS测试无法在所有设备上正确执行。应使用 SystemCapability 和 canIUse 进行能力判断。
规范来源: 用例低级问题.md 第8条 — "禁止基于deviceInfo.deviceType差异化"
⚠️ 关键陷阱(陷阱2): R006必须扫描所有源代码文件,不是测试文件!
| 应扫描 | 文件扩展名 |
|--------|-----------|
| 所有源代码文件 | .ets, .ts, .js |
错误做法: 只扫描 .test.ets 文件 → 漏报部分问题
正确做法: 使用 get_all_source_files() 获取所有 .ets/.ts/.js 文件
首先扫描文件中是否导入了deviceInfo相关模块:
import re
def has_deviceinfo_import(content: str) -> bool:
patterns = [
r"import\s+.*deviceInfo\s+.*from\s+['\"]@ohos\.deviceInfo['\"]",
r"import\s+\{[^}]*deviceInfo[^}]*\}\s+from\s+['\"]@kit\.BasicServicesKit['\"]",
r"import\s+.*deviceInfo\s+.*from\s+['\"]@kit\.BasicServicesKit['\"]",
r"from\s+['\"]@ohos\.deviceInfo['\"]",
]
for p in patterns:
if re.search(p, content):
return True
return False
在条件判断中直接使用 deviceInfo.deviceType:
def check_direct_device_type(lines: list[str], has_import: bool) -> list[dict]:
issues = []
if not has_import:
return issues
for i, line in enumerate(lines, 1):
# 排除: console.log/info/debug/error/warn
if re.search(r'console\.\s*(log|info|debug|error|warn)', line):
continue
# 排除: 纯赋值语句(不包含条件判断关键字)
stripped = line.strip()
if re.match(r'^(let|const|var|this\.)', stripped) and not re.search(r'\b(if|else|switch|case|return|&&|\|\||\?)\b', stripped):
continue
# 检测: deviceInfo.deviceType 出现在条件上下文中
if re.search(r'\bdeviceInfo\.deviceType\b', line):
# 检查是否在条件表达式中
if re.search(r'(if\s*\(|else\s*if\s*\(|switch\s*\(|case\s+|\?\s*|&&|\|\||==|!=|\breturn\b)', line):
issues.append({
'line': i,
'snippet': line.strip(),
'type': 'direct'
})
return issues
检测从 deviceInfo.deviceType 赋值的变量是否在条件判断中使用:
def check_variable_device_type(lines: list[str], has_import: bool) -> list[dict]:
issues = []
if not has_import:
return issues
# 收集从deviceInfo.deviceType赋值的变量名
assigned_vars = set()
var_pattern = re.compile(
r'(?:let|const|var)\s+(\w+)\s*(?::\s*string)?\s*=\s*deviceInfo\.deviceType'
)
for line in lines:
m = var_pattern.search(line)
if m:
assigned_vars.add(m.group(1))
if not assigned_vars:
return issues
# 检测这些变量是否在条件判断中使用
for i, line in enumerate(lines, 1):
# 排除: console.log/info/debug/error/warn
if re.search(r'console\.\s*(log|info|debug|error|warn)', line):
continue
for var_name in assigned_vars:
# 排除: 纯属性访问(如 deviceTypeInfo.length)
if re.search(rf'\b{re.escape(var_name)}\.\w+\s*$', line.strip()):
continue
# 排除: 纯赋值右侧
if re.match(rf'^\s*(?:let|const|var)\s+{re.escape(var_name)}\s*=', line):
continue
# 检测: 变量出现在条件上下文中
if re.search(rf'\b{re.escape(var_name)}\b', line):
if re.search(r'(if\s*\(|else\s*if\s*\(|switch\s*\(|case\s+|\?\s*|&&|\|\||==|!=)', line):
issues.append({
'line': i,
'snippet': line.strip(),
'type': 'variable',
'var_name': var_name
})
return issues
def check_r006(file_path: str, content: str) -> list[dict]:
lines = content.split('\n')
has_import = has_deviceinfo_import(content)
direct_issues = check_direct_device_type(lines, has_import)
variable_issues = check_variable_device_type(lines, has_import)
all_issues = direct_issues + variable_issues
# 去重(同一行不重复报告)
seen_lines = set()
unique_issues = []
for issue in all_issues:
if issue['line'] not in seen_lines:
seen_lines.add(issue['line'])
unique_issues.append(issue)
return unique_issues
def find_testcase_for_line(lines: list[str], target_line: int, filepath: str) -> str:
if not is_test_file(filepath):
return '-'
for i in range(target_line - 1, -1, -1):
match = re.search(r"\bit\s*\(\s*['\"]([^'\"]+)['\"]", lines[i])
if match:
return match.group(1)
return '-'
| 列名 | 说明 |
|------|------|
| 问题ID | R006 |
| 问题类型 | 禁止基于设备类型差异化 |
| 严重级别 | Critical |
| 文件路径 | 相对路径 |
| 行号 | 问题所在行号 |
| 所属用例 | it(' 后的参数,非测试文件为 - |
| 代码片段 | 匹配到的代码行 |
| 修复建议 | 路径+行号+问题描述 |
{
'rule': 'R006',
'type': '禁止基于设备类型差异化',
'severity': 'Critical',
'file': relative_file_path,
'line': line_number,
'testcase': testcase_name,
'snippet': matched_line.strip(),
'suggestion': f'路径: {relative_file_path}, 行号: {line_number}, 问题描述: 在条件判断中使用了deviceInfo.deviceType,应使用SystemCapability和canIUse进行能力判断'
}
// 错误1: 直接使用deviceInfo.deviceType
import deviceInfo from '@ohos.deviceInfo';
export default function test() {
describe('test', () => {
it('test001', () => {
if (deviceInfo.deviceType == 'default') { // ✗ 错误:基于设备类型判断
// default 设备的测试逻辑
} else {
// 其他设备的测试逻辑
}
});
});
}
// 错误2: 使用deviceTypeInfo变量
import deviceInfo from '@ohos.deviceInfo';
export default function test() {
describe('test', () => {
it('test001', () => {
let deviceTypeInfo: string = deviceInfo.deviceType;
if (deviceTypeInfo == 'default') { // ✗ 错误:基于设备类型判断
// default 设备的测试逻辑
}
});
});
}
// 错误3: 从@kit.BasicServicesKit导入
import { deviceInfo } from '@kit.BasicServicesKit';
it('MenuStyleOptionsTest_0101', 0, async (done: Function) => {
let deviceTypeInfo: string = deviceInfo.deviceType
if (deviceTypeInfo == 'default') { // ✗ 错误:基于设备类型判断
let text = JSON.stringify(...);
expect(text).assertEqual('1');
}
done();
});
// 正确1: 仅打印日志,不影响测试逻辑
import deviceInfo from '@ohos.deviceInfo';
export default function test() {
describe('test', () => {
it('test001', () => {
console.info('Device type = ' + deviceInfo.deviceType); // ✓ 正确:仅打印日志
});
});
}
// 正确2: 使用SystemCapability进行能力判断
export default function test() {
describe('test', () => {
it('test001', () => {
if (canIUse("SystemCapability.xxx")) { // ✓ 正确:基于能力判断
// 基于能力的测试逻辑
}
});
});
}
以下场景不报告问题:
console.info('Device type = ' + deviceInfo.deviceType) — 仅打印,不影响逻辑let deviceTypeInfo: string = deviceInfo.deviceType — 赋值本身不构成条件判断deviceTypeInfo.length — 对变量属性的访问不构成条件判断# 快速扫描直接使用
grep -rn 'deviceInfo\.deviceType' --include='*.ets' --include='*.ts' --include='*.js' /path/to/code
# 快速扫描变量形式
grep -rn 'deviceType' --include='*.ets' --include='*.ts' --include='*.js' /path/to/code
严重性: 严重,曾导致大量误报(window目录R006问题数从46膨胀到数百)
问题: 使用 deviceType == 或 deviceInfo.deviceType 等宽泛正则时,会将单纯的属性访问和日志打印误报为条件判断。
误报案例:
// 误报1: 纯赋值语句 — 不构成条件判断
let deviceType: string = deviceInfo.deviceType; // ✗ 误报!这只是赋值
// 误报2: console日志打印 — 不影响测试逻辑
console.info(`====>${caseName} end fail====` + deviceInfo.deviceType); // ✗ 误报!只是打印
正确应报案例:
// 正确报告: 条件判断中使用设备类型
if (deviceType === '2in1' || (deviceType === 'tablet' && isFreeWindowMode === true) ||
(deviceType === 'phone' && isFreeWindowMode === true)) { // ✓ 应报告
if (deviceInfo.deviceType === '2in1') { // ✓ 应报告
} else if (deviceInfo.deviceType === 'tablet') { // ✓ 应报告
} else if (deviceInfo.deviceType === 'phone') { // ✓ 应报告
修复: 必须同时满足两个条件才报告:
deviceType 或 deviceInfo.deviceTypeif、else if、switch)或三元运算符(?)并且必须排除以下场景:
console.info/log/warn/error/debug(...) 日志打印行let/const/var deviceType = deviceInfo.deviceType 纯赋值行# 错误做法(导致大量误报)
if re.search(r'\bdeviceType\s*==', line): # 匹配所有 == 比较,包括非条件上下文
report_issue()
# 正确做法(先排除非条件行,再检测条件判断)
if re.search(r'console\.\s*(log|info|debug|error|warn)', line):
continue # 跳过日志打印
if re.match(r'^\s*(let|const|var)\s+.*deviceType\s*=\s*deviceInfo\.deviceType', line):
continue # 跳过纯赋值
if not re.search(r'\b(?:if|else\s+if|while|switch)\b', line) and '?' not in line:
continue # 跳过非条件行
# 然后才检测 deviceType 条件判断
if re.search(r'\bdeviceType\s*[!=]==\s*[\'"](?:tablet|phone|2in1|pad|pc|tv|wearable|car|default)[\'"]', line):
report_issue()
影响: R006规则,所有子系统的扫描结果
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