oxlint-plugin-react-doctor 0.2.10 → 0.2.11
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/dist/index.d.ts +9 -2
- package/dist/index.js +46 -42
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -82,10 +82,17 @@ interface ScopeAnalysis {
|
|
|
82
82
|
//#region src/plugin/utils/rule-context.d.ts
|
|
83
83
|
interface BaseRuleContext {
|
|
84
84
|
report: (descriptor: ReportDescriptor) => void;
|
|
85
|
-
|
|
85
|
+
readonly filename?: string;
|
|
86
|
+
/**
|
|
87
|
+
* @deprecated Rules use `context.filename`. Read only as a fallback by
|
|
88
|
+
* `wrapWithSemanticContext`; ESLint implements it as a `this`-bound class
|
|
89
|
+
* method, so it must be called on the host context, never a detached
|
|
90
|
+
* reference.
|
|
91
|
+
*/
|
|
92
|
+
getFilename?: () => string | undefined;
|
|
86
93
|
readonly settings?: Readonly<Record<string, unknown>>;
|
|
87
94
|
}
|
|
88
|
-
interface RuleContext extends BaseRuleContext {
|
|
95
|
+
interface RuleContext extends Omit<BaseRuleContext, "getFilename"> {
|
|
89
96
|
readonly scopes: ScopeAnalysis;
|
|
90
97
|
readonly cfg: ControlFlowAnalysis;
|
|
91
98
|
}
|
package/dist/index.js
CHANGED
|
@@ -223,7 +223,7 @@ const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
|
|
|
223
223
|
//#endregion
|
|
224
224
|
//#region src/plugin/utils/define-rule.ts
|
|
225
225
|
const wrapCreateForTestNoise = (create) => ((context) => {
|
|
226
|
-
if (isTestlikeFilename(context.
|
|
226
|
+
if (isTestlikeFilename(context.filename)) return {};
|
|
227
227
|
return create(context);
|
|
228
228
|
});
|
|
229
229
|
const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
|
|
@@ -3127,7 +3127,7 @@ const asyncParallel = defineRule({
|
|
|
3127
3127
|
severity: "warn",
|
|
3128
3128
|
recommendation: "Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` to run independent operations concurrently",
|
|
3129
3129
|
create: (context) => {
|
|
3130
|
-
const filename = normalizeFilename$1(context.
|
|
3130
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
3131
3131
|
const isBrowserTestFile = BROWSER_TEST_FILE_PATTERN.test(filename);
|
|
3132
3132
|
let hasTestLibraryImport = false;
|
|
3133
3133
|
const shouldSkipFile = () => isBrowserTestFile || hasTestLibraryImport;
|
|
@@ -3330,7 +3330,7 @@ const buttonHasType = defineRule({
|
|
|
3330
3330
|
recommendation: "Set `type=\"button\"` (or `\"submit\"` / `\"reset\"`) explicitly on every `<button>`.",
|
|
3331
3331
|
create: (context) => {
|
|
3332
3332
|
const settings = resolveSettings$48(context.settings);
|
|
3333
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
3333
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
3334
3334
|
return {
|
|
3335
3335
|
JSXOpeningElement(node) {
|
|
3336
3336
|
if (isTestlikeFile) return;
|
|
@@ -3530,7 +3530,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
3530
3530
|
recommendation: "Pair `onClick` with `onKeyUp` / `onKeyDown` / `onKeyPress` for keyboard users.",
|
|
3531
3531
|
category: "Accessibility",
|
|
3532
3532
|
create: (context) => {
|
|
3533
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
3533
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
3534
3534
|
return { JSXOpeningElement(node) {
|
|
3535
3535
|
if (isTestlikeFile) return;
|
|
3536
3536
|
const tag = getElementType(node, context.settings);
|
|
@@ -3791,7 +3791,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
3791
3791
|
category: "Accessibility",
|
|
3792
3792
|
create: (context) => {
|
|
3793
3793
|
const settings = resolveSettings$46(context.settings);
|
|
3794
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
3794
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
3795
3795
|
return { JSXElement(node) {
|
|
3796
3796
|
if (isTestlikeFile) return;
|
|
3797
3797
|
const opening = node.openingElement;
|
|
@@ -8423,6 +8423,7 @@ const jsTosortedImmutable = defineRule({
|
|
|
8423
8423
|
id: "js-tosorted-immutable",
|
|
8424
8424
|
tags: ["test-noise"],
|
|
8425
8425
|
severity: "warn",
|
|
8426
|
+
disabledBy: ["react-native"],
|
|
8426
8427
|
recommendation: "Use `array.toSorted()` (ES2023) instead of `[...array].sort()` for immutable sorting without the spread allocation",
|
|
8427
8428
|
create: (context) => ({ CallExpression(node) {
|
|
8428
8429
|
if (!isMemberProperty(node.callee, "sort")) return;
|
|
@@ -8711,7 +8712,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
8711
8712
|
const settings = resolveSettings$34(context.settings);
|
|
8712
8713
|
const allowedExtensions = normalizeExtensions(settings.extensions);
|
|
8713
8714
|
const allowedList = [...allowedExtensions].map((extension) => `.${extension}`).join(", ");
|
|
8714
|
-
const filename =
|
|
8715
|
+
const filename = normalizeFilename$1(context.filename ?? "fixture.tsx");
|
|
8715
8716
|
const extensionOnly = path.extname(filename).slice(1);
|
|
8716
8717
|
const fileHasAllowedExtension = allowedExtensions.has(extensionOnly);
|
|
8717
8718
|
let didReportMismatch = false;
|
|
@@ -9495,7 +9496,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
9495
9496
|
recommendation: "Memoize the context value (`useMemo`) or hoist it outside the render.",
|
|
9496
9497
|
category: "Performance",
|
|
9497
9498
|
create: (context) => {
|
|
9498
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
9499
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
9499
9500
|
return { JSXOpeningElement(node) {
|
|
9500
9501
|
if (isTestlikeFile) return;
|
|
9501
9502
|
if (!isProviderName(node.name)) return;
|
|
@@ -9841,7 +9842,7 @@ const jsxNoJsxAsProp = defineRule({
|
|
|
9841
9842
|
recommendation: "Hoist the inner JSX outside the render or memoize via `useMemo`.",
|
|
9842
9843
|
category: "Performance",
|
|
9843
9844
|
create: (context) => {
|
|
9844
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
9845
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
9845
9846
|
let memoRegistry = null;
|
|
9846
9847
|
return {
|
|
9847
9848
|
Program(node) {
|
|
@@ -10212,7 +10213,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
10212
10213
|
recommendation: "Memoize the array (`useMemo`) or hoist it outside the component.",
|
|
10213
10214
|
category: "Performance",
|
|
10214
10215
|
create: (context) => {
|
|
10215
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
10216
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
10216
10217
|
let memoRegistry = null;
|
|
10217
10218
|
return {
|
|
10218
10219
|
Program(node) {
|
|
@@ -10674,7 +10675,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
10674
10675
|
recommendation: "Memoize the callback (`useCallback`) or hoist it outside the component.",
|
|
10675
10676
|
category: "Performance",
|
|
10676
10677
|
create: (context) => {
|
|
10677
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
10678
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
10678
10679
|
let memoRegistry = null;
|
|
10679
10680
|
return {
|
|
10680
10681
|
Program(node) {
|
|
@@ -10980,7 +10981,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
10980
10981
|
recommendation: "Memoize the object (`useMemo`) or hoist it outside the component.",
|
|
10981
10982
|
category: "Performance",
|
|
10982
10983
|
create: (context) => {
|
|
10983
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
10984
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
10984
10985
|
let memoRegistry = null;
|
|
10985
10986
|
return {
|
|
10986
10987
|
Program(node) {
|
|
@@ -11785,7 +11786,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
11785
11786
|
category: "Accessibility",
|
|
11786
11787
|
create: (context) => {
|
|
11787
11788
|
const settings = resolveSettings$24(context.settings);
|
|
11788
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
11789
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
11789
11790
|
return { JSXElement(node) {
|
|
11790
11791
|
if (isTestlikeFile) return;
|
|
11791
11792
|
const opening = node.openingElement;
|
|
@@ -12323,7 +12324,7 @@ const nextjsMissingMetadata = defineRule({
|
|
|
12323
12324
|
severity: "warn",
|
|
12324
12325
|
recommendation: "Add `export const metadata = { title: '...', description: '...' }` or `export async function generateMetadata()`",
|
|
12325
12326
|
create: (context) => ({ Program(programNode) {
|
|
12326
|
-
const filename = normalizeFilename$1(context.
|
|
12327
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
12327
12328
|
if (!PAGE_FILE_PATTERN.test(filename)) return;
|
|
12328
12329
|
if (INTERNAL_PAGE_PATH_PATTERN.test(filename)) return;
|
|
12329
12330
|
if (!programNode.body?.some((statement) => {
|
|
@@ -12388,7 +12389,7 @@ const nextjsNoClientFetchForServerData = defineRule({
|
|
|
12388
12389
|
if (!fileHasUseClient || !isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
|
|
12389
12390
|
const callback = getEffectCallback(node);
|
|
12390
12391
|
if (!callback || !containsFetchCall(callback)) return;
|
|
12391
|
-
const filename = normalizeFilename$1(context.
|
|
12392
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
12392
12393
|
if (PAGE_OR_LAYOUT_FILE_PATTERN.test(filename) || PAGES_DIRECTORY_PATTERN.test(filename)) context.report({
|
|
12393
12394
|
node,
|
|
12394
12395
|
message: "useEffect + fetch in a page/layout — fetch data server-side with a server component instead"
|
|
@@ -12421,7 +12422,7 @@ const nextjsNoClientSideRedirect = defineRule({
|
|
|
12421
12422
|
severity: "warn",
|
|
12422
12423
|
recommendation: "Avoid redirects inside useEffect. Use an event handler, middleware, or server-side redirect (App Router: redirect() from next/navigation; Pages Router: getServerSideProps redirect)",
|
|
12423
12424
|
create: (context) => {
|
|
12424
|
-
const filename = normalizeFilename$1(context.
|
|
12425
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
12425
12426
|
const isPagesRouterFile = PAGES_DIRECTORY_PATTERN.test(filename);
|
|
12426
12427
|
return { CallExpression(node) {
|
|
12427
12428
|
if (!isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
|
|
@@ -12490,7 +12491,7 @@ const nextjsNoHeadImport = defineRule({
|
|
|
12490
12491
|
recommendation: "Use the Metadata API instead: `export const metadata = { title: '...' }` or `export async function generateMetadata()`",
|
|
12491
12492
|
create: (context) => ({ ImportDeclaration(node) {
|
|
12492
12493
|
if (node.source?.value !== "next/head") return;
|
|
12493
|
-
const filename = normalizeFilename$1(context.
|
|
12494
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
12494
12495
|
if (!APP_DIRECTORY_PATTERN.test(filename)) return;
|
|
12495
12496
|
context.report({
|
|
12496
12497
|
node,
|
|
@@ -12507,7 +12508,7 @@ const nextjsNoImgElement = defineRule({
|
|
|
12507
12508
|
severity: "warn",
|
|
12508
12509
|
recommendation: "`import Image from 'next/image'` — provides automatic WebP/AVIF, lazy loading, and responsive srcset",
|
|
12509
12510
|
create: (context) => {
|
|
12510
|
-
const filename = normalizeFilename$1(context.
|
|
12511
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
12511
12512
|
const isOgRoute = OG_ROUTE_PATTERN.test(filename);
|
|
12512
12513
|
return { JSXOpeningElement(node) {
|
|
12513
12514
|
if (isOgRoute) return;
|
|
@@ -12861,7 +12862,7 @@ const nextjsNoSideEffectInGetHandler = defineRule({
|
|
|
12861
12862
|
resolveBinding = buildProgramBindingLookup(node);
|
|
12862
12863
|
},
|
|
12863
12864
|
ExportNamedDeclaration(node) {
|
|
12864
|
-
const filename = normalizeFilename$1(context.
|
|
12865
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
12865
12866
|
if (!ROUTE_HANDLER_FILE_PATTERN.test(filename)) return;
|
|
12866
12867
|
if (CRON_ROUTE_PATTERN.test(filename)) return;
|
|
12867
12868
|
if (!isExportedGetHandler(node)) return;
|
|
@@ -14093,7 +14094,7 @@ const noAutofocus = defineRule({
|
|
|
14093
14094
|
category: "Accessibility",
|
|
14094
14095
|
create: (context) => {
|
|
14095
14096
|
const settings = resolveSettings$21(context.settings);
|
|
14096
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
14097
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
14097
14098
|
return { JSXOpeningElement(node) {
|
|
14098
14099
|
if (isTestlikeFile) return;
|
|
14099
14100
|
const autoFocusAttribute = node.attributes.find((attribute) => {
|
|
@@ -14467,7 +14468,7 @@ const noBarrelImport = defineRule({
|
|
|
14467
14468
|
if (didReportForFile) return;
|
|
14468
14469
|
const source = node.source?.value;
|
|
14469
14470
|
if (typeof source !== "string" || !source.startsWith(".")) return;
|
|
14470
|
-
const filename = normalizeFilename$1(context.
|
|
14471
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
14471
14472
|
if (!filename) return;
|
|
14472
14473
|
const importRequests = getRuntimeImportRequests(node);
|
|
14473
14474
|
if (importRequests.length === 0) return;
|
|
@@ -18246,7 +18247,7 @@ const noMultiComp = defineRule({
|
|
|
18246
18247
|
category: "Architecture",
|
|
18247
18248
|
create: (context) => {
|
|
18248
18249
|
const settings = resolveSettings$16(context.settings);
|
|
18249
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
18250
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
18250
18251
|
return { Program(node) {
|
|
18251
18252
|
if (isTestlikeFile) return;
|
|
18252
18253
|
const visitContext = {
|
|
@@ -20193,7 +20194,7 @@ const noSecretsInClientCode = defineRule({
|
|
|
20193
20194
|
severity: "warn",
|
|
20194
20195
|
recommendation: "Move secrets to server-only code. Public client environment variables are bundled into browser code and must not contain secrets",
|
|
20195
20196
|
create: (context) => {
|
|
20196
|
-
const filename = normalizeFilename$1(context.
|
|
20197
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
20197
20198
|
const framework = getReactDoctorStringSetting(context.settings, "framework");
|
|
20198
20199
|
const rootDirectory = getReactDoctorStringSetting(context.settings, "rootDirectory");
|
|
20199
20200
|
let shouldUseVariableNameHeuristic = classifySecretFileExposure(filename, {
|
|
@@ -20448,7 +20449,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
20448
20449
|
category: "Accessibility",
|
|
20449
20450
|
create: (context) => {
|
|
20450
20451
|
const settings = resolveSettings$12(context.settings);
|
|
20451
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
20452
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
20452
20453
|
return { JSXOpeningElement(node) {
|
|
20453
20454
|
if (isTestlikeFile) return;
|
|
20454
20455
|
let hasNonBlockerHandler = false;
|
|
@@ -20525,7 +20526,7 @@ const noStringRefs = defineRule({
|
|
|
20525
20526
|
recommendation: "Use a callback ref (`ref={(node) => { this.foo = node }}`) or `useRef` instead of string refs.",
|
|
20526
20527
|
create: (context) => {
|
|
20527
20528
|
const { noTemplateLiterals = false } = resolveSettings$11(context.settings);
|
|
20528
|
-
const isTestlikeFile = isTestlikeFilename(context.
|
|
20529
|
+
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
20529
20530
|
return {
|
|
20530
20531
|
JSXAttribute(node) {
|
|
20531
20532
|
if (isTestlikeFile) return;
|
|
@@ -22671,7 +22672,7 @@ const onlyExportComponents = defineRule({
|
|
|
22671
22672
|
allowConstantExport: settings.allowConstantExport
|
|
22672
22673
|
};
|
|
22673
22674
|
return { Program(node) {
|
|
22674
|
-
if (!isFileNameAllowed(
|
|
22675
|
+
if (!isFileNameAllowed(normalizeFilename$1(context.filename ?? ""), settings.checkJS)) return;
|
|
22675
22676
|
const allNodes = collectAllNodes(node);
|
|
22676
22677
|
const exports = [];
|
|
22677
22678
|
let hasReactExport = false;
|
|
@@ -24192,7 +24193,7 @@ const renderingSvgPrecision = defineRule({
|
|
|
24192
24193
|
category: "Performance",
|
|
24193
24194
|
recommendation: "Truncate path/points/transform decimals to 1–2 digits — sub-pixel precision adds bytes with no visible difference",
|
|
24194
24195
|
create: (context) => {
|
|
24195
|
-
const filename = context.
|
|
24196
|
+
const filename = context.filename;
|
|
24196
24197
|
const isAutoGenerated = isAutoGeneratedSvgFile(filename ? normalizeFilename$1(filename) : void 0);
|
|
24197
24198
|
return { JSXAttribute(node) {
|
|
24198
24199
|
if (isAutoGenerated) return;
|
|
@@ -25524,7 +25525,8 @@ const classifyPackagePlatform = (filename) => {
|
|
|
25524
25525
|
//#endregion
|
|
25525
25526
|
//#region src/plugin/utils/is-expo-managed-file.ts
|
|
25526
25527
|
const isExpoManagedFileActive = (context) => {
|
|
25527
|
-
const
|
|
25528
|
+
const rawFilename = context.filename;
|
|
25529
|
+
const filename = rawFilename ? normalizeFilename$1(rawFilename) : void 0;
|
|
25528
25530
|
if (filename) {
|
|
25529
25531
|
const packagePlatform = classifyPackagePlatform(filename);
|
|
25530
25532
|
if (packagePlatform === "expo") return true;
|
|
@@ -30692,7 +30694,7 @@ const serverFetchWithoutRevalidate = defineRule({
|
|
|
30692
30694
|
let isServerSideFile = false;
|
|
30693
30695
|
return {
|
|
30694
30696
|
Program(node) {
|
|
30695
|
-
const filename = normalizeFilename$1(context.
|
|
30697
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
30696
30698
|
if (!APP_ROUTER_FILE_PATTERN.test(filename)) {
|
|
30697
30699
|
isServerSideFile = false;
|
|
30698
30700
|
return;
|
|
@@ -30805,7 +30807,7 @@ const serverHoistStaticIo = defineRule({
|
|
|
30805
30807
|
inspectHandlerBody(context, declaration.body, `${handlerName} route handler`, collectIdentifierParams(declaration.params ?? []));
|
|
30806
30808
|
},
|
|
30807
30809
|
ExportDefaultDeclaration(node) {
|
|
30808
|
-
const filename = normalizeFilename$1(context.
|
|
30810
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
30809
30811
|
if (!PAGES_ROUTER_API_PATH_PATTERN.test(filename)) return;
|
|
30810
30812
|
const declaration = node.declaration;
|
|
30811
30813
|
if (!declaration || !isNodeOfType(declaration, "FunctionDeclaration") && !isNodeOfType(declaration, "FunctionExpression") && !isNodeOfType(declaration, "ArrowFunctionExpression")) return;
|
|
@@ -31356,7 +31358,7 @@ const tanstackStartMissingHeadContent = defineRule({
|
|
|
31356
31358
|
};
|
|
31357
31359
|
return {
|
|
31358
31360
|
Program(node) {
|
|
31359
|
-
const filename = context.
|
|
31361
|
+
const filename = context.filename ?? "";
|
|
31360
31362
|
if (!TANSTACK_ROOT_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
31361
31363
|
const statements = node.body ?? [];
|
|
31362
31364
|
for (const statement of statements) collectImportBindings(statement);
|
|
@@ -31366,17 +31368,17 @@ const tanstackStartMissingHeadContent = defineRule({
|
|
|
31366
31368
|
}
|
|
31367
31369
|
},
|
|
31368
31370
|
ImportDeclaration(node) {
|
|
31369
|
-
const filename = context.
|
|
31371
|
+
const filename = context.filename ?? "";
|
|
31370
31372
|
if (!TANSTACK_ROOT_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
31371
31373
|
collectImportBindings(node);
|
|
31372
31374
|
},
|
|
31373
31375
|
VariableDeclarator(node) {
|
|
31374
|
-
const filename = context.
|
|
31376
|
+
const filename = context.filename ?? "";
|
|
31375
31377
|
if (!TANSTACK_ROOT_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
31376
31378
|
collectVariableAlias(node);
|
|
31377
31379
|
},
|
|
31378
31380
|
JSXOpeningElement(node) {
|
|
31379
|
-
const filename = normalizeFilename$1(context.
|
|
31381
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
31380
31382
|
if (!TANSTACK_ROOT_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
31381
31383
|
if (isNodeOfType(node.name, "JSXIdentifier")) {
|
|
31382
31384
|
if (node.name.name === DOCUMENT_HEAD_ELEMENT_NAME) hasDocumentHeadElement = true;
|
|
@@ -31391,7 +31393,7 @@ const tanstackStartMissingHeadContent = defineRule({
|
|
|
31391
31393
|
if (isInsideDocumentHeadElement(node) && isCustomJsxElementName(node.name)) hasCustomHeadChildElement = true;
|
|
31392
31394
|
},
|
|
31393
31395
|
"Program:exit"(programNode) {
|
|
31394
|
-
const filename = normalizeFilename$1(context.
|
|
31396
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
31395
31397
|
if (!TANSTACK_ROOT_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
31396
31398
|
if (hasDocumentHeadElement && !hasHeadContentElement && !hasCustomHeadChildElement) context.report({
|
|
31397
31399
|
node: programNode,
|
|
@@ -31410,7 +31412,7 @@ const tanstackStartNoAnchorElement = defineRule({
|
|
|
31410
31412
|
severity: "warn",
|
|
31411
31413
|
recommendation: "`import { Link } from '@tanstack/react-router'` — enables type-safe routes, preloading via `preload=\"intent\"`, and client-side navigation",
|
|
31412
31414
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
31413
|
-
const filename = normalizeFilename$1(context.
|
|
31415
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
31414
31416
|
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
31415
31417
|
if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
|
|
31416
31418
|
const hrefAttribute = (node.attributes ?? []).find((attribute) => isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === "href");
|
|
@@ -31484,7 +31486,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
31484
31486
|
const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && typeof node.name.name === "string" && node.name.name.startsWith("on") && UPPERCASE_PATTERN.test(node.name.name.charAt(2));
|
|
31485
31487
|
return {
|
|
31486
31488
|
CallExpression(node) {
|
|
31487
|
-
const filename = normalizeFilename$1(context.
|
|
31489
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
31488
31490
|
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
31489
31491
|
if (isDeferredHookCall(node)) deferredCallbackDepth++;
|
|
31490
31492
|
if (deferredCallbackDepth > 0 || eventHandlerDepth > 0) return;
|
|
@@ -31494,17 +31496,17 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
31494
31496
|
});
|
|
31495
31497
|
},
|
|
31496
31498
|
"CallExpression:exit"(node) {
|
|
31497
|
-
const filename = normalizeFilename$1(context.
|
|
31499
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
31498
31500
|
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
31499
31501
|
if (isDeferredHookCall(node)) deferredCallbackDepth = Math.max(0, deferredCallbackDepth - 1);
|
|
31500
31502
|
},
|
|
31501
31503
|
JSXAttribute(node) {
|
|
31502
|
-
const filename = normalizeFilename$1(context.
|
|
31504
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
31503
31505
|
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
31504
31506
|
if (isEventHandlerAttribute(node)) eventHandlerDepth++;
|
|
31505
31507
|
},
|
|
31506
31508
|
"JSXAttribute:exit"(node) {
|
|
31507
|
-
const filename = normalizeFilename$1(context.
|
|
31509
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
31508
31510
|
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
31509
31511
|
if (isEventHandlerAttribute(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
|
|
31510
31512
|
}
|
|
@@ -31585,7 +31587,7 @@ const tanstackStartNoUseEffectFetch = defineRule({
|
|
|
31585
31587
|
severity: "warn",
|
|
31586
31588
|
recommendation: "Fetch data in the route `loader` instead — the router coordinates loading before rendering to avoid waterfalls",
|
|
31587
31589
|
create: (context) => ({ CallExpression(node) {
|
|
31588
|
-
const filename = normalizeFilename$1(context.
|
|
31590
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
31589
31591
|
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
31590
31592
|
if (!isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
|
|
31591
31593
|
const callback = node.arguments?.[0];
|
|
@@ -35254,7 +35256,7 @@ const ruleRegistry = Object.fromEntries(reactDoctorRules.map((rule) => [rule.id,
|
|
|
35254
35256
|
const WEB_FILE_EXTENSION_PATTERN = /\.web\.[cm]?[jt]sx?$/;
|
|
35255
35257
|
const NATIVE_FILE_EXTENSION_PATTERN = /\.(?:ios|android|native)\.[cm]?[jt]sx?$/;
|
|
35256
35258
|
const isReactNativeFileActive = (context) => {
|
|
35257
|
-
const rawFilename = context.
|
|
35259
|
+
const rawFilename = context.filename;
|
|
35258
35260
|
if (!rawFilename) return true;
|
|
35259
35261
|
const filename = normalizeFilename$1(rawFilename);
|
|
35260
35262
|
if (NATIVE_FILE_EXTENSION_PATTERN.test(filename)) return true;
|
|
@@ -35742,7 +35744,9 @@ const wrapWithSemanticContext = (rule) => ({
|
|
|
35742
35744
|
};
|
|
35743
35745
|
const enrichedContext = {
|
|
35744
35746
|
report: baseContext.report,
|
|
35745
|
-
|
|
35747
|
+
get filename() {
|
|
35748
|
+
return baseContext.filename ?? baseContext.getFilename?.();
|
|
35749
|
+
},
|
|
35746
35750
|
settings: baseContext.settings,
|
|
35747
35751
|
get scopes() {
|
|
35748
35752
|
return getScopes();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxlint-plugin-react-doctor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "oxlint plugin for React Doctor: diagnose React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|