@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 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
+ }