11import type { FileInfo , API , Options } from 'jscodeshift' ;
22import postcss , { Plugin , Declaration , Helpers } from 'postcss' ;
3- import valueParser , { ParsedValue , Node } from 'postcss-value-parser' ;
3+ import valueParser , {
4+ ParsedValue ,
5+ Node ,
6+ FunctionNode ,
7+ } from 'postcss-value-parser' ;
48
59import { POLARIS_MIGRATOR_COMMENT } from '../../constants' ;
610import {
@@ -15,6 +19,7 @@ import {isKeyOf} from '../../utilities/type-guards';
1519
1620const processed = Symbol ( 'processed' ) ;
1721const DEFAULT_DURATION = 'base' ;
22+ const DEFAULT_FUNCTION = 'base' ;
1823
1924const durationFuncMap = {
2025 none : '--p-duration-0' ,
@@ -52,6 +57,36 @@ const durationConstantsMap = {
5257 '5s' : '--p-duration-5000' ,
5358} ;
5459
60+ const easingFuncMap = {
61+ base : '--p-ease' ,
62+ in : '--p-ease-in' ,
63+ out : '--p-ease-out' ,
64+ } ;
65+
66+ const easingFuncConstantsMap = {
67+ linear : '--p-linear' ,
68+ ease : '--p-ease' ,
69+ 'ease-in' : '--p-ease-in' ,
70+ 'ease-out' : '--p-ease-out' ,
71+ 'ease-in-out' : '--p-ease-in-out' ,
72+ } ;
73+
74+ const deprecatedEasingFuncs = [ 'anticipate' , 'excite' , 'overshoot' ] ;
75+
76+ // Per the spec for transition easing functions:
77+ // https://w3c.github.io/csswg-drafts/css-easing/#easing-functions
78+ const cssEasingBuiltinFuncs = [
79+ 'linear' ,
80+ 'ease' ,
81+ 'ease-in' ,
82+ 'ease-out' ,
83+ 'ease-in-out' ,
84+ 'cubic-bezier' ,
85+ 'step-start' ,
86+ 'step-end' ,
87+ 'steps' ,
88+ ] ;
89+
5590function normaliseStringifiedNumber ( number : string ) : string {
5691 return Number ( number ) . toString ( ) ;
5792}
@@ -99,11 +134,52 @@ function withParsedValue(
99134 } ) as DeclarationProcessor ;
100135}
101136
137+ function insertUnexpectedEasingFunctionComment (
138+ node : Node ,
139+ decl : ParsedValueDeclaration ,
140+ ) {
141+ decl . before (
142+ postcss . comment ( {
143+ text : `${ POLARIS_MIGRATOR_COMMENT } Unexpected easing function '${ node . value } '. See https://polaris.shopify.com/tokens/motion for possible values.` ,
144+ } ) ,
145+ ) ;
146+ }
147+
148+ function migrateLegacySassEasingFunction (
149+ node : FunctionNode ,
150+ decl : ParsedValueDeclaration ,
151+ ) {
152+ const easingFunc = node . nodes [ 0 ] ?. value ?? DEFAULT_FUNCTION ;
153+
154+ if ( isKeyOf ( easingFuncMap , easingFunc ) ) {
155+ const easingCustomProperty = easingFuncMap [ easingFunc ] ;
156+ setNodeValue ( node , `var(${ easingCustomProperty } )` ) ;
157+ } else {
158+ const comment = deprecatedEasingFuncs . includes ( easingFunc )
159+ ? `The ${ easingFunc } easing function is no longer available in Polaris. See https://polaris.shopify.com/tokens/motion for possible values.`
160+ : `Unexpected easing function '${ easingFunc } '.` ;
161+ decl . before (
162+ postcss . comment ( {
163+ text : `${ POLARIS_MIGRATOR_COMMENT } ${ comment } ` ,
164+ } ) ,
165+ ) ;
166+ }
167+ }
168+
102169interface PluginOptions extends Options , NamespaceOptions { }
103170
104171const plugin = ( options : PluginOptions = { } ) : Plugin => {
105172 const durationFunc = namespace ( 'duration' , options ) ;
106173
174+ const easingFuncHandlers = {
175+ [ namespace ( 'easing' , options ) ] : migrateLegacySassEasingFunction ,
176+ // Per the spec, these can all be functions:
177+ // https://w3c.github.io/csswg-drafts/css-easing/#easing-functions
178+ linear : insertUnexpectedEasingFunctionComment ,
179+ 'cubic-bezier' : insertUnexpectedEasingFunctionComment ,
180+ steps : insertUnexpectedEasingFunctionComment ,
181+ } ;
182+
107183 function mutateTransitionDurationValue (
108184 node : Node ,
109185 decl : ParsedValueDeclaration ,
@@ -153,6 +229,32 @@ const plugin = (options: PluginOptions = {}): Plugin => {
153229 return false ;
154230 }
155231
232+ function mutateTransitionFunctionValue (
233+ node : Node ,
234+ decl : ParsedValueDeclaration ,
235+ ) : boolean {
236+ if ( isPolarisVar ( node ) ) {
237+ return true ;
238+ }
239+
240+ if ( node . type === 'function' && isKeyOf ( easingFuncHandlers , node . value ) ) {
241+ easingFuncHandlers [ node . value ] ( node , decl ) ;
242+ return true ;
243+ }
244+
245+ if ( node . type === 'word' ) {
246+ if ( isKeyOf ( easingFuncConstantsMap , node . value ) ) {
247+ setNodeValue ( node , `var(${ easingFuncConstantsMap [ node . value ] } )` ) ;
248+ return true ;
249+ } else if ( cssEasingBuiltinFuncs . includes ( node . value ) ) {
250+ insertUnexpectedEasingFunctionComment ( node , decl ) ;
251+ return true ;
252+ }
253+ }
254+
255+ return false ;
256+ }
257+
156258 function mutateTransitionDelayValue (
157259 node : Node ,
158260 decl : ParsedValueDeclaration ,
@@ -200,7 +302,10 @@ const plugin = (options: PluginOptions = {}): Plugin => {
200302 if ( isTransformableDuration ( unit ) || isSassFunction ( durationFunc , node ) ) {
201303 timings . push ( node ) ;
202304 } else {
203- // TODO: Try process it as an easing function
305+ // This node could be either the property to animate, or an easing
306+ // function. We try mutate the easing function, but if not we assume
307+ // it's the property to animate and therefore do not leave a comment.
308+ mutateTransitionFunctionValue ( node , decl ) ;
204309 }
205310 } ) ;
206311
@@ -270,6 +375,18 @@ const plugin = (options: PluginOptions = {}): Plugin => {
270375 } ) ;
271376 } ) ,
272377
378+ 'transition-timing-function' : withParsedValue ( ( decl ) => {
379+ decl . parsedValue . nodes . forEach ( ( node ) => {
380+ if ( ! mutateTransitionFunctionValue ( node , decl ) ) {
381+ decl . before (
382+ postcss . comment ( {
383+ text : POLARIS_MIGRATOR_COMMENT ,
384+ } ) ,
385+ ) ;
386+ }
387+ } ) ;
388+ } ) ,
389+
273390 transition : withParsedValue ( ( decl ) => {
274391 if ( hasNumericOperator ( decl . parsedValue ) ) {
275392 decl . before (
0 commit comments