diff --git a/.changeset/dry-walls-marry.md b/.changeset/dry-walls-marry.md new file mode 100644 index 000000000..017d43164 --- /dev/null +++ b/.changeset/dry-walls-marry.md @@ -0,0 +1,5 @@ +--- +'@primer/primitives': minor +--- + +Adding a pascalCase trasnformer diff --git a/src/transformers/namePathToPascalCase.test.ts b/src/transformers/namePathToPascalCase.test.ts new file mode 100644 index 000000000..4f2e42967 --- /dev/null +++ b/src/transformers/namePathToPascalCase.test.ts @@ -0,0 +1,55 @@ +import {getMockToken} from '../test-utilities' +import {namePathToPascalCase} from './namePathToPascalCase' + +describe('Transformer: namePathToPascalCase', () => { + it('converts path elements to dot.notation and ignores name proprty', () => { + const input = [ + getMockToken({ + name: 'tokenName', + path: ['path', 'to', 'token'], + }), + getMockToken({ + name: 'tokenName', + path: ['PATH', 'tO', 'Token'], + }), + getMockToken({ + name: 'tokenName', + path: ['path', 'toToken'], + }), + getMockToken({ + name: 'tokenName', + path: ['pathtoToken'], + }), + ] + const expectedOutput = ['PathToToken', 'PATHTOToken', 'PathToToken', 'PathtoToken'] + + expect(input.map(item => namePathToPascalCase.transformer(item, {}))).toStrictEqual(expectedOutput) + }) + + it('removes `@`, so we can use it for the default hack', () => { + const input = [ + getMockToken({ + name: 'tokenName', + path: ['fgColor', 'accent', '@'], + }), + getMockToken({ + name: 'tokenName', + path: ['fgColor', '@', 'muted'], + }), + ] + const expectedOutput = ['FgColorAccent', 'FgColorMuted'] + expect(input.map(item => namePathToPascalCase.transformer(item, {}))).toStrictEqual(expectedOutput) + }) + + it('adds prefix to token name', () => { + const platform = { + prefix: 'PRIMER', + } + const input = getMockToken({ + name: 'tokenName', + path: ['start', 'pathTo', 'token'], + }) + const expectedOutput = 'PRIMERStartPathToToken' + expect(namePathToPascalCase.transformer(input, platform)).toStrictEqual(expectedOutput) + }) +}) diff --git a/src/transformers/namePathToPascalCase.ts b/src/transformers/namePathToPascalCase.ts new file mode 100644 index 000000000..023a3ef28 --- /dev/null +++ b/src/transformers/namePathToPascalCase.ts @@ -0,0 +1,13 @@ +import type StyleDictionary from 'style-dictionary' +import {toPascalCase} from '../utilities/toPascalCase' +/** + * @description converts the [TransformedToken's](https://github.com/amzn/style-dictionary/blob/main/types/TransformedToken.d.ts) `.path` array to a PascalCase string, preserves casing of parts + * @type name transformer — [StyleDictionary.NameTransform](https://github.com/amzn/style-dictionary/blob/main/types/Transform.d.ts) + * @matcher omitted to match all tokens + * @transformer returns `string` PascalCase + */ +export const namePathToPascalCase: StyleDictionary.Transform = { + type: `name`, + transformer: (token: StyleDictionary.TransformedToken, options?: StyleDictionary.Platform): string => + toPascalCase([options?.prefix || '', ...token.path]), +} diff --git a/src/utilities/filterStringArray.test.ts b/src/utilities/filterStringArray.test.ts new file mode 100644 index 000000000..28859f616 --- /dev/null +++ b/src/utilities/filterStringArray.test.ts @@ -0,0 +1,25 @@ +import {filterStringArray} from './filterStringArray' + +describe('Utilities: filterStringArray', () => { + it('keeps words', () => { + expect(filterStringArray(['primer', 'test'])).toStrictEqual(['primer', 'test']) + }) + + it('it filters out undefined, etc.', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(filterStringArray(['primer', false, null, undefined, ''])).toStrictEqual(['primer']) + }) + + it('remove special chars', () => { + expect(filterStringArray(['primer@test', 'Y_e-s', 'with space'])).toStrictEqual([ + 'primer', + 'test', + 'Y', + 'e', + 's', + 'with', + 'space', + ]) + }) +}) diff --git a/src/utilities/filterStringArray.ts b/src/utilities/filterStringArray.ts new file mode 100644 index 000000000..dcab338e2 --- /dev/null +++ b/src/utilities/filterStringArray.ts @@ -0,0 +1,14 @@ +export const filterStringArray = (string: string[]): string[] => { + // match unsupported characters + const regex = /[^a-zA-Z0-9]+/g + // replace any non-letter and non-number character and split into word array + const stringArray = string + .filter(Boolean) + .join(' ') + .replace(regex, ' ') + .split(' ') + // remove undefined if exists + .filter((part: unknown): part is string => Boolean(part) && typeof part === 'string') + + return stringArray +} diff --git a/src/utilities/toCamelCase.ts b/src/utilities/toCamelCase.ts index 5e64b2dfa..40556dc5e 100644 --- a/src/utilities/toCamelCase.ts +++ b/src/utilities/toCamelCase.ts @@ -1,22 +1,13 @@ +import {filterStringArray} from './filterStringArray' import {upperCaseFirstCharacter} from './upperCaseFirstCharacter' export const toCamelCase = (string: string | string[]) => { if (!Array.isArray(string)) { string = [string] } - // match unsupported characters - const regex = /[^a-zA-Z0-9]+/g - // replace any non-letter and non-number character and split into word array - const stringArray = string - .filter(part => part !== '@') - .filter(Boolean) - .join(' ') - .replace(regex, ' ') - .split(' ') + return ( - stringArray - // remove undefined if exists - .filter((part: unknown): part is string => typeof part === 'string') + filterStringArray(string) // ucFirst all but first part .map((part: string, index: number) => { if (index > 0) { diff --git a/src/utilities/toPascalCase.test.ts b/src/utilities/toPascalCase.test.ts new file mode 100644 index 000000000..28673166a --- /dev/null +++ b/src/utilities/toPascalCase.test.ts @@ -0,0 +1,27 @@ +import {toPascalCase} from './toPascalCase' + +describe('Utilities: toPascalCase', () => { + it('it transforms all lowercase word', () => { + expect(toPascalCase('primer')).toStrictEqual('Primer') + }) + + it('it transforms all lowercase sentence (words with spaces)', () => { + expect(toPascalCase('primer design token')).toStrictEqual('PrimerDesignToken') + }) + + it('it transforms all words with special chars', () => { + expect(toPascalCase('primer_design-token+edition')).toStrictEqual('PrimerDesignTokenEdition') + }) + + it('it preserves casing for words that are already all uppercased', () => { + expect(toPascalCase('PRIMER')).toStrictEqual('PRIMER') + }) + + it('it transforms all camelCase word', () => { + expect(toPascalCase('camelCase')).toStrictEqual('CamelCase') + }) + + it('it transforms array of words', () => { + expect(toPascalCase(['primer', 'design', 'token'])).toStrictEqual('PrimerDesignToken') + }) +}) diff --git a/src/utilities/toPascalCase.ts b/src/utilities/toPascalCase.ts new file mode 100644 index 000000000..76b31b61d --- /dev/null +++ b/src/utilities/toPascalCase.ts @@ -0,0 +1,17 @@ +import {filterStringArray} from './filterStringArray' +import {upperCaseFirstCharacter} from './upperCaseFirstCharacter' + +export const toPascalCase = (string: string | string[]) => { + if (!Array.isArray(string)) { + string = [string] + } + + return ( + filterStringArray(string) + // ucFirst all but first part + .map((part: string) => { + return upperCaseFirstCharacter(part) + }) + .join('') + ) +}