oxlint-plugin-react-doctor 0.6.0-dev.113637e → 0.6.0-dev.5f60bef
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.js +123 -99
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1573,7 +1573,6 @@ const LAYOUT_FILE_NAMES = [
|
|
|
1573
1573
|
];
|
|
1574
1574
|
const METADATA_EXPORT_NAMES = ["metadata", "generateMetadata"];
|
|
1575
1575
|
const INTERNAL_PAGE_PATH_PATTERN = /\/(?:(?:\((?:dashboard|admin|settings|account|internal|manage|console|portal|auth|onboarding|app|ee|protected)\))|(?:dashboard|admin|settings|account|internal|manage|console|portal))\//i;
|
|
1576
|
-
const PAGES_DIRECTORY_PATTERN = /\/pages\//;
|
|
1577
1576
|
const NEXTJS_NAVIGATION_FUNCTIONS = new Set([
|
|
1578
1577
|
"redirect",
|
|
1579
1578
|
"permanentRedirect",
|
|
@@ -1591,7 +1590,6 @@ const CACHE_REVALIDATION_FUNCTION_NAMES = new Set([
|
|
|
1591
1590
|
]);
|
|
1592
1591
|
const GOOGLE_FONTS_PATTERN = /fonts\.googleapis\.com/;
|
|
1593
1592
|
const POLYFILL_SCRIPT_PATTERN = /polyfill\.io|polyfill\.min\.js|cdn\.polyfill/;
|
|
1594
|
-
const APP_DIRECTORY_PATTERN = /\/app\//;
|
|
1595
1593
|
const ROUTE_HANDLER_FILE_PATTERN = new RegExp(`/route\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
1596
1594
|
const CRON_ROUTE_PATTERN = /\/(?:cron|jobs\/cron)(?:\/|$)/i;
|
|
1597
1595
|
const MUTATING_ROUTE_SEGMENTS = new Set([
|
|
@@ -1628,7 +1626,7 @@ const isNextjsMetadataImageRouteFilename = (rawFilename) => {
|
|
|
1628
1626
|
};
|
|
1629
1627
|
//#endregion
|
|
1630
1628
|
//#region src/plugin/utils/normalize-filename.ts
|
|
1631
|
-
const normalizeFilename
|
|
1629
|
+
const normalizeFilename = (filename) => filename.replaceAll("\\", "/");
|
|
1632
1630
|
//#endregion
|
|
1633
1631
|
//#region src/plugin/utils/is-generated-image-render-context.ts
|
|
1634
1632
|
const IMAGE_RESPONSE_MODULES = ["next/og", "@vercel/og"];
|
|
@@ -1636,7 +1634,7 @@ const SATORI_MODULE = "satori";
|
|
|
1636
1634
|
const generatedImageJsxCache = /* @__PURE__ */ new WeakMap();
|
|
1637
1635
|
const isGeneratedImageRenderFilename = (rawFilename) => {
|
|
1638
1636
|
if (!rawFilename) return false;
|
|
1639
|
-
return isNextjsMetadataImageRouteFilename(normalizeFilename
|
|
1637
|
+
return isNextjsMetadataImageRouteFilename(normalizeFilename(rawFilename));
|
|
1640
1638
|
};
|
|
1641
1639
|
const isImageResponseCallee = (contextNode, callee) => {
|
|
1642
1640
|
if (isNodeOfType(callee, "Identifier")) return IMAGE_RESPONSE_MODULES.some((moduleSource) => getImportedNameFromModule(contextNode, callee.name, moduleSource) === "ImageResponse");
|
|
@@ -4598,7 +4596,7 @@ const asyncParallel = defineRule({
|
|
|
4598
4596
|
severity: "warn",
|
|
4599
4597
|
recommendation: "Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` so independent calls run at the same time",
|
|
4600
4598
|
create: (context) => {
|
|
4601
|
-
const filename = normalizeFilename
|
|
4599
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
4602
4600
|
const isBrowserTestFile = BROWSER_TEST_FILE_PATTERN.test(filename);
|
|
4603
4601
|
let hasTestLibraryImport = false;
|
|
4604
4602
|
const shouldSkipFile = () => isBrowserTestFile || hasTestLibraryImport;
|
|
@@ -8182,7 +8180,7 @@ const expoNoNonInlinedEnv = defineRule({
|
|
|
8182
8180
|
severity: "warn",
|
|
8183
8181
|
recommendation: "Read env vars with static dotted access (`process.env.EXPO_PUBLIC_NAME`). Computed access and destructuring aren't inlined by babel-preset-expo and resolve to `undefined` at runtime.",
|
|
8184
8182
|
create: (context) => {
|
|
8185
|
-
const filename = normalizeFilename
|
|
8183
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
8186
8184
|
if (filename && NODE_OR_BUILD_FILE.test(filename)) return EMPTY_VISITORS$3;
|
|
8187
8185
|
return {
|
|
8188
8186
|
MemberExpression(node) {
|
|
@@ -11617,7 +11615,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
11617
11615
|
create: (context) => {
|
|
11618
11616
|
const settings = resolveSettings$33(context.settings);
|
|
11619
11617
|
const allowedExtensions = normalizeExtensions(settings.extensions);
|
|
11620
|
-
const filename = normalizeFilename
|
|
11618
|
+
const filename = normalizeFilename(context.filename ?? "fixture.tsx");
|
|
11621
11619
|
const extensionOnly = path.extname(filename).slice(1);
|
|
11622
11620
|
const fileHasAllowedExtension = allowedExtensions.has(extensionOnly);
|
|
11623
11621
|
let didReportMismatch = false;
|
|
@@ -15257,6 +15255,52 @@ const nextjsAsyncClientComponent = defineRule({
|
|
|
15257
15255
|
}
|
|
15258
15256
|
});
|
|
15259
15257
|
//#endregion
|
|
15258
|
+
//#region src/plugin/utils/get-project-relative-filename.ts
|
|
15259
|
+
const getProjectRelativeFilename = (filename, rootDirectory) => {
|
|
15260
|
+
const normalizedFilename = normalizeFilename(filename);
|
|
15261
|
+
if (!rootDirectory) return normalizedFilename;
|
|
15262
|
+
const rootDirectoryPrefix = `${normalizeFilename(rootDirectory).replace(/\/+$/, "")}/`;
|
|
15263
|
+
if (!normalizedFilename.startsWith(rootDirectoryPrefix)) return normalizedFilename;
|
|
15264
|
+
return normalizedFilename.slice(rootDirectoryPrefix.length);
|
|
15265
|
+
};
|
|
15266
|
+
//#endregion
|
|
15267
|
+
//#region src/plugin/utils/get-react-doctor-setting.ts
|
|
15268
|
+
const readReactDoctorSettingsBag = (settings) => {
|
|
15269
|
+
const reactDoctorSettings = settings?.["react-doctor"];
|
|
15270
|
+
if (typeof reactDoctorSettings !== "object" || reactDoctorSettings === null || Array.isArray(reactDoctorSettings)) return null;
|
|
15271
|
+
return reactDoctorSettings;
|
|
15272
|
+
};
|
|
15273
|
+
const readOwnPropertyValue = (bag, settingName) => Object.getOwnPropertyDescriptor(bag, settingName)?.value;
|
|
15274
|
+
const getReactDoctorStringSetting = (settings, settingName) => {
|
|
15275
|
+
const bag = readReactDoctorSettingsBag(settings);
|
|
15276
|
+
if (!bag) return void 0;
|
|
15277
|
+
const settingValue = readOwnPropertyValue(bag, settingName);
|
|
15278
|
+
return typeof settingValue === "string" ? settingValue : void 0;
|
|
15279
|
+
};
|
|
15280
|
+
const getReactDoctorNumberSetting = (settings, settingName) => {
|
|
15281
|
+
const bag = readReactDoctorSettingsBag(settings);
|
|
15282
|
+
if (!bag) return void 0;
|
|
15283
|
+
const settingValue = readOwnPropertyValue(bag, settingName);
|
|
15284
|
+
return typeof settingValue === "number" && Number.isFinite(settingValue) ? settingValue : void 0;
|
|
15285
|
+
};
|
|
15286
|
+
const getReactDoctorStringArraySetting = (settings, settingName) => {
|
|
15287
|
+
const bag = readReactDoctorSettingsBag(settings);
|
|
15288
|
+
if (!bag) return [];
|
|
15289
|
+
const settingValue = readOwnPropertyValue(bag, settingName);
|
|
15290
|
+
if (!Array.isArray(settingValue)) return [];
|
|
15291
|
+
return settingValue.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
15292
|
+
};
|
|
15293
|
+
//#endregion
|
|
15294
|
+
//#region src/plugin/utils/is-in-project-directory.ts
|
|
15295
|
+
const isInProjectDirectory = (context, directoryPath) => {
|
|
15296
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
15297
|
+
if (filename.length === 0) return false;
|
|
15298
|
+
const directorySegment = `/${directoryPath}/`;
|
|
15299
|
+
const relativeFilename = getProjectRelativeFilename(filename, getReactDoctorStringSetting(context.settings, "rootDirectory"));
|
|
15300
|
+
if (relativeFilename !== filename) return relativeFilename.startsWith(`${directoryPath}/`) || relativeFilename.includes(directorySegment);
|
|
15301
|
+
return filename.indexOf(directorySegment, 1) !== -1;
|
|
15302
|
+
};
|
|
15303
|
+
//#endregion
|
|
15260
15304
|
//#region src/plugin/rules/nextjs/nextjs-error-boundary-missing-use-client.ts
|
|
15261
15305
|
const nextjsErrorBoundaryMissingUseClient = defineRule({
|
|
15262
15306
|
id: "nextjs-error-boundary-missing-use-client",
|
|
@@ -15266,8 +15310,8 @@ const nextjsErrorBoundaryMissingUseClient = defineRule({
|
|
|
15266
15310
|
severity: "error",
|
|
15267
15311
|
recommendation: "Add `'use client'` at the top of this file. Error boundaries must be Client Components to catch and render fallback UI",
|
|
15268
15312
|
create: (context) => ({ Program(programNode) {
|
|
15269
|
-
const filename = normalizeFilename
|
|
15270
|
-
if (!
|
|
15313
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
15314
|
+
if (!isInProjectDirectory(context, "app")) return;
|
|
15271
15315
|
if (!ERROR_BOUNDARY_FILE_PATTERN.test(filename)) return;
|
|
15272
15316
|
if (hasDirective(programNode, "use client")) return;
|
|
15273
15317
|
context.report({
|
|
@@ -15298,8 +15342,8 @@ const nextjsGlobalErrorMissingHtmlBody = defineRule({
|
|
|
15298
15342
|
severity: "error",
|
|
15299
15343
|
recommendation: "Wrap your error UI in `<html><body>...</body></html>`. The root layout is unmounted when global-error renders",
|
|
15300
15344
|
create: (context) => ({ Program(programNode) {
|
|
15301
|
-
const filename = normalizeFilename
|
|
15302
|
-
if (!
|
|
15345
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
15346
|
+
if (!isInProjectDirectory(context, "app")) return;
|
|
15303
15347
|
if (!GLOBAL_ERROR_FILE_PATTERN.test(filename)) return;
|
|
15304
15348
|
const foundTags = fileContainsJsxElements(programNode, REQUIRED_HTML_TAGS);
|
|
15305
15349
|
const missingTags = REQUIRED_HTML_TAGS.filter((tagName) => !foundTags.has(tagName)).map((tagName) => `<${tagName}>`);
|
|
@@ -15430,7 +15474,7 @@ const nextjsMissingMetadata = defineRule({
|
|
|
15430
15474
|
severity: "warn",
|
|
15431
15475
|
recommendation: "Add metadata or `generateMetadata()` so search engines and social previews get a title and description.",
|
|
15432
15476
|
create: (context) => ({ Program(programNode) {
|
|
15433
|
-
const filename = normalizeFilename
|
|
15477
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
15434
15478
|
if (!PAGE_FILE_PATTERN.test(filename)) return;
|
|
15435
15479
|
if (INTERNAL_PAGE_PATH_PATTERN.test(filename)) return;
|
|
15436
15480
|
if (programNode.body?.some((statement) => {
|
|
@@ -15563,8 +15607,8 @@ const nextjsNoClientFetchForServerData = defineRule({
|
|
|
15563
15607
|
if (!fileHasUseClient || !isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
|
|
15564
15608
|
const callback = getEffectCallback(node);
|
|
15565
15609
|
if (!callback || !containsFetchCall(callback, { stopAtFunctionBoundary: true })) return;
|
|
15566
|
-
const filename = normalizeFilename
|
|
15567
|
-
if (PAGE_OR_LAYOUT_FILE_PATTERN.test(filename) ||
|
|
15610
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
15611
|
+
if (PAGE_OR_LAYOUT_FILE_PATTERN.test(filename) || isInProjectDirectory(context, "pages")) context.report({
|
|
15568
15612
|
node,
|
|
15569
15613
|
message: "useEffect + fetch in a page/layout makes your users wait through an extra round trip & loading spinner."
|
|
15570
15614
|
});
|
|
@@ -15663,8 +15707,8 @@ const nextjsNoDefaultExportInRouteHandler = defineRule({
|
|
|
15663
15707
|
let programNode = null;
|
|
15664
15708
|
return {
|
|
15665
15709
|
Program(node) {
|
|
15666
|
-
const filename = normalizeFilename
|
|
15667
|
-
isAppRouteHandler =
|
|
15710
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
15711
|
+
isAppRouteHandler = isInProjectDirectory(context, "app") && ROUTE_HANDLER_FILE_PATTERN.test(filename);
|
|
15668
15712
|
programNode = node;
|
|
15669
15713
|
},
|
|
15670
15714
|
ExportDefaultDeclaration(node) {
|
|
@@ -15700,7 +15744,7 @@ const nextjsNoEdgeOgRuntime = defineRule({
|
|
|
15700
15744
|
let isOgImageFile = false;
|
|
15701
15745
|
return {
|
|
15702
15746
|
Program() {
|
|
15703
|
-
const filename = normalizeFilename
|
|
15747
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
15704
15748
|
isOgImageFile = OG_IMAGE_FILE_PATTERN.test(filename);
|
|
15705
15749
|
},
|
|
15706
15750
|
ExportNamedDeclaration(node) {
|
|
@@ -15776,8 +15820,7 @@ const nextjsNoHeadImport = defineRule({
|
|
|
15776
15820
|
recommendation: "Use the Metadata API because `next/head` is ignored in the App Router and meta tags will not render.",
|
|
15777
15821
|
create: (context) => ({ ImportDeclaration(node) {
|
|
15778
15822
|
if (node.source?.value !== "next/head") return;
|
|
15779
|
-
|
|
15780
|
-
if (!APP_DIRECTORY_PATTERN.test(filename)) return;
|
|
15823
|
+
if (!isInProjectDirectory(context, "app")) return;
|
|
15781
15824
|
context.report({
|
|
15782
15825
|
node,
|
|
15783
15826
|
message: "next/head silently does nothing in the App Router, so your meta tags never render."
|
|
@@ -16235,7 +16278,7 @@ const nextjsNoSideEffectInGetHandler = defineRule({
|
|
|
16235
16278
|
resolveBinding = buildProgramBindingLookup(node);
|
|
16236
16279
|
},
|
|
16237
16280
|
ExportNamedDeclaration(node) {
|
|
16238
|
-
const filename = normalizeFilename
|
|
16281
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
16239
16282
|
if (!ROUTE_HANDLER_FILE_PATTERN.test(filename)) return;
|
|
16240
16283
|
if (CRON_ROUTE_PATTERN.test(filename)) return;
|
|
16241
16284
|
if (!isExportedGetHandler(node)) return;
|
|
@@ -17014,7 +17057,7 @@ const nextjsNoUseSearchParamsWithoutSuspense = defineRule({
|
|
|
17014
17057
|
let suspenseLocalNames = /* @__PURE__ */ new Set();
|
|
17015
17058
|
return {
|
|
17016
17059
|
Program(programNode) {
|
|
17017
|
-
const filename = normalizeFilename
|
|
17060
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
17018
17061
|
isPageOrLayoutFile = PAGE_OR_LAYOUT_FILE_PATTERN.test(filename);
|
|
17019
17062
|
if (!isPageOrLayoutFile) return;
|
|
17020
17063
|
hasAncestorLayoutSuspense = hasAncestorSuspenseLayout(context.filename ?? "");
|
|
@@ -17386,11 +17429,19 @@ const isSynchronous = (node, within) => {
|
|
|
17386
17429
|
if (isNodeOfType(node, "AwaitExpression") || isNodeOfType(node, "UnaryExpression") && node.operator === "void" || isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression")) return false;
|
|
17387
17430
|
return isSynchronous(node.parent, within);
|
|
17388
17431
|
};
|
|
17432
|
+
const unwrapUseCallback = (node) => {
|
|
17433
|
+
if (!node || !isNodeOfType(node, "CallExpression")) return node;
|
|
17434
|
+
const callee = node.callee;
|
|
17435
|
+
return isNodeOfType(callee, "Identifier") && callee.name === "useCallback" || isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useCallback" ? node.arguments?.[0] : node;
|
|
17436
|
+
};
|
|
17389
17437
|
const resolveToFunction = (ref) => {
|
|
17390
17438
|
const definitionNode = ref.resolved?.defs[0]?.node;
|
|
17391
17439
|
if (!definitionNode) return null;
|
|
17392
17440
|
if (isFunctionLike$1(definitionNode)) return definitionNode;
|
|
17393
|
-
if (isNodeOfType(definitionNode, "VariableDeclarator")
|
|
17441
|
+
if (isNodeOfType(definitionNode, "VariableDeclarator")) {
|
|
17442
|
+
const initializer = unwrapUseCallback(definitionNode.init);
|
|
17443
|
+
if (isFunctionLike$1(initializer)) return initializer;
|
|
17444
|
+
}
|
|
17394
17445
|
return null;
|
|
17395
17446
|
};
|
|
17396
17447
|
const resolvesToAsyncFunction = (ref) => Boolean(resolveToFunction(ref)?.async);
|
|
@@ -18658,40 +18709,13 @@ const classifyPackagePlatform = (filename) => {
|
|
|
18658
18709
|
return result;
|
|
18659
18710
|
};
|
|
18660
18711
|
//#endregion
|
|
18661
|
-
//#region src/plugin/utils/get-react-doctor-setting.ts
|
|
18662
|
-
const readReactDoctorSettingsBag = (settings) => {
|
|
18663
|
-
const reactDoctorSettings = settings?.["react-doctor"];
|
|
18664
|
-
if (typeof reactDoctorSettings !== "object" || reactDoctorSettings === null || Array.isArray(reactDoctorSettings)) return null;
|
|
18665
|
-
return reactDoctorSettings;
|
|
18666
|
-
};
|
|
18667
|
-
const readOwnPropertyValue = (bag, settingName) => Object.getOwnPropertyDescriptor(bag, settingName)?.value;
|
|
18668
|
-
const getReactDoctorStringSetting = (settings, settingName) => {
|
|
18669
|
-
const bag = readReactDoctorSettingsBag(settings);
|
|
18670
|
-
if (!bag) return void 0;
|
|
18671
|
-
const settingValue = readOwnPropertyValue(bag, settingName);
|
|
18672
|
-
return typeof settingValue === "string" ? settingValue : void 0;
|
|
18673
|
-
};
|
|
18674
|
-
const getReactDoctorNumberSetting = (settings, settingName) => {
|
|
18675
|
-
const bag = readReactDoctorSettingsBag(settings);
|
|
18676
|
-
if (!bag) return void 0;
|
|
18677
|
-
const settingValue = readOwnPropertyValue(bag, settingName);
|
|
18678
|
-
return typeof settingValue === "number" && Number.isFinite(settingValue) ? settingValue : void 0;
|
|
18679
|
-
};
|
|
18680
|
-
const getReactDoctorStringArraySetting = (settings, settingName) => {
|
|
18681
|
-
const bag = readReactDoctorSettingsBag(settings);
|
|
18682
|
-
if (!bag) return [];
|
|
18683
|
-
const settingValue = readOwnPropertyValue(bag, settingName);
|
|
18684
|
-
if (!Array.isArray(settingValue)) return [];
|
|
18685
|
-
return settingValue.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
18686
|
-
};
|
|
18687
|
-
//#endregion
|
|
18688
18712
|
//#region src/plugin/utils/is-react-native-file.ts
|
|
18689
18713
|
const WEB_FILE_EXTENSION_PATTERN = /\.web\.[cm]?[jt]sx?$/;
|
|
18690
18714
|
const NATIVE_FILE_EXTENSION_PATTERN = /\.(?:ios|android|native)\.[cm]?[jt]sx?$/;
|
|
18691
18715
|
const classifyReactNativeFileTarget = (context) => {
|
|
18692
18716
|
const rawFilename = context.filename;
|
|
18693
18717
|
if (!rawFilename) return "unknown";
|
|
18694
|
-
const filename = normalizeFilename
|
|
18718
|
+
const filename = normalizeFilename(rawFilename);
|
|
18695
18719
|
if (NATIVE_FILE_EXTENSION_PATTERN.test(filename)) return "react-native";
|
|
18696
18720
|
if (WEB_FILE_EXTENSION_PATTERN.test(filename)) return "web";
|
|
18697
18721
|
const packagePlatform = classifyPackagePlatform(filename);
|
|
@@ -18748,7 +18772,7 @@ const noBarrelImport = defineRule({
|
|
|
18748
18772
|
if (didReportForFile) return;
|
|
18749
18773
|
const source = node.source?.value;
|
|
18750
18774
|
if (typeof source !== "string" || !source.startsWith(".")) return;
|
|
18751
|
-
const filename = normalizeFilename
|
|
18775
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
18752
18776
|
if (!filename) return;
|
|
18753
18777
|
const importRequests = getRuntimeImportRequests(node);
|
|
18754
18778
|
if (importRequests.length === 0) return;
|
|
@@ -26219,15 +26243,6 @@ const CLIENT_APP_DIRECTORY_FRAMEWORKS = new Set([
|
|
|
26219
26243
|
"vite"
|
|
26220
26244
|
]);
|
|
26221
26245
|
const isInsideDirectory = (pathSegments, directoryNames) => pathSegments.some((pathSegment) => directoryNames.has(pathSegment));
|
|
26222
|
-
const normalizeFilename = (filename) => filename.replaceAll("\\", "/");
|
|
26223
|
-
const normalizeDirectory = (directory) => normalizeFilename(directory).replace(/\/+$/, "");
|
|
26224
|
-
const getProjectRelativeFilename = (filename, rootDirectory) => {
|
|
26225
|
-
const normalizedFilename = normalizeFilename(filename);
|
|
26226
|
-
if (!rootDirectory) return normalizedFilename;
|
|
26227
|
-
const rootDirectoryPrefix = `${normalizeDirectory(rootDirectory)}/`;
|
|
26228
|
-
if (!normalizedFilename.startsWith(rootDirectoryPrefix)) return normalizedFilename;
|
|
26229
|
-
return normalizedFilename.slice(rootDirectoryPrefix.length);
|
|
26230
|
-
};
|
|
26231
26246
|
const getClassifiablePathSegments = (pathSegments) => {
|
|
26232
26247
|
const srcIndex = pathSegments.lastIndexOf("src");
|
|
26233
26248
|
if (srcIndex === -1) return pathSegments;
|
|
@@ -26289,7 +26304,6 @@ const tokenizeIdentifierWords = (identifierName) => {
|
|
|
26289
26304
|
const getIdentifierTrailingWord = (identifierName) => tokenizeIdentifierWords(identifierName).at(-1) ?? identifierName.toLowerCase();
|
|
26290
26305
|
//#endregion
|
|
26291
26306
|
//#region src/plugin/constants/tanstack.ts
|
|
26292
|
-
const TANSTACK_ROUTE_FILE_PATTERN = /\/routes\//;
|
|
26293
26307
|
const TANSTACK_ROOT_ROUTE_FILE_PATTERN = /__root\.(tsx?|jsx?)$/;
|
|
26294
26308
|
const TANSTACK_ROUTE_PROPERTY_ORDER = [
|
|
26295
26309
|
"params",
|
|
@@ -26401,7 +26415,7 @@ const noSecretsInClientCode = defineRule({
|
|
|
26401
26415
|
severity: "warn",
|
|
26402
26416
|
recommendation: "Move secrets to server-only code. Anything in client env variables gets shipped to the browser, so it can't hold secrets.",
|
|
26403
26417
|
create: (context) => {
|
|
26404
|
-
const filename = normalizeFilename
|
|
26418
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
26405
26419
|
const framework = getReactDoctorStringSetting(context.settings, "framework");
|
|
26406
26420
|
const rootDirectory = getReactDoctorStringSetting(context.settings, "rootDirectory");
|
|
26407
26421
|
let shouldUseVariableNameHeuristic = classifySecretFileExposure(filename, {
|
|
@@ -29567,7 +29581,7 @@ const onlyExportComponents = defineRule({
|
|
|
29567
29581
|
allowConstantExport: settings.allowConstantExport
|
|
29568
29582
|
};
|
|
29569
29583
|
return { Program(node) {
|
|
29570
|
-
if (!isFileNameAllowed(normalizeFilename
|
|
29584
|
+
if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return;
|
|
29571
29585
|
const allNodes = collectAllNodes(node);
|
|
29572
29586
|
const exports = [];
|
|
29573
29587
|
let hasReactExport = false;
|
|
@@ -32001,6 +32015,7 @@ const renderingHydrationMismatchTime = defineRule({
|
|
|
32001
32015
|
});
|
|
32002
32016
|
//#endregion
|
|
32003
32017
|
//#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
|
|
32018
|
+
const USE_EFFECT_ONLY = new Set(["useEffect"]);
|
|
32004
32019
|
const renderingHydrationNoFlicker = defineRule({
|
|
32005
32020
|
id: "rendering-hydration-no-flicker",
|
|
32006
32021
|
title: "useEffect setState flashes on mount",
|
|
@@ -32008,7 +32023,7 @@ const renderingHydrationNoFlicker = defineRule({
|
|
|
32008
32023
|
severity: "warn",
|
|
32009
32024
|
recommendation: "Use `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)` or add `suppressHydrationWarning` to the element",
|
|
32010
32025
|
create: (context) => ({ CallExpression(node) {
|
|
32011
|
-
if (!isHookCall$1(node,
|
|
32026
|
+
if (!isHookCall$1(node, USE_EFFECT_ONLY) || (node.arguments?.length ?? 0) < 2) return;
|
|
32012
32027
|
const depsNode = node.arguments[1];
|
|
32013
32028
|
if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
|
|
32014
32029
|
const callback = getEffectCallback(node);
|
|
@@ -32081,7 +32096,7 @@ const renderingSvgPrecision = defineRule({
|
|
|
32081
32096
|
recommendation: "Round path, points, and transform decimals to 1 or 2 digits. The extra precision adds bytes with no visible difference.",
|
|
32082
32097
|
create: (context) => {
|
|
32083
32098
|
const filename = context.filename;
|
|
32084
|
-
const isAutoGenerated = isAutoGeneratedSvgFile(filename ? normalizeFilename
|
|
32099
|
+
const isAutoGenerated = isAutoGeneratedSvgFile(filename ? normalizeFilename(filename) : void 0);
|
|
32085
32100
|
return { JSXAttribute(node) {
|
|
32086
32101
|
if (isAutoGenerated) return;
|
|
32087
32102
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
@@ -33231,7 +33246,7 @@ const rnDetoxMissingAwait = defineRule({
|
|
|
33231
33246
|
severity: "warn",
|
|
33232
33247
|
recommendation: "Prepend `await` to Detox actions, `waitFor(...)` chains, and `expect(element(...))` assertions. They return promises tied to Detox's synchronization, so a missing await runs steps out of order.",
|
|
33233
33248
|
create: (context) => {
|
|
33234
|
-
const filename = normalizeFilename
|
|
33249
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
33235
33250
|
if (!filename || !DETOX_TEST_FILE.test(filename)) return EMPTY_VISITORS$2;
|
|
33236
33251
|
return { ExpressionStatement(node) {
|
|
33237
33252
|
const expression = node.expression;
|
|
@@ -34961,7 +34976,7 @@ const rnPreferContentInsetAdjustment = defineRetiredRule({
|
|
|
34961
34976
|
//#region src/plugin/utils/is-expo-managed-file.ts
|
|
34962
34977
|
const isExpoManagedFileActive = (context) => {
|
|
34963
34978
|
const rawFilename = context.filename;
|
|
34964
|
-
const filename = rawFilename ? normalizeFilename
|
|
34979
|
+
const filename = rawFilename ? normalizeFilename(rawFilename) : void 0;
|
|
34965
34980
|
if (filename) {
|
|
34966
34981
|
const packagePlatform = classifyPackagePlatform(filename);
|
|
34967
34982
|
if (packagePlatform === "expo") return true;
|
|
@@ -35735,6 +35750,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
35735
35750
|
"aria-labelledby",
|
|
35736
35751
|
"aria-live",
|
|
35737
35752
|
"aria-owns",
|
|
35753
|
+
"aria-readonly",
|
|
35738
35754
|
"aria-relevant",
|
|
35739
35755
|
"aria-required",
|
|
35740
35756
|
"aria-roledescription"
|
|
@@ -35785,6 +35801,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
35785
35801
|
"aria-labelledby",
|
|
35786
35802
|
"aria-live",
|
|
35787
35803
|
"aria-owns",
|
|
35804
|
+
"aria-readonly",
|
|
35788
35805
|
"aria-relevant",
|
|
35789
35806
|
"aria-required",
|
|
35790
35807
|
"aria-roledescription",
|
|
@@ -35816,6 +35833,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
35816
35833
|
"aria-labelledby",
|
|
35817
35834
|
"aria-live",
|
|
35818
35835
|
"aria-owns",
|
|
35836
|
+
"aria-readonly",
|
|
35819
35837
|
"aria-relevant",
|
|
35820
35838
|
"aria-required",
|
|
35821
35839
|
"aria-roledescription"
|
|
@@ -37114,7 +37132,9 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
37114
37132
|
"aria-label",
|
|
37115
37133
|
"aria-labelledby",
|
|
37116
37134
|
"aria-live",
|
|
37135
|
+
"aria-multiselectable",
|
|
37117
37136
|
"aria-owns",
|
|
37137
|
+
"aria-readonly",
|
|
37118
37138
|
"aria-relevant",
|
|
37119
37139
|
"aria-roledescription",
|
|
37120
37140
|
"aria-rowcount"
|
|
@@ -37142,6 +37162,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
37142
37162
|
"aria-labelledby",
|
|
37143
37163
|
"aria-live",
|
|
37144
37164
|
"aria-owns",
|
|
37165
|
+
"aria-readonly",
|
|
37145
37166
|
"aria-relevant",
|
|
37146
37167
|
"aria-required",
|
|
37147
37168
|
"aria-roledescription",
|
|
@@ -37331,8 +37352,10 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
37331
37352
|
"aria-label",
|
|
37332
37353
|
"aria-labelledby",
|
|
37333
37354
|
"aria-live",
|
|
37355
|
+
"aria-multiselectable",
|
|
37334
37356
|
"aria-orientation",
|
|
37335
37357
|
"aria-owns",
|
|
37358
|
+
"aria-readonly",
|
|
37336
37359
|
"aria-relevant",
|
|
37337
37360
|
"aria-required",
|
|
37338
37361
|
"aria-roledescription"
|
|
@@ -37548,6 +37571,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
37548
37571
|
"aria-live",
|
|
37549
37572
|
"aria-owns",
|
|
37550
37573
|
"aria-posinset",
|
|
37574
|
+
"aria-readonly",
|
|
37551
37575
|
"aria-relevant",
|
|
37552
37576
|
"aria-required",
|
|
37553
37577
|
"aria-roledescription",
|
|
@@ -37576,6 +37600,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
37576
37600
|
"aria-live",
|
|
37577
37601
|
"aria-owns",
|
|
37578
37602
|
"aria-posinset",
|
|
37603
|
+
"aria-readonly",
|
|
37579
37604
|
"aria-relevant",
|
|
37580
37605
|
"aria-required",
|
|
37581
37606
|
"aria-roledescription",
|
|
@@ -37778,6 +37803,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
37778
37803
|
"aria-live",
|
|
37779
37804
|
"aria-orientation",
|
|
37780
37805
|
"aria-owns",
|
|
37806
|
+
"aria-readonly",
|
|
37781
37807
|
"aria-relevant",
|
|
37782
37808
|
"aria-required",
|
|
37783
37809
|
"aria-roledescription"
|
|
@@ -37912,6 +37938,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
37912
37938
|
"aria-labelledby",
|
|
37913
37939
|
"aria-live",
|
|
37914
37940
|
"aria-owns",
|
|
37941
|
+
"aria-readonly",
|
|
37915
37942
|
"aria-relevant",
|
|
37916
37943
|
"aria-required",
|
|
37917
37944
|
"aria-roledescription",
|
|
@@ -37988,6 +38015,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
37988
38015
|
"aria-multiline",
|
|
37989
38016
|
"aria-owns",
|
|
37990
38017
|
"aria-placeholder",
|
|
38018
|
+
"aria-readonly",
|
|
37991
38019
|
"aria-relevant",
|
|
37992
38020
|
"aria-required",
|
|
37993
38021
|
"aria-roledescription"
|
|
@@ -38098,10 +38126,11 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
38098
38126
|
"aria-live",
|
|
38099
38127
|
"aria-orientation",
|
|
38100
38128
|
"aria-owns",
|
|
38129
|
+
"aria-readonly",
|
|
38101
38130
|
"aria-relevant",
|
|
38102
38131
|
"aria-roledescription",
|
|
38103
|
-
"aria-valuemin",
|
|
38104
38132
|
"aria-valuemax",
|
|
38133
|
+
"aria-valuemin",
|
|
38105
38134
|
"aria-valuenow",
|
|
38106
38135
|
"aria-valuetext"
|
|
38107
38136
|
]),
|
|
@@ -38125,6 +38154,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
38125
38154
|
"aria-labelledby",
|
|
38126
38155
|
"aria-live",
|
|
38127
38156
|
"aria-owns",
|
|
38157
|
+
"aria-readonly",
|
|
38128
38158
|
"aria-relevant",
|
|
38129
38159
|
"aria-required",
|
|
38130
38160
|
"aria-roledescription",
|
|
@@ -38258,6 +38288,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
38258
38288
|
"aria-labelledby",
|
|
38259
38289
|
"aria-live",
|
|
38260
38290
|
"aria-owns",
|
|
38291
|
+
"aria-readonly",
|
|
38261
38292
|
"aria-relevant",
|
|
38262
38293
|
"aria-required",
|
|
38263
38294
|
"aria-roledescription"
|
|
@@ -38326,6 +38357,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
38326
38357
|
"aria-labelledby",
|
|
38327
38358
|
"aria-level",
|
|
38328
38359
|
"aria-live",
|
|
38360
|
+
"aria-multiselectable",
|
|
38329
38361
|
"aria-orientation",
|
|
38330
38362
|
"aria-owns",
|
|
38331
38363
|
"aria-relevant",
|
|
@@ -38393,6 +38425,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
38393
38425
|
"aria-multiline",
|
|
38394
38426
|
"aria-owns",
|
|
38395
38427
|
"aria-placeholder",
|
|
38428
|
+
"aria-readonly",
|
|
38396
38429
|
"aria-relevant",
|
|
38397
38430
|
"aria-required",
|
|
38398
38431
|
"aria-roledescription"
|
|
@@ -38495,6 +38528,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
38495
38528
|
"aria-label",
|
|
38496
38529
|
"aria-labelledby",
|
|
38497
38530
|
"aria-live",
|
|
38531
|
+
"aria-multiselectable",
|
|
38498
38532
|
"aria-orientation",
|
|
38499
38533
|
"aria-owns",
|
|
38500
38534
|
"aria-relevant",
|
|
@@ -38512,6 +38546,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
38512
38546
|
"aria-details",
|
|
38513
38547
|
"aria-disabled",
|
|
38514
38548
|
"aria-dropeffect",
|
|
38549
|
+
"aria-errormessage",
|
|
38515
38550
|
"aria-flowto",
|
|
38516
38551
|
"aria-grabbed",
|
|
38517
38552
|
"aria-hidden",
|
|
@@ -38520,8 +38555,10 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
38520
38555
|
"aria-label",
|
|
38521
38556
|
"aria-labelledby",
|
|
38522
38557
|
"aria-live",
|
|
38558
|
+
"aria-multiselectable",
|
|
38523
38559
|
"aria-orientation",
|
|
38524
38560
|
"aria-owns",
|
|
38561
|
+
"aria-readonly",
|
|
38525
38562
|
"aria-relevant",
|
|
38526
38563
|
"aria-required",
|
|
38527
38564
|
"aria-roledescription",
|
|
@@ -39553,8 +39590,8 @@ const serverFetchWithoutRevalidate = defineRule({
|
|
|
39553
39590
|
let isServerSideFile = false;
|
|
39554
39591
|
return {
|
|
39555
39592
|
Program(node) {
|
|
39556
|
-
const filename = normalizeFilename
|
|
39557
|
-
if (!APP_ROUTER_FILE_PATTERN.test(filename)) {
|
|
39593
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
39594
|
+
if (!isInProjectDirectory(context, "app") || !APP_ROUTER_FILE_PATTERN.test(filename)) {
|
|
39558
39595
|
isServerSideFile = false;
|
|
39559
39596
|
return;
|
|
39560
39597
|
}
|
|
@@ -39639,7 +39676,6 @@ const collectRequestTaintedNames = (handlerBody, handlerParamNames) => {
|
|
|
39639
39676
|
});
|
|
39640
39677
|
return taintedNames;
|
|
39641
39678
|
};
|
|
39642
|
-
const PAGES_ROUTER_API_PATH_PATTERN = /\/pages\/api\//;
|
|
39643
39679
|
const inspectHandlerBody = (context, handlerBody, handlerLabel, handlerParamNames) => {
|
|
39644
39680
|
const requestTaintedNames = collectRequestTaintedNames(handlerBody, handlerParamNames);
|
|
39645
39681
|
walkAst(handlerBody, (child) => {
|
|
@@ -39680,8 +39716,7 @@ const serverHoistStaticIo = defineRule({
|
|
|
39680
39716
|
inspectHandlerBody(context, declaration.body, `${handlerName} route handler`, collectHandlerParamNames(declaration.params ?? []));
|
|
39681
39717
|
},
|
|
39682
39718
|
ExportDefaultDeclaration(node) {
|
|
39683
|
-
|
|
39684
|
-
if (!PAGES_ROUTER_API_PATH_PATTERN.test(filename)) return;
|
|
39719
|
+
if (!isInProjectDirectory(context, "pages/api")) return;
|
|
39685
39720
|
const declaration = node.declaration;
|
|
39686
39721
|
if (!isFunctionLike$1(declaration)) return;
|
|
39687
39722
|
if (!declaration.async) return;
|
|
@@ -40735,7 +40770,7 @@ const tanstackStartMissingHeadContent = defineRule({
|
|
|
40735
40770
|
collectVariableAlias(node);
|
|
40736
40771
|
},
|
|
40737
40772
|
JSXOpeningElement(node) {
|
|
40738
|
-
const filename = normalizeFilename
|
|
40773
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
40739
40774
|
if (!TANSTACK_ROOT_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40740
40775
|
if (isNodeOfType(node.name, "JSXIdentifier")) {
|
|
40741
40776
|
if (node.name.name === DOCUMENT_HEAD_ELEMENT_NAME) hasDocumentHeadElement = true;
|
|
@@ -40750,7 +40785,7 @@ const tanstackStartMissingHeadContent = defineRule({
|
|
|
40750
40785
|
if (isInsideDocumentHeadElement(node) && isCustomJsxElementName(node.name)) hasCustomHeadChildElement = true;
|
|
40751
40786
|
},
|
|
40752
40787
|
"Program:exit"(programNode) {
|
|
40753
|
-
const filename = normalizeFilename
|
|
40788
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
40754
40789
|
if (!TANSTACK_ROOT_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40755
40790
|
if (hasDocumentHeadElement && !hasHeadContentElement && !hasCustomHeadChildElement) context.report({
|
|
40756
40791
|
node: programNode,
|
|
@@ -40776,8 +40811,7 @@ const tanstackStartNoAnchorElement = defineRule({
|
|
|
40776
40811
|
severity: "warn",
|
|
40777
40812
|
recommendation: "Use `Link` from `@tanstack/react-router` so internal navigation keeps client state, preloading, and typed routes.",
|
|
40778
40813
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
40779
|
-
|
|
40780
|
-
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40814
|
+
if (!isInProjectDirectory(context, "routes")) return;
|
|
40781
40815
|
if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
|
|
40782
40816
|
const attributes = node.attributes ?? [];
|
|
40783
40817
|
const hrefAttribute = findJsxAttribute(attributes, "href");
|
|
@@ -40860,6 +40894,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
40860
40894
|
severity: "warn",
|
|
40861
40895
|
recommendation: "Use `throw redirect({ to: '/path' })` in `beforeLoad` or `loader`. navigate() during render causes hydration issues.",
|
|
40862
40896
|
create: (context) => {
|
|
40897
|
+
const isRouteFile = isInProjectDirectory(context, "routes");
|
|
40863
40898
|
let deferredCallbackDepth = 0;
|
|
40864
40899
|
let eventHandlerDepth = 0;
|
|
40865
40900
|
const isDeferredHookCall = (node) => isHookCall$1(node, EFFECT_HOOK_NAMES$1) || isHookCall$1(node, "useCallback") || isHookCall$1(node, "useMemo");
|
|
@@ -40923,8 +40958,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
40923
40958
|
};
|
|
40924
40959
|
return {
|
|
40925
40960
|
CallExpression(node) {
|
|
40926
|
-
|
|
40927
|
-
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40961
|
+
if (!isRouteFile) return;
|
|
40928
40962
|
if (isDeferredHookCall(node)) deferredCallbackDepth++;
|
|
40929
40963
|
if (deferredCallbackDepth > 0 || eventHandlerDepth > 0) return;
|
|
40930
40964
|
if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "navigate" && (node.arguments?.length ?? 0) > 0) {
|
|
@@ -40936,48 +40970,39 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
40936
40970
|
}
|
|
40937
40971
|
},
|
|
40938
40972
|
"CallExpression:exit"(node) {
|
|
40939
|
-
|
|
40940
|
-
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40973
|
+
if (!isRouteFile) return;
|
|
40941
40974
|
if (isDeferredHookCall(node)) deferredCallbackDepth = Math.max(0, deferredCallbackDepth - 1);
|
|
40942
40975
|
},
|
|
40943
40976
|
JSXAttribute(node) {
|
|
40944
|
-
|
|
40945
|
-
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40977
|
+
if (!isRouteFile) return;
|
|
40946
40978
|
if (isEventHandlerAttribute(node)) eventHandlerDepth++;
|
|
40947
40979
|
},
|
|
40948
40980
|
"JSXAttribute:exit"(node) {
|
|
40949
|
-
|
|
40950
|
-
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40981
|
+
if (!isRouteFile) return;
|
|
40951
40982
|
if (isEventHandlerAttribute(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
|
|
40952
40983
|
},
|
|
40953
40984
|
Property(node) {
|
|
40954
|
-
|
|
40955
|
-
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40985
|
+
if (!isRouteFile) return;
|
|
40956
40986
|
if (isEventHandlerProperty(node)) eventHandlerDepth++;
|
|
40957
40987
|
},
|
|
40958
40988
|
"Property:exit"(node) {
|
|
40959
|
-
|
|
40960
|
-
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40989
|
+
if (!isRouteFile) return;
|
|
40961
40990
|
if (isEventHandlerProperty(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
|
|
40962
40991
|
},
|
|
40963
40992
|
VariableDeclarator(node) {
|
|
40964
|
-
|
|
40965
|
-
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40993
|
+
if (!isRouteFile) return;
|
|
40966
40994
|
if (isHandlerNamedVariableDeclarator(node)) eventHandlerDepth++;
|
|
40967
40995
|
},
|
|
40968
40996
|
"VariableDeclarator:exit"(node) {
|
|
40969
|
-
|
|
40970
|
-
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40997
|
+
if (!isRouteFile) return;
|
|
40971
40998
|
if (isHandlerNamedVariableDeclarator(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
|
|
40972
40999
|
},
|
|
40973
41000
|
FunctionDeclaration(node) {
|
|
40974
|
-
|
|
40975
|
-
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
41001
|
+
if (!isRouteFile) return;
|
|
40976
41002
|
if (isHandlerNamedFunctionDeclaration(node)) eventHandlerDepth++;
|
|
40977
41003
|
},
|
|
40978
41004
|
"FunctionDeclaration:exit"(node) {
|
|
40979
|
-
|
|
40980
|
-
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
41005
|
+
if (!isRouteFile) return;
|
|
40981
41006
|
if (isHandlerNamedFunctionDeclaration(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
|
|
40982
41007
|
}
|
|
40983
41008
|
};
|
|
@@ -41062,8 +41087,7 @@ const tanstackStartNoUseEffectFetch = defineRule({
|
|
|
41062
41087
|
severity: "warn",
|
|
41063
41088
|
recommendation: "Fetch data in the route `loader` instead. The router loads it before rendering and avoids waterfalls.",
|
|
41064
41089
|
create: (context) => ({ CallExpression(node) {
|
|
41065
|
-
|
|
41066
|
-
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
41090
|
+
if (!isInProjectDirectory(context, "routes")) return;
|
|
41067
41091
|
if (!isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
|
|
41068
41092
|
const callback = node.arguments?.[0];
|
|
41069
41093
|
if (!callback) return;
|