Skip to content

Instantly share code, notes, and snippets.

@temoncher
Last active December 17, 2022 10:43
Show Gist options
  • Select an option

  • Save temoncher/65ac0106d3377df56ef515fdaee479f8 to your computer and use it in GitHub Desktop.

Select an option

Save temoncher/65ac0106d3377df56ef515fdaee479f8 to your computer and use it in GitHub Desktop.
ESLint rule that autofixes enums into constants
// @ts-check
/** @type {import('@typescript-eslint/utils').TSESLint.RuleModule} */
module.exports = {
meta: {
docs: {
description:
"See [TypeScript Enums are TERRIBLE. Here's Why. - Michigan TypeScript](https://www.youtube.com/watch?v=0fTdCSH_QEU)",
},
type: 'suggestion',
fixable: 'code',
schema: [
{
type: 'object',
properties: {
UNSAFE_oneSidedNumericEnums: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
},
create: (context) => {
const { UNSAFE_oneSidedNumericEnums } = context.options[0] ?? {
UNSAFE_oneSidedNumericEnums: false,
};
return {
// eslint-disable-next-line complexity, max-statements
TSEnumDeclaration(node) {
const enumName = node.id.name;
const keyValuePairs = [];
let currentEnumNumber = 0;
for (const member of node.members) {
if (member.id.type !== 'Identifier') {
throw new Error('Member id is not an `Identifier` node');
}
if (!member.initializer) {
keyValuePairs.push([member.id.name, currentEnumNumber]);
if (!UNSAFE_oneSidedNumericEnums) {
keyValuePairs.push([currentEnumNumber, member.id.name]);
}
currentEnumNumber++;
continue;
}
if (member.initializer.type === 'Literal') {
if (typeof member.initializer.value === 'number') {
keyValuePairs.push([member.id.name, member.initializer.value]);
if (!UNSAFE_oneSidedNumericEnums) {
keyValuePairs.push([member.initializer.value, member.id.name]);
}
currentEnumNumber = member.initializer.value + 1;
continue;
}
if (typeof member.initializer.value === 'string') {
keyValuePairs.push([member.id.name, member.initializer.value]);
continue;
}
}
// Can't autofix
context.report({
node,
message:
'Please use object-style enums',
});
return;
}
const enumIsExported = node.parent?.type === 'ExportNamedDeclaration';
context.report({
node,
message:
'Please use object-style enums',
*fix(fixer) {
const keyValuePairsText = keyValuePairs
.map(([key, value]) =>
typeof value === 'string' ? `${key}:'${value}'` : `${key}:${value}`
)
.join(',');
const exportPrefixOrEmpty = enumIsExported ? 'export ' : '';
const enumValueText = `${exportPrefixOrEmpty}const ${enumName} = {${keyValuePairsText}} as const;`;
const enumTypeText = `${exportPrefixOrEmpty}type ${enumName} = typeof ${enumName}[keyof typeof ${enumName}];`;
yield fixer.replaceText(
enumIsExported ? node.parent : node,
`${enumValueText}\n${enumTypeText}`
);
},
});
},
};
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment