UNPKG

eslint-plugin-logical-properties

Version:
246 lines (239 loc) 7.68 kB
import type { ObjectExpression, } from 'estree'; import type { Rule, } from 'eslint'; import type { DirectionalRuleConfig, PluginOptions, } from '../types.js'; import { isValidProperty } from '../utils/isValidProperty.js'; import { directionalMappingTransformerFactory, } from './directionalMapping.js'; import { directionalShorthandTransformerFactory, } from './directionalShorthand.js'; import { MustDisablePropertyError } from '../utils/MustDisablePropertyError.js'; import { directionalShorthandMappingTransformerFactory, } from './directionalShorthandMapping.js'; import { directionalDisableTransformerFactory, } from './directionalDisable.js'; import { defaultFunctions, defaultJsxAttributes, defaultKeyframes, defaultResolvers, rulePrefix, } from '../constants.js'; import { directionalValueTransformerFactory, } from './directionalValue.js'; import { configSchema } from '../config.js'; import { propertyTraverseSet } from '../utils/propertyTraverse.js'; import { getValidPropertyName } from '../utils/getValidPropertyName.js'; const getOptional = ( input: string[] | Readonly<string[]> | undefined, fallback: string[] | Readonly<string[]>, ): string[] => ((input && input.length > 0) ? [...input] : [...fallback]); const isTargetJsxAttribute = ( node: unknown, names: string[], ): node is { type: 'JSXAttribute' name: { type: 'JSXIdentifier' name: string, } value: { type: 'JSXExpressionContainer' expression: unknown, } } => ( typeof node === 'object' && node !== null && 'type' in node && node.type === 'JSXAttribute' && 'name' in node && !!node.name && typeof node.name === 'object' && node.name !== null && 'type' in node.name && node.name.type === 'JSXIdentifier' && 'name' in node.name && typeof node.name.name === 'string' && names.includes(node.name.name) && 'value' in node && !!node.value && typeof node.value === 'object' && node.value !== null && 'type' in node.value && node.value.type === 'JSXExpressionContainer' && 'expression' in node.value && typeof node.value.expression === 'object' && node.value.expression !== null && 'type' in node.value.expression && node.value.expression.type === 'ObjectExpression' ); export const transformDirectionalProperty = ( node: ObjectExpression, context: Rule.RuleContext, config: DirectionalRuleConfig, resolvers: Exclude<PluginOptions['resolvers'], undefined>, ) => { const { disabled = [], mappings = {}, shorthands = {}, shorthandPairMappings = {}, shorthandMappings = {}, values = {}, } = config; const toDisable = Array.isArray(disabled) ? disabled : [disabled]; const mappingsSources = Object.keys(mappings); const shorthandSources = Object.keys(shorthands); const shorthandPairMappingsSources = Object.keys(shorthandPairMappings); const shorthandMappingsSources = Object.keys(shorthandMappings); const valueSources = Object.keys(values); const transformerFactoryInput = { node, context, config, }; const directionalMappingTransformer = directionalMappingTransformerFactory( transformerFactoryInput, ); const directionalShorthandTransformer = directionalShorthandTransformerFactory( transformerFactoryInput, ); const directionalShorthandMappingTransformer = directionalShorthandMappingTransformerFactory( transformerFactoryInput, ); const directionalDisableTransformer = directionalDisableTransformerFactory( transformerFactoryInput, ); const directionalValueTransformer = directionalValueTransformerFactory( transformerFactoryInput, ); for (const property of node.properties) { if (isValidProperty(property)) { const propertyName = getValidPropertyName(property)!; try { if (mappingsSources.includes(propertyName)) { directionalMappingTransformer(property); } else if ( shorthandSources.includes(propertyName) || shorthandPairMappingsSources.includes(propertyName) ) { directionalShorthandTransformer(property); } else if (shorthandMappingsSources.includes(propertyName)) { directionalShorthandMappingTransformer(property); } else if (valueSources.includes(propertyName)) { directionalValueTransformer(property); } else if (toDisable.includes(propertyName)) { throw new MustDisablePropertyError(); } } catch (error: unknown) { if (error instanceof MustDisablePropertyError) { directionalDisableTransformer(property); } } } } const resolvedObjects = propertyTraverseSet(node, resolvers); for (const object of resolvedObjects) { transformDirectionalProperty(object, context, config, resolvers); } }; export const generateDirectionalRules = (config: DirectionalRuleConfig): Rule.RuleModule => ({ meta: { type: 'problem', fixable: 'code', schema: [configSchema], }, create(context) { const [ruleOptions = {}] = context.options ?? []; const settings = ((context.settings?.[rulePrefix] ?? {}) as PluginOptions); const options = ruleOptions as PluginOptions; const nodeFunctionNames = getOptional( options.functions, getOptional(settings.functions, defaultFunctions), ); const nodeKeyframesNames = getOptional( options.keyframes, getOptional(settings.keyframes, defaultKeyframes), ); const nodeJsxAttributesNames = getOptional( options.jsxAttributes, getOptional(settings.jsxAttributes, defaultJsxAttributes), ); const nodeResolvers = getOptional( options.resolvers, getOptional(settings.resolvers, defaultResolvers), ); return { JSXAttribute(node) { if (isTargetJsxAttribute(node, nodeJsxAttributesNames)) { transformDirectionalProperty( node.value.expression as ObjectExpression, context, config, nodeResolvers, ); } }, CallExpression(node) { if ( node.callee.type === 'Identifier' && nodeFunctionNames.includes(node.callee.name) ) { for (const rules of node.arguments) { if (rules.type === 'ArrayExpression') { for (const ruleSet of rules.elements) { if (ruleSet?.type === 'ObjectExpression') { transformDirectionalProperty( ruleSet as unknown as ObjectExpression, context, config, [], ); } } } else if (rules.type === 'ObjectExpression') { transformDirectionalProperty( rules as unknown as ObjectExpression, context, config, nodeResolvers, ); } } } else if ( node.callee.type === 'Identifier' && nodeKeyframesNames.includes(node.callee.name) ) { for (const argument of node.arguments) { if (argument.type === 'ObjectExpression') { for (const property of argument.properties) { if ( property.type === 'Property' && property.value.type === 'ObjectExpression' ) { transformDirectionalProperty( property.value as unknown as ObjectExpression, context, config, nodeResolvers, ); } } } } } }, }; }, });