@foundbyte/eslint-plugin 1.1.5-alpha.0 → 1.1.5-alpha.1

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 CHANGED
@@ -77,6 +77,13 @@ module.exports = {
77
77
  },
78
78
  ],
79
79
  '@foundbyte/require-json-parse-type': 'error',
80
+ '@foundbyte/no-restricted-literal-types': [
81
+ 'error',
82
+ {
83
+ forbidden: ['null', 'undefined', ''],
84
+ allowInUnions: true,
85
+ },
86
+ ],
80
87
  },
81
88
  }
82
89
  ```
@@ -631,9 +638,15 @@ const a: { s: number } = JSON.parse('{s:1}')
631
638
  // ✅ 正确 - 解构赋值带有显式类型注解
632
639
  const { s }: { s: number } = JSON.parse('{s:1}')
633
640
 
641
+ // ✅ 正确 - 三元表达式中的 JSON.parse 带有显式类型注解
642
+ const qualification: string[] = info.qualification ? JSON.parse(info.qualification) : []
643
+
634
644
  // ❌ 错误 - 缺少类型注解
635
645
  const a = JSON.parse('{s:1}')
636
646
 
647
+ // ❌ 错误 - 三元表达式中的 JSON.parse 缺少类型注解
648
+ const qualification = info.qualification ? JSON.parse(info.qualification) : []
649
+
637
650
  // ❌ 错误 - 使用 as 断言不算作变量声明类型注解
638
651
  const a = JSON.parse('{s:1}') as { s: number }
639
652
  ```
@@ -646,6 +659,73 @@ const a = JSON.parse('{s:1}') as { s: number }
646
659
 
647
660
  **注意:**
648
661
 
649
- - 该规则只检查变量声明的初始化表达式直接是 `JSON.parse(...)` 的情况
662
+ - 该规则检查变量声明的初始化表达式中是否包含 `JSON.parse(...)`,包括三元表达式、逻辑表达式、对象/数组字面量等场景
650
663
  - 对于 `const a = JSON.parse('...') as Type` 也会报错,因为它要求类型注解必须声明在变量本身
664
+ - 函数体内部的 `JSON.parse` 不影响外层变量,不会被误判
651
665
  - 如希望允许 `as` 类型断言,请搭配 `@foundbyte/no-initial-as-assertion` 规则按需配置
666
+
667
+ ### no-restricted-literal-types
668
+
669
+ 禁止在类型注解中使用指定的无意义字面量类型,例如 `null`、`undefined`、空字符串字面量 `''` 等。
670
+
671
+ **配置示例:**
672
+
673
+ ```javascript
674
+ // 基本用法 - 禁止 null、undefined 和空字符串字面量类型
675
+ '@foundbyte/no-restricted-literal-types': ['error', {
676
+ forbidden: ['null', 'undefined', ''],
677
+ }]
678
+
679
+ // 允许联合类型中的 null / undefined
680
+ '@foundbyte/no-restricted-literal-types': ['error', {
681
+ forbidden: ['null', 'undefined'],
682
+ allowInUnions: true,
683
+ }]
684
+ ```
685
+
686
+ **示例:**
687
+
688
+ ```typescript
689
+ // ❌ 错误 - 无意义的 null 类型
690
+ interface OrderItem {
691
+ seatName: null
692
+ refundStatus: null
693
+ }
694
+
695
+ // ❌ 错误 - 无意义的 undefined 类型
696
+ interface Config {
697
+ value: undefined
698
+ }
699
+
700
+ // ❌ 错误 - 无意义的空字符串字面量类型
701
+ interface Query {
702
+ keyword: ''
703
+ }
704
+
705
+ // ✅ 正确 - 使用具体类型
706
+ interface OrderItem {
707
+ seatName: string
708
+ refundStatus: number
709
+ }
710
+
711
+ // ✅ 正确 - 联合类型中的 null / undefined 是有意义的(当 allowInUnions 为 true 时)
712
+ interface User {
713
+ phone: string | null
714
+ email?: string
715
+ }
716
+ ```
717
+
718
+ **配置选项:**
719
+
720
+ - `forbidden` (array, 必填): 要禁止的字面量类型列表
721
+ - 支持关键字类型:`'null'`、`'undefined'`、`'void'`、`'never'`、`'any'`、`'unknown'`、`'boolean'`、`'number'`、`'string'`、`'symbol'`、`'bigint'`、`'object'`
722
+ - 支持字面量值:`''`、`0`、`true`、`false` 等
723
+
724
+ - `allowInUnions` (boolean, 默认 `false`): 是否允许在联合类型中使用
725
+ - 为 `true` 时,`string | null` 不会被报错,但独立的 `null` 类型仍然会报错
726
+ - 为 `false` 时,任何出现 `null` / `undefined` 等的地方都会报错
727
+
728
+ **注意:**
729
+
730
+ - 该规则针对类型注解中的字面量,不影响运行时值,例如 `const a = null` 不会触发
731
+ - 建议配合 TypeScript 严格模式使用,逐步替换项目中的无意义类型声明
package/index.js CHANGED
@@ -9,6 +9,7 @@ const noExcessiveAnyRule = require('./lib/rules/no-excessive-any')
9
9
  const noDisabledCommentsRule = require('./lib/rules/no-disabled-comments')
10
10
  const noRestrictedComponentsRule = require('./lib/rules/no-restricted-components')
11
11
  const requireJsonParseTypeRule = require('./lib/rules/require-json-parse-type')
12
+ const noRestrictedLiteralTypesRule = require('./lib/rules/no-restricted-literal-types')
12
13
 
13
14
  // 限制属性,同时相当于限制某些组件禁止使用
14
15
  const restrictions = [
@@ -44,6 +45,7 @@ module.exports = {
44
45
  'no-disabled-comments': noDisabledCommentsRule,
45
46
  'no-restricted-components': noRestrictedComponentsRule,
46
47
  'require-json-parse-type': requireJsonParseTypeRule,
48
+ 'no-restricted-literal-types': noRestrictedLiteralTypesRule,
47
49
  },
48
50
  configs: {
49
51
  customize: (options) => {
@@ -0,0 +1,22 @@
1
+ import { Rule } from 'eslint'
2
+
3
+ export interface NoRestrictedLiteralTypesOptions {
4
+ /**
5
+ * 要禁止的字面量类型列表
6
+ * @example ['null', 'undefined', '']
7
+ */
8
+ forbidden: Array<string | number | boolean>
9
+
10
+ /**
11
+ * 是否允许在联合类型中使用,例如 string | null
12
+ * @default false
13
+ */
14
+ allowInUnions?: boolean
15
+ }
16
+
17
+ /**
18
+ * 禁止在类型注解中使用指定的字面量类型
19
+ */
20
+ declare const rule: Rule.RuleModule
21
+
22
+ export default rule
@@ -0,0 +1,158 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'suggestion',
4
+ docs: {
5
+ description: '禁止在类型注解中使用指定的字面量类型',
6
+ category: 'Best Practices',
7
+ recommended: false,
8
+ },
9
+ fixable: null,
10
+ schema: [
11
+ {
12
+ type: 'object',
13
+ properties: {
14
+ forbidden: {
15
+ type: 'array',
16
+ items: {
17
+ anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }],
18
+ },
19
+ description:
20
+ '要禁止的字面量类型列表,支持 null、undefined、void、never、any、unknown 等关键字,以及空字符串、0、true 等具体字面量',
21
+ },
22
+ allowInUnions: {
23
+ type: 'boolean',
24
+ default: false,
25
+ description: '是否允许在联合类型中使用,例如 string | null',
26
+ },
27
+ },
28
+ required: ['forbidden'],
29
+ additionalProperties: false,
30
+ },
31
+ ],
32
+ messages: {
33
+ noRestrictedLiteralType: '禁止在类型中使用 "{{type}}" 字面量类型',
34
+ },
35
+ },
36
+
37
+ create(context) {
38
+ const options = context.options[0] || {}
39
+ const forbidden = options.forbidden || []
40
+ const allowInUnions = options.allowInUnions || false
41
+
42
+ if (forbidden.length === 0) {
43
+ return {}
44
+ }
45
+
46
+ const forbiddenStrings = new Set()
47
+ const forbiddenNumbers = new Set()
48
+ const forbiddenBooleans = new Set()
49
+
50
+ forbidden.forEach((item) => {
51
+ if (typeof item === 'string') {
52
+ forbiddenStrings.add(item)
53
+ } else if (typeof item === 'number') {
54
+ forbiddenNumbers.add(item)
55
+ } else if (typeof item === 'boolean') {
56
+ forbiddenBooleans.add(item)
57
+ }
58
+ })
59
+
60
+ // 关键字类型映射
61
+ const keywordTypeMap = {
62
+ null: 'TSNullKeyword',
63
+ undefined: 'TSUndefinedKeyword',
64
+ void: 'TSVoidKeyword',
65
+ never: 'TSNeverKeyword',
66
+ any: 'TSAnyKeyword',
67
+ unknown: 'TSUnknownKeyword',
68
+ boolean: 'TSBooleanKeyword',
69
+ number: 'TSNumberKeyword',
70
+ string: 'TSStringKeyword',
71
+ symbol: 'TSSymbolKeyword',
72
+ bigint: 'TSBigIntKeyword',
73
+ object: 'TSObjectKeyword',
74
+ }
75
+
76
+ /**
77
+ * 检查类型是否位于联合类型中
78
+ * @param {Object} node AST 节点
79
+ * @returns {boolean}
80
+ */
81
+ function isInUnion(node) {
82
+ return node.parent && node.parent.type === 'TSUnionType'
83
+ }
84
+
85
+ /**
86
+ * 获取匹配到的 forbidden 值
87
+ * @param {Object} node AST 节点
88
+ * @returns {string|number|boolean|null}
89
+ */
90
+ function getMatchedForbidden(node) {
91
+ if (allowInUnions && isInUnion(node)) {
92
+ return null
93
+ }
94
+
95
+ // 关键字类型检查
96
+ for (const [key, typeName] of Object.entries(keywordTypeMap)) {
97
+ if (forbiddenStrings.has(key) && node.type === typeName) {
98
+ return key
99
+ }
100
+ }
101
+
102
+ // 字面量类型检查
103
+ if (node.type === 'TSLiteralType' && node.literal) {
104
+ const literal = node.literal
105
+
106
+ if (literal.type === 'Literal') {
107
+ if (typeof literal.value === 'string' && forbiddenStrings.has(literal.value)) {
108
+ return literal.value
109
+ }
110
+ if (typeof literal.value === 'number' && forbiddenNumbers.has(literal.value)) {
111
+ return literal.value
112
+ }
113
+ if (typeof literal.value === 'boolean' && forbiddenBooleans.has(literal.value)) {
114
+ return literal.value
115
+ }
116
+ }
117
+ }
118
+
119
+ return null
120
+ }
121
+
122
+ const targetTypes = [
123
+ 'TSNullKeyword',
124
+ 'TSUndefinedKeyword',
125
+ 'TSVoidKeyword',
126
+ 'TSNeverKeyword',
127
+ 'TSAnyKeyword',
128
+ 'TSUnknownKeyword',
129
+ 'TSBooleanKeyword',
130
+ 'TSNumberKeyword',
131
+ 'TSStringKeyword',
132
+ 'TSSymbolKeyword',
133
+ 'TSBigIntKeyword',
134
+ 'TSObjectKeyword',
135
+ 'TSLiteralType',
136
+ ]
137
+
138
+ const visitors = {}
139
+ targetTypes.forEach((type) => {
140
+ visitors[type] = function (node) {
141
+ const matched = getMatchedForbidden(node)
142
+ if (matched === null) {
143
+ return
144
+ }
145
+
146
+ context.report({
147
+ node,
148
+ messageId: 'noRestrictedLiteralType',
149
+ data: {
150
+ type: String(matched),
151
+ },
152
+ })
153
+ }
154
+ })
155
+
156
+ return visitors
157
+ },
158
+ }
@@ -80,6 +80,43 @@ module.exports = {
80
80
  return node
81
81
  }
82
82
 
83
+ /**
84
+ * 递归检查表达式中是否包含 JSON.parse(...) 调用
85
+ * 用于处理三元表达式、逻辑表达式、对象/数组字面量等场景
86
+ * @param {Object} node AST 节点
87
+ * @returns {boolean}
88
+ */
89
+ function containsJsonParse(node) {
90
+ if (!node || typeof node !== 'object') {
91
+ return false
92
+ }
93
+
94
+ const unwrapped = unwrapExpression(node)
95
+ if (isJsonParseCall(unwrapped)) {
96
+ return true
97
+ }
98
+
99
+ // 不进入函数体内部,函数体是独立作用域,不影响外层变量类型
100
+ if (unwrapped.type === 'ArrowFunctionExpression' || unwrapped.type === 'FunctionExpression') {
101
+ return false
102
+ }
103
+
104
+ return Object.keys(unwrapped).some((key) => {
105
+ if (key === 'parent' || key === 'loc' || key === 'range') {
106
+ return false
107
+ }
108
+
109
+ const value = unwrapped[key]
110
+ if (Array.isArray(value)) {
111
+ return value.some((child) => containsJsonParse(child))
112
+ }
113
+ if (value && typeof value === 'object' && value.type) {
114
+ return containsJsonParse(value)
115
+ }
116
+ return false
117
+ })
118
+ }
119
+
83
120
  /**
84
121
  * 检查变量声明是否使用了 JSON.parse 并且缺少显式类型注解
85
122
  * @param {Object} node VariableDeclaration 节点
@@ -94,8 +131,7 @@ module.exports = {
94
131
  return
95
132
  }
96
133
 
97
- const unwrappedInit = unwrapExpression(declaration.init)
98
- if (!isJsonParseCall(unwrappedInit)) {
134
+ if (!containsJsonParse(declaration.init)) {
99
135
  return
100
136
  }
101
137
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foundbyte/eslint-plugin",
3
- "version": "1.1.5-alpha.0",
3
+ "version": "1.1.5-alpha.1",
4
4
  "description": "ESLint plugin for enforcing code style rules including arrow function padding, Vue script lines limit, and TypeScript type comment requirements",
5
5
  "author": "",
6
6
  "license": "ISC",