source: imaps-frontend/node_modules/eslint-plugin-react/lib/rules/function-component-definition.js@ 0c6b92a

main
Last change on this file since 0c6b92a was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

  • Property mode set to 100644
File size: 8.2 KB
RevLine 
[d565449]1/**
2 * @fileoverview Standardize the way function component get defined
3 * @author Stefan Wullems
4 */
5
6'use strict';
7
8const arrayIncludes = require('array-includes');
9const Components = require('../util/Components');
10const docsUrl = require('../util/docsUrl');
11const reportC = require('../util/report');
12const getText = require('../util/eslint').getText;
[0c6b92a]13const propsUtil = require('../util/props');
[d565449]14
15// ------------------------------------------------------------------------------
16// Rule Definition
17// ------------------------------------------------------------------------------
18
19function buildFunction(template, parts) {
20 return Object.keys(parts).reduce(
21 (acc, key) => acc.replace(`{${key}}`, () => parts[key] || ''),
22 template
23 );
24}
25
26const NAMED_FUNCTION_TEMPLATES = {
27 'function-declaration': 'function {name}{typeParams}({params}){returnType} {body}',
28 'arrow-function': '{varType} {name}{typeAnnotation} = {typeParams}({params}){returnType} => {body}',
29 'function-expression': '{varType} {name}{typeAnnotation} = function{typeParams}({params}){returnType} {body}',
30};
31
32const UNNAMED_FUNCTION_TEMPLATES = {
33 'function-expression': 'function{typeParams}({params}){returnType} {body}',
34 'arrow-function': '{typeParams}({params}){returnType} => {body}',
35};
36
37function hasOneUnconstrainedTypeParam(node) {
[0c6b92a]38 const nodeTypeArguments = propsUtil.getTypeArguments(node);
[d565449]39
[0c6b92a]40 return nodeTypeArguments
41 && nodeTypeArguments.params
42 && nodeTypeArguments.params.length === 1
43 && !nodeTypeArguments.params[0].constraint;
[d565449]44}
45
46function hasName(node) {
47 return (
48 node.type === 'FunctionDeclaration'
49 || node.parent.type === 'VariableDeclarator'
50 );
51}
52
53function getNodeText(prop, source) {
54 if (!prop) return null;
55 return source.slice(prop.range[0], prop.range[1]);
56}
57
58function getName(node) {
59 if (node.type === 'FunctionDeclaration') {
60 return node.id.name;
61 }
62
63 if (
64 node.type === 'ArrowFunctionExpression'
65 || node.type === 'FunctionExpression'
66 ) {
67 return hasName(node) && node.parent.id.name;
68 }
69}
70
71function getParams(node, source) {
72 if (node.params.length === 0) return null;
73 return source.slice(
74 node.params[0].range[0],
75 node.params[node.params.length - 1].range[1]
76 );
77}
78
79function getBody(node, source) {
80 const range = node.body.range;
81
82 if (node.body.type !== 'BlockStatement') {
83 return ['{', ` return ${source.slice(range[0], range[1])}`, '}'].join('\n');
84 }
85
86 return source.slice(range[0], range[1]);
87}
88
89function getTypeAnnotation(node, source) {
90 if (!hasName(node) || node.type === 'FunctionDeclaration') return;
91
92 if (
93 node.type === 'ArrowFunctionExpression'
94 || node.type === 'FunctionExpression'
95 ) {
96 return getNodeText(node.parent.id.typeAnnotation, source);
97 }
98}
99
100function isUnfixableBecauseOfExport(node) {
101 return (
102 node.type === 'FunctionDeclaration'
103 && node.parent
104 && node.parent.type === 'ExportDefaultDeclaration'
105 );
106}
107
108function isFunctionExpressionWithName(node) {
109 return node.type === 'FunctionExpression' && node.id && node.id.name;
110}
111
112const messages = {
113 'function-declaration': 'Function component is not a function declaration',
114 'function-expression': 'Function component is not a function expression',
115 'arrow-function': 'Function component is not an arrow function',
116};
117
118/** @type {import('eslint').Rule.RuleModule} */
119module.exports = {
120 meta: {
121 docs: {
122 description: 'Enforce a specific function type for function components',
123 category: 'Stylistic Issues',
124 recommended: false,
125 url: docsUrl('function-component-definition'),
126 },
127 fixable: 'code',
128
129 messages,
130
131 schema: [
132 {
133 type: 'object',
134 properties: {
135 namedComponents: {
136 anyOf: [
137 {
138 enum: [
139 'function-declaration',
140 'arrow-function',
141 'function-expression',
142 ],
143 },
144 {
145 type: 'array',
146 items: {
147 type: 'string',
148 enum: [
149 'function-declaration',
150 'arrow-function',
151 'function-expression',
152 ],
153 },
154 },
155 ],
156 },
157 unnamedComponents: {
158 anyOf: [
159 { enum: ['arrow-function', 'function-expression'] },
160 {
161 type: 'array',
162 items: {
163 type: 'string',
164 enum: ['arrow-function', 'function-expression'],
165 },
166 },
167 ],
168 },
169 },
170 },
171 ],
172 },
173
174 create: Components.detect((context, components) => {
175 const configuration = context.options[0] || {};
176 let fileVarType = 'var';
177
178 const namedConfig = [].concat(
179 configuration.namedComponents || 'function-declaration'
180 );
181 const unnamedConfig = [].concat(
182 configuration.unnamedComponents || 'function-expression'
183 );
184
185 function getFixer(node, options) {
186 const source = getText(context);
187
188 const typeAnnotation = getTypeAnnotation(node, source);
189
190 if (options.type === 'function-declaration' && typeAnnotation) {
191 return;
192 }
193 if (options.type === 'arrow-function' && hasOneUnconstrainedTypeParam(node)) {
194 return;
195 }
196 if (isUnfixableBecauseOfExport(node)) return;
197 if (isFunctionExpressionWithName(node)) return;
198 let varType = fileVarType;
199 if (
200 (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression')
201 && node.parent.type === 'VariableDeclarator'
202 ) {
203 varType = node.parent.parent.kind;
204 }
205
[0c6b92a]206 const nodeTypeArguments = propsUtil.getTypeArguments(node);
[d565449]207 return (fixer) => fixer.replaceTextRange(
208 options.range,
209 buildFunction(options.template, {
210 typeAnnotation,
[0c6b92a]211 typeParams: getNodeText(nodeTypeArguments, source),
[d565449]212 params: getParams(node, source),
213 returnType: getNodeText(node.returnType, source),
214 body: getBody(node, source),
215 name: getName(node),
216 varType,
217 })
218 );
219 }
220
221 function report(node, options) {
222 reportC(context, messages[options.messageId], options.messageId, {
223 node,
224 fix: getFixer(node, options.fixerOptions),
225 });
226 }
227
228 function validate(node, functionType) {
229 if (!components.get(node)) return;
230
231 if (node.parent && node.parent.type === 'Property') return;
232
233 if (hasName(node) && !arrayIncludes(namedConfig, functionType)) {
234 report(node, {
235 messageId: namedConfig[0],
236 fixerOptions: {
237 type: namedConfig[0],
238 template: NAMED_FUNCTION_TEMPLATES[namedConfig[0]],
239 range:
240 node.type === 'FunctionDeclaration'
241 ? node.range
242 : node.parent.parent.range,
243 },
244 });
245 }
246 if (!hasName(node) && !arrayIncludes(unnamedConfig, functionType)) {
247 report(node, {
248 messageId: unnamedConfig[0],
249 fixerOptions: {
250 type: unnamedConfig[0],
251 template: UNNAMED_FUNCTION_TEMPLATES[unnamedConfig[0]],
252 range: node.range,
253 },
254 });
255 }
256 }
257
258 // --------------------------------------------------------------------------
259 // Public
260 // --------------------------------------------------------------------------
261 const validatePairs = [];
262 let hasES6OrJsx = false;
263 return {
264 FunctionDeclaration(node) {
265 validatePairs.push([node, 'function-declaration']);
266 },
267 ArrowFunctionExpression(node) {
268 validatePairs.push([node, 'arrow-function']);
269 },
270 FunctionExpression(node) {
271 validatePairs.push([node, 'function-expression']);
272 },
273 VariableDeclaration(node) {
274 hasES6OrJsx = hasES6OrJsx || node.kind === 'const' || node.kind === 'let';
275 },
276 'Program:exit'() {
277 if (hasES6OrJsx) fileVarType = 'const';
278 validatePairs.forEach((pair) => validate(pair[0], pair[1]));
279 },
280 'ImportDeclaration, ExportNamedDeclaration, ExportDefaultDeclaration, ExportAllDeclaration, ExportSpecifier, ExportDefaultSpecifier, JSXElement, TSExportAssignment, TSImportEqualsDeclaration'() {
281 hasES6OrJsx = true;
282 },
283 };
284 }),
285};
Note: See TracBrowser for help on using the repository browser.