@ashstack/lint 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/core/index.js +1 -1
- package/dist/core/rules/core/index.js +1 -1
- package/dist/core/rules/core/max-complexity.js +1 -0
- package/dist/core/rules/core/max-lines.js +1 -1
- package/dist/core/rules/core/no-comments.js +1 -1
- package/dist/core/rules/core/no-packed-condition.js +1 -1
- package/dist/lib/registry.js +1 -1
- package/dist/lib/rule-types/core.d.ts +19 -37
- package/dist/lib/rule-types/react-native.d.ts +111 -1
- package/dist/lib/rule-types/react.d.ts +88 -2
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.js +1 -1
- package/dist/react/rules/i18n/no-bare-attrs.js +1 -1
- package/dist/react/rules/react/components-tsx-only.js +1 -0
- package/dist/react/rules/react/index.js +1 -1
- package/dist/react/rules/react/prefer-design-system.js +1 -0
- package/dist/react-native/index.js +1 -1
- package/dist/react-native/rules/react-native/no-manual-memo.js +1 -1
- package/dist/react-native/rules/unistyles/content-container.js +1 -1
- package/package.json +1 -1
- package/dist/core/rules/core/components-tsx-only.js +0 -1
- package/dist/core/rules/core/use-design-system.js +0 -1
- /package/dist/{core/rules/core → react/rules/react}/hoist-intl.js +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ashstack/lint
|
|
2
2
|
|
|
3
|
-
Strict shared [oxlint](https://oxc.rs) config: <!-- rule-counts -->
|
|
3
|
+
Strict shared [oxlint](https://oxc.rs) config: <!-- rule-counts -->117 rules on plain TypeScript, 201 with React, 258 on React Native, 77 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
|
|
|
@@ -89,7 +89,7 @@ Import bans follow detection only; disable one by rule name.
|
|
|
89
89
|
|
|
90
90
|
## Opt-in rules
|
|
91
91
|
|
|
92
|
-
<!-- opt-in -->Off by default, since they need a team decision first: `@ashstack/
|
|
92
|
+
<!-- opt-in -->Off by default, since they need a team decision first: `@ashstack/react/prefer-design-system` and `@ashstack/react/components-tsx-only`.<!-- /opt-in -->
|
|
93
93
|
|
|
94
94
|
## Rule reference
|
|
95
95
|
|
package/dist/core/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{composeModules as o}from"../lib/module.js";import{coreRegistry as n}from"../lib/registry.js";const s={"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",
|
|
1
|
+
import{composeModules as o}from"../lib/module.js";import{coreRegistry as n}from"../lib/registry.js";const s={"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","max-depth":["error",3],"max-params":["error",4],"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(n,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:{...s,...r.rules},overrides:[{files:["**/*.tsx"],rules:{"func-style":"off"}},{files:["**/*.d.ts"],rules:{"typescript/no-explicit-any":"off"}},{files:TEST_FILES,rules:{"@ashstack/core/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{defineModule as o}from"../../../lib/module.js";import{
|
|
1
|
+
import{defineModule as o}from"../../../lib/module.js";import{maxComplexity as n}from"./max-complexity.js";import{maxLines as m}from"./max-lines.js";import{noComments as e}from"./no-comments.js";import{noNamingConvention as i}from"./no-naming-convention.js";import{noPackedCondition as t}from"./no-packed-condition.js";export default o({meta:{name:"@ashstack/core"},url:import.meta.url,docsWhen:"always on via `core()` and every entry above it",rules:{"no-comments":e,"no-naming-convention":i,"no-packed-condition":t,"max-lines":m,"max-complexity":n}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ancestors as m,FUNCTION_TYPES as f,problem as h}from"../../../lib/ast.js";const p=15,S=new Set(["IfStatement","SwitchStatement","ForStatement","ForInStatement","ForOfStatement","WhileStatement","DoWhileStatement","CatchClause","ConditionalExpression"]),x=new Set(["&&","||"]),l=(t)=>{if(t.type!=="IfStatement")return!1;const{parent:o}=t;return o.type==="IfStatement"&&o.alternate===t},E=(t)=>t!==null&&t!==void 0&&t.type!=="IfStatement",u=(t)=>{let o=0;for(const e of m(t)){if(f.has(e.type))return o;if(S.has(e.type)&&!l(e))o+=1}return o};export const maxComplexity=h("Cap the cognitive complexity of one function: its branches, weighted by nesting depth. An `if`, `switch`, loop, `catch` or ternary costs a point plus one per enclosing structure. An `else` or a run of `&&`/`||` costs a flat point, `??` costs nothing. A nested function is scored on its own. Replaces the built-in `complexity`, which reads a 20-case `switch` as 20 decisions.",{meta:{schema:[{type:"integer",minimum:1,default:p,description:"Highest cognitive complexity one function may reach."}]},createOnce(t){let o=p,e=0;const r=[],c=()=>{r.push(e);e=0},a=(n)=>{const i=e;e=r.pop()??0;if(i<=o)return;t.report({node:n,message:`Extract the deepest branches into functions of their own — ${i} cognitive complexity, past ${o}. Depth is what costs: an \`if\` two structures in counts 3.`})},s=(n)=>{e+=1+u(n)};return{before(){o=typeof t.options[0]==="number"?t.options[0]:p;e=0;r.length=0;return!0},FunctionDeclaration:c,FunctionExpression:c,ArrowFunctionExpression:c,"FunctionDeclaration:exit":a,"FunctionExpression:exit":a,"ArrowFunctionExpression:exit":a,IfStatement(n){e+=l(n)?1:1+u(n);if(E(n.alternate))e+=1},SwitchStatement:s,ForStatement:s,ForInStatement:s,ForOfStatement:s,WhileStatement:s,DoWhileStatement:s,CatchClause:s,ConditionalExpression:s,LogicalExpression(n){if(!x.has(n.operator))return;const{parent:i}=n;if(i.type==="LogicalExpression"&&i.operator===n.operator)return;e+=1}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isMemberCall as
|
|
1
|
+
import{isMemberCall as c,receiverName as d}from"../../../lib/ast.js";const i=300,m=new Set(["StyleSheet","stylex"]),f=10,u=32,h=(e)=>c(e,"create")&&m.has(d(e)??""),p=(e,r)=>{const t=new Uint8Array(e.length);for(const[s,o]of r)t.fill(1,s,o);let l=0,n=!1;for(let s=0;s<e.length;s++){const o=e.charCodeAt(s);if(o===f){if(n)l++;n=!1}else if(o>u&&t[s]===0)n=!0}return n?l+1:l};export const maxLines={meta:{type:"problem",docs:{description:"Cap the lines of code in one file. Blank lines, comments and the style tables `StyleSheet.create` and `stylex.create` build are not counted, so a colocated stylesheet costs the file nothing. Replaces the built-in `max-lines`, which counts all of them."},schema:[{type:"integer",minimum:1,default:i,description:"Highest number of code lines one file may hold."}]},createOnce(e){let r=[];return{before(){r=[];return!0},CallExpression(t){if(h(t))r.push([t.start,t.end])},"Program:exit"(t){const l=t.body.at(-1);if(!l)return;const n=typeof e.options[0]==="number"?e.options[0]:i,s=e.sourceCode.getAllComments().map((a)=>[a.start,a.end]),o=p(e.sourceCode.getText(),[...r,...s]);if(o<=n)return;e.report({node:l,message:`Move part of this file elsewhere — ${o} counted lines, past ${n}. Blank lines, comments and style tables are already excluded.`})}}}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{optionsOf as
|
|
1
|
+
import{optionsOf as v}from"../../../lib/ast.js";const E=["@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"],D=/^(?<kind>what|why):\s*(?<fact>[\s\S]+)$/i,m=new Set(["Block","MultiLine"]),k=new Set(["Shebang","Hashbang"]),u=10,f=120,p=2,A="Make the code say this, then delete the line — rename, Extract Function, Guard Clause, or named steps. Done when the sentence reads off a name or the control flow.",C=" `// what:` fits only a fact that outlives the code — a platform bug, an upstream contract, a measured number; what the code does is not one, and still owes the refactor.",_=" This project runs with the comment hatch closed: nothing but a tooling directive survives here. A `memo` worth keeping goes behind `// oxlint-disable-next-line @ashstack/react-native/no-manual-memo -- <the real cost>`, which is a directive and states the cost in the same line.",x=(e)=>A+(e?C:_),H="Attach this block to the declaration it documents, or Rename and Extract Function until the code reads without it.",d={block:"Rewrite this as a single `// what: <fact>` line comment; if the fact needs a paragraph, name the pieces in code instead.",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 and refactor what the others explain into names."},M=(e,t)=>`Delete \`what:\` comments from this file until at most ${e} remain (it has ${t}): move each one's logic into a function named for it.`,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(),F=(e)=>E.some((t)=>e.startsWith(t)),I=(e)=>m.has(e.type)&&e.value.startsWith("*"),R=(e)=>{const t=[];for(const o of e.sourceCode.getAllComments()){if(k.has(o.type))continue;const l=O(o);if(F(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},w=(e,t)=>{if(!t)return[];const o=D.exec(e.body)?.groups;if(!o?.kind||!o.fact)return[];const l=o.kind.toLowerCase()==="why"?"why":"what";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()==="",g=(e)=>e.escapeHatch!==!1;export const noComments={meta:{type:"problem",docs:{description:"Disallow every comment that is not a `// what:` fact, a `// why:` marker, or a tooling directive. The diagnostic names the refactor that removes it. A `// why:` line is what `@ashstack/react-native/no-manual-memo` requires above a kept `memo`, so no budget counts it; `escapeHatch: false` closes even that."},schema:[{type:"object",properties:{jsdoc:{enum:["allow","report"],default:"report",description:'With `"allow"`, a `/** */` block attached to the declaration below it is kept. A floating one still reports.'},escapeHatch:{type:"boolean",default:!0,description:"With `false`, nothing but a tooling directive survives: the `// what:` hatch, `// why:` markers and JSDoc all report, whatever `jsdoc` says."},budget:{type:"integer",minimum:0,default:p,description:"How many `// what:` lines one file may keep. `// why:` lines never count against it."}},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})},S=(n,r)=>{const{text:s}=e.sourceCode,i=g(r);for(const a of n){const{comment:c}=a;if(w(a,i).length>0)continue;if(!i||r.jsdoc!=="allow"||!I(c))h(c,x(i));else if(!t.has(L(s,c.end)))h(c,H)}},T=(n,r)=>{const s=[];for(const i of n)for(const a of w(i,r)){const c=N(a.comment,a.body,a.fact);if(c===null)s.push(a);else h(a.comment,c)}return s},b=(n)=>{const r=e.sourceCode.getText(),s=n.map((i)=>i.comment);for(const[i,a]of s.entries()){const c=s[i-1];if(c&&j(r,c,a))h(a,d.stacked)}},y=(n,r)=>{const s=r.budget??p,i=n.filter((c)=>c.kind==="what"),a=i[s];if(a)h(a.comment,M(s,i.length))};return{...l,before(){t.clear();return!0},"Program:exit"(){const n=v(e,{}),r=R(e);S(r,n);const s=T(r,g(n));b(s);y(s,n)}}}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{gate as s}from"../../../lib/ast.js";const p=new Set(["===","!==","==","!=","<","<=",">",">=","in","instanceof"]),o=5,r=(t)=>{if(!t)return 0;if(t.type==="LogicalExpression")return 1+r(t.left)+r(t.right);if(t.type==="BinaryExpression")return(p.has(t.operator)?1:0)+r(t.left)+r(t.right);if(t.type==="UnaryExpression")return t.operator==="!"?r(t.argument):0;if(t.type==="ConditionalExpression")return 1+r(t.test)+r(t.consequent)+r(t.alternate);return 0};export const noPackedCondition={meta:{type:"problem",docs:{description:"Require a condition to be split into named booleans once it holds too many boolean operators and comparisons.
|
|
1
|
+
import{gate as s}from"../../../lib/ast.js";const p=new Set(["===","!==","==","!=","<","<=",">",">=","in","instanceof"]),o=5,r=(t)=>{if(!t)return 0;if(t.type==="LogicalExpression")return 1+r(t.left)+r(t.right);if(t.type==="BinaryExpression")return(p.has(t.operator)?1:0)+r(t.left)+r(t.right);if(t.type==="UnaryExpression")return t.operator==="!"?r(t.argument):0;if(t.type==="ConditionalExpression")return 1+r(t.test)+r(t.consequent)+r(t.alternate);return 0};export const noPackedCondition={meta:{type:"problem",docs:{description:"Require a condition to be split into named booleans once it holds too many boolean operators and comparisons."},schema:[{type:"integer",minimum:1,default:o,description:"Highest number of operators one condition may pack."}]},createOnce(t){let i=o;const n=(e)=>{if(!e)return;const a=r(e);if(a<=i)return;t.report({node:e,message:`Extract each part into a named variable like \`hasValue\`, until this condition reads as those names joined by \`&&\`/\`||\` — it packs ${a} operators, past the ${i} a reader takes in.`})};return{before(){i=typeof t.options[0]==="number"?t.options[0]:o;return s(t,"&&","||","??")},IfStatement(e){n(e.test)},ConditionalExpression(e){n(e.test)},WhileStatement(e){n(e.test)},DoWhileStatement(e){n(e.test)},ForStatement(e){n(e.test)}}}};
|
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":{},"
|
|
1
|
+
const e=(t)=>new URL(t,import.meta.url).href;export const coreRegistry=[{meta:{name:"@ashstack/core"},url:e("../core/rules/core/index.js"),rules:{"no-comments":{},"no-naming-convention":{},"no-packed-condition":{},"max-lines":{},"max-complexity":{}}},{meta:{name:"@ashstack/zod"},url:e("../core/rules/zod/index.js"),option:"zod",packages:["zod"],rules:{"prefer-enum":{}}}],reactRegistry=[{meta:{name:"@ashstack/react"},url:e("../react/rules/react/index.js"),rules:{"no-unlabeled-icon-button":{},"no-svg-without-title":{},"hoist-intl":{},"prefer-design-system":{defaultOff:!0},"components-tsx-only":{defaultOff:!0}}},{meta:{name:"@ashstack/query"},url:e("../react/rules/query/index.js"),option:"query",packages:["@tanstack/react-query"],rules:{"no-inline-keys":{},"no-deprecated-filters":{},"require-destructured-hooks":{},"no-fetch-in-query-fn":{},"next-page-param-undefined":{}}},{meta:{name:"@ashstack/zustand"},url:e("../react/rules/zustand/index.js"),option:"zustand",packages:["zustand"],rules:{"require-selector":{}}},{meta:{name:"@ashstack/i18n"},url:e("../react/rules/i18n/index.js"),option:"i18n",packages:["i18next","react-i18next","@lingui/core","react-intl","use-intl","next-intl","expo-localization"],rules:{"no-bare-text":{},"no-bare-attrs":{},"no-bare-toast":{}}},{meta:{name:"@ashstack/tailwind"},url:e("../react/rules/tailwind/index.js"),option:"tailwind",packages:["tailwindcss"],rules:{"prefer-cn":{},"use-logical-classes":{}}},{meta:{name:"@ashstack/tanstack-router"},url:e("../react/rules/tanstack-router/index.js"),option:"tanstackRouter",packages:["@tanstack/react-router"],rules:{"require-selector":{},"no-search-casts":{}}}],reactNativeRegistry=[{meta:{name:"@ashstack/react-native"},url:e("../react-native/rules/react-native/index.js"),restrictedImports:{paths:[{name:"react",importNames:["forwardRef"],message:"Accept `ref` as a regular prop and delete the `forwardRef` wrapper — React 19 passes `ref` like any other prop."},{name:"react",importNames:["useContext"],message:"Call `use(Context)` instead of `useContext(Context)`; React 19 replaces the hook with it."}],patterns:[{group:["expo-linear-gradient","react-native-linear-gradient"],message:"Apply the gradient through the `experimental_backgroundImage` style property instead of a gradient component."}]},rules:{"no-keyboard-will-events":{},"no-scroll-position-state":{},"no-conditional-style-array":{},"no-leaked-render":{},"no-rn-image-network-source":{},"no-redundant-view-nesting":{},"no-rn-namespace-import":{},"no-unlabeled-icon-pressable":{},"hoist-stateless-function":{},"no-manual-memo":{},"no-dynamic-import":{}}},{meta:{name:"@ashstack/unistyles"},url:e("../react-native/rules/unistyles/index.js"),option:"unistyles",packages:["react-native-unistyles"],restrictedImports:{paths:[{name:"react-native",importNames:["StyleSheet"],message:"Import `StyleSheet` from `react-native-unistyles` instead of `react-native`."},{name:"react-native",importNames:["useWindowDimensions","Dimensions"],message:"Read screen size from `rt.screen` inside `StyleSheet.create`, or `UnistylesRuntime.screen` outside it, instead of the `Dimensions` APIs."},{name:"react-native",importNames:["SafeAreaView"],message:'Set `contentInsetAdjustmentBehavior="automatic"` on the scroll view, or apply `rt.insets` from `react-native-unistyles`, instead of `SafeAreaView`.'},{name:"react-native-safe-area-context",importNames:["useSafeAreaInsets","useSafeAreaFrame","SafeAreaView"],message:"Read safe-area values from `rt.insets`, or `UnistylesRuntime.insets` outside a stylesheet, in `react-native-unistyles`."}]},rules:{"animated-theme":{},"content-container":{},"in-sheet":{},insets:{},"no-hardcoded-color":{},"no-hardcoded-spacing":{},"no-margin":{},"no-paramless-dynamic-function":{},"no-style-spread":{},"no-unused-styles":{},"rtl-style-call":{},"theme-screen-component":{},"theme-style-attr":{}}},{meta:{name:"@ashstack/legend-list"},url:e("../react-native/rules/legend-list/index.js"),option:"legendList",packages:["@legendapp/list"],restrictedImports:{paths:[{name:"react-native",importNames:["FlatList","SectionList","VirtualizedList"],message:"Render this list with `LegendList` from `@legendapp/list/react-native`; a `ScrollView` plus `map` is fine for fewer than 20 static items."},{name:"@shopify/flash-list",message:"Import `LegendList` from `@legendapp/list/react-native` — Legend List replaced FlashList in this stack."}],patterns:[{group:["@legendapp/list"],message:"Import from the platform entrypoint `@legendapp/list/react-native`, not the package root."}]},rules:{"required-props":{},"no-index-key-extractor":{},"no-remount-key":{},"no-inline-render-item-props":{},"no-mixed-children":{},"no-flex-in-content-container":{},"typed-items-need-item-type":{},"no-scrollview-map":{},"no-unsupported-props":{}}},{meta:{name:"@ashstack/legend-state"},url:e("../react-native/rules/legend-state/index.js"),option:"legendState",packages:["@legendapp/state"],restrictedImports:{paths:[{name:"@legendapp/state/react",importNames:["use$","useSelector"],message:"Use `useValue` from `@legendapp/state/react`; `use$` and `useSelector` are deprecated."}]},rules:{"no-assignment":{},naming:{},"no-nested-observable":{},"no-react-mirror":{},"no-untracked-get-in-jsx":{},"no-peek-in-selector":{},"no-object-selector":{},"no-observable-in-component":{}}},{meta:{name:"@ashstack/reanimated"},url:e("../react-native/rules/reanimated/index.js"),option:"reanimated",packages:["react-native-reanimated"],restrictedImports:{paths:[{name:"react-native",importNames:["Animated"],message:"Use `react-native-reanimated` instead of React Native's `Animated` API."},{name:"react-native-reanimated",importNames:["runOnJS","runOnUI"],message:"Use `scheduleOnRN` and `scheduleOnUI` from `react-native-worklets`; `runOnJS` and `runOnUI` are deprecated."},{name:"react-native-worklets",importNames:["runOnJS","runOnUI"],message:"Use `scheduleOnRN` and `scheduleOnUI` from `react-native-worklets`; `runOnJS` and `runOnUI` are deprecated."}]},rules:{"animated-reaction-safety":{},"animated-style-needs-animated-component":{},"animated-updater-purity":{},"gpu-properties-only":{},"hoist-layout-animation-builder":{},"interpolate-needs-clamp":{},"no-shared-value-dot-value":{},"no-react-state-from-continuous-worklet":{},"prefer-lazy-shared-value-initializer":{},"schedule-on-rn-scope":{},"shared-value-usage":{}}},{meta:{name:"@ashstack/turbo-image"},url:e("../react-native/rules/turbo-image/index.js"),option:"turboImage",packages:["react-native-turbo-image"],rules:{"require-resize":{},"require-cache-policy":{}}},{meta:{name:"@ashstack/skia"},url:e("../react-native/rules/skia/index.js"),option:"skia",packages:["@shopify/react-native-skia"],restrictedImports:{paths:[{name:"@shopify/react-native-skia",importNames:["usePathValue","usePathInterpolation"],message:"Keep one stable `SkPath` buffer and mutate it inside `useDerivedValue`, with consumers reading the driving shared value."}]},rules:{"canvas-opaque":{}}},{meta:{name:"@ashstack/keyboard"},url:e("../react-native/rules/keyboard/index.js"),option:"keyboard",packages:["react-native-keyboard-controller"],restrictedImports:{paths:[{name:"react-native",importNames:["KeyboardAvoidingView"],message:"Import `KeyboardAvoidingView` from `react-native-keyboard-controller` — same props, tracked per frame. For a scrolling form, its `KeyboardAwareScrollView`."}]},rules:{}}];
|
|
@@ -2,11 +2,13 @@ import type { AllowWarnDeny } from "oxlint";
|
|
|
2
2
|
/** A severity, or a severity followed by as many of the rule's options as you want to set. */
|
|
3
3
|
type RuleSetting<Options extends unknown[] = []> = AllowWarnDeny | [AllowWarnDeny, ...Partial<Options>];
|
|
4
4
|
/** Every rule id `core()` adds on top of the entry below it. */
|
|
5
|
-
export type CoreRuleId = "@ashstack/core/no-comments" | "@ashstack/core/no-naming-convention" | "@ashstack/core/no-packed-condition" | "@ashstack/core/
|
|
5
|
+
export type CoreRuleId = "@ashstack/core/no-comments" | "@ashstack/core/no-naming-convention" | "@ashstack/core/no-packed-condition" | "@ashstack/core/max-lines" | "@ashstack/core/max-complexity" | "@ashstack/zod/prefer-enum";
|
|
6
6
|
declare module "oxlint" {
|
|
7
7
|
interface DummyRuleMap {
|
|
8
8
|
/**
|
|
9
|
-
* Disallow every comment that is
|
|
9
|
+
* Disallow every comment that is not a `// what:` fact, a `// why:` marker, or a tooling directive. The diagnostic names the refactor that removes it. A `// why:` line is what `@ashstack/react-native/no-manual-memo` requires above a kept `memo`, so no budget counts it; `escapeHatch: false` closes even that.
|
|
10
|
+
*
|
|
11
|
+
* **Default: on.**
|
|
10
12
|
*
|
|
11
13
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackcoreno-comments
|
|
12
14
|
*/
|
|
@@ -20,60 +22,40 @@ declare module "oxlint" {
|
|
|
20
22
|
/**
|
|
21
23
|
* Require a variable, object property, type member or enum member to use one of the casings allowed for its kind.
|
|
22
24
|
*
|
|
25
|
+
* **Default: on.**
|
|
26
|
+
*
|
|
23
27
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackcoreno-naming-convention
|
|
24
28
|
*/
|
|
25
29
|
"@ashstack/core/no-naming-convention"?: RuleSetting;
|
|
26
30
|
/**
|
|
27
|
-
* Require a condition to be split into named booleans once it holds too many boolean operators and comparisons.
|
|
31
|
+
* Require a condition to be split into named booleans once it holds too many boolean operators and comparisons.
|
|
32
|
+
*
|
|
33
|
+
* **Default: on.**
|
|
28
34
|
*
|
|
29
35
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackcoreno-packed-condition
|
|
30
36
|
*/
|
|
31
37
|
"@ashstack/core/no-packed-condition"?: RuleSetting<[number]>;
|
|
32
38
|
/**
|
|
33
|
-
*
|
|
39
|
+
* Cap the lines of code in one file. Blank lines, comments and the style tables `StyleSheet.create` and `stylex.create` build are not counted, so a colocated stylesheet costs the file nothing. Replaces the built-in `max-lines`, which counts all of them.
|
|
34
40
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackcoreuse-design-system
|
|
38
|
-
*/
|
|
39
|
-
"@ashstack/core/use-design-system"?: RuleSetting<[
|
|
40
|
-
{
|
|
41
|
-
dir?: string;
|
|
42
|
-
alias?: string;
|
|
43
|
-
use?: Record<string, string | Array<string> | {
|
|
44
|
-
replaces: string | Array<string>;
|
|
45
|
-
from?: string;
|
|
46
|
-
path?: string;
|
|
47
|
-
reason?: string;
|
|
48
|
-
}>;
|
|
49
|
-
exempt?: Array<string>;
|
|
50
|
-
}
|
|
51
|
-
]>;
|
|
52
|
-
/**
|
|
53
|
-
* Require every file under the components directory to render JSX or be a re-export barrel. `dir` says which directory, defaulting to `src/components`.
|
|
41
|
+
* **Default: on.**
|
|
54
42
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackcorecomponents-tsx-only
|
|
43
|
+
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackcoremax-lines
|
|
58
44
|
*/
|
|
59
|
-
"@ashstack/core/
|
|
60
|
-
dir?: string;
|
|
61
|
-
}]>;
|
|
45
|
+
"@ashstack/core/max-lines"?: RuleSetting<[number]>;
|
|
62
46
|
/**
|
|
63
|
-
*
|
|
47
|
+
* Cap the cognitive complexity of one function: its branches, weighted by nesting depth. An `if`, `switch`, loop, `catch` or ternary costs a point plus one per enclosing structure. An `else` or a run of `&&`/`||` costs a flat point, `??` costs nothing. A nested function is scored on its own. Replaces the built-in `complexity`, which reads a 20-case `switch` as 20 decisions.
|
|
64
48
|
*
|
|
65
|
-
*
|
|
66
|
-
*/
|
|
67
|
-
"@ashstack/core/hoist-intl"?: RuleSetting;
|
|
68
|
-
/**
|
|
69
|
-
* Cap the lines of code in one file, counting neither blank lines, comments, nor the style tables `StyleSheet.create` and `stylex.create` build. A stylesheet is data, and keeping it next to the component it styles is the point — it should not spend the budget that logic spends. The option says how many lines: 300 by default, which `core()` keeps, and 250 from `react()` down, where a file past it is several components rather than one long one. Replaces the built-in `max-lines`, which counts every line of all three.
|
|
49
|
+
* **Default: on.**
|
|
70
50
|
*
|
|
71
|
-
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackcoremax-
|
|
51
|
+
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackcoremax-complexity
|
|
72
52
|
*/
|
|
73
|
-
"@ashstack/core/max-
|
|
53
|
+
"@ashstack/core/max-complexity"?: RuleSetting<[number]>;
|
|
74
54
|
/**
|
|
75
55
|
* Disallow `z.nativeEnum()` and any `z.union()` whose members are all `z.literal()` strings.
|
|
76
56
|
*
|
|
57
|
+
* **Default: on**, when `zod` is a dependency.
|
|
58
|
+
*
|
|
77
59
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackzodprefer-enum
|
|
78
60
|
*/
|
|
79
61
|
"@ashstack/zod/prefer-enum"?: RuleSetting;
|
|
@@ -8,59 +8,79 @@ declare module "oxlint" {
|
|
|
8
8
|
/**
|
|
9
9
|
* Disallow the `keyboardWill*` event names. They are iOS-only, so on Android the listener registers and never fires.
|
|
10
10
|
*
|
|
11
|
+
* **Default: on.**
|
|
12
|
+
*
|
|
11
13
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreact-nativeno-keyboard-will-events
|
|
12
14
|
*/
|
|
13
15
|
"@ashstack/react-native/no-keyboard-will-events"?: RuleSetting;
|
|
14
16
|
/**
|
|
15
17
|
* Disallow a React state setter inside a scroll handler prop. Scroll fires every frame, and so would the re-render.
|
|
16
18
|
*
|
|
19
|
+
* **Default: on.**
|
|
20
|
+
*
|
|
17
21
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreact-nativeno-scroll-position-state
|
|
18
22
|
*/
|
|
19
23
|
"@ashstack/react-native/no-scroll-position-state"?: RuleSetting;
|
|
20
24
|
/**
|
|
21
25
|
* Disallow conditional and logical entries inside a JSX `style` array. A falsy entry leaves a hole that breaks the Unistyles C++ proxy.
|
|
22
26
|
*
|
|
27
|
+
* **Default: on.**
|
|
28
|
+
*
|
|
23
29
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreact-nativeno-conditional-style-array
|
|
24
30
|
*/
|
|
25
31
|
"@ashstack/react-native/no-conditional-style-array"?: RuleSetting;
|
|
26
32
|
/**
|
|
27
33
|
* Disallow a `&&` guard in JSX on a value this rule can see is a number or a string — a `.length`, a `.size`, arithmetic, a literal. The falsy left operand leaks into the output, and a bare `0` crashes React Native with "Text strings must be rendered within a <Text> component".
|
|
28
34
|
*
|
|
35
|
+
* **Default: on.**
|
|
36
|
+
*
|
|
29
37
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreact-nativeno-leaked-render
|
|
30
38
|
*/
|
|
31
39
|
"@ashstack/react-native/no-leaked-render"?: RuleSetting;
|
|
32
40
|
/**
|
|
33
41
|
* Disallow a react-native `<Image>` with a network `{ uri }` source. It has no disk cache and no decode sizing, so the image re-downloads at full size on every cold start.
|
|
34
42
|
*
|
|
43
|
+
* **Default: on.**
|
|
44
|
+
*
|
|
35
45
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreact-nativeno-rn-image-network-source
|
|
36
46
|
*/
|
|
37
47
|
"@ashstack/react-native/no-rn-image-network-source"?: RuleSetting;
|
|
38
48
|
/**
|
|
39
49
|
* Disallow a View or Animated.View that wraps an identical view when neither carries anything but a style. Every extra host view is a real node in the native tree.
|
|
40
50
|
*
|
|
51
|
+
* **Default: on.**
|
|
52
|
+
*
|
|
41
53
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreact-nativeno-redundant-view-nesting
|
|
42
54
|
*/
|
|
43
55
|
"@ashstack/react-native/no-redundant-view-nesting"?: RuleSetting;
|
|
44
56
|
/**
|
|
45
57
|
* Disallow a namespace import of react-native and a re-export of its `Platform`. Both defeat Metro's platform shaking, so dead `Platform.OS` branches ship in both bundles.
|
|
46
58
|
*
|
|
59
|
+
* **Default: on.**
|
|
60
|
+
*
|
|
47
61
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreact-nativeno-rn-namespace-import
|
|
48
62
|
*/
|
|
49
63
|
"@ashstack/react-native/no-rn-namespace-import"?: RuleSetting;
|
|
50
64
|
/**
|
|
51
65
|
* Require an accessible name on an icon-only touchable or an icon-only Expo UI `<Button>`. Without a label, hint or visible text, a screen reader cannot reach the control.
|
|
52
66
|
*
|
|
67
|
+
* **Default: on.**
|
|
68
|
+
*
|
|
53
69
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreact-nativeno-unlabeled-icon-pressable
|
|
54
70
|
*/
|
|
55
71
|
"@ashstack/react-native/no-unlabeled-icon-pressable"?: RuleSetting;
|
|
56
72
|
/**
|
|
57
73
|
* Require module scope for a non-component function that reads nothing from the component around it. Out there it is created once, keeps a stable identity, and can be tested without rendering.
|
|
58
74
|
*
|
|
75
|
+
* **Default: on.**
|
|
76
|
+
*
|
|
59
77
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreact-nativehoist-stateless-function
|
|
60
78
|
*/
|
|
61
79
|
"@ashstack/react-native/hoist-stateless-function"?: RuleSetting;
|
|
62
80
|
/**
|
|
63
|
-
* Require a `// why:` line above every kept `useMemo`, `useCallback` and `memo`. The React Compiler memoises on a best-effort basis,
|
|
81
|
+
* Require a `// why:` line above every kept `useMemo`, `useCallback` and `memo`. The React Compiler memoises on a best-effort basis, so a memo is allowed where the cost is real: a list row, or a computation measured as heavy. Turns off with `reactCompiler: false`.
|
|
82
|
+
*
|
|
83
|
+
* **Default: on.**
|
|
64
84
|
*
|
|
65
85
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreact-nativeno-manual-memo
|
|
66
86
|
*/
|
|
@@ -68,270 +88,360 @@ declare module "oxlint" {
|
|
|
68
88
|
/**
|
|
69
89
|
* Disallow dynamic `import()` outside a `React.lazy` or `dynamic` wrapper. Metro inlines it into the same bundle, so nothing is deferred and the module is hidden from the typechecker.
|
|
70
90
|
*
|
|
91
|
+
* **Default: on.**
|
|
92
|
+
*
|
|
71
93
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreact-nativeno-dynamic-import
|
|
72
94
|
*/
|
|
73
95
|
"@ashstack/react-native/no-dynamic-import"?: RuleSetting;
|
|
74
96
|
/**
|
|
75
97
|
* A Reanimated worklet hook cannot see `useUnistyles()` theme changes on the UI thread. Read the shared value from `useAnimatedTheme()` there instead.
|
|
76
98
|
*
|
|
99
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
100
|
+
*
|
|
77
101
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylesanimated-theme
|
|
78
102
|
*/
|
|
79
103
|
"@ashstack/unistyles/animated-theme"?: RuleSetting;
|
|
80
104
|
/**
|
|
81
105
|
* A raw component never subscribes its `contentContainerStyle` to theme or `rt` updates. Wrap the component with `withUnistyles` when the style depends on either.
|
|
82
106
|
*
|
|
107
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
108
|
+
*
|
|
83
109
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylescontent-container
|
|
84
110
|
*/
|
|
85
111
|
"@ashstack/unistyles/content-container"?: RuleSetting;
|
|
86
112
|
/**
|
|
87
113
|
* Inside `StyleSheet.create`, require `rt` and theme values rather than Dimensions, PixelRatio, Appearance, I18nManager, StatusBar or `UnistylesRuntime`. It also covers logical spacing, `boxShadow`, `borderCurve` and a redundant `as const`.
|
|
88
114
|
*
|
|
115
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
116
|
+
*
|
|
89
117
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylesin-sheet
|
|
90
118
|
*/
|
|
91
119
|
"@ashstack/unistyles/in-sheet"?: RuleSetting;
|
|
92
120
|
/**
|
|
93
121
|
* Disallow passing `useSafeAreaInsets()` values into a dynamic style function or an inline JSX style object. Read `rt.insets` inside `StyleSheet.create` instead.
|
|
94
122
|
*
|
|
123
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
124
|
+
*
|
|
95
125
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylesinsets
|
|
96
126
|
*/
|
|
97
127
|
"@ashstack/unistyles/insets"?: RuleSetting;
|
|
98
128
|
/**
|
|
99
129
|
* Disallow hex and CSS-function colors inside `StyleSheet.create`. A raw color skips dark mode and never changes with the theme.
|
|
100
130
|
*
|
|
131
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
132
|
+
*
|
|
101
133
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylesno-hardcoded-color
|
|
102
134
|
*/
|
|
103
135
|
"@ashstack/unistyles/no-hardcoded-color"?: RuleSetting;
|
|
104
136
|
/**
|
|
105
137
|
* Require `theme.spacing` or `theme.sizing.scale` for spacing, radius and type values inside `StyleSheet.create`, instead of raw numbers.
|
|
106
138
|
*
|
|
139
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
140
|
+
*
|
|
107
141
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylesno-hardcoded-spacing
|
|
108
142
|
*/
|
|
109
143
|
"@ashstack/unistyles/no-hardcoded-spacing"?: RuleSetting;
|
|
110
144
|
/**
|
|
111
145
|
* Disallow non-negative `margin` inside `StyleSheet.create`; `gap` on the parent or `padding` on the element spaces children without leaving a hole when one is removed.
|
|
112
146
|
*
|
|
147
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
148
|
+
*
|
|
113
149
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylesno-margin
|
|
114
150
|
*/
|
|
115
151
|
"@ashstack/unistyles/no-margin"?: RuleSetting;
|
|
116
152
|
/**
|
|
117
153
|
* Disallow a style written as a function that takes no arguments. It returns the same object on every render, and `theme` and `rt` reach a static style anyway, so the function only adds a call at each use site. The suggestion turns `() => ({ ... })` back into the object.
|
|
118
154
|
*
|
|
155
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
156
|
+
*
|
|
119
157
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylesno-paramless-dynamic-function
|
|
120
158
|
*/
|
|
121
159
|
"@ashstack/unistyles/no-paramless-dynamic-function"?: RuleSetting;
|
|
122
160
|
/**
|
|
123
161
|
* Disallow spreading a stylesheet style into another object. The spread reads through the Unistyles C++ proxy once, so the result stops reacting to the theme.
|
|
124
162
|
*
|
|
163
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
164
|
+
*
|
|
125
165
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylesno-style-spread
|
|
126
166
|
*/
|
|
127
167
|
"@ashstack/unistyles/no-style-spread"?: RuleSetting;
|
|
128
168
|
/**
|
|
129
169
|
* Disallow stylesheet keys that nothing in the file reads. A computed key, a computed read, or a sheet that leaves the module skips the whole file.
|
|
130
170
|
*
|
|
171
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
172
|
+
*
|
|
131
173
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylesno-unused-styles
|
|
132
174
|
*/
|
|
133
175
|
"@ashstack/unistyles/no-unused-styles"?: RuleSetting;
|
|
134
176
|
/**
|
|
135
177
|
* Disallow passing `I18nManager.isRTL` from JSX into a dynamic style function. Unistyles tracks the dependency itself once the style reads `rt.rtl`.
|
|
136
178
|
*
|
|
179
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
180
|
+
*
|
|
137
181
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylesrtl-style-call
|
|
138
182
|
*/
|
|
139
183
|
"@ashstack/unistyles/rtl-style-call"?: RuleSetting;
|
|
140
184
|
/**
|
|
141
185
|
* `theme.screen.*` is a snapshot taken at module initialization, so a component that reads it never sees the current size. `useUnistyles().rt.screen` and `useWindowDimensions` do.
|
|
142
186
|
*
|
|
187
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
188
|
+
*
|
|
143
189
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylestheme-screen-component
|
|
144
190
|
*/
|
|
145
191
|
"@ashstack/unistyles/theme-screen-component"?: RuleSetting;
|
|
146
192
|
/**
|
|
147
193
|
* Theme-dependent values belong in `StyleSheet.create`, not in a JSX `style` prop that reads a `useUnistyles()` theme.
|
|
148
194
|
*
|
|
195
|
+
* **Default: on**, when `react-native-unistyles` is a dependency.
|
|
196
|
+
*
|
|
149
197
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackunistylestheme-style-attr
|
|
150
198
|
*/
|
|
151
199
|
"@ashstack/unistyles/theme-style-attr"?: RuleSetting;
|
|
152
200
|
/**
|
|
153
201
|
* Require `keyExtractor` and an explicit `recycleItems` on a Legend List. Without a key extractor the list keys its rows by index.
|
|
154
202
|
*
|
|
203
|
+
* **Default: on**, when `@legendapp/list` is a dependency.
|
|
204
|
+
*
|
|
155
205
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-listrequired-props
|
|
156
206
|
*/
|
|
157
207
|
"@ashstack/legend-list/required-props"?: RuleSetting;
|
|
158
208
|
/**
|
|
159
209
|
* Disallow a `keyExtractor` that uses its index parameter. Cached sizes and recycled row state hang off the key, so a prepend points every measurement at the wrong item.
|
|
160
210
|
*
|
|
211
|
+
* **Default: on**, when `@legendapp/list` is a dependency.
|
|
212
|
+
*
|
|
161
213
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-listno-index-key-extractor
|
|
162
214
|
*/
|
|
163
215
|
"@ashstack/legend-list/no-index-key-extractor"?: RuleSetting;
|
|
164
216
|
/**
|
|
165
217
|
* Disallow `key` on a Legend List, which remounts on any key change and loses its measurements and scroll position. Pass `dataKey` instead.
|
|
166
218
|
*
|
|
219
|
+
* **Default: on**, when `@legendapp/list` is a dependency.
|
|
220
|
+
*
|
|
167
221
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-listno-remount-key
|
|
168
222
|
*/
|
|
169
223
|
"@ashstack/legend-list/no-remount-key"?: RuleSetting;
|
|
170
224
|
/**
|
|
171
225
|
* Disallow inline object and array literals on props nested inside `renderItem`. A row whose props take a new identity every render can never be skipped.
|
|
172
226
|
*
|
|
227
|
+
* **Default: on**, when `@legendapp/list` is a dependency.
|
|
228
|
+
*
|
|
173
229
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-listno-inline-render-item-props
|
|
174
230
|
*/
|
|
175
231
|
"@ashstack/legend-list/no-inline-render-item-props"?: RuleSetting;
|
|
176
232
|
/**
|
|
177
233
|
* Disallow passing both `data` and real children to a Legend List. The combination is unsupported and one of the two is dropped without a warning.
|
|
178
234
|
*
|
|
235
|
+
* **Default: on**, when `@legendapp/list` is a dependency.
|
|
236
|
+
*
|
|
179
237
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-listno-mixed-children
|
|
180
238
|
*/
|
|
181
239
|
"@ashstack/legend-list/no-mixed-children"?: RuleSetting;
|
|
182
240
|
/**
|
|
183
241
|
* Disallow `flex` in a Legend List's `contentContainerStyle`, where it sizes the scrolled content to the viewport and the list ends up measuring zero height.
|
|
184
242
|
*
|
|
243
|
+
* **Default: on**, when `@legendapp/list` is a dependency.
|
|
244
|
+
*
|
|
185
245
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-listno-flex-in-content-container
|
|
186
246
|
*/
|
|
187
247
|
"@ashstack/legend-list/no-flex-in-content-container"?: RuleSetting;
|
|
188
248
|
/**
|
|
189
249
|
* Require `getItemType` when a row branches on `item.type`. Without it every layout shares one recycling pool and one size average.
|
|
190
250
|
*
|
|
251
|
+
* **Default: on**, when `@legendapp/list` is a dependency.
|
|
252
|
+
*
|
|
191
253
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-listtyped-items-need-item-type
|
|
192
254
|
*/
|
|
193
255
|
"@ashstack/legend-list/typed-items-need-item-type"?: RuleSetting;
|
|
194
256
|
/**
|
|
195
257
|
* A `ScrollView` mounts every child up front, so a mapped collection does not belong in its children.
|
|
196
258
|
*
|
|
259
|
+
* **Default: on**, when `@legendapp/list` is a dependency.
|
|
260
|
+
*
|
|
197
261
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-listno-scrollview-map
|
|
198
262
|
*/
|
|
199
263
|
"@ashstack/legend-list/no-scrollview-map"?: RuleSetting;
|
|
200
264
|
/**
|
|
201
265
|
* Disallow FlashList and FlatList props that Legend List v3 does not have. It ignores them rather than rejecting them, so the feature looks broken.
|
|
202
266
|
*
|
|
267
|
+
* **Default: on**, when `@legendapp/list` is a dependency.
|
|
268
|
+
*
|
|
203
269
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-listno-unsupported-props
|
|
204
270
|
*/
|
|
205
271
|
"@ashstack/legend-list/no-unsupported-props"?: RuleSetting;
|
|
206
272
|
/**
|
|
207
273
|
* Write an observable with `.set(...)` or `.assign({...})`. Assigning or incrementing it with an operator is a silent no-op.
|
|
208
274
|
*
|
|
275
|
+
* **Default: on**, when `@legendapp/state` is a dependency.
|
|
276
|
+
*
|
|
209
277
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-stateno-assignment
|
|
210
278
|
*/
|
|
211
279
|
"@ashstack/legend-state/no-assignment"?: RuleSetting;
|
|
212
280
|
/**
|
|
213
281
|
* A variable initialized from `observable()` or `useObservable()` needs a trailing `$`. The other rules in this module key off that suffix.
|
|
214
282
|
*
|
|
283
|
+
* **Default: on**, when `@legendapp/state` is a dependency.
|
|
284
|
+
*
|
|
215
285
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-statenaming
|
|
216
286
|
*/
|
|
217
287
|
"@ashstack/legend-state/naming"?: RuleSetting;
|
|
218
288
|
/**
|
|
219
289
|
* Never pass an observable to `observable()` or `useObservable()`. The wrapper is a second node, and reads and writes on it never reach the original.
|
|
220
290
|
*
|
|
291
|
+
* **Default: on**, when `@legendapp/state` is a dependency.
|
|
292
|
+
*
|
|
221
293
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-stateno-nested-observable
|
|
222
294
|
*/
|
|
223
295
|
"@ashstack/legend-state/no-nested-observable"?: RuleSetting;
|
|
224
296
|
/**
|
|
225
297
|
* Seeding `useState` from an observable's `get()` or `peek()` gives the value two owners. Read it with `useValue(...)` where it renders.
|
|
226
298
|
*
|
|
299
|
+
* **Default: on**, when `@legendapp/state` is a dependency.
|
|
300
|
+
*
|
|
227
301
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-stateno-react-mirror
|
|
228
302
|
*/
|
|
229
303
|
"@ashstack/legend-state/no-react-mirror"?: RuleSetting;
|
|
230
304
|
/**
|
|
231
305
|
* A `get()` placed directly in a JSX expression container is a plain read. The value renders once and never updates.
|
|
232
306
|
*
|
|
307
|
+
* **Default: on**, when `@legendapp/state` is a dependency.
|
|
308
|
+
*
|
|
233
309
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-stateno-untracked-get-in-jsx
|
|
234
310
|
*/
|
|
235
311
|
"@ashstack/legend-state/no-untracked-get-in-jsx"?: RuleSetting;
|
|
236
312
|
/**
|
|
237
313
|
* `peek()` never subscribes, so a selector or tracking callback that uses it never re-runs. Call `get()` there and keep `peek()` for handlers.
|
|
238
314
|
*
|
|
315
|
+
* **Default: on**, when `@legendapp/state` is a dependency.
|
|
316
|
+
*
|
|
239
317
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-stateno-peek-in-selector
|
|
240
318
|
*/
|
|
241
319
|
"@ashstack/legend-state/no-peek-in-selector"?: RuleSetting;
|
|
242
320
|
/**
|
|
243
321
|
* A `useValue` selector that builds a new object or array returns a fresh identity every run. The component then re-renders on every store change.
|
|
244
322
|
*
|
|
323
|
+
* **Default: on**, when `@legendapp/state` is a dependency.
|
|
324
|
+
*
|
|
245
325
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-stateno-object-selector
|
|
246
326
|
*/
|
|
247
327
|
"@ashstack/legend-state/no-object-selector"?: RuleSetting;
|
|
248
328
|
/**
|
|
249
329
|
* Calling `observable()` inside a component or hook makes a new observable on every render. Use `useObservable()` or a module-level store instead.
|
|
250
330
|
*
|
|
331
|
+
* **Default: on**, when `@legendapp/state` is a dependency.
|
|
332
|
+
*
|
|
251
333
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacklegend-stateno-observable-in-component
|
|
252
334
|
*/
|
|
253
335
|
"@ashstack/legend-state/no-observable-in-component"?: RuleSetting;
|
|
254
336
|
/**
|
|
255
337
|
* A `useAnimatedReaction` result callback loops forever if it writes a shared value the prepare callback reads. Guard `scheduleOnRN` there on the current result differing from the previous one.
|
|
256
338
|
*
|
|
339
|
+
* **Default: on**, when `react-native-reanimated` is a dependency.
|
|
340
|
+
*
|
|
257
341
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreanimatedanimated-reaction-safety
|
|
258
342
|
*/
|
|
259
343
|
"@ashstack/reanimated/animated-reaction-safety"?: RuleSetting;
|
|
260
344
|
/**
|
|
261
345
|
* An animated style only takes effect on an `Animated.*` component. A plain element applies it once at mount and then never updates.
|
|
262
346
|
*
|
|
347
|
+
* **Default: on**, when `react-native-reanimated` is a dependency.
|
|
348
|
+
*
|
|
263
349
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreanimatedanimated-style-needs-animated-component
|
|
264
350
|
*/
|
|
265
351
|
"@ashstack/reanimated/animated-style-needs-animated-component"?: RuleSetting;
|
|
266
352
|
/**
|
|
267
353
|
* An updater passed to `useAnimatedStyle` or `useAnimatedProps` must stay pure, so it may not write a shared value or call `scheduleOnRN`.
|
|
268
354
|
*
|
|
355
|
+
* **Default: on**, when `react-native-reanimated` is a dependency.
|
|
356
|
+
*
|
|
269
357
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreanimatedanimated-updater-purity
|
|
270
358
|
*/
|
|
271
359
|
"@ashstack/reanimated/animated-updater-purity"?: RuleSetting;
|
|
272
360
|
/**
|
|
273
361
|
* Animate `transform` and `opacity` in `useAnimatedStyle` and `useAnimatedProps`. Layout properties such as `width` or `margin` recalculate layout every frame.
|
|
274
362
|
*
|
|
363
|
+
* **Default: on**, when `react-native-reanimated` is a dependency.
|
|
364
|
+
*
|
|
275
365
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreanimatedgpu-properties-only
|
|
276
366
|
*/
|
|
277
367
|
"@ashstack/reanimated/gpu-properties-only"?: RuleSetting;
|
|
278
368
|
/**
|
|
279
369
|
* A layout animation belongs at module scope, or inside a memo when it depends on component values. The `entering`/`exiting`/`layout` props otherwise rebuild it on every render.
|
|
280
370
|
*
|
|
371
|
+
* **Default: on**, when `react-native-reanimated` is a dependency.
|
|
372
|
+
*
|
|
281
373
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreanimatedhoist-layout-animation-builder
|
|
282
374
|
*/
|
|
283
375
|
"@ashstack/reanimated/hoist-layout-animation-builder"?: RuleSetting;
|
|
284
376
|
/**
|
|
285
377
|
* Give `interpolate()` a fourth argument that clamps. Without one, or with `EXTEND` or `IDENTITY`, the output keeps going past the ends of the input range.
|
|
286
378
|
*
|
|
379
|
+
* **Default: on**, when `react-native-reanimated` is a dependency.
|
|
380
|
+
*
|
|
287
381
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreanimatedinterpolate-needs-clamp
|
|
288
382
|
*/
|
|
289
383
|
"@ashstack/reanimated/interpolate-needs-clamp"?: RuleSetting;
|
|
290
384
|
/**
|
|
291
385
|
* Shared values are read with `.get()` and written with `.set(...)`, never through `.value`. React Compiler cannot track a `.value` access.
|
|
292
386
|
*
|
|
387
|
+
* **Default: on**, when `react-native-reanimated` is a dependency.
|
|
388
|
+
*
|
|
293
389
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreanimatedno-shared-value-dot-value
|
|
294
390
|
*/
|
|
295
391
|
"@ashstack/reanimated/no-shared-value-dot-value"?: RuleSetting;
|
|
296
392
|
/**
|
|
297
393
|
* A worklet that runs every frame must not send a React state setter through `scheduleOnRN`. That puts a Fabric commit on an animation frame.
|
|
298
394
|
*
|
|
395
|
+
* **Default: on**, when `react-native-reanimated` is a dependency.
|
|
396
|
+
*
|
|
299
397
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreanimatedno-react-state-from-continuous-worklet
|
|
300
398
|
*/
|
|
301
399
|
"@ashstack/reanimated/no-react-state-from-continuous-worklet"?: RuleSetting;
|
|
302
400
|
/**
|
|
303
401
|
* Pass a computed `useSharedValue` initial value as a function. An eager call runs on every render while only the first result is kept.
|
|
304
402
|
*
|
|
403
|
+
* **Default: on**, when `react-native-reanimated` is a dependency.
|
|
404
|
+
*
|
|
305
405
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreanimatedprefer-lazy-shared-value-initializer
|
|
306
406
|
*/
|
|
307
407
|
"@ashstack/reanimated/prefer-lazy-shared-value-initializer"?: RuleSetting;
|
|
308
408
|
/**
|
|
309
409
|
* `scheduleOnRN` takes a function declared in RN Runtime scope. An inline callback can end up created on the wrong runtime.
|
|
310
410
|
*
|
|
411
|
+
* **Default: on**, when `react-native-reanimated` is a dependency.
|
|
412
|
+
*
|
|
311
413
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreanimatedschedule-on-rn-scope
|
|
312
414
|
*/
|
|
313
415
|
"@ashstack/reanimated/schedule-on-rn-scope"?: RuleSetting;
|
|
314
416
|
/**
|
|
315
417
|
* Destructuring a shared value or mutating what its `get()` returned detaches it from Reanimated reactivity. Reading or writing one while JSX evaluates also makes render impure.
|
|
316
418
|
*
|
|
419
|
+
* **Default: on**, when `react-native-reanimated` is a dependency.
|
|
420
|
+
*
|
|
317
421
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreanimatedshared-value-usage
|
|
318
422
|
*/
|
|
319
423
|
"@ashstack/reanimated/shared-value-usage"?: RuleSetting;
|
|
320
424
|
/**
|
|
321
425
|
* Require `resize` on a TurboImage so the native decoder downsamples before the bitmap reaches memory. A full-resolution decode wastes tens of megabytes and stalls the first frame.
|
|
322
426
|
*
|
|
427
|
+
* **Default: on**, when `react-native-turbo-image` is a dependency.
|
|
428
|
+
*
|
|
323
429
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackturbo-imagerequire-resize
|
|
324
430
|
*/
|
|
325
431
|
"@ashstack/turbo-image/require-resize"?: RuleSetting;
|
|
326
432
|
/**
|
|
327
433
|
* Require `cachePolicy` on a TurboImage. Without one the image is re-fetched over the network on every cold start, so an already-scrolled feed costs its bandwidth again.
|
|
328
434
|
*
|
|
435
|
+
* **Default: on**, when `react-native-turbo-image` is a dependency.
|
|
436
|
+
*
|
|
329
437
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackturbo-imagerequire-cache-policy
|
|
330
438
|
*/
|
|
331
439
|
"@ashstack/turbo-image/require-cache-policy"?: RuleSetting;
|
|
332
440
|
/**
|
|
333
441
|
* Require an explicit `opaque` prop on a Skia `<Canvas>`. A fullscreen animated canvas wants it on; anything that needs transparency or a view transform wants it off.
|
|
334
442
|
*
|
|
443
|
+
* **Default: on**, when `@shopify/react-native-skia` is a dependency.
|
|
444
|
+
*
|
|
335
445
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackskiacanvas-opaque
|
|
336
446
|
*/
|
|
337
447
|
"@ashstack/skia/canvas-opaque"?: RuleSetting;
|
|
@@ -2,65 +2,123 @@ import type { AllowWarnDeny } from "oxlint";
|
|
|
2
2
|
/** A severity, or a severity followed by as many of the rule's options as you want to set. */
|
|
3
3
|
type RuleSetting<Options extends unknown[] = []> = AllowWarnDeny | [AllowWarnDeny, ...Partial<Options>];
|
|
4
4
|
/** Every rule id `react()` adds on top of the entry below it. */
|
|
5
|
-
export type ReactRuleId = "@ashstack/react/no-unlabeled-icon-button" | "@ashstack/react/no-svg-without-title" | "@ashstack/query/no-inline-keys" | "@ashstack/query/no-deprecated-filters" | "@ashstack/query/require-destructured-hooks" | "@ashstack/query/no-fetch-in-query-fn" | "@ashstack/query/next-page-param-undefined" | "@ashstack/zustand/require-selector" | "@ashstack/i18n/no-bare-text" | "@ashstack/i18n/no-bare-attrs" | "@ashstack/i18n/no-bare-toast" | "@ashstack/tailwind/prefer-cn" | "@ashstack/tailwind/use-logical-classes" | "@ashstack/tanstack-router/require-selector" | "@ashstack/tanstack-router/no-search-casts" | "@ashstack/effects/no-derived-state" | "@ashstack/effects/no-chain-state-updates" | "@ashstack/effects/no-event-handler" | "@ashstack/effects/no-adjust-state-on-prop-change" | "@ashstack/effects/no-reset-all-state-on-prop-change" | "@ashstack/effects/no-pass-live-state-to-parent" | "@ashstack/effects/no-pass-data-to-parent" | "@ashstack/effects/no-external-store-subscription" | "@ashstack/effects/no-initialize-state";
|
|
5
|
+
export type ReactRuleId = "@ashstack/react/no-unlabeled-icon-button" | "@ashstack/react/no-svg-without-title" | "@ashstack/react/hoist-intl" | "@ashstack/react/prefer-design-system" | "@ashstack/react/components-tsx-only" | "@ashstack/query/no-inline-keys" | "@ashstack/query/no-deprecated-filters" | "@ashstack/query/require-destructured-hooks" | "@ashstack/query/no-fetch-in-query-fn" | "@ashstack/query/next-page-param-undefined" | "@ashstack/zustand/require-selector" | "@ashstack/i18n/no-bare-text" | "@ashstack/i18n/no-bare-attrs" | "@ashstack/i18n/no-bare-toast" | "@ashstack/tailwind/prefer-cn" | "@ashstack/tailwind/use-logical-classes" | "@ashstack/tanstack-router/require-selector" | "@ashstack/tanstack-router/no-search-casts" | "@ashstack/effects/no-derived-state" | "@ashstack/effects/no-chain-state-updates" | "@ashstack/effects/no-event-handler" | "@ashstack/effects/no-adjust-state-on-prop-change" | "@ashstack/effects/no-reset-all-state-on-prop-change" | "@ashstack/effects/no-pass-live-state-to-parent" | "@ashstack/effects/no-pass-data-to-parent" | "@ashstack/effects/no-external-store-subscription" | "@ashstack/effects/no-initialize-state";
|
|
6
6
|
declare module "oxlint" {
|
|
7
7
|
interface DummyRuleMap {
|
|
8
8
|
/**
|
|
9
9
|
* 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.
|
|
10
10
|
*
|
|
11
|
+
* **Default: on.**
|
|
12
|
+
*
|
|
11
13
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreactno-unlabeled-icon-button
|
|
12
14
|
*/
|
|
13
15
|
"@ashstack/react/no-unlabeled-icon-button"?: RuleSetting;
|
|
14
16
|
/**
|
|
15
17
|
* 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.
|
|
16
18
|
*
|
|
19
|
+
* **Default: on.**
|
|
20
|
+
*
|
|
17
21
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreactno-svg-without-title
|
|
18
22
|
*/
|
|
19
23
|
"@ashstack/react/no-svg-without-title"?: RuleSetting;
|
|
24
|
+
/**
|
|
25
|
+
* Disallow `new Intl.*` inside a function that renders JSX, unless the call already sits in `useMemo` or `useCallback`.
|
|
26
|
+
*
|
|
27
|
+
* **Default: on.**
|
|
28
|
+
*
|
|
29
|
+
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreacthoist-intl
|
|
30
|
+
*/
|
|
31
|
+
"@ashstack/react/hoist-intl"?: RuleSetting;
|
|
32
|
+
/**
|
|
33
|
+
* Disallow importing a raw primitive your design system already wraps. Wrappers come from scanning the design-system directory and from the `use` map. Files inside the design system are skipped.
|
|
34
|
+
*
|
|
35
|
+
* **Default: off.** Turn it on by id in your `rules` block.
|
|
36
|
+
*
|
|
37
|
+
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreactprefer-design-system
|
|
38
|
+
*/
|
|
39
|
+
"@ashstack/react/prefer-design-system"?: RuleSetting<[
|
|
40
|
+
{
|
|
41
|
+
dir?: string;
|
|
42
|
+
alias?: string;
|
|
43
|
+
use?: Record<string, string | Array<string> | {
|
|
44
|
+
replaces: string | Array<string>;
|
|
45
|
+
from?: string;
|
|
46
|
+
path?: string;
|
|
47
|
+
reason?: string;
|
|
48
|
+
}>;
|
|
49
|
+
exempt?: Array<string>;
|
|
50
|
+
}
|
|
51
|
+
]>;
|
|
52
|
+
/**
|
|
53
|
+
* Require every file under the components directory to render JSX or be a re-export barrel.
|
|
54
|
+
*
|
|
55
|
+
* **Default: off.** Turn it on by id in your `rules` block.
|
|
56
|
+
*
|
|
57
|
+
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackreactcomponents-tsx-only
|
|
58
|
+
*/
|
|
59
|
+
"@ashstack/react/components-tsx-only"?: RuleSetting<[{
|
|
60
|
+
dir?: string;
|
|
61
|
+
}]>;
|
|
20
62
|
/**
|
|
21
63
|
* Disallow a query key written as an array literal at the call site instead of coming from a keys factory.
|
|
22
64
|
*
|
|
65
|
+
* **Default: on**, when `@tanstack/react-query` is a dependency.
|
|
66
|
+
*
|
|
23
67
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackqueryno-inline-keys
|
|
24
68
|
*/
|
|
25
69
|
"@ashstack/query/no-inline-keys"?: RuleSetting;
|
|
26
70
|
/**
|
|
27
71
|
* Disallow the positional key argument that TanStack Query v5 removed from `invalidateQueries` and its sibling methods. The suggestion rewrites it to the filter-object form.
|
|
28
72
|
*
|
|
73
|
+
* **Default: on**, when `@tanstack/react-query` is a dependency.
|
|
74
|
+
*
|
|
29
75
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackqueryno-deprecated-filters
|
|
30
76
|
*/
|
|
31
77
|
"@ashstack/query/no-deprecated-filters"?: RuleSetting;
|
|
32
78
|
/**
|
|
33
79
|
* Require destructuring the result of a hook imported from an `@/api/*.queries` or `@/api/*.mutations` module.
|
|
34
80
|
*
|
|
81
|
+
* **Default: on**, when `@tanstack/react-query` is a dependency.
|
|
82
|
+
*
|
|
35
83
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackqueryrequire-destructured-hooks
|
|
36
84
|
*/
|
|
37
85
|
"@ashstack/query/require-destructured-hooks"?: RuleSetting;
|
|
38
86
|
/**
|
|
39
87
|
* Disallow a bare `fetch(` inside a `queryFn` or `mutationFn`.
|
|
40
88
|
*
|
|
89
|
+
* **Default: on**, when `@tanstack/react-query` is a dependency.
|
|
90
|
+
*
|
|
41
91
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackqueryno-fetch-in-query-fn
|
|
42
92
|
*/
|
|
43
93
|
"@ashstack/query/no-fetch-in-query-fn"?: RuleSetting;
|
|
44
94
|
/**
|
|
45
95
|
* Disallow `return null` in the body of a `getNextPageParam`.
|
|
46
96
|
*
|
|
97
|
+
* **Default: on**, when `@tanstack/react-query` is a dependency.
|
|
98
|
+
*
|
|
47
99
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackquerynext-page-param-undefined
|
|
48
100
|
*/
|
|
49
101
|
"@ashstack/query/next-page-param-undefined"?: RuleSetting;
|
|
50
102
|
/**
|
|
51
103
|
* Require a selector on a store hook, rather than no arguments or `undefined` in its place.
|
|
52
104
|
*
|
|
105
|
+
* **Default: on**, when `zustand` is a dependency.
|
|
106
|
+
*
|
|
53
107
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackzustandrequire-selector
|
|
54
108
|
*/
|
|
55
109
|
"@ashstack/zustand/require-selector"?: RuleSetting;
|
|
56
110
|
/**
|
|
57
111
|
* Disallow a JSX element that carries no attributes and whose single child is plain literal text.
|
|
58
112
|
*
|
|
113
|
+
* **Default: on**, when `i18next`, `react-i18next`, `@lingui/core`, `react-intl`, `use-intl`, `next-intl` or `expo-localization` is a dependency.
|
|
114
|
+
*
|
|
59
115
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacki18nno-bare-text
|
|
60
116
|
*/
|
|
61
117
|
"@ashstack/i18n/no-bare-text"?: RuleSetting;
|
|
62
118
|
/**
|
|
63
|
-
* Disallow a plain string literal on a
|
|
119
|
+
* Disallow a plain string literal on a user-visible JSX attribute.
|
|
120
|
+
*
|
|
121
|
+
* **Default: on**, when `i18next`, `react-i18next`, `@lingui/core`, `react-intl`, `use-intl`, `next-intl` or `expo-localization` is a dependency.
|
|
64
122
|
*
|
|
65
123
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacki18nno-bare-attrs
|
|
66
124
|
*/
|
|
@@ -70,84 +128,112 @@ declare module "oxlint" {
|
|
|
70
128
|
/**
|
|
71
129
|
* Disallow a `toast.*` call whose only argument is a string literal.
|
|
72
130
|
*
|
|
131
|
+
* **Default: on**, when `i18next`, `react-i18next`, `@lingui/core`, `react-intl`, `use-intl`, `next-intl` or `expo-localization` is a dependency.
|
|
132
|
+
*
|
|
73
133
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacki18nno-bare-toast
|
|
74
134
|
*/
|
|
75
135
|
"@ashstack/i18n/no-bare-toast"?: RuleSetting;
|
|
76
136
|
/**
|
|
77
137
|
* 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.
|
|
78
138
|
*
|
|
139
|
+
* **Default: on**, when `tailwindcss` is a dependency.
|
|
140
|
+
*
|
|
79
141
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacktailwindprefer-cn
|
|
80
142
|
*/
|
|
81
143
|
"@ashstack/tailwind/prefer-cn"?: RuleSetting;
|
|
82
144
|
/**
|
|
83
145
|
* 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.
|
|
84
146
|
*
|
|
147
|
+
* **Default: on**, when `tailwindcss` is a dependency.
|
|
148
|
+
*
|
|
85
149
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacktailwinduse-logical-classes
|
|
86
150
|
*/
|
|
87
151
|
"@ashstack/tailwind/use-logical-classes"?: RuleSetting;
|
|
88
152
|
/**
|
|
89
153
|
* 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.
|
|
90
154
|
*
|
|
155
|
+
* **Default: on**, when `@tanstack/react-router` is a dependency.
|
|
156
|
+
*
|
|
91
157
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacktanstack-routerrequire-selector
|
|
92
158
|
*/
|
|
93
159
|
"@ashstack/tanstack-router/require-selector"?: RuleSetting;
|
|
94
160
|
/**
|
|
95
161
|
* Disallow an `as` assertion on a `useSearch()` result or on `router.state.location.search`. The route's `validateSearch` schema is what supplies the type.
|
|
96
162
|
*
|
|
163
|
+
* **Default: on**, when `@tanstack/react-router` is a dependency.
|
|
164
|
+
*
|
|
97
165
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstacktanstack-routerno-search-casts
|
|
98
166
|
*/
|
|
99
167
|
"@ashstack/tanstack-router/no-search-casts"?: RuleSetting;
|
|
100
168
|
/**
|
|
101
169
|
* Disallow storing derived state in an effect. [Why](https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)
|
|
102
170
|
*
|
|
171
|
+
* **Default: on.**
|
|
172
|
+
*
|
|
103
173
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackeffectsno-derived-state
|
|
104
174
|
*/
|
|
105
175
|
"@ashstack/effects/no-derived-state"?: RuleSetting;
|
|
106
176
|
/**
|
|
107
177
|
* Disallow chaining state changes in an effect. [Why](https://react.dev/learn/you-might-not-need-an-effect#chains-of-computations)
|
|
108
178
|
*
|
|
179
|
+
* **Default: on.**
|
|
180
|
+
*
|
|
109
181
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackeffectsno-chain-state-updates
|
|
110
182
|
*/
|
|
111
183
|
"@ashstack/effects/no-chain-state-updates"?: RuleSetting;
|
|
112
184
|
/**
|
|
113
185
|
* Disallow using state and an effect as an event handler. [Why](https://react.dev/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers)
|
|
114
186
|
*
|
|
187
|
+
* **Default: on.**
|
|
188
|
+
*
|
|
115
189
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackeffectsno-event-handler
|
|
116
190
|
*/
|
|
117
191
|
"@ashstack/effects/no-event-handler"?: RuleSetting;
|
|
118
192
|
/**
|
|
119
193
|
* Disallow adjusting state in an effect when a prop changes. [Why](https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes)
|
|
120
194
|
*
|
|
195
|
+
* **Default: on.**
|
|
196
|
+
*
|
|
121
197
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackeffectsno-adjust-state-on-prop-change
|
|
122
198
|
*/
|
|
123
199
|
"@ashstack/effects/no-adjust-state-on-prop-change"?: RuleSetting;
|
|
124
200
|
/**
|
|
125
201
|
* Disallow resetting all state in an effect when a prop changes. [Why](https://react.dev/learn/you-might-not-need-an-effect#resetting-all-state-when-a-prop-changes)
|
|
126
202
|
*
|
|
203
|
+
* **Default: on.**
|
|
204
|
+
*
|
|
127
205
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackeffectsno-reset-all-state-on-prop-change
|
|
128
206
|
*/
|
|
129
207
|
"@ashstack/effects/no-reset-all-state-on-prop-change"?: RuleSetting;
|
|
130
208
|
/**
|
|
131
209
|
* Disallow passing live state to parents in an effect. [Why](https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes)
|
|
132
210
|
*
|
|
211
|
+
* **Default: on.**
|
|
212
|
+
*
|
|
133
213
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackeffectsno-pass-live-state-to-parent
|
|
134
214
|
*/
|
|
135
215
|
"@ashstack/effects/no-pass-live-state-to-parent"?: RuleSetting;
|
|
136
216
|
/**
|
|
137
217
|
* Disallow passing data to parents in an effect. [Why](https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent)
|
|
138
218
|
*
|
|
219
|
+
* **Default: on.**
|
|
220
|
+
*
|
|
139
221
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackeffectsno-pass-data-to-parent
|
|
140
222
|
*/
|
|
141
223
|
"@ashstack/effects/no-pass-data-to-parent"?: RuleSetting;
|
|
142
224
|
/**
|
|
143
225
|
* Disallow subscribing to an external store in an effect. [Why](https://react.dev/learn/you-might-not-need-an-effect#subscribing-to-an-external-store)
|
|
144
226
|
*
|
|
227
|
+
* **Default: on.**
|
|
228
|
+
*
|
|
145
229
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackeffectsno-external-store-subscription
|
|
146
230
|
*/
|
|
147
231
|
"@ashstack/effects/no-external-store-subscription"?: RuleSetting;
|
|
148
232
|
/**
|
|
149
233
|
* Disallow initializing state in an effect. [Why](https://tkdodo.eu/blog/avoiding-hydration-mismatches-with-use-sync-external-store)
|
|
150
234
|
*
|
|
235
|
+
* **Default: on.**
|
|
236
|
+
*
|
|
151
237
|
* @see https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md#ashstackeffectsno-initialize-state
|
|
152
238
|
*/
|
|
153
239
|
"@ashstack/effects/no-initialize-state"?: RuleSetting;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ import type { OxlintConfig, ReactOptions } from "../lib/types.js";
|
|
|
7
7
|
* `stylex.create` — so every line left is one a reader has to follow.
|
|
8
8
|
*/
|
|
9
9
|
export declare const COMPONENT_MAX_LINES = 250;
|
|
10
|
+
/**
|
|
11
|
+
* Tighter than core's 15, because a component's branching belongs in the tree
|
|
12
|
+
* it returns rather than in the function that returns it: a guard or two, a
|
|
13
|
+
* ternary or two, and the rest is JSX. Extracting a handler or a `map`
|
|
14
|
+
* callback drops the count on its own —
|
|
15
|
+
* `@ashstack/core/max-complexity` scores every function separately.
|
|
16
|
+
*/
|
|
17
|
+
export declare const COMPONENT_MAX_COMPLEXITY = 10;
|
|
10
18
|
/**
|
|
11
19
|
* React (web) entry — everything in core plus react, jsx-a11y, React Compiler
|
|
12
20
|
* diagnostics, you-might-not-need-an-effect, and the auto-detected library
|
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 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"];export const COMPONENT_MAX_LINES=250;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"];export const COMPONENT_MAX_LINES=250,COMPONENT_MAX_COMPLEXITY=10;const x={...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:{...x,...f(e.reactCompiler??!0),...t,...r.rules,"@ashstack/core/max-lines":["error",COMPONENT_MAX_LINES],"@ashstack/core/max-complexity":["error",COMPONENT_MAX_COMPLEXITY]},overrides:[{files:u,rules:{"unicorn/filename-case":"off"}}]})};export{react};export default react;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{attributeName as
|
|
1
|
+
import{attributeName as s,optionsOf as o}from"../../../lib/ast.js";const l='Pass `t("<key>")` as this attribute value and add the key to every locale file.',a=["placeholder","accessibilityLabel","accessibilityHint","title"];export const noBareAttrs={meta:{type:"problem",docs:{description:"Disallow a plain string literal on a user-visible JSX attribute."},schema:[{type:"object",properties:{attributes:{type:"array",items:{type:"string"},minItems:1,default:a,description:"Attributes checked for bare strings. Replaces the list rather than adding to it."}},additionalProperties:!1}]},createOnce(i){const r=new Set;return{before(){r.clear();const t=o(i,{}).attributes;for(const e of t??a)r.add(e)},JSXAttribute(t){if(!r.has(s(t)))return;const{value:e}=t;if(e?.type!=="Literal"||typeof e.value!=="string"||e.value.length===0)return;i.report({node:t,message:l})}}}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{optionsOf as s}from"../../../lib/ast.js";const n="Move this file to `src/utils`, `src/hooks`, or `src/api/<feature>/` — `components/` holds only files that render JSX.",t="src/components",i=(r)=>r.every((e)=>e.type==="ImportDeclaration"||e.type==="ExportAllDeclaration"||e.type==="ExportNamedDeclaration"&&e.declaration==null);export const componentsTsxOnly={meta:{type:"problem",docs:{description:"Require every file under the components directory to render JSX or be a re-export barrel."},schema:[{type:"object",properties:{dir:{type:"string",minLength:1,default:t,description:"Directory whose files must render JSX."}},additionalProperties:!1}],defaultOff:!0},createOnce(r){let e=!1;return{before(){e=!1;const{dir:o=t}=s(r,{});return r.filename.replaceAll("\\","/").includes(`/${o}/`)},JSXElement(){e=!0},JSXFragment(){e=!0},"Program:exit"(o){if(e||i(o.body))return;r.report({node:o,message:n})}}}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineModule as t}from"../../../lib/module.js";import{
|
|
1
|
+
import{defineModule as t}from"../../../lib/module.js";import{componentsTsxOnly as o}from"./components-tsx-only.js";import{hoistIntl as e}from"./hoist-intl.js";import{noSvgWithoutTitle as n}from"./no-svg-without-title.js";import{noUnlabeledIconButton as r}from"./no-unlabeled-icon-button.js";import{preferDesignSystem as i}from"./prefer-design-system.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":r,"no-svg-without-title":n,"hoist-intl":e,"prefer-design-system":i,"components-tsx-only":o}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readdirSync as g}from"node:fs";import{isAbsolute as f,join as h}from"node:path";import{optionsOf as b}from"../../../lib/ast.js";const d="src/components/ui",l="@/components/ui",S=/\.(?:ios|android|native|web)$/,m=/^[@~#][^/]*\//,A={Pressable:["TouchableOpacity","TouchableHighlight","TouchableWithoutFeedback","TouchableNativeFeedback"]},y={anyOf:[{type:"string"},{type:"array",items:{type:"string"}}]},w={anyOf:[...y.anyOf,{type:"object",properties:{replaces:y,from:{type:"string"},path:{type:"string"},reason:{type:"string"}},required:["replaces"],additionalProperties:!1}]},E=(e)=>e.split("-").filter(Boolean).map((t)=>t.charAt(0).toUpperCase()+t.slice(1)).join(""),I=(e)=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),p=(e,t,r,s)=>{const n=e.get(t)??new Map;n.set(r,s);e.set(t,n)},x=(e,t,r)=>{let s;try{s=g(f(t)?t:h(process.cwd(),t))}catch{return}for(const n of s){if(!n.endsWith(".tsx"))continue;const o=n.slice(0,-4).replace(S,"");if(o==="index")continue;const a=E(o),i=`${r}/${o}`;p(e,"react-native",a,{name:a,from:i});for(const c of A[a]??[])p(e,"react-native",c,{name:a,from:i})}},O=(e,t,r)=>{for(const[s,n]of Object.entries(r)){const o=typeof n==="string"||Array.isArray(n)?{replaces:n}:n,a={name:s,from:o.path??`${t}/${I(s)}`,reason:o.reason};for(const i of[o.replaces].flat())p(e,o.from??"react-native",i,a)}},T=(e,t,r)=>{const s=[e,t.replace(m,"")];for(const n of Object.values(r)){if(typeof n==="string"||Array.isArray(n)||n.path===void 0)continue;s.push(n.path.replace(m,""))}return s.filter((n)=>n.length>0)},u=new Map,_=(e,t)=>{if(!e)return!1;const r=e.replaceAll("\\","/");return t.some((s)=>r.includes(f(s)?s:`/${s}`))},v=(e)=>{const t=e.dir??d,r=e.alias??l,s=e.use??{},n=JSON.stringify([t,r,e.use]);let o=u.get(n);if(o===void 0){const a=new Map;x(a,t,r);O(a,r,s);o={banned:a,wrappers:T(t,r,s)};u.set(n,o)}return{banned:o.banned,exempt:[...o.wrappers,...e.exempt??[]]}};export const preferDesignSystem={meta:{type:"problem",docs:{description:"Disallow importing a raw primitive your design system already wraps. Wrappers come from scanning the design-system directory and from the `use` map. Files inside the design system are skipped."},schema:[{type:"object",properties:{dir:{type:"string",minLength:1,default:d,description:"Directory scanned for wrapper components. Every `.tsx` file in it becomes a wrapper."},alias:{type:"string",minLength:1,default:l,description:"Import prefix the diagnostic points at, and a second folder counted as the design system."},use:{type:"object",additionalProperties:w,default:{},description:"Wrappers the scan cannot find, keyed by component name.",examples:[{Button:"Pressable",Text:["Text","RNText"],Sheet:{replaces:"Modal",from:"react-native",path:"@/ui/sheet",reason:"It owns insets."}}]},exempt:{type:"array",items:{type:"string"},default:[],description:"Extra path fragments to skip, added to the design system's own folders."}},additionalProperties:!1}],defaultOff:!0},createOnce(e){let t=new Map;return{before(){const r=b(e,{}),s=v(r);t=s.banned;if(t.size===0)return!1;return!_(e.filename,s.exempt)},ImportDeclaration(r){const s=r.source.value,n=t.get(s);if(n===void 0)return;for(const o of r.specifiers){if(o.type!=="ImportSpecifier")continue;const{imported:a}=o;if(a.type!=="Identifier")continue;const i=n.get(a.name);if(i===void 0)continue;const c=i.reason?` ${i.reason}`:"";e.report({node:o,message:`Import \`${i.name}\` from "${i.from}" instead of \`${a.name}\` from ${s}.${c}`})}}}}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TEST_FILES as a}from"../core/index.js";import{mergeConfigs as r}from"../lib/merge.js";import{composeModules as s}from"../lib/module.js";import{coreRegistry as o,reactNativeRegistry as i,reactRegistry as n}from"../lib/registry.js";import c,{COMPONENT_MAX_LINES as
|
|
1
|
+
import{TEST_FILES as a}from"../core/index.js";import{mergeConfigs as r}from"../lib/merge.js";import{composeModules as s}from"../lib/module.js";import{coreRegistry as o,reactNativeRegistry as i,reactRegistry as n}from"../lib/registry.js";import c,{COMPONENT_MAX_COMPLEXITY as p,COMPONENT_MAX_LINES as m}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`. Inside a SwiftUI/Compose host view, keep React Native's `Pressable` and suppress this rule naming the host."}]}},{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 l={"eslint/no-empty":"error","eslint/no-empty-function":"error"},u={"@ashstack/effects/no-event-handler":"off"},d=(e)=>e?{}:{"@ashstack/react-native/no-manual-memo":"off"},reactNative=(e={})=>{const t=s([...o,...n,...i],e,banGroups);return r(c(e),{jsPlugins:t.jsPlugins,globals:{__DEV__:"readonly"},rules:{...l,...u,"no-restricted-imports":["error",t.restricted],...t.rules,"@ashstack/core/max-lines":["error",m],"@ashstack/core/max-complexity":["error",p],...d(e.reactCompiler??!0)},ignorePatterns:["**/.expo/**","**/android/**","**/ios/**"],overrides:[{files:a,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 u}from"../../../lib/ast.js";const i=new Set(["useMemo","useCallback","memo"]),c=/^why:/i,
|
|
1
|
+
import{gate as m,problem as u}from"../../../lib/ast.js";const i=new Set(["useMemo","useCallback","memo"]),c=/^why:/i,f=10,p=(e)=>e.type==="Identifier"&&i.has(e.name)?e.name:null,h=(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 i.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 h(t);return null},y=(e)=>{const t=[0];for(let o=0;o<e.length;o++)if(e.charCodeAt(o)===f)t.push(o+1);return t},C=(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},b=(e,t)=>{const o=new Set;for(const n of e){if(!c.test(n.value.trim()))continue;for(let r=t(n.start);r<=t(n.end);r++)o.add(r)}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, so a memo is allowed where the cost is real: a list row, or a computation measured as heavy. Turns off with `reactCompiler: false`.",{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=(s)=>C(o,s),r=b(e.sourceCode.getAllComments(),n);for(const{node:s,name:l}of t){const a=n(s.start);if(r.has(a)||r.has(a-1))continue;e.report({node:s,message:`Add a \`// why:\` line above this \`${l}\` naming the real cost — a list row, or a computation you measured as heavy. If neither applies, delete it.`})}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{calleeName as
|
|
1
|
+
import{calleeName as h,findInSubtree as b,gate as d,problem as w,subtreeHas as S,tagIdentifier as C}from"../../../lib/ast.js";import{isStyleSheetCreate as g,memberPath as E,propertyName as I,stylesObjectOf as M}from"./shared.js";const y={contentContainerRuntime:"Wrap this component with `withUnistyles`, or pass `contentContainerStyle` a hook-fed style — on a raw component it never subscribes to Unistyles updates.",contentContainerTheme:"Wrap this component with `withUnistyles`, or pass `contentContainerStyle` a hook-fed style — on a raw component it never subscribes to theme changes."};export const contentContainer=w("A raw component never subscribes its `contentContainerStyle` to theme or `rt` updates. Wrap the component with `withUnistyles` when the style depends on either.",{createOnce(l){const a=new Map,p=new Set,c=[],f=(e)=>{const t=b(e.value,(s)=>s.type==="MemberExpression"&&s.object.type==="Identifier"&&a.has(s.object.name));if(t?.type!=="MemberExpression")return null;const n=e.parent;if(n.type==="JSXOpeningElement"&&p.has(C(n.name)))return null;const{object:o,property:i}=t;if(o.type!=="Identifier")return null;const r=a.get(o.name)?.get(i.type==="Identifier"?i.name:"");if(!r)return null;const m=(s)=>S(r,(u)=>u.type==="MemberExpression"&&E(u).startsWith(s));if(m("rt."))return y.contentContainerRuntime;return m("theme.")?y.contentContainerTheme:null};return{before(){a.clear();p.clear();c.length=0;return d(l,"contentContainerStyle")},VariableDeclarator(e){const{id:t,init:n}=e;if(t.type!=="Identifier")return;if(h(n)==="withUnistyles"){p.add(t.name);return}if(n?.type!=="CallExpression"||!g(n))return;const o=M(n.arguments[0]);if(!o)return;const i=new Map;for(const r of o.properties){if(r.type!=="Property")continue;i.set(I(r),r.value)}a.set(t.name,i)},JSXAttribute(e){if(e.name.type!=="JSXIdentifier"||e.name.name!=="contentContainerStyle")return;c.push(e)},"Program:exit"(){for(const e of c){const t=f(e);if(t!==null)l.report({node:e,message:t})}}}}});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashstack/lint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "ashStack shared oxlint configuration and custom rules. Entries: core, react, react-native, with per-library rule groups auto-detected from your dependencies. Consume from oxlint.config.mts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{optionsOf as t}from"../../../lib/ast.js";const s="Move this file to `src/utils`, `src/hooks`, or `src/api/<feature>/` — `components/` holds only files that render JSX.",n="src/components",i=(r)=>r.every((e)=>e.type==="ImportDeclaration"||e.type==="ExportAllDeclaration"||e.type==="ExportNamedDeclaration"&&e.declaration==null);export const componentsTsxOnly={meta:{type:"problem",docs:{description:"Require every file under the components directory to render JSX or be a re-export barrel. `dir` says which directory, defaulting to `src/components`."},schema:[{type:"object",properties:{dir:{type:"string",minLength:1}},additionalProperties:!1}],defaultOff:!0},createOnce(r){let e=!1;return{before(){e=!1;const{dir:o=n}=t(r,{});return r.filename.replaceAll("\\","/").includes(`/${o}/`)},JSXElement(){e=!0},JSXFragment(){e=!0},"Program:exit"(o){if(e||i(o.body))return;r.report({node:o,message:s})}}}};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{readdirSync as l}from"node:fs";import{isAbsolute as d,join as y}from"node:path";import{optionsOf as u}from"../../../lib/ast.js";const g="src/components/ui",b="@/components/ui",h=/\.(?:ios|android|native|web)$/,S={Pressable:["TouchableOpacity","TouchableHighlight","TouchableWithoutFeedback","TouchableNativeFeedback"]},m={anyOf:[{type:"string"},{type:"array",items:{type:"string"}}]},A={anyOf:[...m.anyOf,{type:"object",properties:{replaces:m,from:{type:"string"},path:{type:"string"},reason:{type:"string"}},required:["replaces"],additionalProperties:!1}]},E=(e)=>e.split("-").filter(Boolean).map((t)=>t.charAt(0).toUpperCase()+t.slice(1)).join(""),I=(e)=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),p=(e,t,r,o)=>{const n=e.get(t)??new Map;n.set(r,o);e.set(t,n)},O=(e,t,r)=>{let o;try{o=l(d(t)?t:y(process.cwd(),t))}catch{return}for(const n of o){if(!n.endsWith(".tsx"))continue;const s=n.slice(0,-4).replace(h,"");if(s==="index")continue;const i=E(s),a=`${r}/${s}`;p(e,"react-native",i,{name:i,from:a});for(const c of S[i]??[])p(e,"react-native",c,{name:i,from:a})}},w=(e,t,r)=>{for(const[o,n]of Object.entries(r)){const s=typeof n==="string"||Array.isArray(n)?{replaces:n}:n,i={name:o,from:s.path??`${t}/${I(o)}`,reason:s.reason};for(const a of[s.replaces].flat())p(e,s.from??"react-native",a,i)}},f=new Map,_=(e,t)=>!!e&&t.some((r)=>e.includes(r)),M=(e)=>{const t=e.dir??g,r=e.alias??b,o=e.exempt??[t],n=JSON.stringify([t,r,e.use]);let s=f.get(n);if(s===void 0){s=new Map;O(s,t,r);if(e.use)w(s,r,e.use);f.set(n,s)}return{banned:s,exempt:o}};export const useDesignSystem={meta:{type:"problem",docs:{description:"Disallow importing a raw primitive your design system already wraps. Wrapped components come from scanning the design-system directory, plus the explicit `use` map for names, paths and source modules the scan cannot infer. Files under the design-system directory (or `exempt`) are skipped."},schema:[{type:"object",properties:{dir:{type:"string",minLength:1},alias:{type:"string",minLength:1},use:{type:"object",additionalProperties:A},exempt:{type:"array",items:{type:"string"}}},additionalProperties:!1}],defaultOff:!0},createOnce(e){let t=new Map;return{before(){const r=u(e,{}),o=M(r);t=o.banned;if(t.size===0)return!1;const{filename:n}=e;if(_(n,o.exempt))return!1;return!0},ImportDeclaration(r){const o=r.source.value,n=t.get(o);if(n===void 0)return;for(const s of r.specifiers){if(s.type!=="ImportSpecifier")continue;const{imported:i}=s;if(i.type!=="Identifier")continue;const a=n.get(i.name);if(a===void 0)continue;const c=a.reason?` ${a.reason}`:"";e.report({node:s,message:`Import \`${a.name}\` from "${a.from}" instead of \`${i.name}\` from ${o}.${c}`})}}}}};
|
|
File without changes
|