eslint-plugin-svg 0.0.0 → 0.0.2

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,15 @@ 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 | ✅ | | |
60
+ | [no-elements](https://eslint-plugin-svg.ntnyq.com/rules/no-elements) | Disallow elements by name | | | |
61
+ | [no-doctype](https://eslint-plugin-svg.ntnyq.com/rules/no-doctype) | Disallow doctype | ✅ | 🔧 | |
55
62
 
56
63
  ## License
57
64
 
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,507 @@ 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-doctype": "error",
74
+ "svg/no-empty-container": "error",
75
+ "svg/no-empty-desc": "error",
76
+ "svg/no-empty-text": "error",
77
+ "svg/no-empty-title": "error"
78
+ }
79
+ })
59
80
  ];
60
81
  var configs = {
61
82
  recommended
62
83
  };
63
84
 
85
+ // package.json
86
+ var name = "eslint-plugin-svg";
87
+ var version = "0.0.2";
88
+
64
89
  // src/meta.ts
65
90
  var meta = {
66
- name: "eslint-plugin-svg",
67
- version: "0.0.0"
91
+ name,
92
+ version
68
93
  };
69
94
 
95
+ // src/constants/index.ts
96
+ var CONTAINER_ELEMENTS = [
97
+ "a",
98
+ "defs",
99
+ "g",
100
+ "marker",
101
+ "mask",
102
+ "missing-glyph",
103
+ "pattern",
104
+ "svg",
105
+ "switch",
106
+ "symbol"
107
+ ];
108
+ var DEPRECATED_ELEMENTS = [
109
+ "altGlyph",
110
+ "altGlyphDef",
111
+ "altGlyphItem",
112
+ "animateColor",
113
+ "cursor",
114
+ "font",
115
+ "font-face",
116
+ "font-face-format",
117
+ "font-face-name",
118
+ "font-face-src",
119
+ "font-face-uri",
120
+ "glyph",
121
+ "glyphRef",
122
+ "hkern",
123
+ "missing-glyph",
124
+ "tref",
125
+ "vkern"
126
+ ];
127
+
128
+ // src/utils/merge.ts
129
+ function isObjectNotArray(obj) {
130
+ return typeof obj === "object" && obj != null && !Array.isArray(obj);
131
+ }
132
+ function deepMerge(first = {}, second = {}) {
133
+ const keys = new Set(Object.keys(first).concat(Object.keys(second)));
134
+ return Array.from(keys).reduce((acc, key) => {
135
+ const firstHasKey = key in first;
136
+ const secondHasKey = key in second;
137
+ const firstValue = first[key];
138
+ const secondValue = second[key];
139
+ if (firstHasKey && secondHasKey) {
140
+ if (isObjectNotArray(firstValue) && isObjectNotArray(secondValue)) {
141
+ acc[key] = deepMerge(firstValue, secondValue);
142
+ } else {
143
+ acc[key] = secondValue;
144
+ }
145
+ } else if (firstHasKey) {
146
+ acc[key] = firstValue;
147
+ } else {
148
+ acc[key] = secondValue;
149
+ }
150
+ return acc;
151
+ }, {});
152
+ }
153
+
154
+ // src/utils/casing.ts
155
+ var import_utils = require("@ntnyq/utils");
156
+
157
+ // src/utils/createRule.ts
158
+ var docsUrl = "https://eslint-plugin-svg.ntnyq.com/rules/";
159
+ function createRule({
160
+ create,
161
+ defaultOptions: defaultOptions4,
162
+ meta: meta2
163
+ }) {
164
+ return {
165
+ create: (context) => {
166
+ const optionsCount = Math.max(context.options.length, defaultOptions4.length);
167
+ const optionsWithDefault = Array.from({ length: optionsCount }, (_, i) => {
168
+ if (isObjectNotArray(context.options[i]) && isObjectNotArray(defaultOptions4[i])) {
169
+ return deepMerge(defaultOptions4[i], context.options[i]);
170
+ }
171
+ return context.options[i] ?? defaultOptions4[i];
172
+ });
173
+ return create(context, optionsWithDefault);
174
+ },
175
+ defaultOptions: defaultOptions4,
176
+ meta: {
177
+ ...meta2,
178
+ defaultOptions: defaultOptions4
179
+ }
180
+ };
181
+ }
182
+ function RuleCreator(urlCreator) {
183
+ return function createNamedRule({
184
+ name: name2,
185
+ meta: meta2,
186
+ ...rule
187
+ }) {
188
+ return createRule({
189
+ meta: {
190
+ ...meta2,
191
+ docs: {
192
+ ...meta2.docs,
193
+ url: urlCreator(name2)
194
+ }
195
+ },
196
+ ...rule
197
+ });
198
+ };
199
+ }
200
+ var createESLintRule = RuleCreator((ruleName) => `${docsUrl}${ruleName}.html`);
201
+
202
+ // src/utils/resolveOptions.ts
203
+ function resolveOptions(options) {
204
+ return options?.[0] || {};
205
+ }
206
+
207
+ // src/rules/no-deprecated.ts
208
+ var RULE_NAME = "no-deprecated";
209
+ var defaultOptions = {};
210
+ var no_deprecated_default = createESLintRule({
211
+ name: RULE_NAME,
212
+ meta: {
213
+ type: "suggestion",
214
+ docs: {
215
+ description: "disallow deprecated elements",
216
+ recommended: true
217
+ },
218
+ schema: [
219
+ {
220
+ type: "object",
221
+ properties: {
222
+ allowElements: {
223
+ type: "array",
224
+ description: "Allowed deprecated elements",
225
+ items: {
226
+ type: "string"
227
+ }
228
+ }
229
+ },
230
+ additionalProperties: false
231
+ }
232
+ ],
233
+ messages: {
234
+ deprecatedElement: `Element '{{name}}' is deprecated`
235
+ }
236
+ },
237
+ defaultOptions: [defaultOptions],
238
+ create(context) {
239
+ const { allowElements = [] } = resolveOptions(context.options);
240
+ const deprecatedElements = new Set(
241
+ DEPRECATED_ELEMENTS.filter((element) => !allowElements.includes(element))
242
+ );
243
+ return {
244
+ Tag(node) {
245
+ if (deprecatedElements.has(node.name)) {
246
+ context.report({
247
+ node,
248
+ messageId: "deprecatedElement",
249
+ data: {
250
+ name: node.name
251
+ }
252
+ });
253
+ }
254
+ }
255
+ };
256
+ }
257
+ });
258
+
259
+ // src/rules/no-doctype.ts
260
+ var RULE_NAME2 = "no-doctype";
261
+ var no_doctype_default = createESLintRule({
262
+ name: RULE_NAME2,
263
+ meta: {
264
+ type: "suggestion",
265
+ docs: {
266
+ description: "disallow doctype",
267
+ recommended: true
268
+ },
269
+ fixable: "code",
270
+ schema: [],
271
+ messages: {
272
+ invalid: "Doctype is not allowed"
273
+ }
274
+ },
275
+ defaultOptions: [],
276
+ create(context) {
277
+ return {
278
+ Doctype(node) {
279
+ context.report({
280
+ node,
281
+ messageId: "invalid",
282
+ fix: (fixer) => fixer.remove(node)
283
+ });
284
+ }
285
+ };
286
+ }
287
+ });
288
+
289
+ // src/rules/no-elements.ts
290
+ var RULE_NAME3 = "no-elements";
291
+ var defaultOptions2 = {};
292
+ var no_elements_default = createESLintRule({
293
+ name: RULE_NAME3,
294
+ meta: {
295
+ type: "suggestion",
296
+ docs: {
297
+ description: "disallow elements by name",
298
+ recommended: true
299
+ },
300
+ schema: [
301
+ {
302
+ type: "object",
303
+ properties: {
304
+ elements: {
305
+ type: "array",
306
+ description: "elements to be disallowed",
307
+ items: {
308
+ type: "string"
309
+ }
310
+ }
311
+ },
312
+ additionalProperties: false
313
+ }
314
+ ],
315
+ messages: {
316
+ invalid: `Element '{{name}}' is not allowed`
317
+ }
318
+ },
319
+ defaultOptions: [defaultOptions2],
320
+ create(context) {
321
+ const { elements = [] } = resolveOptions(context.options);
322
+ if (!elements.length) {
323
+ return {};
324
+ }
325
+ return {
326
+ Tag(node) {
327
+ if (elements.includes(node.name)) {
328
+ context.report({
329
+ node,
330
+ messageId: "invalid",
331
+ data: {
332
+ name: node.name
333
+ }
334
+ });
335
+ }
336
+ }
337
+ };
338
+ }
339
+ });
340
+
341
+ // src/rules/no-empty-container.ts
342
+ var import_utils5 = require("@ntnyq/utils");
343
+ var RULE_NAME4 = "no-empty-container";
344
+ var defaultOptions3 = {};
345
+ var no_empty_container_default = createESLintRule({
346
+ name: RULE_NAME4,
347
+ meta: {
348
+ type: "suggestion",
349
+ docs: {
350
+ description: "disallow empty container element",
351
+ recommended: true
352
+ },
353
+ schema: [
354
+ {
355
+ type: "object",
356
+ properties: {
357
+ elements: {
358
+ type: "array",
359
+ description: "container elements to be checked",
360
+ items: {
361
+ type: "string"
362
+ },
363
+ uniqueItems: true
364
+ },
365
+ ignores: {
366
+ type: "array",
367
+ description: "container elements to be ignored",
368
+ items: {
369
+ type: "string"
370
+ },
371
+ uniqueItems: true
372
+ },
373
+ ignoreComments: {
374
+ type: "boolean",
375
+ description: "whether ignore comments nodes"
376
+ },
377
+ ignoreWhitespace: {
378
+ type: "boolean",
379
+ description: "whether ignore whitespace nodes"
380
+ }
381
+ },
382
+ additionalProperties: false
383
+ }
384
+ ],
385
+ messages: {
386
+ invalid: `Container element '{{name}}' must not be empty`
387
+ }
388
+ },
389
+ defaultOptions: [defaultOptions3],
390
+ create(context) {
391
+ const {
392
+ elements = [],
393
+ ignores = [],
394
+ ignoreComments = true,
395
+ ignoreWhitespace = true
396
+ } = resolveOptions(context.options);
397
+ const containerElements = new Set(
398
+ [
399
+ // built-in container elements
400
+ ...CONTAINER_ELEMENTS,
401
+ // user defined container elements
402
+ ...elements
403
+ ].filter((v) => !ignores.includes(v))
404
+ );
405
+ return {
406
+ Tag(node) {
407
+ if (!containerElements.has(node.name)) {
408
+ return;
409
+ }
410
+ const children = node.children.filter((n) => !ignoreComments || n.type !== "Comment").filter((n) => !ignoreWhitespace || n.type !== "Text" || (0, import_utils5.isNonEmptyString)(n.value.trim()));
411
+ if (children.length === 0) {
412
+ context.report({
413
+ node,
414
+ messageId: "invalid",
415
+ data: {
416
+ name: node.name
417
+ }
418
+ });
419
+ }
420
+ }
421
+ };
422
+ }
423
+ });
424
+
425
+ // src/rules/no-empty-desc.ts
426
+ var RULE_NAME5 = "no-empty-desc";
427
+ var no_empty_desc_default = createESLintRule({
428
+ name: RULE_NAME5,
429
+ meta: {
430
+ type: "suggestion",
431
+ docs: {
432
+ description: "disallow empty desc element",
433
+ recommended: true
434
+ },
435
+ schema: [],
436
+ messages: {
437
+ invalid: "Element desc must not be empty"
438
+ }
439
+ },
440
+ defaultOptions: [],
441
+ create(context) {
442
+ return {
443
+ Tag(node) {
444
+ if (node.name !== "desc") {
445
+ return;
446
+ }
447
+ const textNode = node.children.find((n) => n.type === "Text");
448
+ if (textNode) {
449
+ if (!textNode.value.trim()) {
450
+ return context.report({
451
+ node,
452
+ messageId: "invalid"
453
+ });
454
+ }
455
+ } else {
456
+ return context.report({
457
+ node,
458
+ messageId: "invalid"
459
+ });
460
+ }
461
+ }
462
+ };
463
+ }
464
+ });
465
+
466
+ // src/rules/no-empty-text.ts
467
+ var RULE_NAME6 = "no-empty-text";
468
+ var no_empty_text_default = createESLintRule({
469
+ name: RULE_NAME6,
470
+ meta: {
471
+ type: "suggestion",
472
+ docs: {
473
+ description: "disallow empty text element",
474
+ recommended: true
475
+ },
476
+ schema: [],
477
+ messages: {
478
+ invalid: "Element text must not be empty"
479
+ }
480
+ },
481
+ defaultOptions: [],
482
+ create(context) {
483
+ return {
484
+ Tag(node) {
485
+ if (node.name !== "text") {
486
+ return;
487
+ }
488
+ const textNode = node.children.find((n) => n.type === "Text");
489
+ if (textNode) {
490
+ if (!textNode.value.trim()) {
491
+ return context.report({
492
+ node,
493
+ messageId: "invalid"
494
+ });
495
+ }
496
+ } else {
497
+ return context.report({
498
+ node,
499
+ messageId: "invalid"
500
+ });
501
+ }
502
+ }
503
+ };
504
+ }
505
+ });
506
+
507
+ // src/rules/no-empty-title.ts
508
+ var RULE_NAME7 = "no-empty-title";
509
+ var no_empty_title_default = createESLintRule({
510
+ name: RULE_NAME7,
511
+ meta: {
512
+ type: "suggestion",
513
+ docs: {
514
+ description: "disallow empty title element",
515
+ recommended: true
516
+ },
517
+ schema: [],
518
+ messages: {
519
+ invalid: "Element title must not be empty"
520
+ }
521
+ },
522
+ defaultOptions: [],
523
+ create(context) {
524
+ return {
525
+ Tag(node) {
526
+ if (node.name !== "title") {
527
+ return;
528
+ }
529
+ const textNode = node.children.find((n) => n.type === "Text");
530
+ if (textNode) {
531
+ if (!textNode.value.trim()) {
532
+ return context.report({
533
+ node,
534
+ messageId: "invalid"
535
+ });
536
+ }
537
+ } else {
538
+ return context.report({
539
+ node,
540
+ messageId: "invalid"
541
+ });
542
+ }
543
+ }
544
+ };
545
+ }
546
+ });
547
+
70
548
  // src/rules/index.ts
71
- var rules = {};
549
+ var rules = {
550
+ "no-deprecated": no_deprecated_default,
551
+ "no-doctype": no_doctype_default,
552
+ "no-elements": no_elements_default,
553
+ "no-empty-container": no_empty_container_default,
554
+ "no-empty-desc": no_empty_desc_default,
555
+ "no-empty-text": no_empty_text_default,
556
+ "no-empty-title": no_empty_title_default
557
+ };
72
558
 
73
559
  // src/index.ts
74
560
  var plugin = {
@@ -77,11 +563,11 @@ var plugin = {
77
563
  rules,
78
564
  configs
79
565
  };
80
- var src_default = plugin;
566
+ var index_default = plugin;
81
567
  // Annotate the CommonJS export names for ESM import in node:
82
568
  0 && (module.exports = {
83
569
  configs,
84
- createRecommendedConfig,
570
+ createConfig,
85
571
  meta,
86
572
  plugin,
87
573
  recommended,