@ashstack/lint 0.10.1 → 0.10.2
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 +17 -6
- package/dist/react/rules/stylex/imports.js +1 -1
- package/dist/react/rules/stylex/index.js +1 -1
- package/dist/react/rules/stylex/require-tokens.js +1 -1
- package/dist/react/rules/stylex/use-sx-prop.js +1 -0
- 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,
|
|
3
|
+
Strict shared [oxlint](https://oxc.rs) config: <!-- rule-counts -->116 rules on plain TypeScript, 211 with React, 268 on React Native, 82 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-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:{}}];
|
|
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":{},"use-sx-prop":{}}},{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-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";
|
|
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/stylex/use-sx-prop" | "@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
|
/**
|
|
@@ -158,16 +158,27 @@ declare module "oxlint" {
|
|
|
158
158
|
*/
|
|
159
159
|
"@ashstack/stylex/no-duplicate-styles"?: RuleSetting;
|
|
160
160
|
/**
|
|
161
|
-
* Use
|
|
161
|
+
* Use token groups for configured properties in `stylex.create`; colors and radii are checked by default.
|
|
162
162
|
*
|
|
163
163
|
* **Default: on**, when `@stylexjs/stylex` is a dependency.
|
|
164
164
|
*
|
|
165
165
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackstylexrequire-tokens
|
|
166
166
|
*/
|
|
167
|
-
"@ashstack/stylex/require-tokens"?: RuleSetting<[
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
"@ashstack/stylex/require-tokens"?: RuleSetting<[
|
|
168
|
+
{
|
|
169
|
+
colors?: string | false;
|
|
170
|
+
radii?: string | false;
|
|
171
|
+
tokens?: Record<string, Array<string>>;
|
|
172
|
+
}
|
|
173
|
+
]>;
|
|
174
|
+
/**
|
|
175
|
+
* Name StyleX override props `sx` or `<slot>Sx`.
|
|
176
|
+
*
|
|
177
|
+
* **Default: on**, when `@stylexjs/stylex` is a dependency.
|
|
178
|
+
*
|
|
179
|
+
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackstylexuse-sx-prop
|
|
180
|
+
*/
|
|
181
|
+
"@ashstack/stylex/use-sx-prop"?: RuleSetting;
|
|
171
182
|
/**
|
|
172
183
|
* Require a dynamic class value to go through `cn(...)` before it reaches a `class`, `className` or `*ClassName` prop. Reports both where the value reaches the prop and where a variable named after classes is declared.
|
|
173
184
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const n=new Set(["@stylexjs/stylex","stylex"]);export const collectImports=(p,i,t)=>{for(const r of p){if(r.type!=="ImportDeclaration"||!n.has(r.source.value))continue;for(const e of r.specifiers){if(e.type==="ImportNamespaceSpecifier")t.namespaces.add(e.local.name);if(e.type==="ImportSpecifier"&&e.imported.type==="Identifier"&&e.imported.name===i)t.named.add(e.local.name)}}},isImportedBinding=(p,i)=>{if(i.type!=="Identifier")return!1;let t=p.sourceCode.getScope(i);while(t){const r=t.set.get(i.name);if(r)return r.defs.some((e)=>e.type==="ImportBinding");t=t.upper}return!1},isStylexCall=(p,i,t,r)=>{if(i.type!=="CallExpression")return!1;const{callee:e}=i;let s=null;if(e.type==="Identifier")s=e;if(e.type==="MemberExpression")s=e.object;if(s?.type!=="Identifier")return!1;return(e.type==="Identifier"&&r.named.has(s.name)||e.type==="MemberExpression"&&!e.computed&&r.namespaces.has(s.name)&&e.property.type==="Identifier"&&e.property.name===t)&&isImportedBinding(p,s)};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineModule as e}from"../../../lib/module.js";import{inlineProps as o}from"./inline-props.js";import{noConflictingProps as
|
|
1
|
+
import{defineModule as e}from"../../../lib/module.js";import{inlineProps as o}from"./inline-props.js";import{noConflictingProps as r}from"./no-conflicting-props.js";import{noDuplicateStyles as t}from"./no-duplicate-styles.js";import{requireTokens as s}from"./require-tokens.js";import{useSxProp as i}from"./use-sx-prop.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":r,"no-duplicate-styles":t,"require-tokens":s,"use-sx-prop":i}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{hasAncestor as
|
|
1
|
+
import{hasAncestor as h,isMemberCall as b,optionsOf as k,problem as v,propertyKeyName as d}from"../../../lib/ast.js";import{collectImports as E,isStylexCall as O}from"./imports.js";const S=/^(?:color|.*Color|fill|stroke)$/,x=/^border(?:TopLeft|TopRight|BottomLeft|BottomRight|StartStart|StartEnd|EndStart|EndEnd)?Radius$/,C=/^(?:boxShadow|textShadow|filter|backgroundImage|borderImageSource)$/,L=/#[\da-f]{3,8}\b/i,R=/(?:rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|color)\(/i,w=new Set(["currentColor","inherit","initial","unset","revert","revert-layer","none"]),T=new Set(["inherit","initial","unset","revert","revert-layer"]),I=(e)=>{if(S.test(e))return"color";if(x.test(e))return"radius";if(C.test(e))return"composite";return null},A=(e)=>{const t=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replaceAll("\\*",".*");return new RegExp(`^${t}$`)},g=(e)=>{if(e.type==="Identifier")return e.name;if(e.type!=="MemberExpression")return"";const t=g(e.object);let i="";if(!e.computed&&e.property.type==="Identifier")i=e.property.name;if(e.computed&&e.property.type==="Literal"&&typeof e.property.value==="string")i=e.property.value;return t&&i?`${t}.${i}`:""},m=(e,t)=>{if(e.type!=="MemberExpression")return!1;const i=g(e);return i.startsWith(`${t}.`)&&!i.slice(t.length+1).includes(".")},j=(e,t)=>{if(e.type==="Identifier")return e.name==="undefined";if(e.type!=="Literal")return!1;if(e.value===null)return!0;if(t==="color")return typeof e.value==="string"&&w.has(e.value);return e.value===0||e.value==="0"||typeof e.value==="string"&&T.has(e.value)},M=(e,t)=>{if(e.type!=="TemplateLiteral"||e.expressions.length===0)return!1;if(!e.expressions.every((a)=>m(a,t)))return!1;const i=e.quasis.map((a,s)=>`${a.value.cooked??a.value.raw}${s<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(i)},P=(e,t,{group:i,name:a})=>{let s="";if(t.type==="TemplateLiteral")s=t.quasis.map((r)=>r.value.cooked??r.value.raw).join("");if(t.type==="Literal"&&typeof t.value==="string")s=t.value;const c=s.includes("color-mix(")&&(t.type!=="TemplateLiteral"||!t.expressions.every((r)=>m(r,i)));if(L.test(s)||R.test(s)||c)e.report({node:t,message:`Use an existing \`${i}.*\` token in \`${a}\`.`})},p=(e,t,i)=>{if(t.type==="ObjectExpression"){t.properties.forEach((r)=>{if(r.type==="Property")p(e,r.value,i)});return}if(t.type==="ArrayExpression"){t.elements.forEach((r)=>{if(r)p(e,r,i)});return}if(t.type==="ConditionalExpression"){p(e,t.consequent,i);p(e,t.alternate,i);return}if(t.type==="CallExpression"&&b(t,"firstThatWorks")){t.arguments.forEach((r)=>p(e,r,i));return}const{kind:a,group:s,name:c}=i;if(a==="composite"){P(e,t,i);return}if(j(t,a))return;if(m(t,s))return;if(a==="color"&&M(t,s))return;e.report({node:t,message:`Use an existing \`${s}.*\` token for \`${c}\`.`})};export const requireTokens=v("Use token groups for configured properties in `stylex.create`; colors and radii are checked by default.",{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."},tokens:{type:"object",default:{},propertyNames:{type:"string",pattern:"^[A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*)*$"},additionalProperties:{type:"array",items:{type:"string",minLength:1},minItems:1,uniqueItems:!0},description:"Additional token groups mapped to exact CSS properties or * patterns. Explicit mappings override built-in property checks.",examples:[{spacing:["padding*","margin*","gap"]}]}},additionalProperties:!1}]},createOnce(e){const t={namespaces:new Set,named:new Set};let i="colors",a="radii",s=[];const c=(r)=>O(e,r,"create",t);return{before(){t.namespaces.clear();t.named.clear();const r=k(e,{});i=r.colors??"colors";a=r.radii??"radii";s=Object.entries(r.tokens??{}).flatMap(([n,l])=>l.map((o)=>({group:n,pattern:A(o)})));return e.sourceCode.text.includes("stylex")},Program(r){E(r.body,"create",t)},Property(r){const n=d(r),l=I(n);if(l===null&&s.length===0)return;let o,f;for(const u of s){if(!u.pattern.test(n))continue;if(o&&o!==u.group){f=u.group;break}o=u.group}if(o===void 0&&l===null)return;if(!h(r,c))return;if(f){e.report({node:r,message:`Map \`${n}\` to one token group; matched ${o}, ${f}.`});return}const y=o??(l==="radius"?a:i);if(y===!1)return;p(e,r.value,{kind:l??"token",group:y,name:n})}}}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{problem as p}from"../../../lib/ast.js";import{collectImports as a,isImportedBinding as y}from"./imports.js";const l=/^[a-z][a-zA-Z0-9]*Sx$/;export const useSxProp=p("Name StyleX override props `sx` or `<slot>Sx`.",{createOnce(s){const r={namespaces:new Set,named:new Set},n=(e)=>{if(e.type==="TSTypeReference"){const t=e.typeName;if(t.type==="Identifier")return r.named.has(t.name)&&y(s,t);return t.type==="TSQualifiedName"&&t.left.type==="Identifier"&&r.namespaces.has(t.left.name)&&(t.right.name==="StyleXStyles"||t.right.name==="StyleXStylesWithout")&&y(s,t.left)}if(e.type==="TSUnionType"||e.type==="TSIntersectionType")return e.types.some(n);if(e.type==="TSParenthesizedType")return n(e.typeAnnotation);return!1};return{before(){r.namespaces.clear();r.named.clear();return s.sourceCode.text.includes("StyleXStyles")},Program(e){a(e.body,"StyleXStyles",r);a(e.body,"StyleXStylesWithout",r)},TSPropertySignature(e){if(r.named.size===0&&r.namespaces.size===0)return;if(e.computed||!e.typeAnnotation||!n(e.typeAnnotation.typeAnnotation))return;let t=null;if(e.key.type==="Identifier")t=e.key.name;if(e.key.type==="Literal"&&typeof e.key.value==="string")t=e.key.value;if(t===null||t==="sx"||l.test(t))return;const i=t.startsWith("style")?"sx":`${t.replace(/Style$/,"")}Sx`;s.report({node:e.key,message:`Rename \`${t}\` to \`${i}\` for a StyleX override prop.`})}}}});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashstack/lint",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
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",
|