| /** |
| * @fileoverview Enforce default props alphabetical sorting |
| * @author Vladimir Kattsov |
| */ |
| |
| 'use strict'; |
| |
| const variableUtil = require('../util/variable'); |
| const docsUrl = require('../util/docsUrl'); |
| const propWrapperUtil = require('../util/propWrapper'); |
| |
| // ------------------------------------------------------------------------------ |
| // Rule Definition |
| // ------------------------------------------------------------------------------ |
| |
| module.exports = { |
| meta: { |
| docs: { |
| description: 'Enforce default props alphabetical sorting', |
| category: 'Stylistic Issues', |
| recommended: false, |
| url: docsUrl('jsx-sort-default-props') |
| }, |
| |
| schema: [{ |
| type: 'object', |
| properties: { |
| ignoreCase: { |
| type: 'boolean' |
| } |
| }, |
| additionalProperties: false |
| }] |
| }, |
| |
| create(context) { |
| const configuration = context.options[0] || {}; |
| const ignoreCase = configuration.ignoreCase || false; |
| |
| /** |
| * Get properties name |
| * @param {Object} node - Property. |
| * @returns {String} Property name. |
| */ |
| function getPropertyName(node) { |
| if (node.key || ['MethodDefinition', 'Property'].indexOf(node.type) !== -1) { |
| return node.key.name; |
| } |
| if (node.type === 'MemberExpression') { |
| return node.property.name; |
| // Special case for class properties |
| // (babel-eslint@5 does not expose property name so we have to rely on tokens) |
| } |
| if (node.type === 'ClassProperty') { |
| const tokens = context.getFirstTokens(node, 2); |
| return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value; |
| } |
| return ''; |
| } |
| |
| /** |
| * Checks if the Identifier node passed in looks like a defaultProps declaration. |
| * @param {ASTNode} node The node to check. Must be an Identifier node. |
| * @returns {Boolean} `true` if the node is a defaultProps declaration, `false` if not |
| */ |
| function isDefaultPropsDeclaration(node) { |
| const propName = getPropertyName(node); |
| return (propName === 'defaultProps' || propName === 'getDefaultProps'); |
| } |
| |
| function getKey(node) { |
| return context.getSourceCode().getText(node.key || node.argument); |
| } |
| |
| /** |
| * Find a variable by name in the current scope. |
| * @param {string} name Name of the variable to look for. |
| * @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise. |
| */ |
| function findVariableByName(name) { |
| const variable = variableUtil.variablesInScope(context).find(item => item.name === name); |
| |
| if (!variable || !variable.defs[0] || !variable.defs[0].node) { |
| return null; |
| } |
| |
| if (variable.defs[0].node.type === 'TypeAlias') { |
| return variable.defs[0].node.right; |
| } |
| |
| return variable.defs[0].node.init; |
| } |
| |
| /** |
| * Checks if defaultProps declarations are sorted |
| * @param {Array} declarations The array of AST nodes being checked. |
| * @returns {void} |
| */ |
| function checkSorted(declarations) { |
| declarations.reduce((prev, curr, idx, decls) => { |
| if (/Spread(?:Property|Element)$/.test(curr.type)) { |
| return decls[idx + 1]; |
| } |
| |
| let prevPropName = getKey(prev); |
| let currentPropName = getKey(curr); |
| |
| if (ignoreCase) { |
| prevPropName = prevPropName.toLowerCase(); |
| currentPropName = currentPropName.toLowerCase(); |
| } |
| |
| if (currentPropName < prevPropName) { |
| context.report({ |
| node: curr, |
| message: 'Default prop types declarations should be sorted alphabetically' |
| }); |
| |
| return prev; |
| } |
| |
| return curr; |
| }, declarations[0]); |
| } |
| |
| function checkNode(node) { |
| switch (node && node.type) { |
| case 'ObjectExpression': |
| checkSorted(node.properties); |
| break; |
| case 'Identifier': { |
| const propTypesObject = findVariableByName(node.name); |
| if (propTypesObject && propTypesObject.properties) { |
| checkSorted(propTypesObject.properties); |
| } |
| break; |
| } |
| case 'CallExpression': { |
| const innerNode = node.arguments && node.arguments[0]; |
| if (propWrapperUtil.isPropWrapperFunction(context, node.callee.name) && innerNode) { |
| checkNode(innerNode); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| // -------------------------------------------------------------------------- |
| // Public API |
| // -------------------------------------------------------------------------- |
| |
| return { |
| ClassProperty(node) { |
| if (!isDefaultPropsDeclaration(node)) { |
| return; |
| } |
| |
| checkNode(node.value); |
| }, |
| |
| MemberExpression(node) { |
| if (!isDefaultPropsDeclaration(node)) { |
| return; |
| } |
| |
| checkNode(node.parent.right); |
| } |
| }; |
| } |
| }; |