194 lines
5.9 KiB
JavaScript
194 lines
5.9 KiB
JavaScript
/**
|
|
* @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}'
|
|
};
|
|
|
|
const ERROR_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'
|
|
};
|
|
|
|
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',
|
|
|
|
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,
|
|
message: options.message,
|
|
fix: getFixer(node, options.fixerOptions)
|
|
});
|
|
}
|
|
|
|
function validate(node, functionType) {
|
|
if (!components.get(node)) return;
|
|
if (hasName(node) && namedConfig !== functionType) {
|
|
report(node, {
|
|
message: ERROR_MESSAGES[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, {
|
|
message: ERROR_MESSAGES[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'); }
|
|
};
|
|
})
|
|
};
|