cloudflare/kumo

Public

mirrored fromhttps://github.com/cloudflare/kumoAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
9fbf3a830bde68cc1da790676dc967d1f731fe04

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

lint/enforce-variant-standard.js

186lines · modepreview

import { defineRule } from "oxlint";

/**
 * Enforces the Kumo variant export standard for components:
 * - KUMO_{COMPONENT}_VARIANTS (required)
 * - KUMO_{COMPONENT}_DEFAULT_VARIANTS (required)
 * - KUMO_{COMPONENT}_BASE_STYLES (optional, but must have KUMO_ prefix)
 *
 * Only applies to files in src/components/**\/*.tsx
 */

/**
 * Extract component name from file path.
 * Example: "src/components/button/button.tsx" -> "BUTTON"
 */
function getComponentNameFromPath(filename) {
  const match = filename.match(/src\/components\/([^/]+)\/\1\.tsx$/);
  if (!match) return null;
  return match[1].toUpperCase().replace(/-/g, "_");
}

/**
 * Check if export name matches expected pattern.
 * Returns { valid: boolean, expectedName?: string, exportType?: string }
 */
function validateExportName(exportName, componentName) {
  const variantsPattern = `KUMO_${componentName}_VARIANTS`;
  const defaultVariantsPattern = `KUMO_${componentName}_DEFAULT_VARIANTS`;
  const baseStylesPattern = `KUMO_${componentName}_BASE_STYLES`;

  if (exportName === variantsPattern) {
    return { valid: true, exportType: "VARIANTS" };
  }
  if (exportName === defaultVariantsPattern) {
    return { valid: true, exportType: "DEFAULT_VARIANTS" };
  }
  if (exportName === baseStylesPattern) {
    return { valid: true, exportType: "BASE_STYLES" };
  }

  // Check for incorrect naming patterns
  if (exportName.endsWith("_VARIANTS") && exportName.startsWith("KUMO_")) {
    return {
      valid: false,
      expectedName: variantsPattern,
      exportType: "VARIANTS",
    };
  }
  if (
    exportName.endsWith("_DEFAULT_VARIANTS") &&
    exportName.startsWith("KUMO_")
  ) {
    return {
      valid: false,
      expectedName: defaultVariantsPattern,
      exportType: "DEFAULT_VARIANTS",
    };
  }
  if (exportName.endsWith("_BASE_STYLES")) {
    // BASE_STYLES must have KUMO_ prefix
    if (!exportName.startsWith("KUMO_")) {
      return {
        valid: false,
        expectedName: baseStylesPattern,
        exportType: "BASE_STYLES",
      };
    }
    // Wrong component name
    return {
      valid: false,
      expectedName: baseStylesPattern,
      exportType: "BASE_STYLES",
    };
  }

  return { valid: true }; // Not a variant-related export
}

export const enforceVariantStandardRule = defineRule({
  meta: {
    type: "problem",
    docs: {
      description:
        "Enforce Kumo variant standard: KUMO_{COMPONENT}_VARIANTS, KUMO_{COMPONENT}_DEFAULT_VARIANTS, and optionally KUMO_{COMPONENT}_BASE_STYLES",
    },
    messages: {
      incorrectName:
        "Export name '{{actual}}' should be '{{expected}}' to follow Kumo variant naming convention",
      missingVariants:
        "Component must export KUMO_{{component}}_VARIANTS. Found: {{found}}",
      missingDefaultVariants:
        "Component must export KUMO_{{component}}_DEFAULT_VARIANTS. Found: {{found}}",
    },
    schema: [],
  },
  defaultOptions: [],
  createOnce(context) {
    const foundExports = new Set();
    let programNode = null;
    let filename = null;
    let componentName = null;
    let shouldCheck = false;

    return {
      Program(node) {
        programNode = node;
        filename = context.filename;

        // Only apply to component files in src/components/**/*.tsx
        if (!filename.match(/src\/components\/[^/]+\/[^/]+\.tsx$/)) {
          shouldCheck = false;
          return;
        }

        componentName = getComponentNameFromPath(filename);
        shouldCheck = componentName !== null;
      },
      ExportNamedDeclaration(node) {
        if (!shouldCheck) return;

        // Check for named const exports
        if (
          node.declaration &&
          node.declaration.type === "VariableDeclaration"
        ) {
          for (const decl of node.declaration.declarations) {
            if (decl.id && decl.id.type === "Identifier") {
              const exportName = decl.id.name;
              foundExports.add(exportName);

              const validation = validateExportName(exportName, componentName);
              if (!validation.valid && validation.expectedName) {
                context.report({
                  node: decl.id,
                  messageId: "incorrectName",
                  data: {
                    actual: exportName,
                    expected: validation.expectedName,
                  },
                });
              }
            }
          }
        }
      },
      "Program:exit"() {
        if (!shouldCheck) return;

        const expectedVariants = `KUMO_${componentName}_VARIANTS`;
        const expectedDefaultVariants = `KUMO_${componentName}_DEFAULT_VARIANTS`;

        // Check for required exports at end of file
        const hasVariants = foundExports.has(expectedVariants);
        const hasDefaultVariants = foundExports.has(expectedDefaultVariants);

        if (!hasVariants) {
          context.report({
            node: programNode,
            messageId: "missingVariants",
            data: {
              component: componentName,
              found:
                Array.from(foundExports)
                  .filter((e) => e.includes("VARIANT"))
                  .join(", ") || "none",
            },
          });
        }

        if (!hasDefaultVariants) {
          context.report({
            node: programNode,
            messageId: "missingDefaultVariants",
            data: {
              component: componentName,
              found:
                Array.from(foundExports)
                  .filter((e) => e.includes("VARIANT"))
                  .join(", ") || "none",
            },
          });
        }
      },
    };
  },
});