@qiun/eslint-plugin-ucharts
v2.0.0
Published
ESLint plugin enforcing Safe TS Subset v4.0 rules for uCharts cross-platform compatibility (Swift/Kotlin/ArkTS/UTS)
Maintainers
Readme
eslint-plugin-ucharts
ESLint plugin enforcing Safe TS Subset v4.0 rules for uCharts cross-platform compatibility (Swift / Kotlin / ArkTS / UTS)
Background
uCharts v4 的 core/ 目录代码会被自动转译为 Swift(iOS)、Kotlin(Android)、ArkTS(HarmonyOS)、UTS(uni-app x) 原生代码。为了确保转译成功,core/ 中的 TypeScript 代码必须遵守 Safe TS Subset v4.0 规范。
本插件提供 18 条 ESLint 规则(v2.0),在编码阶段自动检测违反 Safe TS Subset v4.0 的语法,避免转译时出错。
📖 完整规范文档:Safe TS Subset v4.0
Installation
npm install -D @qiun/eslint-plugin-uchartsUsage
Quick Start: Recommended Config
// eslint.config.mjs (Flat Config)
import uchartsPlugin from '@qiun/eslint-plugin-ucharts';
export default [
{
plugins: {
'@qiun/ucharts': uchartsPlugin
},
rules: {
'@qiun/ucharts/recommended': 'error'
}
}
];Strict Mode (All Rules as Error)
{
extends: ['plugin:@qiun/ucharts/strict']
}v4-Production (Production-Ready)
最严格的配置,适用于生产环境发布前最终检查:
{
extends: ['plugin:@qiun/ucharts/v4-production']
}Manual Configuration
{
plugins: ['@qiun/ucharts'],
rules: {
'@qiun/ucharts/no-destructuring': 'error',
'@qiun/ucharts/no-object-spread': 'error',
'@qiun/ucharts/no-typeof-runtime-check': 'error', // 🆕 v2.0
'@qiun/ucharts/no-object-as-map': 'error', // 🆕 v2.0
// ... see Rules below
}
}Rules (v2.0)
F-Level: Forbidden (Compile-Blocking) — 14 rules
| Rule | ID | Default | Description |
|------|----|---------|-------------|
| no-destructuring | F-03~F-05 | error | 禁止解构赋值/解构参数 |
| no-object-spread | F-06 | error | 禁止对象展开 {...obj} |
| no-dynamic-property-access | F-08~F-09 | error | 禁止动态属性访问 obj[key] |
| no-index-signature | F-07 | error | 禁止 Index Signature [key: string]: T |
| no-for-in | F-17 | error | 禁止 for...in 循环 |
| no-with-statement | F-18 | error | 禁止 with 语句 |
| no-nested-function | F-21 | error | 禁止嵌套函数声明 |
| require-null-over-undefined | M-01 | error | 要求用 null 替代 undefined |
| no-web-api-direct | M-07~M-12 | error | 禁止直接调用 Web API |
| no-weakmap-proxy | M-04~M-05 | warn | 禁止 WeakMap / Proxy |
| no-typeof-runtime-check | F-29 | error | 禁止 typeof 运行时类型检查 🆕 |
| no-object-as-map | F-30 | error | 禁止对象作为 Map 使用 🆕 |
| require-explicit-return-type | F-31 | warn | 要求显式返回类型注解 🆕 |
| no-double-type-cast | F-32 | error | 禁止双重类型转换 as Any as T 🆕 |
M-Level: Medium (Replace with Alternative) — 1 rule
| Rule | ID | Default | Description |
|------|----|---------|-------------|
| no-js-builtin-api | M-17, M-20~M-22 | error | 禁止直接调用 JS 内置 API (Object/Array/String/Math) 🆕 |
C-Level: Code Style (Best Practices) — 3 rules
| Rule | ID | Default | Description |
|------|----|---------|-------------|
| prefer-template-literal | C-04, R-14 | warn | 优先使用模板字符串拼接 🆕 |
| no-nested-ternary | F-28, C-10 | error | 禁止嵌套三元表达式 🆕 |
| class-member-order | R-12, C-09 | warn | 类成员遵循标准顺序 (Swift/Kotlin) 🆕 |
Rule Details
F-03~F-05: no-destructuring
检测所有形式的解构:
const { a, b } = obj; // ❌ Object destructuring
const [x, y] = arr; // ❌ Array destructuring
function foo({ name }) {} // ❌ Parameter destructuringFix: 使用显式属性访问:
const a = obj.a;
const b = obj.b;
const x = arr[0];
function foo(param) { const name = param.name; }F-06: no-object-spread
const merged = { ...obj1, ...obj2 }; // ❌Fix: 使用 merge 工具函数:
const merged = merge(obj1, obj2);F-07: no-index-signature
interface Dict {
[key: string]: any; // ❌ Index Signature
}Fix: 使用显式字段或 Map:
interface Dict {
name: string;
value: number;
}F-08~F-09: no-dynamic-property-access
const key = 'name';
obj[key] = value; // ❌ dynamic keyFix: 使用 Map 存储动态键值对:
const map = new Map<string, any>();
map.set(key, value);F-21: no-nested-function
function outer() {
function inner() {} // ❌ nested function declaration
}Fix: 提取到模块级别或使用箭头函数。
M-01: require-null-over-undefined
let x = undefined; // ❌
if (x === undefined) {} // ❌
return undefined; // ❌Fix: 统一使用 null:
let x = null;
if (x == null) {}
return null;M-07~M-12: no-web-api-direct
禁止直接调用必须通过抽象层的平台特定 API:
console.log('msg'); // ❌ use Logger from core/platform/logger.ts
requestAnimationFrame(cb); // ❌ use getAnimationScheduler()
performance.now(); // ❌ use getAnimationScheduler().now()
setTimeout(fn, 100); // ❌ use getAnimationScheduler().setTimeout()Fix: 从 core/platform/ 导入平台抽象模块。
M-04~M-05: no-weakmap-proxy
const wm = new WeakMap(); // ⚠️ warn: 各平台行为不一致
const p = new Proxy(target, {}); // ⚠️ warn: ArkTS 不支持Fix: 使用 Map 替代 WeakMap;使用显式适配器类替代 Proxy。
🆕 F-29: no-typeof-runtime-check
v2.0 新增规则 — 禁止使用 typeof 进行运行时类型检查:
if (typeof item === 'string') { ... } // ❌
if (typeof obj === 'object') { ... } // ❌Fix: 使用类型守卫函数:
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}
if (isString(item)) { ... } // ✅
if (isObject(obj)) { ... } // ✅Auto-fix: ESLint 可自动将 typeof x === 'string' 转换为 isString(x)。
🆕 F-30: no-object-as-map
v2.0 新增规则 — 禁止使用 JS 对象作为 Map:
const entries = Object.entries(obj); // ❌ Kotlin/Swift 不存在
const newObj = Object.fromEntries(entries); // ❌ Kotlin/Swift 不存在
const keys = Object.keys(obj); // ❌ 应使用 Map.keys()
const values = Object.values(obj); // ❌ 应使用 Map.values()Fix: 使用 Map 替代对象存储键值对:
const map = new Map<string, any>();
map.set('key', 'value');
const entries = Array.from(map.entries());🆕 F-31: require-explicit-return-type
v2.0 新增规则 — 要求所有公开函数必须有显式返回类型注解:
function getData() { // ❌ 隐式返回类型
return { items: [] };
}Fix: 添加显式返回类型:
function getData(): { items: Item[] } { // ✅
return { items: [] };
}Strict Mode: 在 v4-production 配置中,箭头函数也必须显式声明返回类型。
🆕 F-32: no-double-type-cast
v2.0 新增规则 — 禁止双重类型转换:
const result = obj as Any as T; // ❌ 完全丢失类型安全
const value = data as unknown as MyType; // ❌Fix: 使用单次安全转换或类型检查:
const result = obj as T; // ✅ 单次转换
const result2 = obj as? T; // ✅ 可选链转换
if (result !== null) { ... } // ✅ 带检查🆕 M-17, M-20~M-22: no-js-builtin-api
v2.0 新增规则 — 禁止直接调用需要平台映射的 JS 内置 API:
Object.entries(obj); // ❌ 需映射为 map.entries()
Array.prototype.push.call(arr, item); // ❌
str.includes(sub); // ⚠️ 部分平台名称不同
Math.floor(x); // ⚠️ 平台差异Fix: 参考 Safe TS Subset v4.0 API映射表
| JS API | Kotlin | Swift |
|--------|--------|-------|
| arr.push(item) | arr.add(item) | arr.append(item) |
| arr.pop() | arr.removeAt(arr.size - 1) | arr.removeLast() |
| Object.keys(obj) | (obj as Map<String, Any>).keys | (obj as! [String: Any]).keys |
🆕 C-04: prefer-template-literal
v2.0 新增规则 — 优先使用模板字符串拼接:
const str = "prefix_" + index + "_suffix"; // ❌Fix:
const str = `prefix_${index}_suffix`; // ✅Auto-fix: ESLint 可自动将字符串拼接转换为模板字符串。
🆕 F-28: no-nested-ternary
v2.0 新增规则 — 禁止嵌套三元表达式:
const result = condition1 ? (condition2 ? value1 : value2) : value3; // ❌Fix: 使用 if-else 或提取变量:
let result: string;
if (condition1) {
result = condition2 ? value1 : value2;
} else {
result = value3;
}🆕 C-09: class-member-order
v2.0 新增规则 — 强制类成员遵循 Swift/Kotlin 惯例顺序:
class BadOrder {
render() { } // ❌ 应在 constructor 后
private _state; // ❌ 应在最前
constructor() { } // ❌ 应在属性后
dispose() { } // ❌ 应在 constructor 后
static VERSION = '1'; // ❌ 应在最前
}Fix: 遵循标准顺序:
class GoodOrder {
static readonly VERSION = '1.0.0'; // 1. 静态属性
private _state; // 2. 实例属性
constructor() { } // 3. 构造函数
dispose() { } // 4. 生命周期方法
render() { } // 5. 公共方法
protected beforeRender() { } // 6. 受保护方法
private createInitialState() { } // 7. 私有方法
}Config Presets
recommended
平衡的严格度,适合大多数项目。所有规则为 error,部分规则为 warn:
{
extends: ['plugin:@qiun/ucharts/recommended']
}strict
所有规则均为 error,适用于必须通过跨平台转译的代码:
{
extends: ['plugin:@qiun/ucharts/strict']
}v4-production 🆕
生产级最严格配置,适用于发布前最终质量门禁:
{
extends: ['plugin:@qiun/ucharts/v4-production']
}特点:
- 扩展了
no-web-api-direct的禁止列表(增加 setTimeout/setInterval/performance.measure) - 扩展了
no-js-builtin-api的禁止列表(完整覆盖 Object/Array/String/Math API) require-explicit-return-type排除私有方法和箭头函数
Integration with VSCode
Settings
{
"eslint.enable": true,
"eslint.run": "onType",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}Auto-Fix Support
以下规则支持自动修复:
| Rule | Fix Example |
|------|-------------|
| no-typeof-runtime-check | typeof x === 'string' → isString(x) |
| no-double-type-cast | obj as Any as T → obj as T |
| prefer-template-literal | "a" + b → `a${b}` |
Integration with uCharts Project
Package.json Scripts
{
"scripts": {
"lint": "eslint . --ext .ts,.js",
"lint:fix": "eslint . --fix",
"lint:subset": "eslint core/ --ext .ts --rule 'plugin:@qiun/ucharts/strict'",
"lint:v4": "eslint core/ --ext .ts --rule 'plugin:@qiun/ucharts/v4-production'",
"check:subset": "npm run lint:v4 && echo '✅ Safe TS Subset v4.0 check passed!'"
}
}File Exemptions
对需要特殊处理的文件使用 overrides:
{
files: ['core/util/merge.ts'],
rules: {
'@qiun/ucharts/no-dynamic-property-access': 'off',
'@qiun/ucharts/no-index-signature': 'off',
'@qiun/ucharts/no-object-as-map': 'off',
'@qiun/ucharts/no-js-builtin-api': 'off'
}
},
{
files: ['core/util/type-guards.ts'],
rules: {
'@qiun/ucharts/no-typeof-runtime-check': 'off'
}
},
{
files: ['core/platform/**/*.ts'],
rules: {
'@qiun-ucharts/no-web-api-direct': 'off',
'@qiun-ucharts/require-null-over-undefined': 'off',
'@qiun-ucharts/no-js-builtin-api': 'off'
}
}Development
# Clone the uCharts repo
git clone https://github.com/qiun/ucharts.git
cd uCharts-v4
# Install dependencies
npm install
# Lint core/ with v4-production config
npm run lint:v4
# Generate violation report
npm run lint:subset:report
# Run with verbose output
npx eslint core/ --ext .ts -f stylishMigration Guide (v1.0 → v2.0)
Breaking Changes
v2.0 完全向后兼容 v1.0。主要变化:
- 规则数量增加:10 → 18 条
- 新增配置预设:
v4-production - 更严格的检查:新增 8 条规则覆盖 v4.0 规范
Quick Migration
# Update plugin
npm install @qiun/eslint-plugin-ucharts@latest
# Check violations
npm run lint:subset:reportRelated Documents
- Safe TS Subset v4.0 — 完整规范文档
- ESLint v2.0 Upgrade Guide — 详细升级方案
- Safe TS Subset v3.0 Quality Assessment — 转译质量分析报告
