UNPKG

ember-codemod-css-modules

Version:

Codemod to replace ember-component-css with ember-css-modules (compatible with embroider-css-modules)

111 lines (110 loc) 4.74 kB
/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { AST } from '@codemod-utils/ast-javascript'; import { createFiles, parseFilePath } from '@codemod-utils/files'; function ensureImports(file, data) { const traverse = AST.traverse(false); let lastImportDeclarationPath; let decoratorImportDeclarationPath; const ast = traverse(file, { visitImportDeclaration(path) { if (!lastImportDeclarationPath) { lastImportDeclarationPath = path; // @ts-ignore: Assume that types from external packages are correct } else if (path.node.start > lastImportDeclarationPath.node.start) { lastImportDeclarationPath = path; } if (path.value.source.value === '@ember-decorators/component') { decoratorImportDeclarationPath = path; } return false; }, }); // @ts-ignore: Assume that types from external packages are correct const nodes = ast.program.body; // @ts-ignore: Assume that types from external packages are correct let index = lastImportDeclarationPath?.name ?? -1; if (decoratorImportDeclarationPath) { // @ts-ignore: Assume that types from external packages are correct let specifiers = decoratorImportDeclarationPath.value.specifiers; // @ts-ignore: Assume that types from external packages are correct if (!specifiers.find((x) => x.imported.name === 'classNames')) { specifiers.push(AST.builders.importSpecifier(AST.builders.identifier('classNames'))); } } else { nodes.splice(index, 0, AST.builders.importDeclaration([ AST.builders.importSpecifier(AST.builders.identifier('classNames')), ], AST.builders.literal('@ember-decorators/component'))); } nodes.splice(index, 0, AST.builders.importDeclaration([ AST.builders.importDefaultSpecifier(AST.builders.identifier(data.__styles__)), ], AST.builders.literal(`./${data.fileName}.module.scss`))); return AST.print(ast); } function ensureModifier(file, data) { const traverse = AST.traverse(false); const ast = traverse(file, { visitClassDeclaration(path) { if (!path.value.decorators) path.value.decorators = []; const identifier = AST.builders.identifier(`${data.__styles__}.component`); const decorators = path.value.decorators; // @ts-ignore: Assume that types from external packages are correct const classNamesDecorator = decorators.find(x => x.expression.callee?.name == 'classNames'); if (classNamesDecorator) { classNamesDecorator.expression.arguments.push(identifier); } else { decorators.push(AST.builders.decorator(AST.builders.callExpression(AST.builders.identifier('classNames'), [identifier]))); } return false; }, }); return AST.print(ast); } function ensureAttribute(file, data) { const traverse = AST.traverse(false); const ast = traverse(file, { visitClassDeclaration(path) { const { body } = path.node.body; const nodesToAdd = [ AST.builders.classProperty(AST.builders.identifier(data.__styles__), AST.builders.identifier(data.__styles__)), ]; if (body.length > 0) { // @ts-ignore: Assume that types from external packages are correct nodesToAdd.push(AST.builders.noop()); } body.unshift(...nodesToAdd); return false; }, }); return AST.print(ast); } export function updateClass(entry, options) { const { __styles__, projectRoot } = options; const { jsPath } = entry; const { name } = parseFilePath(jsPath); const data = { __styles__, fileName: name, }; try { let file = readFileSync(join(projectRoot, jsPath), 'utf8'); file = ensureImports(file, data); file = ensureModifier(file, data); if (entry.hasHbsUsage) file = ensureAttribute(file, data); const fileMap = new Map([[jsPath, file]]); createFiles(fileMap, options); } catch (error) { let message = `WARNING: updateClass could not update \`${jsPath}\`. Please update the file manually.`; if (error instanceof Error) { message += ` (${error.message})`; } console.warn(`${message}\n`); } }