From 44024b04063520ef724d3db461d6680f0ec087eb Mon Sep 17 00:00:00 2001 From: Kilian Panot Date: Thu, 31 Oct 2024 15:24:52 +0900 Subject: [PATCH] feat(design): add sorting way based on regexps --- .../helpers/style-renderer-options.ts | 18 ++++++- .../builders/generate-style/schema.json | 4 ++ .../design/builders/generate-style/schema.ts | 6 +++ .../design-token-style.renderer.spec.ts | 47 ++++++++++++++++++- .../renderers/design-token-style.renderer.ts | 25 ++++++++++ .../testing/mocks/design-token-theme.json | 6 +++ 6 files changed, 103 insertions(+), 3 deletions(-) diff --git a/packages/@o3r/design/builders/generate-style/helpers/style-renderer-options.ts b/packages/@o3r/design/builders/generate-style/helpers/style-renderer-options.ts index 367a10ba7e..f94f226e5f 100644 --- a/packages/@o3r/design/builders/generate-style/helpers/style-renderer-options.ts +++ b/packages/@o3r/design/builders/generate-style/helpers/style-renderer-options.ts @@ -2,6 +2,7 @@ import type { BuilderContext } from '@angular-devkit/architect'; import { type CssTokenDefinitionRendererOptions, type CssTokenValueRendererOptions, + type DesignTokenListTransform, type DesignTokenRendererOptions, type DesignTokenVariableStructure, getCssStyleContentUpdater, @@ -12,6 +13,7 @@ import { getSassTokenValueRenderer, getTokenSorterByName, getTokenSorterByRef, + getTokenSorterFromRegExpList, type SassTokenDefinitionRendererOptions, type SassTokenValueRendererOptions, type TokenKeyRenderer, @@ -19,6 +21,7 @@ import { } from '../../../src/public_api'; import type { GenerateStyleSchematicsSchema } from '../schema'; import { resolve } from 'node:path'; +import { readFileSync } from 'node:fs'; export const getStyleRendererOptions = (tokenVariableNameRenderer: TokenKeyRenderer | undefined , options: GenerateStyleSchematicsSchema, context: BuilderContext): DesignTokenRendererOptions => { @@ -113,14 +116,25 @@ export const getStyleRendererOptions = (tokenVariableNameRenderer: TokenKeyRende /** Sorting strategy of variables based on selected language */ const tokenListTransforms = ((language) => { + const customSorter: DesignTokenListTransform[] = []; + if (options.sortOrderPatternsFilePath) { + try { + const regExps = (JSON.parse(readFileSync(resolve(context.workspaceRoot, options.sortOrderPatternsFilePath), {encoding: 'utf8'})) as string[]) + .map((item) => new RegExp(item.replace(/^\/(.*)\/$/, '$1'))); + customSorter.push(getTokenSorterFromRegExpList(regExps)); + } catch { + context.logger.warn(`The specified RegExp file ${options.sortOrderPatternsFilePath} is not reachable or not correctly formatted.`); + context.logger.warn(`The order list will be ignored`); + } + } switch (language) { case 'scss': case 'sass': { - return [getTokenSorterByName, getTokenSorterByRef]; + return [getTokenSorterByName, ...customSorter, getTokenSorterByRef]; } case 'css': default: { - return [getTokenSorterByName]; + return [getTokenSorterByName, ...customSorter]; } } })(options.variableType || options.language); diff --git a/packages/@o3r/design/builders/generate-style/schema.json b/packages/@o3r/design/builders/generate-style/schema.json index c7f5abfb3d..69c6dad114 100644 --- a/packages/@o3r/design/builders/generate-style/schema.json +++ b/packages/@o3r/design/builders/generate-style/schema.json @@ -102,6 +102,10 @@ "type": "boolean", "default": false, "description": "Determine if the builder should fail if a missing Design Token reference is detected" + }, + "sortOrderPatternsFilePath": { + "type": "string", + "description": "Path to the JSON file exposing an ordered array of RegExps applied to the token name which will define the priority of the generated variables. (Note: not matching tokens will default to ASC order)" } }, "additionalProperties": true, diff --git a/packages/@o3r/design/builders/generate-style/schema.ts b/packages/@o3r/design/builders/generate-style/schema.ts index bbb0de41dd..add3e5ef58 100644 --- a/packages/@o3r/design/builders/generate-style/schema.ts +++ b/packages/@o3r/design/builders/generate-style/schema.ts @@ -67,4 +67,10 @@ export interface GenerateStyleSchematicsSchema extends SchematicOptionObject { /** Path to a template file to apply as default configuration to a Design Token extension */ templateFile?: string | string[]; + + /** + * Path to the JSON file exposing an ordered array of RegExps applied to the token name which will define the priority of the generated variables. + * Note: not matching tokens will default to ASC order. + */ + sortOrderPatternsFilePath?: string; } diff --git a/packages/@o3r/design/src/core/design-token/renderers/design-token-style.renderer.spec.ts b/packages/@o3r/design/src/core/design-token/renderers/design-token-style.renderer.spec.ts index 114dbd2894..fa2f50671b 100644 --- a/packages/@o3r/design/src/core/design-token/renderers/design-token-style.renderer.spec.ts +++ b/packages/@o3r/design/src/core/design-token/renderers/design-token-style.renderer.spec.ts @@ -3,7 +3,13 @@ import { promises as fs } from 'node:fs'; import { resolve } from 'node:path'; import type { DesignTokenGroup, DesignTokenSpecification } from '../design-token-specification.interface'; import type { DesignTokenVariableSet } from '../parsers'; -import { computeFileToUpdatePath, getTokenSorterByName, getTokenSorterByRef, renderDesignTokens } from './design-token-style.renderer'; +import { + computeFileToUpdatePath, + getTokenSorterByName, + getTokenSorterByRef, + getTokenSorterFromRegExpList, + renderDesignTokens +} from './design-token-style.renderer'; describe('Design Token Renderer', () => { let exampleVariable!: DesignTokenSpecification; @@ -157,6 +163,45 @@ describe('Design Token Renderer', () => { }); }); + describe('getTokenSorterFromRegExpList', () => { + it('should sort properly based on regExps', () => { + const regExps = [ + /override$/, + /shadow/ + ]; + const list = Array.from(designTokens.values()); + const sortedTokens = getTokenSorterFromRegExpList(regExps)(designTokens)(list); + + expect(list.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'example.var-expect-override')) + .toBeGreaterThan(list.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'example.var1')); + expect(sortedTokens.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'example.var-expect-override')) + .toBeLessThan(sortedTokens.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'example.var1')); + + expect(list.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'example.test.shadow')) + .toBeGreaterThan(list.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'example.var1')); + expect(sortedTokens.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'example.test.shadow')) + .toBeLessThan(sortedTokens.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'example.var1')); + + expect(list.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'example.test.shadow')) + .toBeGreaterThan(list.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'example.example.var-expect-override')); + expect(sortedTokens.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'example.test.shadow')) + .toBeGreaterThan(sortedTokens.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'example.example.var-expect-override')); + }); + + it('should not sort unmatched tokens', () => { + const regExps = [ + /override$/, + /shadow/ + ]; + const list = Array.from(designTokens.values()); + const sortedTokens = getTokenSorterFromRegExpList(regExps)(designTokens)(list); + + expect(sortedTokens.length).toBe(list.length); + expect(sortedTokens.findIndex(({ tokenReferenceName }) => tokenReferenceName === 'last-group.last-token')) + .toBe(sortedTokens.length - 1); + }); + }); + describe('getTokenSorterByName', () => { let designTokensToSort!: DesignTokenVariableSet; beforeEach(() => { diff --git a/packages/@o3r/design/src/core/design-token/renderers/design-token-style.renderer.ts b/packages/@o3r/design/src/core/design-token/renderers/design-token-style.renderer.ts index c17be52ff9..45f3d53745 100644 --- a/packages/@o3r/design/src/core/design-token/renderers/design-token-style.renderer.ts +++ b/packages/@o3r/design/src/core/design-token/renderers/design-token-style.renderer.ts @@ -98,6 +98,31 @@ export const getTokenSorterByRef: DesignTokenListTransform = (variableSet) => { }; }; +/** + * Reorganize the Tokens based on a ordered list of RegExps + * @param regExps Ordered list of regular expressions defining the order of the Tokens. + */ +export const getTokenSorterFromRegExpList: (regExps: RegExp[]) => DesignTokenListTransform = (regExps) => (_variableSet, options) => { + + return (tokens) => + tokens + .map((token) => ({ index: regExps.findIndex((r) => r.test(token.getKey(options?.tokenVariableNameRenderer))), token })) + .sort((a, b) => { + if (a.index === -1) { + if (b.index === -1) { + return 0; + } + return 1; + } else { + if (b.index === -1) { + return -1; + } + return b.index - a.index; + } + }) + .map(({token}) => token); +}; + /** * Retrieve default file writer (based on Node `fs.promise.writeFile` interface) * @param existsFile Function determining if the file exists diff --git a/packages/@o3r/design/testing/mocks/design-token-theme.json b/packages/@o3r/design/testing/mocks/design-token-theme.json index 6d658661cc..d3019daa15 100644 --- a/packages/@o3r/design/testing/mocks/design-token-theme.json +++ b/packages/@o3r/design/testing/mocks/design-token-theme.json @@ -185,5 +185,11 @@ } } } + }, + "last-group": { + "last-token": { + "$type": "color", + "$value": "#aaa" + } } }