Skip to content

Commit

Permalink
Merge branch 'vibe3' into vibe3-remove-tooltip-max-width-prop
Browse files Browse the repository at this point in the history
  • Loading branch information
talkor committed Aug 11, 2024
2 parents 7a330cc + 9337498 commit 49a1c40
Show file tree
Hide file tree
Showing 125 changed files with 751 additions and 288 deletions.
2 changes: 1 addition & 1 deletion packages/codemod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
"dependencies": {
"chalk": "^4.1.2",
"jscodeshift": "^0.16.0"
"jscodeshift": "^0.16.1"
},
"devDependencies": {
"@types/jscodeshift": "^0.11.11",
Expand Down
15 changes: 13 additions & 2 deletions packages/codemod/src/utils/component-jsx-utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { Collection, JSXElement } from "jscodeshift";
import { ASTPath, Collection, JSCodeshift, JSXElement } from "jscodeshift";

/**
* Finds all JSX elements that match a component name.
*/
export function findComponentElements(root: Collection, componentName: string): Collection<JSXElement> {
return root.find(JSXElement, { openingElement: { name: { name: componentName } } });
return root.findJSXElements(componentName);
}

/**
* Updates the name of a JSX element.
*/
export function updateComponentName(j: JSCodeshift, elementPath: ASTPath<JSXElement>, newName: string) {
const elements = [elementPath.node.openingElement, elementPath.node.closingElement];
elements.forEach(element => {
if (!element?.name) return;
element.name = j.jsxIdentifier(newName);
});
}
46 changes: 46 additions & 0 deletions packages/codemod/src/utils/import-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,52 @@ export function getCoreNextImportsForFile(root: Collection): Collection<ImportDe
return getImports(root, CORE_NEXT_IMPORT_PATH);
}

export function findImportsThatIncludesSpecifier(
j: JSCodeshift,
paths: Collection<ImportDeclaration>,
specifierName: string
): Collection<ImportDeclaration> {
return paths.filter(path => {
const specifiers = extractSpecifiersFromImport(j, path);
return specifiers.some(spec => spec.imported.name === specifierName);
});
}

/**
* Updates the name of an import specifier (named import) in an import declaration.
*/
export function updateImportSpecifierName(
j: JSCodeshift,
importDeclarationPath: ASTPath<ImportDeclaration>,
oldName: string,
newName: string,
alias?: string
) {
j(importDeclarationPath)
.find(ImportSpecifier, { imported: { name: oldName } })
.replaceWith(path => {
// override alias, or use current alias if exists, or use newName if no alias is provided
const oldAlias = path.node.local?.name;
const hasAlias = oldAlias && oldAlias !== oldName;
const newAlias = alias || newName;
return j.importSpecifier(j.identifier(newName), j.identifier(hasAlias ? oldAlias : newAlias));
});
}

/**
* Removes a specific import specifier (named import) from an import declaration.
* If the import declaration does not have any other specifiers, the whole import declaration is removed afterward.
*/
export function removeSpecifierFromImport(j: JSCodeshift, path: ASTPath<ImportDeclaration>, specifierName: string) {
j(path)
.find(ImportSpecifier, { imported: { name: specifierName } })
.remove();
const specifiers = path.node.specifiers;
if (!specifiers?.length) {
j(path).remove();
}
}

/**
* Retrieves all import specifiers (named imports) from a given import declaration.
*/
Expand Down
36 changes: 35 additions & 1 deletion packages/codemod/src/utils/prop-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
JSCodeshift,
JSXAttribute,
JSXElement,
JSXExpressionContainer,
JSXIdentifier,
JSXOpeningElement,
Literal
Literal,
MemberExpression
} from "jscodeshift";
import { logPropMigrationError } from "./report-utils";

Expand Down Expand Up @@ -120,3 +122,35 @@ export function migratePropsNames(
}
});
}

/**
* Updates props that are using the component namespace to use the new namespace
* e.g. <OldName size={OldName.sizes.XXL}> -> <NewName size={NewName.sizes.XXL}>
* Should be used when updating specifiers and component jsx names
*/
export function updateComponentNamespaceProps(
j: JSCodeshift,
elementPath: ASTPath<JSXElement>,
oldNamespace: string,
newNamespace: string
) {
j(elementPath)
.find(JSXAttribute)
.find(JSXExpressionContainer)
.forEach(exprContainerPath => {
j(exprContainerPath)
.find(MemberExpression)
.forEach(memberExprPath => {
// Only update the base of the MemberExpression chain
// e.g. <OldName size={OldName.attr.other.OldName.XXL}> would only update the first 'OldName'
let base = memberExprPath.node.object;
while (base.type === "MemberExpression") {
base = base.object;
}

if (base.type === "Identifier" && base.name === oldNamespace) {
base.name = newNamespace;
}
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { defineInlineTest } from "jscodeshift/src/testUtils";
import transform from "../search-component-import-migration";

describe("SearchComponent component migration", () => {
defineInlineTest(
transform,
{},
`
import { SearchComponent } from "monday-ui-react-core";
<SearchComponent autoFocus placeholder="Search imported regular" />
`,
`
import { Search } from "monday-ui-react-core";
<Search autoFocus placeholder="Search imported regular" />
`,
"should update import and update self-closing jsx accordingly"
);

defineInlineTest(
transform,
{},
`
import { SearchComponent } from "monday-ui-react-core";
<SearchComponent autoFocus placeholder="Search imported regular"></SearchComponent>
`,
`
import { Search } from "monday-ui-react-core";
<Search autoFocus placeholder="Search imported regular"></Search>
`,
"should update import and update jsx accordingly"
);

defineInlineTest(
transform,
{},
`
import { SearchComponent } from "monday-ui-react-core";
<>
<SearchComponent autoFocus placeholder="Search imported regular" />
<SearchComponent />
<SearchComponent debounceRate={300} />
</>
`,
`
import { Search } from "monday-ui-react-core";
<>
<Search autoFocus placeholder="Search imported regular" />
<Search />
<Search debounceRate={300} />
</>
`,
"should update import and update multiple jsx usages"
);

defineInlineTest(
transform,
{},
`
import { SearchComponent as SC } from "monday-ui-react-core";
<SC autoFocus placeholder="Search as alias" />
`,
`
import { Search as SC } from "monday-ui-react-core";
<SC autoFocus placeholder="Search as alias" />
`,
"should update import but not update jsx when component is imported with an alias"
);

defineInlineTest(
transform,
{},
`
import { SearchComponent as SC } from "monday-ui-react-core";
<>
<SC autoFocus placeholder="Search as alias" />
<SC />
<SC debounceRate={300} />
</>
`,
`
import { Search as SC } from "monday-ui-react-core";
<>
<SC autoFocus placeholder="Search as alias" />
<SC />
<SC debounceRate={300} />
</>
`,
"should update import but not update any jsx usage when component is imported with an alias"
);

defineInlineTest(
transform,
{},
`
import { Search, SearchComponent } from "monday-ui-react-core";
<SearchComponent autoFocus placeholder="Search imported regular" />
`,
`
import { Search } from "monday-ui-react-core";
<SearchComponent autoFocus placeholder="Search imported regular" />
`,
"should remove 'SearchComponent' when both 'Search' and 'SearchComponent' are imported from core"
);

defineInlineTest(
transform,
{},
`
import { Search } from "other-component-library";
import { SearchComponent } from "monday-ui-react-core";
<SearchComponent autoFocus placeholder="Search imported regular" />
`,
`
import { Search } from "other-component-library";
import { Search as SearchComponent } from "monday-ui-react-core";
<SearchComponent autoFocus placeholder="Search imported regular" />
`,
"should update 'SearchComponent' with alias when both 'Search' and 'SearchComponent' are imported, and 'Search' is not imported from core"
);

defineInlineTest(
transform,
{},
`
import { OtherComponent } from "monday-ui-react-core";
<OtherComponent autoFocus placeholder="I'm not Search" />
`,
`
import { OtherComponent } from "monday-ui-react-core";
<OtherComponent autoFocus placeholder="I'm not Search" />
`,
"should not change unrelated imports"
);

defineInlineTest(
transform,
{},
`
import { OtherComponent as SearchComponent } from "monday-ui-react-core";
<SearchComponent autoFocus placeholder="I'm not Search" />
`,
`
import { OtherComponent as SearchComponent } from "monday-ui-react-core";
<SearchComponent autoFocus placeholder="I'm not Search" />
`,
"should not change unrelated imports that are aliased as SearchComponent"
);

defineInlineTest(
transform,
{},
`
import { Search as SearchComponent } from "monday-ui-react-core/next";
<SearchComponent autoFocus placeholder="I'm Search" />
`,
`
import { Search as SearchComponent } from "monday-ui-react-core/next";
<SearchComponent autoFocus placeholder="I'm Search" />
`,
"should not change Search from core/next import that is aliased as SearchComponent"
);

defineInlineTest(
transform,
{},
`
import { SearchComponent } from "monday-ui-react-core";
<SearchComponent autoFocus placeholder="I'm SearchComponent" size={SearchComponent.sizes.other.SMALL} />
`,
`
import { Search } from "monday-ui-react-core";
<Search autoFocus placeholder="I'm SearchComponent" size={Search.sizes.other.SMALL} />
`,
"should change props that uses the namespace of 'SearchComponent' to 'Search'"
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { TransformationContext } from "../../../types";
* 2. Update the 'isDisabled' prop to 'disabled'
*/
function transform({ j, root, filePath }: TransformationContext) {
if (!getCoreImportsForFile(root).length) return;
const imports = getCoreImportsForFile(root);
const componentName = getComponentNameOrAliasFromImports(j, imports, "Avatar");
if (!componentName) return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ImportDeclaration } from "jscodeshift";
import {
getCoreImportsForFile,
wrap,
getComponentNameOrAliasFromImports,
updateImportSpecifierName,
findComponentElements,
findImportsThatIncludesSpecifier,
removeSpecifierFromImport,
updateComponentName,
updateComponentNamespaceProps
} from "../../../src/utils";
import { TransformationContext } from "../../../types";

/**
* 1. Remove the 'SearchComponent' import if 'Search' is already imported from 'monday-ui-react-core'
* 2. Update the 'SearchComponent' import to 'Search' (or 'Search as SearchComponent' if 'Search' is already imported from other source)
* 3. Update the component name jsx usage from 'SearchComponent' to 'Search'
*/
function transform({ j, root }: TransformationContext) {
const coreImports = getCoreImportsForFile(root);
if (!coreImports.length) return;

const coreOldSearchComponentImports = findImportsThatIncludesSpecifier(j, coreImports, "SearchComponent");
if (!coreOldSearchComponentImports.length) return;

const allImports = root.find(ImportDeclaration);

// This finds all imports that has 'Search' as imported specifier
const rootSearchImports = findImportsThatIncludesSpecifier(j, allImports, "Search");

// Check in addition if the import of 'Search' that was found is from Vibe
const isSearchAlreadyImportedFromCore = rootSearchImports.some(
path => path.node.source?.value === "monday-ui-react-core"
);

// Keep the original alias if it exists
const componentName = getComponentNameOrAliasFromImports(j, coreOldSearchComponentImports.at(0), "SearchComponent");
const hasAlias = !!componentName && componentName !== "SearchComponent";

// if 'Search' specifier already exists in file (not from Vibe!) alias should be added to the new import
const newAlias = rootSearchImports.length ? "SearchComponent" : "";
const alias = hasAlias ? componentName : newAlias;

if (isSearchAlreadyImportedFromCore) {
removeSpecifierFromImport(j, coreOldSearchComponentImports.at(0).get(), "SearchComponent");
} else {
updateImportSpecifierName(j, coreOldSearchComponentImports.at(0).get(), "SearchComponent", "Search", alias);
}

const elements = findComponentElements(root, componentName || "SearchComponent");
if (!elements.length) return;

elements.forEach(elementPath => {
updateComponentName(j, elementPath, alias || "Search");
updateComponentNamespaceProps(j, elementPath, "SearchComponent", alias || "Search");
});
}

export default wrap(transform);
Loading

0 comments on commit 49a1c40

Please sign in to comment.