@astroscope/eslint-plugin-i18n 0.1.1 → 0.1.3

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.
Files changed (2) hide show
  1. package/dist/index.js +135 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -41,14 +41,21 @@ var DEFAULT_IGNORE_PATTERNS = [
41
41
  // numbers
42
42
  /^\s*\|\s*$/,
43
43
  // pipe separators
44
- /^\s*[•·–—]\s*$/
44
+ /^\s*[•·–—]\s*$/,
45
45
  // bullets / dashes
46
+ /^[\s\p{P}\p{S}]+$/u,
47
+ // punctuation / symbols only (e.g. "(", ",", "/ —")
48
+ /^#[0-9a-fA-F]{3,8}$/,
49
+ // hex colors (e.g. "#003366", "#fff")
50
+ /^\d+x\d+$/
51
+ // dimensions (e.g. "180x180")
46
52
  ];
47
53
  var DEFAULT_IGNORE_ATTRIBUTES = [
48
- "className",
49
- "class",
54
+ // html attributes
50
55
  "id",
51
- "key",
56
+ "class",
57
+ "className",
58
+ "style",
52
59
  "href",
53
60
  "src",
54
61
  "type",
@@ -60,9 +67,93 @@ var DEFAULT_IGNORE_ATTRIBUTES = [
60
67
  "rel",
61
68
  "method",
62
69
  "action",
70
+ "loading",
71
+ "decoding",
72
+ "autoComplete",
73
+ "allow",
74
+ "as",
75
+ "sizes",
76
+ "color",
77
+ "crossorigin",
78
+ "fetchPriority",
79
+ "fetchpriority",
80
+ "referrerpolicy",
81
+ "charset",
82
+ "lang",
83
+ "property",
84
+ "itemprop",
85
+ "itemtype",
86
+ "form",
87
+ "key",
88
+ "slot",
89
+ // common component props
90
+ "variant",
91
+ "size",
92
+ "mode",
93
+ "orientation",
94
+ "align",
95
+ "icon",
96
+ "tag",
97
+ "tagName",
98
+ // html aria attributes (non-text)
99
+ "aria-hidden",
100
+ "aria-live",
101
+ "aria-atomic",
102
+ // astro attributes
103
+ "class:list",
104
+ // testing attributes
63
105
  "data-testid",
64
106
  "data-cy",
65
- "slot"
107
+ // svg attributes
108
+ "d",
109
+ "viewBox",
110
+ "xmlns",
111
+ "fill",
112
+ "stroke",
113
+ "strokeWidth",
114
+ "strokeLinecap",
115
+ "strokeLinejoin",
116
+ "clipPath",
117
+ "transform",
118
+ "points",
119
+ "pathLength",
120
+ "filter",
121
+ "filterUnits",
122
+ "colorInterpolationFilters",
123
+ "floodOpacity",
124
+ "in",
125
+ "in2",
126
+ "result",
127
+ "stroke-linecap",
128
+ "stroke-linejoin",
129
+ "clip-rule",
130
+ "fill-rule",
131
+ "path",
132
+ "values",
133
+ "operator",
134
+ "mode",
135
+ "stdDeviation",
136
+ "dy",
137
+ "dx",
138
+ "x",
139
+ "y",
140
+ "x1",
141
+ "x2",
142
+ "y1",
143
+ "y2",
144
+ "cx",
145
+ "cy",
146
+ "r",
147
+ "rx",
148
+ "ry",
149
+ "width",
150
+ "height"
151
+ ];
152
+ var DEFAULT_IGNORE_ATTRIBUTE_PATTERNS = [
153
+ /classNames?$/i,
154
+ // *ClassName, *classNames, *classname (e.g. labelClassName, classNames)
155
+ /^data-/
156
+ // data-* attributes
66
157
  ];
67
158
  var noRawStringsInJsx = {
68
159
  meta: {
@@ -92,21 +183,30 @@ var noRawStringsInJsx = {
92
183
  },
93
184
  create(context) {
94
185
  const options = context.options[0] ?? {};
95
- const userPatterns = (options.ignorePatterns ?? []).map((p) => new RegExp(p));
96
- const allPatterns = [...DEFAULT_IGNORE_PATTERNS, ...userPatterns];
97
- const ignoreAttributes = /* @__PURE__ */ new Set([...DEFAULT_IGNORE_ATTRIBUTES, ...options.ignoreAttributes ?? []]);
186
+ const allPatterns = options.ignorePatterns ? options.ignorePatterns.map((p) => new RegExp(p)) : DEFAULT_IGNORE_PATTERNS;
187
+ const ignoreAttributes = new Set(options.ignoreAttributes ?? DEFAULT_IGNORE_ATTRIBUTES);
98
188
  function shouldIgnore(text) {
99
189
  return allPatterns.some((p) => p.test(text));
100
190
  }
101
- function isInsideIgnoredAttribute(node) {
102
- const parent = node.parent;
103
- if (parent?.type === "JSXAttribute") {
104
- const attrName = parent.name?.type === "JSXIdentifier" ? parent.name.name : parent.name?.type === "JSXNamespacedName" ? `${parent.name.namespace.name}:${parent.name.name.name}` : null;
105
- if (attrName && ignoreAttributes.has(attrName)) return true;
191
+ function shouldIgnoreAttribute(name) {
192
+ if (ignoreAttributes.has(name)) return true;
193
+ return DEFAULT_IGNORE_ATTRIBUTE_PATTERNS.some((p) => p.test(name));
194
+ }
195
+ function getAttributeName(node) {
196
+ if (node.name?.type === "JSXIdentifier") return node.name.name;
197
+ if (node.name?.type === "JSXNamespacedName") {
198
+ return `${node.name.namespace.name}:${node.name.name.name}`;
106
199
  }
107
- if (parent?.type === "JSXExpressionContainer" && parent.parent?.type === "JSXAttribute") {
108
- const attrName = parent.parent.name?.type === "JSXIdentifier" ? parent.parent.name.name : null;
109
- if (attrName && ignoreAttributes.has(attrName)) return true;
200
+ return null;
201
+ }
202
+ function isInsideIgnoredAttribute(node) {
203
+ let current = node.parent;
204
+ while (current) {
205
+ if (current.type === "JSXAttribute") {
206
+ const attrName = getAttributeName(current);
207
+ return attrName ? shouldIgnoreAttribute(attrName) : false;
208
+ }
209
+ current = current.parent;
110
210
  }
111
211
  return false;
112
212
  }
@@ -130,12 +230,30 @@ var noRawStringsInJsx = {
130
230
  if (shouldIgnore(text)) return;
131
231
  if (isInsideIgnoredAttribute(node)) return;
132
232
  const attrName = node.name?.type === "JSXIdentifier" ? node.name.name : node.name?.type === "JSXNamespacedName" ? `${node.name.namespace.name}:${node.name.name.name}` : null;
133
- if (attrName && ignoreAttributes.has(attrName)) return;
233
+ if (attrName && shouldIgnoreAttribute(attrName)) return;
134
234
  context.report({
135
235
  node: node.value,
136
236
  messageId: "rawString",
137
237
  data: { text: text.length > 40 ? `${text.slice(0, 40)}...` : text }
138
238
  });
239
+ },
240
+ // string literals inside JSX expressions: <div title={value ?? 'fallback'} /> or <div>{'text'}</div>
241
+ Literal(node) {
242
+ if (typeof node.value !== "string") return;
243
+ const ancestors = context.sourceCode.getAncestors(node);
244
+ const inJsxExpression = ancestors.some((a) => a.type === "JSXExpressionContainer");
245
+ if (!inJsxExpression) return;
246
+ if (ancestors.some((a) => a.type === "CallExpression")) return;
247
+ if (node.parent?.type === "BinaryExpression") return;
248
+ if (node.parent?.type === "TSAsExpression") return;
249
+ const text = node.value;
250
+ if (shouldIgnore(text)) return;
251
+ if (isInsideIgnoredAttribute(node)) return;
252
+ context.report({
253
+ node,
254
+ messageId: "rawString",
255
+ data: { text: text.length > 40 ? `${text.slice(0, 40)}...` : text }
256
+ });
139
257
  }
140
258
  };
141
259
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astroscope/eslint-plugin-i18n",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "ESLint rules for @astroscope/i18n",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",