@ashstack/lint 0.5.1 → 0.6.0
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-native.d.ts +1 -3
- package/dist/lib/types.d.ts +6 -0
- package/dist/react/index.js +1 -1
- package/dist/react-native/index.js +1 -1
- package/dist/react-native/rules/react-native/no-manual-memo.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 -->118 rules on plain TypeScript,
|
|
3
|
+
Strict shared [oxlint](https://oxc.rs) config: <!-- rule-counts -->118 rules on plain TypeScript, 201 with React, 257 on React Native, 74 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":{},"use-design-system":{defaultOff:!0},"components-tsx-only":{defaultOff:!0},"hoist-intl":{}}},{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":{}}},{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/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":{
|
|
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":{},"use-design-system":{defaultOff:!0},"components-tsx-only":{defaultOff:!0},"hoist-intl":{}}},{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":{}}},{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/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-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. These legacy path-value hooks self-dirty Reanimated mappers and re-record idle canvases."}]},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, and it tracks the keyboard per frame instead of waiting for Android's post-animation `keyboardDidShow`. For a scrolling form use its `KeyboardAwareScrollView`; to just shift a view, use `rt.insets.ime`."}]},rules:{}}];
|
|
@@ -60,9 +60,7 @@ declare module "oxlint" {
|
|
|
60
60
|
*/
|
|
61
61
|
"@ashstack/react-native/hoist-stateless-function"?: RuleSetting;
|
|
62
62
|
/**
|
|
63
|
-
* Require a `// why:` line above every kept `useMemo`, `useCallback` and `memo`. The React Compiler memoises on a best-effort basis, not a guarantee, so a memo is allowed where the cost is real: something rendered per list row, or a computation measured as heavy. The `// why:` line names which of the two applies. `@ashstack/core/no-comments` keeps that line and never counts it against its `budget`, so both rules run together.
|
|
64
|
-
*
|
|
65
|
-
* Enabled only when one of `babel-plugin-react-compiler`, `react-compiler-runtime`, `react-compiler-marker` is a dependency.
|
|
63
|
+
* Require a `// why:` line above every kept `useMemo`, `useCallback` and `memo`. The React Compiler memoises on a best-effort basis, not a guarantee, so a memo is allowed where the cost is real: something rendered per list row, or a computation measured as heavy. The `// why:` line names which of the two applies. `@ashstack/core/no-comments` keeps that line and never counts it against its `budget`, so both rules run together. Assumes the compiler is on — pass `reactCompiler: false` to the entry and this rule turns off, since without it a hand-written memo is the only memo there is.
|
|
66
64
|
*
|
|
67
65
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreact-nativeno-manual-memo
|
|
68
66
|
*/
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -102,6 +102,12 @@ export type CoreOptions = {
|
|
|
102
102
|
zod?: boolean;
|
|
103
103
|
};
|
|
104
104
|
export type ReactOptions = CoreOptions & {
|
|
105
|
+
/**
|
|
106
|
+
* Is the React Compiler on? Defaults to `true` — every other React setting here assumes it.
|
|
107
|
+
* Set `false` and the four [`react-perf/` inline-prop rules](https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#react)
|
|
108
|
+
* come back on, since without the compiler a fresh object, array, function or element in a prop really does re-render the child.
|
|
109
|
+
*/
|
|
110
|
+
reactCompiler?: boolean;
|
|
105
111
|
/** [`@ashstack/query/` rules](https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackquery) — detected from `@tanstack/react-query` */
|
|
106
112
|
query?: boolean;
|
|
107
113
|
/** [`@ashstack/zustand/` rules](https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackzustand) — detected from `zustand` */
|
package/dist/react/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import o from"../core/index.js";import{EFFECT_RULES as t,effectPlugin as a}from"../lib/effect-plugin.js";import{mergeConfigs as s}from"../lib/merge.js";import{composeModules as n}from"../lib/module.js";import{coreRegistry as c,reactRegistry as i}from"../lib/registry.js";const
|
|
1
|
+
import o from"../core/index.js";import{EFFECT_RULES as t,effectPlugin as a}from"../lib/effect-plugin.js";import{mergeConfigs as s}from"../lib/merge.js";import{composeModules as n}from"../lib/module.js";import{coreRegistry as c,reactRegistry as i}from"../lib/registry.js";const p={"eslint/no-empty":"off","eslint/no-empty-function":"off"},l={"react/error-boundaries":"error","react/globals":"error","react/immutability":"error","react/incompatible-library":"error","react/preserve-manual-memoization":"error","react/purity":"error","react/refs":"error","react/set-state-in-effect":"off","react/set-state-in-render":"error","react/static-components":"error","react/use-memo":"error","react/void-use-memo":"error","react/capitalized-calls":"error","react/exhaustive-effect-dependencies":"error","react/hooks":"error","react/memo-dependencies":"error","react/no-deriving-state-in-effects":"off","react/invariant":"error","react/rule-suppression":"error","react/syntax":"error","react/todo":"error","react/unsupported-syntax":"error"},f=(e)=>{const r=e?"off":"error";return{"react-perf/jsx-no-jsx-as-prop":r,"react-perf/jsx-no-new-array-as-prop":r,"react-perf/jsx-no-new-function-as-prop":r,"react-perf/jsx-no-new-object-as-prop":r,"react/jsx-no-constructed-context-values":r,"react/no-object-type-as-default-prop":r}},u=["**/routes/**","**/src/app/**","**/app/**/_layout.tsx","**/app/**/+*.tsx"],d={...p,"max-lines-per-function":["error",{max:120,skipBlankLines:!0,skipComments:!0}],"react/react-in-jsx-scope":"off","react/style-prop-object":"off","react/only-export-components":"off","react/rules-of-hooks":"error","react/exhaustive-deps":"error","react/button-has-type":"error","react/checked-requires-onchange-or-readonly":"error","react/iframe-missing-sandbox":"error","react/jsx-boolean-value":"error","react/jsx-key":"error","react/jsx-no-comment-textnodes":"error","react/jsx-no-duplicate-props":"error","react/jsx-no-target-blank":"error","react/jsx-no-useless-fragment":"error","react/no-array-index-key":"error","react/no-children-prop":"error","react/no-danger":"error","react/no-danger-with-children":"error","react/no-direct-mutation-state":"error","react/no-is-mounted":"error","react/no-render-return-value":"error","react/no-string-refs":"error","react/no-this-in-sfc":"error","react/no-unsafe":"error","react/no-unescaped-entities":"error","react/no-unstable-nested-components":["error",{allowAsProps:!0}],"react/require-render-return":"error","react/self-closing-comp":"error","react/void-dom-elements-no-children":"error",...l,"jsx-a11y/alt-text":"error","jsx-a11y/anchor-has-content":"error","jsx-a11y/anchor-is-valid":"error","jsx-a11y/click-events-have-key-events":"off","jsx-a11y/control-has-associated-label":"off","jsx-a11y/heading-has-content":"error","jsx-a11y/img-redundant-alt":"error","jsx-a11y/interactive-supports-focus":"error","jsx-a11y/label-has-associated-control":"error","jsx-a11y/no-aria-hidden-on-focusable":"error","jsx-a11y/no-autofocus":["error",{ignoreNonDOM:!0}],"jsx-a11y/no-noninteractive-element-interactions":"off","jsx-a11y/no-noninteractive-tabindex":"error","jsx-a11y/no-redundant-roles":"error","jsx-a11y/no-static-element-interactions":"error","jsx-a11y/prefer-tag-over-role":"off","jsx-a11y/role-has-required-aria-props":"error","jsx-a11y/role-supports-aria-props":"error","unicorn/filename-case":["error",{cases:{kebabCase:!0,pascalCase:!0}}]},react=(e={})=>{const r=n([...c,...i],e);return s(o(e),{plugins:["react","jsx-a11y","react-perf"],jsPlugins:[a,...r.jsPlugins],rules:{...d,...f(e.reactCompiler??!0),...t,...r.rules},overrides:[{files:u,rules:{"unicorn/filename-case":"off"}}]})};export{react};export default react;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TEST_FILES as
|
|
1
|
+
import{TEST_FILES as r}from"../core/index.js";import{mergeConfigs as s}from"../lib/merge.js";import{composeModules as a}from"../lib/module.js";import{coreRegistry as o,reactNativeRegistry as i,reactRegistry as n}from"../lib/registry.js";import c from"../react/index.js";export const banGroups=[{packages:["react-native-gesture-handler"],restrictedImports:{paths:[{name:"react-native",importNames:["Pressable","TouchableOpacity","TouchableHighlight","TouchableWithoutFeedback","TouchableNativeFeedback"],message:"Import `Pressable` from `react-native-gesture-handler` so it cooperates with the gesture system instead of competing with it. Exception: inside a SwiftUI/Compose host view touches never reach `GestureHandlerRootView` — keep React Native's `Pressable` there and suppress this rule with the host named in the reason."}]}},{packages:["expo-router"],restrictedImports:{patterns:[{group:["@react-navigation/stack","@react-navigation/bottom-tabs"],message:"Use expo-router's `Stack` / `NativeTabs` instead of a raw react-navigation navigator."}]}},{packages:["expo-font"],restrictedImports:{paths:[{name:"expo-font",importNames:["useFonts","loadAsync"],message:"Declare the fonts in the `expo-font` config plugin so they load at build time, instead of `useFonts`/`loadAsync` at runtime."}]}},{packages:["react-native-quick-crypto"],restrictedImports:{paths:[{name:"crypto-js",message:"Use `react-native-quick-crypto` instead — `crypto-js` is pure JavaScript and runs orders of magnitude slower."}]}}];const p={"eslint/no-empty":"error","eslint/no-empty-function":"error"},m={"@ashstack/effects/no-event-handler":"off"},l=(e)=>e?{}:{"@ashstack/react-native/no-manual-memo":"off"},reactNative=(e={})=>{const t=a([...o,...n,...i],e,banGroups);return s(c(e),{jsPlugins:t.jsPlugins,globals:{__DEV__:"readonly"},rules:{...p,...m,"no-restricted-imports":["error",t.restricted],...t.rules,...l(e.reactCompiler??!0)},ignorePatterns:["**/.expo/**","**/android/**","**/ios/**"],overrides:[{files:r,rules:{"@ashstack/react-native/no-dynamic-import":"off","@ashstack/react-native/hoist-stateless-function":"off"}}]})};export{reactNative};export default reactNative;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{gate as m,problem as
|
|
1
|
+
import{gate as m,problem as u}from"../../../lib/ast.js";const l=new Set(["useMemo","useCallback","memo"]),c=/^why:/i,h=10,p=(e)=>e.type==="Identifier"&&l.has(e.name)?e.name:null,f=(e)=>{if(e.type!=="MemberExpression"||e.computed)return null;if(e.object.type!=="Identifier"||e.object.name!=="React")return null;const t=e.property.name;return l.has(t)?t:null},d=(e)=>{if(e.type!=="CallExpression")return null;const{callee:t}=e;if(t.type==="Identifier")return p(t);if(t.type==="MemberExpression")return f(t);return null},y=(e)=>{const t=[0];for(let o=0;o<e.length;o++)if(e.charCodeAt(o)===h)t.push(o+1);return t},w=(e,t)=>{let o=0,n=e.length-1;while(o<n){const s=Math.floor((o+n+1)/2);if((e[s]??0)<=t)o=s;else n=s-1}return o},b=(e,t)=>{const o=new Set;for(const n of e){if(!c.test(n.value.trim()))continue;for(let s=t(n.start);s<=t(n.end);s++)o.add(s)}return o};export const noManualMemo=u("Require a `// why:` line above every kept `useMemo`, `useCallback` and `memo`. The React Compiler memoises on a best-effort basis, not a guarantee, so a memo is allowed where the cost is real: something rendered per list row, or a computation measured as heavy. The `// why:` line names which of the two applies. `@ashstack/core/no-comments` keeps that line and never counts it against its `budget`, so both rules run together. Assumes the compiler is on — pass `reactCompiler: false` to the entry and this rule turns off, since without it a hand-written memo is the only memo there is.",{createOnce(e){let t=[];return{before(){t=[];return m(e,"useMemo","useCallback","memo")},CallExpression(o){const n=d(o);if(n!==null)t.push({node:o,name:n})},"Program:exit"(){if(t.length===0)return;const o=y(e.sourceCode.getText()),n=(r)=>w(o,r),s=b(e.sourceCode.getAllComments(),n);for(const{node:r,name:a}of t){const i=n(r.start);if(s.has(i)||s.has(i-1))continue;e.report({node:r,message:`The React Compiler memoises on a best-effort basis, not a guarantee, so a \`${a}\` is allowed where you know the cost is real: something rendered per row in a list, or a computation you measured as heavy. Add a \`// why:\` line directly above it naming which of the two applies. If neither does, delete the \`${a}\`.`})}}}}});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashstack/lint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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",
|