{"record":{"id":"c929fc61c5fad523","repo":"swc-project/swc","slug":"percentage-function-math-functions-or-ident-wi","errorCode":null,"errorMessage":"percentage, function (math functions) or ident (with 'none' value) token","messagePattern":"percentage, function \\(math functions\\) or ident \\(with 'none' value\\) token","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/swc_css_parser/src/parser/values_and_units/mod.rs","lineNumber":586,"sourceCode":"                                }\n                                Token::Function { value, .. } if is_math_function(value) => {\n                                    Ok(Some(ComponentValue::Function(parser.parse()?)))\n                                }\n                                tok!(\"ident\") => {\n                                    let ident: Box<Ident> = parser.parse()?;\n\n                                    if ident.value.eq_ignore_ascii_case(\"none\") {\n                                        Ok(Some(ComponentValue::Ident(ident)))\n                                    } else {\n                                        Err(Error::new(\n                                            ident.span,\n                                            ErrorKind::Expected(\"'none' value of an ident token\"),\n                                        ))\n                                    }\n                                }\n                                _ => {\n                                    if !has_variable_before {\n                                        Err(Error::new(\n                                            parser.input.cur_span(),\n                                            ErrorKind::Expected(\n                                                \"percentage, function (math functions) or ident \\\n                                                 (with 'none' value) token\",\n                                            ),\n                                        ))\n                                    } else {\n                                        Ok(None)\n                                    }\n                                }\n                            },\n                            &mut has_variable,\n                        )?;\n\n                        if let Some(percentage_or_none) = percentage_or_none {\n                            values.push(percentage_or_none);\n                        }\n","sourceCodeStart":568,"sourceCodeEnd":604,"githubUrl":"https://github.com/swc-project/swc/blob/5176682b65416c6b5de6b47379ae1588ea3ecb3f/crates/swc_css_parser/src/parser/values_and_units/mod.rs#L568-L604","documentation":"Thrown by swc_css_parser when the SECOND channel of hsl()/hsla() (saturation) starts with a token other than percentage, math function, or ident (crates/swc_css_parser/src/parser/values_and_units/mod.rs:584-596). ErrorKind::Expected(\"percentage, function (math functions) or ident (with 'none' value) token\") is reported at the current span. The most common hit is a bare NUMBER in the saturation slot — 'hsl(120, 50, 50%)' — because CSS requires saturation to be a percentage; dimensions, hashes, and strings also land here.","triggerScenarios":"parse_string on 'hsl(120, 50, 50%)' or 'hsl(120 50 50%)' (number where percentage is required), 'hsl(120, 50px, 50%)', 'hsl(120, #fa, 50%)'. The '_' arm at line 584 fires when has_variable_before is false.","commonSituations":"Classic legacy-syntax mistake: hsl written with unit-less numbers (some tutorials and old design tools emit 'hsl(210, 70, 40)'); SCSS/JS color math emitting raw floats into hsl() strings; copy-paste from color pickers that expose slider values 0-100 without '%'.","solutions":["Add '%' to the saturation (and lightness) values: 'hsl(120, 50%, 50%)' or 'hsl(120 50% 50%)'.","Modern space syntax also requires percentages for s/l: 'hsl(120 100% 50%)'.","For computed values, format with the unit: format!(\"hsl({}, {}%, {}%)\", h, s, l).","Use var() indirection for machine-generated channels to bypass strict checks."],"exampleFix":"/* before */\ncolor: hsl(120, 50, 50%);\ncolor: hsl(210, 70, 40);\n\n/* after */\ncolor: hsl(120, 50%, 50%);\ncolor: hsl(210, 70%, 40%);","handlingStrategy":"try-catch","validationCode":"fn hsl_saturation_token_ok(arg: &str) -> bool {\n    let a = arg.trim();\n    a.ends_with('%')\n        || a.eq_ignore_ascii_case(\"none\")\n        || a.starts_with(\"var(\")\n        || a.ends_with(')') && [\"calc(\",\"min(\",\"max(\",\"clamp(\"].iter().any(|f| a.to_ascii_lowercase().starts_with(f)))\n    // 'hsl(120, 50, 50%)' fails: bare number 50 is rejected\n}","typeGuard":"fn is_percentage_or_none(text: &str) -> bool {\n    let t = text.trim();\n    t.ends_with('%') || t.eq_ignore_ascii_case(\"none\") || t.starts_with(\"var(\")\n}","tryCatchPattern":"if let Err(err) = swc_css_parser::parse_string::<Stylesheet>(css, config) {\n    if matches!(&err.kind(), ErrorKind::Expected(m) if m.contains(\"percentage, function (math functions) or ident\")) {\n        let (span, _) = err.into_inner();\n        // saturation token invalid — most often a bare number; append '%' at the reported span\n    }\n    return Err(err.into());\n}","preventionTips":["hsl()/hsla() saturation MUST be a percentage in both legacy and modern syntax — always append '%'.","When formatting from numbers: format!(\"hsl({}, {}%, {}%)\", h, s, l).","Add a lint/regex in CI: /hsl\\([^)]*,\\s*\\d+\\s*,/ catches missing '%' in legacy syntax."],"tags":["css","swc","css-parser","hsl","saturation","percentage","unexpected-token"],"backgroundTag":"css-color-function-invalid-token","analyzedSha":"5176682b65416c6b5de6b47379ae1588ea3ecb3f","analyzedAt":"2026-08-17T16:16:52.067Z","contentChangedAt":"2026-08-17T16:16:52.067Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}