UNPKG

@itwin/appui-codemod

Version:

Codemod scripts for AppUI.

565 lines (499 loc) 24.2 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { FileInfo, API, Options, ASTPath, JSCodeshift, JSXElement, ObjectProperty, SpreadProperty, Collection, JSXIdentifier, ObjectExpression, Property } from "jscodeshift"; import { ConfigExpression, jsxElementToUniFormat, buildProperty, isSpecifiedConfigExpression, isExpressionKind, ExpressionKind, uniFormatToObjectExpression, concatExpressions, getObjectProperty, handleConditionalExpression, takeFirstArrayExpressionElement } from "../utils/TransformUtils"; import { isSpecifiedJSXElement } from "../utils/typeGuards"; import { useImportDeclaration } from "../utils/ImportDeclaration"; import { useImportSpecifier } from "../utils/ImportSpecifier"; import { useObjectExpression, ObjectExpressionCollection } from "../utils/ObjectExpression"; import { transformAbstractWidget } from "./widget"; export default function transformer(file: FileInfo, api: API, options: Options) { const j = api.jscodeshift; useImportDeclaration(j); useImportSpecifier(j); const root = j(file.source); const appuiReactImportDec = root.findImportDeclarations("@itwin/appui-react"); const widgetName = appuiReactImportDec.findSpecifiers("Widget").getLocalName(); widgetName !== "" && root.findJSXElements(widgetName).forEach((widget) => { const widgetConfig = transformWidget(j, widget); widget.replace(widgetConfig); }); const zoneName = appuiReactImportDec.findSpecifiers("Zone").getLocalName(); zoneName !== "" && root.findJSXElements(zoneName).forEach((zone) => { const zoneConfig = transformZone(j, zone); zone.replace(zoneConfig); }); const stagePanelName = appuiReactImportDec.findSpecifiers("StagePanel").getLocalName(); stagePanelName !== "" && root.findJSXElements(stagePanelName).forEach((stagePanel) => { const stagePanelConfig = transformStagePanel(j, stagePanel); stagePanel.replace(stagePanelConfig); }); const frontstageName = appuiReactImportDec.findSpecifiers("Frontstage").getLocalName(); frontstageName !== "" && root.findJSXElements(frontstageName).forEach((frontstage) => { const frontstageConfig = transformFrontstage(j, frontstage); frontstage.replace(frontstageConfig); }); appuiReactImportDec .removeSpecifier("Widget") .removeSpecifier("Zone") .removeSpecifier("StagePanel") .removeSpecifier("Frontstage"); return root.toSource(options.printOptions); } export function transformFrontstage(j: JSCodeshift, frontstage: ASTPath<JSXElement>): ConfigExpression { // TODO: registerMethod const uni = jsxElementToUniFormat(j, frontstage); const { name: configName, attrMap, spreadAttr } = uni; const frontstageProps: (ObjectProperty | SpreadProperty)[] = []; const handledExpressions = new Set(["key", "applicationData", "defaultTool", "defaultContentId", "isInFooterMode", "isIModelIndependent", "runtimeProps"]); const appuiReactImportDec = j(frontstage).closest(j.File).findImportDeclarations("@itwin/appui-react"); const zoneName = appuiReactImportDec.findSpecifiers("Zone").getLocalName(); const stagePanelName = appuiReactImportDec.findSpecifiers("StagePanel").getLocalName(); const idExpr = attrMap.getAttributeExpression("id") ?? attrMap.getAttributeExpression("key"); if (idExpr) { const id = buildProperty(j, "id", idExpr); frontstageProps.push(id); handledExpressions.add("id"); } else { // TODO: Handle missing mandatory property } const contentGroupExpr = attrMap.getAttributeExpression("contentGroup"); if (contentGroupExpr) { const contentGroup = buildProperty(j, "contentGroup", contentGroupExpr); frontstageProps.push(contentGroup); handledExpressions.add("contentGroup"); } else { // TODO: Handle missing mandatory property } const usageExpr = attrMap.getAttributeExpression("usage"); if (usageExpr) { const usage = buildProperty(j, "usage", usageExpr); frontstageProps.push(usage); handledExpressions.add("usage"); } const versionExpr = attrMap.getAttributeExpression("version"); if (versionExpr) { const version = buildProperty(j, "version", versionExpr); frontstageProps.push(version); handledExpressions.add("version"); } else { // TODO: Handle missing mandatory property } const contentManipulationExpr = attrMap.getAttributeExpression("contentManipulationTools") ?? attrMap.getAttributeExpression("topLeft"); if (contentManipulationExpr) { const modifiedContentManipulationExpr = handleToolWidget(j, contentManipulationExpr); const contentManipulation = buildProperty(j, "contentManipulation", modifiedContentManipulationExpr); frontstageProps.push(contentManipulation); handledExpressions.add("contentManipulationTools"); handledExpressions.add("topLeft"); } const toolSettingsExpr = attrMap.getAttributeExpression("toolSettings") ?? attrMap.getAttributeExpression("topCenter"); if (toolSettingsExpr) { const modifiedToolSettingsExpr = handleToolWidget(j, toolSettingsExpr); const toolSettings = buildProperty(j, "toolSettings", modifiedToolSettingsExpr); frontstageProps.push(toolSettings); handledExpressions.add("toolSettings"); handledExpressions.add("topCenter"); } const viewNavigationExpr = attrMap.getAttributeExpression("viewNavigationTools") ?? attrMap.getAttributeExpression("topRight"); if (viewNavigationExpr) { const modifiedViewNavigationExpr = handleToolWidget(j, viewNavigationExpr); const viewNavigation = buildProperty(j, "viewNavigation", modifiedViewNavigationExpr); frontstageProps.push(viewNavigation); handledExpressions.add("viewNavigationTools"); handledExpressions.add("topRight"); } const statusBarExpr = attrMap.getAttributeExpression("statusBar") ?? attrMap.getAttributeExpression("bottomCenter"); if (statusBarExpr) { const modifiedStatusBarExpr = handleToolWidget(j, statusBarExpr); const statusBar = buildProperty(j, "statusBar", modifiedStatusBarExpr); frontstageProps.push(statusBar); handledExpressions.add("statusBar"); handledExpressions.add("bottomCenter"); } let topPanelExpr = attrMap.getAttributeExpression("topPanel"); let topPanelEnd = attrMap.getAttributeExpression("topMostPanel"); if (topPanelExpr || topPanelEnd) { if (topPanelExpr) { topPanelExpr = handleSideStagePanel(j, topPanelExpr, stagePanelName); if (topPanelExpr) handledExpressions.add("topPanel"); } else { topPanelExpr = createEmptyConfig(j, j.jsxIdentifier("StagePanel")); } if (topPanelExpr && topPanelEnd) { topPanelEnd = handleSideStagePanel(j, topPanelEnd, stagePanelName); if (topPanelEnd && isSpecifiedConfigExpression(topPanelEnd, "StagePanel")) { handledExpressions.add("topMostPanel"); const start = getStagePanelSectionProperty(j, topPanelEnd, "start")?.value; const end = getStagePanelSectionProperty(j, topPanelEnd, "end")?.value; let successfulAppend: boolean = true; if (start && isExpressionKind(j, start) && isSpecifiedConfigExpression(topPanelExpr, "StagePanel")) successfulAppend = appendStagePanelSection(j, topPanelExpr, "end", start) && successfulAppend ? true : false; if (end && isExpressionKind(j, end) && isSpecifiedConfigExpression(topPanelExpr, "StagePanel")) successfulAppend = appendStagePanelSection(j, topPanelExpr, "end", end) && successfulAppend ? true : false; if ((start || end) && successfulAppend) handledExpressions.add("topMostPanel"); } } if (topPanelExpr) { const topPanel = buildProperty(j, "topPanel", topPanelExpr); frontstageProps.push(topPanel); } } let leftPanelExpr = attrMap.getAttributeExpression("leftPanel"); let leftPanelStart = attrMap.getAttributeExpression("centerLeft"); let leftPanelEnd = attrMap.getAttributeExpression("bottomLeft"); if (leftPanelExpr || leftPanelStart || leftPanelEnd) { if (leftPanelExpr) { leftPanelExpr = handleSideStagePanel(j, leftPanelExpr, stagePanelName); if (leftPanelExpr) handledExpressions.add("leftPanel"); } else { leftPanelExpr = createEmptyConfig(j, j.jsxIdentifier("StagePanel")); } leftPanelStart = leftPanelStart ? handleSideStagePanelZone(j, leftPanelStart, zoneName) : undefined; let successfulAppend: boolean = true; if (leftPanelExpr && leftPanelStart) successfulAppend = appendStagePanelSection(j, leftPanelExpr as ConfigExpression, "start", leftPanelStart) && successfulAppend ? true : false; if (leftPanelExpr && successfulAppend) handledExpressions.add("centerLeft"); successfulAppend = true; leftPanelEnd = leftPanelEnd ? handleSideStagePanelZone(j, leftPanelEnd, zoneName) : undefined; if (leftPanelExpr && leftPanelEnd) successfulAppend = appendStagePanelSection(j, leftPanelExpr as ConfigExpression, "end", leftPanelEnd) && successfulAppend ? true : false; if (leftPanelExpr && successfulAppend) handledExpressions.add("bottomLeft"); if (leftPanelExpr) { const leftPanel = buildProperty(j, "leftPanel", leftPanelExpr); frontstageProps.push(leftPanel); } } let rightPanelExpr = attrMap.getAttributeExpression("rightPanel"); let rightPanelStart = attrMap.getAttributeExpression("centerRight"); let rightPanelEnd = attrMap.getAttributeExpression("bottomRight"); if (rightPanelExpr || rightPanelStart || rightPanelEnd) { if (rightPanelExpr) { rightPanelExpr = handleSideStagePanel(j, rightPanelExpr, stagePanelName); if (rightPanelExpr) handledExpressions.add("rightPanel"); } else { rightPanelExpr = createEmptyConfig(j, j.jsxIdentifier("StagePanel")); } rightPanelStart = rightPanelStart ? handleSideStagePanelZone(j, rightPanelStart, zoneName) : undefined; let successfulAppend: boolean = true; if (rightPanelExpr && rightPanelStart) successfulAppend = appendStagePanelSection(j, rightPanelExpr as ConfigExpression, "start", rightPanelStart) && successfulAppend ? true : false; if (rightPanelExpr && successfulAppend) handledExpressions.add("centerRight"); successfulAppend = true; rightPanelEnd = rightPanelEnd ? handleSideStagePanelZone(j, rightPanelEnd, zoneName) : undefined; if (rightPanelExpr && rightPanelEnd) successfulAppend = appendStagePanelSection(j, rightPanelExpr as ConfigExpression, "end", rightPanelEnd) && successfulAppend ? true : false; if (rightPanelExpr && successfulAppend) handledExpressions.add("bottomRight"); if (rightPanelExpr) { const rightPanel = buildProperty(j, "rightPanel", rightPanelExpr); frontstageProps.push(rightPanel); } } let bottomPanelExpr = attrMap.getAttributeExpression("bottomPanel"); let bottomPanelEnd = attrMap.getAttributeExpression("bottomMostPanel"); if (bottomPanelExpr || bottomPanelEnd) { if (bottomPanelExpr) { bottomPanelExpr = handleSideStagePanel(j, bottomPanelExpr, stagePanelName); if (bottomPanelExpr) handledExpressions.add("bottomPanel"); } else { bottomPanelExpr = createEmptyConfig(j, j.jsxIdentifier("StagePanel")); } if (bottomPanelExpr && bottomPanelEnd) { bottomPanelEnd = handleSideStagePanel(j, bottomPanelEnd, stagePanelName); if (bottomPanelEnd && isSpecifiedConfigExpression(bottomPanelEnd, "StagePanel")) { const start = getStagePanelSectionProperty(j, bottomPanelEnd, "start")?.value; const end = getStagePanelSectionProperty(j, bottomPanelEnd, "end")?.value; let successfulAppend: boolean = true; if (start && isExpressionKind(j, start) && isSpecifiedConfigExpression(bottomPanelExpr, "StagePanel")) successfulAppend = appendStagePanelSection(j, bottomPanelExpr, "end", start) && successfulAppend ? true : false; if (end && isExpressionKind(j, end) && isSpecifiedConfigExpression(bottomPanelExpr, "StagePanel")) successfulAppend = appendStagePanelSection(j, bottomPanelExpr, "end", end) && successfulAppend ? true : false; if ((start || end) && successfulAppend) handledExpressions.add("bottomMostPanel"); } } if (bottomPanelExpr) { const bottomPanel = buildProperty(j, "bottomPanel", bottomPanelExpr); frontstageProps.push(bottomPanel); } } const styleExpr = attrMap.getAttributeExpression("style"); if (styleExpr) { const style = buildProperty(j, "style", styleExpr); frontstageProps.push(style); handledExpressions.add("style"); } const itemIdExpr = attrMap.getAttributeExpression("itemId"); if (itemIdExpr) { const itemId = buildProperty(j, "itemId", itemIdExpr); frontstageProps.push(itemId); handledExpressions.add("itemId"); } const classNameExpr = attrMap.getAttributeExpression("className"); if (classNameExpr) { const className = buildProperty(j, "className", classNameExpr); frontstageProps.push(className); handledExpressions.add("className"); } spreadAttr.forEach((expr) => { const spreadProperty = buildProperty(j, null, expr); frontstageProps.push(spreadProperty); }); attrMap.forEach((value, key) => { if (handledExpressions.has(key) === false && value && value !== "UnhandledExpression") { const unhandledProp = j.objectProperty(j.identifier(key), value); frontstageProps.push(unhandledProp); } }); return { ...j.objectExpression(frontstageProps), configName }; } export function transformStagePanel(j: JSCodeshift, stagePanel: ASTPath<JSXElement>): ConfigExpression { // TODO: registerMethod const { name: configName, attrMap, spreadAttr } = jsxElementToUniFormat(j, stagePanel); const stagePanelProps: (ObjectProperty | SpreadProperty)[] = []; const handledExpressions = new Set(["key", "allowedZones", "applicationData", "runtimeProps", "isInFooterMode", "isIModelIndependent", "runtimeProps"]); const defaultStateExpr = attrMap.getAttributeExpression("defaultState"); if (defaultStateExpr) { const defaultState = buildProperty(j, "defaultState", defaultStateExpr); stagePanelProps.push(defaultState); handledExpressions.add("defaultState"); } const maxSizeExpr = attrMap.getAttributeExpression("maxSize"); if (maxSizeExpr) { const maxSize = buildProperty(j, "maxSize", maxSizeExpr); stagePanelProps.push(maxSize); handledExpressions.add("maxSize"); } const minSizeExpr = attrMap.getAttributeExpression("minSize"); if (minSizeExpr) { const minSize = buildProperty(j, "minSize", minSizeExpr); stagePanelProps.push(minSize); handledExpressions.add("minSize"); } const pinnedExpr = attrMap.getAttributeExpression("pinned"); if (pinnedExpr) { const pinned = buildProperty(j, "pinned", pinnedExpr); stagePanelProps.push(pinned); handledExpressions.add("pinned"); } const resizableExpr = attrMap.getAttributeExpression("resizable"); if (resizableExpr) { const resizable = buildProperty(j, "resizable", resizableExpr); stagePanelProps.push(resizable); handledExpressions.add("resizable"); } const sizeExpr = attrMap.getAttributeExpression("size"); if (sizeExpr) { const size = buildProperty(j, "size", sizeExpr); stagePanelProps.push(size); handledExpressions.add("size"); } let panelZonesExpr = attrMap.getAttributeExpression("panelZones"); let widgetsExpr = attrMap.getAttributeExpression("widgets"); if (panelZonesExpr || widgetsExpr) { let startExpr = j.arrayExpression([]); let endExpr = j.arrayExpression([]); if (panelZonesExpr?.type === "ObjectExpression") { const [start, end] = extractSectionsFromPanelZones(j, panelZonesExpr); startExpr = concatExpressions(j, startExpr, start); endExpr = concatExpressions(j, endExpr, end); handledExpressions.add("panelZones"); } if (widgetsExpr) { startExpr = concatExpressions(j, startExpr, widgetsExpr); handledExpressions.add("widgets"); } const sectionsObjectProps: ObjectProperty[] = []; if (startExpr.elements.length > 0) sectionsObjectProps.push(j.objectProperty(j.identifier("start"), startExpr)); if (endExpr.elements.length > 0) sectionsObjectProps.push(j.objectProperty(j.identifier("end"), endExpr)); if (sectionsObjectProps.length > 0) { const sectionsObject = j.objectExpression(sectionsObjectProps); const sections = buildProperty(j, "sections", sectionsObject); stagePanelProps.push(sections); } } spreadAttr.forEach((expr) => { const spreadProperty = buildProperty(j, null, expr); stagePanelProps.push(spreadProperty); }); attrMap.forEach((value, key) => { if (handledExpressions.has(key) === false && value && value !== "UnhandledExpression") { const unhandledProp = j.objectProperty(j.identifier(key), value); stagePanelProps.push(unhandledProp); } }); return { ...j.objectExpression(stagePanelProps), configName }; } export function transformZone(j: JSCodeshift, zone: ASTPath<JSXElement>): ExpressionKind { const { attrMap, spreadAttr } = jsxElementToUniFormat(j, zone); const widgetsExpr = attrMap.getAttributeExpression("widgets"); if (widgetsExpr) return widgetsExpr; if (spreadAttr.length === 1) { return spreadAttr[0]; } else if (spreadAttr.length > 1) { // TODO: log warning return spreadAttr[0]; } return j.identifier("undefined"); } export function transformWidget(j: JSCodeshift, widget: ASTPath<JSXElement>): ConfigExpression { // TODO: registerMethod const uni = jsxElementToUniFormat(j, widget); const widgetObj = uniFormatToObjectExpression(j, uni); replaceWidgetObjectExpression(j, j(widgetObj)); return { ...widgetObj, configName: uni.name }; } export function handleSideStagePanel(j: JSCodeshift, expression: ExpressionKind, panelName: string): ConfigExpression | undefined { if (isSpecifiedJSXElement(expression, panelName)) { expression = transformFrontstage(j, j(expression).getAST()[0]); } if (isSpecifiedConfigExpression(expression, "StagePanel")) { return expression; } return undefined; } export function handleSideStagePanelZone(j: JSCodeshift, expression: ExpressionKind, panelZoneName: string): ExpressionKind | undefined { if (isSpecifiedJSXElement(expression, panelZoneName)) { expression = transformZone(j, j(expression).getAST()[0]); } if (expression.type === "Identifier" && expression.name === "undefined") { return undefined; } return expression; } export function createEmptyConfig(j: JSCodeshift, configName: JSXIdentifier): ConfigExpression { return { ...j.objectExpression([]), configName }; } export function handleToolWidget(j: JSCodeshift, expression: ExpressionKind): ExpressionKind { function handleExpression(expr: ExpressionKind): ExpressionKind { let newExpr: ExpressionKind | undefined = undefined; if (!newExpr && expr.type === "ConditionalExpression") newExpr = handleConditionalExpression(j, expr, handleExpression); if (!newExpr && expr.type === "ArrayExpression") newExpr = takeFirstArrayExpressionElement(j, expr); if (!newExpr) { // TODO: log warning return expr; } return newExpr; } return handleExpression(expression); } export function extractSectionsFromPanelZones(j: JSCodeshift, expression: ObjectExpression) { const result: [ExpressionKind | undefined, ExpressionKind | undefined] = [undefined, undefined] const start = getObjectProperty(j, expression, "start")?.value; if (start && start.type === "ObjectExpression") { const startWidgets = getObjectProperty(j, start, "widgets")?.value; if (startWidgets && isExpressionKind(j, startWidgets)) result[0] = startWidgets; } else if (start && isExpressionKind(j, start)) { result[0] = j.memberExpression(start, j.identifier("widgets")); } const middle = getObjectProperty(j, expression, "middle")?.value; if (middle && middle.type === "ObjectExpression") { const middleWidgets = getObjectProperty(j, middle, "widgets")?.value; if (middleWidgets && isExpressionKind(j, middleWidgets)) { if (result[0]) result[0] = concatExpressions(j, result[0], middleWidgets); else result[0] = middleWidgets; } } else if (middle && isExpressionKind(j, middle)) { const middleMemberExpr = j.memberExpression(middle, j.identifier("widgets")) if (result[0]) result[0] = concatExpressions(j, result[0], middleMemberExpr); else result[0] = middleMemberExpr; } const end = getObjectProperty(j, expression, "end")?.value; if (end && end.type === "ObjectExpression") { const endWidgets = getObjectProperty(j, end, "widgets")?.value; if (endWidgets && isExpressionKind(j, endWidgets)) result[1] = endWidgets; } else if (end && isExpressionKind(j, end)) { result[1] = j.memberExpression(end, j.identifier("widgets")); } return result; } export function appendStagePanelSection(j: JSCodeshift, stagePanel: ConfigExpression, sectionToAppend: "start" | "end", exprToAppend: ExpressionKind): "Success" | undefined { let sections = getObjectProperty(j, stagePanel, "sections"); if (!sections) { sections = j.objectProperty(j.identifier("sections"), j.objectExpression([])); stagePanel.properties.push(sections); } if (sections.value.type !== "ObjectExpression") { // TODO: log warning return undefined; } let section = getObjectProperty(j, sections.value, sectionToAppend); if (!section) { section = j.objectProperty(j.identifier(sectionToAppend), j.arrayExpression([])); sections.value.properties.push(section); } if (!isExpressionKind(j, section.value)) { // TODO: log warning return undefined; } section.value = concatExpressions(j, section.value, exprToAppend); return "Success"; } export function getStagePanelSectionProperty(j: JSCodeshift, stagePanel: ConfigExpression, sectionToGet: "start" | "end"): ObjectProperty | Property | undefined { const sections = getObjectProperty(j, stagePanel, "sections"); if (!sections) return undefined; if (sections.value.type !== "ObjectExpression") { // TODO: log warning return undefined; } const section = getObjectProperty(j, sections.value, sectionToGet); if (!section) return undefined; if (!isExpressionKind(j, section.value)) { // TODO: log warning return undefined; } return section; } export function replaceWidgetObjectExpression(j: JSCodeshift, widget: Collection<ObjectExpression>) { useObjectExpression(j); transformAbstractWidget(j, widget as ObjectExpressionCollection) .removeProperty("key", (_path, property) => { const idProp = getObjectProperty(j, _path.node, "id"); if (idProp === undefined && property.type === "ObjectProperty") { const newIdProp = j.objectProperty(j.identifier("id"), property.value); _path.node.properties.push(newIdProp); } }) .removeProperty("isFreeform") .removeProperty("isToolSettings") .removeProperty("isStatusBar") .removeProperty("fillZone") .removeProperty("syncEventIds") .removeProperty("stateFunc") .renameProperty("iconSpec", "icon") .renameProperty("isFloatingStateSupported", "canFloat") .renameProperty("badgeType", "badge") .renameProperty("element", "content"); }