Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,37 +48,34 @@ exports[`globalCssToCssModule transforms correctly 1`] = `
:global(.copy-link-action) {
opacity: 0;
}

&:hover,
&:focus-within {
:global(.copy-link-action) {
opacity: 1;
}
}
}
.spacer {
flex: 1 1 0;
}

/* &__icon-chevron { */
.icon-chevron {
opacity: 0.6;
margin: auto;
}
.alert {
margin: 0 0.25rem;
padding: 0.125rem 0.25rem;
cursor: default;
user-select: none;
.logo {
display: flex;
}

/* &__action { */
.action {
margin: 0.5rem 0.625rem 0.5rem 0;
padding: 0.25rem;
/* &__action-list-item { */
.action-list-item {
/* Have a small gap between buttons so they are visually distinct when pressed */
/* stylelint-disable-next-line declaration-property-unit-whitelist */
margin-left: 1px;

:global(.theme-light) & {
margin-top: 0;
margin-bottom: 0;
/* &:hover { */
&:hover {
/* background: var(--color-bg-1); */
background: var(--color-bg-1);

/* .theme-light & { */
:global(.theme-light) & {
background: inherit;
}
}
}

Expand All @@ -94,31 +91,38 @@ exports[`globalCssToCssModule transforms correctly 1`] = `
}
}

/* &__action-list-item { */
.action-list-item {
/* Have a small gap between buttons so they are visually distinct when pressed */
/* stylelint-disable-next-line declaration-property-unit-whitelist */
margin-left: 1px;

/* &:hover { */
&:hover {
/* background: var(--color-bg-1); */
background: var(--color-bg-1);
/* &__action { */
.action {
margin: 0.5rem 0.625rem 0.5rem 0;
padding: 0.25rem;

/* .theme-light & { */
:global(.theme-light) & {
background: inherit;
}
:global(.theme-light) & {
margin-top: 0;
margin-bottom: 0;
}
}

.alert {
margin: 0 0.25rem;
padding: 0.125rem 0.25rem;
cursor: default;
user-select: none;
}

/* &__icon-chevron { */
.icon-chevron {
opacity: 0.6;
margin: auto;
}

.spacer {
flex: 1 1 0;
}

/* &__kek-pek { */
.kek-pek {
color: red;
}
.logo {
display: flex;
}
"
`;

Expand Down
10 changes: 3 additions & 7 deletions src/transforms/globalCssToCssModule/globalCssToCssModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { addClassNamesUtilImportIfNeeded } from '../../utils/classNamesUtility'

import { getCssModuleExportNameMap } from './postcss/getCssModuleExportNameMap'
import { transformFileToCssModule } from './postcss/transformFileToCssModule'
import { getNodesWithClassName } from './ts/getNodesWithClassName'
import { STYLES_IDENTIFIER, processNodesWithClassName } from './ts/processNodesWithClassName'
import { STYLES_IDENTIFIER } from './ts/processNodesWithClassName'
import { transformComponentFile } from './ts/transformComponentFile'

/**
* Convert globally scoped stylesheet tied to the React component into a CSS Module.
Expand Down Expand Up @@ -68,11 +68,7 @@ export async function globalCssToCssModule(options: CodemodOptions): CodemodResu
sourceFilePath: cssFilePath,
})

processNodesWithClassName({
exportNameMap,
nodesWithClassName: getNodesWithClassName(tsSourceFile),
})

transformComponentFile({ tsSourceFile, exportNameMap, cssModuleFileName })
addClassNamesUtilImportIfNeeded(tsSourceFile)
tsSourceFile.addImportDeclaration({
defaultImport: STYLES_IDENTIFIER,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ describe('transformFileToCssModule', () => {
white-space: nowrap;
}

&:disabled &__button {
display: none;
}

@media (--xs-breakpoint-down) {
border-radius: var(--border-radius);
}
Expand Down Expand Up @@ -67,21 +71,25 @@ describe('transformFileToCssModule', () => {
white-space: nowrap;
}

&:disabled .button {
display: none;
}

@media (--xs-breakpoint-down) {
border-radius: var(--border-radius);
}
}

/* &__button comment*/
.button {
margin-top: 1px;
}

:global(.theme-light) {
.spacer {
flex: 1 1 0;
}
}

/* &__button comment*/
.button {
margin-top: 1px;
}
`

const { css, filePath } = await transformFileToCssModule({ sourceCss, sourceFilePath: 'whatever.scss' })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import camelcase from 'camelcase'

import { decapitalize, isDefined } from '../../../utils'

interface RemovedPrefix {
Expand Down Expand Up @@ -62,7 +64,7 @@ export function getPrefixesToRemove(exportNameMap: Record<string, string>): Remo
if (matches) {
return {
prefix: matches[0],
exportName: exportNameMap[matches[1]],
exportName: camelcase(matches[1]),
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AcceptedPlugin, Rule, ChildNode } from 'postcss'
import { AcceptedPlugin, Rule, ChildNode, Root } from 'postcss'
import parser, { isRoot, Selector } from 'postcss-selector-parser'

interface PostcssToCssModulePluginOptions {
Expand Down Expand Up @@ -110,15 +110,19 @@ function updateChildSelectors(parent: Rule, child: Rule): string[] {
* Some important comment about &__button selector.
* .button { ... }
*/
const parentOrComment = pickComment(child.prev(), parent)
parentOrComment.after(child)
pickComment(child.prev(), parent.root())
parent.root().last?.after(child)
}

if (parent.nodes.length === 0) {
parent.remove()
}

return updatedChildSelectors
}

function replaceSelectorNodesIfNeeded(nodes: Selector): boolean {
return nodes.reduce<boolean>((shouldRemoveNesting, node) => {
return nodes.reduce<boolean>((shouldRemoveNesting, node, index) => {
/**
* Assume that all nested classes and ids not starting with `&` are global:
*
Expand Down Expand Up @@ -167,7 +171,26 @@ function replaceSelectorNodesIfNeeded(nodes: Selector): boolean {
node.replaceWith(parse(''))
nextNode.replaceWith(parse(nextNodeValue.replace('__', '.')))

return true
/**
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool example

* If its not the first node of the selector — keep nesting in place
*
* ```scss
* .menu {
* &:hover &__button { ... }
* }
* ```
*
* Turns into:
*
* ```scss
* .menu {
* &:hover .button { ... }
* }
* ```
*/
if (index === 0) {
return true
}
}
}
}
Expand All @@ -181,9 +204,10 @@ function wrapSelectorInGlobalKeyword(selector: string): string {
}

// If passed node is a comment -> attach it to the end of the file and return it, otherwise return the passed node.
function pickComment(maybeCommentNode: ChildNode | undefined, parent: Rule): Rule {
function pickComment(maybeCommentNode: ChildNode | undefined, parent: Rule | Root): Rule | Root {
if (maybeCommentNode && maybeCommentNode.type === 'comment') {
parent.after(maybeCommentNode)
parent.last?.after(maybeCommentNode)

return maybeCommentNode as unknown as Rule
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,21 @@ describe('getClassNameNodeReplacement', () => {

describe.each(testCases)('in parent kind $parentKind', ({ fileSource, replacement: expectedReplacement }) => {
const parentNode = getParentFromFirstClassNameNode(fileSource)
const getReplacement = (options?: Partial<GetClassNameNodeReplacementOptions>) =>
getClassNameNodeReplacement({
const getReplacement = (options?: Partial<GetClassNameNodeReplacementOptions>) => {
const result = getClassNameNodeReplacement({
parentNode,
exportNameReferences,
leftOverClassName,
...options,
})

if (result.isParentTransformed) {
throw new Error('No parent transform is expected')
}

return result.replacement
}

it('returns correct replacement with `leftOverClassName` provided', () => {
const replacement = getReplacement()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('processNodesWithClassName', () => {
`)

processNodesWithClassName({
usageStats: {},
nodesWithClassName: getNodesWithClassName(sourceFile),
exportNameMap: {
kek: 'kek',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ import { splitClassName } from '../splitClassName'

describe('splitClassName', () => {
it('splits correctly', () => {
const { exportNames, leftOverClassnames } = splitClassName('kek kek--wow d-flex mr-1', {
kek: 'kek',
'kek--wow': 'kekWow',
const { exportNames, leftOverClassnames } = splitClassName({
usageStats: {},
className: 'kek kek--wow d-flex mr-1',
exportNameMap: {
kek: 'kek',
'kek--wow': 'kekWow',
},
})

expect(exportNames).toEqual(['kek', 'kekWow'])
Expand Down
Loading