R008: 用例声明格式不规范

SkillDev tools

A skill for dev tools by openharmonyinsight.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the R008: 用例声明格式不规范 skill

What this skill tells your AI

The instructions your AI receives, as published by openharmonyinsight/openharmony-skills in skills/check-test-code-quality/rules/R008/SKILL.md and read by ahel’s review.

规则信息

属性
规则编号R008
问题类型用例声明格式不规范
严重级别Warning
规则复杂度complex

问题描述

测试用例的文档注释格式不符合规范要求,包括缺少注释标记、分隔符错误、空行等问题。

规范来源: 用例低级问题.md 第15条 — "XTS上库用例声明需符合要求"

规范要求

  1. 文档注释以 /** 开头,以 */ 结尾,每行以 * 开始
  2. 参数名以 @ 修饰,参数名和参数值以(一或若干个)空格分隔,禁止使用其他分隔符
  3. 文档注释结束行的下一行应紧接要修饰的测试用例,禁止出现空行

扫描范围

应扫描文件扩展名
测试文件.test.ets, .test.ts, .test.js

检测逻辑

步骤1: 收集测试文件

def get_r008_scan_files(directory: str) -> list[str]:
    test_files = get_test_files(directory)  # .test.ets, .test.ts, .test.js
    return test_files

步骤2: 提取文档注释块

在测试文件中,找到所有位于 it( 调用之前的文档注释块:

import re

def extract_doc_blocks(content: str) -> list[dict]:
    lines = content.split('\n')
    doc_blocks = []

    i = 0
    while i < len(lines):
        line = lines[i]
        stripped = line.strip()

        # 查找 /** 开头的文档注释
        if stripped.startswith('/**'):
            block_start = i + 1  # 1-indexed
            block_lines = [line]
            j = i + 1

            # 收集注释块内容直到 */
            while j < len(lines):
                block_lines.append(lines[j])
                if '*/' in lines[j]:
                    break
                j += 1

            block_end = j + 1  # 1-indexed
            block_text = '\n'.join(block_lines)

            # 检查注释块后是否紧跟 it() 调用
            next_code_line_idx = j + 1
            testcase_name = None
            has_empty_line = False

            # 跳过空行
            while next_code_line_idx < len(lines) and lines[next_code_line_idx].strip() == '':
                has_empty_line = True
                next_code_line_idx += 1

            if next_code_line_idx < len(lines):
                next_line = lines[next_code_line_idx].strip()
                tc_match = re.search(r"\bit\s*\(\s*['\"]([^'\"]+)['\"]", next_line)
                if tc_match:
                    testcase_name = tc_match.group(1)

            doc_blocks.append({
                'start_line': block_start,
                'end_line': block_end,
                'lines': block_lines,
                'text': block_text,
                'testcase': testcase_name or '-',
                'has_empty_line_before_it': has_empty_line,
                'next_code_line': next_code_line_idx + 1 if next_code_line_idx < len(lines) else None
            })

            i = j + 1
        else:
            i += 1

    return doc_blocks

步骤3: 检测注释格式问题

对每个文档注释块,检测以下5种问题:

def check_doc_block(block: dict) -> list[dict]:
    issues = []
    lines = block['lines']
    testcase = block['testcase']

    # 问题1: 缺少 /** 开头或 */ 结尾
    first_line = lines[0].strip()
    last_line = lines[-1].strip()
    if not first_line.startswith('/**'):
        issues.append({
            'line': block['start_line'],
            'type': 'missing_opening',
            'snippet': first_line,
            'detail': '文档注释未以/**开头'
        })
    if '*/' not in last_line and (len(lines) == 1 or '*/' not in lines[-1]):
        issues.append({
            'line': block['end_line'],
            'type': 'missing_closing',
            'snippet': last_line,
            'detail': '文档注释未以*/结尾'
        })

    # 问题2: 每行未以 * 开始(跳过首行和末行)
    for idx, line in enumerate(lines[1:-1], start=block['start_line'] + 1):
        stripped = line.strip()
        if stripped and not stripped.startswith('*'):
            issues.append({
                'line': idx,
                'type': 'missing_asterisk',
                'snippet': stripped,
                'detail': '注释行未以*开始'
            })

    # 问题3: 参数名缺少 @ 修饰符
    for idx, line in enumerate(lines, start=block['start_line']):
        stripped = line.strip().lstrip('*').strip()
        # 匹配 tc.name, tc.number 等无 @ 前缀的参数
        no_at_match = re.match(r'^(tc\.\w+)\s+\S', stripped)
        if no_at_match:
            issues.append({
                'line': idx,
                'type': 'missing_at_modifier',
                'snippet': line.strip(),
                'detail': f'参数 {no_at_match.group(1)} 缺少@修饰符'
            })

    # 问题4: 使用冒号分隔符(应使用空格)
    for idx, line in enumerate(lines, start=block['start_line']):
        # 匹配 @tc.name : value 或 @tc.name: value 模式
        colon_match = re.search(r'@(tc\.\w+)\s*:\s', line)
        if colon_match:
            issues.append({
                'line': idx,
                'type': 'colon_separator',
                'snippet': line.strip(),
                'detail': f'参数 {colon_match.group(1)} 使用了冒号分隔符,应使用空格'
            })

    # 问题5: 注释结束行与测试用例之间存在空行
    if block['has_empty_line_before_it'] and block['testcase'] != '-':
        issues.append({
            'line': block['end_line'],
            'type': 'empty_line_before_it',
            'snippet': '*/ 后存在空行',
            'detail': '文档注释结束行与测试用例之间不应有空行'
        })

    return issues

步骤4: 输出问题

def check_r008(file_path: str, content: str) -> list[dict]:
    doc_blocks = extract_doc_blocks(content)
    all_issues = []

    for block in doc_blocks:
        issues = check_doc_block(block)
        for issue in issues:
            all_issues.append({
                'rule': 'R008',
                'type': '用例声明格式不规范',
                'severity': 'Warning',
                'file': file_path,
                'line': issue['line'],
                'testcase': block['testcase'],
                'snippet': issue['snippet'],
                'suggestion': (
                    f'路径: {file_path}, 行号: {issue["line"]}, '
                    f'问题描述: {issue["detail"]}'
                )
            })

    return all_issues

输出格式

列名说明
问题IDR008
问题类型用例声明格式不规范
严重级别Warning
文件路径相对路径
行号问题所在行号
所属用例关联的 it(' 参数名
代码片段匹配到的代码行
修复建议路径+行号+问题描述

错误示例

// 错误1: 缺少@修饰符
/**
 * tc.name        testName        // ✗ 错误:缺少@
 * tc.number      SUB_XXX_XXXX_XXXX  // ✗ 错误:缺少@
 */
it('testName', () => {
  console.info('test001');
});
// 错误2: 使用/* 开头(应为/**)
/*                                           // ✗ 错误:应使用 /**
 * @tc.name        testName
 */
it('testName', () => {
  console.info('test001');
});
// 错误3: 使用冒号分隔
/**
 * @tc.name       : testName              // ✗ 错误:不应使用冒号
 * @tc.number    : SUB_XXX_XXXX_XXXX     // ✗ 错误:不应使用冒号
 */
it('testName', () => {
  console.info('test001');
});
// 错误4: 注释结束行与用例间有空行
/**
 * @tc.name        testName
 */
                                                     // ✗ 错误:不应有空行
it('testName', () => {
  console.info('test001');
});
// 错误5: 注释行未以*开始
/**
   tc.name        testName       // ✗ 错误:未以*开始
 * @tc.number      SUB_XXX_XXXX
 */
it('testName', () => {
  console.info('test001');
});

正确示例

// 正确: 使用标准格式
/**
 * @tc.name        testName
 * @tc.number      SUB_XXX_XXXX_XXXX
 * @tc.desc        测试用例描述
 * @tc.size        MEDIUMTEST
 * @tc.type        Function
 * @tc.level       Level2
 */
it('testName', TestType.FUNCTION | Size.MEDIUMTEST | Level.LEVEL2, async (done) => {
  console.info('test001');
  done();
});

检测问题类型汇总

类型编号问题检测方法
1缺少 /** 开头首行不以 /** 开始
2缺少 */ 结尾末行不包含 */
3每行未以 * 开始中间行不以 * 开始
4参数名缺少 @ 修饰符行内容匹配 tc.\w+ 但无 @ 前缀
5使用冒号分隔符行内容匹配 @tc.\w+\s*:
6注释与用例间有空行*/it( 之间存在空行

注意事项

  1. R008属于Warning级别,默认不扫描,需使用 --level warning--level all
  2. 预期问题数量约321676个,扫描时间约60分钟
  3. 只扫描测试文件(.test.ets/.test.ts/.test.js),不扫描非测试源代码文件
  4. 同一个注释块可能存在多个问题,每个问题分别报告
  5. testcase字段取注释块后紧跟的 it(' 参数名

技术挑战与解决方案

挑战: 解析JSDoc注释并检查完整性

解决方案:

required_tags = [
    '@tc.number', '@tc.name', '@tc.desc', '@tc.type',
    '@tc.level', '@tc.size', '@tc.size', '@tc.since'
]

---

## 最新评估结果与实现状态(2026-04-14)

### 实现状态

**当前状态**: ❌ **未实现**(使用占位符扫描器)

**技术原因**:
- R008属于"模型生成规则",需要根据本文档的检测逻辑动态生成扫描代码
- 在 `scripts/main.py` 的 `load_rule_scanners()` 函数中,R008被替换为noop扫描器(空操作)
- 当前实现返回空列表,无法检测任何问题

### 改进方案

**方案1:实现为预置脚本(推荐)**

在 `scripts/simple_rules.py` 中添加R008的扫描实现:

```python
# ======================== R008: 用例声明格式不规范 ========================
# Source: rules/R008/SKILL.md

_R008_NO_AT_RE = re.compile(r'^\s*\*\s*(tc\.\w+)\s+\S')


def scan_r008(files, base_dir):
    issues = []
    for fp in files:
        try:
            with open(fp, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
        except Exception:
            continue
        lines = content.split('\n')
        in_doc_comment = False
        doc_start_line = 0
        testcase_name = None

        for i, line in enumerate(lines, 1):
            stripped = line.strip()

            # 检测 /** 开始
            if stripped.startswith('/**'):
                in_doc_comment = True
                doc_start_line = i
                continue

            # 检测 */ 结束
            if in_doc_comment and '*/' in stripped:
                in_doc_comment = False
                # 查找下一个 it() 调用
                for j in range(i, min(i + 5, len(lines))):
                    m = re.search(r"\bit\s*\(\s*['\"]([^'\"]+)['\"]", lines[j])
                    if m:
                        testcase_name = m.group(1)
                        break
                continue

            # 在注释块内检测问题
            if in_doc_comment:
                # 检测缺少 @ 修饰符
                m = _R008_NO_AT_RE.search(stripped)
                if m:
                    issues.append(_make_issue(
                        'R008', '用例声明格式不规范', 'Warning',
                        fp, base_dir, i, line,
                        f'路径: {os.path.relpath(fp, base_dir)}, 行号: {i}, 问题描述: 参数 {m.group(1)} 缺少@修饰符',
                        testcase=testcase_name or '-'))

    return issues

方案2:动态生成检测代码

修改 scripts/main.py,在扫描时根据本文档的检测逻辑动态生成并执行R008检测代码。

方案3:独立扫描脚本

创建独立的 scripts/scan_r008.py 文件,实现R008的完整检测逻辑。

自动修复支持

根据 guides/R008_testcase_format/R008_FIX_GUIDE.md,R008支持自动修复:

修复内容:

  1. tc.name 改为 @tc.name
  2. tc.number 改为 @tc.number
  3. 删除多余空行

修复命令:

python3 scripts/main.py /path/to/code --rules R008 --fix

参考文档

详细评估报告见:/home/xianf/master/test/xts/evaluation_report.md

Signals

GitHub stars
34
Forks
7
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
r008
Source
github.com/openharmonyinsight/openharmony-skills