@ashstack/lint 0.3.0 → 0.4.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 +25 -2
- package/dist/core/index.d.ts +4 -2
- package/dist/core/index.js +1 -1
- package/dist/core/rules/core/no-comments.js +1 -1
- package/dist/lib/ast.js +1 -1
- package/dist/lib/effect-plugin.js +1 -0
- package/dist/lib/types.d.ts +5 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +1 -1
- package/dist/react/rules/query/next-page-param-undefined.js +1 -1
- package/dist/react/rules/query/no-fetch-in-query-fn.js +1 -1
- package/dist/react/rules/query/no-inline-keys.js +1 -1
- package/dist/react/rules/react/index.js +1 -0
- package/dist/react/rules/react/no-svg-without-title.js +1 -0
- package/dist/react/rules/react/no-unlabeled-icon-button.js +1 -0
- package/dist/react/rules/react/shared.js +1 -0
- package/dist/react/rules/tailwind/index.js +1 -0
- package/dist/react/rules/tailwind/prefer-cn.js +1 -0
- package/dist/react/rules/tailwind/shared.js +1 -0
- package/dist/react/rules/tailwind/use-logical-classes.js +1 -0
- package/dist/react/rules/tanstack-router/index.js +1 -0
- package/dist/react/rules/tanstack-router/no-search-casts.js +1 -0
- package/dist/react/rules/tanstack-router/require-selector.js +1 -0
- package/dist/react/rules/tanstack-router/shared.js +1 -0
- package/dist/react-native/index.d.ts +2 -0
- package/dist/react-native/index.js +1 -1
- package/dist/react-native/rules/legend-list/index.js +1 -1
- package/dist/react-native/rules/react-native/no-manual-memo.js +1 -1
- package/package.json +11 -11
- package/dist/core/rules/core/components-tsx-only.d.ts +0 -2
- package/dist/core/rules/core/hoist-intl.d.ts +0 -2
- package/dist/core/rules/core/index.d.ts +0 -2
- package/dist/core/rules/core/no-comments.d.ts +0 -2
- package/dist/core/rules/core/no-naming-convention.d.ts +0 -2
- package/dist/core/rules/core/no-packed-condition.d.ts +0 -2
- package/dist/core/rules/core/use-design-system.d.ts +0 -2
- package/dist/core/rules/zod/index.d.ts +0 -2
- package/dist/core/rules/zod/prefer-enum.d.ts +0 -2
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -1
- package/dist/lib/ast.d.ts +0 -44
- package/dist/lib/detect.d.ts +0 -9
- package/dist/lib/merge.d.ts +0 -9
- package/dist/lib/module.d.ts +0 -19
- package/dist/react/rules/i18n/index.d.ts +0 -2
- package/dist/react/rules/i18n/no-bare-attrs.d.ts +0 -2
- package/dist/react/rules/i18n/no-bare-text.d.ts +0 -2
- package/dist/react/rules/i18n/no-bare-toast.d.ts +0 -2
- package/dist/react/rules/query/index.d.ts +0 -2
- package/dist/react/rules/query/next-page-param-undefined.d.ts +0 -2
- package/dist/react/rules/query/no-deprecated-filters.d.ts +0 -2
- package/dist/react/rules/query/no-fetch-in-query-fn.d.ts +0 -2
- package/dist/react/rules/query/no-inline-keys.d.ts +0 -2
- package/dist/react/rules/query/require-destructured-hooks.d.ts +0 -2
- package/dist/react/rules/query/shared.d.ts +0 -2
- package/dist/react/rules/query/shared.js +0 -1
- package/dist/react/rules/zustand/index.d.ts +0 -2
- package/dist/react/rules/zustand/require-selector.d.ts +0 -2
- package/dist/react-native/rules/keyboard/index.d.ts +0 -2
- package/dist/react-native/rules/legend-list/index.d.ts +0 -2
- package/dist/react-native/rules/legend-list/no-flex-in-content-container.d.ts +0 -2
- package/dist/react-native/rules/legend-list/no-index-key-extractor.d.ts +0 -2
- package/dist/react-native/rules/legend-list/no-inline-data.d.ts +0 -2
- package/dist/react-native/rules/legend-list/no-inline-data.js +0 -1
- package/dist/react-native/rules/legend-list/no-inline-extra-data.d.ts +0 -2
- package/dist/react-native/rules/legend-list/no-inline-extra-data.js +0 -1
- package/dist/react-native/rules/legend-list/no-inline-render-item-props.d.ts +0 -2
- package/dist/react-native/rules/legend-list/no-mixed-children.d.ts +0 -2
- package/dist/react-native/rules/legend-list/no-remount-key.d.ts +0 -2
- package/dist/react-native/rules/legend-list/no-scrollview-map.d.ts +0 -2
- package/dist/react-native/rules/legend-list/no-unsupported-props.d.ts +0 -2
- package/dist/react-native/rules/legend-list/required-props.d.ts +0 -2
- package/dist/react-native/rules/legend-list/shared.d.ts +0 -19
- package/dist/react-native/rules/legend-list/typed-items-need-item-type.d.ts +0 -2
- package/dist/react-native/rules/legend-state/index.d.ts +0 -2
- package/dist/react-native/rules/legend-state/naming.d.ts +0 -2
- package/dist/react-native/rules/legend-state/no-assignment.d.ts +0 -2
- package/dist/react-native/rules/legend-state/no-nested-observable.d.ts +0 -2
- package/dist/react-native/rules/legend-state/no-object-selector.d.ts +0 -2
- package/dist/react-native/rules/legend-state/no-observable-in-component.d.ts +0 -2
- package/dist/react-native/rules/legend-state/no-peek-in-selector.d.ts +0 -2
- package/dist/react-native/rules/legend-state/no-react-mirror.d.ts +0 -2
- package/dist/react-native/rules/legend-state/no-untracked-get-in-jsx.d.ts +0 -2
- package/dist/react-native/rules/legend-state/shared.d.ts +0 -9
- package/dist/react-native/rules/react-native/hoist-stateless-function.d.ts +0 -2
- package/dist/react-native/rules/react-native/index.d.ts +0 -2
- package/dist/react-native/rules/react-native/no-conditional-style-array.d.ts +0 -2
- package/dist/react-native/rules/react-native/no-dynamic-import.d.ts +0 -2
- package/dist/react-native/rules/react-native/no-keyboard-will-events.d.ts +0 -2
- package/dist/react-native/rules/react-native/no-leaked-render.d.ts +0 -2
- package/dist/react-native/rules/react-native/no-manual-memo.d.ts +0 -2
- package/dist/react-native/rules/react-native/no-redundant-view-nesting.d.ts +0 -2
- package/dist/react-native/rules/react-native/no-rn-image-network-source.d.ts +0 -2
- package/dist/react-native/rules/react-native/no-rn-namespace-import.d.ts +0 -2
- package/dist/react-native/rules/react-native/no-scroll-position-state.d.ts +0 -2
- package/dist/react-native/rules/react-native/no-unlabeled-icon-pressable.d.ts +0 -2
- package/dist/react-native/rules/reanimated/animated-reaction-safety.d.ts +0 -2
- package/dist/react-native/rules/reanimated/animated-style-needs-animated-component.d.ts +0 -2
- package/dist/react-native/rules/reanimated/animated-updater-purity.d.ts +0 -2
- package/dist/react-native/rules/reanimated/gpu-properties-only.d.ts +0 -2
- package/dist/react-native/rules/reanimated/hoist-layout-animation-builder.d.ts +0 -2
- package/dist/react-native/rules/reanimated/index.d.ts +0 -2
- package/dist/react-native/rules/reanimated/interpolate-needs-clamp.d.ts +0 -2
- package/dist/react-native/rules/reanimated/no-react-state-from-continuous-worklet.d.ts +0 -2
- package/dist/react-native/rules/reanimated/no-shared-value-dot-value.d.ts +0 -2
- package/dist/react-native/rules/reanimated/prefer-lazy-shared-value-initializer.d.ts +0 -2
- package/dist/react-native/rules/reanimated/schedule-on-rn-scope.d.ts +0 -2
- package/dist/react-native/rules/reanimated/shared-value-usage.d.ts +0 -2
- package/dist/react-native/rules/reanimated/shared.d.ts +0 -5
- package/dist/react-native/rules/skia/canvas-opaque.d.ts +0 -2
- package/dist/react-native/rules/skia/index.d.ts +0 -2
- package/dist/react-native/rules/turbo-image/index.d.ts +0 -2
- package/dist/react-native/rules/turbo-image/require-cache-policy.d.ts +0 -2
- package/dist/react-native/rules/turbo-image/require-resize.d.ts +0 -2
- package/dist/react-native/rules/unistyles/animated-theme.d.ts +0 -2
- package/dist/react-native/rules/unistyles/content-container.d.ts +0 -2
- package/dist/react-native/rules/unistyles/in-sheet.d.ts +0 -2
- package/dist/react-native/rules/unistyles/index.d.ts +0 -2
- package/dist/react-native/rules/unistyles/insets.d.ts +0 -2
- package/dist/react-native/rules/unistyles/no-hardcoded-color.d.ts +0 -2
- package/dist/react-native/rules/unistyles/no-hardcoded-spacing.d.ts +0 -2
- package/dist/react-native/rules/unistyles/no-margin.d.ts +0 -2
- package/dist/react-native/rules/unistyles/no-style-spread.d.ts +0 -2
- package/dist/react-native/rules/unistyles/no-unused-styles.d.ts +0 -2
- package/dist/react-native/rules/unistyles/rtl-style-call.d.ts +0 -2
- package/dist/react-native/rules/unistyles/shared.d.ts +0 -16
- package/dist/react-native/rules/unistyles/theme-screen-component.d.ts +0 -2
- package/dist/react-native/rules/unistyles/theme-style-attr.d.ts +0 -2
- package/dist/react-native/stylesheet.d.ts +0 -11
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ashstack/lint
|
|
2
2
|
|
|
3
|
-
Strict shared [oxlint](https://oxc.rs) config:
|
|
3
|
+
Strict shared [oxlint](https://oxc.rs) config: <!-- rule-counts -->118 rules on plain TypeScript, 204 with React, 259 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
|
|
|
@@ -14,7 +14,7 @@ bun add -d oxlint oxlint-tsgolint @ashstack/lint
|
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
16
|
// oxlint.config.mts (JSON configs can't resolve npm packages, so TS config only)
|
|
17
|
-
import { core } from "@ashstack/lint"; // or: react,
|
|
17
|
+
import { core } from "@ashstack/lint/core"; // or: /react, /react-native
|
|
18
18
|
import { defineConfig } from "oxlint";
|
|
19
19
|
|
|
20
20
|
export default defineConfig({
|
|
@@ -29,6 +29,29 @@ oxlint --type-aware --deny-warnings .
|
|
|
29
29
|
|
|
30
30
|
Type-aware rules need the `oxlint-tsgolint` peer. Node 22.18+.
|
|
31
31
|
|
|
32
|
+
Import the entry you use. There is no package root to import from, so nothing
|
|
33
|
+
loads rule files you have no use for: `@ashstack/lint/core` loads 27 KB where
|
|
34
|
+
a root re-export of all three would load 137 KB. `react` and `react-native`
|
|
35
|
+
load most of it either way, since they configure most of the rules.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { core } from "@ashstack/lint/core";
|
|
39
|
+
import { react } from "@ashstack/lint/react";
|
|
40
|
+
import { reactNative } from "@ashstack/lint/react-native";
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Each entry also has a default export, and carries its own options type
|
|
44
|
+
(`CoreOptions`, `ReactOptions`, `ReactNativeOptions`).
|
|
45
|
+
|
|
46
|
+
`react()` adds nine rules from `eslint-plugin-react-you-might-not-need-an-effect`.
|
|
47
|
+
It is an optional peer, because it unpacks to 218 KB that a backend or CLI on
|
|
48
|
+
`core()` should not install. Add it for the effect rules; without it `react()`
|
|
49
|
+
works and leaves those nine out.
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
bun add -d eslint-plugin-react-you-might-not-need-an-effect
|
|
53
|
+
```
|
|
54
|
+
|
|
32
55
|
## What each entry adds
|
|
33
56
|
|
|
34
57
|
`core()` is a strict eslint / typescript / unicorn / promise / import base plus the `@ashstack/core/` convention rules. `react()` adds react, jsx-a11y, React Compiler and you-might-not-need-an-effect. `react-native()` adds the generic `@ashstack/react-native/` rules: leaked renders, view nesting, iOS-only keyboard events, remote images.
|
package/dist/core/index.d.ts
CHANGED
|
@@ -7,9 +7,11 @@ export declare const TEST_FILES: string[];
|
|
|
7
7
|
* Library modules (zod) auto-enable when the consumer depends on the library;
|
|
8
8
|
* pass an explicit boolean to force either way.
|
|
9
9
|
*
|
|
10
|
-
* Some of the enabled `typescript/` rules are type-aware (`no-
|
|
11
|
-
* `no-
|
|
10
|
+
* Some of the enabled `typescript/` rules are type-aware (`no-unnecessary-condition`,
|
|
11
|
+
* `no-unsafe-type-assertion`, …): they only report when oxlint runs with
|
|
12
12
|
* `--type-aware` and the `oxlint-tsgolint` peer installed.
|
|
13
13
|
*/
|
|
14
14
|
declare const core: (options?: CoreOptions) => OxlintConfig;
|
|
15
|
+
export { core };
|
|
16
|
+
export type { CoreOptions, ModuleManifest } from "../lib/types.js";
|
|
15
17
|
export default core;
|
package/dist/core/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{composeModules as o}from"../lib/module.js";import n from"./rules/core/index.js";import s from"./rules/zod/index.js";export const coreModules=[n,s];const t={"array-callback-return":["error",{allowImplicit:!0}],"constructor-super":"error","default-param-last":"error",eqeqeq:["error","always",{null:"ignore"}],"for-direction":"error","getter-return":"error","func-style":["error","expression"],"no-array-constructor":"error","no-async-promise-executor":"error","no-case-declarations":"error","no-class-assign":"error","no-compare-neg-zero":"error","no-cond-assign":"error","no-const-assign":"error","no-constant-condition":"error","no-control-regex":"error","no-debugger":"error","no-dupe-class-members":"error","no-dupe-else-if":"error","no-dupe-keys":"error","no-duplicate-case":"error","no-empty":"error","no-empty-character-class":"error","no-empty-pattern":"error","no-ex-assign":"error","no-extra-boolean-cast":"error","no-fallthrough":"error","no-func-assign":"error","no-global-assign":"error","no-import-assign":"error","no-irregular-whitespace":"error","no-loss-of-precision":"error","no-misleading-character-class":"error","no-negated-condition":"error","no-nested-ternary":"error","no-new-native-nonconstructor":"error","no-nonoctal-decimal-escape":"error","no-obj-calls":"error","no-param-reassign":"error","no-prototype-builtins":"error","no-redeclare":"off","no-regex-spaces":"error","no-self-assign":"error","no-setter-return":"error","no-shadow-restricted-names":"error","no-sparse-arrays":"error","no-this-before-super":"error","no-undef":"off","no-unreachable":"error","no-unsafe-finally":"error","no-unsafe-negation":"error","no-unsafe-optional-chaining":"error","no-unused-labels":"error","no-unused-private-class-members":"error","no-useless-catch":"error","no-useless-constructor":"error","no-useless-escape":"error","no-var":"error","no-with":"error","object-shorthand":["error","methods"],"prefer-rest-params":"error","require-yield":"error","use-isnan":"error","valid-typeof":"error","no-unused-vars":["error",{args:"after-used",argsIgnorePattern:"^_",caughtErrors:"all",caughtErrorsIgnorePattern:"^_",varsIgnorePattern:"^_",destructuredArrayIgnorePattern:"^_",ignoreRestSiblings:!0,fix:{imports:"safe-fix"}}],"no-use-before-define":"off",complexity:["error",{max:12}],"max-depth":["error",3],"max-params":["error",4],"max-lines":["error",{max:300,skipBlankLines:!0,skipComments:!0}],"max-lines-per-function":["error",{max:250,skipBlankLines:!0,skipComments:!0}],"eslint/no-await-in-loop":"error","eslint/no-console":"off","eslint/no-constant-binary-expression":"off","eslint/no-control-regex":"error","eslint/no-else-return":"error","eslint/no-empty":"error","eslint/no-empty-function":"error","eslint/no-new":"off","eslint/no-self-compare":"error","eslint/no-shadow":"error","eslint/no-template-curly-in-string":"error","eslint/no-unassigned-vars":"off","eslint/no-underscore-dangle":"off","eslint/no-unmodified-loop-condition":"off","eslint/no-unused-expressions":"off","eslint/no-useless-concat":"off","eslint/preserve-caught-error":"off","eslint/prefer-arrow-callback":"error","eslint/prefer-const":"error","eslint/prefer-template":"error","typescript/consistent-type-imports":"error","typescript/dot-notation":"error","typescript/no-confusing-void-expression":"error","typescript/no-empty-object-type":["error",{allowInterfaces:"with-single-extends"}],"typescript/no-explicit-any":"error","typescript/no-extra-non-null-assertion":"error","typescript/no-extraneous-class":"error","typescript/no-floating-promises":"
|
|
1
|
+
import{composeModules as o}from"../lib/module.js";import n from"./rules/core/index.js";import s from"./rules/zod/index.js";export const coreModules=[n,s];const t={"array-callback-return":["error",{allowImplicit:!0}],"constructor-super":"error","default-param-last":"error",eqeqeq:["error","always",{null:"ignore"}],"for-direction":"error","getter-return":"error","func-style":["error","expression"],"no-array-constructor":"error","no-async-promise-executor":"error","no-case-declarations":"error","no-class-assign":"error","no-compare-neg-zero":"error","no-cond-assign":"error","no-const-assign":"error","no-constant-condition":"error","no-control-regex":"error","no-debugger":"error","no-dupe-class-members":"error","no-dupe-else-if":"error","no-dupe-keys":"error","no-duplicate-case":"error","no-empty":"error","no-empty-character-class":"error","no-empty-pattern":"error","no-ex-assign":"error","no-extra-boolean-cast":"error","no-fallthrough":"error","no-func-assign":"error","no-global-assign":"error","no-import-assign":"error","no-irregular-whitespace":"error","no-loss-of-precision":"error","no-misleading-character-class":"error","no-negated-condition":"error","no-nested-ternary":"error","no-new-native-nonconstructor":"error","no-nonoctal-decimal-escape":"error","no-obj-calls":"error","no-param-reassign":"error","no-prototype-builtins":"error","no-redeclare":"off","no-regex-spaces":"error","no-self-assign":"error","no-setter-return":"error","no-shadow-restricted-names":"error","no-sparse-arrays":"error","no-this-before-super":"error","no-undef":"off","no-unreachable":"error","no-unsafe-finally":"error","no-unsafe-negation":"error","no-unsafe-optional-chaining":"error","no-unused-labels":"error","no-unused-private-class-members":"error","no-useless-catch":"error","no-useless-constructor":"error","no-useless-escape":"error","no-var":"error","no-with":"error","object-shorthand":["error","methods"],"prefer-rest-params":"error","require-yield":"error","use-isnan":"error","valid-typeof":"error","no-unused-vars":["error",{args:"after-used",argsIgnorePattern:"^_",caughtErrors:"all",caughtErrorsIgnorePattern:"^_",varsIgnorePattern:"^_",destructuredArrayIgnorePattern:"^_",ignoreRestSiblings:!0,fix:{imports:"safe-fix"}}],"no-use-before-define":"off",complexity:["error",{max:12}],"max-depth":["error",3],"max-params":["error",4],"max-lines":["error",{max:300,skipBlankLines:!0,skipComments:!0}],"max-lines-per-function":["error",{max:250,skipBlankLines:!0,skipComments:!0}],"eslint/no-await-in-loop":"error","eslint/no-console":"off","eslint/no-constant-binary-expression":"off","eslint/no-control-regex":"error","eslint/no-else-return":"error","eslint/no-empty":"error","eslint/no-empty-function":"error","eslint/no-new":"off","eslint/no-self-compare":"error","eslint/no-shadow":"error","eslint/no-template-curly-in-string":"error","eslint/no-unassigned-vars":"off","eslint/no-underscore-dangle":"off","eslint/no-unmodified-loop-condition":"off","eslint/no-unused-expressions":"off","eslint/no-useless-concat":"off","eslint/preserve-caught-error":"off","eslint/prefer-arrow-callback":"error","eslint/prefer-const":"error","eslint/prefer-template":"error","typescript/consistent-type-imports":"error","typescript/dot-notation":"error","typescript/no-confusing-void-expression":"error","typescript/no-empty-object-type":["error",{allowInterfaces:"with-single-extends"}],"typescript/no-explicit-any":"error","typescript/no-extra-non-null-assertion":"error","typescript/no-extraneous-class":"error","typescript/no-floating-promises":"off","typescript/no-import-type-side-effects":"error","typescript/no-misused-new":"error","typescript/no-misused-promises":["error",{checksVoidReturn:!1}],"typescript/no-namespace":"error","typescript/no-non-null-asserted-optional-chain":"error","typescript/no-non-null-assertion":"error","typescript/no-unnecessary-boolean-literal-compare":"error","typescript/no-unnecessary-condition":"error","typescript/no-unnecessary-type-assertion":"error","typescript/no-unnecessary-type-constraint":"error","typescript/no-unsafe-declaration-merging":"error","typescript/no-unsafe-function-type":"error","typescript/no-unsafe-type-assertion":"error","typescript/no-wrapper-object-types":"error","typescript/prefer-as-const":"error","typescript/prefer-namespace-keyword":"error","typescript/prefer-nullish-coalescing":"error","typescript/require-await":"error","typescript/switch-exhaustiveness-check":["error",{considerDefaultExhaustiveForUnions:!0}],"import/no-commonjs":"off","import/no-cycle":"error","import/no-duplicates":"error","import/no-named-as-default":"off","import/no-named-as-default-member":"off","import/no-self-import":"off","import/no-unassigned-import":"off","unicorn/consistent-function-scoping":"off","unicorn/no-array-reverse":"off","unicorn/no-array-sort":"off","unicorn/no-empty-file":"off","unicorn/no-new-array":"off","unicorn/no-useless-fallback-in-spread":"off","unicorn/no-useless-spread":"off","unicorn/prefer-add-event-listener":"off","unicorn/prefer-array-flat-map":"error","unicorn/prefer-date-now":"error","unicorn/prefer-logical-operator-over-ternary":"error","unicorn/prefer-node-protocol":"error","unicorn/prefer-optional-catch-binding":"error","unicorn/require-module-specifiers":"off","unicorn/throw-new-error":"error","promise/always-return":"off","promise/no-callback-in-promise":"off","promise/no-multiple-resolved":"off","promise/no-promise-in-callback":"off","oxc/no-async-endpoint-handlers":"off"};export const TEST_FILES=["**/*.test.ts","**/*.test.tsx","**/*.spec.ts","**/*.spec.tsx","**/__tests__/**","**/tests/**"];const core=(e={})=>{const r=o(coreModules,e);return{plugins:["eslint","typescript","import","unicorn","promise","oxc"],jsPlugins:r.jsPlugins,env:{builtin:!0,es2024:!0},categories:{correctness:"error",suspicious:"error",perf:"error",pedantic:"off",style:"off",restriction:"off",nursery:"off"},rules:{...t,...r.rules},overrides:[{files:["**/*.tsx"],rules:{"func-style":"off"}},{files:["**/*.d.ts"],rules:{"typescript/no-explicit-any":"off"}},{files:TEST_FILES,rules:{"max-lines":"off","max-lines-per-function":"off","typescript/await-thenable":"off","typescript/no-non-null-assertion":"off","typescript/no-unsafe-type-assertion":"off","typescript/require-await":"off","typescript/unbound-method":"off"}}]}};export{core};export default core;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{optionsOf as
|
|
1
|
+
import{optionsOf as b}from"../../../lib/ast.js";const v=["@ts-expect-error","@ts-ignore","@ts-nocheck","@ts-check","@jsx","@jsxRuntime","@jsxImportSource","prettier-ignore","eslint-disable","eslint-enable","oxlint-disable","oxlint-enable","oxfmt-ignore","react-doctor-disable","@type","<reference","#__PURE__","@__PURE__","v8 ignore","c8 ignore","istanbul ignore","@vitest-environment","EXPECT_PASS","EXPECT_FAIL"],E=/^(?<kind>what|why):\s*(?<fact>[\s\S]+)$/i,m=new Set(["Block","MultiLine"]),k=new Set(["Shebang","Hashbang"]),u=10,f=120,C=2,D="Make the code say this, then delete the line — nearly every comment here is one rename or one refactor away from unnecessary. Reach for the move that fits: Rename the value, function or type to the plain word this sentence uses; Extract Function for the block it summarises, named after the claim it makes; Guard Clause for the nesting it walks the reader through; split the clever expression it decodes into named steps. You are done when the sentence's content is legible in an identifier, a signature or the control flow.",A=" The `// what:` hatch carries one kind of fact: one that outlives this code — a platform bug, an upstream contract, a number measured on a device. A fact about what this code does fails that test, and the refactor is still owed.",_=" This project runs with the `// what:` hatch turned off, so the refactor is the only way out. The one line left is `// why:`, which a kept `memo` requires.",x=(e)=>D+(e?A:_),H="Attach this block to the declaration it documents, or Rename and Extract Function until the code reads without it. A `/** */` block earns its place as documentation of the symbol directly beneath it.",d={block:"Rewrite this as a single `// what: <fact>` line comment. If the fact needs a paragraph, name the pieces in code until it fits on one line.",shortFact:`Write the fact after \`what:\` (at least ${u} characters), or delete the comment.`,tooLong:`Trim this \`what:\` line under ${f} characters, moving what is left into names in the code.`,stacked:"Keep one `what:` line here — the single irreducible fact — and refactor what the others explain into named values and functions."},M=(e,t)=>`Delete \`what:\` comments from this file until at most ${e} remain (it has ${t}): move the logic each one annotates into a function whose name carries what the comment says.`,P=new Set(["ExportNamedDeclaration","ExportDefaultDeclaration","ExportAllDeclaration","FunctionDeclaration","VariableDeclaration","ClassDeclaration","MethodDefinition","PropertyDefinition","Property","TSInterfaceDeclaration","TSTypeAliasDeclaration","TSEnumDeclaration","TSEnumMember","TSModuleDeclaration","TSDeclareFunction","TSPropertySignature","TSMethodSignature","TSIndexSignature","TSCallSignatureDeclaration","TSConstructSignatureDeclaration","TSAbstractMethodDefinition","TSAbstractPropertyDefinition","AccessorProperty","ImportDeclaration"]),O=(e)=>(m.has(e.type)?e.value.replace(/^[*\s]+/,""):e.value).trim(),R=(e)=>v.some((t)=>e.startsWith(t)),F=(e)=>m.has(e.type)&&e.value.startsWith("*"),I=(e)=>{const t=[];for(const o of e.sourceCode.getAllComments()){if(k.has(o.type))continue;const l=O(o);if(R(l))continue;t.push({comment:o,body:l})}return t},L=(e,t)=>{let o=t;while(/\s/.test(e.charAt(o)))o+=1;return o},p=(e,t)=>{const o=E.exec(e.body)?.groups;if(!o?.kind||!o.fact)return[];const l=o.kind.toLowerCase()==="why"?"why":"what";if(l==="what"&&!t)return[];return[{...e,kind:l,fact:o.fact}]},N=(e,t,o)=>{if(m.has(e.type))return d.block;if(o.trim().length<u)return d.shortFact;if(t.length>f)return d.tooLong;return null},j=(e,t,o)=>e.slice(t.end,o.start).trim()==="",w=(e)=>e.escapeHatch!==!1;export const noComments={meta:{type:"problem",docs:{description:'Disallow every comment that is neither a `// what: <fact>` line, a `// why:` marker, nor a tooling directive. The message names the refactoring that removes it: Rename, Extract Function, Guard Clause. Surviving `// what:` lines are held to one short line each, at most `budget` per file (default 2); `escapeHatch: false` removes that hatch, so no discretionary prose survives. A `// why:` line is the marker `@ashstack/react-native/no-manual-memo` requires above a kept `memo`: held to the same one-line shape, never counted against `budget`, and kept even with `escapeHatch: false`, so the two rules run together. With `jsdoc: "allow"`, a `/** */` block documenting the declaration directly beneath it is kept, while a floating one still reports.'},schema:[{type:"object",properties:{jsdoc:{enum:["allow","report"]},escapeHatch:{type:"boolean"},budget:{type:"integer",minimum:0}},additionalProperties:!1}]},createOnce(e){const t=new Set,o=(n)=>{t.add(n.start)},l={};for(const n of P)l[n]=o;const h=(n,r)=>{e.report({node:n,message:r})},g=(n,r)=>{const{text:s}=e.sourceCode,c=w(r);for(const a of n){const{comment:i}=a;if(p(a,c).length>0)continue;if(r.jsdoc!=="allow"||!F(i))h(i,x(c));else if(!t.has(L(s,i.end)))h(i,H)}},S=(n,r)=>{const s=[];for(const c of n)for(const a of p(c,r)){const i=N(a.comment,a.body,a.fact);if(i===null)s.push(a);else h(a.comment,i)}return s},T=(n)=>{const r=e.sourceCode.getText(),s=n.map((c)=>c.comment);for(const[c,a]of s.entries()){const i=s[c-1];if(i&&j(r,i,a))h(a,d.stacked)}},y=(n,r)=>{const s=r.budget??C,c=n.filter((i)=>i.kind==="what"),a=c[s];if(a)h(a.comment,M(s,c.length))};return{...l,before(){t.clear();return!0},"Program:exit"(){const n=b(e,{}),r=I(e);g(r,n);const s=S(r,w(n));T(s);y(s,n)}}}};
|
package/dist/lib/ast.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const p=new Set(["parent","loc","range","start","end","type"]),o=(t,e)=>{for(const r of t){const n=findInSubtree(r,e);if(n)return n}return null},i=(t)=>{const e=[];for(const[r,n]of Object.entries(t)){if(p.has(r))continue;if(n!==null&&typeof n==="object")e.push(n)}return e};export const findInSubtree=(t,e)=>{if(t===null||typeof t!=="object")return null;if(Array.isArray(t))return o(t,e);const r=t;if(typeof r.type!=="string")return null;if(e(r))return r;return o(i(r),e)},subtreeHas=(t,e)=>findInSubtree(t,e)!==null,calleeName=(t)=>{if(t?.type!=="CallExpression")return"";const{callee:e}=t;if(e.type==="Identifier")return e.name;if(e.type==="MemberExpression"&&e.property.type==="Identifier")return e.property.name;return""},ancestors=function*(t){let e=t.parent;while(e){yield e;e=e.parent}},enclosingCall=(t,e)=>{for(const r of ancestors(t))if(r.type==="CallExpression"&&e.has(calleeName(r)))return r;return null},closestAncestor=(t,e)=>{for(const r of ancestors(t))if(e.has(r.type))return r;return null},hasAncestor=(t,e)=>{for(const r of ancestors(t))if(e(r))return!0;return!1},isWithin=(t,e)=>{for(const r of ancestors(t))if(r===e)return!0;return!1},crossesFunctionBefore=(t,e,r)=>{for(const n of ancestors(t)){if(n===e)return!1;if(r.has(n.type))return!0}return!1},isMemberCall=(t,e)=>t?.type==="CallExpression"&&t.callee.type==="MemberExpression"&&!t.callee.computed&&t.callee.property.type==="Identifier"&&t.callee.property.name===e,receiverName=(t)=>{if(t?.type!=="CallExpression"||t.callee.type!=="MemberExpression")return null;const{object:e}=t.callee;return e.type==="Identifier"?e.name:null},tagIdentifier=(t)=>{let e=t;while(e?.type==="JSXMemberExpression")e=e.property;if(e?.type==="JSXIdentifier"||e?.type==="Identifier")return e.name;return""},tagPath=(t)=>{if(t?.type==="JSXIdentifier"||t?.type==="Identifier")return t.name;if(t?.type!=="JSXMemberExpression")return"";const e=tagPath(t.object),r=tagPath(t.property);return e===""||r===""?"":`${e}.${r}`};const s=/^(?:Animated|Reanimated)/;export const componentName=(t)=>{const e=tagPath(t),r=e.lastIndexOf(".");if(r!==-1)return s.test(e.slice(0,r))?e.slice(r+1):"";const n=e.replace(s,"");return n===""?e:n},attributeName=(t)=>{if(t.type!=="JSXAttribute")return"";const{name:e}=t;return e.type==="JSXIdentifier"?e.name:e.name.name},importedSpecifiers=(t,e)=>t.type==="ImportDeclaration"&&t.source.value===e?[...t.specifiers]:[],importedNames=(t,e)=>{const r=[];for(const n of importedSpecifiers(t,e)){if(n.type!=="ImportSpecifier")continue;if(n.imported.type!=="Identifier")continue;r.push({imported:n.imported.name,local:n.local.name})}return r},propertyKeyName=(t)=>{if(t.type!=="Property"||t.computed)return"";const{key:e}=t;if(e.type==="Identifier")return e.name;return e.type==="Literal"?String(e.value):""},propertyValue=(t,e)=>{if(t?.type!=="ObjectExpression")return null;for(const r of t.properties)if(r.type==="Property"&&propertyKeyName(r)===e)return r.value;return null},FUNCTION_TYPES=new Set(["FunctionDeclaration","FunctionExpression","ArrowFunctionExpression"]),COMPONENT_OR_HOOK=/^(?:[A-Z]|use[A-Z])/,isFunction=(t)=>!!t&&FUNCTION_TYPES.has(t.type),gate=(t,...e)=>e.some((r)=>t.sourceCode.text.includes(r)),optionsOf=(t,e)=>t.options[0]??e,problem=(t,e)=>({...e,meta:{type:"problem",...e.meta,docs:{description:t}}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{fileURLToPath as e}from"node:url";const t="eslint-plugin-react-you-might-not-need-an-effect",r={"react-effect/no-derived-state":"error","react-effect/no-chain-state-updates":"error","react-effect/no-event-handler":"error","react-effect/no-adjust-state-on-prop-change":"error","react-effect/no-reset-all-state-on-prop-change":"error","react-effect/no-pass-live-state-to-parent":"error","react-effect/no-pass-data-to-parent":"error","react-effect/no-external-store-subscription":"error","react-effect/no-initialize-state":"error"};export const effectPlugin=()=>{try{return{present:!0,jsPlugins:[{name:"react-effect",specifier:e(import.meta.resolve(t))}],rules:r}}catch{return{present:!1,jsPlugins:[],rules:{}}}};
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -85,6 +85,10 @@ export type ReactOptions = CoreOptions & {
|
|
|
85
85
|
zustand?: boolean;
|
|
86
86
|
/** `@ashstack/i18n/` rules — detected from i18next/lingui/react-intl/use-intl/next-intl/expo-localization */
|
|
87
87
|
i18n?: boolean;
|
|
88
|
+
/** `@ashstack/tailwind/` rules — detected from `tailwindcss` */
|
|
89
|
+
tailwind?: boolean;
|
|
90
|
+
/** `@ashstack/tanstack-router/` rules — detected from `@tanstack/react-router` */
|
|
91
|
+
tanstackRouter?: boolean;
|
|
88
92
|
};
|
|
89
93
|
/**
|
|
90
94
|
* Every toggle is a rule module — one rule namespace each. Import bans and
|
|
@@ -111,4 +115,5 @@ export type ReactNativeOptions = ReactOptions & {
|
|
|
111
115
|
export type OxlintConfig = OxlintNativeConfig;
|
|
112
116
|
/** The `rules` block of a config: oxlint's own map, so a built-in rule's options are checked. */
|
|
113
117
|
export type RuleMap = NonNullable<OxlintConfig["rules"]>;
|
|
118
|
+
export type JsPlugin = NonNullable<OxlintConfig["jsPlugins"]>[number];
|
|
114
119
|
export {};
|
package/dist/react/index.d.ts
CHANGED
|
@@ -6,4 +6,6 @@ export declare const reactModules: import("../lib/types.js").ModuleManifest[];
|
|
|
6
6
|
* modules (query, zustand, i18n).
|
|
7
7
|
*/
|
|
8
8
|
declare const react: (options?: ReactOptions) => OxlintConfig;
|
|
9
|
+
export { react };
|
|
10
|
+
export type { ReactOptions, ModuleManifest } from "../lib/types.js";
|
|
9
11
|
export default react;
|
package/dist/react/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import t,{coreModules as a}from"../core/index.js";import{effectPlugin as s}from"../lib/effect-plugin.js";import{mergeConfigs as n}from"../lib/merge.js";import{composeModules as c}from"../lib/module.js";import i from"./rules/i18n/index.js";import l from"./rules/query/index.js";import u from"./rules/react/index.js";import p from"./rules/tailwind/index.js";import f from"./rules/tanstack-router/index.js";import m from"./rules/zustand/index.js";export const reactModules=[u,l,m,i,p,f];const d={"eslint/no-empty":"off","eslint/no-empty-function":"off"},x={"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":"error","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":"error","react/invariant":"error","react/rule-suppression":"error","react/syntax":"error","react/todo":"error","react/unsupported-syntax":"error"},y=["**/routes/**","**/src/app/**","**/app/**/_layout.tsx","**/app/**/+*.tsx"],j={...d,"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-constructed-context-values":"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",...x,"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=(r={})=>{const e=c([...a,...reactModules],r),o=s();return n(t(r),{plugins:["react","jsx-a11y","react-perf"],jsPlugins:[...o.jsPlugins,...e.jsPlugins],rules:{...j,...o.rules,...e.rules},overrides:[{files:y,rules:{"unicorn/filename-case":"off"}}]})};export{react};export default react;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{crossesFunctionBefore as n,findInSubtree as o,FUNCTION_TYPES as u,gate as l,problem as s,propertyKeyName as i}from"../../../lib/ast.js";const g="Return `undefined` to signal there are no more pages. `null` is a valid page param, so returning it reads as a real next cursor and the list keeps fetching forever.",p=(e)=>e.type==="ReturnStatement"&&e.argument?.type==="Literal"&&e.argument.value===null,m=(e)=>e.type==="ArrowFunctionExpression"&&e.body.type==="Literal"&&e.body.value===null?e.body:null;export const nextPageParamUndefined=s("Disallow `return null` in the body of a `getNextPageParam`.",{createOnce(e){return{before(){return l(e,"getNextPageParam")},Property(t){if(i(t)!=="getNextPageParam")return;const{value:r}=t,a=m(r)??o(r,p);if(!a||n(a,r,u))return;e.report({node:r,message:g})}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{gate as r,problem as a,subtreeHas as
|
|
1
|
+
import{gate as r,problem as a,propertyKeyName as n,subtreeHas as s}from"../../../lib/ast.js";const o="Call a typed function from the feature's `*.requests.ts` module here instead of `fetch`. That client is what sends the auth header, applies the timeout, retries a 429 or 5xx, and throws the app's error type.",i=new Set(["queryFn","mutationFn"]),u=(e)=>e.type==="CallExpression"&&e.callee.type==="Identifier"&&e.callee.name==="fetch";export const noFetchInQueryFn=a("Disallow a bare `fetch(` inside a `queryFn` or `mutationFn`.",{createOnce(e){return{before(){return r(e,"queryFn","mutationFn")},Property(t){if(!i.has(n(t)))return;if(!s(t.value,u))return;e.report({node:t.value,message:o})}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{problem as a}from"../../../lib/ast.js";
|
|
1
|
+
import{problem as a,propertyKeyName as y}from"../../../lib/ast.js";const i={inlineQueryKey:"Define this key in the feature's `*.keys.ts` factory and reference it: `queryKey: someKeys.scope(...)`, so invalidation elsewhere matches the same key.",inlineGetQueryData:"Read through the feature's `*.keys.ts` factory: `getQueryData(someKeys.scope())`.",inlineSetQueryData:"Write through the feature's `*.keys.ts` factory: `setQueryData(someKeys.scope(), updater)`."},o=new Set(["useQuery","useSuspenseQuery","useInfiniteQuery","useSuspenseInfiniteQuery","useQueries"]),p=new Set(["invalidateQueries","removeQueries","refetchQueries","cancelQueries","resetQueries","fetchQuery","prefetchQuery","ensureQueryData","getQueriesData","setQueriesData"]),c=(t)=>{if(t.type!=="CallExpression")return"";const{callee:e}=t;if(e.type!=="MemberExpression"||e.computed)return"";const{property:r}=e;return r.type==="Identifier"?r.name:""},f=(t)=>{if(t.type!=="CallExpression")return;const[e]=t.arguments;return e?.type==="ArrayExpression"?e:void 0},l=(t,e)=>{const r=t.type==="CallExpression"?t.callee:void 0;return r?.type==="Identifier"&&o.has(r.name)||p.has(e)},Q=(t)=>{if(t.type!=="Property"||t.computed)return!1;const{key:e}=t;if(e.type==="Identifier")return e.name==="queryKey";return e.type==="Literal"&&e.value==="queryKey"},m=(t)=>{if(t.type!=="ObjectExpression")return[];const e=t.properties.find((r)=>r.type==="Property"&&y(r)==="queries");if(e?.type!=="Property"||e.value.type!=="ArrayExpression")return[t];return[t,...e.value.elements.filter((r)=>r!==null)]},h=(t)=>{if(t.type!=="CallExpression")return[];const[e]=t.arguments;if(e===void 0)return[];const r=[];for(const s of m(e)){if(s.type!=="ObjectExpression")continue;for(const n of s.properties){if(n.type!=="Property"||!Q(n))continue;const{value:u}=n;if(u.type==="ArrayExpression")r.push(u)}}return r};export const noInlineKeys=a("Disallow a query key written as an array literal at the call site instead of coming from a keys factory.",{createOnce(t){return{CallExpression(e){const r=c(e);if(r==="getQueryData"||r==="setQueryData"){const s=f(e);if(s===void 0)return;t.report({node:s,message:r==="getQueryData"?i.inlineGetQueryData:i.inlineSetQueryData});return}if(!l(e,r))return;for(const s of h(e))t.report({node:s,message:i.inlineQueryKey})}}}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineModule as t}from"../../../lib/module.js";import{noSvgWithoutTitle as e}from"./no-svg-without-title.js";import{noUnlabeledIconButton as o}from"./no-unlabeled-icon-button.js";export default t({meta:{name:"@ashstack/react"},url:import.meta.url,docsWhen:"always on via `react()` and every entry above it",rules:{"no-unlabeled-icon-button":o,"no-svg-without-title":e}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{attributeName as a,gate as s,problem as o}from"../../../lib/ast.js";import{childNames as l,namedBy as m,NAMING_ATTRIBUTES as c}from"./shared.js";const f=new Set(["presentation","none"]),p='Give this `<svg>` a `<title>` child with content, or an `aria-label`, so a screen reader can name it. If it carries no meaning of its own, mark it `aria-hidden="true"` or `role="presentation"` and let the text beside it do the naming.',u=(e)=>{if(e?.type==="Literal")return e.value!=="false";if(e?.type!=="JSXExpressionContainer")return!0;const{expression:t}=e;return!(t.type==="Literal"&&(t.value===!1||t.value==="false"))},d=(e)=>e?.type==="Literal"&&typeof e.value==="string"&&f.has(e.value),g=(e)=>{if(e.type!=="JSXAttribute")return!1;const t=a(e);if(t==="aria-hidden")return u(e.value);if(t==="role")return d(e.value);return m(e,c)};export const noSvgWithoutTitle=o("Require an accessible name on an inline `<svg>`: a `<title>` child with content, an `aria-label`, or a marker that it is decorative. Every child counts, and a self-closing `<svg />` reports.",{createOnce(e){const t=(r)=>{if(r.type!=="JSXElement")return!1;const{name:n}=r.openingElement;if(n.type!=="JSXIdentifier"||n.name!=="title")return!1;return r.children.some((i)=>l(i,e)||i.type==="JSXElement"||i.type==="JSXFragment")};return{before(){return s(e,"<svg")},JSXElement(r){const n=r.openingElement;if(n.name.type!=="JSXIdentifier"||n.name.name!=="svg")return;if(n.attributes.some(g))return;if(r.children.some(t))return;e.report({node:n.name,message:p})}}}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{attributeName as o,findInSubtree as i,gate as s,problem as l,tagIdentifier as m}from"../../../lib/ast.js";import{attributeHasContent as u,childNames as c,namedBy as b,NAMING_ATTRIBUTES as p}from"./shared.js";const g=new Set(["button","Button"]),S="Add an `aria-label` (or `aria-labelledby`), or render visible text, so this icon-only button has an accessible name. An `<img alt>` or an `<svg><title>` inside it counts too.",d=(e)=>e.type==="JSXAttribute"&&o(e)==="alt"&&u(e.value),f=(e)=>e.type==="JSXOpeningElement"&&m(e.name)==="img"&&e.attributes.some(d);export const noUnlabeledIconButton=l("Require an accessible name on an icon-only `<button>` or `<Button>`. Without a label, visible text or image alt text, a screen reader cannot reach the control.",{createOnce(e){const r=(t)=>c(t,e)||f(t);return{before(){return s(e,"button","Button")},JSXElement(t){const a=t.openingElement;if(a.name.type!=="JSXIdentifier"||!g.has(a.name.name))return;if(a.attributes.some((n)=>b(n,p)))return;if(!t.children.some((n)=>n.type==="JSXElement"||n.type==="JSXFragment"))return;if(t.children.some((n)=>i(n,r)!==null))return;e.report({node:a.name,message:S})}}}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{attributeName as r}from"../../../lib/ast.js";export const NAMING_ATTRIBUTES=new Set(["aria-label","aria-labelledby"]);const n=/^(?:null|undefined|false|true|""|''|``)$/;export const attributeHasContent=(e)=>{if(e?.type==="Literal")return typeof e.value==="string"&&e.value.trim()!=="";return e?.type==="JSXExpressionContainer"},namedBy=(e,t)=>e.type==="JSXAttribute"&&t.has(r(e))&&attributeHasContent(e.value),childNames=(e,t)=>{if(e.type==="JSXText")return e.value.trim()!=="";if(e.type!=="JSXExpressionContainer")return!1;return!n.test(t.sourceCode.getText(e.expression).trim())};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineModule as e}from"../../../lib/module.js";import{preferCn as a}from"./prefer-cn.js";import{useLogicalClasses as s}from"./use-logical-classes.js";export default e({meta:{name:"@ashstack/tailwind"},url:import.meta.url,packages:["tailwindcss"],option:"tailwind",docsWhen:"auto-enabled by `react()` when `tailwindcss` is a dependency",rules:{"prefer-cn":a,"use-logical-classes":s}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{attributeName as f,importedNames as h,problem as m}from"../../../lib/ast.js";import{CLASS_ATTRIBUTE as d,CLASS_BINDING as S,CLASS_MARKER as y,COMPOSER_MODULES as b,KNOWN_COMPOSERS as c}from"./shared.js";const g=new Set(["join","concat"]),w=new Set(["ConditionalExpression","LogicalExpression"]),a={attribute:"Wrap this class value in `cn(...)`. It folds the conditional inputs into one string and lets a later Tailwind utility win over an earlier one that sets the same property.",precomputed:"Wrap this value in `cn(...)` where it reaches the class prop, or build it with `cn(...)` where it is declared. Conflicting Tailwind utilities survive into the DOM otherwise.",declaration:"Build this class value with `cn(...)` instead of joining it by hand, so conflicting Tailwind utilities resolve to the last one and falsy branches drop out."},E=(e,r)=>{if(e.type==="MemberExpression")return e.property.type==="Identifier"&&g.has(e.property.name);if(e.type!=="Identifier")return!1;return c.has(e.name)?e.name!=="cn":r.has(e.name)},p=(e,r)=>{if(!e)return!1;if(w.has(e.type))return!0;if(e.type==="TemplateLiteral")return e.expressions.length>0;if(e.type==="BinaryExpression")return e.operator==="+";return e.type==="CallExpression"&&E(e.callee,r)},C=(e)=>{if(e.type!=="JSXAttribute")return null;const{value:r}=e;return r?.type==="JSXExpressionContainer"?r.expression:null};export const preferCn=m("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.",{createOnce(e){const r=new Set,s=new Map,o=[],u=()=>{const t=new Set;for(const[n,i]of s){if(!S.test(n))continue;if(!p(i,r))continue;t.add(n);e.report({node:i.parent??i,message:a.declaration})}return t},l=(t)=>{for(const n of o){const i=C(n);if(!i)continue;if(p(i,r)){e.report({node:n,message:a.attribute});continue}if(i.type!=="Identifier"||t.has(i.name))continue;if(!p(s.get(i.name),r))continue;e.report({node:n,message:a.precomputed})}};return{before(){r.clear();s.clear();o.length=0;return y.test(e.sourceCode.text)},ImportDeclaration(t){for(const n of b)for(const{local:i}of h(t,n))if(!c.has(i))r.add(i)},VariableDeclarator(t){if(t.id.type==="Identifier"&&t.init)s.set(t.id.name,t.init)},JSXAttribute(t){if(d.test(f(t)))o.push(t)},"Program:exit"(){l(u())}}}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CLASS_ATTRIBUTE=/^(?:class|className|[A-Za-z_$][A-Za-z0-9_$]*ClassName)$/,CLASS_BINDING=/^[A-Za-z_$][A-Za-z0-9_$]*(?:class(?:es|name|names)?|styles?)$/i,CLASS_MARKER=/class|style/i,KNOWN_COMPOSERS=new Set(["cn","clsx","classNames","classnames","twMerge"]),COMPOSER_MODULES=["clsx","classnames","tailwind-merge"];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{attributeName as l,hasAncestor as o,problem as n}from"../../../lib/ast.js";import{CLASS_ATTRIBUTE as d,CLASS_BINDING as f}from"./shared.js";const u="ml|mr|pl|pr|left|right|scroll-ml|scroll-mr|scroll-pl|scroll-pr",c="border-l|border-r|rounded-l|rounded-r|rounded-tl|rounded-tr|rounded-bl|rounded-br",m="text-left|text-right|float-left|float-right|clear-left|clear-right",p=String.raw`(?:(?:${u})-\S+|(?:${c})(?:-\S+)?|${m})`,s=String.raw`[\s"'\`{}]`,r=new RegExp(String.raw`(?:^|${s})(?:[^\s:]+:)*!?-?${p}(?:$|${s})`),a="Swap this for its logical twin so the layout mirrors in Arabic and Hebrew: `ms`/`me` instead of `ml`/`mr`, `text-start` instead of `text-left`. Padding, insets, borders and corners follow the same pattern. Keep the physical utility only where the coordinate really is the screen rather than the reading order.",i=(t)=>o(t,(e)=>{if(e.type==="JSXAttribute")return d.test(l(e));if(e.type!=="VariableDeclarator")return!1;return e.id.type==="Identifier"&&f.test(e.id.name)});export const useLogicalClasses=n("Require a logical Tailwind utility over its physical left/right twin, so a right-to-left layout mirrors. Reads class props and bindings named after classes, through variant prefixes and the `!` and `-` modifiers.",{createOnce(t){return{before(){return r.test(t.sourceCode.text)},Literal(e){if(typeof e.value!=="string")return;if(!r.test(e.value))return;if(!i(e))return;t.report({node:e,message:a})},TemplateElement(e){if(!r.test(e.value.raw))return;if(!i(e))return;t.report({node:e,message:a})}}}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineModule as e}from"../../../lib/module.js";import{noSearchCasts as t}from"./no-search-casts.js";import{requireSelector as r}from"./require-selector.js";export default e({meta:{name:"@ashstack/tanstack-router"},url:import.meta.url,packages:["@tanstack/react-router"],option:"tanstackRouter",docsWhen:"auto-enabled by `react()` when `@tanstack/react-router` is a dependency",rules:{"require-selector":r,"no-search-casts":t}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{gate as u,importedNames as m,problem as f}from"../../../lib/ast.js";import{ROUTER_MODULE as h}from"./shared.js";const d=["search","location","state"],l={search:"Drop this assertion and let the route's `validateSearch` schema type the result. An `as` here claims a shape the URL never has to honour; widen the schema, or parse the value, if the type is wrong.",routerState:"Drop this assertion and read validated search through `useSearch({ from: ... })`. `router.state.location.search` is whatever the URL held, so an `as` here is a claim about a string nobody checked."},S=(t)=>{let e=t;while(e.type==="TSAsExpression")e=e.expression;return e},c=(t)=>t?.type==="CallExpression"&&t.callee.type==="Identifier"?t.callee.name:"",y=(t)=>{if(t.type!=="MemberExpression")return"";const{property:e}=t;if(e.type==="Identifier")return t.computed?"":e.name;return e.type==="Literal"?String(e.value):""},g=(t)=>{let e=t;for(const s of d){if(e.type!=="MemberExpression"||y(e)!==s)return null;e=e.object}return e};export const noSearchCasts=f("Disallow an `as` assertion on a `useSearch()` result or on `router.state.location.search`. The route's `validateSearch` schema is what supplies the type.",{createOnce(t){const e=new Set,s=new Set,n=new Set,i=[],p=(r)=>r.type==="Identifier"?n.has(r.name):s.has(c(r));return{before(){e.clear();s.clear();n.clear();i.length=0;return u(t,h)},ImportDeclaration(r){for(const{imported:a,local:o}of m(r,h))if(a==="useSearch")e.add(o);else if(a==="useRouter")s.add(o)},VariableDeclarator(r){if(r.id.type!=="Identifier")return;if(s.has(c(r.init)))n.add(r.id.name)},TSAsExpression(r){if(r.parent.type==="TSAsExpression")return;i.push({node:r,inner:S(r.expression)})},"Program:exit"(){for(const{node:r,inner:a}of i){if(e.has(c(a))){t.report({node:r,message:l.search});continue}const o=g(a);if(o&&p(o))t.report({node:r,message:l.routerState})}}}}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{gate as h,importedNames as m,problem as p,propertyValue as i}from"../../../lib/ast.js";import{ROUTER_MODULE as l}from"./shared.js";const d=new Set(["useLocation","useRouterState"]),c={whole:"This hook subscribes to the whole router value, so the component re-renders on every navigation. Pass a `select` that returns what it reads, e.g. `{ select: location => location.pathname }`.",noSelect:"This hook subscribes to the whole router value, so the component re-renders on every navigation. Add a `select` returning the smallest value it reads.",search:"`useSearch({ strict: false })` subscribes to every route-search change. Add a `select` returning the keys this component reads."},u=(t)=>i(t,"select")!==null,f=(t)=>{const s=i(t,"strict");return s?.type==="Literal"&&s.value===!1};export const requireSelector=p("Require a `select` on `useLocation`, `useRouterState` and non-strict `useSearch`, so a component reads the smallest router value it needs instead of re-rendering on every navigation.",{createOnce(t){const s=new Set,a=new Set,n=[];return{before(){s.clear();a.clear();n.length=0;return h(t,l)},ImportDeclaration(e){for(const{imported:o,local:r}of m(e,l))if(d.has(o))s.add(r);else if(o==="useSearch")a.add(r)},CallExpression(e){if(e.callee.type==="Identifier")n.push({node:e,name:e.callee.name,options:e.arguments[0]})},"Program:exit"(){for(const{node:e,name:o,options:r}of n){if(s.has(o)){if(!r)t.report({node:e,message:c.whole});else if(!u(r))t.report({node:e,message:c.noSelect});continue}if(!a.has(o)||!f(r))continue;if(!u(r))t.report({node:e,message:c.search})}}}}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ROUTER_MODULE="@tanstack/react-router";
|
|
@@ -8,4 +8,6 @@ export declare const banGroups: BanGroup[];
|
|
|
8
8
|
* `rules` block always overrides.
|
|
9
9
|
*/
|
|
10
10
|
declare const reactNative: (options?: ReactNativeOptions) => OxlintConfig;
|
|
11
|
+
export { reactNative };
|
|
12
|
+
export type { ReactNativeOptions, ModuleManifest } from "../lib/types.js";
|
|
11
13
|
export default reactNative;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TEST_FILES as o,coreModules as r}from"../core/index.js";import{mergeConfigs as a}from"../lib/merge.js";import{composeModules as
|
|
1
|
+
import{TEST_FILES as o,coreModules as r}from"../core/index.js";import{effectPlugin as s}from"../lib/effect-plugin.js";import{mergeConfigs as a}from"../lib/merge.js";import{composeModules as i}from"../lib/module.js";import n,{reactModules as c}from"../react/index.js";import m from"./rules/keyboard/index.js";import p from"./rules/legend-list/index.js";import u from"./rules/legend-state/index.js";import d from"./rules/react-native/index.js";import l from"./rules/reanimated/index.js";import f from"./rules/skia/index.js";import g from"./rules/turbo-image/index.js";import h from"./rules/unistyles/index.js";export const reactNativeModules=[d,h,p,u,l,g,f,m],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 v={"eslint/no-empty":"error","eslint/no-empty-function":"error"},b={"react-effect/no-event-handler":"off"},reactNative=(t={})=>{const e=i([...r,...c,...reactNativeModules],t,banGroups);return a(n(t),{jsPlugins:e.jsPlugins,globals:{__DEV__:"readonly"},rules:{...v,...s().present?b:{},"no-restricted-imports":["error",e.restricted],...e.rules},ignorePatterns:["**/.expo/**","**/android/**","**/ios/**"],overrides:[{files:o,rules:{"@ashstack/react-native/no-dynamic-import":"off","@ashstack/react-native/hoist-stateless-function":"off"}}]})};export{reactNative};export default reactNative;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineModule as e}from"../../../lib/module.js";import{noFlexInContentContainer as t}from"./no-flex-in-content-container.js";import{noIndexKeyExtractor as
|
|
1
|
+
import{defineModule as e}from"../../../lib/module.js";import{noFlexInContentContainer as t}from"./no-flex-in-content-container.js";import{noIndexKeyExtractor as o}from"./no-index-key-extractor.js";import{noInlineRenderItemProps as r}from"./no-inline-render-item-props.js";import{noMixedChildren as n}from"./no-mixed-children.js";import{noRemountKey as i}from"./no-remount-key.js";import{noScrollviewMap as p}from"./no-scrollview-map.js";import{noUnsupportedProps as s}from"./no-unsupported-props.js";import{requiredProps as a}from"./required-props.js";import{typedItemsNeedItemType as m}from"./typed-items-need-item-type.js";export default e({meta:{name:"@ashstack/legend-list"},rules:{"required-props":a,"no-index-key-extractor":o,"no-remount-key":i,"no-inline-render-item-props":r,"no-mixed-children":n,"no-flex-in-content-container":t,"typed-items-need-item-type":m,"no-scrollview-map":p,"no-unsupported-props":s},url:import.meta.url,packages:["@legendapp/list"],option:"legendList",docsWhen:"auto-enabled when `@legendapp/list` is a dependency",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."}]}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{gate as
|
|
1
|
+
import{gate as m,problem as c}from"../../../lib/ast.js";const l=new Set(["useMemo","useCallback","memo"]),u=["babel-plugin-react-compiler","react-compiler-runtime","react-compiler-marker"],h=/^why:/i,p=10,f=(e)=>e.type==="Identifier"&&l.has(e.name)?e.name:null,d=(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},b=(e)=>{if(e.type!=="CallExpression")return null;const{callee:t}=e;if(t.type==="Identifier")return f(t);if(t.type==="MemberExpression")return d(t);return null},g=(e)=>{const t=[0];for(let o=0;o<e.length;o++)if(e.charCodeAt(o)===p)t.push(o+1);return t},y=(e,t)=>{let o=0,n=e.length-1;while(o<n){const r=Math.floor((o+n+1)/2);if((e[r]??0)<=t)o=r;else n=r-1}return o},w=(e,t)=>{const o=new Set;for(const n of e){if(!h.test(n.value.trim()))continue;for(let r=t(n.start);r<=t(n.end);r++)o.add(r)}return o};export const noManualMemo=c("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.",{meta:{packages:u},createOnce(e){let t=[];return{before(){t=[];return m(e,"useMemo","useCallback","memo")},CallExpression(o){const n=b(o);if(n!==null)t.push({node:o,name:n})},"Program:exit"(){if(t.length===0)return;const o=g(e.sourceCode.getText()),n=(s)=>y(o,s),r=w(e.sourceCode.getAllComments(),n);for(const{node:s,name:a}of t){const i=n(s.start);if(r.has(i)||r.has(i-1))continue;e.report({node:s,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.4.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",
|
|
@@ -24,10 +24,6 @@
|
|
|
24
24
|
"expo"
|
|
25
25
|
],
|
|
26
26
|
"exports": {
|
|
27
|
-
".": {
|
|
28
|
-
"types": "./dist/index.d.ts",
|
|
29
|
-
"default": "./dist/index.js"
|
|
30
|
-
},
|
|
31
27
|
"./core": {
|
|
32
28
|
"types": "./dist/core/index.d.ts",
|
|
33
29
|
"default": "./dist/core/index.js"
|
|
@@ -42,28 +38,32 @@
|
|
|
42
38
|
}
|
|
43
39
|
},
|
|
44
40
|
"files": [
|
|
45
|
-
"dist"
|
|
41
|
+
"dist",
|
|
42
|
+
"!dist/index.js",
|
|
43
|
+
"!dist/index.d.ts"
|
|
46
44
|
],
|
|
47
45
|
"publishConfig": {
|
|
48
46
|
"access": "public"
|
|
49
47
|
},
|
|
50
|
-
"dependencies": {
|
|
51
|
-
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1"
|
|
52
|
-
},
|
|
53
48
|
"peerDependencies": {
|
|
54
49
|
"oxlint": ">=1.79.0",
|
|
55
|
-
"oxlint-tsgolint": ">=7.0.0"
|
|
50
|
+
"oxlint-tsgolint": ">=7.0.0",
|
|
51
|
+
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1"
|
|
56
52
|
},
|
|
57
53
|
"peerDependenciesMeta": {
|
|
58
54
|
"oxlint-tsgolint": {
|
|
59
55
|
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"eslint-plugin-react-you-might-not-need-an-effect": {
|
|
58
|
+
"optional": true
|
|
60
59
|
}
|
|
61
60
|
},
|
|
62
61
|
"scripts": {
|
|
63
|
-
"prepack": "rm -rf dist && tsc && bun build $(find dist -name '*.js') --minify --target=node --root dist --outdir dist --no-bundle"
|
|
62
|
+
"prepack": "rm -rf dist && tsc && bun build $(find dist -name '*.js') --minify --target=node --root dist --outdir dist --no-bundle && bun ../../scripts/prune-types.ts"
|
|
64
63
|
},
|
|
65
64
|
"devDependencies": {
|
|
66
65
|
"@types/node": "^22.10.0",
|
|
66
|
+
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1",
|
|
67
67
|
"typescript": "^7.0.2"
|
|
68
68
|
}
|
|
69
69
|
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { default as core, coreModules } from "./core/index.js";
|
|
2
|
-
export { default as react, reactModules } from "./react/index.js";
|
|
3
|
-
export { default as reactNative, reactNativeModules, banGroups } from "./react-native/index.js";
|
|
4
|
-
export type { CoreOptions, ReactOptions, ReactNativeOptions, ModuleManifest } from "./lib/types.js";
|
package/dist/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export{default as core,coreModules}from"./core/index.js";export{default as react,reactModules}from"./react/index.js";export{default as reactNative,reactNativeModules,banGroups}from"./react-native/index.js";
|
package/dist/lib/ast.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { AstNode, Rule, RuleBody, RuleContext, Visitor } from "./types.js";
|
|
2
|
-
type Predicate = (node: AstNode) => boolean;
|
|
3
|
-
export declare const findInSubtree: (node: unknown, predicate: Predicate) => AstNode | null;
|
|
4
|
-
export declare const subtreeHas: (node: unknown, predicate: Predicate) => boolean;
|
|
5
|
-
/** The called name: `foo()` and `a.b.foo()` both read as `foo`; anything else is `""`. */
|
|
6
|
-
export declare const calleeName: (node: AstNode | null | undefined) => string;
|
|
7
|
-
/** Every ancestor of `node`, innermost first. */
|
|
8
|
-
export declare const ancestors: (node: AstNode) => Generator<AstNode>;
|
|
9
|
-
export declare const enclosingCall: (node: AstNode, names: ReadonlySet<string>) => AstNode | null;
|
|
10
|
-
export declare const closestAncestor: (node: AstNode, types: ReadonlySet<string>) => AstNode | null;
|
|
11
|
-
export declare const hasAncestor: (node: AstNode, predicate: Predicate) => boolean;
|
|
12
|
-
export declare const isWithin: (node: AstNode, container: AstNode) => boolean;
|
|
13
|
-
export declare const crossesFunctionBefore: (node: AstNode, container: AstNode, functionTypes: ReadonlySet<string>) => boolean;
|
|
14
|
-
export declare const isMemberCall: (node: AstNode | null | undefined, method: string) => boolean;
|
|
15
|
-
/** The object a method call hangs off: `store.get()` reads as `store`. */
|
|
16
|
-
export declare const receiverName: (node: AstNode | null | undefined) => string | null;
|
|
17
|
-
/** The last segment of a JSX tag name: `Animated.View` reads as `View`. */
|
|
18
|
-
export declare const tagIdentifier: (node: AstNode | null | undefined) => string;
|
|
19
|
-
/** The full dotted JSX tag name: `Animated.View` reads as `Animated.View`. Empty when a segment is not a plain name. */
|
|
20
|
-
export declare const tagPath: (node: AstNode | null | undefined) => string;
|
|
21
|
-
/**
|
|
22
|
-
* Which component a tag names, seeing through animation wrappers and nothing
|
|
23
|
-
* else: `Animated.View` and `AnimatedView` both read as `View`, while
|
|
24
|
-
* `Ui.View` reads as `""` — a different component that merely ends in `View`.
|
|
25
|
-
*/
|
|
26
|
-
export declare const componentName: (node: AstNode | null | undefined) => string;
|
|
27
|
-
export declare const attributeName: (attribute: AstNode) => string;
|
|
28
|
-
export declare const importedSpecifiers: (node: AstNode, source: string) => AstNode[];
|
|
29
|
-
/** Function-ish node types, for ancestor walks. */
|
|
30
|
-
export declare const FUNCTION_TYPES: Set<string>;
|
|
31
|
-
/** Component or hook name: `Capitalized` or `useX`. */
|
|
32
|
-
export declare const COMPONENT_OR_HOOK: RegExp;
|
|
33
|
-
export declare const isFunction: (node: AstNode | null | undefined) => boolean;
|
|
34
|
-
/** Source-text gate for createOnce rules: skip whole files that cannot contain the pattern. */
|
|
35
|
-
export declare const gate: (context: RuleContext, ...markers: string[]) => boolean;
|
|
36
|
-
/**
|
|
37
|
-
* A rule's first option object, or `fallback` when the consumer passed none.
|
|
38
|
-
* oxlint types options as untyped JSON, so this is the one place a rule's
|
|
39
|
-
* option shape is asserted rather than checked.
|
|
40
|
-
*/
|
|
41
|
-
export declare const optionsOf: <T extends object>(context: RuleContext, fallback: T) => T;
|
|
42
|
-
/** Shorthand for a problem rule with a description. */
|
|
43
|
-
export declare const problem: (description: string, rule: RuleBody) => Rule;
|
|
44
|
-
export type { AstNode, Rule, Visitor };
|
package/dist/lib/detect.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Should this rule group ship enabled? An explicit option wins; otherwise the
|
|
3
|
-
* group is on when one of `packages` is a dependency, and always on when
|
|
4
|
-
* `packages` is absent. Dependencies come from every package.json between the
|
|
5
|
-
* current working directory and the repo boundary — the directory holding
|
|
6
|
-
* `.git` — so in a monorepo the app package, the workspace root, or both
|
|
7
|
-
* count, and a stray package.json above the repo cannot enable anything.
|
|
8
|
-
*/
|
|
9
|
-
export declare const detect: (option: boolean | undefined, packages: string[] | undefined) => boolean;
|
package/dist/lib/merge.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { OxlintConfig } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Layer `delta` over `base` into one flat config — the way `react` is built
|
|
4
|
-
* from `core` plus a react delta. Plugin lists union, `overrides` append,
|
|
5
|
-
* `rules` and the other keyed blocks are last-wins per key. Entries compose
|
|
6
|
-
* with this rather than oxlint's `extends` so every entry is a single
|
|
7
|
-
* inspectable object and a consumer's own overrides stay trivial.
|
|
8
|
-
*/
|
|
9
|
-
export declare const mergeConfigs: (base: OxlintConfig, delta: OxlintConfig) => OxlintConfig;
|
package/dist/lib/module.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { BanGroup, ModuleManifest, RestrictedImports } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Identity helper: types the manifest and keeps the shape honest. The returned
|
|
4
|
-
* object is handed to oxlint as the plugin (it ignores the manifest extras).
|
|
5
|
-
*/
|
|
6
|
-
export declare const defineModule: (manifest: ModuleManifest) => ModuleManifest;
|
|
7
|
-
declare const shortName: (module: ModuleManifest) => string;
|
|
8
|
-
export interface Composed {
|
|
9
|
-
jsPlugins: string[];
|
|
10
|
-
rules: Record<string, "error">;
|
|
11
|
-
restricted: Required<RestrictedImports>;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Turn manifests + options into the config fragments an entry spreads in:
|
|
15
|
-
* which plugin files to load, which rules to enable, and the merged import
|
|
16
|
-
* bans from the enabled modules plus the enabled ban-only groups.
|
|
17
|
-
*/
|
|
18
|
-
export declare const composeModules: (modules: ModuleManifest[], options: Record<string, boolean | undefined>, banGroups?: BanGroup[]) => Composed;
|
|
19
|
-
export { shortName };
|