eslint-plugin-svg 0.0.0 → 0.0.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
@@ -8,8 +8,8 @@
8
8
 
9
9
  > Rules for consistent, readable and valid SVG files.
10
10
 
11
- > [!WARNING]
12
- > This plugin is Work In Progress. API may change at every release. Please use with caution.
11
+ > [!CAUTION]
12
+ > Status: Work In Progress. Not ready for use.
13
13
 
14
14
  ## Install
15
15
 
@@ -36,8 +36,8 @@ import pluginSvg from 'eslint-plugin-svg'
36
36
  * @type {import('eslint').Linter.Config[]}
37
37
  */
38
38
  export default [
39
- ...pluginSvg.configs.recommended,
40
39
  // Other configs...
40
+ ...pluginSvg.configs.recommended,
41
41
  ]
42
42
  ```
43
43
 
@@ -50,8 +50,13 @@ export default [
50
50
  🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
51
51
  💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
52
52
 
53
- | Name | Description | 💼 | 🔧 | 💡 |
54
- | :--- | :---------- | :-: | :-: | :-: |
53
+ | Name | Description | 💼 | 🔧 | 💡 |
54
+ | :--------------------------------------------------------------------------------- | :------------------------------- | :-: | :-: | :-: |
55
+ | [no-empty-title](https://eslint-plugin-svg.ntnyq.com/rules/no-empty-title) | Disallow empty title element | ✅ | | |
56
+ | [no-empty-desc](https://eslint-plugin-svg.ntnyq.com/rules/no-empty-desc) | Disallow empty desc element | ✅ | | |
57
+ | [no-empty-text](https://eslint-plugin-svg.ntnyq.com/rules/no-empty-text) | Disallow empty text element | ✅ | | |
58
+ | [no-empty-container](https://eslint-plugin-svg.ntnyq.com/rules/no-empty-container) | Disallow empty container element | ✅ | | |
59
+ | [no-deprecated](https://eslint-plugin-svg.ntnyq.com/rules/no-deprecated) | Disallow deprecated elements | ✅ | | |
55
60
 
56
61
  ## License
57
62
 
package/dist/index.cjs CHANGED
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
+ for (var name2 in all)
10
+ __defProp(target, name2, { get: all[name2], enumerable: true });
9
11
  };
10
12
  var __copyProps = (to, from, except, desc) => {
11
13
  if (from && typeof from === "object" || typeof from === "function") {
@@ -15,30 +17,35 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
21
- var src_exports = {};
22
- __export(src_exports, {
31
+ var index_exports = {};
32
+ __export(index_exports, {
23
33
  configs: () => configs,
24
- createRecommendedConfig: () => createRecommendedConfig,
25
- default: () => src_default,
34
+ createConfig: () => createConfig,
35
+ default: () => index_default,
26
36
  meta: () => meta,
27
37
  plugin: () => plugin,
28
38
  recommended: () => recommended,
29
39
  rules: () => rules
30
40
  });
31
- module.exports = __toCommonJS(src_exports);
41
+ module.exports = __toCommonJS(index_exports);
32
42
 
33
43
  // src/configs/index.ts
34
- function createRecommendedConfig(options = {}) {
44
+ var parserSVG = __toESM(require("svg-eslint-parser"), 1);
45
+ function createConfig(options = {}) {
35
46
  const config = {
36
47
  ...options,
37
- // Overrides
38
- name: options.name || "svg/recommended",
39
48
  files: options.files || ["**/*.svg"],
40
- ignores: options.ignores || [],
41
- languageOptions: options.languageOptions,
42
49
  plugins: {
43
50
  ...options.plugins || {},
44
51
  /* v8 ignore start */
@@ -47,28 +54,410 @@ function createRecommendedConfig(options = {}) {
47
54
  }
48
55
  /* v8 ignore stop */
49
56
  },
57
+ languageOptions: {
58
+ ...options.languageOptions || {},
59
+ parser: parserSVG
60
+ },
50
61
  rules: {
51
- ...options.overridesRules || {}
62
+ ...options.rules
52
63
  }
53
64
  };
54
65
  return config;
55
66
  }
56
67
  var recommended = [
57
- // flat recommended config
58
- createRecommendedConfig()
68
+ createConfig({
69
+ name: "svg/recommended",
70
+ // @keep-sorted
71
+ rules: {
72
+ "svg/no-deprecated": "error",
73
+ "svg/no-empty-container": "error",
74
+ "svg/no-empty-desc": "error",
75
+ "svg/no-empty-text": "error",
76
+ "svg/no-empty-title": "error"
77
+ }
78
+ })
59
79
  ];
60
80
  var configs = {
61
81
  recommended
62
82
  };
63
83
 
84
+ // package.json
85
+ var name = "eslint-plugin-svg";
86
+ var version = "0.0.1";
87
+
64
88
  // src/meta.ts
65
89
  var meta = {
66
- name: "eslint-plugin-svg",
67
- version: "0.0.0"
90
+ name,
91
+ version
68
92
  };
69
93
 
94
+ // src/constants/index.ts
95
+ var CONTAINER_ELEMENTS = [
96
+ "a",
97
+ "defs",
98
+ "g",
99
+ "marker",
100
+ "mask",
101
+ "missing-glyph",
102
+ "pattern",
103
+ "svg",
104
+ "switch",
105
+ "symbol"
106
+ ];
107
+ var DEPRECATED_ELEMENTS = [
108
+ "altGlyph",
109
+ "altGlyphDef",
110
+ "altGlyphItem",
111
+ "animateColor",
112
+ "cursor",
113
+ "font",
114
+ "font-face",
115
+ "font-face-format",
116
+ "font-face-name",
117
+ "font-face-src",
118
+ "font-face-uri",
119
+ "glyph",
120
+ "glyphRef",
121
+ "hkern",
122
+ "missing-glyph",
123
+ "tref",
124
+ "vkern"
125
+ ];
126
+
127
+ // src/utils/merge.ts
128
+ function isObjectNotArray(obj) {
129
+ return typeof obj === "object" && obj != null && !Array.isArray(obj);
130
+ }
131
+ function deepMerge(first = {}, second = {}) {
132
+ const keys = new Set(Object.keys(first).concat(Object.keys(second)));
133
+ return Array.from(keys).reduce((acc, key) => {
134
+ const firstHasKey = key in first;
135
+ const secondHasKey = key in second;
136
+ const firstValue = first[key];
137
+ const secondValue = second[key];
138
+ if (firstHasKey && secondHasKey) {
139
+ if (isObjectNotArray(firstValue) && isObjectNotArray(secondValue)) {
140
+ acc[key] = deepMerge(firstValue, secondValue);
141
+ } else {
142
+ acc[key] = secondValue;
143
+ }
144
+ } else if (firstHasKey) {
145
+ acc[key] = firstValue;
146
+ } else {
147
+ acc[key] = secondValue;
148
+ }
149
+ return acc;
150
+ }, {});
151
+ }
152
+
153
+ // src/utils/casing.ts
154
+ var import_utils = require("@ntnyq/utils");
155
+
156
+ // src/utils/createRule.ts
157
+ var docsUrl = "https://eslint-plugin-svg.ntnyq.com/rules/";
158
+ function createRule({
159
+ create,
160
+ defaultOptions: defaultOptions3,
161
+ meta: meta2
162
+ }) {
163
+ return {
164
+ create: (context) => {
165
+ const optionsCount = Math.max(context.options.length, defaultOptions3.length);
166
+ const optionsWithDefault = Array.from({ length: optionsCount }, (_, i) => {
167
+ if (isObjectNotArray(context.options[i]) && isObjectNotArray(defaultOptions3[i])) {
168
+ return deepMerge(defaultOptions3[i], context.options[i]);
169
+ }
170
+ return context.options[i] ?? defaultOptions3[i];
171
+ });
172
+ return create(context, optionsWithDefault);
173
+ },
174
+ defaultOptions: defaultOptions3,
175
+ meta: {
176
+ ...meta2,
177
+ defaultOptions: defaultOptions3
178
+ }
179
+ };
180
+ }
181
+ function RuleCreator(urlCreator) {
182
+ return function createNamedRule({
183
+ name: name2,
184
+ meta: meta2,
185
+ ...rule
186
+ }) {
187
+ return createRule({
188
+ meta: {
189
+ ...meta2,
190
+ docs: {
191
+ ...meta2.docs,
192
+ url: urlCreator(name2)
193
+ }
194
+ },
195
+ ...rule
196
+ });
197
+ };
198
+ }
199
+ var createESLintRule = RuleCreator((ruleName) => `${docsUrl}${ruleName}.html`);
200
+
201
+ // src/rules/no-deprecated.ts
202
+ var RULE_NAME = "no-deprecated";
203
+ var defaultOptions = {};
204
+ var no_deprecated_default = createESLintRule({
205
+ name: RULE_NAME,
206
+ meta: {
207
+ type: "suggestion",
208
+ docs: {
209
+ description: "disallow deprecated elements",
210
+ recommended: true
211
+ },
212
+ schema: [
213
+ {
214
+ type: "object",
215
+ properties: {
216
+ allowElements: {
217
+ type: "array",
218
+ description: "Allowed deprecated elements",
219
+ items: {
220
+ type: "string"
221
+ }
222
+ }
223
+ },
224
+ additionalProperties: false
225
+ }
226
+ ],
227
+ messages: {
228
+ deprecatedElement: `Element '{{name}}' is deprecated`
229
+ }
230
+ },
231
+ defaultOptions: [defaultOptions],
232
+ create(context) {
233
+ const { allowElements = [] } = context.options?.[0] || {};
234
+ const deprecatedElements = new Set(
235
+ DEPRECATED_ELEMENTS.filter((element) => !allowElements.includes(element))
236
+ );
237
+ return {
238
+ Tag(node) {
239
+ if (deprecatedElements.has(node.name)) {
240
+ context.report({
241
+ node,
242
+ messageId: "deprecatedElement",
243
+ data: {
244
+ name: node.name
245
+ }
246
+ });
247
+ }
248
+ }
249
+ };
250
+ }
251
+ });
252
+
253
+ // src/rules/no-empty-container.ts
254
+ var import_utils3 = require("@ntnyq/utils");
255
+ var RULE_NAME2 = "no-empty-container";
256
+ var defaultOptions2 = {};
257
+ var no_empty_container_default = createESLintRule({
258
+ name: RULE_NAME2,
259
+ meta: {
260
+ type: "suggestion",
261
+ docs: {
262
+ description: "disallow empty container element",
263
+ recommended: true
264
+ },
265
+ schema: [
266
+ {
267
+ type: "object",
268
+ properties: {
269
+ elements: {
270
+ type: "array",
271
+ description: "container elements to be checked",
272
+ items: {
273
+ type: "string"
274
+ },
275
+ uniqueItems: true
276
+ },
277
+ ignores: {
278
+ type: "array",
279
+ description: "container elements to be ignored",
280
+ items: {
281
+ type: "string"
282
+ },
283
+ uniqueItems: true
284
+ },
285
+ ignoreComments: {
286
+ type: "boolean",
287
+ description: "whether ignore comments nodes"
288
+ },
289
+ ignoreWhitespace: {
290
+ type: "boolean",
291
+ description: "whether ignore whitespace nodes"
292
+ }
293
+ },
294
+ additionalProperties: false
295
+ }
296
+ ],
297
+ messages: {
298
+ invalid: `Container element '{{name}}' must not be empty`
299
+ }
300
+ },
301
+ defaultOptions: [defaultOptions2],
302
+ create(context) {
303
+ const {
304
+ elements = CONTAINER_ELEMENTS,
305
+ ignores = [],
306
+ ignoreComments = true,
307
+ ignoreWhitespace = true
308
+ } = context.options?.[0] || {};
309
+ const containerElements = new Set(elements.filter((v) => !ignores.includes(v)));
310
+ return {
311
+ Tag(node) {
312
+ if (!containerElements.has(node.name)) {
313
+ return;
314
+ }
315
+ const children = node.children.filter((n) => !ignoreComments || n.type !== "Comment").filter((n) => !ignoreWhitespace || n.type !== "Text" || (0, import_utils3.isNonEmptyString)(n.value.trim()));
316
+ if (children.length === 0) {
317
+ context.report({
318
+ node,
319
+ messageId: "invalid",
320
+ data: {
321
+ name: node.name
322
+ }
323
+ });
324
+ }
325
+ }
326
+ };
327
+ }
328
+ });
329
+
330
+ // src/rules/no-empty-desc.ts
331
+ var RULE_NAME3 = "no-empty-desc";
332
+ var no_empty_desc_default = createESLintRule({
333
+ name: RULE_NAME3,
334
+ meta: {
335
+ type: "suggestion",
336
+ docs: {
337
+ description: "disallow empty desc element",
338
+ recommended: true
339
+ },
340
+ schema: [],
341
+ messages: {
342
+ invalid: "Element desc must not be empty"
343
+ }
344
+ },
345
+ defaultOptions: [],
346
+ create(context) {
347
+ return {
348
+ Tag(node) {
349
+ if (node.name !== "desc") {
350
+ return;
351
+ }
352
+ const textNode = node.children.find((n) => n.type === "Text");
353
+ if (textNode) {
354
+ if (!textNode.value.trim()) {
355
+ return context.report({
356
+ node,
357
+ messageId: "invalid"
358
+ });
359
+ }
360
+ } else {
361
+ return context.report({
362
+ node,
363
+ messageId: "invalid"
364
+ });
365
+ }
366
+ }
367
+ };
368
+ }
369
+ });
370
+
371
+ // src/rules/no-empty-text.ts
372
+ var RULE_NAME4 = "no-empty-text";
373
+ var no_empty_text_default = createESLintRule({
374
+ name: RULE_NAME4,
375
+ meta: {
376
+ type: "suggestion",
377
+ docs: {
378
+ description: "disallow empty text element",
379
+ recommended: true
380
+ },
381
+ schema: [],
382
+ messages: {
383
+ invalid: "Element text must not be empty"
384
+ }
385
+ },
386
+ defaultOptions: [],
387
+ create(context) {
388
+ return {
389
+ Tag(node) {
390
+ if (node.name !== "text") {
391
+ return;
392
+ }
393
+ const textNode = node.children.find((n) => n.type === "Text");
394
+ if (textNode) {
395
+ if (!textNode.value.trim()) {
396
+ return context.report({
397
+ node,
398
+ messageId: "invalid"
399
+ });
400
+ }
401
+ } else {
402
+ return context.report({
403
+ node,
404
+ messageId: "invalid"
405
+ });
406
+ }
407
+ }
408
+ };
409
+ }
410
+ });
411
+
412
+ // src/rules/no-empty-title.ts
413
+ var RULE_NAME5 = "no-empty-title";
414
+ var no_empty_title_default = createESLintRule({
415
+ name: RULE_NAME5,
416
+ meta: {
417
+ type: "suggestion",
418
+ docs: {
419
+ description: "disallow empty title element",
420
+ recommended: true
421
+ },
422
+ schema: [],
423
+ messages: {
424
+ invalid: "Element title must not be empty"
425
+ }
426
+ },
427
+ defaultOptions: [],
428
+ create(context) {
429
+ return {
430
+ Tag(node) {
431
+ if (node.name !== "title") {
432
+ return;
433
+ }
434
+ const textNode = node.children.find((n) => n.type === "Text");
435
+ if (textNode) {
436
+ if (!textNode.value.trim()) {
437
+ return context.report({
438
+ node,
439
+ messageId: "invalid"
440
+ });
441
+ }
442
+ } else {
443
+ return context.report({
444
+ node,
445
+ messageId: "invalid"
446
+ });
447
+ }
448
+ }
449
+ };
450
+ }
451
+ });
452
+
70
453
  // src/rules/index.ts
71
- var rules = {};
454
+ var rules = {
455
+ "no-deprecated": no_deprecated_default,
456
+ "no-empty-container": no_empty_container_default,
457
+ "no-empty-desc": no_empty_desc_default,
458
+ "no-empty-text": no_empty_text_default,
459
+ "no-empty-title": no_empty_title_default
460
+ };
72
461
 
73
462
  // src/index.ts
74
463
  var plugin = {
@@ -77,11 +466,11 @@ var plugin = {
77
466
  rules,
78
467
  configs
79
468
  };
80
- var src_default = plugin;
469
+ var index_default = plugin;
81
470
  // Annotate the CommonJS export names for ESM import in node:
82
471
  0 && (module.exports = {
83
472
  configs,
84
- createRecommendedConfig,
473
+ createConfig,
85
474
  meta,
86
475
  plugin,
87
476
  recommended,