Skip to content

Instantly share code, notes, and snippets.

@aztack
Created September 25, 2024 06:35
Show Gist options
  • Save aztack/4e1b46f5051e035b15378972f0558b17 to your computer and use it in GitHub Desktop.
Save aztack/4e1b46f5051e035b15378972f0558b17 to your computer and use it in GitHub Desktop.
extract-possible-values-of-classname-lepusCls
/* eslint-disable @typescript-eslint/no-shadow */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable curly */
/* eslint-disable @typescript-eslint/no-use-before-define */
/* eslint-disable max-lines-per-function */
/* eslint-disable no-param-reassign */
/* eslint-disable prefer-template */
/* eslint-disable array-callback-return */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import 'colors';
import classnames from 'classnames';
import { highlight } from 'cli-highlight';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { Project, SyntaxKind } from "ts-morph";
// @ts-ignore
globalThis.classnames = classnames;
// @ts-ignore
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const rootDir = path.resolve(__dirname, '..', '..');
// Initialize a project and source file
const project = new Project();
const indexTs = path.join(rootDir, 'rc-button', 'src', 'index.tsx');
const sourceFile = project.addSourceFileAtPath(indexTs);
// Helper function to extract union types
function extractUnionTypes(arg: any): string[] {
const argType = arg.getType();
const unionValues: string[] = [];
if (argType.isUnion()) {
argType.getUnionTypes().forEach((unionType: { getText: () => string; }) => {
unionValues.push(unionType.getText());
});
}
return unionValues;
}
function stripInterpolation(str: string) {
return str.replace(/["'`${}]/g, '');
}
// Generate all combinations of arrays of strings
function generateCombinations(arrays: string[][]): string[][] {
if (arrays.length === 0) return [[]];
const [first, ...rest] = arrays;
const restCombinations = generateCombinations(rest);
// @ts-ignore
return first.flatMap(item => restCombinations.map(comb => [item, ...comb]));
}
// Recursive function to handle array, object, and template strings
function handleArguments(arg: any): string[][] {
const kind = arg.getKind();
if (kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NoSubstitutionTemplateLiteral) {
return [[stripInterpolation(arg.getText())]]; // Return string literals as array of one element
}
if (kind === SyntaxKind.ArrayLiteralExpression) {
// Recursively process array elements and generate combinations
const arrayElements = arg.getElements().map((element: any) => handleArguments(element));
return generateCombinations(arrayElements);
}
if (kind === SyntaxKind.ObjectLiteralExpression) {
// Process object literals with conditional classes
return arg.getProperties().flatMap((prop: any) => {
const key = prop.getNameNode();
const value = prop.getInitializer();
if (key.getKind() === SyntaxKind.ComputedPropertyName) {
// Get TemplateExpression from key
const templateExpression = key.getFirstChildByKindOrThrow(SyntaxKind.TemplateExpression);
const keyCombinations = extractTemplateLiteralCombinations(templateExpression);
return keyCombinations.map(key => [key, ...handleArguments(value).flat()]);
} else {
return [[key.getText()]];
}
});
}
if (kind === SyntaxKind.TemplateExpression) {
return extractTemplateLiteralCombinations(arg).map(val => [val]);
}
return [];
}
// Example helper function for extracting combinations of template literals
function extractTemplateLiteralCombinations(templateLiteral: any): string[] {
const head = templateLiteral.getHead().getText();
const combinedValues: string[][] = [];
templateLiteral.getTemplateSpans().forEach((span: any) => {
const expr = span.getExpression();
const literalPart = span.getLiteral().getText();
if (expr.getKind() === SyntaxKind.Identifier) {
const unionValues = extractUnionTypes(expr);
combinedValues.push(unionValues.map(value => value + literalPart));
} else {
combinedValues.push([expr.getText() + literalPart]);
}
});
const possibilities = generateCombinations(combinedValues);
const ret = possibilities.map(possibility => head + possibility.join(""));
return ret.map(stripInterpolation);
}
// Generate dynamic classnames function
function generateDynamicClassnameFunction(args: any[]): Function | null {
const allCombinations = handleMultipleArguments(args);
console.log(`${allCombinations.length} combinations`);
// if (allCombinations.length > 50) {
// console.warn(`Too many combinations, skip`);
// return null;
// }
// Generate multiple calls to classnames, passing each combination as arguments
const calls = allCombinations.map(combination => `
classes.push(globalThis.classnames(...${JSON.stringify(combination)}));
`.trim()).join("\n");
const code = `const classes = [];\n${calls}\nreturn classes;`;
if (process.argv.some(arg => arg === '--debug')) {
console.log(highlight(`() => {\n${code}\n}`, {
language: 'typescript',
ignoreIllegals: true
}));
}
return new Function(code);
}
// Function to handle multiple arguments (strings, objects, arrays, template literals)
function handleMultipleArguments(args: any[]): string[][] {
// Flatten all processed arguments into an array of arrays (i.e., combinations of arguments)
// @ts-ignore
return args.flatMap((arg: any) => {
const processedArgs = handleArguments(arg); // Process each argument
return processedArgs.map(arg => [arg]); // Wrap each result in an array to preserve grouping
}).filter(Boolean); // Remove falsy values
}
// Main function to find `this.lepusCls` calls and handle multiple arguments
function findLepusClsStringPossibilities() {
const methodCalls = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
const allClasses = new Set();
methodCalls.forEach((callExpression) => {
const expression = callExpression.getExpression();
if (
expression.getKind() === SyntaxKind.PropertyAccessExpression &&
expression.getText() === "this.lepusCls"
) {
console.log(`${indexTs}:${callExpression.getStartLineNumber()}`.green);
const args = callExpression.getArguments();
console.log(highlight(callExpression.getText(), {
language: 'typescript',
ignoreIllegals: true
}));
// Generate the dynamic function using all arguments
const dynamicClassnameFunction = generateDynamicClassnameFunction(args);
if (!dynamicClassnameFunction) {
return;
}
// Execute the dynamic function and log the results
try {
const resultClasses = dynamicClassnameFunction();
resultClasses.forEach((classname: any) => {
allClasses.add(classname);
console.log(classname);
});
} catch (e) {
const error = e instanceof Error ? e.message : String(e);
console.error(`${error}`.red);
}
}
});
console.log(`-`.repeat(80).green);
const finalIds = new Set();
Array.from(allClasses).forEach((classname: any) => {
classname.split(' ').forEach((name: string) => finalIds.add(name));
});
console.log(Array.from(finalIds).sort());
}
// Execute the function
findLepusClsStringPossibilities();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment