import { AtRule , Declaration , Root } from "postcss" ; import postcssValueParser from "postcss-value-parser" ; import { deprecatedTokenReplacements } from "../../deprecatedTokens" ; import { cantReplaceDeprecatedTokenInAtRuleMessage , cssVariableUsedWithinUnsupportedFunction , deprecatedTokenInVariableMessage , deprecatedTokenUsageMessage , deprecatedTokenUsageWithoutReplacementMessage , containsDeprecatedKaizenTokenWithNoReplacement , invalidEquationContainingKaizenTokenMessage , replacementCssVariableUsedWithinUnsupportedFunction , } from "../messages" ; import { Options , RuleDefinition } from "../types" ; import { cssStandardFunctions } from "../util/cssStandardFunctions" ; import { kaizenTokensByName } from "../util/kaizenTokens" ; import { isVariable , replaceTokenInVariable , stringifyVariable , } from "../util/variableUtils" ; import { walkVariablesOnValue } from "../util/walkers" ; import { fixAlphaModificationFunctions } from "./lib/fixAlphaModificationFunctions" ; import { containsEquationThatDoesntWorkWithCSSVariables } from "./no-invalid-use-of-var-tokens-in-equations" ; const deprecatedKaizenTokenPattern = new RegExp ( `(${ Array . from ( deprecatedTokenReplacements . keys ( ) ) . join ( "|" ) })` ) ; /**
 * Determine if any deprecated token strings show up within the input, and return the pattern matches, allowing you to determine which ones they were.
 */ function getDeprecatedKaizenTokenPatternMatches ( value ) { return value . match ( deprecatedKaizenTokenPattern ) ; } const functionsThatSupportCSSVariables = new Set ( cssStandardFunctions ) ; functionsThatSupportCSSVariables . delete ( "saturate" ) ; /**
 * This is the set of functions that we know don't support CSS variables.
 * Rather than have an allow-list of functions that we know do support CSS variables, a deny-list should
 * be a lot less strict on users.
 * It should say "we know that AT LEAST these functions won't support CSS variables"
 * rather than "you can only use CSS variables within these functions".
 * The former allows for use of CSS variables in functions we've never seen before, such as user defined ones like `ca-margin`.
 *
 * At the time of writing this, we've just populated this list with color manipulation functions, then subtracted ones
 * from the allow list so that any potential overloaded functions (ones that work both with the SASS compiler and natively in the browser, like rgba()) are allowed.
 * https://www.tutorialsteacher.com/sass/sass-color-functions
 */ const functionsThatDontSupportCSSVariables = new Set ( [ "mix" , "shade" , "tint" , "darken" , "lighten" , "adjust-hue" , "saturate" , "desaturate" , "add-alpha" , "transparentize" , "adjust-color" , "scale-color" , "change-color" , "opacify" , ] . filter ( i => ! functionsThatSupportCSSVariables . has ( i ) ) ) ; const atRulesThatDontSupportCSSVars = new Set ( [ "media" , "supports" , "document" , "page" , "font-face" , "keyframes" , "viewport" , "counter-style" , "font-feature-values" , "property" , "color-profile" , ] ) ; returns function detectAndFixInvalidTokens ( nodeValue , postcssNode , options ) { let parsedValue = postcssValueParser ( nodeValue ) ; let newValue = nodeValue ; let changed = false ; let reported = false ; function report ( { message , autofixAvailable , } ) { reported = true ; options . reporter ( { message , autofixAvailable , node : postcssNode , } ) ; } if ( containsEquationThatDoesntWorkWithCSSVariables ( postcssNode , parsedValue , options ) ) { report ( { message : invalidEquationContainingKaizenTokenMessage , autofixAvailable : false , } ) ; return ; } const fixAlphaModificationFunctionsResult = fixAlphaModificationFunctions ( nodeValue ) ; if ( fixAlphaModificationFunctionsResult . errors . length ) { fixAlphaModificationFunctionsResult . errors . forEach ( error => report ( { message : error , autofixAvailable : false , } ) ) ; return ; } else { parsedValue = postcssValueParser ( fixAlphaModificationFunctionsResult . newValue ) ; newValue = fixAlphaModificationFunctionsResult . newValue ; changed = true ; } walkVariablesOnValue ( parsedValue , ( variableNode , variable , parentFunction ) => { const parentFunctionName = parentFunction ? parentFunction . value : undefined ; const isDeprecated = deprecatedTokenReplacements . has ( variable . name ) ; if ( parentFunctionName && functionsThatDontSupportCSSVariables . has ( parentFunctionName ) && variable . kaizenToken ?. cssVariable && ! isDeprecated ) { report ( { message : cssVariableUsedWithinUnsupportedFunction ( variable . name , parentFunctionName ) , autofixAvailable : false , } ) ; return ; } if ( ! isDeprecated ) { return ; } const replacement = deprecatedTokenReplacements . get ( variable . name ) ; if ( ! replacement ) { report ( { message : deprecatedTokenUsageWithoutReplacementMessage ( variable . name ) , autofixAvailable : false , } ) ; return ; } const replacementKaizenToken = kaizenTokensByName [ replacement ] ; if ( ! replacementKaizenToken ) { report ( { message : "Internal error. Replacement token is not an existing Kaizen token" , autofixAvailable : false , } ) ; return ; } const commitReplacement = ( ) => { changed = true ; if ( ! options . fix ) { report ( { message : deprecatedTokenUsageMessage ( variable . name , replacement ) , autofixAvailable : true , } ) ; } else { const replacementVariable = replaceTokenInVariable ( variable , replacementKaizenToken ) ; variableNode . value = stringifyVariable ( replacementVariable ) ; newValue = postcssValueParser . stringify ( parsedValue . nodes ) ; } } ; const isCurrentTokenCssVariable = ! ! variable . kaizenToken ?. cssVariable ; const isReplacementTokenCssVariable = ! ! replacementKaizenToken . cssVariable ; if ( isCurrentTokenCssVariable || ! isReplacementTokenCssVariable ) { commitReplacement ( ) ; return ; } if ( isVariable ( postcssNode ) ) { report ( { message : deprecatedTokenInVariableMessage ( variable . name , replacement ) , autofixAvailable : false , } ) ; return ; } if ( parentFunctionName && functionsThatDontSupportCSSVariables . has ( parentFunctionName ) && replacementKaizenToken . cssVariable ) { report ( { message : replacementCssVariableUsedWithinUnsupportedFunction ( variable . name , replacementKaizenToken . name , parentFunctionName ) , autofixAvailable : false , } ) ; return ; } if ( postcssNode . type === "atrule" && replacementKaizenToken . cssVariable && atRulesThatDontSupportCSSVars . has ( postcssNode . name ) ) { report ( { message : cantReplaceDeprecatedTokenInAtRuleMessage ( variable . name , replacementKaizenToken . name ) , autofixAvailable : false , } ) ; return ; } commitReplacement ( ) ; } ) ; if ( containsEquationThatDoesntWorkWithCSSVariables ( postcssNode , postcssValueParser ( newValue ) , options ) ) { report ( { message : invalidEquationContainingKaizenTokenMessage , autofixAvailable : false , } ) ; return ; } if ( ! reported ) { const deprecatedTokenMatches = getDeprecatedKaizenTokenPatternMatches ( newValue ) ; if ( deprecatedTokenMatches ) { options . reporter ( { message : containsDeprecatedKaizenTokenWithNoReplacement ( deprecatedTokenMatches [ 1 ] ) , autofixAvailable : false , node : postcssNode , } ) ; } } if ( ! changed ) return undefined ; return newValue ; } /**
 * This is a stylelint rule which detects and autofixes invalid usages of tokens.
 * See  detectAndFixInvalidTokens for how this works.
  export const noInvalidUseOfTokens = { name : "no-invalid-use-of-tokens" , ruleFunction : ( styleSheetNode , options ) => { styleSheetNode . walkAtRules ( postcssNode => { const newValue = detectAndFixInvalidTokens ( postcssNode . params , postcssNode , options ) ; if ( options . fix && newValue ) { postcssNode . replaceWith ( postcssNode . clone ( { params : newValue , } ) ) ; } } ) ; styleSheetNode . walkDecls ( postcssNode => { const newValue = detectAndFixInvalidTokens ( postcssNode . value , postcssNode , options ) ; if ( options . fix && newValue ) { postcssNode . replaceWith ( postcssNode . clone ( { value : newValue , } ) ) ; } } ) ; } , } ;