-
Notifications
You must be signed in to change notification settings - Fork 185
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: remove FormItemTopLabel multiline prop (#7578)
h2. Описание Сейчас в компонент `FormItemTopLabel` можно прокинуть проп `multiline`. А также данное значение можно получить из контекста `FormItemContext` из поля `topMultiline`. Поэтому нужно выпилить проп `multiline` и всегда брать значение из контекста. h2. Изменения - Выпилил проп `multiline` из `FormItemTopLabel` - Обновил скриншотные тесты - Реализованы кодмоды для правильного обновления, чтобы визуально ничего не сломалось h2. Release notes h2. BREAKING CHANGE - FormItem: у сабкомпонента `FormItem.TopLabel` параметр `multiline` был удален, теперь он всегда берется из параметра `topMultiline` у компонент `FormItem` ```diff <FormItem + topMultiline top={ <FormItem.Top> - <FormItem.TopLabel htmlFor="about" multiline>Дополнительная информация</FormItem.TopLabel> + <FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel> <FormItem.TopAside>0/100</FormItem.TopAside> </FormItem.Top> } > <div/> </FormItem> ```
- Loading branch information
1 parent
a5dba7b
commit 4902ab8
Showing
17 changed files
with
410 additions
and
38 deletions.
There are no files selected for viewing
120 changes: 120 additions & 0 deletions
120
packages/codemods/src/transforms/v7/__testfixtures__/form-item/basic.input.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import { FormItem } from '@vkontakte/vkui'; | ||
import React from 'react'; | ||
import '@vkontakte/vkui/dist/vkui.css'; | ||
|
||
const App = () => { | ||
const flag = true; | ||
const calculateMultiline = () => true; | ||
return ( | ||
<React.Fragment> | ||
{/* test 1: should find TopLabel inside top prop */} | ||
<FormItem | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about" multiline>Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
> | ||
<div/> | ||
</FormItem> | ||
|
||
{/* test 2: should find TopLabel inside topNode prop */} | ||
<FormItem | ||
topNode={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about" multiline>Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
> | ||
<div/> | ||
</FormItem> | ||
|
||
{/* test 3: TopLabel multiline={true} -> FormItem topMultiline */} | ||
<FormItem | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about" multiline={true}>Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
topMultiline={false} | ||
> | ||
<div/> | ||
</FormItem> | ||
|
||
{/* test 4: TopLabel multiline={false} -> FormItem topMultiline={false} */} | ||
<FormItem | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about" multiline={false}>Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
topMultiline={true} | ||
> | ||
<div/> | ||
</FormItem> | ||
|
||
{/* test 5: should find multiline in child of FormItem */} | ||
<FormItem> | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about" multiline>Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
</FormItem> | ||
|
||
{/* test 6: should move multiline expression in topMultiline */} | ||
<FormItem | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about" multiline={flag && calculateMultiline()}>Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
> | ||
<div/> | ||
</FormItem> | ||
|
||
{/* test 6: should replace topMultiline expression by multiline expression */} | ||
<FormItem | ||
topMultiline={!flag || calculateMultiline()} | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about" multiline={flag && calculateMultiline()}>Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
> | ||
<div/> | ||
</FormItem> | ||
|
||
{/* test 7: do nothing because multiline not set */} | ||
<FormItem | ||
topMultiline | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
> | ||
<div/> | ||
</FormItem> | ||
|
||
{/* test 8: remove unnecessary attribute */} | ||
<FormItem | ||
topMultiline={false} | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
> | ||
<div/> | ||
</FormItem> | ||
</React.Fragment> | ||
); | ||
}; |
112 changes: 112 additions & 0 deletions
112
packages/codemods/src/transforms/v7/__tests__/__snapshots__/form-item.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`form-item transforms correctly 1`] = ` | ||
"import { FormItem } from '@vkontakte/vkui'; | ||
import React from 'react'; | ||
import '@vkontakte/vkui/dist/vkui.css'; | ||
const App = () => { | ||
const flag = true; | ||
const calculateMultiline = () => true; | ||
return ( | ||
(<React.Fragment> | ||
{/* test 1: should find TopLabel inside top prop */} | ||
<FormItem | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
topMultiline> | ||
<div/> | ||
</FormItem> | ||
{/* test 2: should find TopLabel inside topNode prop */} | ||
<FormItem | ||
topNode={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
topMultiline> | ||
<div/> | ||
</FormItem> | ||
{/* test 3: TopLabel multiline={true} -> FormItem topMultiline */} | ||
<FormItem | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
topMultiline | ||
> | ||
<div/> | ||
</FormItem> | ||
{/* test 4: TopLabel multiline={false} -> FormItem topMultiline={false} */} | ||
<FormItem | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
}> | ||
<div/> | ||
</FormItem> | ||
{/* test 5: should find multiline in child of FormItem */} | ||
<FormItem topMultiline> | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
</FormItem> | ||
{/* test 6: should move multiline expression in topMultiline */} | ||
<FormItem | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
topMultiline={flag && calculateMultiline()}> | ||
<div/> | ||
</FormItem> | ||
{/* test 6: should replace topMultiline expression by multiline expression */} | ||
<FormItem | ||
topMultiline={flag && calculateMultiline()} | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
> | ||
<div/> | ||
</FormItem> | ||
{/* test 7: do nothing because multiline not set */} | ||
<FormItem | ||
topMultiline | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
} | ||
> | ||
<div/> | ||
</FormItem> | ||
{/* test 8: remove unnecessary attribute */} | ||
<FormItem | ||
top={ | ||
<FormItem.Top> | ||
<FormItem.TopLabel htmlFor="about">Дополнительная информация</FormItem.TopLabel> | ||
<FormItem.TopAside>0/100</FormItem.TopAside> | ||
</FormItem.Top> | ||
}> | ||
<div/> | ||
</FormItem> | ||
</React.Fragment>) | ||
); | ||
};" | ||
`; |
11 changes: 11 additions & 0 deletions
11
packages/codemods/src/transforms/v7/__tests__/form-item.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
jest.autoMockOff(); | ||
import { defineSnapshotTestFromFixture } from '../../../testHelpers/testHelper'; | ||
|
||
const name = 'form-item'; | ||
const fixtures = ['basic'] as const; | ||
|
||
describe(name, () => { | ||
fixtures.forEach((test) => | ||
defineSnapshotTestFromFixture(__dirname, name, global.TRANSFORM_OPTIONS, `${name}/${test}`), | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import { API, FileInfo, JSXAttribute, JSXElement, JSXExpressionContainer } from 'jscodeshift'; | ||
import { getImportInfo } from '../../codemod-helpers'; | ||
import { JSCodeShiftOptions } from '../../types'; | ||
|
||
export const parser = 'tsx'; | ||
|
||
export default function transformer(file: FileInfo, api: API, options: JSCodeShiftOptions) { | ||
const { alias } = options; | ||
const j = api.jscodeshift; | ||
const source = j(file.source); | ||
const { localName } = getImportInfo(j, file, 'FormItem', alias); | ||
|
||
if (!localName) { | ||
return source.toSource(); | ||
} | ||
|
||
function processTopLabel(element: JSXElement): JSXExpressionContainer | true | undefined { | ||
if ( | ||
element.openingElement.name.type === 'JSXMemberExpression' && | ||
element.openingElement.name.property?.name === 'TopLabel' | ||
) { | ||
let multilineValue: JSXExpressionContainer | true | undefined; | ||
element.openingElement.attributes = element.openingElement.attributes?.filter((attr) => { | ||
if (attr.type === 'JSXAttribute' && attr.name.name === 'multiline') { | ||
if (!attr.value) { | ||
multilineValue = true; | ||
} else if (attr.value?.type === 'JSXExpressionContainer') { | ||
multilineValue = attr.value; | ||
} | ||
return false; | ||
} | ||
return true; | ||
}); | ||
return multilineValue; | ||
} | ||
return undefined; | ||
} | ||
|
||
function findTopLabelRecursive(element: JSXElement): JSXExpressionContainer | true | undefined { | ||
let result = processTopLabel(element); | ||
if (result !== undefined) { | ||
return result; | ||
} | ||
|
||
if (!element.children) { | ||
return undefined; | ||
} | ||
|
||
for (const child of element.children) { | ||
if (child.type === 'JSXElement') { | ||
result = findTopLabelRecursive(child); | ||
if (result !== undefined) { | ||
return result; | ||
} | ||
} | ||
} | ||
|
||
return undefined; | ||
} | ||
|
||
source.find(j.JSXElement, { openingElement: { name: { name: localName } } }).forEach((path) => { | ||
const formItem = path.node; | ||
let topMultiline: JSXAttribute | undefined; | ||
let topLabelMultiline: JSXExpressionContainer | true | undefined; | ||
const formItemAttributes = formItem.openingElement.attributes; | ||
|
||
const prettifyTopMultilineAttribute = () => { | ||
// Избавляемся от лишнего: | ||
// если topMultiline={false} можно убрать аттрибут | ||
// если topMultiline={true} можно убрать {true} | ||
if (topMultiline && formItemAttributes?.includes(topMultiline)) { | ||
if ( | ||
topMultiline?.value?.type === 'JSXExpressionContainer' && | ||
topMultiline.value.expression.type === 'BooleanLiteral' | ||
) { | ||
if (topMultiline.value.expression.value) { | ||
topMultiline.value = null; | ||
} else { | ||
formItemAttributes.splice(formItemAttributes.indexOf(topMultiline), 1); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
// Проверяем существующий topMultiline проп | ||
formItemAttributes?.forEach((attr) => { | ||
if (attr.type === 'JSXAttribute' && attr.name.name === 'topMultiline') { | ||
topMultiline = attr; | ||
} | ||
}); | ||
|
||
// Ищем FormItem.TopLabel в пропе top и topNode | ||
formItemAttributes?.forEach((attr) => { | ||
if ( | ||
attr.type === 'JSXAttribute' && | ||
(attr.name.name === 'top' || attr.name.name === 'topNode') | ||
) { | ||
if (attr.value?.type === 'JSXElement') { | ||
topLabelMultiline = findTopLabelRecursive(attr.value); | ||
} else if ( | ||
attr.value?.type === 'JSXExpressionContainer' && | ||
attr.value.expression.type === 'JSXElement' | ||
) { | ||
topLabelMultiline = findTopLabelRecursive(attr.value.expression); | ||
} | ||
} | ||
}); | ||
|
||
// Ищем FormItem.TopLabel в children | ||
if (topLabelMultiline === undefined && formItem.children) { | ||
for (const child of formItem.children) { | ||
if (child.type === 'JSXElement') { | ||
topLabelMultiline = findTopLabelRecursive(child); | ||
if (topLabelMultiline !== undefined) { | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
if (!topLabelMultiline) { | ||
prettifyTopMultilineAttribute(); | ||
return; | ||
} | ||
const newTopMultilineValue = topLabelMultiline === true ? null : topLabelMultiline; | ||
// Обновляем или добавляем topMultiline проп | ||
if (topMultiline) { | ||
// Если у FormItem задан topMultiline -> переопределяем | ||
topMultiline.value = newTopMultilineValue; | ||
} else if (topLabelMultiline) { | ||
// Если у FormItem не задан topMultiline | ||
// добавляем его в аргументы | ||
formItemAttributes?.push( | ||
j.jsxAttribute(j.jsxIdentifier('topMultiline'), newTopMultilineValue), | ||
); | ||
} | ||
prettifyTopMultilineAttribute(); | ||
}); | ||
|
||
return source.toSource(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.