Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(references): ability to reference other tokens without 'value' #746

Merged
merged 2 commits into from
Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion __integration__/android.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {buildPath} = require('./_constants');
describe('integration', () => {
describe('android', () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows our token files to be .json or .jsonc. I made this change so I could add a comment in a token file about testing the use of leaving off .value.

platforms: {
android: {
transformGroup: `android`,
Expand Down
2 changes: 1 addition & 1 deletion __integration__/compose.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {buildPath} = require('./_constants');
describe('integration', () => {
describe('compose', () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
compose: {
transformGroup: `compose`,
Expand Down
2 changes: 1 addition & 1 deletion __integration__/css.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {buildPath} = require('./_constants');
describe('integration', () => {
describe('css', () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
// Testing proper string interpolation with multiple references here.
// This is a CSS/web-specific thing so only including them in this
// integration test.
Expand Down
2 changes: 1 addition & 1 deletion __integration__/flutter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {buildPath} = require('./_constants');
describe('integration', () => {
describe('flutter', () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
flutter: {
transformGroup: `flutter`,
Expand Down
2 changes: 1 addition & 1 deletion __integration__/iOSObjectiveC.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {buildPath} = require('./_constants');
describe('integration', () => {
describe('ios objective-c', () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
flutter: {
transformGroup: `ios`,
Expand Down
12 changes: 6 additions & 6 deletions __integration__/logging/file.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe(`integration`, () => {
describe(`file`, () => {
it(`should warn user empty properties`, () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
transformGroup: `css`,
Expand All @@ -58,7 +58,7 @@ describe(`integration`, () => {
it(`should not warn user of empty properties with log level set to error`, () => {
StyleDictionary.extend({
logLevel: `error`,
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
transformGroup: `css`,
Expand All @@ -75,7 +75,7 @@ describe(`integration`, () => {

it(`should warn user of name collisions`, () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
// no name transform means there will be name collisions
Expand All @@ -95,7 +95,7 @@ describe(`integration`, () => {
it(`should not warn user of name collisions with log level set to error`, () => {
StyleDictionary.extend({
logLevel: `error`,
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
// no name transform means there will be name collisions
Expand All @@ -114,7 +114,7 @@ describe(`integration`, () => {

it(`should warn user of filtered references`, () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
transformGroup: `css`,
Expand All @@ -138,7 +138,7 @@ describe(`integration`, () => {
it(`should not warn user of filtered references with log level set to error`, () => {
StyleDictionary.extend({
logLevel: `error`,
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
transformGroup: `css`,
Expand Down
2 changes: 1 addition & 1 deletion __integration__/outputReferences.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('integration', () => {
StyleDictionary.extend({
// we are only testing showFileHeader options so we don't need
// the full source.
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
transformGroup: 'css',
Expand Down
2 changes: 1 addition & 1 deletion __integration__/scss.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const {buildPath} = require('./_constants');
describe(`integration`, () => {
describe(`scss`, () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
transformGroup: `scss`,
Expand Down
2 changes: 1 addition & 1 deletion __integration__/swift.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {buildPath} = require('./_constants');
describe('integration', () => {
describe('swift', () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
flutter: {
transformGroup: `ios-swift`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
"primary": { "value": "{color.core.neutral.1100.value}" },
"secondary": { "value": "{color.core.neutral.900.value}" },
"tertiary": { "value": "{color.core.neutral.800.value}" },

"interactive": {
"_": { "value": "{color.brand.primary.value}" },
"hover": { "value": "{color.brand.primary.value}" },
"active": { "value": "{color.brand.secondary.value}" },
"disabled": { "value": "{color.font.tertiary.value}" }
},

"danger": { "value": "{color.core.red.1000.value}" },
"warning": { "value": "{color.core.orange.1000.value}" },
"success": { "value": "{color.core.green.1000.value}" }
// make sure references without .value work too
"success": { "value": "{color.core.green.1000}" }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

}
}
}
175 changes: 175 additions & 0 deletions __tests__/exportPlatform.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,179 @@ describe('exportPlatform', () => {
});
});

it('should handle .value and non .value references per the W3C spec', () => {
const tokens = {
colors: {
red: { value: '#f00' },
error: { value: '{colors.red}' },
danger: { value: '{colors.error}' },
alert: { value: '{colors.error.value}' },
}
}

const expected = {
colors: {
red: {
value: '#f00',
name: 'colors-red',
path: ['colors','red'],
attributes: {
category: 'colors',
type: 'red'
},
original: {
value: '#f00'
}
},
error: {
value: '#f00',
name: 'colors-error',
path: ['colors','error'],
attributes: {
category: 'colors',
type: 'error'
},
original: {
value: '{colors.red}'
}
},
danger: {
value: '#f00',
name: 'colors-danger',
path: ['colors','danger'],
attributes: {
category: 'colors',
type: 'danger'
},
original: {
value: '{colors.error}'
}
},
alert: {
value: '#f00',
name: 'colors-alert',
path: ['colors','alert'],
attributes: {
category: 'colors',
type: 'alert'
},
original: {
value: '{colors.error.value}'
}
},
}
}

const actual = StyleDictionary.extend({
tokens,
platforms: {
css: {
transformGroup: `css`
}
}
}).exportPlatform('css');
expect(actual).toEqual(expected);
});

describe('token references without .value', () => {
const tokens = {
color: {
red: { value: '#f00' },
error: { value: '{color.red}' },
errorWithValue: { value: '{color.red.value}' },
danger: { value: '{color.error}' },
dangerWithValue: { value: '{color.error.value}' },
dangerErrorValue: { value: '{color.errorWithValue}' }
}
}

const actual = StyleDictionary.extend({
tokens,
platforms: {
css: {
transformGroup: 'css'
}
}
}).exportPlatform('css');

it('should work if referenced directly', () => {
expect(actual.color.error.value).toEqual('#ff0000');
});
it('should work if there is a transitive reference', () => {
expect(actual.color.danger.value).toEqual('#ff0000');
});
it('should work if there is a transitive reference with .value', () => {
expect(actual.color.errorWithValue.value).toEqual('#ff0000');
expect(actual.color.dangerWithValue.value).toEqual('#ff0000');
expect(actual.color.dangerErrorValue.value).toEqual('#ff0000');
});
});

describe('non-token references', () => {
const tokens = {
nonTokenColor: 'hsl(10,20%,20%)',
hue: {
red: '10',
green: '120',
blue: '220'
},
comment: 'hello',
color: {
red: {
// Note having references as part of the value,
// either in an object like this, or in an interpolated
// string like below, requires the use of transitive
// transforms if you want it to be transformed.
value: {
h: '{hue.red}',
s: '100%',
l: '50%'
}
},
blue: {
value: '{nonTokenColor}',
comment: '{comment}'
},
green: {
value: 'hsl({hue.green}, 50%, 50%)'
}
}
}

// making the css/color transform transitive so we can be sure the references
// get resolved properly and transformed.
const transitiveTransform = Object.assign({},
StyleDictionary.transform['color/css'],
{transitive: true}
);

const actual = StyleDictionary.extend({
tokens,
transform: {
transitiveTransform
},
platforms: {
css: {
transforms: [
'attribute/cti',
'name/cti/kebab',
'transitiveTransform'
]
}
}
}).exportPlatform('css');

it('should work if referenced directly', () => {
expect(actual.color.blue.value).toEqual('#3d2c29');
});
it('should work if referenced from a non-value', () => {
expect(actual.color.blue.comment).toEqual(tokens.comment);
});
it('should work if interpolated', () => {
expect(actual.color.green.value).toEqual('#40bf40');
});
it('should work if part of an object value', () => {
expect(actual.color.red.value).toEqual('#ff2b00');
});
});
});
11 changes: 11 additions & 0 deletions lib/utils/resolveObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,19 @@ function compile_value(value, stack) {
// Find what the value is referencing
const pathName = getPath(variable, options);
const context = getName(current_context, options);
const refHasValue = pathName[pathName.length-1] === 'value';
ref = resolveReference(pathName, updated_object);

// If the reference doesn't end in 'value'
// and
// the reference points to someplace that has a `value` attribute
// we should take the '.value' of the reference
// per the W3C draft spec where references do not have .value
// https://design-tokens.github.io/community-group/format/#aliases-references
if (!refHasValue && ref && ref.hasOwnProperty('value')) {
ref = ref.value;
}

if (typeof ref !== 'undefined') {
if (typeof ref === 'string') {
to_ret = value.replace(match, ref);
Expand Down