HarmonyOS开发:Lint规则

举报
Jack20 发表于 2026/06/24 16:36:20 2026/06/24
【摘要】 HarmonyOS开发:Lint规则📌 核心要点:Lint是代码规范的执行者——没有Lint的规范只是建议,有了Lint的规范才是规则。 背景与动机团队定了编码规范,写了个文档发到群里,“大家按这个来”。结果呢?一周后没人看,一个月后没人记得,三个月后代码风格五花八门——有人用分号,有人不用;有人用单引号,有人用双引号;有人函数名驼峰,有人函数名下划线。为什么编码规范总是执行不下去?因为...

HarmonyOS开发:Lint规则

📌 核心要点:Lint是代码规范的执行者——没有Lint的规范只是建议,有了Lint的规范才是规则。

背景与动机

团队定了编码规范,写了个文档发到群里,“大家按这个来”。结果呢?一周后没人看,一个月后没人记得,三个月后代码风格五花八门——有人用分号,有人不用;有人用单引号,有人用双引号;有人函数名驼峰,有人函数名下划线。

为什么编码规范总是执行不下去?因为靠人自觉遵守的规则,等于没有规则。你Code Review的时候指出格式问题,开发者觉得你吹毛求疵;你不指出来,代码风格就越来越乱。

Lint工具解决的就是这个问题——把规范变成机器检查。格式不对?Lint报错。命名不规范?Lint报错。复杂度太高?Lint报错。不用你开口,机器替你执法,谁都没意见。

鸿蒙项目的Lint还有一层特殊意义:ArkTS的语法限制比TypeScript多,很多TypeScript里合法的写法在ArkTS里不行。Lint可以在编码阶段就提醒你,不用等到编译报错才发现。

核心原理

Lint的工作流程

graph TD
    A[源代码.ets/.ts] --> B[解析器Parser]
    B --> C[AST抽象语法树]
    C --> D[规则引擎]
    
    E[规则配置.eslintrc] --> D
    F[自定义规则插件] --> D
    
    D --> G[遍历AST节点]
    G --> H[规则匹配]
    H --> I{匹配到问题?}
    I -->|| J[生成Lint报告]
    I -->|| K[通过检查]
    J --> L[输出错误/警告]
    
    classDef sourceStyle fill:#FF6B6B,stroke:#C0392B,color:#fff,font-weight:bold
    classDef processStyle fill:#4ECDC4,stroke:#1ABC9C,color:#fff
    classDef decisionStyle fill:#6C5CE7,stroke:#8E44AD,color:#fff
    classDef resultStyle fill:#FFE66D,stroke:#F39C12,color:#333
    
    class A,E,F sourceStyle
    class B,C,D,G,H processStyle
    class I decisionStyle
    class J,K,L resultStyle

Lint规则的分类体系

graph LR
    A[Lint规则体系] --> B[格式规范]
    A --> C[代码质量]
    A --> D[最佳实践]
    A --> E[ArkTS专项]
    
    B --> B1[缩进/空格]
    B --> B2[引号风格]
    B --> B3[分号使用]
    B --> B4[行长度限制]
    
    C --> C1[复杂度控制]
    C --> C2[重复代码检测]
    C --> C3[未使用变量]
    C --> C4[类型安全]
    
    D --> D1[错误处理规范]
    D --> D2[异步模式]
    D --> D3[命名规范]
    D --> D4[组件规范]
    
    E --> E1[装饰器使用]
    E --> E2[状态管理规范]
    E --> E3[生命周期规范]
    E --> E4[并发安全]
    
    classDef mainStyle fill:#FF6B6B,stroke:#C0392B,color:#fff,font-weight:bold
    classDef subStyle fill:#4ECDC4,stroke:#1ABC9C,color:#fff
    classDef detailStyle fill:#FFE66D,stroke:#F39C12,color:#333
    
    class A mainStyle
    class B,C,D,E subStyle
    class B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4,E1,E2,E3,E4 detailStyle

代码实战

基础用法:ESLint配置鸿蒙项目

鸿蒙项目使用ESLint作为Lint引擎,配合ArkTS专用插件:

// .eslintrc.arkts.js - 鸿蒙项目ESLint配置

module.exports = {
  // 根配置,防止向上查找
  root: true,
  
  // 指定解析器
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2022,
    sourceType: 'module',
    project: './tsconfig.json',
    // ArkTS特有的解析选项
    extraFileExtensions: ['.ets'],
  },

  // 插件
  plugins: [
    '@typescript-eslint',
    // 鸿蒙专用Lint插件(如果有)
    // 'arkts-lint',
  ],

  // 继承的规则集
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
    '@typescript-eslint/recommended-requiring-type-checking',
    // 鸿蒙推荐规则集
    // 'arkts-lint/recommended',
  ],

  // 全局变量
  globals: {
    // 鸿蒙全局API
    hilog: 'readonly',
    router: 'readonly',
    promptAction: 'readonly',
  },

  // 规则配置
  rules: {
    // === 格式规范 ===
    'indent': ['error', 2],                          // 2空格缩进
    'quotes': ['error', 'single', { avoidEscape: true }],  // 单引号
    'semi': ['error', 'always'],                      // 必须分号
    'max-len': ['warn', { 
      code: 120,                                      // 行宽120
      ignoreStrings: true,                            // 忽略字符串
      ignoreTemplateLiterals: true,                   // 忽略模板字符串
    }],
    'comma-dangle': ['error', 'always-multiline'],    // 多行尾逗号

    // === 代码质量 ===
    'complexity': ['warn', { max: 15 }],              // 圈复杂度上限15
    'max-lines-per-function': ['warn', { 
      max: 80,                                        // 函数不超过80行
      skipBlankLines: true,
      skipComments: true,
    }],
    'max-depth': ['warn', { max: 4 }],               // 嵌套深度不超过4
    'no-duplicate-imports': 'error',                  // 禁止重复导入
    'no-unused-vars': 'off',                          // 关闭基础规则
    '@typescript-eslint/no-unused-vars': ['error', {  // 使用TS版本
      argsIgnorePattern: '^_',                        // _开头的参数允许未使用
      varsIgnorePattern: '^_',
    }],

    // === 类型安全 ===
    '@typescript-eslint/no-explicit-any': 'error',     // 禁止any
    '@typescript-eslint/no-non-null-assertion': 'warn', // 警告非空断言
    '@typescript-eslint/prefer-nullish-coalescing': 'error', // 使用??代替||
    '@typescript-eslint/prefer-optional-chain': 'error',     // 使用可选链
    '@typescript-eslint/strict-boolean-expressions': ['warn', {
      allowString: false,                             // 禁止字符串隐式转布尔
      allowNumber: false,                             // 禁止数字隐式转布尔
      allowNullableObject: true,                      // 允许对象判空
    }],

    // === 最佳实践 ===
    'no-console': ['warn', { allow: ['warn', 'error'] }], // 禁止console.log
    'no-throw-literal': 'error',                       // throw必须抛出Error对象
    'prefer-promise-reject-errors': 'error',           // reject必须用Error
    'no-return-await': 'error',                        // 不需要return await
    '@typescript-eslint/return-await': ['error', 'in-try-catch'], // try-catch中需要await

    // === ArkTS专项 ===
    // 以下规则需要arkts-lint插件支持
    // 'arkts-lint/no-any': 'error',                   // 禁止any类型
    // 'arkts-lint/no-dynamic-property': 'error',       // 禁止动态属性
    // 'arkts-lint/state-decorator-usage': 'error',     // 状态装饰器规范
    // 'arkts-lint/component-lifecycle': 'warn',        // 生命周期规范
    // 'arkts-lint/no-eval': 'error',                   // 禁止eval
  },

  // 覆盖配置:针对不同目录使用不同规则
  overrides: [
    {
      // 测试文件放宽规则
      files: ['**/*.test.ets', '**/*.spec.ets', '**/ohosTest/**'],
      rules: {
        'max-lines-per-function': 'off',
        'complexity': 'off',
        '@typescript-eslint/no-explicit-any': 'off',
      },
    },
    {
      // Mock数据文件放宽规则
      files: ['**/mock/**', '**/fixtures/**'],
      rules: {
        '@typescript-eslint/no-explicit-any': 'off',
        'max-len': 'off',
      },
    },
  ],
};

进阶用法:自定义Lint规则

团队有特殊的编码规范?内置规则覆盖不了?写自定义规则:

// custom-rules/no-direct-state-mutation.ts
// 自定义规则:禁止在组件外部直接修改@State属性

import { ESLintUtils } from '@typescript-eslint/utils';

const createRule = ESLintUtils.RuleCreator(
  (name) => `https://your-team.github.io/lint-rules/${name}`
);

export const noDirectStateMutation = createRule({
  name: 'no-direct-state-mutation',
  meta: {
    type: 'problem',
    docs: {
      description: '禁止在组件外部直接修改@State装饰的属性',
      recommended: 'error',
    },
    messages: {
      noExternalMutation: 
        '不允许在组件外部直接修改@State属性 "{{propName}}"。' +
        '应通过组件提供的方法或@Link进行修改。',
    },
    schema: [],  // 无额外配置
  },

  defaultOptions: [],

  create(context) {
    // 收集当前文件中所有@State属性
    const stateProperties = new Map<string, string>(); // 属性名 -> 组件名

    return {
      // 第一步:收集@State属性
      'PropertyDefinition'(node: any) {
        const decorators = node.decorators;
        if (!decorators) return;

        const isState = decorators.some((dec: any) => {
          const expr = dec.expression;
          return expr.type === 'Identifier' && expr.name === 'State';
        });

        if (isState && node.key.type === 'Identifier') {
          stateProperties.set(node.key.name, '');
        }
      },

      // 第二步:检查赋值操作
      'AssignmentExpression'(node: any) {
        const left = node.left;
        
        // 检查 obj.prop = value 形式
        if (left.type === 'MemberExpression') {
          const property = left.property;
          if (property.type === 'Identifier' && 
              stateProperties.has(property.name)) {
            // 检查是否在组件方法内
            const scope = context.getScope();
            const inComponent = scope.type === 'method' || 
                               scope.type === 'function';
            
            // 简化判断:如果在函数外赋值@State属性,报错
            // 实际实现需要更精确的作用域分析
            context.report({
              node,
              messageId: 'noExternalMutation',
              data: {
                propName: property.name,
              },
            });
          }
        }
      },
    };
  },
});
// custom-rules/prefer-lazy-foreach.ts
// 自定义规则:大数据列表推荐使用LazyForEach

import { ESLintUtils } from '@typescript-eslint/utils';

const createRule = ESLintUtils.RuleCreator(
  (name) => `https://your-team.github.io/lint-rules/${name}`
);

export const preferLazyForEach = createRule({
  name: 'prefer-lazy-foreach',
  meta: {
    type: 'suggestion',
    docs: {
      description: '在List/Grid组件中推荐使用LazyForEach替代ForEach',
      recommended: 'warn',
    },
    messages: {
      preferLazyForEach: 
        '在List/Grid组件中使用ForEach可能导致性能问题。' +
        '数据量较大时请使用LazyForEach,支持按需加载。',
    },
    schema: [{
      type: 'object',
      properties: {
        minItemCount: {
          type: 'number',
          default: 50,  // 默认50条以上建议使用LazyForEach
        },
      },
      additionalProperties: false,
    }],
  },

  defaultOptions: [{ minItemCount: 50 }],

  create(context, [options]) {
    return {
      // 检测ForEach在List/Grid中的使用
      'CallExpression'(node: any) {
        const callee = node.callee;
        
        // 判断是否是ForEach调用
        if (callee.type !== 'Identifier' || callee.name !== 'ForEach') {
          return;
        }

        // 向上查找是否在List或Grid组件内
        let parent = node.parent;
        let inListComponent = false;
        
        while (parent) {
          if (parent.type === 'CallExpression' && 
              parent.callee.type === 'Identifier') {
            const name = parent.callee.name;
            if (name === 'List' || name === 'Grid' || 
                name === 'WaterFlow' || name === 'Swiper') {
              inListComponent = true;
              break;
            }
          }
          parent = parent.parent;
        }

        if (inListComponent) {
          context.report({
            node,
            messageId: 'preferLazyForEach',
          });
        }
      },
    };
  },
});

完整示例:团队Lint规范配置包

把Lint配置封装成npm包,团队所有项目统一使用:

// eslint-config-harmony/index.js
// 团队共享的Lint配置包

module.exports = {
  // 基础配置(所有项目通用)
  base: {
    root: true,
    parser: '@typescript-eslint/parser',
    parserOptions: {
      ecmaVersion: 2022,
      sourceType: 'module',
      extraFileExtensions: ['.ets'],
    },
    plugins: ['@typescript-eslint'],
    extends: [
      'eslint:recommended',
      '@typescript-eslint/recommended',
    ],
    rules: {
      // 格式规范
      'indent': ['error', 2],
      'quotes': ['error', 'single'],
      'semi': ['error', 'always'],
      'max-len': ['warn', { code: 120 }],
      'comma-dangle': ['error', 'always-multiline'],
      'no-trailing-spaces': 'error',
      'eol-last': ['error', 'always'],

      // 代码质量
      'complexity': ['warn', { max: 15 }],
      'max-lines-per-function': ['warn', { max: 80 }],
      'max-depth': ['warn', { max: 4 }],
      'max-params': ['warn', { max: 5 }],
      'no-duplicate-imports': 'error',

      // 类型安全
      '@typescript-eslint/no-explicit-any': 'error',
      '@typescript-eslint/no-non-null-assertion': 'warn',
      '@typescript-eslint/prefer-nullish-coalescing': 'error',
      '@typescript-eslint/prefer-optional-chain': 'error',
      '@typescript-eslint/no-unused-vars': ['error', {
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_',
      }],
    },
  },

  // 严格配置(核心模块使用)
  strict: {
    extends: ['./index.js'].concat([
      '@typescript-eslint/recommended-requiring-type-checking',
    ]),
    rules: {
      // 更严格的质量要求
      'complexity': ['error', { max: 10 }],
      'max-lines-per-function': ['error', { max: 60 }],
      'max-depth': ['error', { max: 3 }],
      
      // 更严格的类型检查
      '@typescript-eslint/no-unsafe-assignment': 'error',
      '@typescript-eslint/no-unsafe-call': 'error',
      '@typescript-eslint/no-unsafe-member-access': 'error',
      '@typescript-eslint/no-unsafe-return': 'error',
    },
  },

  // 宽松配置(测试代码使用)
  test: {
    extends: ['./index.js'],
    rules: {
      'max-lines-per-function': 'off',
      'complexity': 'off',
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-non-null-assertion': 'off',
    },
  },
};

// package.json - 发布为npm包
// {
//   "name": "@your-team/eslint-config-harmony",
//   "version": "1.0.0",
//   "main": "index.js",
//   "peerDependencies": {
//     "@typescript-eslint/eslint-plugin": "^6.0.0",
//     "@typescript-eslint/parser": "^6.0.0",
//     "eslint": "^8.0.0"
//   }
// }
// 项目中使用团队配置
// .eslintrc.js

// 基础项目
module.exports = {
  extends: ['@your-team/eslint-config-harmony/base'],
};

// 核心模块
module.exports = {
  extends: ['@your-team/eslint-config-harmony/strict'],
};

// 测试代码
module.exports = {
  extends: ['@your-team/eslint-config-harmony/test'],
};
// package.json - Lint脚本配置
{
  "scripts": {
    // 运行Lint
    "lint": "eslint entry/src/main/ets --ext .ets,.ts",
    
    // 自动修复
    "lint:fix": "eslint entry/src/main/ets --ext .ets,.ts --fix",
    
    // 检查特定规则
    "lint:complexity": "eslint entry/src/main/ets --ext .ets,.ts --rule 'complexity: error'",
    
    // 生成Lint报告
    "lint:report": "eslint entry/src/main/ets --ext .ets,.ts -f json -o lint-report.json",
    
    // Pre-commit钩子(配合husky)
    "lint:staged": "eslint --ext .ets,.ts"
  },
  
  // 依赖
  "devDependencies": {
    "eslint": "^8.50.0",
    "@typescript-eslint/eslint-plugin": "^6.7.0",
    "@typescript-eslint/parser": "^6.7.0",
    "@your-team/eslint-config-harmony": "^1.0.0"
  }
}

踩坑与注意事项

坑1:Lint规则太多,开发者体验差

一保存文件就满屏红线,开发者心态崩了,直接eslint-disable全关。

解决方案

  • 区分error和warning:格式问题用warning,类型问题用error
  • 渐进式引入:先开启核心规则,团队适应后再加更多
  • 自动修复优先:能自动修复的问题不要手动改,配置保存时自动格式化
  • Pre-commit只检查error级别,warning不阻塞提交

坑2:.ets文件解析器兼容问题

ESLint默认不支持.ets扩展名,需要额外配置。

解决方案

// .eslintrc.js中必须配置
{
  parserOptions: {
    extraFileExtensions: ['.ets'],  // 告诉解析器支持.ets
  },
  // 或在overrides中处理
  overrides: [{
    files: ['**/*.ets'],
    parser: '@typescript-eslint/parser',
  }],
}

坑3:团队Lint配置不一致

A项目用一套配置,B项目用另一套,新人换项目就懵了。

解决方案

  • Lint配置封装成npm包,所有项目统一引用
  • 配置变更走版本管理,项目按需升级
  • CI中检查Lint配置是否是最新版本

坑4:自定义规则维护成本高

自己写的Lint规则,过几个版本就跑不动了。

解决方案

  • 自定义规则尽量简单,只做AST层面的模式匹配
  • 复杂的业务逻辑检查用脚本实现,不要硬塞进Lint
  • 定期review自定义规则的必要性,该删就删

坑5:Lint与Prettier冲突

ESLint的格式规则和Prettier的格式化结果不一致,保存时格式化来格式化去。

解决方案

  • 格式化全部交给Prettier,ESLint只管代码质量
  • 使用eslint-config-prettier关闭ESLint中与Prettier冲突的规则
  • 不要同时配置ESLint的格式规则和Prettier

HarmonyOS 6适配说明

HarmonyOS 6在Lint工具链方面的改进:

  1. ArkTS Lint官方插件:华为官方发布了@ohos/eslint-plugin-arkts,内置30+条ArkTS专用规则,覆盖装饰器规范、状态管理、生命周期等领域。之前需要自己写的规则,现在官方提供了。

  2. DevEco Studio内置Lint:IDE内置了Lint检查,不再需要单独安装和配置ESLint。开箱即用,但仍然支持自定义扩展。

  3. Lint规则与API版本关联:新版本的Lint规则可以检查API版本兼容性——你用了API 12的接口,但项目最低支持API 10,Lint会提醒你。

  4. 自动修复增强:更多的规则支持自动修复(--fix),包括ArkTS特有的类型注解补全、装饰器参数规范等。

  5. Lint性能优化:大型项目的Lint速度提升了约40%,增量Lint只需要1-2秒。

总结

Lint是编码规范的自动化执行者。没有Lint,规范就是一纸空文;有了Lint,规范才是真正的规则。

核心要点:

  • 格式归Prettier,质量归ESLint:不要让两个工具打架
  • 团队配置统一:封装成npm包,所有项目共用
  • 渐进式引入:不要一次开太多规则,团队受不了
  • 自动修复优先:能自动修的不要手动改
  • 自定义规则要克制:维护成本高,能不用就不用
维度 评分 说明
学习难度 ⭐⭐⭐ 配置项多,自定义规则需要AST知识
使用频率 ⭐⭐⭐⭐⭐ 每次保存文件都在跑
重要程度 ⭐⭐⭐⭐ 规范的执行者,团队协作的基础
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。