packages/eslint-plugin-fb-flow/rules/use-flow-enums.js (92 lines of code) (raw):

/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+flow * @format */ 'use strict'; function isIdentifier(node, expectedName) { return ( node.type === 'Identifier' && (expectedName === undefined || node.name === expectedName) ); } function isLegacyEnum(node) { const {callee, arguments: args} = node; // Legacy enums: `keyMirror({...})` and `Object.freeze({...})` const literalTypes = new Set(['TemplateLiteral', 'Literal']); const uniqueValues = new Set(); const uniqueKeys = new Set(); const uniqueTypes = new Set(); if ( // `keyMirror(...)` or `Object.freeze(...)` (isIdentifier(callee, 'keyMirror') || (callee.type === 'MemberExpression' && isIdentifier(callee.object, 'Object') && isIdentifier(callee.property, 'freeze'))) && // A single argument that's an object literal. args.length === 1 && args[0].type === 'ObjectExpression' && args[0].properties.length > 0 && args[0].properties.every(prop => { const isLiteral = !prop.computed && !prop.shorthand && !prop.method && prop.key != null && prop.value != null && isIdentifier(prop.key) && // Valid enum patterns names must start with an uppercase [A-Z] /^[A-Z]/.test(prop.key.name) && literalTypes.has(prop.value.type) && // String literals that don't follow this regex pattern are probably // just part of a bag of constants rather than an enum-like pattern. (prop.value.type !== 'Literal' || /^[-a-zA-Z_0-9]*$/.test(prop.value.value)); if (prop.value != null && prop.value.value != null) { uniqueValues.add(prop.value.value); } else if (prop.key) { // if the value is null, assume a keyMirror and increment the Set() uniqueValues.add(prop.key.name); } if (prop.key != null) { uniqueKeys.add(prop.key.name); } if (prop.value != null) { uniqueTypes.add(typeof prop.value.value); } return isLiteral; }) && // Check all keys are unique uniqueKeys.size === args[0].properties.length && // Check all values are unique uniqueValues.size === args[0].properties.length && // The `type` of each property value is the same. uniqueTypes.size === 1 ) { return true; } return false; } module.exports = { meta: { messages: { useFlowEnumsObjectFreeze: 'Use Flow Enums when creating enum objects. Ex. `enum Foo { {{key}} = {{value}}, ... }`', useFlowEnumsKeyMirror: 'Use Flow Enums when creating enum objects. Ex. `enum Foo { {{key}}, ... }`', }, }, create(context) { return { CallExpression(node) { if (isLegacyEnum(node)) { const key = node.arguments[0].properties[0].key.name; const value = node.arguments[0].properties[0].value.value; if (value) { context.report({ node, messageId: 'useFlowEnumsObjectFreeze', data: { key, value, }, }); } else { context.report({ node, messageId: 'useFlowEnumsKeyMirror', data: { key, }, }); } } }, }; }, };