source: imaps-frontend/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 6.7 KB
RevLine 
[d565449]1/**
2 * @fileoverview Enforce shorthand or standard form for React fragments.
3 * @author Alex Zherdev
4 */
5
6'use strict';
7
8const elementType = require('jsx-ast-utils/elementType');
9const pragmaUtil = require('../util/pragma');
10const variableUtil = require('../util/variable');
11const testReactVersion = require('../util/version').testReactVersion;
12const docsUrl = require('../util/docsUrl');
13const report = require('../util/report');
14const getText = require('../util/eslint').getText;
15
16// ------------------------------------------------------------------------------
17// Rule Definition
18// ------------------------------------------------------------------------------
19
20function replaceNode(source, node, text) {
21 return `${source.slice(0, node.range[0])}${text}${source.slice(node.range[1])}`;
22}
23
24const messages = {
25 fragmentsNotSupported: 'Fragments are only supported starting from React v16.2. '
26 + 'Please disable the `react/jsx-fragments` rule in `eslint` settings or upgrade your version of React.',
27 preferPragma: 'Prefer {{react}}.{{fragment}} over fragment shorthand',
28 preferFragment: 'Prefer fragment shorthand over {{react}}.{{fragment}}',
29};
30
31module.exports = {
32 meta: {
33 docs: {
34 description: 'Enforce shorthand or standard form for React fragments',
35 category: 'Stylistic Issues',
36 recommended: false,
37 url: docsUrl('jsx-fragments'),
38 },
39 fixable: 'code',
40
41 messages,
42
43 schema: [{
44 enum: ['syntax', 'element'],
45 }],
46 },
47
48 create(context) {
49 const configuration = context.options[0] || 'syntax';
50 const reactPragma = pragmaUtil.getFromContext(context);
51 const fragmentPragma = pragmaUtil.getFragmentFromContext(context);
52 const openFragShort = '<>';
53 const closeFragShort = '</>';
54 const openFragLong = `<${reactPragma}.${fragmentPragma}>`;
55 const closeFragLong = `</${reactPragma}.${fragmentPragma}>`;
56
57 function reportOnReactVersion(node) {
58 if (!testReactVersion(context, '>= 16.2.0')) {
59 report(context, messages.fragmentsNotSupported, 'fragmentsNotSupported', {
60 node,
61 });
62 return true;
63 }
64
65 return false;
66 }
67
68 function getFixerToLong(jsxFragment) {
69 if (!jsxFragment.closingFragment || !jsxFragment.openingFragment) {
70 // the old TS parser crashes here
71 // TODO: FIXME: can we fake these two descriptors?
72 return null;
73 }
74 return function fix(fixer) {
75 let source = getText(context);
76 source = replaceNode(source, jsxFragment.closingFragment, closeFragLong);
77 source = replaceNode(source, jsxFragment.openingFragment, openFragLong);
78 const lengthDiff = openFragLong.length - getText(context, jsxFragment.openingFragment).length
79 + closeFragLong.length - getText(context, jsxFragment.closingFragment).length;
80 const range = jsxFragment.range;
81 return fixer.replaceTextRange(range, source.slice(range[0], range[1] + lengthDiff));
82 };
83 }
84
85 function getFixerToShort(jsxElement) {
86 return function fix(fixer) {
87 let source = getText(context);
88 let lengthDiff;
89 if (jsxElement.closingElement) {
90 source = replaceNode(source, jsxElement.closingElement, closeFragShort);
91 source = replaceNode(source, jsxElement.openingElement, openFragShort);
92 lengthDiff = getText(context, jsxElement.openingElement).length - openFragShort.length
93 + getText(context, jsxElement.closingElement).length - closeFragShort.length;
94 } else {
95 source = replaceNode(source, jsxElement.openingElement, `${openFragShort}${closeFragShort}`);
96 lengthDiff = getText(context, jsxElement.openingElement).length - openFragShort.length
97 - closeFragShort.length;
98 }
99
100 const range = jsxElement.range;
101 return fixer.replaceTextRange(range, source.slice(range[0], range[1] - lengthDiff));
102 };
103 }
104
105 function refersToReactFragment(node, name) {
106 const variableInit = variableUtil.findVariableByName(context, node, name);
107 if (!variableInit) {
108 return false;
109 }
110
111 // const { Fragment } = React;
112 if (variableInit.type === 'Identifier' && variableInit.name === reactPragma) {
113 return true;
114 }
115
116 // const Fragment = React.Fragment;
117 if (
118 variableInit.type === 'MemberExpression'
119 && variableInit.object.type === 'Identifier'
120 && variableInit.object.name === reactPragma
121 && variableInit.property.type === 'Identifier'
122 && variableInit.property.name === fragmentPragma
123 ) {
124 return true;
125 }
126
127 // const { Fragment } = require('react');
128 if (
129 variableInit.callee
130 && variableInit.callee.name === 'require'
131 && variableInit.arguments
132 && variableInit.arguments[0]
133 && variableInit.arguments[0].value === 'react'
134 ) {
135 return true;
136 }
137
138 return false;
139 }
140
141 const jsxElements = [];
142 const fragmentNames = new Set([`${reactPragma}.${fragmentPragma}`]);
143
144 // --------------------------------------------------------------------------
145 // Public
146 // --------------------------------------------------------------------------
147
148 return {
149 JSXElement(node) {
150 jsxElements.push(node);
151 },
152
153 JSXFragment(node) {
154 if (reportOnReactVersion(node)) {
155 return;
156 }
157
158 if (configuration === 'element') {
159 report(context, messages.preferPragma, 'preferPragma', {
160 node,
161 data: {
162 react: reactPragma,
163 fragment: fragmentPragma,
164 },
165 fix: getFixerToLong(node),
166 });
167 }
168 },
169
170 ImportDeclaration(node) {
171 if (node.source && node.source.value === 'react') {
172 node.specifiers.forEach((spec) => {
173 if (spec.imported && spec.imported.name === fragmentPragma) {
174 if (spec.local) {
175 fragmentNames.add(spec.local.name);
176 }
177 }
178 });
179 }
180 },
181
182 'Program:exit'() {
183 jsxElements.forEach((node) => {
184 const openingEl = node.openingElement;
185 const elName = elementType(openingEl);
186
187 if (fragmentNames.has(elName) || refersToReactFragment(node, elName)) {
188 if (reportOnReactVersion(node)) {
189 return;
190 }
191
192 const attrs = openingEl.attributes;
193 if (configuration === 'syntax' && !(attrs && attrs.length > 0)) {
194 report(context, messages.preferFragment, 'preferFragment', {
195 node,
196 data: {
197 react: reactPragma,
198 fragment: fragmentPragma,
199 },
200 fix: getFixerToShort(node),
201 });
202 }
203 }
204 });
205 },
206 };
207 },
208};
Note: See TracBrowser for help on using the repository browser.