diff --git a/packages/react-components/token-analyzer-preview/library/README.md b/packages/react-components/token-analyzer-preview/library/README.md index 7d9c04c5ef35fb..90468b8b6b274f 100644 --- a/packages/react-components/token-analyzer-preview/library/README.md +++ b/packages/react-components/token-analyzer-preview/library/README.md @@ -8,7 +8,7 @@ A static analysis tool that scans your project's style files to track and analyz - `createCustomFocusIndicatorStyle` is a special function that is used throughout the library so we might be able to special case it - if we have file imports we need to analyze those such as importing base styles - we also need to ensure var analysis is done correctly after the refactor -- Manage makeResetStyles (likely same as makeStyles) + ~~- Manage makeResetStyles (likely same as makeStyles)~~ - Button has some weird patterns in it where it uses makeResetStyles and then uses enums to pull in the styles, we might need to account for those as well. - what if we have multiple `makeStyles` calls merged, are we handling that correctly or just nuking the conflicts in our output? - make sure this works with shorthand spread diff --git a/packages/react-components/token-analyzer-preview/library/analysis.json b/packages/react-components/token-analyzer-preview/library/analysis.json new file mode 100644 index 00000000000000..eb97b2a4669ac7 --- /dev/null +++ b/packages/react-components/token-analyzer-preview/library/analysis.json @@ -0,0 +1,974 @@ +{ + "library/src/components/Button/useButtonStyles.styles.ts": { + "styles": { + "useRootBaseClassName": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorNeutralBackground1", + "path": ["backgroundColor"] + }, + { + "property": "color", + "token": "tokens.colorNeutralForeground1", + "path": ["color"] + }, + { + "property": "fontFamily", + "token": "tokens.fontFamilyBase", + "path": ["fontFamily"] + }, + { + "property": "borderRadius", + "token": "tokens.borderRadiusMedium", + "path": ["borderRadius"] + }, + { + "property": "fontSize", + "token": "tokens.fontSizeBase300", + "path": ["fontSize"] + }, + { + "property": "fontWeight", + "token": "tokens.fontWeightSemibold", + "path": ["fontWeight"] + }, + { + "property": "lineHeight", + "token": "tokens.lineHeightBase300", + "path": ["lineHeight"] + }, + { + "property": "transitionDuration", + "token": "tokens.durationFaster", + "path": ["transitionDuration"] + }, + { + "property": "transitionTimingFunction", + "token": "tokens.curveEasyEase", + "path": ["transitionTimingFunction"] + } + ], + "nested": { + "':hover'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorNeutralBackground1Hover", + "path": [] + }, + { + "property": "borderColor", + "token": "tokens.colorNeutralStroke1Hover", + "path": [] + }, + { + "property": "color", + "token": "tokens.colorNeutralForeground1Hover", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorNeutralBackground1Pressed", + "path": [] + }, + { + "property": "borderColor", + "token": "tokens.colorNeutralStroke1Pressed", + "path": [] + }, + { + "property": "color", + "token": "tokens.colorNeutralForeground1Pressed", + "path": [] + } + ] + } + }, + "assignedVariables": ["rootBaseClassName"], + "isClassFunction": true + }, + "useIconBaseClassName": { + "tokens": [ + { + "property": "[iconSpacingVar]", + "token": "tokens.spacingHorizontalSNudge", + "path": ["[iconSpacingVar]"] + } + ], + "nested": {}, + "assignedVariables": ["iconBaseClassName"], + "isClassFunction": true + }, + "outline": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackground", + "path": ["backgroundColor"] + } + ], + "nested": { + "':hover'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackground", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackground", + "path": [] + } + ] + } + } + }, + "primary": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorBrandBackground", + "path": ["backgroundColor"] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundOnBrand", + "path": ["color"] + } + ], + "nested": { + "':hover'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorBrandBackgroundHover", + "path": [] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundOnBrand", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorBrandBackgroundPressed", + "path": [] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundOnBrand", + "path": [] + } + ] + } + } + }, + "subtle": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackground", + "path": ["backgroundColor"] + } + ], + "nested": { + "':hover'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackground", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackground", + "path": [] + } + ] + } + } + }, + "transparent": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackground", + "path": ["backgroundColor"] + } + ], + "nested": { + "':hover'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackground", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackground", + "path": [] + } + ] + } + } + }, + "circular": { + "tokens": [ + { + "property": "borderRadius", + "token": "tokens.borderRadiusCircular", + "path": ["borderRadius"] + } + ] + }, + "square": { + "tokens": [ + { + "property": "borderRadius", + "token": "tokens.borderRadiusNone", + "path": ["borderRadius"] + } + ] + }, + "small": { + "tokens": [ + { + "property": "[iconSpacingVar]", + "token": "tokens.spacingHorizontalXS", + "path": ["[iconSpacingVar]"] + } + ] + }, + "large": { + "tokens": [ + { + "property": "[iconSpacingVar]", + "token": "tokens.spacingHorizontalSNudge", + "path": ["[iconSpacingVar]"] + } + ] + }, + "base": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorNeutralBackgroundDisabled", + "path": ["backgroundColor"] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundDisabled", + "path": ["color"] + } + ], + "nested": { + "[`& .${buttonClassNames.icon}`]": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForegroundDisabled", + "path": [] + } + ] + }, + "':hover'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorNeutralBackgroundDisabled", + "path": [] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundDisabled", + "path": [] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundDisabled", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorNeutralBackgroundDisabled", + "path": [] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundDisabled", + "path": [] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundDisabled", + "path": [] + } + ] + } + } + } + }, + "metadata": { + "styleConditions": { + "rootBaseClassName": { + "isBase": true + }, + "rootStyles[size]": { + "isBase": true + }, + "rootStyles[shape]": { + "isBase": true + }, + "rootFocusStyles[size]": { + "isBase": true + }, + "rootFocusStyles[shape]": { + "isBase": true + }, + "iconBaseClassName": { + "isBase": true + }, + "iconStyles[size]": { + "isBase": true + } + } + } + }, + "library/src/components/CompoundButton/useCompoundButtonStyles.styles.ts": { + "styles": { + "base": { + "tokens": [ + { + "property": "fontWeight", + "token": "tokens.fontWeightRegular", + "path": ["fontWeight"] + } + ] + }, + "primary": { + "tokens": [], + "nested": { + "[`& .${compoundButtonClassNames.secondaryContent}`]": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForegroundOnBrand", + "path": [] + } + ] + }, + "':hover'": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForegroundOnBrand", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForegroundOnBrand", + "path": [] + } + ] + } + } + }, + "subtle": { + "tokens": [], + "nested": { + "[`& .${compoundButtonClassNames.secondaryContent}`]": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForeground2", + "path": [] + } + ] + }, + "':hover'": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForeground2Hover", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForeground2Pressed", + "path": [] + } + ] + } + } + }, + "transparent": { + "tokens": [], + "nested": { + "[`& .${compoundButtonClassNames.secondaryContent}`]": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForeground2", + "path": [] + } + ] + }, + "':hover'": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForeground2BrandHover", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForeground2BrandPressed", + "path": [] + } + ] + } + } + }, + "small": { + "tokens": [ + { + "property": "fontSize", + "token": "tokens.fontSizeBase200", + "path": ["fontSize"] + } + ] + }, + "medium": { + "tokens": [ + { + "property": "fontSize", + "token": "tokens.fontSizeBase200", + "path": ["fontSize"] + } + ] + }, + "large": { + "tokens": [ + { + "property": "fontSize", + "token": "tokens.fontSizeBase300", + "path": ["fontSize"] + } + ] + }, + "disabled": { + "tokens": [], + "nested": { + "[`& .${compoundButtonClassNames.secondaryContent}`]": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForegroundDisabled", + "path": [] + } + ] + }, + "':hover'": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForegroundDisabled", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForegroundDisabled", + "path": [] + } + ] + } + } + }, + "before": { + "tokens": [ + { + "property": "marginRight", + "token": "tokens.spacingHorizontalM", + "path": ["marginRight"] + } + ] + }, + "after": { + "tokens": [ + { + "property": "marginLeft", + "token": "tokens.spacingHorizontalM", + "path": ["marginLeft"] + } + ] + } + }, + "metadata": { + "styleConditions": { + "rootStyles[size]": { + "isBase": true + }, + "secondaryContentStyles[size]": { + "isBase": true + } + } + } + }, + "library/src/components/MenuButton/useMenuButtonStyles.styles.ts": { + "styles": { + "outline": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForeground1Selected", + "path": ["color"] + } + ] + }, + "primary": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorBrandBackgroundSelected", + "path": ["backgroundColor"] + } + ] + }, + "secondary": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForeground1Selected", + "path": ["color"] + } + ] + }, + "subtle": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForeground2BrandSelected", + "path": ["color"] + } + ] + }, + "transparent": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForeground2BrandSelected", + "path": ["color"] + } + ] + }, + "small": { + "tokens": [ + { + "property": "lineHeight", + "token": "tokens.lineHeightBase200", + "path": ["lineHeight"] + } + ] + }, + "medium": { + "tokens": [ + { + "property": "lineHeight", + "token": "tokens.lineHeightBase200", + "path": ["lineHeight"] + } + ] + }, + "large": { + "tokens": [ + { + "property": "lineHeight", + "token": "tokens.lineHeightBase400", + "path": ["lineHeight"] + } + ] + }, + "notIconOnly": { + "tokens": [ + { + "property": "marginLeft", + "token": "tokens.spacingHorizontalXS", + "path": ["marginLeft"] + } + ] + } + }, + "metadata": { + "styleConditions": {} + } + }, + "library/src/components/SplitButton/useSplitButtonStyles.styles.ts": { + "styles": { + "primary": { + "tokens": [], + "nested": { + "[`& .${splitButtonClassNames.primaryActionButton}`]": { + "tokens": [ + { + "property": "borderRightColor", + "token": "tokens.colorNeutralStrokeOnBrand", + "path": [] + } + ] + }, + "':hover'": { + "tokens": [ + { + "property": "borderRightColor", + "token": "tokens.colorNeutralStrokeOnBrand", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "borderRightColor", + "token": "tokens.colorNeutralStrokeOnBrand", + "path": [] + } + ] + } + } + }, + "subtle": { + "tokens": [], + "nested": { + "[`& .${splitButtonClassNames.primaryActionButton}`]": { + "tokens": [ + { + "property": "borderRightColor", + "token": "tokens.colorNeutralStroke1", + "path": [] + } + ] + }, + "':hover'": { + "tokens": [ + { + "property": "borderRightColor", + "token": "tokens.colorNeutralStroke1Hover", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "borderRightColor", + "token": "tokens.colorNeutralStroke1Pressed", + "path": [] + } + ] + } + } + }, + "transparent": { + "tokens": [], + "nested": { + "[`& .${splitButtonClassNames.primaryActionButton}`]": { + "tokens": [ + { + "property": "borderRightColor", + "token": "tokens.colorNeutralStroke1", + "path": [] + } + ] + }, + "':hover'": { + "tokens": [ + { + "property": "borderRightColor", + "token": "tokens.colorNeutralStroke1Hover", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "borderRightColor", + "token": "tokens.colorNeutralStroke1Pressed", + "path": [] + } + ] + } + } + }, + "disabled": { + "tokens": [], + "nested": { + "[`& .${splitButtonClassNames.primaryActionButton}`]": { + "tokens": [ + { + "property": "borderRightColor", + "token": "tokens.colorNeutralStrokeDisabled", + "path": [] + } + ] + }, + "':hover'": { + "tokens": [ + { + "property": "borderRightColor", + "token": "tokens.colorNeutralStrokeDisabled", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "borderRightColor", + "token": "tokens.colorNeutralStrokeDisabled", + "path": [] + } + ] + } + } + } + }, + "metadata": { + "styleConditions": {} + } + }, + "library/src/components/ToggleButton/useToggleButtonStyles.styles.ts": { + "styles": { + "base": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorNeutralBackgroundDisabled", + "path": ["backgroundColor"] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundDisabled", + "path": ["color"] + } + ], + "nested": { + "':hover'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorNeutralBackgroundDisabled", + "path": [] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundDisabled", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorNeutralBackgroundDisabled", + "path": [] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundDisabled", + "path": [] + } + ] + } + } + }, + "outline": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackgroundSelected", + "path": ["backgroundColor"] + } + ], + "nested": { + "':hover'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackgroundHover", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackgroundPressed", + "path": [] + } + ] + } + } + }, + "primary": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorBrandBackgroundSelected", + "path": ["backgroundColor"] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundOnBrand", + "path": ["color"] + } + ], + "nested": { + "':hover'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorBrandBackgroundHover", + "path": [] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundOnBrand", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorBrandBackgroundPressed", + "path": [] + }, + { + "property": "color", + "token": "tokens.colorNeutralForegroundOnBrand", + "path": [] + } + ] + } + } + }, + "subtle": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackground", + "path": ["backgroundColor"] + } + ], + "nested": { + "':hover'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackgroundHover", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackgroundPressed", + "path": [] + } + ] + } + } + }, + "transparent": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackground", + "path": ["backgroundColor"] + } + ], + "nested": { + "':hover'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackgroundHover", + "path": [] + } + ] + }, + "':hover:active'": { + "tokens": [ + { + "property": "backgroundColor", + "token": "tokens.colorTransparentBackgroundPressed", + "path": [] + } + ] + } + } + }, + "subtleOrTransparent": { + "tokens": [ + { + "property": "color", + "token": "tokens.colorNeutralForeground2BrandSelected", + "path": ["color"] + } + ] + } + }, + "metadata": { + "styleConditions": {} + } + } +} diff --git a/packages/react-components/token-analyzer-preview/library/src/astAnalyzer.ts b/packages/react-components/token-analyzer-preview/library/src/astAnalyzer.ts index 0cd76c8da21e7a..c0eb71eeaa28dc 100644 --- a/packages/react-components/token-analyzer-preview/library/src/astAnalyzer.ts +++ b/packages/react-components/token-analyzer-preview/library/src/astAnalyzer.ts @@ -16,12 +16,17 @@ interface StyleMapping { conditionalStyles: StyleCondition[]; } +interface VariableMapping { + variableName: string; + functionName: string; +} + /** * Process a style property to extract token references. * Property names are derived from the actual CSS property in the path, * not the object key containing them. */ -function processStyleProperty(prop: PropertyAssignment): TokenReference[] { +function processStyleProperty(prop: PropertyAssignment, isResetStyles?: Boolean): TokenReference[] { const tokens: TokenReference[] = []; const parentName = prop.getName(); @@ -30,6 +35,11 @@ function processStyleProperty(prop: PropertyAssignment): TokenReference[] { return; } + // If we're processing a reset style, we need to add the parent name to the path + if (isResetStyles && path.length === 0) { + path.push(parentName); + } + if (Node.isStringLiteral(node) || Node.isIdentifier(node)) { const text = node.getText(); const matches = text.match(TOKEN_REGEX); @@ -68,7 +78,6 @@ function processStyleProperty(prop: PropertyAssignment): TokenReference[] { return tokens; } - /** * Analyzes mergeClasses calls to determine style relationships */ @@ -102,6 +111,9 @@ function analyzeMergeClasses(sourceFile: SourceFile): StyleMapping[] { condition: arg.getLeft().getText(), }); } + } else if (!arg.getText().includes('.')) { + // We found a single variable (makeResetStyles or other assignment), add to base styles for lookup later + mapping.baseStyles.push(arg.getText()); } }); @@ -201,7 +213,6 @@ async function analyzeMakeStyles(sourceFile: SourceFile): Promise sourceFile.forEachDescendant(node => { if (Node.isCallExpression(node) && node.getExpression().getText() === 'makeStyles') { const stylesArg = node.getArguments()[0]; - if (Node.isObjectLiteralExpression(stylesArg)) { // Process the styles object stylesArg.getProperties().forEach(prop => { @@ -214,9 +225,62 @@ async function analyzeMakeStyles(sourceFile: SourceFile): Promise } }); } + } else if (Node.isCallExpression(node) && node.getExpression().getText() === 'makeResetStyles') { + // Similar to above, but the styles are stored under the assigned function name instead of local variable + const stylesArg = node.getArguments()[0]; + const parentNode = node.getParent(); + if (Node.isVariableDeclaration(parentNode)) { + const styleName = parentNode.getName(); + // We store 'isClassFunction' to differentiate from makeStyles and link mergeClasses variables + analysis[styleName] = { tokens: [], nested: {}, assignedVariables: [], isClassFunction: true }; + if (Node.isObjectLiteralExpression(stylesArg)) { + // Process the styles object + stylesArg.getProperties().forEach(prop => { + if (Node.isPropertyAssignment(prop)) { + const tokens = processStyleProperty(prop, true); + if (tokens.length) { + const styleContent = createStyleContent(tokens); + analysis[styleName].tokens = analysis[styleName].tokens.concat(styleContent.tokens); + analysis[styleName].nested = { + ...analysis[styleName].nested, + ...styleContent.nested, + }; + } + } + }); + } + } + } + }); + + const variables: VariableMapping[] = []; + const resetStyleFunctionNames: string[] = Object.keys(analysis).filter( + (styleName: string) => analysis[styleName].isClassFunction, + ); + + sourceFile.forEachDescendant(node => { + // We do a second parse to link known style functions (i.e. makeResetStyles assigned function variable names). + // This is necessary to handle cases where we're using a variable directly in mergeClasses to link styles. + + if (Node.isCallExpression(node) && resetStyleFunctionNames.includes(node.getExpression().getText())) { + const parentNode = node.getParent(); + const functionName = node.getExpression().getText(); + if (Node.isVariableDeclaration(parentNode)) { + const variableName = parentNode.getName(); + const variableMap: VariableMapping = { + functionName, + variableName, + }; + variables.push(variableMap); + } } }); + // Store our makeResetStyles assigned variables in the analysis to link later + variables.forEach(variable => { + analysis[variable.functionName].assignedVariables?.push(variable.variableName); + }); + return analysis; } diff --git a/packages/react-components/token-analyzer-preview/library/src/types.ts b/packages/react-components/token-analyzer-preview/library/src/types.ts index 90c4eb6ae8ab96..3dc8213d02aeed 100644 --- a/packages/react-components/token-analyzer-preview/library/src/types.ts +++ b/packages/react-components/token-analyzer-preview/library/src/types.ts @@ -10,6 +10,8 @@ export interface TokenReference { export interface StyleContent { tokens: TokenReference[]; nested?: StyleAnalysis; + isClassFunction?: boolean; + assignedVariables?: string[]; } export interface StyleAnalysis {