oxlint-plugin-react-doctor 0.6.0-dev.5f60bef → 0.6.0-dev.6885698
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 +96 -89
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1573,6 +1573,7 @@ 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\//;
|
|
1576
1577
|
const NEXTJS_NAVIGATION_FUNCTIONS = new Set([
|
|
1577
1578
|
"redirect",
|
|
1578
1579
|
"permanentRedirect",
|
|
@@ -1590,6 +1591,7 @@ const CACHE_REVALIDATION_FUNCTION_NAMES = new Set([
|
|
|
1590
1591
|
]);
|
|
1591
1592
|
const GOOGLE_FONTS_PATTERN = /fonts\.googleapis\.com/;
|
|
1592
1593
|
const POLYFILL_SCRIPT_PATTERN = /polyfill\.io|polyfill\.min\.js|cdn\.polyfill/;
|
|
1594
|
+
const APP_DIRECTORY_PATTERN = /\/app\//;
|
|
1593
1595
|
const ROUTE_HANDLER_FILE_PATTERN = new RegExp(`/route\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
1594
1596
|
const CRON_ROUTE_PATTERN = /\/(?:cron|jobs\/cron)(?:\/|$)/i;
|
|
1595
1597
|
const MUTATING_ROUTE_SEGMENTS = new Set([
|
|
@@ -1626,7 +1628,7 @@ const isNextjsMetadataImageRouteFilename = (rawFilename) => {
|
|
|
1626
1628
|
};
|
|
1627
1629
|
//#endregion
|
|
1628
1630
|
//#region src/plugin/utils/normalize-filename.ts
|
|
1629
|
-
const normalizeFilename = (filename) => filename.replaceAll("\\", "/");
|
|
1631
|
+
const normalizeFilename$1 = (filename) => filename.replaceAll("\\", "/");
|
|
1630
1632
|
//#endregion
|
|
1631
1633
|
//#region src/plugin/utils/is-generated-image-render-context.ts
|
|
1632
1634
|
const IMAGE_RESPONSE_MODULES = ["next/og", "@vercel/og"];
|
|
@@ -1634,7 +1636,7 @@ const SATORI_MODULE = "satori";
|
|
|
1634
1636
|
const generatedImageJsxCache = /* @__PURE__ */ new WeakMap();
|
|
1635
1637
|
const isGeneratedImageRenderFilename = (rawFilename) => {
|
|
1636
1638
|
if (!rawFilename) return false;
|
|
1637
|
-
return isNextjsMetadataImageRouteFilename(normalizeFilename(rawFilename));
|
|
1639
|
+
return isNextjsMetadataImageRouteFilename(normalizeFilename$1(rawFilename));
|
|
1638
1640
|
};
|
|
1639
1641
|
const isImageResponseCallee = (contextNode, callee) => {
|
|
1640
1642
|
if (isNodeOfType(callee, "Identifier")) return IMAGE_RESPONSE_MODULES.some((moduleSource) => getImportedNameFromModule(contextNode, callee.name, moduleSource) === "ImageResponse");
|
|
@@ -4596,7 +4598,7 @@ const asyncParallel = defineRule({
|
|
|
4596
4598
|
severity: "warn",
|
|
4597
4599
|
recommendation: "Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` so independent calls run at the same time",
|
|
4598
4600
|
create: (context) => {
|
|
4599
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
4601
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
4600
4602
|
const isBrowserTestFile = BROWSER_TEST_FILE_PATTERN.test(filename);
|
|
4601
4603
|
let hasTestLibraryImport = false;
|
|
4602
4604
|
const shouldSkipFile = () => isBrowserTestFile || hasTestLibraryImport;
|
|
@@ -8180,7 +8182,7 @@ const expoNoNonInlinedEnv = defineRule({
|
|
|
8180
8182
|
severity: "warn",
|
|
8181
8183
|
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.",
|
|
8182
8184
|
create: (context) => {
|
|
8183
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
8185
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
8184
8186
|
if (filename && NODE_OR_BUILD_FILE.test(filename)) return EMPTY_VISITORS$3;
|
|
8185
8187
|
return {
|
|
8186
8188
|
MemberExpression(node) {
|
|
@@ -11615,7 +11617,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
11615
11617
|
create: (context) => {
|
|
11616
11618
|
const settings = resolveSettings$33(context.settings);
|
|
11617
11619
|
const allowedExtensions = normalizeExtensions(settings.extensions);
|
|
11618
|
-
const filename = normalizeFilename(context.filename ?? "fixture.tsx");
|
|
11620
|
+
const filename = normalizeFilename$1(context.filename ?? "fixture.tsx");
|
|
11619
11621
|
const extensionOnly = path.extname(filename).slice(1);
|
|
11620
11622
|
const fileHasAllowedExtension = allowedExtensions.has(extensionOnly);
|
|
11621
11623
|
let didReportMismatch = false;
|
|
@@ -15255,52 +15257,6 @@ const nextjsAsyncClientComponent = defineRule({
|
|
|
15255
15257
|
}
|
|
15256
15258
|
});
|
|
15257
15259
|
//#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
|
|
15304
15260
|
//#region src/plugin/rules/nextjs/nextjs-error-boundary-missing-use-client.ts
|
|
15305
15261
|
const nextjsErrorBoundaryMissingUseClient = defineRule({
|
|
15306
15262
|
id: "nextjs-error-boundary-missing-use-client",
|
|
@@ -15310,8 +15266,8 @@ const nextjsErrorBoundaryMissingUseClient = defineRule({
|
|
|
15310
15266
|
severity: "error",
|
|
15311
15267
|
recommendation: "Add `'use client'` at the top of this file. Error boundaries must be Client Components to catch and render fallback UI",
|
|
15312
15268
|
create: (context) => ({ Program(programNode) {
|
|
15313
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
15314
|
-
if (!
|
|
15269
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
15270
|
+
if (!APP_DIRECTORY_PATTERN.test(filename)) return;
|
|
15315
15271
|
if (!ERROR_BOUNDARY_FILE_PATTERN.test(filename)) return;
|
|
15316
15272
|
if (hasDirective(programNode, "use client")) return;
|
|
15317
15273
|
context.report({
|
|
@@ -15342,8 +15298,8 @@ const nextjsGlobalErrorMissingHtmlBody = defineRule({
|
|
|
15342
15298
|
severity: "error",
|
|
15343
15299
|
recommendation: "Wrap your error UI in `<html><body>...</body></html>`. The root layout is unmounted when global-error renders",
|
|
15344
15300
|
create: (context) => ({ Program(programNode) {
|
|
15345
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
15346
|
-
if (!
|
|
15301
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
15302
|
+
if (!APP_DIRECTORY_PATTERN.test(filename)) return;
|
|
15347
15303
|
if (!GLOBAL_ERROR_FILE_PATTERN.test(filename)) return;
|
|
15348
15304
|
const foundTags = fileContainsJsxElements(programNode, REQUIRED_HTML_TAGS);
|
|
15349
15305
|
const missingTags = REQUIRED_HTML_TAGS.filter((tagName) => !foundTags.has(tagName)).map((tagName) => `<${tagName}>`);
|
|
@@ -15474,7 +15430,7 @@ const nextjsMissingMetadata = defineRule({
|
|
|
15474
15430
|
severity: "warn",
|
|
15475
15431
|
recommendation: "Add metadata or `generateMetadata()` so search engines and social previews get a title and description.",
|
|
15476
15432
|
create: (context) => ({ Program(programNode) {
|
|
15477
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
15433
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
15478
15434
|
if (!PAGE_FILE_PATTERN.test(filename)) return;
|
|
15479
15435
|
if (INTERNAL_PAGE_PATH_PATTERN.test(filename)) return;
|
|
15480
15436
|
if (programNode.body?.some((statement) => {
|
|
@@ -15607,8 +15563,8 @@ const nextjsNoClientFetchForServerData = defineRule({
|
|
|
15607
15563
|
if (!fileHasUseClient || !isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
|
|
15608
15564
|
const callback = getEffectCallback(node);
|
|
15609
15565
|
if (!callback || !containsFetchCall(callback, { stopAtFunctionBoundary: true })) return;
|
|
15610
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
15611
|
-
if (PAGE_OR_LAYOUT_FILE_PATTERN.test(filename) ||
|
|
15566
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
15567
|
+
if (PAGE_OR_LAYOUT_FILE_PATTERN.test(filename) || PAGES_DIRECTORY_PATTERN.test(filename)) context.report({
|
|
15612
15568
|
node,
|
|
15613
15569
|
message: "useEffect + fetch in a page/layout makes your users wait through an extra round trip & loading spinner."
|
|
15614
15570
|
});
|
|
@@ -15707,8 +15663,8 @@ const nextjsNoDefaultExportInRouteHandler = defineRule({
|
|
|
15707
15663
|
let programNode = null;
|
|
15708
15664
|
return {
|
|
15709
15665
|
Program(node) {
|
|
15710
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
15711
|
-
isAppRouteHandler =
|
|
15666
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
15667
|
+
isAppRouteHandler = APP_DIRECTORY_PATTERN.test(filename) && ROUTE_HANDLER_FILE_PATTERN.test(filename);
|
|
15712
15668
|
programNode = node;
|
|
15713
15669
|
},
|
|
15714
15670
|
ExportDefaultDeclaration(node) {
|
|
@@ -15744,7 +15700,7 @@ const nextjsNoEdgeOgRuntime = defineRule({
|
|
|
15744
15700
|
let isOgImageFile = false;
|
|
15745
15701
|
return {
|
|
15746
15702
|
Program() {
|
|
15747
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
15703
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
15748
15704
|
isOgImageFile = OG_IMAGE_FILE_PATTERN.test(filename);
|
|
15749
15705
|
},
|
|
15750
15706
|
ExportNamedDeclaration(node) {
|
|
@@ -15820,7 +15776,8 @@ const nextjsNoHeadImport = defineRule({
|
|
|
15820
15776
|
recommendation: "Use the Metadata API because `next/head` is ignored in the App Router and meta tags will not render.",
|
|
15821
15777
|
create: (context) => ({ ImportDeclaration(node) {
|
|
15822
15778
|
if (node.source?.value !== "next/head") return;
|
|
15823
|
-
|
|
15779
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
15780
|
+
if (!APP_DIRECTORY_PATTERN.test(filename)) return;
|
|
15824
15781
|
context.report({
|
|
15825
15782
|
node,
|
|
15826
15783
|
message: "next/head silently does nothing in the App Router, so your meta tags never render."
|
|
@@ -16278,7 +16235,7 @@ const nextjsNoSideEffectInGetHandler = defineRule({
|
|
|
16278
16235
|
resolveBinding = buildProgramBindingLookup(node);
|
|
16279
16236
|
},
|
|
16280
16237
|
ExportNamedDeclaration(node) {
|
|
16281
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
16238
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
16282
16239
|
if (!ROUTE_HANDLER_FILE_PATTERN.test(filename)) return;
|
|
16283
16240
|
if (CRON_ROUTE_PATTERN.test(filename)) return;
|
|
16284
16241
|
if (!isExportedGetHandler(node)) return;
|
|
@@ -17057,7 +17014,7 @@ const nextjsNoUseSearchParamsWithoutSuspense = defineRule({
|
|
|
17057
17014
|
let suspenseLocalNames = /* @__PURE__ */ new Set();
|
|
17058
17015
|
return {
|
|
17059
17016
|
Program(programNode) {
|
|
17060
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
17017
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
17061
17018
|
isPageOrLayoutFile = PAGE_OR_LAYOUT_FILE_PATTERN.test(filename);
|
|
17062
17019
|
if (!isPageOrLayoutFile) return;
|
|
17063
17020
|
hasAncestorLayoutSuspense = hasAncestorSuspenseLayout(context.filename ?? "");
|
|
@@ -18709,13 +18666,40 @@ const classifyPackagePlatform = (filename) => {
|
|
|
18709
18666
|
return result;
|
|
18710
18667
|
};
|
|
18711
18668
|
//#endregion
|
|
18669
|
+
//#region src/plugin/utils/get-react-doctor-setting.ts
|
|
18670
|
+
const readReactDoctorSettingsBag = (settings) => {
|
|
18671
|
+
const reactDoctorSettings = settings?.["react-doctor"];
|
|
18672
|
+
if (typeof reactDoctorSettings !== "object" || reactDoctorSettings === null || Array.isArray(reactDoctorSettings)) return null;
|
|
18673
|
+
return reactDoctorSettings;
|
|
18674
|
+
};
|
|
18675
|
+
const readOwnPropertyValue = (bag, settingName) => Object.getOwnPropertyDescriptor(bag, settingName)?.value;
|
|
18676
|
+
const getReactDoctorStringSetting = (settings, settingName) => {
|
|
18677
|
+
const bag = readReactDoctorSettingsBag(settings);
|
|
18678
|
+
if (!bag) return void 0;
|
|
18679
|
+
const settingValue = readOwnPropertyValue(bag, settingName);
|
|
18680
|
+
return typeof settingValue === "string" ? settingValue : void 0;
|
|
18681
|
+
};
|
|
18682
|
+
const getReactDoctorNumberSetting = (settings, settingName) => {
|
|
18683
|
+
const bag = readReactDoctorSettingsBag(settings);
|
|
18684
|
+
if (!bag) return void 0;
|
|
18685
|
+
const settingValue = readOwnPropertyValue(bag, settingName);
|
|
18686
|
+
return typeof settingValue === "number" && Number.isFinite(settingValue) ? settingValue : void 0;
|
|
18687
|
+
};
|
|
18688
|
+
const getReactDoctorStringArraySetting = (settings, settingName) => {
|
|
18689
|
+
const bag = readReactDoctorSettingsBag(settings);
|
|
18690
|
+
if (!bag) return [];
|
|
18691
|
+
const settingValue = readOwnPropertyValue(bag, settingName);
|
|
18692
|
+
if (!Array.isArray(settingValue)) return [];
|
|
18693
|
+
return settingValue.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
18694
|
+
};
|
|
18695
|
+
//#endregion
|
|
18712
18696
|
//#region src/plugin/utils/is-react-native-file.ts
|
|
18713
18697
|
const WEB_FILE_EXTENSION_PATTERN = /\.web\.[cm]?[jt]sx?$/;
|
|
18714
18698
|
const NATIVE_FILE_EXTENSION_PATTERN = /\.(?:ios|android|native)\.[cm]?[jt]sx?$/;
|
|
18715
18699
|
const classifyReactNativeFileTarget = (context) => {
|
|
18716
18700
|
const rawFilename = context.filename;
|
|
18717
18701
|
if (!rawFilename) return "unknown";
|
|
18718
|
-
const filename = normalizeFilename(rawFilename);
|
|
18702
|
+
const filename = normalizeFilename$1(rawFilename);
|
|
18719
18703
|
if (NATIVE_FILE_EXTENSION_PATTERN.test(filename)) return "react-native";
|
|
18720
18704
|
if (WEB_FILE_EXTENSION_PATTERN.test(filename)) return "web";
|
|
18721
18705
|
const packagePlatform = classifyPackagePlatform(filename);
|
|
@@ -18772,7 +18756,7 @@ const noBarrelImport = defineRule({
|
|
|
18772
18756
|
if (didReportForFile) return;
|
|
18773
18757
|
const source = node.source?.value;
|
|
18774
18758
|
if (typeof source !== "string" || !source.startsWith(".")) return;
|
|
18775
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
18759
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
18776
18760
|
if (!filename) return;
|
|
18777
18761
|
const importRequests = getRuntimeImportRequests(node);
|
|
18778
18762
|
if (importRequests.length === 0) return;
|
|
@@ -26243,6 +26227,15 @@ const CLIENT_APP_DIRECTORY_FRAMEWORKS = new Set([
|
|
|
26243
26227
|
"vite"
|
|
26244
26228
|
]);
|
|
26245
26229
|
const isInsideDirectory = (pathSegments, directoryNames) => pathSegments.some((pathSegment) => directoryNames.has(pathSegment));
|
|
26230
|
+
const normalizeFilename = (filename) => filename.replaceAll("\\", "/");
|
|
26231
|
+
const normalizeDirectory = (directory) => normalizeFilename(directory).replace(/\/+$/, "");
|
|
26232
|
+
const getProjectRelativeFilename = (filename, rootDirectory) => {
|
|
26233
|
+
const normalizedFilename = normalizeFilename(filename);
|
|
26234
|
+
if (!rootDirectory) return normalizedFilename;
|
|
26235
|
+
const rootDirectoryPrefix = `${normalizeDirectory(rootDirectory)}/`;
|
|
26236
|
+
if (!normalizedFilename.startsWith(rootDirectoryPrefix)) return normalizedFilename;
|
|
26237
|
+
return normalizedFilename.slice(rootDirectoryPrefix.length);
|
|
26238
|
+
};
|
|
26246
26239
|
const getClassifiablePathSegments = (pathSegments) => {
|
|
26247
26240
|
const srcIndex = pathSegments.lastIndexOf("src");
|
|
26248
26241
|
if (srcIndex === -1) return pathSegments;
|
|
@@ -26304,6 +26297,7 @@ const tokenizeIdentifierWords = (identifierName) => {
|
|
|
26304
26297
|
const getIdentifierTrailingWord = (identifierName) => tokenizeIdentifierWords(identifierName).at(-1) ?? identifierName.toLowerCase();
|
|
26305
26298
|
//#endregion
|
|
26306
26299
|
//#region src/plugin/constants/tanstack.ts
|
|
26300
|
+
const TANSTACK_ROUTE_FILE_PATTERN = /\/routes\//;
|
|
26307
26301
|
const TANSTACK_ROOT_ROUTE_FILE_PATTERN = /__root\.(tsx?|jsx?)$/;
|
|
26308
26302
|
const TANSTACK_ROUTE_PROPERTY_ORDER = [
|
|
26309
26303
|
"params",
|
|
@@ -26415,7 +26409,7 @@ const noSecretsInClientCode = defineRule({
|
|
|
26415
26409
|
severity: "warn",
|
|
26416
26410
|
recommendation: "Move secrets to server-only code. Anything in client env variables gets shipped to the browser, so it can't hold secrets.",
|
|
26417
26411
|
create: (context) => {
|
|
26418
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
26412
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
26419
26413
|
const framework = getReactDoctorStringSetting(context.settings, "framework");
|
|
26420
26414
|
const rootDirectory = getReactDoctorStringSetting(context.settings, "rootDirectory");
|
|
26421
26415
|
let shouldUseVariableNameHeuristic = classifySecretFileExposure(filename, {
|
|
@@ -29581,7 +29575,7 @@ const onlyExportComponents = defineRule({
|
|
|
29581
29575
|
allowConstantExport: settings.allowConstantExport
|
|
29582
29576
|
};
|
|
29583
29577
|
return { Program(node) {
|
|
29584
|
-
if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return;
|
|
29578
|
+
if (!isFileNameAllowed(normalizeFilename$1(context.filename ?? ""), settings.checkJS)) return;
|
|
29585
29579
|
const allNodes = collectAllNodes(node);
|
|
29586
29580
|
const exports = [];
|
|
29587
29581
|
let hasReactExport = false;
|
|
@@ -32096,7 +32090,7 @@ const renderingSvgPrecision = defineRule({
|
|
|
32096
32090
|
recommendation: "Round path, points, and transform decimals to 1 or 2 digits. The extra precision adds bytes with no visible difference.",
|
|
32097
32091
|
create: (context) => {
|
|
32098
32092
|
const filename = context.filename;
|
|
32099
|
-
const isAutoGenerated = isAutoGeneratedSvgFile(filename ? normalizeFilename(filename) : void 0);
|
|
32093
|
+
const isAutoGenerated = isAutoGeneratedSvgFile(filename ? normalizeFilename$1(filename) : void 0);
|
|
32100
32094
|
return { JSXAttribute(node) {
|
|
32101
32095
|
if (isAutoGenerated) return;
|
|
32102
32096
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
@@ -33246,7 +33240,7 @@ const rnDetoxMissingAwait = defineRule({
|
|
|
33246
33240
|
severity: "warn",
|
|
33247
33241
|
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.",
|
|
33248
33242
|
create: (context) => {
|
|
33249
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
33243
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
33250
33244
|
if (!filename || !DETOX_TEST_FILE.test(filename)) return EMPTY_VISITORS$2;
|
|
33251
33245
|
return { ExpressionStatement(node) {
|
|
33252
33246
|
const expression = node.expression;
|
|
@@ -34976,7 +34970,7 @@ const rnPreferContentInsetAdjustment = defineRetiredRule({
|
|
|
34976
34970
|
//#region src/plugin/utils/is-expo-managed-file.ts
|
|
34977
34971
|
const isExpoManagedFileActive = (context) => {
|
|
34978
34972
|
const rawFilename = context.filename;
|
|
34979
|
-
const filename = rawFilename ? normalizeFilename(rawFilename) : void 0;
|
|
34973
|
+
const filename = rawFilename ? normalizeFilename$1(rawFilename) : void 0;
|
|
34980
34974
|
if (filename) {
|
|
34981
34975
|
const packagePlatform = classifyPackagePlatform(filename);
|
|
34982
34976
|
if (packagePlatform === "expo") return true;
|
|
@@ -39590,8 +39584,8 @@ const serverFetchWithoutRevalidate = defineRule({
|
|
|
39590
39584
|
let isServerSideFile = false;
|
|
39591
39585
|
return {
|
|
39592
39586
|
Program(node) {
|
|
39593
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
39594
|
-
if (!
|
|
39587
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
39588
|
+
if (!APP_ROUTER_FILE_PATTERN.test(filename)) {
|
|
39595
39589
|
isServerSideFile = false;
|
|
39596
39590
|
return;
|
|
39597
39591
|
}
|
|
@@ -39676,6 +39670,7 @@ const collectRequestTaintedNames = (handlerBody, handlerParamNames) => {
|
|
|
39676
39670
|
});
|
|
39677
39671
|
return taintedNames;
|
|
39678
39672
|
};
|
|
39673
|
+
const PAGES_ROUTER_API_PATH_PATTERN = /\/pages\/api\//;
|
|
39679
39674
|
const inspectHandlerBody = (context, handlerBody, handlerLabel, handlerParamNames) => {
|
|
39680
39675
|
const requestTaintedNames = collectRequestTaintedNames(handlerBody, handlerParamNames);
|
|
39681
39676
|
walkAst(handlerBody, (child) => {
|
|
@@ -39716,7 +39711,8 @@ const serverHoistStaticIo = defineRule({
|
|
|
39716
39711
|
inspectHandlerBody(context, declaration.body, `${handlerName} route handler`, collectHandlerParamNames(declaration.params ?? []));
|
|
39717
39712
|
},
|
|
39718
39713
|
ExportDefaultDeclaration(node) {
|
|
39719
|
-
|
|
39714
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
39715
|
+
if (!PAGES_ROUTER_API_PATH_PATTERN.test(filename)) return;
|
|
39720
39716
|
const declaration = node.declaration;
|
|
39721
39717
|
if (!isFunctionLike$1(declaration)) return;
|
|
39722
39718
|
if (!declaration.async) return;
|
|
@@ -40770,7 +40766,7 @@ const tanstackStartMissingHeadContent = defineRule({
|
|
|
40770
40766
|
collectVariableAlias(node);
|
|
40771
40767
|
},
|
|
40772
40768
|
JSXOpeningElement(node) {
|
|
40773
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
40769
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
40774
40770
|
if (!TANSTACK_ROOT_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40775
40771
|
if (isNodeOfType(node.name, "JSXIdentifier")) {
|
|
40776
40772
|
if (node.name.name === DOCUMENT_HEAD_ELEMENT_NAME) hasDocumentHeadElement = true;
|
|
@@ -40785,7 +40781,7 @@ const tanstackStartMissingHeadContent = defineRule({
|
|
|
40785
40781
|
if (isInsideDocumentHeadElement(node) && isCustomJsxElementName(node.name)) hasCustomHeadChildElement = true;
|
|
40786
40782
|
},
|
|
40787
40783
|
"Program:exit"(programNode) {
|
|
40788
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
40784
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
40789
40785
|
if (!TANSTACK_ROOT_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40790
40786
|
if (hasDocumentHeadElement && !hasHeadContentElement && !hasCustomHeadChildElement) context.report({
|
|
40791
40787
|
node: programNode,
|
|
@@ -40811,7 +40807,8 @@ const tanstackStartNoAnchorElement = defineRule({
|
|
|
40811
40807
|
severity: "warn",
|
|
40812
40808
|
recommendation: "Use `Link` from `@tanstack/react-router` so internal navigation keeps client state, preloading, and typed routes.",
|
|
40813
40809
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
40814
|
-
|
|
40810
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
40811
|
+
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40815
40812
|
if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
|
|
40816
40813
|
const attributes = node.attributes ?? [];
|
|
40817
40814
|
const hrefAttribute = findJsxAttribute(attributes, "href");
|
|
@@ -40894,7 +40891,6 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
40894
40891
|
severity: "warn",
|
|
40895
40892
|
recommendation: "Use `throw redirect({ to: '/path' })` in `beforeLoad` or `loader`. navigate() during render causes hydration issues.",
|
|
40896
40893
|
create: (context) => {
|
|
40897
|
-
const isRouteFile = isInProjectDirectory(context, "routes");
|
|
40898
40894
|
let deferredCallbackDepth = 0;
|
|
40899
40895
|
let eventHandlerDepth = 0;
|
|
40900
40896
|
const isDeferredHookCall = (node) => isHookCall$1(node, EFFECT_HOOK_NAMES$1) || isHookCall$1(node, "useCallback") || isHookCall$1(node, "useMemo");
|
|
@@ -40958,7 +40954,8 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
40958
40954
|
};
|
|
40959
40955
|
return {
|
|
40960
40956
|
CallExpression(node) {
|
|
40961
|
-
|
|
40957
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
40958
|
+
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40962
40959
|
if (isDeferredHookCall(node)) deferredCallbackDepth++;
|
|
40963
40960
|
if (deferredCallbackDepth > 0 || eventHandlerDepth > 0) return;
|
|
40964
40961
|
if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "navigate" && (node.arguments?.length ?? 0) > 0) {
|
|
@@ -40970,39 +40967,48 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
40970
40967
|
}
|
|
40971
40968
|
},
|
|
40972
40969
|
"CallExpression:exit"(node) {
|
|
40973
|
-
|
|
40970
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
40971
|
+
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40974
40972
|
if (isDeferredHookCall(node)) deferredCallbackDepth = Math.max(0, deferredCallbackDepth - 1);
|
|
40975
40973
|
},
|
|
40976
40974
|
JSXAttribute(node) {
|
|
40977
|
-
|
|
40975
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
40976
|
+
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40978
40977
|
if (isEventHandlerAttribute(node)) eventHandlerDepth++;
|
|
40979
40978
|
},
|
|
40980
40979
|
"JSXAttribute:exit"(node) {
|
|
40981
|
-
|
|
40980
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
40981
|
+
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40982
40982
|
if (isEventHandlerAttribute(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
|
|
40983
40983
|
},
|
|
40984
40984
|
Property(node) {
|
|
40985
|
-
|
|
40985
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
40986
|
+
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40986
40987
|
if (isEventHandlerProperty(node)) eventHandlerDepth++;
|
|
40987
40988
|
},
|
|
40988
40989
|
"Property:exit"(node) {
|
|
40989
|
-
|
|
40990
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
40991
|
+
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40990
40992
|
if (isEventHandlerProperty(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
|
|
40991
40993
|
},
|
|
40992
40994
|
VariableDeclarator(node) {
|
|
40993
|
-
|
|
40995
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
40996
|
+
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40994
40997
|
if (isHandlerNamedVariableDeclarator(node)) eventHandlerDepth++;
|
|
40995
40998
|
},
|
|
40996
40999
|
"VariableDeclarator:exit"(node) {
|
|
40997
|
-
|
|
41000
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
41001
|
+
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
40998
41002
|
if (isHandlerNamedVariableDeclarator(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
|
|
40999
41003
|
},
|
|
41000
41004
|
FunctionDeclaration(node) {
|
|
41001
|
-
|
|
41005
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
41006
|
+
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
41002
41007
|
if (isHandlerNamedFunctionDeclaration(node)) eventHandlerDepth++;
|
|
41003
41008
|
},
|
|
41004
41009
|
"FunctionDeclaration:exit"(node) {
|
|
41005
|
-
|
|
41010
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
41011
|
+
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
41006
41012
|
if (isHandlerNamedFunctionDeclaration(node)) eventHandlerDepth = Math.max(0, eventHandlerDepth - 1);
|
|
41007
41013
|
}
|
|
41008
41014
|
};
|
|
@@ -41087,7 +41093,8 @@ const tanstackStartNoUseEffectFetch = defineRule({
|
|
|
41087
41093
|
severity: "warn",
|
|
41088
41094
|
recommendation: "Fetch data in the route `loader` instead. The router loads it before rendering and avoids waterfalls.",
|
|
41089
41095
|
create: (context) => ({ CallExpression(node) {
|
|
41090
|
-
|
|
41096
|
+
const filename = normalizeFilename$1(context.filename ?? "");
|
|
41097
|
+
if (!TANSTACK_ROUTE_FILE_PATTERN.test(filename)) return;
|
|
41091
41098
|
if (!isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
|
|
41092
41099
|
const callback = node.arguments?.[0];
|
|
41093
41100
|
if (!callback) return;
|