UNPKG

3 kBJavaScriptView Raw
1/**
2 * @fileoverview Prevent extra closing tags for components without children
3 * @author Yannick Croissant
4 */
5
6'use strict';
7
8const docsUrl = require('../util/docsUrl');
9const jsxUtil = require('../util/jsx');
10
11// ------------------------------------------------------------------------------
12// Rule Definition
13// ------------------------------------------------------------------------------
14
15const optionDefaults = {component: true, html: true};
16
17module.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 // Public
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 // Represents the last character of the JSXOpeningElement, the '>' character
90 const openingElementEnding = node.range[1] - 1;
91 // Represents the last character of the JSXClosingElement, the '>' character
92 const closingElementEnding = node.parent.closingElement.range[1];
93
94 // Replace />.*<\/.*>/ with '/>'
95 const range = [openingElementEnding, closingElementEnding];
96 return fixer.replaceTextRange(range, ' />');
97 }
98 });
99 }
100 };
101 }
102};