@ashstack/lint 0.10.0 → 0.10.1
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.
- package/README.md +1 -1
- package/dist/lib/registry.js +1 -1
- package/dist/lib/rule-types/react.d.ts +10 -2
- package/dist/lib/stylex-plugin.js +1 -1
- package/dist/react/rules/stylex/index.js +1 -1
- package/dist/react/rules/stylex/inline-props.js +1 -1
- package/dist/react/rules/stylex/no-conflicting-props.js +1 -0
- package/dist/react/rules/stylex/no-duplicate-styles.js +1 -1
- package/dist/react/rules/stylex/require-tokens.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ashstack/lint
|
|
2
2
|
|
|
3
|
-
Strict shared [oxlint](https://oxc.rs) config: <!-- rule-counts -->116 rules on plain TypeScript, 210 with React, 267 on React Native,
|
|
3
|
+
Strict shared [oxlint](https://oxc.rs) config: <!-- rule-counts -->116 rules on plain TypeScript, 210 with React, 267 on React Native, 81 of them custom-built<!-- /rule-counts -->. Three entries, each containing the one before it: `core()`, `react()`, `react-native()`.
|
|
4
4
|
|
|
5
5
|
Library-specific rules ship only when you depend on that library, so nothing fires about code you don't have.
|
|
6
6
|
|
package/dist/lib/registry.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=(t)=>new URL(t,import.meta.url).href;export const coreRegistry=[{meta:{name:"@ashstack/core"},url:e("../core/rules/core/index.js"),rules:{"no-comments":{},"no-naming-convention":{},"no-packed-condition":{},"max-lines":{},"max-complexity":{}}},{meta:{name:"@ashstack/zod"},url:e("../core/rules/zod/index.js"),option:"zod",packages:["zod"],rules:{"prefer-enum":{}}}],reactRegistry=[{meta:{name:"@ashstack/react"},url:e("../react/rules/react/index.js"),rules:{"no-unlabeled-icon-button":{},"no-svg-without-title":{},"hoist-intl":{},"prefer-design-system":{defaultOff:!0},"components-tsx-only":{defaultOff:!0}}},{meta:{name:"@ashstack/query"},url:e("../react/rules/query/index.js"),option:"query",packages:["@tanstack/react-query"],rules:{"no-inline-keys":{},"no-deprecated-filters":{},"require-destructured-hooks":{},"no-fetch-in-query-fn":{},"next-page-param-undefined":{}}},{meta:{name:"@ashstack/zustand"},url:e("../react/rules/zustand/index.js"),option:"zustand",packages:["zustand"],rules:{"require-selector":{}}},{meta:{name:"@ashstack/i18n"},url:e("../react/rules/i18n/index.js"),option:"i18n",packages:["i18next","react-i18next","@lingui/core","react-intl","use-intl","next-intl","expo-localization"],rules:{"no-bare-text":{},"no-bare-attrs":{},"no-bare-toast":{}}},{meta:{name:"@ashstack/stylex"},url:e("../react/rules/stylex/index.js"),option:"stylex",packages:["@stylexjs/stylex"],rules:{"inline-props":{},"no-duplicate-styles":{},"require-tokens":{}}},{meta:{name:"@ashstack/tailwind"},url:e("../react/rules/tailwind/index.js"),option:"tailwind",packages:["tailwindcss"],rules:{"prefer-cn":{},"use-logical-classes":{}}},{meta:{name:"@ashstack/tanstack-router"},url:e("../react/rules/tanstack-router/index.js"),option:"tanstackRouter",packages:["@tanstack/react-router"],rules:{"require-selector":{},"no-search-casts":{}}}],reactNativeRegistry=[{meta:{name:"@ashstack/react-native"},url:e("../react-native/rules/react-native/index.js"),restrictedImports:{paths:[{name:"react",importNames:["forwardRef"],message:"Accept `ref` as a regular prop and delete the `forwardRef` wrapper — React 19 passes `ref` like any other prop."},{name:"react",importNames:["useContext"],message:"Call `use(Context)` instead of `useContext(Context)`; React 19 replaces the hook with it."}],patterns:[{group:["expo-linear-gradient","react-native-linear-gradient"],message:"Apply the gradient through the `experimental_backgroundImage` style property instead of a gradient component."}]},rules:{"no-keyboard-will-events":{},"no-scroll-position-state":{},"no-conditional-style-array":{},"no-leaked-render":{},"no-rn-image-network-source":{},"no-redundant-view-nesting":{},"no-rn-namespace-import":{},"no-unlabeled-icon-pressable":{},"hoist-stateless-function":{},"no-manual-memo":{},"no-dynamic-import":{}}},{meta:{name:"@ashstack/unistyles"},url:e("../react-native/rules/unistyles/index.js"),option:"unistyles",packages:["react-native-unistyles"],restrictedImports:{paths:[{name:"react-native",importNames:["StyleSheet"],message:"Import `StyleSheet` from `react-native-unistyles` instead of `react-native`."},{name:"react-native",importNames:["useWindowDimensions","Dimensions"],message:"Read screen size from `rt.screen` inside `StyleSheet.create`, or `UnistylesRuntime.screen` outside it, instead of the `Dimensions` APIs."},{name:"react-native",importNames:["SafeAreaView"],message:'Set `contentInsetAdjustmentBehavior="automatic"` on the scroll view, or apply `rt.insets` from `react-native-unistyles`, instead of `SafeAreaView`.'},{name:"react-native-safe-area-context",importNames:["useSafeAreaInsets","useSafeAreaFrame","SafeAreaView"],message:"Read safe-area values from `rt.insets`, or `UnistylesRuntime.insets` outside a stylesheet, in `react-native-unistyles`."}]},rules:{"animated-theme":{},"content-container":{},"in-sheet":{},insets:{},"no-hardcoded-color":{},"no-hardcoded-spacing":{},"no-margin":{},"no-paramless-dynamic-function":{},"no-style-spread":{},"no-unused-styles":{},"rtl-style-call":{},"theme-screen-component":{},"theme-style-attr":{}}},{meta:{name:"@ashstack/legend-list"},url:e("../react-native/rules/legend-list/index.js"),option:"legendList",packages:["@legendapp/list"],restrictedImports:{paths:[{name:"react-native",importNames:["FlatList","SectionList","VirtualizedList"],message:"Render this list with `LegendList` from `@legendapp/list/react-native`; a `ScrollView` plus `map` is fine for fewer than 20 static items."},{name:"@shopify/flash-list",message:"Import `LegendList` from `@legendapp/list/react-native` — Legend List replaced FlashList in this stack."}],patterns:[{group:["@legendapp/list"],message:"Import from the platform entrypoint `@legendapp/list/react-native`, not the package root."}]},rules:{"required-props":{},"no-index-key-extractor":{},"no-remount-key":{},"no-inline-render-item-props":{},"no-mixed-children":{},"no-flex-in-content-container":{},"typed-items-need-item-type":{},"no-scrollview-map":{},"no-unsupported-props":{}}},{meta:{name:"@ashstack/legend-state"},url:e("../react-native/rules/legend-state/index.js"),option:"legendState",packages:["@legendapp/state"],restrictedImports:{paths:[{name:"@legendapp/state/react",importNames:["use$","useSelector"],message:"Use `useValue` from `@legendapp/state/react`; `use$` and `useSelector` are deprecated."}]},rules:{"no-assignment":{},naming:{},"no-nested-observable":{},"no-react-mirror":{},"no-untracked-get-in-jsx":{},"no-peek-in-selector":{},"no-object-selector":{},"no-observable-in-component":{}}},{meta:{name:"@ashstack/reanimated"},url:e("../react-native/rules/reanimated/index.js"),option:"reanimated",packages:["react-native-reanimated"],restrictedImports:{paths:[{name:"react-native",importNames:["Animated"],message:"Use `react-native-reanimated` instead of React Native's `Animated` API."},{name:"react-native-reanimated",importNames:["runOnJS","runOnUI"],message:"Use `scheduleOnRN` and `scheduleOnUI` from `react-native-worklets`; `runOnJS` and `runOnUI` are deprecated."},{name:"react-native-worklets",importNames:["runOnJS","runOnUI"],message:"Use `scheduleOnRN` and `scheduleOnUI` from `react-native-worklets`; `runOnJS` and `runOnUI` are deprecated."}]},rules:{"animated-reaction-safety":{},"animated-style-needs-animated-component":{},"animated-updater-purity":{},"gpu-properties-only":{},"hoist-layout-animation-builder":{},"interpolate-needs-clamp":{},"no-shared-value-dot-value":{},"no-react-state-from-continuous-worklet":{},"prefer-lazy-shared-value-initializer":{},"schedule-on-rn-scope":{},"shared-value-usage":{}}},{meta:{name:"@ashstack/turbo-image"},url:e("../react-native/rules/turbo-image/index.js"),option:"turboImage",packages:["react-native-turbo-image"],rules:{"require-resize":{},"require-cache-policy":{}}},{meta:{name:"@ashstack/skia"},url:e("../react-native/rules/skia/index.js"),option:"skia",packages:["@shopify/react-native-skia"],restrictedImports:{paths:[{name:"@shopify/react-native-skia",importNames:["usePathValue","usePathInterpolation"],message:"Keep one stable `SkPath` buffer and mutate it inside `useDerivedValue`, with consumers reading the driving shared value."}]},rules:{"canvas-opaque":{}}},{meta:{name:"@ashstack/keyboard"},url:e("../react-native/rules/keyboard/index.js"),option:"keyboard",packages:["react-native-keyboard-controller"],restrictedImports:{paths:[{name:"react-native",importNames:["KeyboardAvoidingView"],message:"Import `KeyboardAvoidingView` from `react-native-keyboard-controller` — same props, tracked per frame. For a scrolling form, its `KeyboardAwareScrollView`."}]},rules:{}}];
|
|
1
|
+
const e=(t)=>new URL(t,import.meta.url).href;export const coreRegistry=[{meta:{name:"@ashstack/core"},url:e("../core/rules/core/index.js"),rules:{"no-comments":{},"no-naming-convention":{},"no-packed-condition":{},"max-lines":{},"max-complexity":{}}},{meta:{name:"@ashstack/zod"},url:e("../core/rules/zod/index.js"),option:"zod",packages:["zod"],rules:{"prefer-enum":{}}}],reactRegistry=[{meta:{name:"@ashstack/react"},url:e("../react/rules/react/index.js"),rules:{"no-unlabeled-icon-button":{},"no-svg-without-title":{},"hoist-intl":{},"prefer-design-system":{defaultOff:!0},"components-tsx-only":{defaultOff:!0}}},{meta:{name:"@ashstack/query"},url:e("../react/rules/query/index.js"),option:"query",packages:["@tanstack/react-query"],rules:{"no-inline-keys":{},"no-deprecated-filters":{},"require-destructured-hooks":{},"no-fetch-in-query-fn":{},"next-page-param-undefined":{}}},{meta:{name:"@ashstack/zustand"},url:e("../react/rules/zustand/index.js"),option:"zustand",packages:["zustand"],rules:{"require-selector":{}}},{meta:{name:"@ashstack/i18n"},url:e("../react/rules/i18n/index.js"),option:"i18n",packages:["i18next","react-i18next","@lingui/core","react-intl","use-intl","next-intl","expo-localization"],rules:{"no-bare-text":{},"no-bare-attrs":{},"no-bare-toast":{}}},{meta:{name:"@ashstack/stylex"},url:e("../react/rules/stylex/index.js"),option:"stylex",packages:["@stylexjs/stylex"],rules:{"inline-props":{},"no-conflicting-props":{},"no-duplicate-styles":{},"require-tokens":{}}},{meta:{name:"@ashstack/tailwind"},url:e("../react/rules/tailwind/index.js"),option:"tailwind",packages:["tailwindcss"],rules:{"prefer-cn":{},"use-logical-classes":{}}},{meta:{name:"@ashstack/tanstack-router"},url:e("../react/rules/tanstack-router/index.js"),option:"tanstackRouter",packages:["@tanstack/react-router"],rules:{"require-selector":{},"no-search-casts":{}}}],reactNativeRegistry=[{meta:{name:"@ashstack/react-native"},url:e("../react-native/rules/react-native/index.js"),restrictedImports:{paths:[{name:"react",importNames:["forwardRef"],message:"Accept `ref` as a regular prop and delete the `forwardRef` wrapper — React 19 passes `ref` like any other prop."},{name:"react",importNames:["useContext"],message:"Call `use(Context)` instead of `useContext(Context)`; React 19 replaces the hook with it."}],patterns:[{group:["expo-linear-gradient","react-native-linear-gradient"],message:"Apply the gradient through the `experimental_backgroundImage` style property instead of a gradient component."}]},rules:{"no-keyboard-will-events":{},"no-scroll-position-state":{},"no-conditional-style-array":{},"no-leaked-render":{},"no-rn-image-network-source":{},"no-redundant-view-nesting":{},"no-rn-namespace-import":{},"no-unlabeled-icon-pressable":{},"hoist-stateless-function":{},"no-manual-memo":{},"no-dynamic-import":{}}},{meta:{name:"@ashstack/unistyles"},url:e("../react-native/rules/unistyles/index.js"),option:"unistyles",packages:["react-native-unistyles"],restrictedImports:{paths:[{name:"react-native",importNames:["StyleSheet"],message:"Import `StyleSheet` from `react-native-unistyles` instead of `react-native`."},{name:"react-native",importNames:["useWindowDimensions","Dimensions"],message:"Read screen size from `rt.screen` inside `StyleSheet.create`, or `UnistylesRuntime.screen` outside it, instead of the `Dimensions` APIs."},{name:"react-native",importNames:["SafeAreaView"],message:'Set `contentInsetAdjustmentBehavior="automatic"` on the scroll view, or apply `rt.insets` from `react-native-unistyles`, instead of `SafeAreaView`.'},{name:"react-native-safe-area-context",importNames:["useSafeAreaInsets","useSafeAreaFrame","SafeAreaView"],message:"Read safe-area values from `rt.insets`, or `UnistylesRuntime.insets` outside a stylesheet, in `react-native-unistyles`."}]},rules:{"animated-theme":{},"content-container":{},"in-sheet":{},insets:{},"no-hardcoded-color":{},"no-hardcoded-spacing":{},"no-margin":{},"no-paramless-dynamic-function":{},"no-style-spread":{},"no-unused-styles":{},"rtl-style-call":{},"theme-screen-component":{},"theme-style-attr":{}}},{meta:{name:"@ashstack/legend-list"},url:e("../react-native/rules/legend-list/index.js"),option:"legendList",packages:["@legendapp/list"],restrictedImports:{paths:[{name:"react-native",importNames:["FlatList","SectionList","VirtualizedList"],message:"Render this list with `LegendList` from `@legendapp/list/react-native`; a `ScrollView` plus `map` is fine for fewer than 20 static items."},{name:"@shopify/flash-list",message:"Import `LegendList` from `@legendapp/list/react-native` — Legend List replaced FlashList in this stack."}],patterns:[{group:["@legendapp/list"],message:"Import from the platform entrypoint `@legendapp/list/react-native`, not the package root."}]},rules:{"required-props":{},"no-index-key-extractor":{},"no-remount-key":{},"no-inline-render-item-props":{},"no-mixed-children":{},"no-flex-in-content-container":{},"typed-items-need-item-type":{},"no-scrollview-map":{},"no-unsupported-props":{}}},{meta:{name:"@ashstack/legend-state"},url:e("../react-native/rules/legend-state/index.js"),option:"legendState",packages:["@legendapp/state"],restrictedImports:{paths:[{name:"@legendapp/state/react",importNames:["use$","useSelector"],message:"Use `useValue` from `@legendapp/state/react`; `use$` and `useSelector` are deprecated."}]},rules:{"no-assignment":{},naming:{},"no-nested-observable":{},"no-react-mirror":{},"no-untracked-get-in-jsx":{},"no-peek-in-selector":{},"no-object-selector":{},"no-observable-in-component":{}}},{meta:{name:"@ashstack/reanimated"},url:e("../react-native/rules/reanimated/index.js"),option:"reanimated",packages:["react-native-reanimated"],restrictedImports:{paths:[{name:"react-native",importNames:["Animated"],message:"Use `react-native-reanimated` instead of React Native's `Animated` API."},{name:"react-native-reanimated",importNames:["runOnJS","runOnUI"],message:"Use `scheduleOnRN` and `scheduleOnUI` from `react-native-worklets`; `runOnJS` and `runOnUI` are deprecated."},{name:"react-native-worklets",importNames:["runOnJS","runOnUI"],message:"Use `scheduleOnRN` and `scheduleOnUI` from `react-native-worklets`; `runOnJS` and `runOnUI` are deprecated."}]},rules:{"animated-reaction-safety":{},"animated-style-needs-animated-component":{},"animated-updater-purity":{},"gpu-properties-only":{},"hoist-layout-animation-builder":{},"interpolate-needs-clamp":{},"no-shared-value-dot-value":{},"no-react-state-from-continuous-worklet":{},"prefer-lazy-shared-value-initializer":{},"schedule-on-rn-scope":{},"shared-value-usage":{}}},{meta:{name:"@ashstack/turbo-image"},url:e("../react-native/rules/turbo-image/index.js"),option:"turboImage",packages:["react-native-turbo-image"],rules:{"require-resize":{},"require-cache-policy":{}}},{meta:{name:"@ashstack/skia"},url:e("../react-native/rules/skia/index.js"),option:"skia",packages:["@shopify/react-native-skia"],restrictedImports:{paths:[{name:"@shopify/react-native-skia",importNames:["usePathValue","usePathInterpolation"],message:"Keep one stable `SkPath` buffer and mutate it inside `useDerivedValue`, with consumers reading the driving shared value."}]},rules:{"canvas-opaque":{}}},{meta:{name:"@ashstack/keyboard"},url:e("../react-native/rules/keyboard/index.js"),option:"keyboard",packages:["react-native-keyboard-controller"],restrictedImports:{paths:[{name:"react-native",importNames:["KeyboardAvoidingView"],message:"Import `KeyboardAvoidingView` from `react-native-keyboard-controller` — same props, tracked per frame. For a scrolling form, its `KeyboardAwareScrollView`."}]},rules:{}}];
|
|
@@ -2,7 +2,7 @@ import type { AllowWarnDeny } from "oxlint";
|
|
|
2
2
|
/** A severity, or a severity followed by as many of the rule's options as you want to set. */
|
|
3
3
|
type RuleSetting<Options extends unknown[] = []> = AllowWarnDeny | [AllowWarnDeny, ...Partial<Options>];
|
|
4
4
|
/** Every rule id `react()` adds on top of the entry below it. */
|
|
5
|
-
export type ReactRuleId = "@ashstack/react/no-unlabeled-icon-button" | "@ashstack/react/no-svg-without-title" | "@ashstack/react/hoist-intl" | "@ashstack/react/prefer-design-system" | "@ashstack/react/components-tsx-only" | "@ashstack/query/no-inline-keys" | "@ashstack/query/no-deprecated-filters" | "@ashstack/query/require-destructured-hooks" | "@ashstack/query/no-fetch-in-query-fn" | "@ashstack/query/next-page-param-undefined" | "@ashstack/zustand/require-selector" | "@ashstack/i18n/no-bare-text" | "@ashstack/i18n/no-bare-attrs" | "@ashstack/i18n/no-bare-toast" | "@ashstack/stylex/inline-props" | "@ashstack/stylex/no-duplicate-styles" | "@ashstack/stylex/require-tokens" | "@ashstack/tailwind/prefer-cn" | "@ashstack/tailwind/use-logical-classes" | "@ashstack/tanstack-router/require-selector" | "@ashstack/tanstack-router/no-search-casts" | "@ashstack/effects/no-derived-state" | "@ashstack/effects/no-chain-state-updates" | "@ashstack/effects/no-event-handler" | "@ashstack/effects/no-adjust-state-on-prop-change" | "@ashstack/effects/no-reset-all-state-on-prop-change" | "@ashstack/effects/no-pass-live-state-to-parent" | "@ashstack/effects/no-pass-data-to-parent" | "@ashstack/effects/no-external-store-subscription" | "@ashstack/effects/no-initialize-state";
|
|
5
|
+
export type ReactRuleId = "@ashstack/react/no-unlabeled-icon-button" | "@ashstack/react/no-svg-without-title" | "@ashstack/react/hoist-intl" | "@ashstack/react/prefer-design-system" | "@ashstack/react/components-tsx-only" | "@ashstack/query/no-inline-keys" | "@ashstack/query/no-deprecated-filters" | "@ashstack/query/require-destructured-hooks" | "@ashstack/query/no-fetch-in-query-fn" | "@ashstack/query/next-page-param-undefined" | "@ashstack/zustand/require-selector" | "@ashstack/i18n/no-bare-text" | "@ashstack/i18n/no-bare-attrs" | "@ashstack/i18n/no-bare-toast" | "@ashstack/stylex/inline-props" | "@ashstack/stylex/no-conflicting-props" | "@ashstack/stylex/no-duplicate-styles" | "@ashstack/stylex/require-tokens" | "@ashstack/tailwind/prefer-cn" | "@ashstack/tailwind/use-logical-classes" | "@ashstack/tanstack-router/require-selector" | "@ashstack/tanstack-router/no-search-casts" | "@ashstack/effects/no-derived-state" | "@ashstack/effects/no-chain-state-updates" | "@ashstack/effects/no-event-handler" | "@ashstack/effects/no-adjust-state-on-prop-change" | "@ashstack/effects/no-reset-all-state-on-prop-change" | "@ashstack/effects/no-pass-live-state-to-parent" | "@ashstack/effects/no-pass-data-to-parent" | "@ashstack/effects/no-external-store-subscription" | "@ashstack/effects/no-initialize-state";
|
|
6
6
|
declare module "oxlint" {
|
|
7
7
|
interface DummyRuleMap {
|
|
8
8
|
/**
|
|
@@ -134,13 +134,21 @@ declare module "oxlint" {
|
|
|
134
134
|
*/
|
|
135
135
|
"@ashstack/i18n/no-bare-toast"?: RuleSetting;
|
|
136
136
|
/**
|
|
137
|
-
*
|
|
137
|
+
* Use `stylex.props(...)` as JSX props or return/store its result.
|
|
138
138
|
*
|
|
139
139
|
* **Default: on**, when `@stylexjs/stylex` is a dependency.
|
|
140
140
|
*
|
|
141
141
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackstylexinline-props
|
|
142
142
|
*/
|
|
143
143
|
"@ashstack/stylex/inline-props"?: RuleSetting;
|
|
144
|
+
/**
|
|
145
|
+
* Report className overrides and inline CSS that overlaps StyleX declarations.
|
|
146
|
+
*
|
|
147
|
+
* **Default: on**, when `@stylexjs/stylex` is a dependency.
|
|
148
|
+
*
|
|
149
|
+
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackstylexno-conflicting-props
|
|
150
|
+
*/
|
|
151
|
+
"@ashstack/stylex/no-conflicting-props"?: RuleSetting;
|
|
144
152
|
/**
|
|
145
153
|
* Reuse identical static styles within one `stylex.create` call.
|
|
146
154
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{fileURLToPath as e}from"node:url";export const STYLEX_SPECIFIER=e(new URL("../../vendor/stylex/index.mjs",import.meta.url)),stylexPlugin={name:"@stylexjs",specifier:STYLEX_SPECIFIER},STYLEX_RULES={"@stylexjs/valid-styles":"error","@stylexjs/valid-shorthands":"error","@stylexjs/no-unused":"error","@stylexjs/enforce-extension":"error","@stylexjs/no-
|
|
1
|
+
import{fileURLToPath as e}from"node:url";export const STYLEX_SPECIFIER=e(new URL("../../vendor/stylex/index.mjs",import.meta.url)),stylexPlugin={name:"@stylexjs",specifier:STYLEX_SPECIFIER},STYLEX_RULES={"@stylexjs/valid-styles":"error","@stylexjs/valid-shorthands":"error","@stylexjs/no-unused":"error","@stylexjs/enforce-extension":"error","@stylexjs/no-legacy-contextual-styles":"error","@stylexjs/no-nonstandard-styles":"error"};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineModule as e}from"../../../lib/module.js";import{inlineProps as
|
|
1
|
+
import{defineModule as e}from"../../../lib/module.js";import{inlineProps as o}from"./inline-props.js";import{noConflictingProps as t}from"./no-conflicting-props.js";import{noDuplicateStyles as r}from"./no-duplicate-styles.js";import{requireTokens as s}from"./require-tokens.js";export default e({meta:{name:"@ashstack/stylex"},url:import.meta.url,packages:["@stylexjs/stylex"],option:"stylex",docsWhen:"auto-enabled by `react()` when `@stylexjs/stylex` is a dependency",rules:{"inline-props":o,"no-conflicting-props":t,"no-duplicate-styles":r,"require-tokens":s}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{problem as
|
|
1
|
+
import{problem as o}from"../../../lib/ast.js";import{collectImports as n,isStylexCall as p}from"./imports.js";const s="Use `stylex.props(...)` as JSX props or return/store its result.";export const inlineProps=o(s,{createOnce(t){const r={namespaces:new Set,named:new Set};return{before(){r.namespaces.clear();r.named.clear();return t.sourceCode.text.includes("stylex")},Program(e){n(e.body,"props",r)},CallExpression(e){if(!p(t,e,"props",r))return;if(["JSXSpreadAttribute","VariableDeclarator","ReturnStatement","ArrowFunctionExpression"].includes(e.parent.type))return;t.report({node:e,message:s})}}}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{attributeName as C,problem as N,propertyKeyName as u,propertyValue as X,subtreeHas as v}from"../../../lib/ast.js";import{collectImports as S,isStylexCall as y}from"./imports.js";const I=(t,a,n)=>{if(t.type!=="MemberExpression"||t.computed)return;if(t.object.type!=="Identifier"||t.property.type!=="Identifier")return;const p=X(a.get(t.object.name),t.property.name);if(p?.type!=="ObjectExpression")return;for(const m of p.properties){const c=u(m);if(c)n.add(c)}},f=(t,a,n)=>{if(t.type==="MemberExpression")I(t,a,n);else if(t.type==="ArrayExpression"){for(const p of t.elements)if(p)f(p,a,n)}else if(t.type==="LogicalExpression")f(t.right,a,n);else if(t.type==="ConditionalExpression"){f(t.consequent,a,n);f(t.alternate,a,n)}};export const noConflictingProps=N("Report className overrides and inline CSS that overlaps StyleX declarations.",{createOnce(t){const a={namespaces:new Set,named:new Set},n={namespaces:new Set,named:new Set},p=new Map,m=new Map,c=[],g=(e)=>{if(e.type!=="JSXSpreadAttribute")return null;const r=e.argument.type==="Identifier"?e.argument.name:null,s=r?m.get(r):e.argument;return s?.type==="CallExpression"&&y(t,s,"props",n)?{call:s,storedName:r}:null},b=(e,r)=>{if(e.type!=="JSXAttribute")return;const s=e.value;if(!(s?.type==="JSXExpressionContainer"&&v(s.expression,(o)=>{if(o.type!=="MemberExpression"||o.object.type!=="Identifier")return!1;const l=o.object.name;return o.property.type==="Identifier"&&o.property.name==="className"&&r.some((d)=>d.storedName===l)})))t.report({node:e,message:"`className` overrides StyleX classes."})},E=(e,r)=>{if(e.type!=="JSXAttribute"||e.value?.type!=="JSXExpressionContainer")return;const s=e.value.expression;if(s.type!=="ObjectExpression")return;const i=new Set;for(const{call:o}of r)if(o.type==="CallExpression")for(const l of o.arguments)f(l,p,i);for(const o of s.properties){const l=u(o);if(i.has(l))t.report({node:o,message:`Inline \`${l}\` overlaps a StyleX declaration.`})}},x=(e)=>{if(e.type!=="JSXOpeningElement")return;const r=[];for(const s of e.attributes){const i=g(s);if(i)r.push(i)}if(r.length===0)return;for(const s of e.attributes){const i=C(s);if(i==="className")b(s,r);if(i==="style")E(s,r)}};return{before(){a.namespaces.clear();a.named.clear();n.namespaces.clear();n.named.clear();p.clear();m.clear();c.length=0;return t.sourceCode.text.includes("stylex")},Program(e){S(e.body,"create",a);S(e.body,"props",n)},VariableDeclarator(e){if(e.id.type!=="Identifier"||e.init?.type!=="CallExpression")return;if(y(t,e.init,"props",n))m.set(e.id.name,e.init);if(!y(t,e.init,"create",a))return;const r=e.init.arguments[0];if(r?.type==="ObjectExpression")p.set(e.id.name,r)},JSXOpeningElement(e){if(e.attributes.some((r)=>r.type==="JSXSpreadAttribute"))c.push(e)},"Program:exit"(){c.forEach(x)}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{problem as
|
|
1
|
+
import{problem as c,propertyKeyName as o}from"../../../lib/ast.js";import{collectImports as l,isStylexCall as u}from"./imports.js";const y=new Set(["parent","loc","range","start","end","raw"]),f=(n,r)=>{if(y.has(n))return;return typeof r==="bigint"?{bigint:r.toString()}:r},m=(n)=>{if(n.type!=="ObjectExpression")return null;const r=[];for(const t of n.properties){if(t.type!=="Property")return null;const a=o(t);if(!a)return null;r.push([a,JSON.stringify(t.value,f)])}return JSON.stringify(r.sort(([t],[a])=>t.localeCompare(a)))},g=(n,r,t)=>{if(n.type!=="ObjectExpression")return;const a=new Map;for(const e of n.properties){if(e.type!=="Property")continue;const i=o(e);if(!i)continue;const s=m(e.value);if(s===null)continue;const p=a.get(s);if(p)t.push({node:e.value,owner:r,key:i,original:p});else a.set(s,i)}};export const noDuplicateStyles=c("Reuse identical static styles within one `stylex.create` call.",{createOnce(n){const r={namespaces:new Set,named:new Set},t=new Set,a=[];return{before(){r.namespaces.clear();r.named.clear();t.clear();a.length=0;return n.sourceCode.text.includes("stylex")},Program(e){l(e.body,"create",r)},MemberExpression(e){if(e.object.type!=="Identifier")return;if(e.computed&&e.property.type!=="Literal")t.add(`${e.object.name}.*`);if(e.property.type==="Identifier"&&!e.computed)t.add(`${e.object.name}.${e.property.name}`);if(e.property.type==="Literal"&&typeof e.property.value==="string")t.add(`${e.object.name}.${e.property.value}`)},CallExpression(e){if(!u(n,e,"create",r))return;if(e.parent.type!=="VariableDeclarator"||e.parent.id.type!=="Identifier")return;if(e.parent.parent.type==="VariableDeclaration"&&e.parent.parent.parent.type==="ExportNamedDeclaration")return;const[i]=e.arguments;if(i)g(i,e.parent.id.name,a)},"Program:exit"(){for(const{node:e,owner:i,key:s,original:p}of a){if(t.has(`${i}.*`)||t.has(`${i}.${s}`)&&t.has(`${i}.${p}`))continue;n.report({node:e,message:`\`${s}\` duplicates \`${p}\`. Reuse \`${p}\`.`})}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{hasAncestor as
|
|
1
|
+
import{hasAncestor as p,isMemberCall as f,optionsOf as y,problem as m,propertyKeyName as h}from"../../../lib/ast.js";import{collectImports as b,isStylexCall as v}from"./imports.js";const O=/^(?:color|.*Color|fill|stroke)$/,g=/^border(?:TopLeft|TopRight|BottomLeft|BottomRight|StartStart|StartEnd|EndStart|EndEnd)?Radius$/,E=/^(?:boxShadow|textShadow|filter|backgroundImage|borderImageSource)$/,S=/#[\da-f]{3,8}\b/i,C=/(?:rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|color)\(/i,L=new Set(["currentColor","inherit","initial","unset","revert","revert-layer","none"]),R=new Set(["inherit","initial","unset","revert","revert-layer"]),T=(e)=>{if(O.test(e))return"color";if(g.test(e))return"radius";if(E.test(e))return"composite";return null},u=(e)=>{if(e.type==="Identifier")return e.name;if(e.type!=="MemberExpression")return"";const r=u(e.object);let t="";if(!e.computed&&e.property.type==="Identifier")t=e.property.name;if(e.computed&&e.property.type==="Literal"&&typeof e.property.value==="string")t=e.property.value;return r&&t?`${r}.${t}`:""},n=(e,r)=>{if(e.type!=="MemberExpression")return!1;const t=u(e);return t.startsWith(`${r}.`)&&!t.slice(r.length+1).includes(".")},x=(e,r)=>{if(e.type==="Identifier")return e.name==="undefined";if(e.type!=="Literal")return!1;if(e.value===null)return!0;if(r==="color")return typeof e.value==="string"&&L.has(e.value);return e.value===0||e.value==="0"||typeof e.value==="string"&&R.has(e.value)},k=(e,r)=>{if(e.type!=="TemplateLiteral"||e.expressions.length===0)return!1;if(!e.expressions.every((s)=>n(s,r)))return!1;const t=e.quasis.map((s,i)=>`${s.value.cooked??s.value.raw}${i<e.expressions.length?"TOKEN":""}`).join("");return/^color-mix\(in (?:srgb|srgb-linear|oklab|oklch|lab|lch|hsl|hwb), TOKEN(?: \d+(?:\.\d+)?%)?, (?:TOKEN(?: \d+(?:\.\d+)?%)?|transparent)\)$/.test(t)},w=(e,r,t)=>{let s="";if(r.type==="TemplateLiteral")s=r.quasis.map((a)=>a.value.cooked??a.value.raw).join("");if(r.type==="Literal"&&typeof r.value==="string")s=r.value;const i=s.includes("color-mix(")&&(r.type!=="TemplateLiteral"||!r.expressions.every((a)=>n(a,t)));if(S.test(s)||C.test(s)||i)e.report({node:r,message:`Use \`${t}.*\` for colors inside CSS values.`})},l=(e,r,t,s)=>{if(r.type==="ObjectExpression"){r.properties.forEach((i)=>{if(i.type==="Property")l(e,i.value,t,s)});return}if(r.type==="ArrayExpression"){r.elements.forEach((i)=>{if(i)l(e,i,t,s)});return}if(r.type==="ConditionalExpression"){l(e,r.consequent,t,s);l(e,r.alternate,t,s);return}if(r.type==="CallExpression"&&f(r,"firstThatWorks")){r.arguments.forEach((i)=>l(e,i,t,s));return}if(t==="composite"){w(e,r,s);return}if(x(r,t))return;if(n(r,s))return;if(t==="color"&&k(r,s))return;e.report({node:r,message:`Use \`${s}.*\` for ${t}.`})};export const requireTokens=m("Use configured color and radius groups in `stylex.create`; defaults are `colors` and `radii`.",{meta:{schema:[{type:"object",properties:{colors:{anyOf:[{type:"string",minLength:1},{type:"boolean",enum:[!1]}],default:"colors",description:"Color token group, or false to disable this check."},radii:{anyOf:[{type:"string",minLength:1},{type:"boolean",enum:[!1]}],default:"radii",description:"Radius token group, or false to disable this check."}},additionalProperties:!1}]},createOnce(e){const r={namespaces:new Set,named:new Set};let t="colors",s="radii";const i=(a)=>v(e,a,"create",r);return{before(){r.namespaces.clear();r.named.clear();({colors:t="colors",radii:s="radii"}=y(e,{}));return e.sourceCode.text.includes("stylex")},Program(a){b(a.body,"create",r)},Property(a){const o=T(h(a));if(o===null)return;if(!p(a,i))return;const c=o==="radius"?s:t;if(c===!1)return;l(e,a.value,o,c)}}}});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashstack/lint",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "ashStack shared oxlint configuration and custom rules. Entries: core, react, react-native, with per-library rule groups auto-detected from your dependencies. Consume from oxlint.config.mts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|