Skip to content

@stylexjs/eslint-plugin More options for propLimits #1893

Description

@ysulyma

Describe the feature request

It would be helpful to have more options for propLimits, in order to disallow certain literal values and instead require using design tokens defined with stylex.defineVars() or stylex.defineConsts(). Here is a strawman proposal.

Example

// eslint.config.js
export default [
  {
    ignores: ["dist/**"],
  },
  {
    files: ["src/**/*.{ts,tsx}"],
    plugins: {
      "@stylexjs": stylexPlugin,
    },
    rules: {
      "@stylexjs/valid-styles": [
        "error",
        {
          propLimits: {
            backgroundColor: {
              limit: {
                variable: true,
              },
              reason: "must pass a StyleX variable",
            },
            borderColor: {
              limit: {
                variable: {
                  name: ["colors", "scales"],
                },
              },
              reason:
                "must pass one of the StyleX variables `colors` or `scales`",
            },
            color: {
              limit: {
                variable: {
                  from: "./src/lib/tokens.stylex.ts",
                  name: ["colors", "scales"],
                },
              },
              reason:
                "must pass one of the StyleX variables `colors` or `scales`",
            },
            marginLeft: {
              // array for unions of acceptable values, similar to existing behavior
              limit: [
                "auto",
                {
                  variable: true,
                },
              ],
              reason:
                "must pass a StyleX variable, or the literal string 'auto'",
            },
            outlineColor: {
              limit: [
                { null: true },
                {
                  variable: {
                    from: "./src/lib/tokens.stylex.ts",
                    name: ["colors"],
                    property: ["destructive", "success"],
                  },
                },
              ],
              reason:
                "must pass one of the StyleX variables `colors` or `scales`",
            },
            paddingLeft: {
              limit: {
                cssVar: true,
              },
              reason: "must pass a CSS variable",
            },
            paddingRight: {
              limit: {
                cssVar: ["--padding-sm", "--padding-lg"],
              },
              reason:
                "must pass one of the CSS variables `--padding-sm` or `--padding-lg`",
            },
            zIndex: {
              limit: [
                {
                  const: {
                    from: "./src/lib/tokens.stylex.ts",

                    // name can be a string if there is only one
                    name: "layers",
                  },
                },
                {
                  calc: true,
                },
              ],
              reason: "must pass a `layers.*` constant from #/lib/tokens.stylex.ts, or a calc() expression",
            },
          },
        },
      ],
    },
  },
];

// component.tsx
import * as stylex from "@stylexjs/stylex";

import { colors, scales as goodScales, spacing } from "#/lib/tokens.stylex.ts";
import { scales as evilScales } from "#/lib/tokens-evil.stylex.ts";

const styles = stylex.create({
  invalid: {
    // invalid: must use a StyleX variable
    backgroundColor: "red",

    // invalid: must use a StyleX variable from the `colors` or `scales` object
    borderColor: spacing.medium,

    // invalid: must use a StyleX variable from the `colors` or `scales` object, and only from `lib/tokens/stylex.ts`
    color: evilScales.blue500,

    // invalid: must use a StyleX variable, or the literal string 'auto'
    marginLeft: "1em",

    // invalid: can only use `colors.destructive` or `colors.success` for outlineColor
    outlineColor: colors.blue,

    // invalid: must pass a CSS variable
    paddingLeft: spacing.medium,

    // invalid: must pass one of the CSS variables `--padding-sm` or `--padding-lg`
    paddingRight: "var(--padding)",

    // invalid: must pass a StyleX `layers.*` constant or a calc() expression
    zIndex: 1,
  },

  valid: {
    // semantically wrong but valid according to above configuration
    backgroundColor: spacing.md,

    borderColor: colors.blue500,

    // validation is against the original export name, not the import name
    color: goodScales.blue500,

    // literal value "auto" explicitly allowed
    marginLeft: "auto",

    outlineColor: {
      ":focus": colors.success,

      // this is why we needed {null: true} in the config
      default: null,
    },
    paddingLeft: "var(--padding)",
    paddingRight: "var(--padding-sm)",
    zIndex: "calc(var(--dialog-level) * 10)",
  },
});

Specification

Variables

{ variable: true } means a variable defined with stylex.defineVars() must be used.

Passing an object allows further constraining the variable:

  • variable.name allows constraining the exported name of the variable. It can be a single string, or an array of strings.
    Potentially could allow globs like pink* to include pink100 through pink900, but this is problematic because a perverse person could use ["pink*"] as a JavaScript key.

  • variable.property allows constraining which property on the variable is used

  • variable.from allows constraining the file the variable is imported from. This is specified by the path relative to the ESLint config file; it is validated after resolving subpath imports and import aliases

Constants

{const: true} and {const: { name: "colors" }} behaves the same as variables, but for stylex.defineConsts() instead of stylex.defineVars().

CSS variables

{ cssVar: true } allows var() expressions. Passing an object allows constraining the name of the variable.

CSS calculations

{calc: true} allows calc() expressions. In this strawman proposal it does not accept further customization.

null

{ null: true } allows null as a value (e.g. for default case in nested blocks)

Remarks

This proposal is really several individual features; for my current use case I'd be quite happy with just being able to do [{null: true}, {variable: true}].

In the current implementation, limit: null is equivalent to limit: {variable: true} since variable values are not validated at all; however, this is causing problems with default: null in nested configs. I am getting around this by doing

export const nullHack = stylex.defineVars({
  null: null,
});

which is awkward.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions