Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/mosaic-native-tooling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@clerk/mosaic": patch
---

- Fixed an issue where an outgoing Flow step could show content from an update that never appeared on screen.
- Fixed an issue where adding a tag while a controlled TagInput update was still pending could use the pending tag list instead of the list shown on screen.
151 changes: 2 additions & 149 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import fs from 'node:fs';
import path from 'node:path';

import pluginEslintComments from '@eslint-community/eslint-plugin-eslint-comments';
import eslint from '@eslint/js';
import configPrettier from 'eslint-config-prettier';
import configTurbo from 'eslint-config-turbo/flat';
Expand All @@ -11,9 +10,7 @@ import pluginJsxA11y from 'eslint-plugin-jsx-a11y';
import pluginPlaywright from 'eslint-plugin-playwright';
import pluginReact from 'eslint-plugin-react';
import pluginReactHooks from 'eslint-plugin-react-hooks';
import pluginStylex from '@stylexjs/eslint-plugin';
import pluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
import pluginSonarjs from 'eslint-plugin-sonarjs';
import pluginTurbo from 'eslint-plugin-turbo';
import pluginUnusedImports from 'eslint-plugin-unused-imports';
import pluginYml from 'eslint-plugin-yml';
Expand Down Expand Up @@ -312,12 +309,6 @@ const noPhysicalCssProperties = {
},
};

const noLeakedConditionalJsx = {
selector: ":matches(JSXElement, JSXFragment) > JSXExpressionContainer > LogicalExpression[operator='&&']",
message:
'Use a ternary (`condition ? <Element /> : null`) for conditional JSX; `&&` renders a falsy left-hand value such as `0`.',
};

export default tseslint.config([
{
name: 'repo/ignores',
Expand All @@ -343,6 +334,8 @@ export default tseslint.config([
'eslint.config.mjs',
'typedoc.config.mjs',
'vitest.workspace.mjs',
'packages/mosaic/**',
'packages/swingset/**',
// package specific ignores
'packages/astro/src/astro-components/**/*.ts',
'packages/backend/src/runtime/**/*',
Expand Down Expand Up @@ -615,134 +608,6 @@ export default tseslint.config([
'custom-rules/no-unstable-methods': 'error',
},
},
{
name: 'packages/mosaic',
files: ['packages/mosaic/src/**/*'],
// Tests assert on style values they receive; they are not authoring styles.
ignores: ['packages/mosaic/src/__tests__/**', 'packages/mosaic/src/**/*.test.{ts,tsx}'],
plugins: {
'@eslint-community/eslint-comments': pluginEslintComments,
'@stylexjs': pluginStylex,
sonarjs: pluginSonarjs,
},
rules: {
'@eslint-community/eslint-comments/require-description': ['error', { ignore: ['eslint-enable'] }],
'sonarjs/cognitive-complexity': ['error', 15],
'sonarjs/no-collapsible-if': 'error',
'sonarjs/no-identical-functions': 'error',
'sonarjs/no-redundant-jump': 'error',
'sonarjs/prefer-single-boolean-return': 'error',
'@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'never' }],
'@typescript-eslint/switch-exhaustiveness-check': 'error',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/ban-ts-comment': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/no-unsafe-argument': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
'@typescript-eslint/no-unsafe-call': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
'@typescript-eslint/only-throw-error': 'error',
'@typescript-eslint/prefer-promise-reject-errors': 'error',
'@typescript-eslint/use-unknown-in-catch-callback-variable': 'error',
'react-hooks/exhaustive-deps': 'error',
'react-hooks/rules-of-hooks': 'error',
'max-lines': ['error', { max: 1000, skipBlankLines: true, skipComments: true }],
'no-restricted-properties': [
'error',
{ object: 'Reflect', property: 'get', message: 'Use typed property access or parse at the boundary.' },
{ object: 'Reflect', property: 'apply', message: 'Call the function directly.' },
],
'@stylexjs/enforce-extension': 'error',
'@stylexjs/no-legacy-contextual-styles': 'error',
'@stylexjs/no-lookahead-selectors': 'error',
'@stylexjs/no-nonstandard-styles': 'error',
'@stylexjs/no-conflicting-props': 'error',
'@stylexjs/no-unused': 'error',
'@stylexjs/sort-keys': 'error',
'@stylexjs/valid-shorthands': 'error',
'@stylexjs/valid-styles': 'error',
'no-restricted-syntax': [
'error',
{
selector: "Property > Literal.key[value='&:hover']",
message: "Use hover() from mosaic/utils instead of bare '&:hover'.",
},
noLeakedConditionalJsx,
{
selector: "JSXOpeningElement[name.name='form']",
message: 'Use `Form` from mosaic/primitives/form, which turns off the browser validation popup.',
},
{
selector: "Property > Literal.key[value='@media (hover: hover)']",
message: "Use hover() from mosaic/utils instead of raw '@media (hover: hover)'.",
},
{
selector: "Property > Literal.key[value='@media (prefers-reduced-motion: no-preference)']",
message:
"Use motionSafe() from mosaic/utils instead of raw '@media (prefers-reduced-motion: no-preference)'.",
},
// A Mosaic part takes `xstyle`, not `className`/`style`; its prop types already reject the pair,
// this names the replacement. Native elements (lowercase) are unaffected.
{
selector:
"JSXOpeningElement[name.type='JSXIdentifier'][name.name=/^[A-Z]/] > JSXAttribute[name.name=/^(className|style)$/]",
message: 'Mosaic parts take `xstyle` (StyleX atoms) instead of `className`/`style`.',
},
{
selector:
"JSXOpeningElement[name.type='JSXMemberExpression'] > JSXAttribute[name.name=/^(className|style)$/]",
message: 'Mosaic parts take `xstyle` (StyleX atoms) instead of `className`/`style`.',
},
// `{...stylex.props(x)}` is the same pair by another route; the types miss it because a
// spread's optional keys skip excess-property checks.
{
selector:
"JSXOpeningElement[name.type='JSXIdentifier'][name.name=/^[A-Z]/] > JSXSpreadAttribute > CallExpression[callee.object.name='stylex'][callee.property.name='props']",
message: 'Mosaic parts take the atoms as `xstyle`, not a `stylex.props(...)` spread.',
},
{
selector:
"JSXOpeningElement[name.type='JSXMemberExpression'] > JSXSpreadAttribute > CallExpression[callee.object.name='stylex'][callee.property.name='props']",
message: 'Mosaic parts take the atoms as `xstyle`, not a `stylex.props(...)` spread.',
},
],
},
},
{
name: 'packages/mosaic/jsx-a11y',
files: ['packages/mosaic/src/**/*'],
rules: {
// Mosaic renders elements through `render={p => <el {...p} />}`, so children and controls sit on
// the outer component. Both rules only see the empty inner element and always report.
'jsx-a11y/heading-has-content': 'off',
'jsx-a11y/label-has-associated-control': 'off',
},
},
{
// StyleX `create()` files author conditions raw (`@media (hover: hover)`, `:hover`) — StyleX
// is compile-time and cannot inline a `hover()`/`motionSafe()` helper imported into `create`,
// so the media-query restrictions above (an Emotion-runtime convention) can't apply here. The
// `@stylexjs/*` rules from the mosaic block still cover these files.
name: 'packages/mosaic - stylex styles',
files: ['packages/mosaic/src/**/*.styles.ts'],
rules: {
'no-restricted-syntax': 'off',
},
},
{
// Primitives are unstyled and take `className`/`style` like any other headless component; the
// `xstyle`-only rule targets styled Mosaic parts and otherwise false-positives on things like
// `<FloatingOverlay style={...}>`, a third-party component, not a Mosaic part.
name: 'packages/mosaic - primitives',
files: ['packages/mosaic/src/primitives/**/*'],
ignores: ['packages/mosaic/src/**/*.test.{ts,tsx}'],
rules: {
'no-restricted-syntax': ['error', noLeakedConditionalJsx],
},
},
{
name: 'packages - vitest',
files: ['packages/*/src/**/*.test.{ts,tsx}'],
Expand Down Expand Up @@ -819,18 +684,6 @@ export default tseslint.config([
],
},
},
{
name: 'packages/swingset',
files: ['packages/swingset/src/**/*'],
rules: {
'turbo/no-undeclared-env-vars': [
'error',
{
allowList: ['SWINGSET_DASHBOARD_URL'],
},
],
},
},
{
name: 'packages/upgrade',
files: ['packages/upgrade/src/**/*'],
Expand Down
Loading
Loading