All files / packages/sds-customization-compliance/src/checks index.ts

90% Statements 9/10
75% Branches 3/4
100% Functions 3/3
100% Lines 7/7

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232                                                                                                                                                                                                                                    3x                                                                                           3x                                                                                                         280x 144x 144x 144x         280x                    
/**
 * The SDS Customization Engine compliance checks.
 *
 * Each check is a pure function `(input: ComponentCheckInput) => ComplianceRow`.
 * The registry below is the canonical list + evaluation order — the Task
 * Panel, Storybook addon, and CLI all render rows in this sequence.
 *
 * Five sub-groups (the `category` field on each `ComplianceRow`):
 *
 *   - **`customer-reach` (20):** structural rules + mechanism-fit checks
 *     that block customer overrides when violated. `fail` severity.
 *     Customer hook writes have to land on customer-reachable selectors
 *     and inside the right `@layer`; foreign descendant chains, BEM-element
 *     reach into other components, and attribute-substring writes all
 *     break customer overrides through one mechanism or another.
 *     Two spacing-syntax rules
 *     (`structural-spacing-direction-syntax`,
 *     `structural-spacing-axis-only-assignment`) gate the
 *     direction/axis two-tier hook contract for `padding-inline-*` /
 *     `margin-block-*` style declarations.
 *     `structural-required-themes-present` gates per-theme paint
 *     coverage: every component that ships `themes/base.css` must also
 *     ship `themes/cosmos.css` and `themes/lightning-blue.css` so the
 *     customer's resolved cascade matches whichever theme the product
 *     surface is running.
 *     `structural-component-registration-discoverability` is an
 *     `info`-tier advisory: it surfaces components whose actual class
 *     roots / hook namespaces aren't covered by the override maps in
 *     `helpers/resolveHookPrefix.ts`, so contributors know to extend
 *     them as part of per-component uplift instead of letting other
 *     `customer-reach` rules silently underreport.
 *     The two bare-BEM rules
 *     (`structural-no-bare-bem-hook-writes`,
 *     `structural-no-bare-bem-paint-rules`) are also customer-reach:
 *     a hook write or paint declaration on a bare `.slds-foo_brand`
 *     modifier (without anchoring `.slds-foo.slds-foo_brand`) inherits
 *     down to every `.slds-foo` descendant of any wrapper carrying the
 *     class, walling the override path.
 *     The package-wide private-hook ban + the high-risk and partial-
 *     coverage mechanism-fit failures (whose recommended fix moves a hook
 *     out of the public contract) also live here.
 *   - **`cascade-hygiene` (3):** advisory mechanism-fit cases that
 *     protect the system's own painting vocabulary against misuse.
 *     `review` severity — the harm is conditional (an advisory
 *     mechanism-fit case reads as "the hook works today but consider
 *     demoting it"), not blocking. Includes `element-color-state-coverage`.
 *   - **`hook-surface` (10):** hook-API checks derived from themeData.
 *     Naming rules, base-hook shape, dead/orphan hooks, etc.
 *   - **`regression` (1):** theme-regression check from the static
 *     CSS-diff audit. Gates the task on default-visual stability.
 *   - **`lbc-alignment` (1):** records observed drift between the
 *     design-system-2 hook surface and a `lightning-*` LBC element's
 *     embedded CSS. `review` severity. Scoped narrowly to LBC by
 *     name: integration-risk surface area is a single, well-defined
 *     consumer category and the category should read that way in the
 *     UI. Reads
 *     `extensions['com.salesforce-ux.lbc-contract']` from the
 *     component's system UIF; passes when no contract is recorded or
 *     the recorded lists are empty.
 *
 * Checks that don't have their data source available (missing themeData,
 * missing regression report, missing source files) report `info` instead
 * of erroring — the UI shows a "not run yet" row rather than a blank slot.
 */
 
import type { ComplianceCategory, ComplianceCheck, ComplianceRow, ComponentCheckInput } from '../types.js';
 
import { baseHookShape } from './base-hook-shape.js';
import { elementColorStateCoverage } from './element-color-state-coverage.js';
import {
  hookHighRiskBaseContractShape,
  hookHighRiskEmptyCoverage,
  hookHighRiskPartialCoverage,
  hookMediumRiskEmptyCoverage,
  hookMediumRiskPartialCoverage,
  hookMediumRiskPartialLowDemand,
} from './hook-mechanism-fit.js';
import { hookNamesSpecCompliant } from './hook-names-spec-compliant.js';
import { borderColorFallbackTransparent } from './border-color-fallback-transparent.js';
import { noInventedStateModifierHooks } from './no-invented-state-modifier-hooks.js';
import { noSizingScaleModifierHooks } from './no-sizing-scale-modifier-hooks.js';
import { noDescendantOwnedColorHooks } from './no-descendant-owned-color-hooks.js';
import { noOrphanHooks } from './no-orphan-hooks.js';
import { baseHookAssignmentsReferenceOpenedHooks } from './base-hook-assignments-reference-opened-hooks.js';
import { hooksMissingFromAllThemes } from './hooks-missing-from-all-themes.js';
import { sharedHookRouting } from './shared-hook-routing.js';
import { noUnintendedVisualChanges } from './no-unintended-visual-changes.js';
import { lbcDesignSystemAlignment } from './lbc-design-system-alignment.js';
 
import { noRootComponentHooks } from './structural/no-root-component-hooks.js';
import { layerPlacement } from './structural/layer-placement.js';
import { noPseudoStateHookWrites } from './structural/no-pseudo-state-hook-writes.js';
import { noDescendantHookWrites } from './structural/no-descendant-hook-writes.js';
import { noBemElementHookWrites } from './structural/no-bem-element-hook-writes.js';
import { noAttributeSubstringHookWrites } from './structural/no-attribute-substring-hook-writes.js';
import { noPrivateHooks } from './structural/no-private-hooks.js';
import { noSharedToGlobalFallback } from './structural/no-shared-to-global-fallback.js';
import { themeBaseReadsCHooksOnly } from './structural/theme-base-reads-c-hooks-only.js';
import { noBareBemHookWrites } from './structural/no-bare-bem-hook-writes.js';
import { noBareBemPaintRules } from './structural/no-bare-bem-paint-rules.js';
import { noBaseThemePaintCollisions } from './structural/no-base-theme-paint-collisions.js';
import { spacingDirectionSyntax } from './structural/spacing-direction-syntax.js';
import { spacingAxisOnlyAssignment } from './structural/spacing-axis-only-assignment.js';
import { requiredThemesPresent } from './structural/required-themes-present.js';
import { componentRegistrationDiscoverability } from './structural/component-registration-discoverability.js';
 
/**
 * Canonical evaluation order:
 *   1. Structural source rules (read raw CSS; the foundation)
 *   2. Hook-surface rules (read themeData; the API shape)
 *   3. Theme-regression rules (read the diff; the migration gate)
 *
 * The Task Panel, Storybook addon, and CLI all consume this order.
 */
export const checks: readonly ComplianceCheck[] = [
  noRootComponentHooks,
  layerPlacement,
  noPseudoStateHookWrites,
  noDescendantHookWrites,
  noBemElementHookWrites,
  noAttributeSubstringHookWrites,
  noPrivateHooks,
  noSharedToGlobalFallback,
  themeBaseReadsCHooksOnly,
  noBareBemHookWrites,
  noBareBemPaintRules,
  noBaseThemePaintCollisions,
  spacingDirectionSyntax,
  spacingAxisOnlyAssignment,
  requiredThemesPresent,
  componentRegistrationDiscoverability,
  baseHookShape,
  elementColorStateCoverage,
  hookHighRiskPartialCoverage,
  hookHighRiskEmptyCoverage,
  hookHighRiskBaseContractShape,
  hookMediumRiskPartialLowDemand,
  hookMediumRiskPartialCoverage,
  hookMediumRiskEmptyCoverage,
  hookNamesSpecCompliant,
  borderColorFallbackTransparent,
  noInventedStateModifierHooks,
  noSizingScaleModifierHooks,
  noDescendantOwnedColorHooks,
  noOrphanHooks,
  baseHookAssignmentsReferenceOpenedHooks,
  hooksMissingFromAllThemes,
  sharedHookRouting,
  noUnintendedVisualChanges,
  lbcDesignSystemAlignment,
];
 
/**
 * Default category for any row whose source check doesn't self-stamp.
 * Hook-surface and regression checks construct rows directly without
 * going through the shared structural helpers, so this map ensures
 * every row in the report carries a category. Structural checks already
 * self-stamp via `failRow` / `reviewRow` / `passRow` / `notRunYetRow` and
 * appear here only for completeness; their explicit value wins.
 */
const DEFAULT_CATEGORY_BY_ID: Record<string, ComplianceCategory> = {
  // Structural — customer-reach.
  'structural-no-root-component-hooks': 'customer-reach',
  'structural-layer-placement': 'customer-reach',
  'structural-no-pseudo-state-hook-writes': 'customer-reach',
  'structural-no-descendant-hook-writes': 'customer-reach',
  'structural-no-bem-element-hook-writes': 'customer-reach',
  'structural-no-attribute-substring-hook-writes': 'customer-reach',
  'structural-no-private-hooks': 'customer-reach',
  'structural-no-shared-to-global-fallback': 'customer-reach',
  'structural-theme-base-reads-c-hooks-only': 'customer-reach',
  // Structural — bare-BEM rules promoted to customer-reach: a hook
  // write or paint declaration on a bare `.slds-foo_brand` modifier
  // (without the `.slds-foo.slds-foo_brand` anchor) inherits down to
  // every `.slds-foo` descendant of any wrapper carrying the class,
  // breaking the customer override path the same way the other
  // customer-reach rules do.
  'structural-no-bare-bem-hook-writes': 'customer-reach',
  'structural-no-bare-bem-paint-rules': 'customer-reach',
  'structural-no-base-theme-paint-collisions': 'customer-reach',
  'structural-spacing-direction-syntax': 'customer-reach',
  'structural-spacing-axis-only-assignment': 'customer-reach',
  'structural-required-themes-present': 'customer-reach',
  // Discoverability advisory: surfaces unregistered BEM roots / hook
  // prefixes so contributors know to extend the override maps in
  // `helpers/resolveHookPrefix.ts`. Lives in `customer-reach` because
  // unregistered components silently bypass customer-reach rules.
  'structural-component-registration-discoverability': 'customer-reach',
  // Hook-surface tier.
  'base-hook-shape': 'hook-surface',
  'element-color-state-coverage': 'cascade-hygiene',
  'hook-high-risk-partial-coverage': 'customer-reach',
  'hook-high-risk-empty-coverage': 'customer-reach',
  'hook-high-risk-base-contract-shape': 'cascade-hygiene',
  'hook-medium-risk-partial-low-demand': 'customer-reach',
  'hook-medium-risk-partial-coverage': 'customer-reach',
  'hook-medium-risk-empty-coverage': 'cascade-hygiene',
  'hook-names-spec-compliant': 'hook-surface',
  'border-color-fallback-transparent': 'hook-surface',
  'no-invented-state-modifier-hooks': 'hook-surface',
  'no-sizing-scale-modifier-hooks': 'hook-surface',
  'no-descendant-owned-color-hooks': 'hook-surface',
  'no-orphan-hooks': 'hook-surface',
  'base-hook-assignments-reference-opened-hooks': 'hook-surface',
  'hooks-missing-from-all-themes': 'hook-surface',
  'shared-hook-routing': 'hook-surface',
  // Regression tier.
  'no-unintended-visual-changes': 'regression',
  // LBC-alignment tier.
  'lbc-design-system-alignment': 'lbc-alignment',
};
 
function withCategory(row: ComplianceRow): ComplianceRow {
  if (row.category) return row;
  const category = DEFAULT_CATEGORY_BY_ID[row.id];
  Iif (!category) return row;
  return { ...row, category };
}
 
/** Run every registered check against a single component. */
export function runChecks(input: ComponentCheckInput): ComplianceRow[] {
  return checks.map((check) => withCategory(check(input)));
}
 
// Individual checks are imported by name from their per-file modules
// (`__tests__/checks/*.test.ts` → `../../src/checks/structural/<name>.js`).
// The `runChecks` + `checks` exports above are the only registry surface
// consumers go through; per-check exports here used to also exist but
// were never imported via this barrel.
 
export * from './helpers/index.js';