@rpcbase/eslint-config 0.17.0 → 0.19.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/eslint-config",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,7 +18,8 @@
18
18
  "dependencies": [],
19
19
  "files": [
20
20
  "package.json",
21
- "eslint.config.js"
21
+ "eslint.config.js",
22
+ "src/"
22
23
  ],
23
24
  "output": [],
24
25
  "env": {
@@ -33,6 +34,7 @@
33
34
  },
34
35
  "dependencies": {
35
36
  "@eslint/js": "9.26.0",
37
+ "@typescript-eslint/experimental-utils": "5.62.0",
36
38
  "eslint-config-flat-gitignore": "2.1.0",
37
39
  "eslint-import-resolver-typescript": "4.3.4",
38
40
  "eslint-plugin-import": "2.31.0",
@@ -43,7 +45,7 @@
43
45
  },
44
46
  "devDependencies": {
45
47
  "@types/eslint": "9.6.1",
46
- "typescript": "^5.x",
47
- "eslint": "^9.x"
48
+ "eslint": "^9.x",
49
+ "typescript": "^5.x"
48
50
  }
49
51
  }
@@ -0,0 +1,160 @@
1
+ import { TSESTree, ESLintUtils } from "@typescript-eslint/experimental-utils"
2
+
3
+ type MessageIds = "loneText"
4
+
5
+ /**
6
+ * Options accepted by the rule.
7
+ * Currently only `ignoredChars` is supported.
8
+ */
9
+ type RuleOptions = {
10
+ /**
11
+ * Additional characters that should be treated as "ignorable"
12
+ * when they appear as the only non-whitespace content of a text node.
13
+ */
14
+ ignoredChars?: string[]
15
+ }
16
+
17
+ type Options = [RuleOptions]
18
+
19
+ const createRule = ESLintUtils.RuleCreator(
20
+ name => `@rpcbase/eslint-config/rules/${name}`
21
+ )
22
+
23
+ const defaultIgnoredChars = ["*", ":", "·", "•", "—", "–", "…", "-", "+", "(", ")", "\"", "\'"]
24
+
25
+ function isIgnoredText(text: string, ignored: string[]) {
26
+ // ignore if every non-whitespace char is in the list
27
+ return (
28
+ text.length > 0 &&
29
+ text.split("").every(ch => ch.trim().length === 0 || ignored.includes(ch))
30
+ )
31
+ }
32
+
33
+ function isMeaningful(child: TSESTree.JSXChild, ignored: string[]): boolean {
34
+ if (child.type === "JSXText") {
35
+ return !isIgnoredText(child.value.trim(), ignored) && child.value.trim().length > 0
36
+ }
37
+
38
+ if (
39
+ child.type === "JSXExpressionContainer" &&
40
+ child.expression.type === "Literal" &&
41
+ typeof child.expression.value === "string"
42
+ ) {
43
+ return child.expression.value.trim().length > 0
44
+ }
45
+
46
+ // Any non-text node counts as meaningful
47
+ return true
48
+ }
49
+
50
+ export const rules = {
51
+ "no-lone-text-node": createRule<Options, MessageIds>({
52
+ name: "no-lone-text-node",
53
+ meta: {
54
+ type: "problem",
55
+ docs: {
56
+ description: "disallow raw text nodes in JSX",
57
+ recommended: "error"
58
+ },
59
+ messages: {
60
+ loneText:
61
+ "Wrap text '{{text}}' in an element (e.g. <span>) to avoid Google Translate crashes."
62
+ },
63
+ schema: [
64
+ {
65
+ type: "object",
66
+ properties: {
67
+ ignoredChars: {
68
+ type: "array",
69
+ items: { type: "string" }
70
+ }
71
+ },
72
+ additionalProperties: false
73
+ }
74
+ ]
75
+ },
76
+ defaultOptions: [{}],
77
+ create(context) {
78
+ //--------------------------------------------------------------------
79
+ // helpers
80
+ //--------------------------------------------------------------------
81
+ function report(node: TSESTree.Node, value: string) {
82
+ context.report({ node, messageId: "loneText", data: { text: value } })
83
+ }
84
+
85
+ //--------------------------------------------------------------------
86
+ // visitors
87
+ //--------------------------------------------------------------------
88
+ return {
89
+ JSXElement(node) {
90
+ const options = context.options[0] ?? {}
91
+ const ignored = options.ignoredChars ?? defaultIgnoredChars
92
+
93
+ // all meaningful children (ignoring whitespace & ignored chars)
94
+ const meaningfulChildren = node.children.filter(child =>
95
+ isMeaningful(child, ignored)
96
+ )
97
+
98
+ const singleMeaningfulTextChild =
99
+ meaningfulChildren.length === 1 &&
100
+ (meaningfulChildren[0].type === "JSXText" ||
101
+ (meaningfulChildren[0].type === "JSXExpressionContainer" &&
102
+ meaningfulChildren[0].expression.type === "Literal" &&
103
+ typeof meaningfulChildren[0].expression.value === "string"))
104
+
105
+ node.children.forEach(child => {
106
+ // --------------------------------------------------------------
107
+ // raw JSX text
108
+ // --------------------------------------------------------------
109
+ if (
110
+ child.type === "JSXText" &&
111
+ !isIgnoredText(child.value.trim(), ignored) &&
112
+ child.value.trim().length > 0
113
+ ) {
114
+ if (singleMeaningfulTextChild) return
115
+ report(child, child.value.trim())
116
+ }
117
+
118
+ // --------------------------------------------------------------
119
+ // {"string literal"} expression
120
+ // --------------------------------------------------------------
121
+ if (
122
+ child.type === "JSXExpressionContainer" &&
123
+ child.expression.type === "Literal" &&
124
+ typeof child.expression.value === "string" &&
125
+ child.expression.value.trim().length > 0
126
+ ) {
127
+ if (singleMeaningfulTextChild) return
128
+ report(child, child.expression.value.trim())
129
+ }
130
+ })
131
+ },
132
+
133
+ // Fragments: always report raw text, even if it's the only child
134
+ JSXFragment(node) {
135
+ const options = context.options[0] ?? {}
136
+ const ignored = options.ignoredChars ?? defaultIgnoredChars
137
+
138
+ node.children.forEach(child => {
139
+ if (
140
+ child.type === "JSXText" &&
141
+ !isIgnoredText(child.value.trim(), ignored) &&
142
+ child.value.trim().length > 0
143
+ ) {
144
+ report(child, child.value.trim())
145
+ }
146
+
147
+ if (
148
+ child.type === "JSXExpressionContainer" &&
149
+ child.expression.type === "Literal" &&
150
+ typeof child.expression.value === "string" &&
151
+ child.expression.value.trim().length > 0
152
+ ) {
153
+ report(child, child.expression.value.trim())
154
+ }
155
+ })
156
+ }
157
+ }
158
+ }
159
+ })
160
+ }