diff --git a/__tests__/common/formatHelpers/createPropertyFormatter.test.js b/__tests__/common/formatHelpers/createPropertyFormatter.test.js index 9099fcf9e..a80e0c264 100644 --- a/__tests__/common/formatHelpers/createPropertyFormatter.test.js +++ b/__tests__/common/formatHelpers/createPropertyFormatter.test.js @@ -10,30 +10,30 @@ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions * and limitations under the License. */ -const createPropertyFormatter = require("../../../lib/common/formatHelpers/createPropertyFormatter"); -const createDictionary = require("../../../lib/utils/createDictionary"); +const createPropertyFormatter = require('../../../lib/common/formatHelpers/createPropertyFormatter'); +const createDictionary = require('../../../lib/utils/createDictionary'); const dictionary = createDictionary({ properties: { tokens: { foo: { original: { - value: "5px", - type: "spacing", + value: '5px', + type: 'spacing' }, attributes: { - category: "tokens", - type: "foo", + category: 'tokens', + type: 'foo' }, - name: "tokens-foo", - path: ["tokens", "foo"], - value: "5px", - type: "spacing", + name: 'tokens-foo', + path: ['tokens', 'foo'], + value: '5px', + type: 'spacing' }, ref: { original: { - value: "{tokens.foo}", - type: "spacing", + value: '{tokens.foo}', + type: 'spacing' }, attributes: { category: 'tokens', @@ -53,22 +53,22 @@ const transformedDictionary = createDictionary({ tokens: { foo: { original: { - value: "5px", - type: "spacing", + value: '5px', + type: 'spacing' }, attributes: { - category: "tokens", - type: "foo", + category: 'tokens', + type: 'foo' }, - name: "tokens-foo", - path: ["tokens", "foo"], - value: "5px", - type: "spacing", + name: 'tokens-foo', + path: ['tokens', 'foo'], + value: '5px', + type: 'spacing' }, ref: { original: { - value: "{tokens.foo}", - type: "spacing", + value: '{tokens.foo}', + type: 'spacing' }, attributes: { category: 'tokens', @@ -79,8 +79,8 @@ const transformedDictionary = createDictionary({ value: 'changed by transitive transform', type: 'spacing' }, - }, - }, + } + } }); const numberDictionary = createDictionary({ @@ -210,39 +210,154 @@ const objectDictionary = createDictionary({ describe('common', () => { describe('formatHelpers', () => { describe('createPropertyFormatter', () => { - it('should support outputReferences', () => { - const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary, format: 'css' }) - expect(propFormatter(dictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;'); - expect(propFormatter(dictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo);'); - }) - - it('should support outputReferences when values are transformed by (transitive) "value" transforms', () => { - const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary: transformedDictionary, format: 'css' }) - expect(propFormatter(transformedDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;'); - expect(propFormatter(transformedDictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo);'); - }) - - it('should support number values for outputReferences', () => { - const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary: numberDictionary, format: 'css' }) - expect(propFormatter(numberDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 10;'); - expect(propFormatter(numberDictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo);'); - }) - - it('should support multiple references for outputReferences', () => { - const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary: multiDictionary, format: 'css' }) - expect(propFormatter(multiDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 10px;'); - expect(propFormatter(multiDictionary.tokens.tokens.bar)).toEqual(' --tokens-bar: 15px;'); - expect(propFormatter(multiDictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo) 5px var(--tokens-bar);'); - }) - - it('should support object value references for outputReferences', () => { - // The ref is an object type value, which means there will usually be some kind of transform (e.g. a CSS shorthand transform) - // to change it from an object to a string. In our example, we use a border CSS shorthand for border token. - // In this case, since it is an object value, we will run the transformation on the transformed (string) value. - const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary: objectDictionary, format: 'css' }) - expect(propFormatter(objectDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;'); - expect(propFormatter(objectDictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo) dashed #FF00FF;'); - }) + describe('outputReferences', () => { + it('should support outputReferences', () => { + const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary, format: 'css' }) + expect(propFormatter(dictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;'); + expect(propFormatter(dictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo);'); + }) + + it('should support outputReferences when values are transformed by (transitive) "value" transforms', () => { + const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary: transformedDictionary, format: 'css' }) + expect(propFormatter(transformedDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;'); + expect(propFormatter(transformedDictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo);'); + }) + + it('should support number values for outputReferences', () => { + const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary: numberDictionary, format: 'css' }) + expect(propFormatter(numberDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 10;'); + expect(propFormatter(numberDictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo);'); + }) + + it('should support multiple references for outputReferences', () => { + const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary: multiDictionary, format: 'css' }) + expect(propFormatter(multiDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 10px;'); + expect(propFormatter(multiDictionary.tokens.tokens.bar)).toEqual(' --tokens-bar: 15px;'); + expect(propFormatter(multiDictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo) 5px var(--tokens-bar);'); + }) + + it('should support object value references for outputReferences', () => { + // The ref is an object type value, which means there will usually be some kind of transform (e.g. a CSS shorthand transform) + // to change it from an object to a string. In our example, we use a border CSS shorthand for border token. + // In this case, since it is an object value, we will run the transformation on the transformed (string) value. + const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary: objectDictionary, format: 'css' }) + expect(propFormatter(objectDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;'); + expect(propFormatter(objectDictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo) dashed #FF00FF;'); + }) + }); + + describe('commentStyle', () => { + const commentProperties = { + color: { + red: { + name: 'color-red', + value: '#FF0000', + comment: 'Foo bar qux', + attributes: { + category: 'color', + type: 'red', + }, + path: ['color', 'red'], + }, + blue: { + name: 'color-blue', + value: '#0000FF', + comment: 'Foo\nbar\nqux', + attributes: { + category: 'color', + type: 'blue', + }, + path: ['color', 'blue'], + }, + green: { + name: 'color-green', + value: '#00FF00', + comment: 'Foo bar qux', + attributes: { + category: 'color', + type: 'green', + }, + path: ['color', 'green'], + }, + }, + }; + + const commentDictionary = createDictionary({ + properties: commentProperties, + }); + + it('should default to putting comment next to the output value', () => { + // long commentStyle + const cssFormatter = createPropertyFormatter({ + format: 'css', + commentDictionary, + }); + // short commentStyle + const sassFormatter = createPropertyFormatter({ + format: 'sass', + commentDictionary, + }); + + // red = single-line comment, blue = multi-line comment + const cssRed = cssFormatter(commentDictionary.tokens.color.red); + const cssBlue = cssFormatter(commentDictionary.tokens.color.blue); + const sassRed = sassFormatter(commentDictionary.tokens.color.red); + const sassBlue = sassFormatter(commentDictionary.tokens.color.blue); + + // Note that since CSS puts it inside a selector, there is an indentation of 2 spaces as well + // CSS also has commentStyle long, whereas sass uses short + expect(cssRed).toMatchInlineSnapshot( + `" --color-red: #FF0000; /* Foo bar qux */"` + ); + + expect(cssBlue).toMatchInlineSnapshot(` +" /** + * Foo + * bar + * qux + */ + --color-blue: #0000FF;" +`); + + expect(sassRed).toMatchInlineSnapshot( + `"$color-red: #FF0000; // Foo bar qux"` + ); + expect(sassBlue).toMatchInlineSnapshot(` +"// Foo +// bar +// qux +$color-blue: #0000FF;" +`); + }); + + it('allows overriding formatting commentStyle', () => { + // long commentStyle + const cssFormatter = createPropertyFormatter({ + format: 'css', + commentDictionary, + formatting: { commentStyle: 'long', commentPosition: 'above' }, + }); + // short commentStyle + const sassFormatter = createPropertyFormatter({ + format: 'sass', + commentDictionary, + formatting: { commentStyle: 'short', commentPosition: 'above' }, + }); + + const cssRed = cssFormatter(commentDictionary.tokens.color.green); + const sassRed = sassFormatter(commentDictionary.tokens.color.green); + + expect(cssRed).toMatchInlineSnapshot(` +" /* Foo bar qux */ + --color-green: #00FF00;" +`); + + expect(sassRed).toMatchInlineSnapshot(` +"// Foo bar qux +$color-green: #00FF00;" +`); + }); + }); }) }) }) diff --git a/lib/common/formatHelpers/createPropertyFormatter.js b/lib/common/formatHelpers/createPropertyFormatter.js index fe7e40633..caaa716dc 100644 --- a/lib/common/formatHelpers/createPropertyFormatter.js +++ b/lib/common/formatHelpers/createPropertyFormatter.js @@ -14,59 +14,61 @@ const defaultFormatting = { prefix: '', commentStyle: 'long', + commentPosition: 'inline', indentation: '', separator: ' =', suffix: ';' -} +}; /** * Split a string comment by newlines and * convert to multi-line comment if necessary * @param {string} to_ret_prop - * @param {{comment: string; style: 'short' | 'long'; indentation: string}} options - * @param {'short' | 'long'} commentStyle - * @param {string} indentation - * @returns {{hasNewLines: boolean, comment: string}} + * @param {{comment: string; style: 'short' | 'long'; position: 'above' | 'inline'; indentation: string}} options + * @returns {string} */ function addComment(to_ret_prop, options) { const { comment, style, indentation } = options; + let { position } = options; - const [commentStyle,placeAbove] = style.split('-above'); const commentsByNewLine = comment.split("\n"); - const hasNewLines = commentsByNewLine.length > 1; - - const commentCase = `${commentStyle}${hasNewLines ? '-new-lines' :''}`; - let processed; + if (commentsByNewLine.length > 1) { + position = 'above'; + } - switch(commentCase) { + let processedComment; + switch (style) { case 'short': - processed = `// ${comment}`; + if (position === 'inline') { + processedComment = `// ${comment}`; + } else { + processedComment = commentsByNewLine.reduce( + (acc, curr) => `${acc}${indentation}// ${curr}\n`, + '' + ); + // remove trailing newline + processedComment = processedComment.replace(/\n$/g, ''); + } break; case 'long': - processed = `/* ${comment} */`; - break; - case 'short-new-lines': - processed = commentsByNewLine.reduce( - (acc, curr) => `${acc}${indentation}// ${curr}\n`, - '' - ); - // remove trailing newline - processed = processed.replace(/\n$/g, ''); - break; - case 'long-new-lines': - processed = commentsByNewLine.reduce( - (acc, curr) => `${acc}${indentation} * ${curr}\n`, - `${indentation}/**\n` - ); - processed += `${indentation} */`; + if (commentsByNewLine.length > 1) { + processedComment = commentsByNewLine.reduce( + (acc, curr) => `${acc}${indentation} * ${curr}\n`, + `${indentation}/**\n` + ); + processedComment += `${indentation} */`; + } else { + processedComment = `${position === 'above' ? indentation : '' + }/* ${comment} */`; + } break; } - if (hasNewLines || placeAbove !== undefined) { + if (position === 'above') { // put the comment above the prop if it's multi-line or if commentStyle ended with -above - to_ret_prop = `${processed}\n${to_ret_prop}`; + to_ret_prop = `${processedComment}\n${to_ret_prop}`; } else { - to_ret_prop = `${to_ret_prop} ${processed}`; + to_ret_prop = `${to_ret_prop} ${processedComment}`; } return to_ret_prop; @@ -111,7 +113,7 @@ function createPropertyFormatter({ themeable = false }) { const formatDefaults = {}; - switch(format) { + switch (format) { case 'css': formatDefaults.prefix = '--'; formatDefaults.indentation = ' '; @@ -136,8 +138,7 @@ function createPropertyFormatter({ formatDefaults.separator = '='; break; } - let {prefix, commentStyle, indentation, separator, suffix} = Object.assign({}, defaultFormatting, formatDefaults, formatting); - + let {prefix, commentStyle, commentPosition, indentation, separator, suffix} = Object.assign({}, defaultFormatting, formatDefaults, formatting); return function(prop) { let to_ret_prop = `${indentation}${prefix}${prop.name}${separator} `; @@ -206,7 +207,12 @@ function createPropertyFormatter({ to_ret_prop += suffix; if (prop.comment && commentStyle !== 'none') { - to_ret_prop = addComment(to_ret_prop, { comment: prop.comment, style: commentStyle, indentation }); + to_ret_prop = addComment(to_ret_prop, { + comment: prop.comment, + style: commentStyle, + position: commentPosition, + indentation, + }); } return to_ret_prop;