Skip to content

Commit

Permalink
feat(design): add sorting way based on regexps
Browse files Browse the repository at this point in the history
  • Loading branch information
kpanot committed Oct 31, 2024
1 parent 8b7d251 commit 44024b0
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { BuilderContext } from '@angular-devkit/architect';
import {
type CssTokenDefinitionRendererOptions,
type CssTokenValueRendererOptions,
type DesignTokenListTransform,
type DesignTokenRendererOptions,
type DesignTokenVariableStructure,
getCssStyleContentUpdater,
Expand All @@ -12,13 +13,15 @@ import {
getSassTokenValueRenderer,
getTokenSorterByName,
getTokenSorterByRef,
getTokenSorterFromRegExpList,
type SassTokenDefinitionRendererOptions,
type SassTokenValueRendererOptions,
type TokenKeyRenderer,
tokenVariableNameSassRenderer
} 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 => {

Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions packages/@o3r/design/builders/generate-style/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions packages/@o3r/design/builders/generate-style/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions packages/@o3r/design/testing/mocks/design-token-theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,5 +185,11 @@
}
}
}
},
"last-group": {
"last-token": {
"$type": "color",
"$value": "#aaa"
}
}
}

0 comments on commit 44024b0

Please sign in to comment.