1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | 'use strict';
|
7 |
|
8 | const docsUrl = require('../util/docsUrl');
|
9 | const jsxUtil = require('../util/jsx');
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | const optionDefaults = {component: true, html: true};
|
16 |
|
17 | module.exports = {
|
18 | meta: {
|
19 | docs: {
|
20 | description: 'Prevent extra closing tags for components without children',
|
21 | category: 'Stylistic Issues',
|
22 | recommended: false,
|
23 | url: docsUrl('self-closing-comp')
|
24 | },
|
25 | fixable: 'code',
|
26 |
|
27 | schema: [{
|
28 | type: 'object',
|
29 | properties: {
|
30 | component: {
|
31 | default: optionDefaults.component,
|
32 | type: 'boolean'
|
33 | },
|
34 | html: {
|
35 | default: optionDefaults.html,
|
36 | type: 'boolean'
|
37 | }
|
38 | },
|
39 | additionalProperties: false
|
40 | }]
|
41 | },
|
42 |
|
43 | create(context) {
|
44 | function isComponent(node) {
|
45 | return (
|
46 | node.name
|
47 | && (node.name.type === 'JSXIdentifier' || node.name.type === 'JSXMemberExpression')
|
48 | && !jsxUtil.isDOMComponent(node)
|
49 | );
|
50 | }
|
51 |
|
52 | function childrenIsEmpty(node) {
|
53 | return node.parent.children.length === 0;
|
54 | }
|
55 |
|
56 | function childrenIsMultilineSpaces(node) {
|
57 | const childrens = node.parent.children;
|
58 |
|
59 | return (
|
60 | childrens.length === 1
|
61 | && (childrens[0].type === 'Literal' || childrens[0].type === 'JSXText')
|
62 | && childrens[0].value.indexOf('\n') !== -1
|
63 | && childrens[0].value.replace(/(?!\xA0)\s/g, '') === ''
|
64 | );
|
65 | }
|
66 |
|
67 | function isShouldBeSelfClosed(node) {
|
68 | const configuration = Object.assign({}, optionDefaults, context.options[0]);
|
69 | return (
|
70 | configuration.component && isComponent(node)
|
71 | || configuration.html && jsxUtil.isDOMComponent(node)
|
72 | ) && !node.selfClosing && (childrenIsEmpty(node) || childrenIsMultilineSpaces(node));
|
73 | }
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 | return {
|
80 |
|
81 | JSXOpeningElement(node) {
|
82 | if (!isShouldBeSelfClosed(node)) {
|
83 | return;
|
84 | }
|
85 | context.report({
|
86 | node,
|
87 | message: 'Empty components are self-closing',
|
88 | fix(fixer) {
|
89 |
|
90 | const openingElementEnding = node.range[1] - 1;
|
91 |
|
92 | const closingElementEnding = node.parent.closingElement.range[1];
|
93 |
|
94 |
|
95 | const range = [openingElementEnding, closingElementEnding];
|
96 | return fixer.replaceTextRange(range, ' />');
|
97 | }
|
98 | });
|
99 | }
|
100 | };
|
101 | }
|
102 | };
|