Skip to content

Commit c1dd5ec

Browse files
committedOct 24, 2023
feat: commentStyle and commentPosition improvements for createPropertyFormatter

File tree

3 files changed

+257
-80
lines changed

3 files changed

+257
-80
lines changed
 

‎.changeset/bright-timers-shout.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'style-dictionary': patch
3+
---
4+
5+
Allow overriding CSS formatting with commentStyle and commentPosition props.
6+
For commentStyle, options are 'short' or 'long'.
7+
For commentPosition, options are 'above' or 'inline'.
8+
9+
We also ensure that the right defaults are picked for CSS, SASS/SCSS, Stylus and Less.
10+
11+
This also contains a fix for ensuring that multi-line comments are automatically put "above" rather than "inline".

‎__tests__/common/formatHelpers/createPropertyFormatter.test.js

+165-54
Original file line numberDiff line numberDiff line change
@@ -209,70 +209,181 @@ const objectDictionary = createDictionary({
209209
describe('common', () => {
210210
describe('formatHelpers', () => {
211211
describe('createPropertyFormatter', () => {
212-
it('should support outputReferences', () => {
213-
const propFormatter = createPropertyFormatter({
214-
outputReferences: true,
215-
dictionary,
216-
format: 'css',
212+
describe('outputReferences', () => {
213+
it('should support outputReferences', () => {
214+
const propFormatter = createPropertyFormatter({
215+
outputReferences: true,
216+
dictionary,
217+
format: 'css',
218+
});
219+
expect(propFormatter(dictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;');
220+
expect(propFormatter(dictionary.tokens.tokens.ref)).toEqual(
221+
' --tokens-ref: var(--tokens-foo);',
222+
);
217223
});
218-
expect(propFormatter(dictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;');
219-
expect(propFormatter(dictionary.tokens.tokens.ref)).toEqual(
220-
' --tokens-ref: var(--tokens-foo);',
221-
);
222-
});
223224

224-
it('should support outputReferences when values are transformed by (transitive) "value" transforms', () => {
225-
const propFormatter = createPropertyFormatter({
226-
outputReferences: true,
227-
dictionary: transformedDictionary,
228-
format: 'css',
225+
it('should support outputReferences when values are transformed by (transitive) "value" transforms', () => {
226+
const propFormatter = createPropertyFormatter({
227+
outputReferences: true,
228+
dictionary: transformedDictionary,
229+
format: 'css',
230+
});
231+
expect(propFormatter(transformedDictionary.tokens.tokens.foo)).toEqual(
232+
' --tokens-foo: 5px;',
233+
);
234+
expect(propFormatter(transformedDictionary.tokens.tokens.ref)).toEqual(
235+
' --tokens-ref: var(--tokens-foo);',
236+
);
229237
});
230-
expect(propFormatter(transformedDictionary.tokens.tokens.foo)).toEqual(
231-
' --tokens-foo: 5px;',
232-
);
233-
expect(propFormatter(transformedDictionary.tokens.tokens.ref)).toEqual(
234-
' --tokens-ref: var(--tokens-foo);',
235-
);
236-
});
237238

238-
it('should support number values for outputReferences', () => {
239-
const propFormatter = createPropertyFormatter({
240-
outputReferences: true,
241-
dictionary: numberDictionary,
242-
format: 'css',
239+
it('should support number values for outputReferences', () => {
240+
const propFormatter = createPropertyFormatter({
241+
outputReferences: true,
242+
dictionary: numberDictionary,
243+
format: 'css',
244+
});
245+
expect(propFormatter(numberDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 10;');
246+
expect(propFormatter(numberDictionary.tokens.tokens.ref)).toEqual(
247+
' --tokens-ref: var(--tokens-foo);',
248+
);
249+
});
250+
251+
it('should support multiple references for outputReferences', () => {
252+
const propFormatter = createPropertyFormatter({
253+
outputReferences: true,
254+
dictionary: multiDictionary,
255+
format: 'css',
256+
});
257+
expect(propFormatter(multiDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 10px;');
258+
expect(propFormatter(multiDictionary.tokens.tokens.bar)).toEqual(' --tokens-bar: 15px;');
259+
expect(propFormatter(multiDictionary.tokens.tokens.ref)).toEqual(
260+
' --tokens-ref: var(--tokens-foo) 5px var(--tokens-bar);',
261+
);
243262
});
244-
expect(propFormatter(numberDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 10;');
245-
expect(propFormatter(numberDictionary.tokens.tokens.ref)).toEqual(
246-
' --tokens-ref: var(--tokens-foo);',
247-
);
248-
});
249263

250-
it('should support multiple references for outputReferences', () => {
251-
const propFormatter = createPropertyFormatter({
252-
outputReferences: true,
253-
dictionary: multiDictionary,
254-
format: 'css',
264+
it('should support object value references for outputReferences', () => {
265+
// The ref is an object type value, which means there will usually be some kind of transform (e.g. a CSS shorthand transform)
266+
// to change it from an object to a string. In our example, we use a border CSS shorthand for border token.
267+
// In this case, since it is an object value, we will run the transformation on the transformed (string) value.
268+
const propFormatter = createPropertyFormatter({
269+
outputReferences: true,
270+
dictionary: objectDictionary,
271+
format: 'css',
272+
});
273+
expect(propFormatter(objectDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;');
274+
expect(propFormatter(objectDictionary.tokens.tokens.ref)).toEqual(
275+
' --tokens-ref: var(--tokens-foo) dashed #FF00FF;',
276+
);
255277
});
256-
expect(propFormatter(multiDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 10px;');
257-
expect(propFormatter(multiDictionary.tokens.tokens.bar)).toEqual(' --tokens-bar: 15px;');
258-
expect(propFormatter(multiDictionary.tokens.tokens.ref)).toEqual(
259-
' --tokens-ref: var(--tokens-foo) 5px var(--tokens-bar);',
260-
);
261278
});
262279

263-
it('should support object value references for outputReferences', () => {
264-
// The ref is an object type value, which means there will usually be some kind of transform (e.g. a CSS shorthand transform)
265-
// to change it from an object to a string. In our example, we use a border CSS shorthand for border token.
266-
// In this case, since it is an object value, we will run the transformation on the transformed (string) value.
267-
const propFormatter = createPropertyFormatter({
268-
outputReferences: true,
269-
dictionary: objectDictionary,
270-
format: 'css',
280+
describe('commentStyle', () => {
281+
const commentProperties = {
282+
color: {
283+
red: {
284+
name: 'color-red',
285+
value: '#FF0000',
286+
comment: 'Foo bar qux',
287+
attributes: {
288+
category: 'color',
289+
type: 'red',
290+
},
291+
path: ['color', 'red'],
292+
},
293+
blue: {
294+
name: 'color-blue',
295+
value: '#0000FF',
296+
comment: 'Foo\nbar\nqux',
297+
attributes: {
298+
category: 'color',
299+
type: 'blue',
300+
},
301+
path: ['color', 'blue'],
302+
},
303+
green: {
304+
name: 'color-green',
305+
value: '#00FF00',
306+
comment: 'Foo bar qux',
307+
attributes: {
308+
category: 'color',
309+
type: 'green',
310+
},
311+
path: ['color', 'green'],
312+
},
313+
},
314+
};
315+
316+
const commentDictionary = createDictionary({
317+
properties: commentProperties,
318+
});
319+
320+
it('should default to putting comment next to the output value', () => {
321+
// long commentStyle
322+
const cssFormatter = createPropertyFormatter({
323+
format: 'css',
324+
commentDictionary,
325+
});
326+
// short commentStyle
327+
const sassFormatter = createPropertyFormatter({
328+
format: 'sass',
329+
commentDictionary,
330+
});
331+
332+
// red = single-line comment, blue = multi-line comment
333+
const cssRed = cssFormatter(commentDictionary.tokens.color.red);
334+
const cssBlue = cssFormatter(commentDictionary.tokens.color.blue);
335+
const sassRed = sassFormatter(commentDictionary.tokens.color.red);
336+
const sassBlue = sassFormatter(commentDictionary.tokens.color.blue);
337+
338+
// Note that since CSS puts it inside a selector, there is an indentation of 2 spaces as well
339+
// CSS also has commentStyle long, whereas sass uses short
340+
expect(cssRed).toMatchInlineSnapshot(`" --color-red: #FF0000; /* Foo bar qux */"`);
341+
342+
expect(cssBlue).toMatchInlineSnapshot(`
343+
" /**
344+
* Foo
345+
* bar
346+
* qux
347+
*/
348+
--color-blue: #0000FF;"
349+
`);
350+
351+
expect(sassRed).toMatchInlineSnapshot(`"$color-red: #FF0000; // Foo bar qux"`);
352+
expect(sassBlue).toMatchInlineSnapshot(`
353+
"// Foo
354+
// bar
355+
// qux
356+
$color-blue: #0000FF;"
357+
`);
358+
});
359+
360+
it('allows overriding formatting commentStyle', () => {
361+
// long commentStyle
362+
const cssFormatter = createPropertyFormatter({
363+
format: 'css',
364+
commentDictionary,
365+
formatting: { commentStyle: 'long', commentPosition: 'above' },
366+
});
367+
// short commentStyle
368+
const sassFormatter = createPropertyFormatter({
369+
format: 'sass',
370+
commentDictionary,
371+
formatting: { commentStyle: 'short', commentPosition: 'above' },
372+
});
373+
374+
const cssRed = cssFormatter(commentDictionary.tokens.color.green);
375+
const sassRed = sassFormatter(commentDictionary.tokens.color.green);
376+
377+
expect(cssRed).toMatchInlineSnapshot(`
378+
" /* Foo bar qux */
379+
--color-green: #00FF00;"
380+
`);
381+
382+
expect(sassRed).toMatchInlineSnapshot(`
383+
"// Foo bar qux
384+
$color-green: #00FF00;"
385+
`);
271386
});
272-
expect(propFormatter(objectDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;');
273-
expect(propFormatter(objectDictionary.tokens.tokens.ref)).toEqual(
274-
' --tokens-ref: var(--tokens-foo) dashed #FF00FF;',
275-
);
276387
});
277388
});
278389
});

‎lib/common/formatHelpers/createPropertyFormatter.js

+81-26
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,65 @@
1414
const defaultFormatting = {
1515
prefix: '',
1616
commentStyle: 'long',
17+
commentPosition: 'inline',
1718
indentation: '',
1819
separator: ' =',
1920
suffix: ';',
2021
};
2122

23+
/**
24+
* Split a string comment by newlines and
25+
* convert to multi-line comment if necessary
26+
* @param {string} to_ret_prop
27+
* @param {{comment: string; style: 'short' | 'long'; position: 'above' | 'inline'; indentation: string}} options
28+
* @returns {string}
29+
*/
30+
function addComment(to_ret_prop, options) {
31+
const { comment, style, indentation } = options;
32+
let { position } = options;
33+
34+
const commentsByNewLine = comment.split('\n');
35+
if (commentsByNewLine.length > 1) {
36+
position = 'above';
37+
}
38+
39+
let processedComment;
40+
switch (style) {
41+
case 'short':
42+
if (position === 'inline') {
43+
processedComment = `// ${comment}`;
44+
} else {
45+
processedComment = commentsByNewLine.reduce(
46+
(acc, curr) => `${acc}${indentation}// ${curr}\n`,
47+
'',
48+
);
49+
// remove trailing newline
50+
processedComment = processedComment.replace(/\n$/g, '');
51+
}
52+
break;
53+
case 'long':
54+
if (commentsByNewLine.length > 1) {
55+
processedComment = commentsByNewLine.reduce(
56+
(acc, curr) => `${acc}${indentation} * ${curr}\n`,
57+
`${indentation}/**\n`,
58+
);
59+
processedComment += `${indentation} */`;
60+
} else {
61+
processedComment = `${position === 'above' ? indentation : ''}/* ${comment} */`;
62+
}
63+
break;
64+
}
65+
66+
if (position === 'above') {
67+
// put the comment above the prop if it's multi-line or if commentStyle ended with -above
68+
to_ret_prop = `${processedComment}\n${to_ret_prop}`;
69+
} else {
70+
to_ret_prop = `${to_ret_prop} ${processedComment}`;
71+
}
72+
73+
return to_ret_prop;
74+
}
75+
2276
/**
2377
* Creates a function that can be used to format a property. This can be useful
2478
* to use as the function on `dictionary.allTokens.map`. The formatting
@@ -57,37 +111,37 @@ export default function createPropertyFormatter({
57111
formatting = {},
58112
themeable = false,
59113
}) {
60-
let { prefix, commentStyle, indentation, separator, suffix } = Object.assign(
61-
{},
62-
defaultFormatting,
63-
formatting,
64-
);
65-
114+
const formatDefaults = {};
66115
switch (format) {
67116
case 'css':
68-
prefix = '--';
69-
indentation = ' ';
70-
separator = ':';
117+
formatDefaults.prefix = '--';
118+
formatDefaults.indentation = ' ';
119+
formatDefaults.separator = ':';
71120
break;
72121
case 'sass':
73-
prefix = '$';
74-
commentStyle = 'short';
75-
indentation = '';
76-
separator = ':';
122+
formatDefaults.prefix = '$';
123+
formatDefaults.commentStyle = 'short';
124+
formatDefaults.indentation = '';
125+
formatDefaults.separator = ':';
77126
break;
78127
case 'less':
79-
prefix = '@';
80-
commentStyle = 'short';
81-
indentation = '';
82-
separator = ':';
128+
formatDefaults.prefix = '@';
129+
formatDefaults.commentStyle = 'short';
130+
formatDefaults.indentation = '';
131+
formatDefaults.separator = ':';
83132
break;
84133
case 'stylus':
85-
prefix = '$';
86-
commentStyle = 'short';
87-
indentation = '';
88-
separator = '=';
134+
formatDefaults.prefix = '$';
135+
formatDefaults.commentStyle = 'short';
136+
formatDefaults.indentation = '';
137+
formatDefaults.separator = '=';
89138
break;
90139
}
140+
let { prefix, commentStyle, commentPosition, indentation, separator, suffix } = {
141+
...defaultFormatting,
142+
...formatDefaults,
143+
...formatting,
144+
};
91145

92146
return function (prop) {
93147
let to_ret_prop = `${indentation}${prefix}${prop.name}${separator} `;
@@ -160,11 +214,12 @@ export default function createPropertyFormatter({
160214
to_ret_prop += suffix;
161215

162216
if (prop.comment && commentStyle !== 'none') {
163-
if (commentStyle === 'short') {
164-
to_ret_prop = to_ret_prop.concat(` // ${prop.comment}`);
165-
} else {
166-
to_ret_prop = to_ret_prop.concat(` /* ${prop.comment} */`);
167-
}
217+
to_ret_prop = addComment(to_ret_prop, {
218+
comment: prop.comment,
219+
style: commentStyle,
220+
position: commentPosition,
221+
indentation,
222+
});
168223
}
169224

170225
return to_ret_prop;

0 commit comments

Comments
 (0)
Please sign in to comment.