123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- /**
- * @fileoverview Standardize the way function component get defined
- * @author Stefan Wullems
- */
- 'use strict';
- const Components = require('../util/Components');
- const docsUrl = require('../util/docsUrl');
- // ------------------------------------------------------------------------------
- // Rule Definition
- // ------------------------------------------------------------------------------
- function buildFunction(template, parts) {
- return Object.keys(parts)
- .reduce((acc, key) => acc.replace(`{${key}}`, parts[key] || ''), template);
- }
- const NAMED_FUNCTION_TEMPLATES = {
- 'function-declaration': 'function {name}{typeParams}({params}){returnType} {body}',
- 'arrow-function': 'var {name}{typeAnnotation} = {typeParams}({params}){returnType} => {body}',
- 'function-expression': 'var {name}{typeAnnotation} = function{typeParams}({params}){returnType} {body}'
- };
- const UNNAMED_FUNCTION_TEMPLATES = {
- 'function-expression': 'function{typeParams}({params}){returnType} {body}',
- 'arrow-function': '{typeParams}({params}){returnType} => {body}'
- };
- function hasOneUnconstrainedTypeParam(node) {
- if (node.typeParameters) {
- return node.typeParameters.params.length === 1 && !node.typeParameters.params[0].constraint;
- }
- return false;
- }
- function hasName(node) {
- return node.type === 'FunctionDeclaration' || node.parent.type === 'VariableDeclarator';
- }
- function getNodeText(prop, source) {
- if (!prop) return null;
- return source.slice(prop.range[0], prop.range[1]);
- }
- function getName(node) {
- if (node.type === 'FunctionDeclaration') {
- return node.id.name;
- }
- if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') {
- return hasName(node) && node.parent.id.name;
- }
- }
- function getParams(node, source) {
- if (node.params.length === 0) return null;
- return source.slice(node.params[0].range[0], node.params[node.params.length - 1].range[1]);
- }
- function getBody(node, source) {
- const range = node.body.range;
- if (node.body.type !== 'BlockStatement') {
- return [
- '{',
- ` return ${source.slice(range[0], range[1])}`,
- '}'
- ].join('\n');
- }
- return source.slice(range[0], range[1]);
- }
- function getTypeAnnotation(node, source) {
- if (!hasName(node) || node.type === 'FunctionDeclaration') return;
- if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') {
- return getNodeText(node.parent.id.typeAnnotation, source);
- }
- }
- function isUnfixableBecauseOfExport(node) {
- return node.type === 'FunctionDeclaration' && node.parent && node.parent.type === 'ExportDefaultDeclaration';
- }
- function isFunctionExpressionWithName(node) {
- return node.type === 'FunctionExpression' && node.id && node.id.name;
- }
- module.exports = {
- meta: {
- docs: {
- description: 'Standardize the way function component get defined',
- category: 'Stylistic Issues',
- recommended: false,
- url: docsUrl('function-component-definition')
- },
- fixable: 'code',
- messages: {
- 'function-declaration': 'Function component is not a function declaration',
- 'function-expression': 'Function component is not a function expression',
- 'arrow-function': 'Function component is not an arrow function'
- },
- schema: [{
- type: 'object',
- properties: {
- namedComponents: {
- enum: ['function-declaration', 'arrow-function', 'function-expression']
- },
- unnamedComponents: {
- enum: ['arrow-function', 'function-expression']
- }
- }
- }]
- },
- create: Components.detect((context, components) => {
- const configuration = context.options[0] || {};
- const namedConfig = configuration.namedComponents || 'function-declaration';
- const unnamedConfig = configuration.unnamedComponents || 'function-expression';
- function getFixer(node, options) {
- const sourceCode = context.getSourceCode();
- const source = sourceCode.getText();
- const typeAnnotation = getTypeAnnotation(node, source);
- if (options.type === 'function-declaration' && typeAnnotation) return;
- if (options.type === 'arrow-function' && hasOneUnconstrainedTypeParam(node)) return;
- if (isUnfixableBecauseOfExport(node)) return;
- if (isFunctionExpressionWithName(node)) return;
- return (fixer) => fixer.replaceTextRange(options.range, buildFunction(options.template, {
- typeAnnotation,
- typeParams: getNodeText(node.typeParameters, source),
- params: getParams(node, source),
- returnType: getNodeText(node.returnType, source),
- body: getBody(node, source),
- name: getName(node)
- }));
- }
- function report(node, options) {
- context.report({
- node,
- messageId: options.messageId,
- fix: getFixer(node, options.fixerOptions)
- });
- }
- function validate(node, functionType) {
- if (!components.get(node)) return;
- if (node.parent && node.parent.type === 'Property') return;
- if (hasName(node) && namedConfig !== functionType) {
- report(node, {
- messageId: namedConfig,
- fixerOptions: {
- type: namedConfig,
- template: NAMED_FUNCTION_TEMPLATES[namedConfig],
- range: node.type === 'FunctionDeclaration'
- ? node.range
- : node.parent.parent.range
- }
- });
- }
- if (!hasName(node) && unnamedConfig !== functionType) {
- report(node, {
- messageId: unnamedConfig,
- fixerOptions: {
- type: unnamedConfig,
- template: UNNAMED_FUNCTION_TEMPLATES[unnamedConfig],
- range: node.range
- }
- });
- }
- }
- // --------------------------------------------------------------------------
- // Public
- // --------------------------------------------------------------------------
- return {
- FunctionDeclaration(node) { validate(node, 'function-declaration'); },
- ArrowFunctionExpression(node) { validate(node, 'arrow-function'); },
- FunctionExpression(node) { validate(node, 'function-expression'); }
- };
- })
- };
|