@qiun/eslint-plugin-ucharts 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -0
- package/index.js +65 -0
- package/lib/rules/no-destructuring.js +62 -0
- package/lib/rules/no-dynamic-property-access.js +48 -0
- package/lib/rules/no-for-in.js +25 -0
- package/lib/rules/no-index-signature.js +25 -0
- package/lib/rules/no-nested-function.js +47 -0
- package/lib/rules/no-object-spread.js +29 -0
- package/lib/rules/no-weakmap-proxy.js +30 -0
- package/lib/rules/no-web-api-direct.js +71 -0
- package/lib/rules/no-with-statement.js +25 -0
- package/lib/rules/require-null-over-undefined.js +52 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# @qiun/eslint-plugin-ucharts
|
|
2
|
+
|
|
3
|
+
[!\[npm version\](https://img.shields.io/npm/v/@qiun/eslint-plugin-ucharts.svg null)](https://www.npmjs.com/package/@qiun/eslint-plugin-ucharts)
|
|
4
|
+
[!\[license\](https://img.shields.io/npm/l/@qiun/eslint-plugin-ucharts.svg null)](https://github.com/qiun/ucharts/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
> ESLint plugin enforcing **Safe TS Subset** rules for uCharts cross-platform compatibility (Swift / Kotlin / ArkTS)
|
|
7
|
+
|
|
8
|
+
## Background
|
|
9
|
+
|
|
10
|
+
uCharts v4 的 `core/` 目录代码会被自动转译为 **Swift(iOS)**、**Kotlin(Android)**、**ArkTS(HarmonyOS)** 原生代码。为了确保转译成功,`core/` 中的 TypeScript 代码必须遵守 **Safe TS Subset** 规范——只使用三种目标语言都支持的语法子集。
|
|
11
|
+
|
|
12
|
+
本插件提供 **11 条 ESLint 规则**,在编码阶段自动检测违反 Safe TS Subset 的语法,避免转译时出错。
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -D @qiun/eslint-plugin-ucharts
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic: Use recommended config
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
// .eslintrc.js
|
|
26
|
+
module.exports = {
|
|
27
|
+
extends: ['plugin:@qiun/ucharts/recommended']
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Strict mode (all rules as error)
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
module.exports = {
|
|
35
|
+
extends: ['plugin:@qiun/ucharts/strict']
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Manual configuration
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
// .eslintrc.js
|
|
43
|
+
module.exports = {
|
|
44
|
+
plugins: ['@qiun/eslint-plugin-ucharts'],
|
|
45
|
+
rules: {
|
|
46
|
+
'@qiun/no-destructuring': 'error',
|
|
47
|
+
'@qiun/no-object-spread': 'error',
|
|
48
|
+
// ... see Rules below
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Rules
|
|
54
|
+
|
|
55
|
+
| Rule | Description | Default | Why |
|
|
56
|
+
| ----------------------------- | ------------------------------------- | ------- | ----------------------- |
|
|
57
|
+
| `no-destructuring` | 禁止解构赋值/解构参数 | `error` | ArkTS 完全禁止解构 |
|
|
58
|
+
| `no-object-spread` | 禁止对象展开 `{...obj}` | `error` | ArkTS 不支持对象展开 |
|
|
59
|
+
| `no-dynamic-property-access` | 禁止动态属性访问 `obj[key]` | `error` | ArkTS 不支持变量键访问 |
|
|
60
|
+
| `no-index-signature` | 禁止 Index Signature `[key: string]: T` | `error` | ArkTS 不支持索引签名 |
|
|
61
|
+
| `no-for-in` | 禁止 `for...in` 循环 | `error` | UTS/ArkTS 不支持 |
|
|
62
|
+
| `no-with-statement` | 禁止 `with` 语句 | `error` | 三平台均不支持 |
|
|
63
|
+
| `no-nested-function` | 禁止嵌套函数声明 | `error` | ArkTS 禁止函数内声明函数 |
|
|
64
|
+
| `require-null-over-undefined` | 要求用 `null` 替代 `undefined` | `error` | UTS/ArkTS 不支持 undefined |
|
|
65
|
+
| `no-web-api-direct` | 禁止直接调用 Web API | `error` | 需使用 platform 抽象接口 |
|
|
66
|
+
| `no-weakmap-proxy` | 禁止 WeakMap / Proxy | `warn` | 各平台行为不一致 |
|
|
67
|
+
|
|
68
|
+
### Rule Details
|
|
69
|
+
|
|
70
|
+
#### `@qiun/no-destructuring`
|
|
71
|
+
|
|
72
|
+
Detects and reports all forms of destructuring:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const { a, b } = obj; // Object destructuring ❌
|
|
76
|
+
const [x, y] = arr; // Array destructuring ❌
|
|
77
|
+
function foo({ name }) {} // Parameter destructuring ❌
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Fix:** Use explicit property access:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const a = obj.a;
|
|
84
|
+
const b = obj.b;
|
|
85
|
+
const x = arr[0];
|
|
86
|
+
function foo(param) { const name = param.name; }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### `@qiun/no-object-spread`
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const merged = { ...obj1, ...obj2 }; // ❌
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Fix:** Use a merge utility function:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const merged = merge(obj1, obj2);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### `@qiun/no-dynamic-property-access`
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const key = 'name';
|
|
105
|
+
obj[key] = value; // ❌ dynamic key
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Fix:** Use Map for dynamic keys:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const map = new Map<string, any>();
|
|
112
|
+
map.set(key, value);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### `@qiun/no-index-signature`
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
interface Dict {
|
|
119
|
+
[key: string]: any; // ❌ Index Signature
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Fix:** Use explicit fields or Map:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
interface Dict {
|
|
127
|
+
name: string;
|
|
128
|
+
value: number;
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### `@qiun/no-nested-function`
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
function outer() {
|
|
136
|
+
function inner() {} // ❌ nested function declaration
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Fix:** Extract to module level or use arrow functions assigned to variables.
|
|
141
|
+
|
|
142
|
+
#### `@qiun/require-null-over-undefined`
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
let x = undefined; // ❌
|
|
146
|
+
if (x === undefined) {} // ❌
|
|
147
|
+
return undefined; // ❌
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Fix:** Use `null` consistently:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
let x = null;
|
|
154
|
+
if (x == null) {}
|
|
155
|
+
return null;
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### `@qiun/no-web-api-direct`
|
|
159
|
+
|
|
160
|
+
Forbids direct calls to platform-specific APIs that must go through abstraction layer:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
console.log('msg'); // ❌ use Logger from core/platform/logger.ts
|
|
164
|
+
requestAnimationFrame(cb); // ❌ use getAnimationScheduler()
|
|
165
|
+
performance.now(); // ❌ use getAnimationScheduler().now()
|
|
166
|
+
setTimeout(fn, 100); // ❌ use getAnimationScheduler().setTimeout()
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Fix:** Import from platform abstraction modules in `core/platform/`.
|
|
170
|
+
|
|
171
|
+
#### `@qiun/no-weakmap-proxy`
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const wm = new WeakMap(); // ⚠️ warn: behavior differs across platforms
|
|
175
|
+
const p = new Proxy(target, {}); // ⚠️ warn: not supported in ArkTS
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Fix:** Use `Map` instead of `WeakMap`; use explicit adapter class instead of `Proxy`.
|
|
179
|
+
|
|
180
|
+
## Config Presets
|
|
181
|
+
|
|
182
|
+
### `recommended`
|
|
183
|
+
|
|
184
|
+
Balanced strictness suitable for most projects. All rules at `error` except:
|
|
185
|
+
|
|
186
|
+
- `no-web-api-direct`: allows configurable forbidden API list
|
|
187
|
+
- `no-weakmap-proxy`: `warn` (not error)
|
|
188
|
+
|
|
189
|
+
### `strict`
|
|
190
|
+
|
|
191
|
+
All rules set to `error`. Recommended for code that **must** pass cross-platform transpilation.
|
|
192
|
+
|
|
193
|
+
## Integration with uCharts Project
|
|
194
|
+
|
|
195
|
+
In the uCharts v4 monorepo, this plugin is configured in two ESLint configs:
|
|
196
|
+
|
|
197
|
+
1. **`.eslintrc.js`** — Full project lint (core/, adapters/) with per-file overrides for exempt files like `core/util/merge.ts`
|
|
198
|
+
2. **`.eslintrc.subset.js`** — Strict subset validation for `core/` only
|
|
199
|
+
|
|
200
|
+
Example override pattern (for files that legitimately need exceptions):
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
overrides: [
|
|
204
|
+
{
|
|
205
|
+
files: ['core/util/merge.ts'],
|
|
206
|
+
rules: {
|
|
207
|
+
'@qiun/no-dynamic-property-access': 'off',
|
|
208
|
+
'@qiun/no-index-signature': 'off'
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
files: ['core/platform/**/*.ts'],
|
|
213
|
+
rules: {
|
|
214
|
+
'@qiun/no-web-api-direct': 'off',
|
|
215
|
+
'@qiun/require-null-over-undefined': 'off'
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
[Apache-2.0](LICENSE)
|
package/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const noObjectSpread = require('./lib/rules/no-object-spread');
|
|
2
|
+
const noDestructuring = require('./lib/rules/no-destructuring');
|
|
3
|
+
const noDynamicPropertyAccess = require('./lib/rules/no-dynamic-property-access');
|
|
4
|
+
const noIndexSignature = require('./lib/rules/no-index-signature');
|
|
5
|
+
const noForIn = require('./lib/rules/no-for-in');
|
|
6
|
+
const noWithStatement = require('./lib/rules/no-with-statement');
|
|
7
|
+
const noNestedFunction = require('./lib/rules/no-nested-function');
|
|
8
|
+
const requireNullOverUndefined = require('./lib/rules/require-null-over-undefined');
|
|
9
|
+
const noWebApiDirect = require('./lib/rules/no-web-api-direct');
|
|
10
|
+
const noWeakMapProxy = require('./lib/rules/no-weakmap-proxy');
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
rules: {
|
|
14
|
+
'no-object-spread': noObjectSpread,
|
|
15
|
+
'no-destructuring': noDestructuring,
|
|
16
|
+
'no-dynamic-property-access': noDynamicPropertyAccess,
|
|
17
|
+
'no-index-signature': noIndexSignature,
|
|
18
|
+
'no-for-in': noForIn,
|
|
19
|
+
'no-with-statement': noWithStatement,
|
|
20
|
+
'no-nested-function': noNestedFunction,
|
|
21
|
+
'require-null-over-undefined': requireNullOverUndefined,
|
|
22
|
+
'no-web-api-direct': noWebApiDirect,
|
|
23
|
+
'no-weakmap-proxy': noWeakMapProxy,
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
configs: {
|
|
27
|
+
recommended: {
|
|
28
|
+
plugins: {
|
|
29
|
+
'@ucharts': require('.')
|
|
30
|
+
},
|
|
31
|
+
rules: {
|
|
32
|
+
'@ucharts/no-object-spread': 'error',
|
|
33
|
+
'@ucharts/no-destructuring': 'error',
|
|
34
|
+
'@ucharts/no-dynamic-property-access': 'error',
|
|
35
|
+
'@ucharts/no-index-signature': 'error',
|
|
36
|
+
'@ucharts/no-for-in': 'error',
|
|
37
|
+
'@ucharts/no-with-statement': 'error',
|
|
38
|
+
'@ucharts/no-nested-function': 'error',
|
|
39
|
+
'@ucharts/require-null-over-undefined': 'error',
|
|
40
|
+
'@ucharts/no-web-api-direct': ['error', {
|
|
41
|
+
forbiddenApis: ['console.log', 'console.warn', 'console.error', 'console.info', 'requestAnimationFrame', 'cancelAnimationFrame', 'performance.now']
|
|
42
|
+
}],
|
|
43
|
+
'@ucharts/no-weakmap-proxy': 'warn',
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
strict: {
|
|
48
|
+
plugins: {
|
|
49
|
+
'@ucharts': require('.')
|
|
50
|
+
},
|
|
51
|
+
rules: {
|
|
52
|
+
'@ucharts/no-object-spread': 'error',
|
|
53
|
+
'@ucharts/no-destructuring': 'error',
|
|
54
|
+
'@ucharts/no-dynamic-property-access': 'error',
|
|
55
|
+
'@ucharts/no-index-signature': 'error',
|
|
56
|
+
'@ucharts/no-for-in': 'error',
|
|
57
|
+
'@ucharts/no-with-statement': 'error',
|
|
58
|
+
'@ucharts/no-nested-function': 'error',
|
|
59
|
+
'@ucharts/require-null-over-undefined': 'error',
|
|
60
|
+
'@ucharts/no-web-api-direct': 'error',
|
|
61
|
+
'@ucharts/no-weakmap-proxy': 'error',
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'Disallow destructuring patterns in Safe TS subset (ArkTS completely forbids them)',
|
|
6
|
+
category: 'uCharts Safe TS Subset',
|
|
7
|
+
recommended: 'error'
|
|
8
|
+
},
|
|
9
|
+
schema: [],
|
|
10
|
+
messages: {
|
|
11
|
+
objectDestructuring: 'Object destructuring is not allowed. Use explicit property access: const a = obj.a;',
|
|
12
|
+
arrayDestructuring: 'Array destructuring is not allowed. Use index access: const a = arr[0];',
|
|
13
|
+
paramDestructuring: 'Parameter destructuring is not allowed. Accept object and access properties explicitly.'
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
create(context) {
|
|
18
|
+
function reportDestructuring(node) {
|
|
19
|
+
if (node.type === 'ObjectPattern') {
|
|
20
|
+
context.report({ node, messageId: 'objectDestructuring' });
|
|
21
|
+
} else if (node.type === 'ArrayPattern') {
|
|
22
|
+
if (node.parent && node.parent.type !== 'ForOfStatement') {
|
|
23
|
+
context.report({ node, messageId: 'arrayDestructuring' });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
VariableDeclarator(node) {
|
|
30
|
+
if (node.id) reportDestructuring(node.id);
|
|
31
|
+
},
|
|
32
|
+
AssignmentExpression(node) {
|
|
33
|
+
if (node.left) reportDestructuring(node.left);
|
|
34
|
+
},
|
|
35
|
+
FunctionDeclaration(node) {
|
|
36
|
+
if (node.params) {
|
|
37
|
+
for (var i = 0; i < node.params.length; i++) {
|
|
38
|
+
var param = node.params[i];
|
|
39
|
+
if (param && param.type === 'ObjectPattern') {
|
|
40
|
+
context.report({ node: param, messageId: 'paramDestructuring' });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
ArrowFunctionExpression(node) {
|
|
46
|
+
if (node.params) {
|
|
47
|
+
for (var j = 0; j < node.params.length; j++) {
|
|
48
|
+
var param2 = node.params[j];
|
|
49
|
+
if (param2 && param2.type === 'ObjectPattern') {
|
|
50
|
+
context.report({ node: param2, messageId: 'paramDestructuring' });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
CatchClause(node) {
|
|
56
|
+
if (node.param && node.param.type === 'ObjectPattern') {
|
|
57
|
+
context.report({ node: node.param, messageId: 'objectDestructuring' });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'Disallow dynamic property access obj[variableKey] in Safe TS subset',
|
|
6
|
+
category: 'uCharts Safe TS Subset',
|
|
7
|
+
recommended: 'error'
|
|
8
|
+
},
|
|
9
|
+
schema: [{
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
allowStringLiteral: { type: 'boolean', default: false },
|
|
13
|
+
allowKnownTypes: { type: 'array', items: { type: 'string' }, default: ['Array'] }
|
|
14
|
+
},
|
|
15
|
+
additionalProperties: false
|
|
16
|
+
}],
|
|
17
|
+
messages: {
|
|
18
|
+
dynamicAccess: 'Dynamic property access on "{{objectName}}" with non-literal key is not allowed. Use Map.get() instead.'
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
create(context) {
|
|
23
|
+
var options = context.options[0] || {};
|
|
24
|
+
var allowStringLiteral = options.allowStringLiteral !== false;
|
|
25
|
+
var allowKnownTypes = new Set(options.allowKnownTypes || ['Array']);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
MemberExpression(node) {
|
|
29
|
+
if (node.computed === true) {
|
|
30
|
+
if (allowStringLiteral && node.property.type === 'Literal') return;
|
|
31
|
+
if (allowKnownTypes.has('Array')) {
|
|
32
|
+
var sourceCode = context.getSourceCode();
|
|
33
|
+
var text = sourceCode.getText(node.object);
|
|
34
|
+
if (text === 'arr' || text.endsWith('s') || text.endsWith('list')) return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
var sourceCode2 = context.getSourceCode();
|
|
38
|
+
var objectName = sourceCode2.getText(node.object);
|
|
39
|
+
context.report({
|
|
40
|
+
node,
|
|
41
|
+
messageId: 'dynamicAccess',
|
|
42
|
+
data: { objectName: objectName }
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'Disallow for...in loop in Safe TS subset',
|
|
6
|
+
category: 'uCharts Safe TS Subset',
|
|
7
|
+
recommended: 'error'
|
|
8
|
+
},
|
|
9
|
+
schema: [],
|
|
10
|
+
messages: {
|
|
11
|
+
forbidden: 'for...in loop is not allowed. Use for...of Object.keys(obj) instead.'
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
create(context) {
|
|
16
|
+
return {
|
|
17
|
+
ForInStatement(node) {
|
|
18
|
+
context.report({
|
|
19
|
+
node,
|
|
20
|
+
messageId: 'forbidden'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'Disallow Index Signature [key: string]: T in Safe TS subset',
|
|
6
|
+
category: 'uCharts Safe TS Subset',
|
|
7
|
+
recommended: 'error'
|
|
8
|
+
},
|
|
9
|
+
schema: [],
|
|
10
|
+
messages: {
|
|
11
|
+
forbidden: 'Index signature is not allowed in Safe TS subset (ArkTS forbids it). Use Map<string, T> or explicit fields instead.'
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
create(context) {
|
|
16
|
+
return {
|
|
17
|
+
TSIndexSignature(node) {
|
|
18
|
+
context.report({
|
|
19
|
+
node,
|
|
20
|
+
messageId: 'forbidden'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'Disallow nested function declarations in Safe TS subset (ArkTS forbids them)',
|
|
6
|
+
category: 'uCharts Safe TS Subset',
|
|
7
|
+
recommended: 'error'
|
|
8
|
+
},
|
|
9
|
+
schema: [{
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
allowArrowFunctions: { type: 'boolean', default: true },
|
|
13
|
+
allowMethods: { type: 'boolean', default: true }
|
|
14
|
+
},
|
|
15
|
+
additionalProperties: false
|
|
16
|
+
}],
|
|
17
|
+
messages: {
|
|
18
|
+
nestedFunction: 'Nested function declaration is not allowed in Safe TS subset. Extract to module level or use an arrow function.'
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
create(context) {
|
|
23
|
+
var options = context.options[0] || {};
|
|
24
|
+
var allowArrowFunctions = options.allowArrowFunctions !== false;
|
|
25
|
+
var allowMethods = options.allowMethods !== false;
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
FunctionDeclaration(node) {
|
|
29
|
+
var parent = node.parent;
|
|
30
|
+
if (!parent) return;
|
|
31
|
+
|
|
32
|
+
if (parent.type === 'Program' ||
|
|
33
|
+
parent.type === 'ExportNamedDeclaration' ||
|
|
34
|
+
parent.type === 'ExportDefaultDeclaration') return;
|
|
35
|
+
|
|
36
|
+
if (allowMethods &&
|
|
37
|
+
(parent.type === 'MethodDefinition' ||
|
|
38
|
+
parent.type === 'PropertyDefinition')) return;
|
|
39
|
+
|
|
40
|
+
context.report({
|
|
41
|
+
node,
|
|
42
|
+
messageId: 'nestedFunction'
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'Disallow object spread expression {...obj} in Safe TS subset',
|
|
6
|
+
category: 'uCharts Safe TS Subset',
|
|
7
|
+
recommended: 'error'
|
|
8
|
+
},
|
|
9
|
+
schema: [],
|
|
10
|
+
messages: {
|
|
11
|
+
forbidden: 'Object spread "{{expr}}" is not allowed in Safe TS subset (ArkTS only allows array spread). Use merge() from core/util/merge.ts instead.'
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
create(context) {
|
|
16
|
+
return {
|
|
17
|
+
SpreadElement(node) {
|
|
18
|
+
if (node.argument && node.argument.type === 'ObjectExpression') {
|
|
19
|
+
const sourceCode = context.getSourceCode();
|
|
20
|
+
context.report({
|
|
21
|
+
node,
|
|
22
|
+
messageId: 'forbidden',
|
|
23
|
+
data: { expr: sourceCode.getText(node) }
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'warning',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'Discourage WeakMap and Proxy usage in Safe TS subset',
|
|
6
|
+
category: 'uCharts Safe TS Subset',
|
|
7
|
+
recommended: 'warn'
|
|
8
|
+
},
|
|
9
|
+
schema: [],
|
|
10
|
+
messages: {
|
|
11
|
+
weakMap: 'WeakMap has inconsistent behavior across platforms. Use Map instead (note memory management implications).',
|
|
12
|
+
proxy: 'Proxy handler restrictions differ across platforms (especially ArkTS). Use an explicit adapter class instead.'
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
create(context) {
|
|
17
|
+
return {
|
|
18
|
+
NewExpression(node) {
|
|
19
|
+
var sourceCode = context.getSourceCode();
|
|
20
|
+
var callee = sourceCode.getText(node.callee);
|
|
21
|
+
|
|
22
|
+
if (callee === 'WeakMap' || callee === 'WeakSet') {
|
|
23
|
+
context.report({ node, messageId: 'weakMap' });
|
|
24
|
+
} else if (callee === 'Proxy') {
|
|
25
|
+
context.report({ node, messageId: 'proxy' });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'Disallow direct usage of Web APIs that do not exist in native environments',
|
|
6
|
+
category: 'uCharts Safe TS Subset',
|
|
7
|
+
recommended: 'error'
|
|
8
|
+
},
|
|
9
|
+
schema: [{
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
forbiddenApis: {
|
|
13
|
+
type: 'array',
|
|
14
|
+
items: { type: 'string' },
|
|
15
|
+
default: [
|
|
16
|
+
'console.log', 'console.warn', 'console.error', 'console.info',
|
|
17
|
+
'requestAnimationFrame', 'cancelAnimationFrame',
|
|
18
|
+
'performance.now', 'performance.measure'
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
additionalProperties: false
|
|
23
|
+
}],
|
|
24
|
+
hasSuggestions: true,
|
|
25
|
+
messages: {
|
|
26
|
+
forbidden: '"{{api}}" is a Web API not available in native platforms. Use platform abstraction from core/platform/ instead.',
|
|
27
|
+
suggestion: 'Import from core/platform/logger.ts or core/platform/animation.ts'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
create(context) {
|
|
32
|
+
var options = context.options[0] || {};
|
|
33
|
+
var forbiddenSet = new Set(options.forbiddenApis || []);
|
|
34
|
+
var sourceCode = context.getSourceCode();
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
CallExpression(node) {
|
|
38
|
+
var expr = sourceCode.getText(node.callee);
|
|
39
|
+
if (forbiddenSet.has(expr)) {
|
|
40
|
+
context.report({
|
|
41
|
+
node,
|
|
42
|
+
messageId: 'forbidden',
|
|
43
|
+
data: { api: expr },
|
|
44
|
+
suggest: [
|
|
45
|
+
{
|
|
46
|
+
desc: 'Import from core/platform/logger.ts or core/platform/animation.ts',
|
|
47
|
+
fix(fixer) {
|
|
48
|
+
if (expr.startsWith('console.')) {
|
|
49
|
+
return fixer.replaceText(node.callee, 'Logger.' + expr.slice(8));
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
MemberExpression(node) {
|
|
60
|
+
var fullExpr = sourceCode.getText(node);
|
|
61
|
+
if (forbiddenSet.has(fullExpr)) {
|
|
62
|
+
context.report({
|
|
63
|
+
node,
|
|
64
|
+
messageId: 'forbidden',
|
|
65
|
+
data: { api: fullExpr }
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'Disallow with statement in Safe TS subset',
|
|
6
|
+
category: 'uCharts Safe TS Subset',
|
|
7
|
+
recommended: 'error'
|
|
8
|
+
},
|
|
9
|
+
schema: [],
|
|
10
|
+
messages: {
|
|
11
|
+
forbidden: 'with statement is not allowed. Use temporary variables instead.'
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
create(context) {
|
|
16
|
+
return {
|
|
17
|
+
WithStatement(node) {
|
|
18
|
+
context.report({
|
|
19
|
+
node,
|
|
20
|
+
messageId: 'forbidden'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'suggestion',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'Require using null instead of undefined in Safe TS subset',
|
|
6
|
+
category: 'uCharts Safe TS Subset',
|
|
7
|
+
recommended: 'error'
|
|
8
|
+
},
|
|
9
|
+
schema: [],
|
|
10
|
+
messages: {
|
|
11
|
+
useNull: 'Use null instead of undefined (UTS/ArkTS do not support undefined).',
|
|
12
|
+
useNullCoalescing: 'Use ?? null instead of ?? undefined.',
|
|
13
|
+
useNotNull: 'Use != null instead of !== undefined.',
|
|
14
|
+
useEqualNull: 'Use == null instead of === undefined.',
|
|
15
|
+
removeOptional: 'Remove optional ? modifier and use explicit null union (T | null).'
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
create(context) {
|
|
20
|
+
var sourceCode = context.getSourceCode();
|
|
21
|
+
|
|
22
|
+
function checkUndefined(node) {
|
|
23
|
+
if (node.type !== 'Identifier' || node.name !== 'undefined') return;
|
|
24
|
+
context.report({ node, messageId: 'useNull' });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
Identifier: checkUndefined,
|
|
29
|
+
|
|
30
|
+
UnaryExpression(node) {
|
|
31
|
+
if (node.operator !== 'typeof') return;
|
|
32
|
+
checkUndefined(node.argument);
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
BinaryExpression(node) {
|
|
36
|
+
var raw = sourceCode.getText(node);
|
|
37
|
+
|
|
38
|
+
if (raw.includes('undefined')) {
|
|
39
|
+
if (node.operator === '===') {
|
|
40
|
+
context.report({ node, messageId: 'useEqualNull' });
|
|
41
|
+
} else if (node.operator === '!==') {
|
|
42
|
+
context.report({ node, messageId: 'useNotNull' });
|
|
43
|
+
} else if (raw.includes('?? undefined')) {
|
|
44
|
+
context.report({ node, messageId: 'useNullCoalescing' });
|
|
45
|
+
} else if (raw.includes('|| undefined')) {
|
|
46
|
+
context.report({ node, messageId: 'useNullCoalescing' });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qiun/eslint-plugin-ucharts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "ESLint plugin enforcing Safe TS Subset rules for uCharts cross-platform compatibility (Swift/Kotlin/ArkTS)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"eslint",
|
|
8
|
+
"eslintplugin",
|
|
9
|
+
"eslint-plugin",
|
|
10
|
+
"ucharts",
|
|
11
|
+
"safe-ts-subset",
|
|
12
|
+
"arkts",
|
|
13
|
+
"uts",
|
|
14
|
+
"cross-platform"
|
|
15
|
+
],
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"author": "秋云",
|
|
18
|
+
"homepage": "https://www.ucharts.cn",
|
|
19
|
+
"files": [
|
|
20
|
+
"index.js",
|
|
21
|
+
"lib/",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"eslint": ">=8.0.0"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=16.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|