@salesforce-ux/eslint-plugin-slds 1.0.7-internal → 1.0.8-internal

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +62 -0
  2. package/build/index.js +240 -104
  3. package/build/index.js.map +3 -3
  4. package/build/rules/v9/lwc-token-to-slds-hook.js.map +2 -2
  5. package/build/rules/v9/no-hardcoded-values/handlers/boxShadowHandler.js +86 -30
  6. package/build/rules/v9/no-hardcoded-values/handlers/boxShadowHandler.js.map +3 -3
  7. package/build/rules/v9/no-hardcoded-values/handlers/colorHandler.js +116 -104
  8. package/build/rules/v9/no-hardcoded-values/handlers/colorHandler.js.map +3 -3
  9. package/build/rules/v9/no-hardcoded-values/handlers/densityHandler.js +83 -30
  10. package/build/rules/v9/no-hardcoded-values/handlers/densityHandler.js.map +3 -3
  11. package/build/rules/v9/no-hardcoded-values/handlers/fontHandler.js +78 -74
  12. package/build/rules/v9/no-hardcoded-values/handlers/fontHandler.js.map +3 -3
  13. package/build/rules/v9/no-hardcoded-values/handlers/index.js +175 -99
  14. package/build/rules/v9/no-hardcoded-values/handlers/index.js.map +3 -3
  15. package/build/rules/v9/no-hardcoded-values/no-hardcoded-values-slds1.js +221 -101
  16. package/build/rules/v9/no-hardcoded-values/no-hardcoded-values-slds1.js.map +3 -3
  17. package/build/rules/v9/no-hardcoded-values/no-hardcoded-values-slds2.js +221 -101
  18. package/build/rules/v9/no-hardcoded-values/no-hardcoded-values-slds2.js.map +3 -3
  19. package/build/rules/v9/no-hardcoded-values/noHardcodedValueRule.js +221 -101
  20. package/build/rules/v9/no-hardcoded-values/noHardcodedValueRule.js.map +3 -3
  21. package/build/rules/v9/no-hardcoded-values/ruleOptionsSchema.js +63 -0
  22. package/build/rules/v9/no-hardcoded-values/ruleOptionsSchema.js.map +7 -0
  23. package/build/rules/v9/no-slds-namespace-for-custom-hooks.js.map +2 -2
  24. package/build/rules/v9/no-slds-var-without-fallback.js.map +2 -2
  25. package/build/src/rules/v9/no-hardcoded-values/ruleOptionsSchema.d.ts +40 -0
  26. package/build/src/types/index.d.ts +31 -0
  27. package/build/src/utils/color-lib-utils.d.ts +16 -9
  28. package/build/src/utils/custom-mapping-utils.d.ts +9 -0
  29. package/build/src/utils/hardcoded-shared-utils.d.ts +1 -0
  30. package/build/src/utils/property-matcher.d.ts +3 -1
  31. package/build/types/index.js.map +1 -1
  32. package/build/utils/boxShadowValueParser.js.map +2 -2
  33. package/build/utils/color-lib-utils.js +26 -50
  34. package/build/utils/color-lib-utils.js.map +2 -2
  35. package/build/utils/css-utils.js.map +2 -2
  36. package/build/utils/custom-mapping-utils.js +62 -0
  37. package/build/utils/custom-mapping-utils.js.map +7 -0
  38. package/build/utils/hardcoded-shared-utils.js +29 -16
  39. package/build/utils/hardcoded-shared-utils.js.map +2 -2
  40. package/build/utils/property-matcher.js +20 -6
  41. package/build/utils/property-matcher.js.map +2 -2
  42. package/package.json +2 -2
package/README.md CHANGED
@@ -96,6 +96,68 @@ By default, the latest version of the plugin supports legacy and flat config sys
96
96
  - `reduce-annotations`: Identifies annotations that must be removed from the code.
97
97
  - `lwc-token-to-slds-hook`: Identifies the deprecated --lwc tokens that must be replaced with the latest --slds tokens. For more information, see lightningdesignsystem.com.
98
98
 
99
+ ## Closest Color Suggestion Logic
100
+
101
+ This plugin suggests SLDS styling hooks that are perceptually closest to a given hardcoded color. The logic lives in `src/utils/color-lib-utils.ts` and is used by `no-hardcoded-values-slds2`.
102
+
103
+ - **API surface**
104
+ - `findClosestColorHook(color, supportedColors, cssProperty): string[]`
105
+ Returns up to 5 hook names, ordered by category priority and perceptual distance.
106
+ - `isHardCodedColor(value): boolean`
107
+ Detects if a string likely represents a hardcoded color (excludes CSS `var(...)`).
108
+
109
+ - **Perceptual metric**
110
+ - Uses `chroma.deltaE` (CIEDE2000) to compare the input color against known SLDS color values.
111
+ - A threshold (`DELTAE_THRESHOLD = 25`) controls how strict the matching is.
112
+ - Exact hex matches short-circuit to distance `0` to avoid float rounding differences.
113
+
114
+ - **Category ordering**
115
+ - Hooks are ranked by category, then by distance (ascending):
116
+ 1. Semantic hooks (e.g., surface, accent, error, success, etc.)
117
+ 2. System hooks
118
+ 3. Palette hooks
119
+ - Only the top 5 suggestions are returned.
120
+
121
+ - **Property awareness**
122
+ - Suggestions consider the CSS property from rule context.
123
+ - Hooks declared for the same property (or `*`) are prioritized if within the distance threshold.
124
+
125
+ - **CSS variables**
126
+ - Values using `var(...)` are not flagged as hardcoded colors and are excluded from matching.
127
+
128
+ - **Semantic hook ordering by CSS property**
129
+ The sorter always applies the same category priority (semantic → system → palette). Within the semantic category, ordering is purely by perceptual distance for the current CSS property; there is no hardcoded sub-priority among semantic families (surface, accent, error, etc.). Property awareness comes from the metadata (`properties` on each hook).
130
+
131
+ - `color`
132
+ - Prefers semantic hooks that declare `properties: ['color']` (e.g., text/foreground-oriented tokens).
133
+ - If multiple semantic hooks are eligible, they are ordered by Delta E (closest first).
134
+ - If none are within threshold, the sorter may fall back to system, then palette.
135
+
136
+ - `background-color`
137
+ - Prefers semantic surface/role tokens that declare `['background-color']`.
138
+ - Among eligible semantic hooks, order is by Delta E.
139
+
140
+ - `border-color` (and `outline-color`)
141
+ - Prefers semantic or system hooks that declare `['border-color']` (or `['outline-color']`).
142
+ - If semantic hooks exist for borders, they are ranked before system; otherwise system hooks lead.
143
+ - Order within the chosen category is by Delta E.
144
+
145
+ - `box-shadow`
146
+ - For color components inside shadows, prefers hooks declaring `['box-shadow']` or universal `['*']`.
147
+ - Ordering within semantic remains by Delta E.
148
+
149
+ - Other properties
150
+ - If hook metadata specifies the current property, those hooks are preferred.
151
+ - Hooks with `['*']` (universal) are considered next.
152
+ - If still none within threshold, different-property hooks may be considered (still subject to category priority and Delta E).
153
+
154
+ - **Example**
155
+ ```js
156
+ // Given a hex color and a CSS property like 'color'
157
+ const suggestions = findClosestColorHook('#ff0000', supportedColors, 'color');
158
+ // => ['--slds-...semantic-...', '--slds-...system-...', ...]
159
+ ```
160
+
99
161
  ## License
100
162
 
101
163
  ISC
package/build/index.js CHANGED
@@ -780,11 +780,7 @@ function isCssColorFunction(value) {
780
780
  }
781
781
 
782
782
  // src/utils/color-lib-utils.ts
783
- var LAB_THRESHOLD = 25;
784
- var isHexCode = (color) => {
785
- const hexPattern = /^#(?:[0-9a-fA-F]{3}){1,2}$/;
786
- return hexPattern.test(color);
787
- };
783
+ var DELTAE_THRESHOLD = 10;
788
784
  var convertToHex = (color) => {
789
785
  try {
790
786
  return (0, import_chroma_js.default)(color).hex();
@@ -792,52 +788,40 @@ var convertToHex = (color) => {
792
788
  return null;
793
789
  }
794
790
  };
791
+ var isHookPropertyMatch = (hook, cssProperty) => {
792
+ return hook.properties.includes(cssProperty) || hook.properties.includes("*");
793
+ };
794
+ function getOrderByCssProp(cssProperty) {
795
+ if (cssProperty === "color" || cssProperty === "fill") {
796
+ return ["surface", "theme", "feedback", "reference"];
797
+ } else if (cssProperty.match(/background/)) {
798
+ return ["surface", "surface-inverse", "theme", "feedback", "reference"];
799
+ } else if (cssProperty.match(/border/) || cssProperty.match(/outline/) || cssProperty.match(/stroke/)) {
800
+ return ["borders", "borders-inverse", "feedback", "theme", "reference"];
801
+ }
802
+ return ["surface", "surface-inverse", "borders", "borders-inverse", "theme", "feedback", "reference"];
803
+ }
795
804
  var findClosestColorHook = (color, supportedColors, cssProperty) => {
796
- const returnStylingHooks = [];
797
- const closestHooksWithSameProperty = [];
798
- const closestHooksWithoutSameProperty = [];
799
- const closestHooksWithAllProperty = [];
800
- const labColor = (0, import_chroma_js.default)(color).lab();
805
+ const closestHooks = [];
801
806
  Object.entries(supportedColors).forEach(([sldsValue, data]) => {
802
- if (sldsValue && isHexCode(sldsValue)) {
807
+ if (sldsValue && isValidColor(sldsValue)) {
803
808
  const hooks = data;
804
809
  hooks.forEach((hook) => {
805
- const labSupportedColor = (0, import_chroma_js.default)(sldsValue).lab();
806
- const distance = JSON.stringify(labColor) === JSON.stringify(labSupportedColor) ? 0 : import_chroma_js.default.distance(import_chroma_js.default.lab(...labColor), import_chroma_js.default.lab(...labSupportedColor), "lab");
807
- if (hook.properties.includes(cssProperty)) {
808
- if (distance <= LAB_THRESHOLD) {
809
- closestHooksWithSameProperty.push({ name: hook.name, distance });
810
- }
811
- } else if (hook.properties.includes("*")) {
812
- if (distance <= LAB_THRESHOLD) {
813
- closestHooksWithAllProperty.push({ name: hook.name, distance });
814
- }
815
- } else {
816
- if (distance <= LAB_THRESHOLD) {
817
- closestHooksWithoutSameProperty.push({ name: hook.name, distance });
818
- }
810
+ const distance = sldsValue.toLowerCase() === color.toLowerCase() ? 0 : import_chroma_js.default.deltaE(sldsValue, color);
811
+ if (isHookPropertyMatch(hook, cssProperty) && distance <= DELTAE_THRESHOLD) {
812
+ closestHooks.push({ distance, group: hook.group, name: hook.name });
819
813
  }
820
814
  });
821
815
  }
822
816
  });
823
- const closesthookGroups = [
824
- { hooks: closestHooksWithSameProperty, distance: 0 },
825
- { hooks: closestHooksWithAllProperty, distance: 0 },
826
- { hooks: closestHooksWithSameProperty, distance: Infinity },
827
- // For hooks with distance > 0
828
- { hooks: closestHooksWithAllProperty, distance: Infinity },
829
- { hooks: closestHooksWithoutSameProperty, distance: Infinity }
830
- ];
831
- for (const group of closesthookGroups) {
832
- const filteredHooks = group.hooks.filter(
833
- (h) => group.distance === 0 ? h.distance === 0 : h.distance > 0
834
- );
835
- if (returnStylingHooks.length < 1 && filteredHooks.length > 0) {
836
- filteredHooks.sort((a, b) => a.distance - b.distance);
837
- returnStylingHooks.push(...filteredHooks.slice(0, 5).map((h) => h.name));
817
+ const hooksByGroupMap = closestHooks.sort((a, b) => a.distance - b.distance).reduce((acc, hook) => {
818
+ if (!acc[hook.group]) {
819
+ acc[hook.group] = [];
838
820
  }
839
- }
840
- return Array.from(new Set(returnStylingHooks));
821
+ acc[hook.group].push(hook.name);
822
+ return acc;
823
+ }, {});
824
+ return getOrderByCssProp(cssProperty).map((group) => hooksByGroupMap[group] || []).flat().slice(0, 5);
841
825
  };
842
826
  var isValidColor = (val) => import_chroma_js.default.valid(val);
843
827
  var extractColorValue = (node) => {
@@ -879,13 +863,33 @@ function isKnownFontWeight(value) {
879
863
  return FONT_WEIGHTS.includes(stringValue.toLowerCase());
880
864
  }
881
865
  function handleShorthandAutoFix(declarationNode, context, valueText, replacements) {
882
- const sortedReplacements = replacements.sort((a, b) => a.start - b.start);
883
- const hasAnyHooks = sortedReplacements.some((r) => r.hasHook);
884
- const canAutoFix = hasAnyHooks;
885
- sortedReplacements.forEach(({ start, end, replacement, displayValue, hasHook }) => {
866
+ if (!replacements || replacements.length === 0) {
867
+ return;
868
+ }
869
+ const sortedReplacements = replacements.sort((a, b) => b.start - a.start);
870
+ const reportNumericValue = context.options?.reportNumericValue || "always";
871
+ const fixCallback = (start, originalValue, replacement) => {
872
+ let newValue = valueText;
873
+ newValue = newValue.substring(0, start) + replacement + newValue.substring(start + originalValue.length);
874
+ if (newValue !== valueText) {
875
+ return (fixer) => {
876
+ return fixer.replaceText(declarationNode.value, newValue);
877
+ };
878
+ }
879
+ };
880
+ sortedReplacements.forEach(({ start, end, replacement, displayValue, hasHook, isNumeric }) => {
886
881
  const originalValue = valueText.substring(start, end);
887
- const valueStartColumn = declarationNode.value.loc.start.column;
888
- const valueColumn = valueStartColumn + start;
882
+ if (isNumeric) {
883
+ if (reportNumericValue === "never") {
884
+ return;
885
+ }
886
+ if (reportNumericValue === "hasReplacement" && !hasHook) {
887
+ return;
888
+ }
889
+ }
890
+ const valueColumnStart = declarationNode.value.loc.start.column + start;
891
+ const valueColumnEnd = valueColumnStart + originalValue.length;
892
+ const canAutoFix = originalValue !== replacement;
889
893
  const { loc: { start: locStart, end: locEnd } } = declarationNode.value;
890
894
  const reportNode = {
891
895
  ...declarationNode.value,
@@ -893,23 +897,16 @@ function handleShorthandAutoFix(declarationNode, context, valueText, replacement
893
897
  ...declarationNode.value.loc,
894
898
  start: {
895
899
  ...locStart,
896
- column: valueColumn
900
+ column: valueColumnStart
897
901
  },
898
902
  end: {
899
903
  ...locEnd,
900
- column: valueColumn + originalValue.length
904
+ column: valueColumnEnd
901
905
  }
902
906
  }
903
907
  };
904
908
  if (hasHook) {
905
- const fix = canAutoFix ? (fixer) => {
906
- let newValue = valueText;
907
- for (let i = sortedReplacements.length - 1; i >= 0; i--) {
908
- const { start: rStart, end: rEnd, replacement: rReplacement } = sortedReplacements[i];
909
- newValue = newValue.substring(0, rStart) + rReplacement + newValue.substring(rEnd);
910
- }
911
- return fixer.replaceText(declarationNode.value, newValue);
912
- } : void 0;
909
+ const fix = canAutoFix ? fixCallback(start, originalValue, replacement) : void 0;
913
910
  context.context.report({
914
911
  node: reportNode,
915
912
  messageId: "hardcodedValue",
@@ -1790,9 +1787,9 @@ function toSelector(properties) {
1790
1787
  });
1791
1788
  return selectorParts.join(", ");
1792
1789
  }
1793
- function resolvePropertyToMatch(cssProperty) {
1790
+ function resolveDensityPropertyToMatch(cssProperty) {
1794
1791
  const propertyToMatch = cssProperty.toLowerCase();
1795
- if (propertyToMatch === "outline" || propertyToMatch === "outline-width" || isBorderWidthProperty(propertyToMatch)) {
1792
+ if (isOutlineWidthProperty(propertyToMatch) || isBorderWidthProperty(propertyToMatch)) {
1796
1793
  return "border-width";
1797
1794
  } else if (isMarginProperty(propertyToMatch)) {
1798
1795
  return "margin";
@@ -1804,13 +1801,58 @@ function resolvePropertyToMatch(cssProperty) {
1804
1801
  return "width";
1805
1802
  } else if (isInsetProperty(propertyToMatch)) {
1806
1803
  return "top";
1807
- } else if (cssProperty === "background" || cssProperty === "background-color") {
1804
+ }
1805
+ return propertyToMatch;
1806
+ }
1807
+ function resolveColorPropertyToMatch(cssProperty) {
1808
+ const propertyToMatch = cssProperty.toLowerCase();
1809
+ if (propertyToMatch === "outline" || propertyToMatch === "outline-color") {
1810
+ return "border-color";
1811
+ } else if (propertyToMatch === "background" || propertyToMatch === "background-color") {
1808
1812
  return "background-color";
1809
- } else if (cssProperty === "outline" || cssProperty === "outline-color" || isBorderColorProperty(cssProperty)) {
1813
+ } else if (isBorderColorProperty(propertyToMatch)) {
1810
1814
  return "border-color";
1811
1815
  }
1812
1816
  return propertyToMatch;
1813
1817
  }
1818
+ function isOutlineWidthProperty(propertyToMatch) {
1819
+ return propertyToMatch === "outline" || propertyToMatch === "outline-width";
1820
+ }
1821
+
1822
+ // src/utils/custom-mapping-utils.ts
1823
+ function matchesPropertyPattern(cssProperty, pattern) {
1824
+ const normalizedProperty = cssProperty.toLowerCase();
1825
+ const normalizedPattern = pattern.toLowerCase();
1826
+ if (normalizedProperty === normalizedPattern) {
1827
+ return true;
1828
+ }
1829
+ if (normalizedPattern.endsWith("*")) {
1830
+ const prefix = normalizedPattern.slice(0, -1);
1831
+ return normalizedProperty.startsWith(prefix);
1832
+ }
1833
+ return false;
1834
+ }
1835
+ function getCustomMapping(cssProperty, value, customMapping) {
1836
+ if (!customMapping) {
1837
+ return null;
1838
+ }
1839
+ const normalizedValue = value.toLowerCase().trim();
1840
+ for (const [hookName, config] of Object.entries(customMapping)) {
1841
+ const propertyMatches = config.properties.some(
1842
+ (pattern) => matchesPropertyPattern(cssProperty, pattern)
1843
+ );
1844
+ if (!propertyMatches) {
1845
+ continue;
1846
+ }
1847
+ const valueMatches = config.values.some(
1848
+ (configValue) => configValue.toLowerCase().trim() === normalizedValue
1849
+ );
1850
+ if (valueMatches) {
1851
+ return hookName;
1852
+ }
1853
+ }
1854
+ return null;
1855
+ }
1814
1856
 
1815
1857
  // src/rules/v9/no-hardcoded-values/handlers/colorHandler.ts
1816
1858
  var handleColorDeclaration = (node, context) => {
@@ -1835,24 +1877,32 @@ function createColorReplacement(colorValue, cssProperty, context, positionInfo,
1835
1877
  if (!hexValue) {
1836
1878
  return null;
1837
1879
  }
1838
- const propToMatch = resolvePropertyToMatch(cssProperty);
1839
- const closestHooks = findClosestColorHook(hexValue, context.valueToStylinghook, propToMatch);
1840
1880
  const start = positionInfo.start.offset;
1841
1881
  const end = positionInfo.end.offset;
1842
1882
  const originalValue = originalValueText ? originalValueText.substring(start, end) : colorValue;
1843
- if (closestHooks.length === 1) {
1844
- return {
1845
- start,
1846
- end,
1847
- replacement: `var(${closestHooks[0]}, ${colorValue})`,
1848
- displayValue: closestHooks[0],
1849
- hasHook: true
1850
- };
1851
- } else if (closestHooks.length > 1) {
1883
+ const customHook = getCustomMapping(cssProperty, colorValue, context.options?.customMapping);
1884
+ let closestHooks = [];
1885
+ if (customHook) {
1886
+ closestHooks = [customHook];
1887
+ } else {
1888
+ const propToMatch = resolveColorPropertyToMatch(cssProperty);
1889
+ closestHooks = findClosestColorHook(hexValue, context.valueToStylinghook, propToMatch);
1890
+ }
1891
+ let replacement = originalValue;
1892
+ let paletteHook = null;
1893
+ if (context.options?.preferPaletteHook && closestHooks.length > 1) {
1894
+ paletteHook = closestHooks.filter((hook) => hook.includes("-palette-"))[0];
1895
+ }
1896
+ if (paletteHook) {
1897
+ replacement = `var(${paletteHook}, ${colorValue})`;
1898
+ } else if (closestHooks.length === 1) {
1899
+ replacement = `var(${closestHooks[0]}, ${colorValue})`;
1900
+ }
1901
+ if (closestHooks.length > 0) {
1852
1902
  return {
1853
1903
  start,
1854
1904
  end,
1855
- replacement: originalValue,
1905
+ replacement,
1856
1906
  // Use original value to preserve spacing
1857
1907
  displayValue: formatSuggestionHooks(closestHooks),
1858
1908
  hasHook: true
@@ -1861,7 +1911,7 @@ function createColorReplacement(colorValue, cssProperty, context, positionInfo,
1861
1911
  return {
1862
1912
  start,
1863
1913
  end,
1864
- replacement: originalValue,
1914
+ replacement,
1865
1915
  // Use original value to preserve spacing
1866
1916
  displayValue: originalValue,
1867
1917
  hasHook: false
@@ -1909,8 +1959,14 @@ function createDimensionReplacement(parsedDimension, cssProperty, context, posit
1909
1959
  return null;
1910
1960
  }
1911
1961
  const rawValue = parsedDimension.unit ? `${parsedDimension.number}${parsedDimension.unit}` : parsedDimension.number.toString();
1912
- const propToMatch = resolvePropertyToMatch(cssProperty);
1913
- const closestHooks = getStylingHooksForDensityValue(parsedDimension, context.valueToStylinghook, propToMatch);
1962
+ const customHook = getCustomMapping(cssProperty, rawValue, context.options?.customMapping);
1963
+ let closestHooks = [];
1964
+ if (customHook) {
1965
+ closestHooks = [customHook];
1966
+ } else {
1967
+ const propToMatch = resolveDensityPropertyToMatch(cssProperty);
1968
+ closestHooks = getStylingHooksForDensityValue(parsedDimension, context.valueToStylinghook, propToMatch);
1969
+ }
1914
1970
  const start = positionInfo.start.offset;
1915
1971
  const end = positionInfo.end.offset;
1916
1972
  if (closestHooks.length === 1) {
@@ -1919,7 +1975,8 @@ function createDimensionReplacement(parsedDimension, cssProperty, context, posit
1919
1975
  end,
1920
1976
  replacement: `var(${closestHooks[0]}, ${rawValue})`,
1921
1977
  displayValue: closestHooks[0],
1922
- hasHook: true
1978
+ hasHook: true,
1979
+ isNumeric: true
1923
1980
  };
1924
1981
  } else if (closestHooks.length > 1) {
1925
1982
  return {
@@ -1927,7 +1984,8 @@ function createDimensionReplacement(parsedDimension, cssProperty, context, posit
1927
1984
  end,
1928
1985
  replacement: rawValue,
1929
1986
  displayValue: formatSuggestionHooks(closestHooks),
1930
- hasHook: true
1987
+ hasHook: true,
1988
+ isNumeric: true
1931
1989
  };
1932
1990
  } else {
1933
1991
  return {
@@ -1935,7 +1993,8 @@ function createDimensionReplacement(parsedDimension, cssProperty, context, posit
1935
1993
  end,
1936
1994
  replacement: rawValue,
1937
1995
  displayValue: rawValue,
1938
- hasHook: false
1996
+ hasHook: false,
1997
+ isNumeric: true
1939
1998
  };
1940
1999
  }
1941
2000
  }
@@ -1976,8 +2035,14 @@ function createFontReplacement(fontValue, cssProperty, context, positionInfo) {
1976
2035
  return null;
1977
2036
  }
1978
2037
  const rawValue = fontValue.unit ? `${fontValue.number}${fontValue.unit}` : fontValue.number.toString();
1979
- const propToMatch = !fontValue.unit && isKnownFontWeight(fontValue.number) ? resolvePropertyToMatch("font-weight") : resolvePropertyToMatch("font-size");
1980
- const closestHooks = getStylingHooksForDensityValue(fontValue, context.valueToStylinghook, propToMatch);
2038
+ const propToMatch = !fontValue.unit && isKnownFontWeight(fontValue.number) ? "font-weight" : "font-size";
2039
+ const customHook = getCustomMapping(propToMatch, rawValue, context.options?.customMapping);
2040
+ let closestHooks = [];
2041
+ if (customHook) {
2042
+ closestHooks = [customHook];
2043
+ } else {
2044
+ closestHooks = getStylingHooksForDensityValue(fontValue, context.valueToStylinghook, propToMatch);
2045
+ }
1981
2046
  const start = positionInfo.start.offset;
1982
2047
  const end = positionInfo.end.offset;
1983
2048
  if (closestHooks.length === 1) {
@@ -1986,7 +2051,8 @@ function createFontReplacement(fontValue, cssProperty, context, positionInfo) {
1986
2051
  end,
1987
2052
  replacement: `var(${closestHooks[0]}, ${rawValue})`,
1988
2053
  displayValue: closestHooks[0],
1989
- hasHook: true
2054
+ hasHook: true,
2055
+ isNumeric: true
1990
2056
  };
1991
2057
  } else if (closestHooks.length > 1) {
1992
2058
  return {
@@ -1994,7 +2060,8 @@ function createFontReplacement(fontValue, cssProperty, context, positionInfo) {
1994
2060
  end,
1995
2061
  replacement: rawValue,
1996
2062
  displayValue: formatSuggestionHooks(closestHooks),
1997
- hasHook: true
2063
+ hasHook: true,
2064
+ isNumeric: true
1998
2065
  };
1999
2066
  } else {
2000
2067
  return {
@@ -2002,7 +2069,8 @@ function createFontReplacement(fontValue, cssProperty, context, positionInfo) {
2002
2069
  end,
2003
2070
  replacement: rawValue,
2004
2071
  displayValue: rawValue,
2005
- hasHook: false
2072
+ hasHook: false,
2073
+ isNumeric: true
2006
2074
  };
2007
2075
  }
2008
2076
  }
@@ -2134,9 +2202,30 @@ function shadowValueToHookEntries(supportedStylinghooks) {
2134
2202
  return [key, value.map((hook) => hook.name)];
2135
2203
  });
2136
2204
  }
2205
+ function reportBoxShadowViolation(node, context, valueText, hooks) {
2206
+ const positionInfo = {
2207
+ start: { offset: 0, line: 1, column: 1 },
2208
+ end: { offset: valueText.length, line: 1, column: valueText.length + 1 }
2209
+ };
2210
+ const replacement = createBoxShadowReplacement(
2211
+ valueText,
2212
+ hooks,
2213
+ context,
2214
+ positionInfo
2215
+ );
2216
+ if (replacement) {
2217
+ const replacements = [replacement];
2218
+ handleShorthandAutoFix(node, context, valueText, replacements);
2219
+ }
2220
+ }
2137
2221
  var handleBoxShadowDeclaration = (node, context) => {
2138
2222
  const cssProperty = node.property.toLowerCase();
2139
2223
  const valueText = context.sourceCode.getText(node.value);
2224
+ const customHook = getCustomMapping(cssProperty, valueText, context.options?.customMapping);
2225
+ if (customHook) {
2226
+ reportBoxShadowViolation(node, context, valueText, [customHook]);
2227
+ return;
2228
+ }
2140
2229
  const shadowHooks = shadowValueToHookEntries(context.valueToStylinghook);
2141
2230
  const parsedCssValue = toBoxShadowValue(valueText);
2142
2231
  if (!parsedCssValue) {
@@ -2146,20 +2235,7 @@ var handleBoxShadowDeclaration = (node, context) => {
2146
2235
  const parsedValueHook = toBoxShadowValue(shadow);
2147
2236
  if (parsedValueHook && isBoxShadowMatch(parsedCssValue, parsedValueHook)) {
2148
2237
  if (closestHooks.length > 0) {
2149
- const positionInfo = {
2150
- start: { offset: 0, line: 1, column: 1 },
2151
- end: { offset: valueText.length, line: 1, column: valueText.length + 1 }
2152
- };
2153
- const replacement = createBoxShadowReplacement(
2154
- valueText,
2155
- closestHooks,
2156
- context,
2157
- positionInfo
2158
- );
2159
- if (replacement) {
2160
- const replacements = [replacement];
2161
- handleShorthandAutoFix(node, context, valueText, replacements);
2162
- }
2238
+ reportBoxShadowViolation(node, context, valueText, closestHooks);
2163
2239
  }
2164
2240
  return;
2165
2241
  }
@@ -2209,6 +2285,42 @@ function isRuleEnabled(context, ruleName3) {
2209
2285
  }
2210
2286
  }
2211
2287
 
2288
+ // src/rules/v9/no-hardcoded-values/ruleOptionsSchema.ts
2289
+ var ruleOptionsSchema = [
2290
+ {
2291
+ type: "object",
2292
+ properties: {
2293
+ reportNumericValue: {
2294
+ type: "string",
2295
+ enum: ["never", "always", "hasReplacement"],
2296
+ default: "always"
2297
+ },
2298
+ customMapping: {
2299
+ type: "object",
2300
+ additionalProperties: {
2301
+ type: "object",
2302
+ properties: {
2303
+ properties: {
2304
+ type: "array",
2305
+ items: { type: "string" }
2306
+ },
2307
+ values: {
2308
+ type: "array",
2309
+ items: { type: "string" }
2310
+ }
2311
+ },
2312
+ required: ["properties", "values"]
2313
+ }
2314
+ },
2315
+ preferPaletteHook: {
2316
+ type: "boolean",
2317
+ default: false
2318
+ }
2319
+ },
2320
+ additionalProperties: false
2321
+ }
2322
+ ];
2323
+
2212
2324
  // src/rules/v9/no-hardcoded-values/noHardcodedValueRule.ts
2213
2325
  function defineNoHardcodedValueRule(config) {
2214
2326
  const { ruleConfig: ruleConfig14, ruleName: ruleName3 } = config;
@@ -2222,16 +2334,24 @@ function defineNoHardcodedValueRule(config) {
2222
2334
  url: url15
2223
2335
  },
2224
2336
  fixable: "code",
2225
- messages: messages15
2337
+ messages: messages15,
2338
+ schema: ruleOptionsSchema
2226
2339
  },
2227
2340
  create(context) {
2228
2341
  if (ruleName3 === "no-hardcoded-values-slds1" && isRuleEnabled(context, "@salesforce-ux/slds/no-hardcoded-values-slds2")) {
2229
2342
  return {};
2230
2343
  }
2344
+ const options = context.options[0] || {};
2345
+ const ruleOptions = {
2346
+ reportNumericValue: options.reportNumericValue || "always",
2347
+ customMapping: options.customMapping || {},
2348
+ preferPaletteHook: options.preferPaletteHook || false
2349
+ };
2231
2350
  const handlerContext = {
2232
2351
  valueToStylinghook: config.valueToStylinghook,
2233
2352
  context,
2234
- sourceCode: context.sourceCode
2353
+ sourceCode: context.sourceCode,
2354
+ options: ruleOptions
2235
2355
  };
2236
2356
  const colorOnlySelector = toSelector(colorProperties);
2237
2357
  const densityOnlySelector = toSelector(densificationProperties);
@@ -2325,8 +2445,24 @@ var eslint_rules_internal_default = {
2325
2445
  "@salesforce-ux/slds/no-slds-namespace-for-custom-hooks": "warn",
2326
2446
  "@salesforce-ux/slds/enforce-component-hook-naming-convention": "error",
2327
2447
  "@salesforce-ux/slds/no-slds-private-var": "warn",
2328
- "@salesforce-ux/slds/no-hardcoded-values-slds1": "error",
2329
- "@salesforce-ux/slds/no-hardcoded-values-slds2": "warn",
2448
+ "@salesforce-ux/slds/no-hardcoded-values-slds2": ["warn", {
2449
+ reportNumericValue: "hasReplacement",
2450
+ preferPaletteHook: true,
2451
+ customMapping: {
2452
+ "--slds-g-font-line-height-4": {
2453
+ properties: ["line-height"],
2454
+ values: ["1.5"]
2455
+ },
2456
+ "--slds-g-sizing-5": {
2457
+ properties: ["width", "height", "max-width", "max-height", "border*"],
2458
+ values: ["1rem", "16px"]
2459
+ },
2460
+ "--slds-g-sizing-content-1": {
2461
+ properties: ["width", "height", "max-width", "max-height", "border*"],
2462
+ values: ["20ch"]
2463
+ }
2464
+ }
2465
+ }],
2330
2466
  "@salesforce-ux/slds/reduce-annotations": "warn"
2331
2467
  },
2332
2468
  html: {
@@ -2359,7 +2495,7 @@ var rules = {
2359
2495
  var plugin = {
2360
2496
  meta: {
2361
2497
  name: "@salesforce-ux/eslint-plugin-slds",
2362
- version: "1.0.7-internal"
2498
+ version: "1.0.8-internal"
2363
2499
  },
2364
2500
  rules,
2365
2501
  configs: {}