Skip to content

Commit cb6fefb

Browse files
committed
feat: add parseValue to api
Alias the postcss-value-parser package under the api as parseValue for transforming values
1 parent fff7fe8 commit cb6fefb

File tree

9 files changed

+93
-5
lines changed

9 files changed

+93
-5
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ The second argument passed to the `transform` function.
109109
It's an object with helpers provided by `css-codemod` to perform transformations.
110110

111111
- `parse`: parse a raw CSS string into an AST. This returns the root node of the underlying abstract syntax tree. Transformations can be made by making direct mutations to the underlying node. This is performed with [PostCSS](https://postcss.org/) so the returned node is a PostCSS [Root](https://postcss.org/api/#root) node. Refer to the [PostCSS API documentation](https://postcss.org/api/) for documentation on nodes and various helpers.
112+
- `parseValue`: parse a CSS string [declaration value](https://postcss.org/api/#declaration-value) into a "mini" AST. This returns a result with all the nodes representing the string value. This is an alias for the [`postcss-value-parser`](https://github.com/TrySound/postcss-value-parser) package. PostCSS itself doesn't parse values and working with complex string values can be challenging. Converting these string values into nodes when necessary can be useful. There are additional parsers for things like selectors, dimensions, media queries, etc. provided in the [PostCSS documentation for plugins](https://github.com/postcss/postcss/blob/main/docs/writing-a-plugin.md#step-3-find-nodes).
112113

113114
### `parser`
114115

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"esbuild": "^0.14.14",
7272
"fs-extra": "^10.0.0",
7373
"glob": "^7.2.0",
74-
"postcss": "^8.4.5"
74+
"postcss": "^8.4.5",
75+
"postcss-value-parser": "^4.2.0"
7576
}
7677
}
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.a {
2+
--old-color: #fafafa;
3+
--other-color: magenta;
4+
5+
color: var(--old-color);
6+
background: orange;
7+
box-shadow: 0 0 0 2px var(--other-color);
8+
border: 2px solid var(--old-color);
9+
}
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.a {
2+
--new-color: #fafafa;
3+
--other-color: magenta;
4+
5+
color: var(--new-color);
6+
background: orange;
7+
box-shadow: 0 0 0 2px var(--other-color);
8+
border: 2px solid var(--new-color);
9+
}
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Transform } from '../../src';
2+
3+
const OLD_VAR = '--old-color';
4+
const NEW_VAR = '--new-color';
5+
6+
export const transform: Transform = (fileInfo, api) => {
7+
const root = api.parse(fileInfo.source);
8+
9+
root.walkDecls(decl => {
10+
if (decl.prop === OLD_VAR) {
11+
// Replace any custom property declarations.
12+
// eg: `--custom-prop: #fff`
13+
decl.prop = NEW_VAR;
14+
} else {
15+
// Replace any uses of the custom property in values.
16+
// eg: `border: 2px solid var(--custom-prop);`
17+
const result = api.parseValue(decl.value);
18+
19+
result.walk(value => {
20+
if (value.type === 'word' && value.value === OLD_VAR) {
21+
value.value = NEW_VAR;
22+
}
23+
});
24+
25+
decl.value = result.toString();
26+
}
27+
});
28+
29+
return root.toString();
30+
};

src/api.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import postcss, { Root, Parser, AcceptedPlugin } from 'postcss';
2+
import parseValue from 'postcss-value-parser';
23

34
export interface TransformAPI {
45
/**
56
* Parse a raw CSS string into an abstract syntax tree and return a PostCSS `Root` node.
67
*/
78
parse(source: string): Root;
9+
10+
/**
11+
* Parse a CSS value string into a series of nodes to operate on.
12+
*/
13+
parseValue(value: string): parseValue.ParsedValue;
814
}
915

1016
const createAPIParse = ({
@@ -47,6 +53,7 @@ export const createAPI = ({
4753
}: { parser?: Parser; plugins?: AcceptedPlugin[] } = {}): TransformAPI => {
4854
const api: TransformAPI = {
4955
parse: createAPIParse({ parser, plugins }),
56+
parseValue,
5057
};
5158

5259
return api;

test/api.test.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Declaration, Rule } from 'postcss';
2+
import { FunctionNode } from 'postcss-value-parser';
23
import { createAPI } from '../src/api';
34

45
describe('api', () => {
@@ -9,16 +10,45 @@ describe('api', () => {
910
const root = api.parse(`.class { color: orange; }`);
1011

1112
expect(root.type).toEqual('root');
12-
expect(root.nodes.length).toEqual(1);
13+
expect(root.nodes).toHaveLength(1);
1314

1415
const rule = root.nodes[0] as Rule;
1516
expect(rule.type).toEqual('rule');
16-
expect(rule.nodes.length).toEqual(1);
17+
expect(rule.nodes).toHaveLength(1);
1718

1819
const declaration = rule.nodes[0] as Declaration;
1920
expect(declaration.type).toEqual('decl');
2021
expect(declaration.prop).toEqual('color');
2122
expect(declaration.value).toEqual('orange');
2223
});
2324
});
25+
26+
describe('#parseValue', () => {
27+
it('should return the value nodes', () => {
28+
const { nodes } = api.parseValue(`1px solid var(--custom-prop)`);
29+
expect(nodes).toHaveLength(5);
30+
31+
let [word1, space1, word2, space2, func1] = nodes;
32+
33+
expect(word1.type).toBe('word');
34+
expect(word1.value).toBe('1px');
35+
36+
expect(space1.type).toBe('space');
37+
expect(space1.value).toBe(' ');
38+
39+
expect(word2.type).toBe('word');
40+
expect(word2.value).toBe('solid');
41+
42+
expect(space2.type).toBe('space');
43+
expect(space2.value).toBe(' ');
44+
45+
func1 = func1 as FunctionNode;
46+
expect(func1.type).toBe('function');
47+
expect(func1.value).toBe('var');
48+
expect(func1.nodes).toHaveLength(1);
49+
const [word3] = func1.nodes;
50+
expect(word3.type).toBe('word');
51+
expect(word3.value).toBe('--custom-prop');
52+
});
53+
});
2454
});

test/cli.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ const run = (recipe: string, { ext = 'css' }: RunOptions = {}) => {
3232
'transform.ts'
3333
);
3434
const inputGlob = path.join(inputDest, '**', fileGlob);
35-
const { stderr } = execa.sync(bin, ['-t', transform, inputGlob]);
35+
const { stdout, stderr } = execa.sync(bin, ['-t', transform, inputGlob]);
36+
expect(stdout).toEqual('');
3637
expect(stderr).toEqual('');
3738

3839
// Compare results

yarn.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -5150,7 +5150,7 @@ postcss-selector-parser@^6.0.2:
51505150
cssesc "^3.0.0"
51515151
util-deprecate "^1.0.2"
51525152

5153-
postcss-value-parser@^4.0.2:
5153+
postcss-value-parser@^4.0.2, postcss-value-parser@^4.2.0:
51545154
version "4.2.0"
51555155
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
51565156
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==

0 commit comments

Comments
 (0)