react-doctor 0.5.6 → 0.5.7-dev.2cadd3f
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/cli.js +2029 -630
- package/dist/index.d.ts +38 -4
- package/dist/index.js +1216 -323
- package/dist/lsp.js +1235 -347
- package/package.json +6 -6
package/dist/lsp.js
CHANGED
|
@@ -8,13 +8,13 @@ import path from "node:path";
|
|
|
8
8
|
import * as NodeChildProcess from "node:child_process";
|
|
9
9
|
import { spawn, spawnSync } from "node:child_process";
|
|
10
10
|
import * as ts from "typescript";
|
|
11
|
-
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
11
|
+
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
12
12
|
import { parseJSON5 } from "confbox";
|
|
13
13
|
import * as NodeUrl from "node:url";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { createJiti } from "jiti";
|
|
16
16
|
import * as Crypto from "node:crypto";
|
|
17
|
-
import crypto from "node:crypto";
|
|
17
|
+
import crypto, { createHash } from "node:crypto";
|
|
18
18
|
import { gzipSync } from "node:zlib";
|
|
19
19
|
import { CodeActionKind, CodeActionTriggerKind, DidChangeWatchedFilesNotification, DocumentDiagnosticReportKind, FileChangeType, TextDocumentSyncKind, TextDocuments, createConnection } from "vscode-languageserver/node.js";
|
|
20
20
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
@@ -7224,7 +7224,7 @@ const provideContext$1 = /* @__PURE__ */ dual(2, (self, context) => {
|
|
|
7224
7224
|
return updateContext$1(self, merge$3(context));
|
|
7225
7225
|
});
|
|
7226
7226
|
/** @internal */
|
|
7227
|
-
const provideService$
|
|
7227
|
+
const provideService$3 = function() {
|
|
7228
7228
|
if (arguments.length === 1) return dual(2, (self, impl) => provideServiceImpl(self, arguments[0], impl));
|
|
7229
7229
|
return dual(3, (self, service, impl) => provideServiceImpl(self, service, impl)).apply(this, arguments);
|
|
7230
7230
|
};
|
|
@@ -7445,7 +7445,7 @@ const constScopeEmpty = { _tag: "Empty" };
|
|
|
7445
7445
|
/** @internal */
|
|
7446
7446
|
const scope = scopeTag;
|
|
7447
7447
|
/** @internal */
|
|
7448
|
-
const provideScope = /* @__PURE__ */ provideService$
|
|
7448
|
+
const provideScope = /* @__PURE__ */ provideService$3(scopeTag);
|
|
7449
7449
|
/** @internal */
|
|
7450
7450
|
const scoped$1 = (self) => withFiber$1((fiber) => {
|
|
7451
7451
|
const prev = fiber.context;
|
|
@@ -7888,7 +7888,7 @@ const makeLatchUnsafe = (open) => new Latch(open ?? false);
|
|
|
7888
7888
|
/** @internal */
|
|
7889
7889
|
const makeLatch = (open) => sync$2(() => makeLatchUnsafe(open));
|
|
7890
7890
|
/** @internal */
|
|
7891
|
-
const withTracerEnabled$1 = /* @__PURE__ */ provideService$
|
|
7891
|
+
const withTracerEnabled$1 = /* @__PURE__ */ provideService$3(TracerEnabled);
|
|
7892
7892
|
const bigint0 = /* @__PURE__ */ BigInt(0);
|
|
7893
7893
|
const NoopSpanProto = {
|
|
7894
7894
|
_tag: "Span",
|
|
@@ -7969,7 +7969,7 @@ const useSpan$1 = (name, ...args) => {
|
|
|
7969
7969
|
}));
|
|
7970
7970
|
});
|
|
7971
7971
|
};
|
|
7972
|
-
const provideParentSpan = /* @__PURE__ */ provideService$
|
|
7972
|
+
const provideParentSpan = /* @__PURE__ */ provideService$3(ParentSpan);
|
|
7973
7973
|
/** @internal */
|
|
7974
7974
|
const withParentSpan$1 = function() {
|
|
7975
7975
|
const dataFirst = isEffect$1(arguments[0]);
|
|
@@ -9536,7 +9536,7 @@ var CurrentMemoMap = class extends Service()("effect/Layer/CurrentMemoMap") {
|
|
|
9536
9536
|
* @category memo map
|
|
9537
9537
|
* @since 2.0.0
|
|
9538
9538
|
*/
|
|
9539
|
-
const buildWithMemoMap = /* @__PURE__ */ dual(3, (self, memoMap, scope) => provideService$
|
|
9539
|
+
const buildWithMemoMap = /* @__PURE__ */ dual(3, (self, memoMap, scope) => provideService$3(map$4(self.build(memoMap, scope), add(CurrentMemoMap, memoMap)), CurrentMemoMap, memoMap));
|
|
9540
9540
|
/**
|
|
9541
9541
|
* Builds a layer into an `Effect` value. Any resources associated with this
|
|
9542
9542
|
* layer will be released when the specified scope is closed unless their scope
|
|
@@ -10889,7 +10889,7 @@ const provide$1 = /* @__PURE__ */ dual((args) => isEffect$1(args[0]), (self, sou
|
|
|
10889
10889
|
/** @internal */
|
|
10890
10890
|
const repeatOrElse = /* @__PURE__ */ dual(3, (self, schedule, orElse) => flatMap$4(toStepWithMetadata(schedule), (step) => {
|
|
10891
10891
|
let meta = CurrentMetadata.defaultValue();
|
|
10892
|
-
return catch_$2(forever$2(tap$2(flatMap$4(suspend$3(() => provideService$
|
|
10892
|
+
return catch_$2(forever$2(tap$2(flatMap$4(suspend$3(() => provideService$3(self, CurrentMetadata, meta)), step), (meta_) => sync$2(() => {
|
|
10893
10893
|
meta = meta_;
|
|
10894
10894
|
})), { disableYield: true }), (error) => isDone$2(error) ? succeed$5(error.value) : orElse(error, meta.attempt === 0 ? none() : some(meta)));
|
|
10895
10895
|
}));
|
|
@@ -10897,7 +10897,7 @@ const repeatOrElse = /* @__PURE__ */ dual(3, (self, schedule, orElse) => flatMap
|
|
|
10897
10897
|
const retryOrElse = /* @__PURE__ */ dual(3, (self, policy, orElse) => flatMap$4(toStepWithMetadata(policy), (step) => {
|
|
10898
10898
|
let meta = CurrentMetadata.defaultValue();
|
|
10899
10899
|
let lastError;
|
|
10900
|
-
const loop = catch_$2(suspend$3(() => provideService$
|
|
10900
|
+
const loop = catch_$2(suspend$3(() => provideService$3(self, CurrentMetadata, meta)), (error) => {
|
|
10901
10901
|
lastError = error;
|
|
10902
10902
|
return flatMap$4(step(error), (meta_) => {
|
|
10903
10903
|
meta = meta_;
|
|
@@ -12977,7 +12977,7 @@ const updateContext = updateContext$1;
|
|
|
12977
12977
|
* @category Context
|
|
12978
12978
|
* @since 2.0.0
|
|
12979
12979
|
*/
|
|
12980
|
-
const provideService = provideService$
|
|
12980
|
+
const provideService$2 = provideService$3;
|
|
12981
12981
|
/**
|
|
12982
12982
|
* Scopes all resources used in this workflow to the lifetime of the workflow,
|
|
12983
12983
|
* ensuring that their finalizers are run as soon as this workflow completes
|
|
@@ -18019,6 +18019,20 @@ function decodeUnknownOption$1(schema, options) {
|
|
|
18019
18019
|
return asOption(decodeUnknownEffect(schema, options));
|
|
18020
18020
|
}
|
|
18021
18021
|
/**
|
|
18022
|
+
* Creates a synchronous decoder for `unknown` input.
|
|
18023
|
+
*
|
|
18024
|
+
* **Details**
|
|
18025
|
+
*
|
|
18026
|
+
* The returned function returns the decoded `Type` on success and throws an
|
|
18027
|
+
* `Error` with the `SchemaIssue.Issue` in its `cause` on decoding failure.
|
|
18028
|
+
*
|
|
18029
|
+
* @category decoding
|
|
18030
|
+
* @since 3.10.0
|
|
18031
|
+
*/
|
|
18032
|
+
function decodeUnknownSync$1(schema, options) {
|
|
18033
|
+
return asSync(decodeUnknownEffect(schema, options));
|
|
18034
|
+
}
|
|
18035
|
+
/**
|
|
18022
18036
|
* Creates an effectful encoder for `unknown` input.
|
|
18023
18037
|
*
|
|
18024
18038
|
* **Details**
|
|
@@ -18320,6 +18334,40 @@ function isSchemaError(u) {
|
|
|
18320
18334
|
*/
|
|
18321
18335
|
const decodeUnknownOption = decodeUnknownOption$1;
|
|
18322
18336
|
/**
|
|
18337
|
+
* Decodes an `unknown` input against a schema synchronously, returning the
|
|
18338
|
+
* decoded value or throwing an `Error` whose cause contains the schema issue.
|
|
18339
|
+
* Use this when you want to validate data at a boundary and treat a schema
|
|
18340
|
+
* mismatch as an exception. For typed input use `decodeSync`.
|
|
18341
|
+
*
|
|
18342
|
+
* **Details**
|
|
18343
|
+
*
|
|
18344
|
+
* Only service-free schemas can be decoded synchronously. For non-throwing
|
|
18345
|
+
* alternatives see `decodeUnknownOption`, `decodeUnknownExit`, or
|
|
18346
|
+
* `decodeUnknownEffect`. Options may be provided either when creating the
|
|
18347
|
+
* decoder or when applying it; application options override creation options.
|
|
18348
|
+
*
|
|
18349
|
+
* **Example** (Decoding with a transformation schema)
|
|
18350
|
+
*
|
|
18351
|
+
* ```ts
|
|
18352
|
+
* import { Schema } from "effect"
|
|
18353
|
+
*
|
|
18354
|
+
* const NumberFromString = Schema.NumberFromString
|
|
18355
|
+
*
|
|
18356
|
+
* console.log(Schema.decodeUnknownSync(NumberFromString)("42"))
|
|
18357
|
+
* // Output: 42
|
|
18358
|
+
*
|
|
18359
|
+
* Schema.decodeUnknownSync(NumberFromString)("not a number")
|
|
18360
|
+
* // throws SchemaError: NumberFromString
|
|
18361
|
+
* // └─ Encoded side transformation failure
|
|
18362
|
+
* // └─ NumberFromString
|
|
18363
|
+
* // └─ Expected a numeric string, actual "not a number"
|
|
18364
|
+
* ```
|
|
18365
|
+
*
|
|
18366
|
+
* @category decoding
|
|
18367
|
+
* @since 4.0.0
|
|
18368
|
+
*/
|
|
18369
|
+
const decodeUnknownSync = decodeUnknownSync$1;
|
|
18370
|
+
/**
|
|
18323
18371
|
* Encodes an `unknown` input against a schema synchronously, throwing a
|
|
18324
18372
|
* {@link SchemaError} on failure. Use this when you want to serialize data at a
|
|
18325
18373
|
* boundary and treat a schema mismatch as an unrecoverable error. For
|
|
@@ -19286,7 +19334,8 @@ var Diagnostic = class extends Class("Diagnostic")({
|
|
|
19286
19334
|
category: String$1,
|
|
19287
19335
|
fileContext: optional(Literals(["test", "story"])),
|
|
19288
19336
|
suppressionHint: optional(String$1),
|
|
19289
|
-
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation))
|
|
19337
|
+
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation)),
|
|
19338
|
+
fixGroupId: optional(String$1)
|
|
19290
19339
|
}) {};
|
|
19291
19340
|
/**
|
|
19292
19341
|
* Deterministic identity string for a diagnostic. Same diagnostic
|
|
@@ -19335,6 +19384,7 @@ var JsonReportProjectEntry = class extends Class("JsonReportProjectEntry")({
|
|
|
19335
19384
|
score: Unknown,
|
|
19336
19385
|
skippedChecks: ArraySchema(String$1),
|
|
19337
19386
|
skippedCheckReasons: optional(Record$1(String$1, String$1)),
|
|
19387
|
+
scannedFileCount: optional(Number$1),
|
|
19338
19388
|
elapsedMilliseconds: Number$1
|
|
19339
19389
|
}) {};
|
|
19340
19390
|
/**
|
|
@@ -25204,6 +25254,14 @@ const runWith = (self, f, onHalt) => suspend$2(() => {
|
|
|
25204
25254
|
return catchDone(flatMap$2(toTransform(self)(done$1(), scope), f), onHalt ? onHalt : succeed$2).pipe(onExit$1((exit) => close(scope, exit)));
|
|
25205
25255
|
});
|
|
25206
25256
|
/**
|
|
25257
|
+
* Provides a concrete service for a context key, removing that service
|
|
25258
|
+
* requirement from the returned channel.
|
|
25259
|
+
*
|
|
25260
|
+
* @category services
|
|
25261
|
+
* @since 2.0.0
|
|
25262
|
+
*/
|
|
25263
|
+
const provideService$1 = /* @__PURE__ */ dual(3, (self, key, service) => fromTransform$1((upstream, scope) => map$3(provideService$2(toTransform(self)(upstream, scope), key, service), provideService$2(key, service))));
|
|
25264
|
+
/**
|
|
25207
25265
|
* Runs a channel and applies an effect to each output element.
|
|
25208
25266
|
*
|
|
25209
25267
|
* **Example** (Running effects for each output)
|
|
@@ -26592,6 +26650,44 @@ const splitLines = (self) => self.channel.pipe(pipeTo(splitLines$1()), fromChann
|
|
|
26592
26650
|
*/
|
|
26593
26651
|
const ensuring = /* @__PURE__ */ dual(2, (self, finalizer) => fromChannel(ensuring$1(self.channel, finalizer)));
|
|
26594
26652
|
/**
|
|
26653
|
+
* Provides the stream with a single required service, eliminating that
|
|
26654
|
+
* requirement from its environment.
|
|
26655
|
+
*
|
|
26656
|
+
* **Example** (Providing a stream service)
|
|
26657
|
+
*
|
|
26658
|
+
* ```ts
|
|
26659
|
+
* import { Console, Context, Effect, Stream } from "effect"
|
|
26660
|
+
*
|
|
26661
|
+
* class Greeter extends Context.Service<Greeter, {
|
|
26662
|
+
* greet: (name: string) => string
|
|
26663
|
+
* }>()("Greeter") {}
|
|
26664
|
+
*
|
|
26665
|
+
* const stream = Stream.fromEffect(
|
|
26666
|
+
* Effect.service(Greeter).pipe(
|
|
26667
|
+
* Effect.map((greeter) => greeter.greet("Ada"))
|
|
26668
|
+
* )
|
|
26669
|
+
* )
|
|
26670
|
+
*
|
|
26671
|
+
* const program = Effect.gen(function*() {
|
|
26672
|
+
* const collected = yield* Stream.runCollect(
|
|
26673
|
+
* stream.pipe(
|
|
26674
|
+
* Stream.provideService(Greeter, {
|
|
26675
|
+
* greet: (name) => `Hello, ${name}`
|
|
26676
|
+
* })
|
|
26677
|
+
* )
|
|
26678
|
+
* )
|
|
26679
|
+
* yield* Console.log(collected)
|
|
26680
|
+
* })
|
|
26681
|
+
*
|
|
26682
|
+
* Effect.runPromise(program)
|
|
26683
|
+
* //=> ["Hello, Ada"]
|
|
26684
|
+
* ```
|
|
26685
|
+
*
|
|
26686
|
+
* @category services
|
|
26687
|
+
* @since 2.0.0
|
|
26688
|
+
*/
|
|
26689
|
+
const provideService = /* @__PURE__ */ dual(3, (self, key, service) => fromChannel(provideService$1(self.channel, key, service)));
|
|
26690
|
+
/**
|
|
26595
26691
|
* Runs a stream with a sink and returns the sink result.
|
|
26596
26692
|
*
|
|
26597
26693
|
* **Example** (Running a stream with a sink)
|
|
@@ -30021,7 +30117,7 @@ const make$8 = /* @__PURE__ */ fnUntraced(function* (options) {
|
|
|
30021
30117
|
const runFork = runForkWith(services);
|
|
30022
30118
|
const exportInterval = max(fromInputUnsafe(options.exportInterval), zero);
|
|
30023
30119
|
let disabledUntil = void 0;
|
|
30024
|
-
const client = filterStatusOk(get$4(services, HttpClient)).pipe(transformResponse(provideService(TracerPropagationEnabled, false)), retryTransient({
|
|
30120
|
+
const client = filterStatusOk(get$4(services, HttpClient)).pipe(transformResponse(provideService$2(TracerPropagationEnabled, false)), retryTransient({
|
|
30025
30121
|
schedule: policy,
|
|
30026
30122
|
times: 3
|
|
30027
30123
|
}));
|
|
@@ -32751,16 +32847,26 @@ const isMinifiedSource = (absolutePath) => {
|
|
|
32751
32847
|
if (fileDescriptor !== void 0) NFS.closeSync(fileDescriptor);
|
|
32752
32848
|
}
|
|
32753
32849
|
};
|
|
32754
|
-
const
|
|
32755
|
-
|
|
32850
|
+
const cachedIsLargeMinifiedByPath = /* @__PURE__ */ new Map();
|
|
32851
|
+
const clearMinifiedFileCache = () => {
|
|
32852
|
+
cachedIsLargeMinifiedByPath.clear();
|
|
32853
|
+
};
|
|
32854
|
+
const statSourceFileSize = (absolutePath) => {
|
|
32756
32855
|
try {
|
|
32757
|
-
|
|
32856
|
+
return NFS.statSync(absolutePath).size;
|
|
32758
32857
|
} catch {
|
|
32759
|
-
return
|
|
32858
|
+
return null;
|
|
32760
32859
|
}
|
|
32761
|
-
if (sizeBytes < 2e4) return false;
|
|
32762
|
-
return isMinifiedSource(absolutePath);
|
|
32763
32860
|
};
|
|
32861
|
+
const isLargeMinifiedFile = (absolutePath, knownSizeBytes) => {
|
|
32862
|
+
const cached = cachedIsLargeMinifiedByPath.get(absolutePath);
|
|
32863
|
+
if (cached !== void 0) return cached;
|
|
32864
|
+
const sizeBytes = knownSizeBytes === void 0 ? statSourceFileSize(absolutePath) : knownSizeBytes;
|
|
32865
|
+
const result = sizeBytes !== null && sizeBytes >= 2e4 && isMinifiedSource(absolutePath);
|
|
32866
|
+
cachedIsLargeMinifiedByPath.set(absolutePath, result);
|
|
32867
|
+
return result;
|
|
32868
|
+
};
|
|
32869
|
+
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
32764
32870
|
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
32765
32871
|
"EACCES",
|
|
32766
32872
|
"EPERM",
|
|
@@ -32770,11 +32876,7 @@ const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
|
32770
32876
|
"ELOOP",
|
|
32771
32877
|
"ENAMETOOLONG"
|
|
32772
32878
|
]);
|
|
32773
|
-
const isIgnorableReaddirError = (error) =>
|
|
32774
|
-
if (typeof error !== "object" || error === null) return false;
|
|
32775
|
-
const errorCode = error.code;
|
|
32776
|
-
return typeof errorCode === "string" && IGNORABLE_READDIR_ERROR_CODES.has(errorCode);
|
|
32777
|
-
};
|
|
32879
|
+
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
32778
32880
|
const readDirectoryEntries = (directoryPath) => {
|
|
32779
32881
|
try {
|
|
32780
32882
|
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
@@ -32824,7 +32926,7 @@ const readPackageJsonUncached = (packageJsonPath) => {
|
|
|
32824
32926
|
return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
32825
32927
|
} catch (error) {
|
|
32826
32928
|
if (error instanceof SyntaxError) return {};
|
|
32827
|
-
if (error
|
|
32929
|
+
if (isErrnoException(error)) {
|
|
32828
32930
|
const { code } = error;
|
|
32829
32931
|
if (code === "EISDIR" || code === "EACCES" || code === "EPERM" || code === "ENOENT") return {};
|
|
32830
32932
|
}
|
|
@@ -33549,17 +33651,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
33549
33651
|
return false;
|
|
33550
33652
|
};
|
|
33551
33653
|
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
|
|
33552
|
-
const
|
|
33553
|
-
const spec = packageJson.dependencies?.
|
|
33654
|
+
const getDependencySpec = (packageJson, packageName) => {
|
|
33655
|
+
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
33554
33656
|
return typeof spec === "string" ? spec : null;
|
|
33555
33657
|
};
|
|
33556
|
-
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson,
|
|
33658
|
+
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
|
|
33557
33659
|
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
33558
|
-
const
|
|
33559
|
-
const spec = packageJson.dependencies?.["@shopify/flash-list"] ?? packageJson.devDependencies?.["@shopify/flash-list"] ?? packageJson.peerDependencies?.["@shopify/flash-list"] ?? packageJson.optionalDependencies?.["@shopify/flash-list"];
|
|
33560
|
-
return typeof spec === "string" ? spec : null;
|
|
33561
|
-
};
|
|
33562
|
-
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getShopifyFlashListDependencySpec);
|
|
33660
|
+
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
|
|
33563
33661
|
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
33564
33662
|
if (version === null || !isCatalogReference(version)) return version;
|
|
33565
33663
|
const catalogName = extractCatalogName(version);
|
|
@@ -33571,11 +33669,7 @@ const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson,
|
|
|
33571
33669
|
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
33572
33670
|
return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
33573
33671
|
};
|
|
33574
|
-
const
|
|
33575
|
-
const spec = packageJson.dependencies?.next ?? packageJson.devDependencies?.next ?? packageJson.peerDependencies?.next ?? packageJson.optionalDependencies?.next;
|
|
33576
|
-
return typeof spec === "string" ? spec : null;
|
|
33577
|
-
};
|
|
33578
|
-
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getNextjsDependencySpec);
|
|
33672
|
+
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
|
|
33579
33673
|
const getPreactVersion = (packageJson) => {
|
|
33580
33674
|
return {
|
|
33581
33675
|
...packageJson.peerDependencies,
|
|
@@ -33636,6 +33730,7 @@ const MILLISECONDS_PER_SECOND = 1e3;
|
|
|
33636
33730
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
33637
33731
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
33638
33732
|
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
33733
|
+
const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
|
|
33639
33734
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
33640
33735
|
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
33641
33736
|
const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
@@ -33657,6 +33752,11 @@ const ES_TARGET_YEAR_BY_NAME = {
|
|
|
33657
33752
|
esnext: 9999
|
|
33658
33753
|
};
|
|
33659
33754
|
/**
|
|
33755
|
+
* tsconfig filenames probed when resolving a project's TypeScript
|
|
33756
|
+
* compiler options — the root config first, then a monorepo base config.
|
|
33757
|
+
*/
|
|
33758
|
+
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
33759
|
+
/**
|
|
33660
33760
|
* Project-config files that `StagedFiles.materialize` copies into
|
|
33661
33761
|
* the temp directory alongside staged sources so oxlint resolves
|
|
33662
33762
|
* `tsconfig` / `package.json` / lint configs the same way it would
|
|
@@ -33703,7 +33803,16 @@ const CONFIG_FINGERPRINT_FILENAMES = [
|
|
|
33703
33803
|
];
|
|
33704
33804
|
const OXLINT_OUTPUT_MAX_BYTES = 50 * 1024 * 1024;
|
|
33705
33805
|
const OXLINT_SPAWN_TIMEOUT_MS = 6e4;
|
|
33806
|
+
const NODE_COMPILE_CACHE_DIR_NAME = "node-compile-cache";
|
|
33807
|
+
const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
|
|
33808
|
+
const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
|
|
33809
|
+
const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
33810
|
+
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
33811
|
+
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
33706
33812
|
const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
|
|
33813
|
+
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
33814
|
+
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
33815
|
+
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
33707
33816
|
const RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES = 10080;
|
|
33708
33817
|
const REACT_SERVER_DOM_PACKAGES = [
|
|
33709
33818
|
"react-server-dom-webpack",
|
|
@@ -33726,14 +33835,25 @@ const APP_ONLY_RULE_KEYS = new Set([
|
|
|
33726
33835
|
]);
|
|
33727
33836
|
const COMPILER_CLEANUP_BUCKET = "compiler-cleanup";
|
|
33728
33837
|
const COMPILER_CLEANUP_RULE_KEYS = new Set(["react-doctor/react-compiler-no-manual-memoization"]);
|
|
33838
|
+
const ROOT_CAUSE_GROUPABLE_RULE_KEYS = new Set([
|
|
33839
|
+
"react-doctor/no-derived-state",
|
|
33840
|
+
"react-doctor/no-derived-state-effect",
|
|
33841
|
+
"react-doctor/no-derived-useState",
|
|
33842
|
+
"react-doctor/no-adjust-state-on-prop-change",
|
|
33843
|
+
"react-doctor/no-reset-all-state-on-prop-change"
|
|
33844
|
+
]);
|
|
33729
33845
|
const MAX_GLOB_PATTERN_LENGTH_CHARS = 1024;
|
|
33730
33846
|
const CONFIG_CACHE_TTL_MS = 300 * 1e3;
|
|
33731
33847
|
const SOCKET_FREE_PURL_API_BASE = "https://firewall-api.socket.dev/purl";
|
|
33732
33848
|
const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
33733
33849
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
33850
|
+
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
33851
|
+
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
33734
33852
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
33735
33853
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
33736
33854
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
33855
|
+
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
33856
|
+
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
33737
33857
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
33738
33858
|
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
33739
33859
|
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
@@ -34178,6 +34298,7 @@ const isTailwindAtLeast = (detected, required) => {
|
|
|
34178
34298
|
if (detected.major !== required.major) return detected.major > required.major;
|
|
34179
34299
|
return detected.minor >= required.minor;
|
|
34180
34300
|
};
|
|
34301
|
+
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
34181
34302
|
var InvalidGlobPatternError = class extends Error {
|
|
34182
34303
|
pattern;
|
|
34183
34304
|
reason;
|
|
@@ -34206,7 +34327,7 @@ const compileGlobPattern = (rawPattern) => {
|
|
|
34206
34327
|
try {
|
|
34207
34328
|
return import_picomatch.default.makeRe(normalizeGlobPattern(rawPattern), PICOMATCH_OPTIONS);
|
|
34208
34329
|
} catch (caughtError) {
|
|
34209
|
-
throw new InvalidGlobPatternError(rawPattern,
|
|
34330
|
+
throw new InvalidGlobPatternError(rawPattern, messageFromUnknown(caughtError));
|
|
34210
34331
|
}
|
|
34211
34332
|
};
|
|
34212
34333
|
const compileGlobPatternsLenient = (patterns, onInvalid) => {
|
|
@@ -34302,115 +34423,6 @@ const buildRuleSeverityControls = (config) => {
|
|
|
34302
34423
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
34303
34424
|
};
|
|
34304
34425
|
};
|
|
34305
|
-
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34306
|
-
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34307
|
-
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
34308
|
-
let stringDelimiter = null;
|
|
34309
|
-
for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
|
|
34310
|
-
const character = line[charIndex];
|
|
34311
|
-
if (stringDelimiter !== null) {
|
|
34312
|
-
if (character === "\\") {
|
|
34313
|
-
charIndex++;
|
|
34314
|
-
continue;
|
|
34315
|
-
}
|
|
34316
|
-
if (character === stringDelimiter) stringDelimiter = null;
|
|
34317
|
-
continue;
|
|
34318
|
-
}
|
|
34319
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
34320
|
-
stringDelimiter = character;
|
|
34321
|
-
continue;
|
|
34322
|
-
}
|
|
34323
|
-
if (character === "/" && line[charIndex + 1] === "/") return true;
|
|
34324
|
-
}
|
|
34325
|
-
return false;
|
|
34326
|
-
};
|
|
34327
|
-
const findOpenerTagOnLine = (line) => {
|
|
34328
|
-
for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
|
|
34329
|
-
if (match.index === void 0) continue;
|
|
34330
|
-
if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
|
|
34331
|
-
}
|
|
34332
|
-
return null;
|
|
34333
|
-
};
|
|
34334
|
-
const findJsxOpenerSpan = (lines, openerLineIndex) => {
|
|
34335
|
-
const openerLine = lines[openerLineIndex];
|
|
34336
|
-
if (openerLine === void 0) return null;
|
|
34337
|
-
const opener = findOpenerTagOnLine(openerLine);
|
|
34338
|
-
if (!opener) return null;
|
|
34339
|
-
const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
|
|
34340
|
-
let braceDepth = 0;
|
|
34341
|
-
let innerAngleDepth = 0;
|
|
34342
|
-
let stringDelimiter = null;
|
|
34343
|
-
for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
|
|
34344
|
-
const currentLine = lines[lineIndex];
|
|
34345
|
-
const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
|
|
34346
|
-
for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
|
|
34347
|
-
const character = currentLine[charIndex];
|
|
34348
|
-
if (stringDelimiter !== null) {
|
|
34349
|
-
if (character === "\\") {
|
|
34350
|
-
charIndex++;
|
|
34351
|
-
continue;
|
|
34352
|
-
}
|
|
34353
|
-
if (character === stringDelimiter) stringDelimiter = null;
|
|
34354
|
-
continue;
|
|
34355
|
-
}
|
|
34356
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
34357
|
-
stringDelimiter = character;
|
|
34358
|
-
continue;
|
|
34359
|
-
}
|
|
34360
|
-
if (character === "{") {
|
|
34361
|
-
braceDepth++;
|
|
34362
|
-
continue;
|
|
34363
|
-
}
|
|
34364
|
-
if (character === "}") {
|
|
34365
|
-
braceDepth--;
|
|
34366
|
-
continue;
|
|
34367
|
-
}
|
|
34368
|
-
if (braceDepth !== 0) continue;
|
|
34369
|
-
if (character === "<") {
|
|
34370
|
-
const followCharacter = currentLine[charIndex + 1];
|
|
34371
|
-
if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
|
|
34372
|
-
continue;
|
|
34373
|
-
}
|
|
34374
|
-
if (character !== ">") continue;
|
|
34375
|
-
const previousCharacter = currentLine[charIndex - 1];
|
|
34376
|
-
const nextCharacter = currentLine[charIndex + 1];
|
|
34377
|
-
if (previousCharacter === "=" || nextCharacter === "=") continue;
|
|
34378
|
-
if (innerAngleDepth > 0) {
|
|
34379
|
-
innerAngleDepth--;
|
|
34380
|
-
continue;
|
|
34381
|
-
}
|
|
34382
|
-
return lineIndex;
|
|
34383
|
-
}
|
|
34384
|
-
}
|
|
34385
|
-
return null;
|
|
34386
|
-
};
|
|
34387
|
-
const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
|
|
34388
|
-
for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
|
|
34389
|
-
const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
|
|
34390
|
-
if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
|
|
34391
|
-
}
|
|
34392
|
-
return null;
|
|
34393
|
-
};
|
|
34394
|
-
const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
34395
|
-
const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
|
|
34396
|
-
const collected = [];
|
|
34397
|
-
let isStillInChain = true;
|
|
34398
|
-
for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
|
|
34399
|
-
const candidateLine = lines[candidateIndex];
|
|
34400
|
-
if (candidateLine === void 0) break;
|
|
34401
|
-
const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
|
|
34402
|
-
if (match) {
|
|
34403
|
-
collected.push({
|
|
34404
|
-
commentLineIndex: candidateIndex,
|
|
34405
|
-
ruleList: match[1],
|
|
34406
|
-
isInChain: isStillInChain
|
|
34407
|
-
});
|
|
34408
|
-
continue;
|
|
34409
|
-
}
|
|
34410
|
-
isStillInChain = false;
|
|
34411
|
-
}
|
|
34412
|
-
return collected;
|
|
34413
|
-
};
|
|
34414
34426
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
34415
34427
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
34416
34428
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -34535,7 +34547,13 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
|
|
|
34535
34547
|
}
|
|
34536
34548
|
const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
|
|
34537
34549
|
const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
|
|
34538
|
-
const
|
|
34550
|
+
const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
|
|
34551
|
+
const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
|
|
34552
|
+
const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
|
|
34553
|
+
const canonicalTarget = canonicalizeRuleKey(targetRuleKey);
|
|
34554
|
+
if (canonicalCandidate === canonicalTarget) return true;
|
|
34555
|
+
return isReactDoctorShortIdOf(canonicalCandidate, canonicalTarget) || isReactDoctorShortIdOf(canonicalTarget, canonicalCandidate);
|
|
34556
|
+
};
|
|
34539
34557
|
const getEquivalentRuleKeys = (ruleKey) => {
|
|
34540
34558
|
const nativeRuleKey = canonicalizeRuleKey(ruleKey);
|
|
34541
34559
|
return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
|
|
@@ -34545,12 +34563,182 @@ const stripDescriptionTail = (ruleList) => {
|
|
|
34545
34563
|
if (!descriptionMatch || descriptionMatch.index === void 0) return ruleList;
|
|
34546
34564
|
return ruleList.slice(0, descriptionMatch.index);
|
|
34547
34565
|
};
|
|
34548
|
-
const
|
|
34566
|
+
const tokenizeRuleList = (ruleList) => {
|
|
34549
34567
|
const trimmed = ruleList?.trim();
|
|
34550
|
-
if (!trimmed) return
|
|
34568
|
+
if (!trimmed) return [];
|
|
34551
34569
|
const ruleSection = stripDescriptionTail(trimmed).trim();
|
|
34552
|
-
if (!ruleSection) return
|
|
34553
|
-
return ruleSection.split(/[,\s]+/).
|
|
34570
|
+
if (!ruleSection) return [];
|
|
34571
|
+
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
34572
|
+
};
|
|
34573
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34574
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34575
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34576
|
+
const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` — change it to \`${ruleId}\`.`;
|
|
34577
|
+
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34578
|
+
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34579
|
+
const candidates = [{
|
|
34580
|
+
line: lines[diagnosticLineIndex],
|
|
34581
|
+
requiredScope: "line"
|
|
34582
|
+
}, {
|
|
34583
|
+
line: lines[diagnosticLineIndex - 1],
|
|
34584
|
+
requiredScope: "next-line"
|
|
34585
|
+
}];
|
|
34586
|
+
for (const { line, requiredScope } of candidates) {
|
|
34587
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
34588
|
+
if (!match) continue;
|
|
34589
|
+
const [, tool, scope, ruleList] = match;
|
|
34590
|
+
if (scope !== requiredScope) continue;
|
|
34591
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
34592
|
+
if (tokens.includes(ruleId)) continue;
|
|
34593
|
+
for (const token of tokens) if (tokenMisnamesRule(token, ruleId)) return buildHint(tool, token, ruleId);
|
|
34594
|
+
}
|
|
34595
|
+
return null;
|
|
34596
|
+
};
|
|
34597
|
+
const detectBlockNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34598
|
+
let openMisname = null;
|
|
34599
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
34600
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
34601
|
+
const line = lines[lineIndex];
|
|
34602
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
34603
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
34604
|
+
if (disableMatch) {
|
|
34605
|
+
const [, tool, ruleList] = disableMatch;
|
|
34606
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
34607
|
+
if (tokens.includes(ruleId)) openMisname = null;
|
|
34608
|
+
else {
|
|
34609
|
+
const misnamed = tokens.find((token) => tokenMisnamesRule(token, ruleId));
|
|
34610
|
+
if (misnamed) openMisname = {
|
|
34611
|
+
tool,
|
|
34612
|
+
token: misnamed
|
|
34613
|
+
};
|
|
34614
|
+
}
|
|
34615
|
+
continue;
|
|
34616
|
+
}
|
|
34617
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
34618
|
+
if (enableMatch) {
|
|
34619
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
34620
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => isSameRuleKey(rule, ruleId))) openMisname = null;
|
|
34621
|
+
}
|
|
34622
|
+
}
|
|
34623
|
+
return openMisname ? buildHint(openMisname.tool, openMisname.token, ruleId) : null;
|
|
34624
|
+
};
|
|
34625
|
+
const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34626
|
+
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
34627
|
+
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
34628
|
+
};
|
|
34629
|
+
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34630
|
+
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34631
|
+
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
34632
|
+
let stringDelimiter = null;
|
|
34633
|
+
for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
|
|
34634
|
+
const character = line[charIndex];
|
|
34635
|
+
if (stringDelimiter !== null) {
|
|
34636
|
+
if (character === "\\") {
|
|
34637
|
+
charIndex++;
|
|
34638
|
+
continue;
|
|
34639
|
+
}
|
|
34640
|
+
if (character === stringDelimiter) stringDelimiter = null;
|
|
34641
|
+
continue;
|
|
34642
|
+
}
|
|
34643
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
34644
|
+
stringDelimiter = character;
|
|
34645
|
+
continue;
|
|
34646
|
+
}
|
|
34647
|
+
if (character === "/" && line[charIndex + 1] === "/") return true;
|
|
34648
|
+
}
|
|
34649
|
+
return false;
|
|
34650
|
+
};
|
|
34651
|
+
const findOpenerTagOnLine = (line) => {
|
|
34652
|
+
for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
|
|
34653
|
+
if (match.index === void 0) continue;
|
|
34654
|
+
if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
|
|
34655
|
+
}
|
|
34656
|
+
return null;
|
|
34657
|
+
};
|
|
34658
|
+
const findJsxOpenerSpan = (lines, openerLineIndex) => {
|
|
34659
|
+
const openerLine = lines[openerLineIndex];
|
|
34660
|
+
if (openerLine === void 0) return null;
|
|
34661
|
+
const opener = findOpenerTagOnLine(openerLine);
|
|
34662
|
+
if (!opener) return null;
|
|
34663
|
+
const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
|
|
34664
|
+
let braceDepth = 0;
|
|
34665
|
+
let innerAngleDepth = 0;
|
|
34666
|
+
let stringDelimiter = null;
|
|
34667
|
+
for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
|
|
34668
|
+
const currentLine = lines[lineIndex];
|
|
34669
|
+
const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
|
|
34670
|
+
for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
|
|
34671
|
+
const character = currentLine[charIndex];
|
|
34672
|
+
if (stringDelimiter !== null) {
|
|
34673
|
+
if (character === "\\") {
|
|
34674
|
+
charIndex++;
|
|
34675
|
+
continue;
|
|
34676
|
+
}
|
|
34677
|
+
if (character === stringDelimiter) stringDelimiter = null;
|
|
34678
|
+
continue;
|
|
34679
|
+
}
|
|
34680
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
34681
|
+
stringDelimiter = character;
|
|
34682
|
+
continue;
|
|
34683
|
+
}
|
|
34684
|
+
if (character === "{") {
|
|
34685
|
+
braceDepth++;
|
|
34686
|
+
continue;
|
|
34687
|
+
}
|
|
34688
|
+
if (character === "}") {
|
|
34689
|
+
braceDepth--;
|
|
34690
|
+
continue;
|
|
34691
|
+
}
|
|
34692
|
+
if (braceDepth !== 0) continue;
|
|
34693
|
+
if (character === "<") {
|
|
34694
|
+
const followCharacter = currentLine[charIndex + 1];
|
|
34695
|
+
if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
|
|
34696
|
+
continue;
|
|
34697
|
+
}
|
|
34698
|
+
if (character !== ">") continue;
|
|
34699
|
+
const previousCharacter = currentLine[charIndex - 1];
|
|
34700
|
+
const nextCharacter = currentLine[charIndex + 1];
|
|
34701
|
+
if (previousCharacter === "=" || nextCharacter === "=") continue;
|
|
34702
|
+
if (innerAngleDepth > 0) {
|
|
34703
|
+
innerAngleDepth--;
|
|
34704
|
+
continue;
|
|
34705
|
+
}
|
|
34706
|
+
return lineIndex;
|
|
34707
|
+
}
|
|
34708
|
+
}
|
|
34709
|
+
return null;
|
|
34710
|
+
};
|
|
34711
|
+
const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
|
|
34712
|
+
for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
|
|
34713
|
+
const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
|
|
34714
|
+
if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
|
|
34715
|
+
}
|
|
34716
|
+
return null;
|
|
34717
|
+
};
|
|
34718
|
+
const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
34719
|
+
const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
|
|
34720
|
+
const collected = [];
|
|
34721
|
+
let isStillInChain = true;
|
|
34722
|
+
for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
|
|
34723
|
+
const candidateLine = lines[candidateIndex];
|
|
34724
|
+
if (candidateLine === void 0) break;
|
|
34725
|
+
const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
|
|
34726
|
+
if (match) {
|
|
34727
|
+
collected.push({
|
|
34728
|
+
commentLineIndex: candidateIndex,
|
|
34729
|
+
ruleList: match[1],
|
|
34730
|
+
isInChain: isStillInChain
|
|
34731
|
+
});
|
|
34732
|
+
continue;
|
|
34733
|
+
}
|
|
34734
|
+
isStillInChain = false;
|
|
34735
|
+
}
|
|
34736
|
+
return collected;
|
|
34737
|
+
};
|
|
34738
|
+
const isRuleListedInComment = (ruleList, ruleId) => {
|
|
34739
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
34740
|
+
if (tokens.length === 0) return true;
|
|
34741
|
+
return tokens.some((token) => isSameRuleKey(token, ruleId));
|
|
34554
34742
|
};
|
|
34555
34743
|
const DISABLE_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
34556
34744
|
const formatLineGap = (gapLineCount) => `${gapLineCount} line${gapLineCount === 1 ? "" : "s"}`;
|
|
@@ -34594,7 +34782,7 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34594
34782
|
};
|
|
34595
34783
|
return {
|
|
34596
34784
|
isSuppressed: false,
|
|
34597
|
-
nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId)
|
|
34785
|
+
nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId) ?? detectForeignDisableNearMiss(lines, diagnosticLineIndex, ruleId)
|
|
34598
34786
|
};
|
|
34599
34787
|
};
|
|
34600
34788
|
/**
|
|
@@ -35020,6 +35208,11 @@ var OxlintBatchExceeded = class extends TaggedErrorClass()("OxlintBatchExceeded"
|
|
|
35020
35208
|
}
|
|
35021
35209
|
}
|
|
35022
35210
|
};
|
|
35211
|
+
var ScanDeadlineExceeded = class extends TaggedErrorClass()("ScanDeadlineExceeded", { detail: String$1 }) {
|
|
35212
|
+
get message() {
|
|
35213
|
+
return `Scan exceeded its overall time budget: ${this.detail}`;
|
|
35214
|
+
}
|
|
35215
|
+
};
|
|
35023
35216
|
var OxlintSpawnFailed = class extends TaggedErrorClass()("OxlintSpawnFailed", { cause: Unknown }) {
|
|
35024
35217
|
get message() {
|
|
35025
35218
|
return `Failed to run oxlint: ${pretty(fail$6(this.cause))}`;
|
|
@@ -35083,6 +35276,7 @@ var GitBaseBranchInvalid = class extends TaggedErrorClass()("GitBaseBranchInvali
|
|
|
35083
35276
|
const ReactDoctorErrorReason = Union([
|
|
35084
35277
|
OxlintUnavailable,
|
|
35085
35278
|
OxlintBatchExceeded,
|
|
35279
|
+
ScanDeadlineExceeded,
|
|
35086
35280
|
OxlintSpawnFailed,
|
|
35087
35281
|
OxlintOutputUnparseable,
|
|
35088
35282
|
ConfigParseFailed,
|
|
@@ -35133,15 +35327,105 @@ const layerOtlp = unwrap$3(gen(function* () {
|
|
|
35133
35327
|
}).pipe(provide$2(layer$8));
|
|
35134
35328
|
}).pipe(orDie));
|
|
35135
35329
|
/**
|
|
35136
|
-
*
|
|
35137
|
-
* `
|
|
35138
|
-
|
|
35330
|
+
* Read a positive-millisecond timeout from an env var, falling back to
|
|
35331
|
+
* `defaultMs` when the var is unset, non-finite, or not strictly positive.
|
|
35332
|
+
*/
|
|
35333
|
+
const readPositiveEnvMs = (envVarName, defaultMs) => {
|
|
35334
|
+
const rawValue = process.env[envVarName];
|
|
35335
|
+
if (rawValue === void 0) return defaultMs;
|
|
35336
|
+
const parsedValue = Number(rawValue);
|
|
35337
|
+
if (!Number.isFinite(parsedValue) || parsedValue <= 0) return defaultMs;
|
|
35338
|
+
return parsedValue;
|
|
35339
|
+
};
|
|
35340
|
+
const CGROUP_V2_MEMORY_MAX_PATH = "/sys/fs/cgroup/memory.max";
|
|
35341
|
+
const CGROUP_V1_MEMORY_LIMIT_PATH = "/sys/fs/cgroup/memory/memory.limit_in_bytes";
|
|
35342
|
+
const CGROUP_UNLIMITED_SENTINEL_BYTES = Number.MAX_SAFE_INTEGER;
|
|
35343
|
+
/**
|
|
35344
|
+
* Parses one raw cgroup memory-limit file value into a positive byte count, or
|
|
35345
|
+
* `undefined` when it represents "no limit" (the v2 `"max"` literal, an empty
|
|
35346
|
+
* read, a non-positive / non-finite value, or v1's near-2^63 unlimited
|
|
35347
|
+
* sentinel). Pure and exported so the classification is unit-testable without
|
|
35348
|
+
* touching the filesystem.
|
|
35349
|
+
*/
|
|
35350
|
+
const parseCgroupMemoryLimitBytes = (raw) => {
|
|
35351
|
+
if (raw === void 0) return void 0;
|
|
35352
|
+
const trimmed = raw.trim();
|
|
35353
|
+
if (trimmed === "" || trimmed === "max") return void 0;
|
|
35354
|
+
const parsed = Number(trimmed);
|
|
35355
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || parsed >= CGROUP_UNLIMITED_SENTINEL_BYTES) return;
|
|
35356
|
+
return parsed;
|
|
35357
|
+
};
|
|
35358
|
+
const CGROUP_MEMORY_LIMIT_PATHS = [CGROUP_V2_MEMORY_MAX_PATH, CGROUP_V1_MEMORY_LIMIT_PATH];
|
|
35359
|
+
/**
|
|
35360
|
+
* Reads this process's cgroup memory limit in bytes from the first candidate
|
|
35361
|
+
* path that yields a real limit, or `undefined` when none does — no cgroup, no
|
|
35362
|
+
* limit, or the files are unreadable (e.g. macOS / Windows dev machines).
|
|
35363
|
+
* `os.totalmem()` reports the HOST total and ignores cgroup memory limits, so a
|
|
35364
|
+
* memory-constrained container over-reports total memory; `resolveAutoScan-
|
|
35365
|
+
* Concurrency` takes `min(totalmem, this)` to honor the limit.
|
|
35366
|
+
*
|
|
35367
|
+
* The cgroup v2 read is the mount-root `memory.max`, which IS the container's
|
|
35368
|
+
* limit under the standard cgroup-namespace setup CI runners use (the
|
|
35369
|
+
* container's own cgroup is the root of its namespaced view). A process in a
|
|
35370
|
+
* non-namespaced nested/delegated cgroup whose root reads `"max"` is not
|
|
35371
|
+
* detected here and falls back to the host total; the EAGAIN/ENOMEM serial
|
|
35372
|
+
* replay in `spawnLintBatches` remains the runtime backstop for that case.
|
|
35373
|
+
*
|
|
35374
|
+
* `candidatePaths` is injectable so tests exercise the v2-wins-over-v1
|
|
35375
|
+
* precedence, the skip-unreadable fallback, and the all-missing case without a
|
|
35376
|
+
* real `/sys/fs/cgroup`.
|
|
35377
|
+
*/
|
|
35378
|
+
const readCgroupMemoryLimitBytes = (candidatePaths = CGROUP_MEMORY_LIMIT_PATHS) => {
|
|
35379
|
+
for (const limitPath of candidatePaths) {
|
|
35380
|
+
let raw;
|
|
35381
|
+
try {
|
|
35382
|
+
raw = fs.readFileSync(limitPath, "utf8");
|
|
35383
|
+
} catch {
|
|
35384
|
+
continue;
|
|
35385
|
+
}
|
|
35386
|
+
const limitBytes = parseCgroupMemoryLimitBytes(raw);
|
|
35387
|
+
if (limitBytes !== void 0) return limitBytes;
|
|
35388
|
+
}
|
|
35389
|
+
};
|
|
35390
|
+
/**
|
|
35391
|
+
* Clamps a requested lint worker count to `[MIN_SCAN_CONCURRENCY,
|
|
35392
|
+
* HARD_MAX_SCAN_CONCURRENCY]` as a finite integer. This is the explicit-pin and
|
|
35393
|
+
* spawn-boundary clamp — the memory-and-core-budgeted auto count comes from
|
|
35394
|
+
* `resolveAutoScanConcurrency`. Out-of-range or non-finite requests degrade to
|
|
35139
35395
|
* `MIN_SCAN_CONCURRENCY` rather than oversubscribing or running zero workers.
|
|
35140
35396
|
*/
|
|
35141
35397
|
const resolveScanConcurrency = (requested) => {
|
|
35142
|
-
|
|
35143
|
-
|
|
35144
|
-
|
|
35398
|
+
if (!Number.isFinite(requested) || requested < 1) return 1;
|
|
35399
|
+
return Math.min(Math.floor(requested), 32);
|
|
35400
|
+
};
|
|
35401
|
+
const readSystemFacts = () => ({
|
|
35402
|
+
availableCores: os.availableParallelism(),
|
|
35403
|
+
totalMemoryBytes: os.totalmem(),
|
|
35404
|
+
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
35405
|
+
});
|
|
35406
|
+
/**
|
|
35407
|
+
* Auto lint-worker count: the smaller of the (cgroup-CPU-aware) core count and
|
|
35408
|
+
* the number of `PER_WORKER_MEM_BUDGET_BYTES` workers that fit in available
|
|
35409
|
+
* memory, then clamped to `[MIN, HARD_MAX]` by `resolveScanConcurrency`.
|
|
35410
|
+
*
|
|
35411
|
+
* `os.availableParallelism()` already respects cgroup CPU quotas, so the core
|
|
35412
|
+
* term needs no help. Available memory is `os.totalmem()` floored by the cgroup
|
|
35413
|
+
* memory limit — `os.freemem()` is deliberately NOT used: it excludes
|
|
35414
|
+
* reclaimable page cache and reads near-zero on macOS / cache-heavy Linux, which
|
|
35415
|
+
* would collapse the auto path to a single worker. `os.totalmem()` reports the
|
|
35416
|
+
* host total even inside a container, so the cgroup limit (read directly,
|
|
35417
|
+
* because Node doesn't fold it into `totalmem()`) is the real ceiling there.
|
|
35418
|
+
*
|
|
35419
|
+
* `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
|
|
35420
|
+
* limited, and ceiling cases without mocking `os` or the filesystem.
|
|
35421
|
+
*/
|
|
35422
|
+
const resolveAutoScanConcurrency = (facts = readSystemFacts()) => {
|
|
35423
|
+
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
35424
|
+
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
|
|
35425
|
+
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
35426
|
+
};
|
|
35427
|
+
const resolveLintBatchOrdering = () => {
|
|
35428
|
+
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "cost" ? "cost" : "arrival";
|
|
35145
35429
|
};
|
|
35146
35430
|
/**
|
|
35147
35431
|
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
@@ -35149,11 +35433,38 @@ const resolveScanConcurrency = (requested) => {
|
|
|
35149
35433
|
* microVMs without recompiling react-doctor. Tests override via
|
|
35150
35434
|
* `Layer.succeed(OxlintSpawnTimeoutMs, ...)`.
|
|
35151
35435
|
*/
|
|
35152
|
-
var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => {
|
|
35153
|
-
|
|
35154
|
-
|
|
35436
|
+
var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS", OXLINT_SPAWN_TIMEOUT_MS) }) {};
|
|
35437
|
+
/**
|
|
35438
|
+
* Effect-side cap on the lint phase. The env var lets CI / eval runners
|
|
35439
|
+
* raise the phase budget for slow large repos without recompiling.
|
|
35440
|
+
* Tests override via `Layer.succeed(LintPhaseTimeoutMs, ...)`.
|
|
35441
|
+
*/
|
|
35442
|
+
var LintPhaseTimeoutMs = class extends Reference("react-doctor/LintPhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_LINT_PHASE_TIMEOUT_MS", LINT_PHASE_TIMEOUT_MS) }) {};
|
|
35443
|
+
/**
|
|
35444
|
+
* Effect-side cap on the dead-code phase, sitting above the in-worker
|
|
35445
|
+
* timeout as a runtime-independent backstop. The env var raises it for
|
|
35446
|
+
* type-heavy projects; tests override via
|
|
35447
|
+
* `Layer.succeed(DeadCodePhaseTimeoutMs, ...)`.
|
|
35448
|
+
*/
|
|
35449
|
+
var DeadCodePhaseTimeoutMs = class extends Reference("react-doctor/DeadCodePhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_DEAD_CODE_PHASE_TIMEOUT_MS", DEAD_CODE_PHASE_TIMEOUT_MS) }) {};
|
|
35450
|
+
/**
|
|
35451
|
+
* Overall scan deadline backstop, bounding everything the per-phase
|
|
35452
|
+
* timeouts don't (wedged git / IO). The env var raises it for very
|
|
35453
|
+
* large repos; tests override via `Layer.succeed(ScanDeadlineMs, ...)`.
|
|
35454
|
+
*/
|
|
35455
|
+
var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SCAN_DEADLINE_MS", SCAN_TOTAL_DEADLINE_MS) }) {};
|
|
35456
|
+
/**
|
|
35457
|
+
* Wall-clock budget for the supply-chain check when it runs on a background
|
|
35458
|
+
* fiber overlapping the lint pass. Reads from the env var on startup so the
|
|
35459
|
+
* eval harness can raise the budget under sandbox microVMs (slower network)
|
|
35460
|
+
* without recompiling react-doctor. Tests override via
|
|
35461
|
+
* `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
|
|
35462
|
+
*/
|
|
35463
|
+
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
|
|
35464
|
+
const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
|
|
35465
|
+
if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35155
35466
|
const parsed = Number(raw);
|
|
35156
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return
|
|
35467
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35157
35468
|
return parsed;
|
|
35158
35469
|
} }) {};
|
|
35159
35470
|
/**
|
|
@@ -35164,31 +35475,93 @@ var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTime
|
|
|
35164
35475
|
*/
|
|
35165
35476
|
var OxlintOutputMaxBytes = class extends Reference("react-doctor/OxlintOutputMaxBytes", { defaultValue: () => OXLINT_OUTPUT_MAX_BYTES }) {};
|
|
35166
35477
|
/**
|
|
35167
|
-
* Number of oxlint subprocesses the lint pass runs in parallel. Defaults
|
|
35168
|
-
*
|
|
35169
|
-
* the box
|
|
35170
|
-
*
|
|
35171
|
-
*
|
|
35172
|
-
*
|
|
35173
|
-
*
|
|
35174
|
-
*
|
|
35175
|
-
*
|
|
35176
|
-
*
|
|
35478
|
+
* Number of oxlint subprocesses the lint pass runs in parallel. Defaults to a
|
|
35479
|
+
* memory-and-core-budgeted auto count (`resolveAutoScanConcurrency`) so large
|
|
35480
|
+
* repos scan fast out of the box without OOMing the native binding on a
|
|
35481
|
+
* high-core / low-memory box; `spawnLintBatches` transparently falls back to a
|
|
35482
|
+
* single worker if a parallel run still exhausts system resources. The CLI's
|
|
35483
|
+
* `--no-parallel` flag forces serial via `Layer.succeed`; the
|
|
35484
|
+
* `REACT_DOCTOR_PARALLEL` env var seeds the default for programmatic / CI
|
|
35485
|
+
* callers that never touch the flag — parallelism is opt-OUT, so only the
|
|
35486
|
+
* explicit serial values pin one worker:
|
|
35487
|
+
*
|
|
35488
|
+
* - unset / `auto` / `true` / `on` → memory-and-core-budgeted auto count
|
|
35177
35489
|
* - `0` / `false` / `off` → `1` (serial)
|
|
35178
35490
|
* - a positive integer → that many workers (clamped)
|
|
35179
|
-
* - any other value →
|
|
35491
|
+
* - any other value → memory-and-core-budgeted auto count
|
|
35180
35492
|
*
|
|
35181
35493
|
* The resolved value is always within
|
|
35182
|
-
* `[MIN_SCAN_CONCURRENCY,
|
|
35494
|
+
* `[MIN_SCAN_CONCURRENCY, HARD_MAX_SCAN_CONCURRENCY]`.
|
|
35183
35495
|
*/
|
|
35184
35496
|
var OxlintConcurrency = class extends Reference("react-doctor/OxlintConcurrency", { defaultValue: () => {
|
|
35185
35497
|
const raw = process.env["REACT_DOCTOR_PARALLEL"];
|
|
35186
|
-
if (raw === void 0) return
|
|
35498
|
+
if (raw === void 0) return resolveAutoScanConcurrency();
|
|
35187
35499
|
const normalized = raw.trim().toLowerCase();
|
|
35188
35500
|
if (normalized === "0" || normalized === "false" || normalized === "off") return 1;
|
|
35189
35501
|
const parsed = Number.parseInt(normalized, 10);
|
|
35190
35502
|
if (Number.isInteger(parsed) && parsed > 0) return resolveScanConcurrency(parsed);
|
|
35191
|
-
return
|
|
35503
|
+
return resolveAutoScanConcurrency();
|
|
35504
|
+
} }) {};
|
|
35505
|
+
/**
|
|
35506
|
+
* Three-state control for overlapping the dead-code pass with the lint pass —
|
|
35507
|
+
* forking dead-code as a child fiber that runs DURING lint instead of strictly
|
|
35508
|
+
* after it.
|
|
35509
|
+
*
|
|
35510
|
+
* - `"auto"` (default) / `"off"` → strictly SEQUENTIAL: dead-code runs after
|
|
35511
|
+
* lint with the full core budget. Both deslop's parse pool and the oxlint
|
|
35512
|
+
* pool are CPU-bound and each size themselves to all cores, so overlapping
|
|
35513
|
+
* them only oversubscribes (~2x the cores) and starves the parse pass past
|
|
35514
|
+
* its timeout — for no wall-clock win, since there are no spare cores to
|
|
35515
|
+
* absorb the second pass. Sequential is both faster per-phase and safe.
|
|
35516
|
+
* - `"on"` → force the overlap anyway. The orchestrator then SPLITS the core
|
|
35517
|
+
* budget (`DEAD_CODE_OVERLAP_PARSE_SHARE`): deslop's parse pool is capped
|
|
35518
|
+
* and lint shrinks to the remainder, so the two sum to the cores instead of
|
|
35519
|
+
* doubling them, and the dead-code timeout scales up for the reduced share.
|
|
35520
|
+
*
|
|
35521
|
+
* Seeded from `REACT_DOCTOR_DEAD_CODE_OVERLAP` so operators get a redeploy-free
|
|
35522
|
+
* switch; tests pin it via `Layer.succeed(DeadCodeOverlap, ...)`.
|
|
35523
|
+
*/
|
|
35524
|
+
var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", { defaultValue: () => {
|
|
35525
|
+
const raw = process.env["REACT_DOCTOR_DEAD_CODE_OVERLAP"]?.trim().toLowerCase();
|
|
35526
|
+
if (raw === "on" || raw === "true" || raw === "1") return "on";
|
|
35527
|
+
if (raw === "off" || raw === "false" || raw === "0") return "off";
|
|
35528
|
+
return "auto";
|
|
35529
|
+
} }) {};
|
|
35530
|
+
/**
|
|
35531
|
+
* How the full-scan lint pass orders its file batches. `"arrival"` (the
|
|
35532
|
+
* default) keeps `git ls-files` discovery order. `"cost"` opts into LPT (feed
|
|
35533
|
+
* the largest files first); set `REACT_DOCTOR_LINT_BATCH_ORDERING=cost`. NOTE:
|
|
35534
|
+
* `cost` is OFF by default because the current sort-desc-then-chunk-100 packs
|
|
35535
|
+
* the heaviest files into one wave-1 batch — on size-skewed repos that mega-
|
|
35536
|
+
* batch is a straggler (and can trip the per-batch timeout + split), measurably
|
|
35537
|
+
* regressing the common full-scan case. LPT needs the heavy files SPREAD across
|
|
35538
|
+
* batches before `cost` earns the default. Tests override via
|
|
35539
|
+
* `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans never reach this
|
|
35540
|
+
* — they pass user-scoped `includePaths` that skip discovery and stay in
|
|
35541
|
+
* arrival order; only the full-scan branch reads it.
|
|
35542
|
+
*/
|
|
35543
|
+
var LintBatchOrdering = class extends Reference("react-doctor/LintBatchOrdering", { defaultValue: resolveLintBatchOrdering }) {};
|
|
35544
|
+
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
35545
|
+
/**
|
|
35546
|
+
* Whether the per-file lint cache (`runners/oxlint/file-lint-cache.ts`) is
|
|
35547
|
+
* active. Defaults ON — repeat scans re-lint only the files whose content
|
|
35548
|
+
* changed, and correctness is guaranteed byte-identical to a cold scan by the
|
|
35549
|
+
* always-fresh cross-file sidecar. Opt-OUT, two knobs (matching the whole-repo
|
|
35550
|
+
* scan cache's `REACT_DOCTOR_NO_CACHE`):
|
|
35551
|
+
*
|
|
35552
|
+
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch; disables BOTH the
|
|
35553
|
+
* whole-repo scan cache and this per-file cache.
|
|
35554
|
+
* - `REACT_DOCTOR_NO_FILE_CACHE` — granular: bust only the per-file cache
|
|
35555
|
+
* while keeping the whole-repo short-circuit.
|
|
35556
|
+
*
|
|
35557
|
+
* Tests override via `Layer.succeed(PerFileLintCacheEnabled, false)`.
|
|
35558
|
+
*/
|
|
35559
|
+
var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintCacheEnabled", { defaultValue: () => {
|
|
35560
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35561
|
+
const noFileCache = process.env["REACT_DOCTOR_NO_FILE_CACHE"]?.toLowerCase() ?? "";
|
|
35562
|
+
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35563
|
+
if (CACHE_DISABLED_VALUES.has(noFileCache)) return false;
|
|
35564
|
+
return true;
|
|
35192
35565
|
} }) {};
|
|
35193
35566
|
const DIAGNOSTIC_SURFACES = [
|
|
35194
35567
|
"cli",
|
|
@@ -35362,7 +35735,6 @@ const PACKAGE_JSON_FILENAME = "package.json";
|
|
|
35362
35735
|
const PACKAGE_JSON_CONFIG_KEY = "reactDoctor";
|
|
35363
35736
|
const LEGACY_CONFIG_FILENAME = "react-doctor.config.json";
|
|
35364
35737
|
const jiti = createJiti(import.meta.url);
|
|
35365
|
-
const formatError = (error) => error instanceof Error ? error.message : String(error);
|
|
35366
35738
|
const importDefaultExport = async (jitiInstance, filePath) => {
|
|
35367
35739
|
const imported = await jitiInstance.import(filePath);
|
|
35368
35740
|
return imported?.default ?? imported;
|
|
@@ -35394,7 +35766,7 @@ const loadModuleConfig = async (filePath) => {
|
|
|
35394
35766
|
try {
|
|
35395
35767
|
return await importDefaultExport(aliasJiti, filePath);
|
|
35396
35768
|
} catch (retryError) {
|
|
35397
|
-
throw new Error(`${
|
|
35769
|
+
throw new Error(`${messageFromUnknown(error)} (retry with ${SELF_PACKAGE_IMPORT_SPECIFIER} aliased to the running react-doctor package also failed: ${messageFromUnknown(retryError)})`, { cause: retryError });
|
|
35398
35770
|
}
|
|
35399
35771
|
}
|
|
35400
35772
|
};
|
|
@@ -35443,7 +35815,7 @@ const loadLegacyConfig = (directory) => {
|
|
|
35443
35815
|
}
|
|
35444
35816
|
warn(`${LEGACY_CONFIG_FILENAME} must contain an object, ignoring.`);
|
|
35445
35817
|
} catch (error) {
|
|
35446
|
-
warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${
|
|
35818
|
+
warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${messageFromUnknown(error)}`);
|
|
35447
35819
|
}
|
|
35448
35820
|
return {
|
|
35449
35821
|
status: "invalid",
|
|
@@ -35470,7 +35842,7 @@ const loadConfigFromDirectory = async (directory) => {
|
|
|
35470
35842
|
warn(`${CONFIG_BASENAME}.${extension} must export an object, ignoring.`);
|
|
35471
35843
|
sawBrokenConfigFile = true;
|
|
35472
35844
|
} catch (error) {
|
|
35473
|
-
warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${
|
|
35845
|
+
warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${messageFromUnknown(error)}`);
|
|
35474
35846
|
sawBrokenConfigFile = true;
|
|
35475
35847
|
}
|
|
35476
35848
|
}
|
|
@@ -35524,6 +35896,31 @@ const resolveConfigRootDir = (config, configSourceDirectory) => {
|
|
|
35524
35896
|
}
|
|
35525
35897
|
return resolvedRootDir;
|
|
35526
35898
|
};
|
|
35899
|
+
const buildFixGroupId = (diagnostic) => createHash("sha1").update(JSON.stringify([
|
|
35900
|
+
diagnostic.filePath,
|
|
35901
|
+
`${diagnostic.plugin}/${diagnostic.rule}`,
|
|
35902
|
+
diagnostic.message
|
|
35903
|
+
])).digest("hex").slice(0, 16);
|
|
35904
|
+
const isGroupableRule = (diagnostic) => ROOT_CAUSE_GROUPABLE_RULE_KEYS.has(`${diagnostic.plugin}/${diagnostic.rule}`);
|
|
35905
|
+
const assignFixGroups = (diagnostics) => {
|
|
35906
|
+
const siteCountByGroupId = /* @__PURE__ */ new Map();
|
|
35907
|
+
for (const diagnostic of diagnostics) {
|
|
35908
|
+
if (!isGroupableRule(diagnostic)) continue;
|
|
35909
|
+
const groupId = buildFixGroupId(diagnostic);
|
|
35910
|
+
siteCountByGroupId.set(groupId, (siteCountByGroupId.get(groupId) ?? 0) + 1);
|
|
35911
|
+
}
|
|
35912
|
+
return diagnostics.map((diagnostic) => {
|
|
35913
|
+
if (!isGroupableRule(diagnostic)) return diagnostic;
|
|
35914
|
+
const groupId = buildFixGroupId(diagnostic);
|
|
35915
|
+
if ((siteCountByGroupId.get(groupId) ?? 0) < 2) return diagnostic;
|
|
35916
|
+
return {
|
|
35917
|
+
...diagnostic,
|
|
35918
|
+
fixGroupId: groupId
|
|
35919
|
+
};
|
|
35920
|
+
});
|
|
35921
|
+
};
|
|
35922
|
+
const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
35923
|
+
const sortDiagnosticsStable = (diagnostics) => [...diagnostics].sort((left, right) => compareStrings(left.filePath, right.filePath) || left.line - right.line || left.column - right.column || compareStrings(left.plugin, right.plugin) || compareStrings(left.rule, right.rule) || compareStrings(left.severity, right.severity) || compareStrings(left.message, right.message));
|
|
35527
35924
|
const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
|
|
35528
35925
|
const buildExpoCheckContext = (rootDirectory, expoVersion) => {
|
|
35529
35926
|
const packageJson = readPackageJson(Path.join(rootDirectory, "package.json"));
|
|
@@ -36030,10 +36427,15 @@ const buildHardeningDiagnostic = (input) => ({
|
|
|
36030
36427
|
column: input.column ?? 0,
|
|
36031
36428
|
category: "Security"
|
|
36032
36429
|
});
|
|
36033
|
-
const checkPnpmHardening = (
|
|
36034
|
-
if (!isPnpmManagedProject(
|
|
36035
|
-
const workspacePath = Path.join(
|
|
36036
|
-
const
|
|
36430
|
+
const checkPnpmHardening = (scanDirectory) => {
|
|
36431
|
+
if (!isPnpmManagedProject(scanDirectory)) return [];
|
|
36432
|
+
const workspacePath = Path.join(scanDirectory, PNPM_WORKSPACE_FILE);
|
|
36433
|
+
const hasWorkspaceFile = isFile(workspacePath);
|
|
36434
|
+
if (!hasWorkspaceFile) {
|
|
36435
|
+
const monorepoRoot = findMonorepoRoot(scanDirectory);
|
|
36436
|
+
if (monorepoRoot !== null && isFile(Path.join(monorepoRoot, PNPM_WORKSPACE_FILE))) return [];
|
|
36437
|
+
}
|
|
36438
|
+
const settings = parseHardeningSettings(hasWorkspaceFile ? NFS.readFileSync(workspacePath, "utf-8") : "");
|
|
36037
36439
|
const diagnostics = [];
|
|
36038
36440
|
if (settings.minimumReleaseAge === null) diagnostics.push(buildHardeningDiagnostic({
|
|
36039
36441
|
message: "pnpm-workspace.yaml is missing `minimumReleaseAge` — newly published versions can ship malware that gets caught and unpublished within hours",
|
|
@@ -36650,7 +37052,7 @@ const readIgnoreFile = (filePath) => {
|
|
|
36650
37052
|
try {
|
|
36651
37053
|
content = NFS.readFileSync(filePath, "utf-8");
|
|
36652
37054
|
} catch (error) {
|
|
36653
|
-
const errnoCode = error
|
|
37055
|
+
const errnoCode = isErrnoException(error) ? error.code : void 0;
|
|
36654
37056
|
if (errnoCode && errnoCode !== "ENOENT") runSync(warn$1(`Could not read ignore file ${filePath}: ${errnoCode}`));
|
|
36655
37057
|
return [];
|
|
36656
37058
|
}
|
|
@@ -36691,8 +37093,8 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
36691
37093
|
cachedPatternsByRoot.set(rootDirectory, patterns);
|
|
36692
37094
|
return patterns;
|
|
36693
37095
|
};
|
|
37096
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36694
37097
|
const KNIP_JSON_FILENAME = "knip.json";
|
|
36695
|
-
const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36696
37098
|
const readJsonFileSafe = (filePath) => {
|
|
36697
37099
|
let rawContents;
|
|
36698
37100
|
try {
|
|
@@ -36708,10 +37110,10 @@ const readJsonFileSafe = (filePath) => {
|
|
|
36708
37110
|
};
|
|
36709
37111
|
const readKnipConfig = (rootDirectory) => {
|
|
36710
37112
|
const knipJson = readJsonFileSafe(path.join(rootDirectory, KNIP_JSON_FILENAME));
|
|
36711
|
-
if (isRecord
|
|
37113
|
+
if (isRecord(knipJson)) return knipJson;
|
|
36712
37114
|
const packageJson = readJsonFileSafe(path.join(rootDirectory, "package.json"));
|
|
36713
|
-
const packageKnipConfig = isRecord
|
|
36714
|
-
return isRecord
|
|
37115
|
+
const packageKnipConfig = isRecord(packageJson) ? packageJson.knip : null;
|
|
37116
|
+
return isRecord(packageKnipConfig) ? packageKnipConfig : null;
|
|
36715
37117
|
};
|
|
36716
37118
|
const normalizePatternList = (value) => {
|
|
36717
37119
|
if (typeof value === "string" && value.length > 0) return [value];
|
|
@@ -36723,10 +37125,10 @@ const prefixWorkspacePatterns = (workspacePattern, patterns) => {
|
|
|
36723
37125
|
return patterns.map((pattern) => pattern.startsWith("!") ? `!${normalizedWorkspacePattern}/${pattern.slice(1)}` : `${normalizedWorkspacePattern}/${pattern}`);
|
|
36724
37126
|
};
|
|
36725
37127
|
const collectKnipWorkspacePatterns = (workspaces, settingName) => {
|
|
36726
|
-
if (!isRecord
|
|
37128
|
+
if (!isRecord(workspaces)) return [];
|
|
36727
37129
|
const patterns = [];
|
|
36728
37130
|
for (const [workspacePattern, workspaceConfig] of Object.entries(workspaces)) {
|
|
36729
|
-
if (!isRecord
|
|
37131
|
+
if (!isRecord(workspaceConfig)) continue;
|
|
36730
37132
|
patterns.push(...prefixWorkspacePatterns(workspacePattern, normalizePatternList(workspaceConfig[settingName])));
|
|
36731
37133
|
}
|
|
36732
37134
|
return patterns;
|
|
@@ -36736,12 +37138,11 @@ const collectKnipPatterns = (rootDirectory, settingName) => {
|
|
|
36736
37138
|
if (!config) return [];
|
|
36737
37139
|
return [...normalizePatternList(config[settingName]), ...collectKnipWorkspacePatterns(config.workspaces, settingName)];
|
|
36738
37140
|
};
|
|
36739
|
-
const collectDeadCodeIgnorePatterns = (rootDirectory
|
|
37141
|
+
const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
36740
37142
|
const seen = /* @__PURE__ */ new Set();
|
|
36741
37143
|
const sources = [
|
|
36742
37144
|
readIgnoreFile(path.join(rootDirectory, ".gitignore")),
|
|
36743
37145
|
collectIgnorePatterns(rootDirectory),
|
|
36744
|
-
userConfig?.ignore?.files ?? [],
|
|
36745
37146
|
collectKnipPatterns(rootDirectory, "ignore")
|
|
36746
37147
|
];
|
|
36747
37148
|
for (const source of sources) for (const pattern of source) seen.add(pattern);
|
|
@@ -36772,8 +37173,6 @@ const toCanonicalPath = (filePath) => {
|
|
|
36772
37173
|
};
|
|
36773
37174
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
36774
37175
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
36775
|
-
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
36776
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36777
37176
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
36778
37177
|
const inputChunks = [];
|
|
36779
37178
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -36821,6 +37220,22 @@ process.stdin.on("end", () => {
|
|
|
36821
37220
|
...(workerInput.ignorePatterns.length > 0
|
|
36822
37221
|
? { ignorePatterns: workerInput.ignorePatterns }
|
|
36823
37222
|
: {}),
|
|
37223
|
+
// We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
|
|
37224
|
+
// unusedDependencies, circularDependencies). Everything else deslop can compute
|
|
37225
|
+
// is pure wasted work for us, and it's the bulk of the runtime:
|
|
37226
|
+
// - semantic: a full TS Program for unusedTypes/enum/class-members/
|
|
37227
|
+
// misclassifiedDependencies (~37-45% of the phase).
|
|
37228
|
+
// - reportCodeQuality: the duplicate-block, complexity, feature-flag,
|
|
37229
|
+
// TypeScript-smell, private-type-leak and re-export-cycle detectors. These
|
|
37230
|
+
// are the single most expensive pass — duplicate-block detection alone was
|
|
37231
|
+
// ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
|
|
37232
|
+
// speedup on a large repo.
|
|
37233
|
+
// Both are provably safe: the consumed graph findings are computed by their own
|
|
37234
|
+
// detectors, independent of these passes (confirmed byte-identical on
|
|
37235
|
+
// excalidraw + mui-material + sentry). tsConfigPath stays — the module resolver
|
|
37236
|
+
// needs it for path-alias resolution in the import graph.
|
|
37237
|
+
semantic: { enabled: false },
|
|
37238
|
+
reportCodeQuality: false,
|
|
36824
37239
|
};
|
|
36825
37240
|
const result = await analyze(defineConfig(config));
|
|
36826
37241
|
emit({ ok: true, result: normalizeResult(result) });
|
|
@@ -36831,7 +37246,7 @@ process.stdin.on("end", () => {
|
|
|
36831
37246
|
});
|
|
36832
37247
|
`;
|
|
36833
37248
|
const resolveTsConfigPath = (rootDirectory) => {
|
|
36834
|
-
for (const filename of TSCONFIG_FILENAMES
|
|
37249
|
+
for (const filename of TSCONFIG_FILENAMES) {
|
|
36835
37250
|
const candidate = Path.join(rootDirectory, filename);
|
|
36836
37251
|
if (NFS.existsSync(candidate)) return candidate;
|
|
36837
37252
|
}
|
|
@@ -36950,7 +37365,11 @@ const createDeadCodeWorker = (input) => {
|
|
|
36950
37365
|
"pipe",
|
|
36951
37366
|
"pipe"
|
|
36952
37367
|
],
|
|
36953
|
-
windowsHide: true
|
|
37368
|
+
windowsHide: true,
|
|
37369
|
+
env: input.parseConcurrency === void 0 ? process.env : {
|
|
37370
|
+
...process.env,
|
|
37371
|
+
DESLOP_PARSE_CONCURRENCY: String(input.parseConcurrency)
|
|
37372
|
+
}
|
|
36954
37373
|
});
|
|
36955
37374
|
const stdoutChunks = [];
|
|
36956
37375
|
const stderrChunks = [];
|
|
@@ -36995,42 +37414,39 @@ const createDeadCodeWorker = (input) => {
|
|
|
36995
37414
|
}
|
|
36996
37415
|
};
|
|
36997
37416
|
};
|
|
36998
|
-
const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve, reject) => {
|
|
37417
|
+
const runDeadCodeWorkerWithTimeout = (handle, timeoutMs, abortSignal) => new Promise((resolve, reject) => {
|
|
36999
37418
|
let didSettle = false;
|
|
37000
|
-
const
|
|
37001
|
-
if (didSettle) return;
|
|
37002
|
-
didSettle = true;
|
|
37003
|
-
handle.terminate?.();
|
|
37004
|
-
reject(/* @__PURE__ */ new Error(`Dead-code worker timed out after ${timeoutMs / MILLISECONDS_PER_SECOND}s.`));
|
|
37005
|
-
}, timeoutMs);
|
|
37006
|
-
timeoutHandle.unref?.();
|
|
37007
|
-
handle.result.then((value) => {
|
|
37008
|
-
if (didSettle) return;
|
|
37009
|
-
didSettle = true;
|
|
37010
|
-
clearTimeout(timeoutHandle);
|
|
37011
|
-
handle.terminate?.();
|
|
37012
|
-
resolve(value);
|
|
37013
|
-
}, (error) => {
|
|
37419
|
+
const settle = (finish) => {
|
|
37014
37420
|
if (didSettle) return;
|
|
37015
37421
|
didSettle = true;
|
|
37016
37422
|
clearTimeout(timeoutHandle);
|
|
37423
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
37017
37424
|
handle.terminate?.();
|
|
37018
|
-
|
|
37019
|
-
}
|
|
37425
|
+
finish();
|
|
37426
|
+
};
|
|
37427
|
+
const onAbort = () => settle(() => reject(/* @__PURE__ */ new Error("Dead-code worker aborted.")));
|
|
37428
|
+
const timeoutHandle = setTimeout(() => settle(() => reject(/* @__PURE__ */ new Error(`Dead-code worker timed out after ${timeoutMs / MILLISECONDS_PER_SECOND}s.`))), timeoutMs);
|
|
37429
|
+
timeoutHandle.unref?.();
|
|
37430
|
+
if (abortSignal?.aborted) {
|
|
37431
|
+
onAbort();
|
|
37432
|
+
return;
|
|
37433
|
+
}
|
|
37434
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
37435
|
+
handle.result.then((value) => settle(() => resolve(value)), (error) => settle(() => reject(error)));
|
|
37020
37436
|
});
|
|
37021
37437
|
const checkDeadCode = async (options) => {
|
|
37022
|
-
const { userConfig } = options;
|
|
37023
37438
|
const rootDirectory = toCanonicalPath(options.rootDirectory);
|
|
37024
37439
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37025
37440
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37026
|
-
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory
|
|
37441
|
+
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37027
37442
|
const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37028
37443
|
rootDirectory,
|
|
37029
37444
|
entryPatterns,
|
|
37030
37445
|
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
37031
37446
|
ignorePatterns,
|
|
37032
|
-
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js")
|
|
37033
|
-
|
|
37447
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
|
|
37448
|
+
parseConcurrency: options.parseConcurrency
|
|
37449
|
+
}), options.workerTimeoutMs ?? 12e4, options.abortSignal));
|
|
37034
37450
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37035
37451
|
const diagnostics = [];
|
|
37036
37452
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37128,7 +37544,37 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
37128
37544
|
return true;
|
|
37129
37545
|
};
|
|
37130
37546
|
const filterDiagnosticsForSurface = (diagnostics, surface, config) => diagnostics.filter((diagnostic) => isDiagnosticOnSurface(diagnostic, surface, config));
|
|
37131
|
-
|
|
37547
|
+
/**
|
|
37548
|
+
* Budget for the dead-code phase, scaled to the work. deslop's graph build is
|
|
37549
|
+
* CPU-bound and roughly linear in file count, so a fixed 120s cap is too tight
|
|
37550
|
+
* for a large repo (where the pass legitimately runs that long) and is then
|
|
37551
|
+
* tipped over by any concurrent load — silently dropping every dead-code
|
|
37552
|
+
* finding. Scaling the budget with file count (and inversely with the core
|
|
37553
|
+
* share when overlapped) lets the pass complete, while the ceiling still
|
|
37554
|
+
* reclaims a genuinely wedged worker. Returns the in-worker SIGKILL deadline
|
|
37555
|
+
* and the Effect-side phase backstop that sits a margin above it.
|
|
37556
|
+
*/
|
|
37557
|
+
const resolveDeadCodeTimeout = (input) => {
|
|
37558
|
+
const coreShareFactor = Math.max(1, input.fullConcurrency / Math.max(1, input.deadCodeConcurrency));
|
|
37559
|
+
const workerTimeoutMs = Math.min(DEAD_CODE_TIMEOUT_CEILING_MS, Math.max(DEAD_CODE_WORKER_TIMEOUT_MS, Math.ceil(input.sourceFileCount * 30 * coreShareFactor)));
|
|
37560
|
+
return {
|
|
37561
|
+
workerTimeoutMs,
|
|
37562
|
+
phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
|
|
37563
|
+
};
|
|
37564
|
+
};
|
|
37565
|
+
const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
|
|
37566
|
+
const entries = [];
|
|
37567
|
+
for (const relativePath of relativePaths) {
|
|
37568
|
+
const absolutePath = Path.resolve(rootDirectory, relativePath);
|
|
37569
|
+
const sizeBytes = statSourceFileSize(absolutePath);
|
|
37570
|
+
if (isLargeMinifiedFile(absolutePath, sizeBytes)) continue;
|
|
37571
|
+
entries.push({
|
|
37572
|
+
path: relativePath,
|
|
37573
|
+
sizeBytes: sizeBytes ?? 0
|
|
37574
|
+
});
|
|
37575
|
+
}
|
|
37576
|
+
return entries;
|
|
37577
|
+
};
|
|
37132
37578
|
const listSourceFilesViaGit = (rootDirectory) => {
|
|
37133
37579
|
const result = spawnSync("git", [
|
|
37134
37580
|
"ls-files",
|
|
@@ -37161,7 +37607,8 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
37161
37607
|
}
|
|
37162
37608
|
return filePaths;
|
|
37163
37609
|
};
|
|
37164
|
-
const
|
|
37610
|
+
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory));
|
|
37611
|
+
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
37165
37612
|
const resolveLintIncludePaths = (rootDirectory, userConfig, project) => {
|
|
37166
37613
|
if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
|
|
37167
37614
|
const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
|
|
@@ -37204,24 +37651,25 @@ var Config = class Config extends Service()("react-doctor/Config") {
|
|
|
37204
37651
|
var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
37205
37652
|
static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
|
|
37206
37653
|
return yield* tryPromise({
|
|
37207
|
-
try: () => checkDeadCode({
|
|
37654
|
+
try: (signal) => checkDeadCode({
|
|
37208
37655
|
rootDirectory: input.rootDirectory,
|
|
37209
|
-
userConfig: input.userConfig
|
|
37656
|
+
userConfig: input.userConfig,
|
|
37657
|
+
parseConcurrency: input.parseConcurrency,
|
|
37658
|
+
workerTimeoutMs: input.workerTimeoutMs,
|
|
37659
|
+
abortSignal: signal
|
|
37210
37660
|
}),
|
|
37211
37661
|
catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
|
|
37212
37662
|
}).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
|
|
37213
37663
|
})()) }));
|
|
37214
37664
|
static layerOf = (diagnostics) => succeed$3(DeadCode, DeadCode.of({ run: () => fromIterable$1(diagnostics) }));
|
|
37215
37665
|
};
|
|
37216
|
-
const createNodeReadFileLinesSync = (rootDirectory) => {
|
|
37217
|
-
|
|
37218
|
-
|
|
37219
|
-
|
|
37220
|
-
|
|
37221
|
-
|
|
37222
|
-
|
|
37223
|
-
}
|
|
37224
|
-
};
|
|
37666
|
+
const createNodeReadFileLinesSync = (rootDirectory) => (filePath) => {
|
|
37667
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.join(rootDirectory, filePath);
|
|
37668
|
+
try {
|
|
37669
|
+
return NFS.readFileSync(absolutePath, "utf-8").split("\n");
|
|
37670
|
+
} catch {
|
|
37671
|
+
return null;
|
|
37672
|
+
}
|
|
37225
37673
|
};
|
|
37226
37674
|
var Files = class Files extends Service()("react-doctor/Files") {
|
|
37227
37675
|
static layerNode = succeed$3(Files, Files.of({
|
|
@@ -37432,7 +37880,10 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37432
37880
|
directory: input.directory,
|
|
37433
37881
|
cause
|
|
37434
37882
|
}) });
|
|
37435
|
-
})
|
|
37883
|
+
}), withSpan("git.exec", { attributes: {
|
|
37884
|
+
"git.command": input.command,
|
|
37885
|
+
"git.subcommand": input.args[0] ?? ""
|
|
37886
|
+
} }));
|
|
37436
37887
|
const runGit = (directory, args) => runCommand({
|
|
37437
37888
|
command: "git",
|
|
37438
37889
|
args,
|
|
@@ -37460,7 +37911,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37460
37911
|
]);
|
|
37461
37912
|
if (candidates.status !== 0) return null;
|
|
37462
37913
|
return trimOrNull(candidates.stdout.split("\n")[0] ?? "");
|
|
37463
|
-
});
|
|
37914
|
+
}).pipe(withSpan("Git.defaultBranch"));
|
|
37464
37915
|
const branchExists = (directory, branch) => runGit(directory, [
|
|
37465
37916
|
"rev-parse",
|
|
37466
37917
|
"--verify",
|
|
@@ -37507,7 +37958,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37507
37958
|
const result = resultOption.value;
|
|
37508
37959
|
if (result.status !== 0) return null;
|
|
37509
37960
|
return parseGithubViewerPermission(result.stdout);
|
|
37510
|
-
}).pipe(catch_$1(() => succeed$2(null)));
|
|
37961
|
+
}).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.githubViewerPermission"));
|
|
37511
37962
|
/**
|
|
37512
37963
|
* Resolves a `--diff A..B` / `A...B` commit range into a changed-file
|
|
37513
37964
|
* selection. Each endpoint is validated with `isSafeGitRevision`
|
|
@@ -37621,7 +38072,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37621
38072
|
changedFiles: splitNullSeparated(diff.stdout),
|
|
37622
38073
|
isCurrentChanges: false
|
|
37623
38074
|
};
|
|
37624
|
-
}),
|
|
38075
|
+
}).pipe(withSpan("Git.diffSelection")),
|
|
37625
38076
|
stagedFilePaths: (directory) => runGit(directory, [
|
|
37626
38077
|
"diff",
|
|
37627
38078
|
"--cached",
|
|
@@ -37663,7 +38114,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37663
38114
|
status: result.status,
|
|
37664
38115
|
stdout: result.stdout
|
|
37665
38116
|
};
|
|
37666
|
-
}),
|
|
38117
|
+
}).pipe(withSpan("Git.grep")),
|
|
37667
38118
|
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
37668
38119
|
if (files.length === 0) return [];
|
|
37669
38120
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
@@ -37679,7 +38130,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37679
38130
|
]);
|
|
37680
38131
|
if (result.status !== 0) return null;
|
|
37681
38132
|
return parseChangedLineRanges(result.stdout);
|
|
37682
|
-
})
|
|
38133
|
+
}).pipe(withSpan("Git.changedLineRanges"))
|
|
37683
38134
|
});
|
|
37684
38135
|
})).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
|
|
37685
38136
|
/**
|
|
@@ -37894,7 +38345,7 @@ const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
|
|
|
37894
38345
|
for (const [absolutePath, originalContent] of originalContents) try {
|
|
37895
38346
|
NFS.writeFileSync(absolutePath, originalContent);
|
|
37896
38347
|
} catch (error) {
|
|
37897
|
-
process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${
|
|
38348
|
+
process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${messageFromUnknown(error)}\n[react-doctor] Run: git checkout -- ${absolutePath}\n`);
|
|
37898
38349
|
}
|
|
37899
38350
|
};
|
|
37900
38351
|
const onExit = () => restore();
|
|
@@ -37918,6 +38369,14 @@ const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
|
|
|
37918
38369
|
process.removeListener("exit", onExit);
|
|
37919
38370
|
};
|
|
37920
38371
|
};
|
|
38372
|
+
const ROOT_DIRECTORY_PLACEHOLDER = "<root>";
|
|
38373
|
+
const normalizeConfigForHash = (config) => {
|
|
38374
|
+
const clone = JSON.parse(JSON.stringify(config));
|
|
38375
|
+
if (clone?.settings?.["react-doctor"]) clone.settings["react-doctor"].rootDirectory = ROOT_DIRECTORY_PLACEHOLDER;
|
|
38376
|
+
if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
|
|
38377
|
+
return clone;
|
|
38378
|
+
};
|
|
38379
|
+
const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update(" ").update(input.tsconfigContent ?? "").digest("hex");
|
|
37921
38380
|
/**
|
|
37922
38381
|
* Loads a plugin module via the local require resolver and extracts
|
|
37923
38382
|
* `(name, ruleNames)` from either `module.exports.meta + rules` or
|
|
@@ -37944,16 +38403,16 @@ const readPluginShape = (pluginSpecifier, loadModule) => {
|
|
|
37944
38403
|
ruleNames: new Set(Object.keys(rules))
|
|
37945
38404
|
};
|
|
37946
38405
|
};
|
|
37947
|
-
const bundledRequire = createRequire(import.meta.url);
|
|
38406
|
+
const bundledRequire$1 = createRequire(import.meta.url);
|
|
37948
38407
|
const resolveReactHooksJsPlugin = (hasReactCompiler, customRulesOnly) => {
|
|
37949
38408
|
if (!hasReactCompiler || customRulesOnly) return null;
|
|
37950
38409
|
let pluginSpecifier;
|
|
37951
38410
|
try {
|
|
37952
|
-
pluginSpecifier = bundledRequire.resolve("eslint-plugin-react-hooks");
|
|
38411
|
+
pluginSpecifier = bundledRequire$1.resolve("eslint-plugin-react-hooks");
|
|
37953
38412
|
} catch {
|
|
37954
38413
|
return null;
|
|
37955
38414
|
}
|
|
37956
|
-
const { ruleNames } = readPluginShape(pluginSpecifier, (spec) => bundledRequire(spec));
|
|
38415
|
+
const { ruleNames } = readPluginShape(pluginSpecifier, (spec) => bundledRequire$1(spec));
|
|
37957
38416
|
return {
|
|
37958
38417
|
entry: {
|
|
37959
38418
|
name: "react-hooks-js",
|
|
@@ -38000,7 +38459,7 @@ const resolveUserPlugin = (spec, configSourceDirectory) => {
|
|
|
38000
38459
|
try {
|
|
38001
38460
|
resolvedSpecifier = isRelative ? Path.resolve(configSourceDirectory, spec) : candidateRequire.resolve(spec);
|
|
38002
38461
|
} catch (error) {
|
|
38003
|
-
warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${
|
|
38462
|
+
warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${messageFromUnknown(error)}`);
|
|
38004
38463
|
return null;
|
|
38005
38464
|
}
|
|
38006
38465
|
const { name, ruleNames } = readPluginShape(resolvedSpecifier, (target) => candidateRequire(target));
|
|
@@ -38072,8 +38531,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38072
38531
|
}
|
|
38073
38532
|
return enabled;
|
|
38074
38533
|
};
|
|
38075
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [] }) => {
|
|
38076
|
-
const reactHooksJsPlugin = resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38534
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
|
|
38535
|
+
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38077
38536
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38078
38537
|
const jsPlugins = [];
|
|
38079
38538
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
@@ -38082,6 +38541,8 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38082
38541
|
for (const registryEntry of REACT_DOCTOR_RULES) {
|
|
38083
38542
|
const rule = reactDoctorPlugin.rules[registryEntry.id];
|
|
38084
38543
|
if (!rule) continue;
|
|
38544
|
+
if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38545
|
+
if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38085
38546
|
if (rule.scan !== void 0) continue;
|
|
38086
38547
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38087
38548
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
@@ -38096,7 +38557,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38096
38557
|
enabledReactDoctorRules[registryEntry.key] = severity;
|
|
38097
38558
|
}
|
|
38098
38559
|
const userPluginRules = {};
|
|
38099
|
-
for (const userPlugin of userPlugins) {
|
|
38560
|
+
if (ruleSelection !== "sidecar") for (const userPlugin of userPlugins) {
|
|
38100
38561
|
Object.assign(userPluginRules, buildUserPluginRules(userPlugin, severityControls));
|
|
38101
38562
|
jsPlugins.push(userPlugin.entry);
|
|
38102
38563
|
}
|
|
@@ -38126,6 +38587,100 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38126
38587
|
}
|
|
38127
38588
|
};
|
|
38128
38589
|
};
|
|
38590
|
+
const atomicWriteJson = (filePath, value) => {
|
|
38591
|
+
try {
|
|
38592
|
+
NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
|
|
38593
|
+
const temporaryPath = `${filePath}.${process.pid}.tmp`;
|
|
38594
|
+
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
38595
|
+
NFS.renameSync(temporaryPath, filePath);
|
|
38596
|
+
} catch {
|
|
38597
|
+
return;
|
|
38598
|
+
}
|
|
38599
|
+
};
|
|
38600
|
+
const failOpenReadJson = (filePath, fallback) => {
|
|
38601
|
+
try {
|
|
38602
|
+
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
38603
|
+
} catch {
|
|
38604
|
+
return fallback;
|
|
38605
|
+
}
|
|
38606
|
+
};
|
|
38607
|
+
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
38608
|
+
const decodeFileDiagnostics = (raw) => {
|
|
38609
|
+
if (!Array.isArray(raw)) return null;
|
|
38610
|
+
try {
|
|
38611
|
+
for (const entry of raw) validateDiagnostic(entry);
|
|
38612
|
+
return raw;
|
|
38613
|
+
} catch {
|
|
38614
|
+
return null;
|
|
38615
|
+
}
|
|
38616
|
+
};
|
|
38617
|
+
const emptyCache = () => ({
|
|
38618
|
+
version: 1,
|
|
38619
|
+
rulesets: {}
|
|
38620
|
+
});
|
|
38621
|
+
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
38622
|
+
const entries = /* @__PURE__ */ new Map();
|
|
38623
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
38624
|
+
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
38625
|
+
const bucket = persisted.rulesets[rulesetHash];
|
|
38626
|
+
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
38627
|
+
for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
|
|
38628
|
+
const decoded = decodeFileDiagnostics(rawDiagnostics);
|
|
38629
|
+
if (decoded !== null) entries.set(fileKey, decoded);
|
|
38630
|
+
}
|
|
38631
|
+
return entries;
|
|
38632
|
+
};
|
|
38633
|
+
const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
38634
|
+
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
38635
|
+
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
38636
|
+
return {
|
|
38637
|
+
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
38638
|
+
store: (fileKey, diagnostics) => {
|
|
38639
|
+
entries.delete(fileKey);
|
|
38640
|
+
entries.set(fileKey, diagnostics);
|
|
38641
|
+
},
|
|
38642
|
+
persist: () => {
|
|
38643
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
38644
|
+
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
38645
|
+
const existingBucket = rulesets[rulesetHash];
|
|
38646
|
+
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
38647
|
+
const ourFiles = {};
|
|
38648
|
+
for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
|
|
38649
|
+
const cappedEntries = Object.entries({
|
|
38650
|
+
...existingFiles,
|
|
38651
|
+
...ourFiles
|
|
38652
|
+
}).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
|
|
38653
|
+
rulesets[rulesetHash] = {
|
|
38654
|
+
updatedAtMs: Date.now(),
|
|
38655
|
+
files: Object.fromEntries(cappedEntries)
|
|
38656
|
+
};
|
|
38657
|
+
const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
|
|
38658
|
+
const prunedRulesets = {};
|
|
38659
|
+
for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
|
|
38660
|
+
atomicWriteJson(cacheFilePath, {
|
|
38661
|
+
version: 1,
|
|
38662
|
+
rulesets: prunedRulesets
|
|
38663
|
+
});
|
|
38664
|
+
}
|
|
38665
|
+
};
|
|
38666
|
+
};
|
|
38667
|
+
const bundledRequire = createRequire(import.meta.url);
|
|
38668
|
+
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
38669
|
+
"oxlint/package.json",
|
|
38670
|
+
"oxlint-plugin-react-doctor/package.json",
|
|
38671
|
+
"eslint-plugin-react-hooks/package.json"
|
|
38672
|
+
];
|
|
38673
|
+
const resolveOxlintToolchainVersions = () => {
|
|
38674
|
+
const versions = [`node=${process.version}`];
|
|
38675
|
+
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
38676
|
+
const packageJson = bundledRequire(specifier);
|
|
38677
|
+
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
38678
|
+
versions.push(`${specifier}=${version}`);
|
|
38679
|
+
} catch {
|
|
38680
|
+
versions.push(`${specifier}=missing`);
|
|
38681
|
+
}
|
|
38682
|
+
return versions;
|
|
38683
|
+
};
|
|
38129
38684
|
const esmRequire = createRequire(import.meta.url);
|
|
38130
38685
|
const resolveOxlintBinary = () => {
|
|
38131
38686
|
const oxlintMainPath = esmRequire.resolve("oxlint");
|
|
@@ -38133,7 +38688,6 @@ const resolveOxlintBinary = () => {
|
|
|
38133
38688
|
return Path.join(oxlintPackageDirectory, "bin", "oxlint");
|
|
38134
38689
|
};
|
|
38135
38690
|
const resolvePluginPath = () => esmRequire.resolve("oxlint-plugin-react-doctor");
|
|
38136
|
-
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
38137
38691
|
const resolveTsConfigRelativePath = (rootDirectory) => {
|
|
38138
38692
|
for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
|
|
38139
38693
|
return null;
|
|
@@ -38505,7 +39059,7 @@ const scopeContainsNonImportBinding = (node, scopeNode, identifierName) => {
|
|
|
38505
39059
|
const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
|
|
38506
39060
|
let currentNode = identifier.parent;
|
|
38507
39061
|
while (currentNode) {
|
|
38508
|
-
if (
|
|
39062
|
+
if (isScopeBoundary(currentNode)) {
|
|
38509
39063
|
if (scopeContainsNonImportBinding(currentNode, currentNode, identifier.text)) return true;
|
|
38510
39064
|
}
|
|
38511
39065
|
if (currentNode === sourceFile) return false;
|
|
@@ -38596,11 +39150,10 @@ const findResolutionInScope = (scopeNode, identifierName, reactImportBindings, s
|
|
|
38596
39150
|
});
|
|
38597
39151
|
return resolution;
|
|
38598
39152
|
};
|
|
38599
|
-
const isScopeNode = isScopeBoundary;
|
|
38600
39153
|
const resolveIdentifierBinding = (identifier, reactImportBindings, sourceFile, visitedDeclarations = /* @__PURE__ */ new Set()) => {
|
|
38601
39154
|
let currentNode = identifier.parent;
|
|
38602
39155
|
while (currentNode) {
|
|
38603
|
-
if (
|
|
39156
|
+
if (isScopeBoundary(currentNode)) {
|
|
38604
39157
|
const resolution = findResolutionInScope(currentNode, identifier.text, reactImportBindings, sourceFile, visitedDeclarations);
|
|
38605
39158
|
if (resolution) return resolution;
|
|
38606
39159
|
}
|
|
@@ -38770,9 +39323,9 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
38770
39323
|
try {
|
|
38771
39324
|
parsed = JSON.parse(sanitizedStdout);
|
|
38772
39325
|
} catch {
|
|
38773
|
-
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0,
|
|
39326
|
+
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
38774
39327
|
}
|
|
38775
|
-
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0,
|
|
39328
|
+
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
38776
39329
|
const minifiedFileCache = /* @__PURE__ */ new Map();
|
|
38777
39330
|
const isMinifiedDiagnosticFile = (filename) => {
|
|
38778
39331
|
const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
|
|
@@ -38809,15 +39362,19 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
38809
39362
|
};
|
|
38810
39363
|
});
|
|
38811
39364
|
};
|
|
38812
|
-
const
|
|
38813
|
-
const
|
|
38814
|
-
for (const [name, value] of Object.entries(
|
|
39365
|
+
const buildOxlintChildEnv = (sourceEnv) => {
|
|
39366
|
+
const childEnv = {};
|
|
39367
|
+
for (const [name, value] of Object.entries(sourceEnv)) {
|
|
38815
39368
|
if (name === "NODE_OPTIONS" || name === "NODE_DEBUG") continue;
|
|
38816
39369
|
if (name.startsWith("npm_config_")) continue;
|
|
38817
|
-
|
|
39370
|
+
childEnv[name] = value;
|
|
38818
39371
|
}
|
|
38819
|
-
|
|
38820
|
-
|
|
39372
|
+
const isCompileCacheDisabled = Boolean(sourceEnv.NODE_DISABLE_COMPILE_CACHE);
|
|
39373
|
+
const isCompileCacheAlreadySet = childEnv.NODE_COMPILE_CACHE !== void 0;
|
|
39374
|
+
if (!isCompileCacheDisabled && !isCompileCacheAlreadySet) childEnv.NODE_COMPILE_CACHE = Path.join(os.tmpdir(), NODE_COMPILE_CACHE_DIR_NAME);
|
|
39375
|
+
return childEnv;
|
|
39376
|
+
};
|
|
39377
|
+
const SANITIZED_ENV = buildOxlintChildEnv(process.env);
|
|
38821
39378
|
/**
|
|
38822
39379
|
* Spawn one oxlint subprocess with hard ceilings on wall time and
|
|
38823
39380
|
* output size. Returns stdout on success; raises a tagged
|
|
@@ -38834,7 +39391,11 @@ const SANITIZED_ENV = (() => {
|
|
|
38834
39391
|
* The first three are splittable (the caller's binary-split retry
|
|
38835
39392
|
* shrinks the batch and re-spawns); the fourth isn't.
|
|
38836
39393
|
*/
|
|
38837
|
-
const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLINT_SPAWN_TIMEOUT_MS, outputMaxBytes = OXLINT_OUTPUT_MAX_BYTES) => new Promise((resolve, reject) => {
|
|
39394
|
+
const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLINT_SPAWN_TIMEOUT_MS, outputMaxBytes = OXLINT_OUTPUT_MAX_BYTES, abortSignal) => new Promise((resolve, reject) => {
|
|
39395
|
+
if (abortSignal?.aborted) {
|
|
39396
|
+
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
|
|
39397
|
+
return;
|
|
39398
|
+
}
|
|
38838
39399
|
const child = spawn(nodeBinaryPath, args, {
|
|
38839
39400
|
cwd: rootDirectory,
|
|
38840
39401
|
env: SANITIZED_ENV,
|
|
@@ -38844,11 +39405,18 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
38844
39405
|
"pipe"
|
|
38845
39406
|
]
|
|
38846
39407
|
});
|
|
39408
|
+
const onAbort = () => {
|
|
39409
|
+
child.kill("SIGKILL");
|
|
39410
|
+
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
|
|
39411
|
+
};
|
|
39412
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
39413
|
+
const clearAbortListener = () => abortSignal?.removeEventListener("abort", onAbort);
|
|
38847
39414
|
const timeoutHandle = setTimeout(() => {
|
|
39415
|
+
clearAbortListener();
|
|
38848
39416
|
child.kill("SIGKILL");
|
|
38849
39417
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
38850
39418
|
kind: "timeout",
|
|
38851
|
-
detail: `${spawnTimeoutMs /
|
|
39419
|
+
detail: `${spawnTimeoutMs / MILLISECONDS_PER_SECOND}s budget exceeded`
|
|
38852
39420
|
}) }));
|
|
38853
39421
|
}, spawnTimeoutMs);
|
|
38854
39422
|
timeoutHandle.unref?.();
|
|
@@ -38879,10 +39447,12 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
38879
39447
|
});
|
|
38880
39448
|
child.on("error", (error) => {
|
|
38881
39449
|
clearTimeout(timeoutHandle);
|
|
39450
|
+
clearAbortListener();
|
|
38882
39451
|
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: error }) }));
|
|
38883
39452
|
});
|
|
38884
39453
|
child.on("close", (_code, signal) => {
|
|
38885
39454
|
clearTimeout(timeoutHandle);
|
|
39455
|
+
clearAbortListener();
|
|
38886
39456
|
if (didKillForSize) {
|
|
38887
39457
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
38888
39458
|
kind: "output-too-large",
|
|
@@ -38949,26 +39519,28 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
38949
39519
|
* loop with a slimmer config in that case.
|
|
38950
39520
|
*/
|
|
38951
39521
|
const spawnLintBatches = async (input) => {
|
|
38952
|
-
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes } = input;
|
|
39522
|
+
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal } = input;
|
|
38953
39523
|
const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
|
|
38954
39524
|
const totalFileCount = fileBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
38955
39525
|
const runBatchPass = async (concurrency) => {
|
|
38956
39526
|
const allDiagnostics = [];
|
|
38957
39527
|
const droppedFiles = [];
|
|
38958
39528
|
let firstDropReason = null;
|
|
38959
|
-
const
|
|
39529
|
+
const splitDeadlineMs = Date.now() + splitTotalBudgetMs;
|
|
39530
|
+
const spawnLintBatch = async (batch, depth) => {
|
|
38960
39531
|
const batchArgs = [...baseArgs, ...batch];
|
|
38961
39532
|
try {
|
|
38962
|
-
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes), project, rootDirectory);
|
|
39533
|
+
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
|
|
38963
39534
|
} catch (error) {
|
|
38964
39535
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
38965
|
-
|
|
39536
|
+
const splitBudgetExhausted = Date.now() >= splitDeadlineMs || depth >= splitMaxDepth;
|
|
39537
|
+
if (batch.length <= 1 || splitBudgetExhausted) {
|
|
38966
39538
|
droppedFiles.push(...batch);
|
|
38967
|
-
if (firstDropReason === null) firstDropReason = error.message;
|
|
39539
|
+
if (firstDropReason === null) firstDropReason = splitBudgetExhausted && batch.length > 1 ? `${error.message} (split budget exhausted after ${splitMaxDepth} levels / ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s)` : error.message;
|
|
38968
39540
|
return [];
|
|
38969
39541
|
}
|
|
38970
39542
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
38971
|
-
return [...await spawnLintBatch(batch.slice(0, splitIndex)), ...await spawnLintBatch(batch.slice(splitIndex))];
|
|
39543
|
+
return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1)];
|
|
38972
39544
|
}
|
|
38973
39545
|
};
|
|
38974
39546
|
let startedFileCount = 0;
|
|
@@ -38985,7 +39557,7 @@ const spawnLintBatches = async (input) => {
|
|
|
38985
39557
|
try {
|
|
38986
39558
|
const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
|
|
38987
39559
|
startedFileCount += batch.length;
|
|
38988
|
-
const batchDiagnostics = await spawnLintBatch(batch);
|
|
39560
|
+
const batchDiagnostics = await spawnLintBatch(batch, 0);
|
|
38989
39561
|
scannedFileCount += batch.length;
|
|
38990
39562
|
if (onFileProgress) {
|
|
38991
39563
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
@@ -39046,6 +39618,22 @@ const validateRuleRegistration = () => {
|
|
|
39046
39618
|
].filter((entry) => entry !== null).join("; ");
|
|
39047
39619
|
console.warn(`[react-doctor] rule-registration drift: ${detail}`);
|
|
39048
39620
|
};
|
|
39621
|
+
const hashFileContents = (filePath) => {
|
|
39622
|
+
try {
|
|
39623
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39624
|
+
} catch {
|
|
39625
|
+
return null;
|
|
39626
|
+
}
|
|
39627
|
+
};
|
|
39628
|
+
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
39629
|
+
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
39630
|
+
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
39631
|
+
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
39632
|
+
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
39633
|
+
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
39634
|
+
return Path.join(os.tmpdir(), "react-doctor-cache", projectCacheSubdir(projectDirectory));
|
|
39635
|
+
};
|
|
39636
|
+
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39049
39637
|
/**
|
|
39050
39638
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
39051
39639
|
* the runner: once for the primary scan, once for the
|
|
@@ -39063,6 +39651,28 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
39063
39651
|
NFS.closeSync(fileHandle);
|
|
39064
39652
|
}
|
|
39065
39653
|
};
|
|
39654
|
+
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
39655
|
+
/**
|
|
39656
|
+
* Detects an oxlint config-load crash caused by the optional
|
|
39657
|
+
* `react-hooks-js` (eslint-plugin-react-hooks) React Compiler plugin and
|
|
39658
|
+
* builds the partial-failure note for it; returns `null` when the failure
|
|
39659
|
+
* was anything else.
|
|
39660
|
+
*
|
|
39661
|
+
* oxlint prints a framed error to stdout (not stderr) and exits non-zero
|
|
39662
|
+
* when a `jsPlugins` entry can't be imported; that non-JSON stdout
|
|
39663
|
+
* surfaces as `OxlintOutputUnparseable`. Because oxlint fails the WHOLE
|
|
39664
|
+
* config load on it, leaving the plugin in would drop every curated
|
|
39665
|
+
* react-doctor diagnostic too — so the caller retries with the plugin
|
|
39666
|
+
* stripped (issue #833). Both markers sit at the start of oxlint's
|
|
39667
|
+
* message, so they survive the `preview` slice even for deep pnpm paths.
|
|
39668
|
+
*/
|
|
39669
|
+
const reactHooksJsPluginDropNote = (error) => {
|
|
39670
|
+
if (!(error instanceof ReactDoctorError) || error.reason._tag !== "OxlintOutputUnparseable") return null;
|
|
39671
|
+
const { preview } = error.reason;
|
|
39672
|
+
if (!preview.includes("Failed to load JS plugin") || !preview.includes("eslint-plugin-react-hooks")) return null;
|
|
39673
|
+
const underlyingReason = preview.match(/Error:[^\n]*/)?.[0]?.trim();
|
|
39674
|
+
return `${REACT_HOOKS_JS_DROP_PREFIX}${underlyingReason ? `: ${underlyingReason}` : ""}. Other rules ran normally.`;
|
|
39675
|
+
};
|
|
39066
39676
|
/**
|
|
39067
39677
|
* The oxlint runner. Composed of three pieces in `runners/oxlint/`:
|
|
39068
39678
|
*
|
|
@@ -39082,7 +39692,7 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
39082
39692
|
* 6. always restore disable directives + clean up the temp dir
|
|
39083
39693
|
*/
|
|
39084
39694
|
const runOxlint = async (options) => {
|
|
39085
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, spawnTimeoutMs, outputMaxBytes } = options;
|
|
39695
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "arrival" } = options;
|
|
39086
39696
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
39087
39697
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
39088
39698
|
validateRuleRegistration();
|
|
@@ -39090,38 +39700,165 @@ const runOxlint = async (options) => {
|
|
|
39090
39700
|
const pluginPath = resolvePluginPath();
|
|
39091
39701
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
39092
39702
|
const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
39093
|
-
const buildConfig = (
|
|
39703
|
+
const buildConfig = (overrides) => createOxlintConfig({
|
|
39094
39704
|
pluginPath,
|
|
39095
39705
|
project,
|
|
39096
39706
|
customRulesOnly,
|
|
39097
|
-
extendsPaths:
|
|
39707
|
+
extendsPaths: overrides.extendsPaths,
|
|
39098
39708
|
ignoredTags,
|
|
39099
39709
|
serverAuthFunctionNames,
|
|
39100
39710
|
severityControls,
|
|
39101
|
-
userPlugins
|
|
39711
|
+
userPlugins,
|
|
39712
|
+
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
39713
|
+
ruleSelection: overrides.ruleSelection
|
|
39102
39714
|
});
|
|
39103
39715
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
39104
39716
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
39105
39717
|
const configPath = Path.join(configDirectory, "oxlintrc.json");
|
|
39106
39718
|
try {
|
|
39107
|
-
const
|
|
39108
|
-
|
|
39109
|
-
|
|
39110
|
-
configPath,
|
|
39111
|
-
"--format",
|
|
39112
|
-
"json"
|
|
39113
|
-
];
|
|
39719
|
+
const oxlintBinary = resolveOxlintBinary();
|
|
39720
|
+
const sharedArgs = [];
|
|
39721
|
+
let tsconfigContent = null;
|
|
39114
39722
|
if (project.hasTypeScript) {
|
|
39115
39723
|
const tsconfigRelativePath = resolveTsConfigRelativePath(rootDirectory);
|
|
39116
|
-
if (tsconfigRelativePath)
|
|
39724
|
+
if (tsconfigRelativePath) {
|
|
39725
|
+
sharedArgs.push("--tsconfig", tsconfigRelativePath);
|
|
39726
|
+
try {
|
|
39727
|
+
tsconfigContent = NFS.readFileSync(Path.resolve(rootDirectory, tsconfigRelativePath), "utf8");
|
|
39728
|
+
} catch {
|
|
39729
|
+
tsconfigContent = null;
|
|
39730
|
+
}
|
|
39731
|
+
}
|
|
39117
39732
|
}
|
|
39118
39733
|
const combinedPatterns = collectIgnorePatterns(rootDirectory);
|
|
39119
39734
|
if (combinedPatterns.length > 0) {
|
|
39120
39735
|
const combinedIgnorePath = Path.join(configDirectory, "combined.ignore");
|
|
39121
39736
|
NFS.writeFileSync(combinedIgnorePath, `${combinedPatterns.join("\n")}\n`);
|
|
39122
|
-
|
|
39737
|
+
sharedArgs.push("--ignore-path", combinedIgnorePath);
|
|
39123
39738
|
}
|
|
39124
|
-
const
|
|
39739
|
+
const makeBaseArgs = (oxlintConfigPath) => [
|
|
39740
|
+
oxlintBinary,
|
|
39741
|
+
"-c",
|
|
39742
|
+
oxlintConfigPath,
|
|
39743
|
+
"--format",
|
|
39744
|
+
"json",
|
|
39745
|
+
...sharedArgs
|
|
39746
|
+
];
|
|
39747
|
+
const discoverScanFiles = () => lintBatchOrdering === "cost" ? sortSourceFilesByCost(listSourceFilesWithSize(rootDirectory)) : listSourceFiles(rootDirectory);
|
|
39748
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : discoverScanFiles();
|
|
39749
|
+
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
39750
|
+
if (files.length === 0) return {
|
|
39751
|
+
diagnostics: [],
|
|
39752
|
+
didDropReactHooksJsPlugin: false,
|
|
39753
|
+
hadPartialFailure: false
|
|
39754
|
+
};
|
|
39755
|
+
let hadPartialFailure = false;
|
|
39756
|
+
const reportPartialFailure = (reason) => {
|
|
39757
|
+
hadPartialFailure = true;
|
|
39758
|
+
onPartialFailure?.(reason);
|
|
39759
|
+
};
|
|
39760
|
+
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
39761
|
+
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
39762
|
+
const passFileBatches = batchIncludePaths(passBaseArgs, files);
|
|
39763
|
+
const spawnPass = () => spawnLintBatches({
|
|
39764
|
+
baseArgs: passBaseArgs,
|
|
39765
|
+
fileBatches: passFileBatches,
|
|
39766
|
+
rootDirectory,
|
|
39767
|
+
nodeBinaryPath,
|
|
39768
|
+
project,
|
|
39769
|
+
onPartialFailure: reportPartialFailure,
|
|
39770
|
+
onFileProgress: fileProgress,
|
|
39771
|
+
spawnTimeoutMs,
|
|
39772
|
+
outputMaxBytes,
|
|
39773
|
+
concurrency: options.concurrency,
|
|
39774
|
+
signal: options.signal
|
|
39775
|
+
});
|
|
39776
|
+
writeOxlintConfig(passConfigPath, buildConfigForPass({}));
|
|
39777
|
+
try {
|
|
39778
|
+
return {
|
|
39779
|
+
diagnostics: await spawnPass(),
|
|
39780
|
+
didDropReactHooksJsPlugin: false,
|
|
39781
|
+
hadPartialFailure
|
|
39782
|
+
};
|
|
39783
|
+
} catch (error) {
|
|
39784
|
+
const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
|
|
39785
|
+
if (reactHooksJsDropNote === null) throw error;
|
|
39786
|
+
writeOxlintConfig(passConfigPath, buildConfigForPass({ disableReactHooksJsPlugin: true }));
|
|
39787
|
+
const diagnostics = await spawnPass();
|
|
39788
|
+
reportPartialFailure(reactHooksJsDropNote);
|
|
39789
|
+
return {
|
|
39790
|
+
diagnostics,
|
|
39791
|
+
didDropReactHooksJsPlugin: true,
|
|
39792
|
+
hadPartialFailure
|
|
39793
|
+
};
|
|
39794
|
+
}
|
|
39795
|
+
};
|
|
39796
|
+
if (perFileLintCacheEnabled && respectInlineDisables && !project.hasReactCompiler && extendsPaths.length === 0 && userPlugins.length === 0) {
|
|
39797
|
+
const rulesetHash = computeRulesetHash({
|
|
39798
|
+
config: buildConfig({
|
|
39799
|
+
extendsPaths: [],
|
|
39800
|
+
ruleSelection: "cacheable"
|
|
39801
|
+
}),
|
|
39802
|
+
toolchainVersions: resolveOxlintToolchainVersions(),
|
|
39803
|
+
ignorePatterns: combinedPatterns,
|
|
39804
|
+
tsconfigContent
|
|
39805
|
+
});
|
|
39806
|
+
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
39807
|
+
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
39808
|
+
const missFiles = [];
|
|
39809
|
+
const replayedDiagnostics = [];
|
|
39810
|
+
for (const candidateFile of candidateFiles) {
|
|
39811
|
+
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
39812
|
+
if (contentHash === null) {
|
|
39813
|
+
missFiles.push(candidateFile);
|
|
39814
|
+
continue;
|
|
39815
|
+
}
|
|
39816
|
+
const cacheKey = `${candidateFile.replaceAll("\\", "/")}${contentHash}`;
|
|
39817
|
+
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
39818
|
+
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
39819
|
+
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
39820
|
+
else replayedDiagnostics.push(...cachedDiagnostics);
|
|
39821
|
+
}
|
|
39822
|
+
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
39823
|
+
const cacheableResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
39824
|
+
extendsPaths: [],
|
|
39825
|
+
ruleSelection: "cacheable",
|
|
39826
|
+
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
39827
|
+
}), "oxlintrc.cacheable.json", missFiles, void 0);
|
|
39828
|
+
const sidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
39829
|
+
extendsPaths: [],
|
|
39830
|
+
ruleSelection: "sidecar"
|
|
39831
|
+
}), "oxlintrc.sidecar.json", candidateFiles, options.onFileProgress);
|
|
39832
|
+
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
39833
|
+
const missFileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
39834
|
+
for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
|
|
39835
|
+
const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
|
|
39836
|
+
let isAttributionSound = true;
|
|
39837
|
+
for (const diagnostic of cacheableResult.diagnostics) {
|
|
39838
|
+
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
39839
|
+
if (missFile === void 0) {
|
|
39840
|
+
isAttributionSound = false;
|
|
39841
|
+
break;
|
|
39842
|
+
}
|
|
39843
|
+
const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
|
|
39844
|
+
fileDiagnostics.push(diagnostic);
|
|
39845
|
+
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
39846
|
+
}
|
|
39847
|
+
if (!cacheableResult.didDropReactHooksJsPlugin && !cacheableResult.hadPartialFailure && isAttributionSound) {
|
|
39848
|
+
for (const missFile of missFiles) {
|
|
39849
|
+
const cacheKey = cacheKeyByFile.get(missFile);
|
|
39850
|
+
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
39851
|
+
}
|
|
39852
|
+
cache.persist();
|
|
39853
|
+
}
|
|
39854
|
+
return dedupeDiagnostics([
|
|
39855
|
+
...replayedDiagnostics,
|
|
39856
|
+
...cacheableResult.diagnostics,
|
|
39857
|
+
...sidecarResult.diagnostics
|
|
39858
|
+
]);
|
|
39859
|
+
}
|
|
39860
|
+
const baseArgs = makeBaseArgs(configPath);
|
|
39861
|
+
const fileBatches = batchIncludePaths(baseArgs, candidateFiles);
|
|
39125
39862
|
const runBatches = () => spawnLintBatches({
|
|
39126
39863
|
baseArgs,
|
|
39127
39864
|
fileBatches,
|
|
@@ -39132,14 +39869,25 @@ const runOxlint = async (options) => {
|
|
|
39132
39869
|
onFileProgress: options.onFileProgress,
|
|
39133
39870
|
spawnTimeoutMs,
|
|
39134
39871
|
outputMaxBytes,
|
|
39135
|
-
concurrency: options.concurrency
|
|
39872
|
+
concurrency: options.concurrency,
|
|
39873
|
+
signal: options.signal
|
|
39136
39874
|
});
|
|
39137
|
-
writeOxlintConfig(configPath, buildConfig(extendsPaths));
|
|
39875
|
+
writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
|
|
39138
39876
|
try {
|
|
39139
39877
|
return await runBatches();
|
|
39140
39878
|
} catch (error) {
|
|
39879
|
+
const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
|
|
39880
|
+
if (reactHooksJsDropNote !== null) {
|
|
39881
|
+
writeOxlintConfig(configPath, buildConfig({
|
|
39882
|
+
extendsPaths,
|
|
39883
|
+
disableReactHooksJsPlugin: true
|
|
39884
|
+
}));
|
|
39885
|
+
const diagnostics = await runBatches();
|
|
39886
|
+
onPartialFailure?.(reactHooksJsDropNote);
|
|
39887
|
+
return diagnostics;
|
|
39888
|
+
}
|
|
39141
39889
|
if (extendsPaths.length === 0) throw error;
|
|
39142
|
-
writeOxlintConfig(configPath, buildConfig([]));
|
|
39890
|
+
writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
|
|
39143
39891
|
return await runBatches();
|
|
39144
39892
|
}
|
|
39145
39893
|
} finally {
|
|
@@ -39201,9 +39949,11 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
39201
39949
|
const spawnTimeoutMs = yield* OxlintSpawnTimeoutMs;
|
|
39202
39950
|
const outputMaxBytes = yield* OxlintOutputMaxBytes;
|
|
39203
39951
|
const concurrency = yield* OxlintConcurrency;
|
|
39952
|
+
const lintBatchOrdering = yield* LintBatchOrdering;
|
|
39953
|
+
const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
|
|
39204
39954
|
const collectedFailures = [];
|
|
39205
39955
|
const diagnostics = yield* tryPromise({
|
|
39206
|
-
try: () => runOxlint({
|
|
39956
|
+
try: (signal) => runOxlint({
|
|
39207
39957
|
rootDirectory: input.rootDirectory,
|
|
39208
39958
|
project: input.project,
|
|
39209
39959
|
includePaths: input.includePaths ? [...input.includePaths] : void 0,
|
|
@@ -39218,9 +39968,13 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
39218
39968
|
collectedFailures.push(reason);
|
|
39219
39969
|
},
|
|
39220
39970
|
onFileProgress: input.onFileProgress,
|
|
39971
|
+
perFileLintCacheEnabled,
|
|
39972
|
+
onCacheStats: input.onCacheStats,
|
|
39221
39973
|
spawnTimeoutMs,
|
|
39222
39974
|
outputMaxBytes,
|
|
39223
|
-
concurrency
|
|
39975
|
+
concurrency,
|
|
39976
|
+
signal,
|
|
39977
|
+
lintBatchOrdering
|
|
39224
39978
|
}),
|
|
39225
39979
|
catch: ensureReactDoctorError
|
|
39226
39980
|
});
|
|
@@ -39612,14 +40366,46 @@ const parseArtifactFromBody = (body) => {
|
|
|
39612
40366
|
}
|
|
39613
40367
|
return null;
|
|
39614
40368
|
};
|
|
39615
|
-
const
|
|
40369
|
+
const isSupplyChainCacheDisabled = () => {
|
|
40370
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
40371
|
+
return noCache === "1" || noCache === "true";
|
|
40372
|
+
};
|
|
40373
|
+
const supplyChainCacheFile = (cacheDirectory, dependency) => {
|
|
40374
|
+
const purlHash = crypto.createHash("sha256").update(toPurl(dependency)).digest("hex").slice(0, 16);
|
|
40375
|
+
return Path.join(cacheDirectory, SUPPLY_CHAIN_CACHE_SUBDIR, `${purlHash}.json`);
|
|
40376
|
+
};
|
|
40377
|
+
const readCachedSocketBody = (cacheFile) => {
|
|
40378
|
+
try {
|
|
40379
|
+
const entry = JSON.parse(NFS.readFileSync(cacheFile, "utf-8"));
|
|
40380
|
+
if (typeof entry === "object" && entry !== null && "fetchedAtMs" in entry && "body" in entry && typeof entry.fetchedAtMs === "number" && typeof entry.body === "string" && Date.now() - entry.fetchedAtMs <= 864e5) return entry.body;
|
|
40381
|
+
} catch {}
|
|
40382
|
+
return null;
|
|
40383
|
+
};
|
|
40384
|
+
const writeCachedSocketBody = (cacheFile, body) => {
|
|
40385
|
+
try {
|
|
40386
|
+
NFS.mkdirSync(Path.dirname(cacheFile), { recursive: true });
|
|
40387
|
+
NFS.writeFileSync(cacheFile, JSON.stringify({
|
|
40388
|
+
fetchedAtMs: Date.now(),
|
|
40389
|
+
body
|
|
40390
|
+
}));
|
|
40391
|
+
} catch {}
|
|
40392
|
+
};
|
|
40393
|
+
const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
|
|
40394
|
+
const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
|
|
40395
|
+
if (cacheFile !== null) {
|
|
40396
|
+
const cachedBody = readCachedSocketBody(cacheFile);
|
|
40397
|
+
if (cachedBody !== null) return parseArtifactFromBody(cachedBody);
|
|
40398
|
+
}
|
|
39616
40399
|
const requestUrl = `${SOCKET_FREE_PURL_API_BASE}/${encodeURIComponent(toPurl(dependency))}`;
|
|
39617
40400
|
const response = await fetch(requestUrl, {
|
|
39618
40401
|
headers: { "User-Agent": SOCKET_FREE_USER_AGENT },
|
|
39619
40402
|
signal
|
|
39620
40403
|
});
|
|
39621
40404
|
if (!response.ok) return null;
|
|
39622
|
-
|
|
40405
|
+
const body = await response.text();
|
|
40406
|
+
const artifact = parseArtifactFromBody(body);
|
|
40407
|
+
if (artifact !== null && cacheFile !== null) writeCachedSocketBody(cacheFile, body);
|
|
40408
|
+
return artifact;
|
|
39623
40409
|
}).pipe(timeout(FETCH_TIMEOUT_MS), orElseSucceed(() => null), tap$1((artifact) => {
|
|
39624
40410
|
const scoreAttributes = {};
|
|
39625
40411
|
if (artifact !== null) {
|
|
@@ -39724,7 +40510,8 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
39724
40510
|
const packageJsonPath = Path.join(input.rootDirectory, "package.json");
|
|
39725
40511
|
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
39726
40512
|
if (dependencies.length === 0) return [];
|
|
39727
|
-
const
|
|
40513
|
+
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
40514
|
+
const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
|
|
39728
40515
|
const diagnostics = [];
|
|
39729
40516
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
39730
40517
|
const artifact = artifacts[index];
|
|
@@ -39749,6 +40536,10 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
39749
40536
|
* The underlying `checkSupplyChain` Effect is total/fail-open — per-package
|
|
39750
40537
|
* timeouts and network failures recover to "skip" — so the stream never
|
|
39751
40538
|
* fails, mirroring `DeadCode`'s stream shape so the two compose the same way.
|
|
40539
|
+
* The orchestrator (`run-inspect.ts`) consumes this stream on a background
|
|
40540
|
+
* fiber whose network time overlaps the lint pass, joined under a generous
|
|
40541
|
+
* wall-clock budget; a budget expiry is the same fail-open outcome as a Socket
|
|
40542
|
+
* outage.
|
|
39752
40543
|
*/
|
|
39753
40544
|
var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
|
|
39754
40545
|
static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(checkSupplyChain(input).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
|
|
@@ -39807,18 +40598,42 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
39807
40598
|
*
|
|
39808
40599
|
* Phases:
|
|
39809
40600
|
*
|
|
39810
|
-
* 1. Config.resolve(directory) → Project.discover → Git metadata
|
|
40601
|
+
* 1. Config.resolve(directory) → Project.discover → Git metadata.
|
|
40602
|
+
* The GitHub viewer-permission lookup is forked onto a background
|
|
40603
|
+
* fiber here and joined late (it feeds score metadata, not
|
|
40604
|
+
* diagnostics).
|
|
39811
40605
|
* 2. beforeLint hook (e.g. CLI renders the project-detection block)
|
|
39812
40606
|
* 3. environment checks (reduced-motion + pnpm hardening +
|
|
39813
|
-
* expo/react-native + security scan)
|
|
39814
|
-
* 4.
|
|
39815
|
-
*
|
|
39816
|
-
*
|
|
39817
|
-
*
|
|
39818
|
-
*
|
|
39819
|
-
*
|
|
39820
|
-
*
|
|
39821
|
-
*
|
|
40607
|
+
* expo/react-native + security scan), collected synchronously
|
|
40608
|
+
* 4. The supply-chain check (Socket.dev) is forked onto a background
|
|
40609
|
+
* fiber so its ~100% network-bound time overlaps the ~100%
|
|
40610
|
+
* CPU/subprocess-bound lint pass below, collapsing two serial
|
|
40611
|
+
* phases into roughly `max(supplyChain, lint)`. It is capped by
|
|
40612
|
+
* `SupplyChainOverlapTimeoutMs` (measured from fork) so a hung
|
|
40613
|
+
* socket can't drag out its join; on timeout it fails open to no
|
|
40614
|
+
* diagnostics — the same outcome class as a Socket outage.
|
|
40615
|
+
* 5. Linter.run runs; DeadCode.run runs concurrently (forked child
|
|
40616
|
+
* fiber) ONLY when the memory gate has headroom to run the 8 GB
|
|
40617
|
+
* dead-code child alongside the oxlint workers — or when overlap is
|
|
40618
|
+
* forced via REACT_DOCTOR_DEAD_CODE_OVERLAP. Otherwise dead-code
|
|
40619
|
+
* runs sequentially after lint, exactly as it did pre-overlap. The
|
|
40620
|
+
* fiber is joined (or interrupted, SIGKILLing its worker, on lint
|
|
40621
|
+
* failure) before diagnostics are concatenated. The afterLint hook
|
|
40622
|
+
* fires between lint and dead-code. Progress spinner labels AND the
|
|
40623
|
+
* final diagnostic / score order stay independent of execution
|
|
40624
|
+
* order, so terminal output is identical either way; supply-chain
|
|
40625
|
+
* rides alongside without a spinner.
|
|
40626
|
+
* 6. Join the supply-chain fiber, then assemble the diagnostics in a
|
|
40627
|
+
* FIXED order (env, supply-chain, lint, dead-code) so the output is
|
|
40628
|
+
* byte-identical regardless of which fiber settled first. The
|
|
40629
|
+
* viewer-permission fiber is joined later, during score-metadata
|
|
40630
|
+
* assembly (it feeds score metadata, not diagnostics). The per-element
|
|
40631
|
+
* `Reporter.emit` side-channel now interleaves supply-chain with lint
|
|
40632
|
+
* emits, so capture-order assertions must target the deterministic
|
|
40633
|
+
* concat below, not emit order (production `Reporter.layerNoop` makes
|
|
40634
|
+
* emit a no-op).
|
|
40635
|
+
* 7. Reporter.finalize
|
|
40636
|
+
* 8. Score.compute against the surface-filtered diagnostic set
|
|
39822
40637
|
*
|
|
39823
40638
|
* The orchestrator owns spinner lifecycle via `Progress`; callers
|
|
39824
40639
|
* choose `Progress.layerOra(...)` for CLI feedback or
|
|
@@ -39876,10 +40691,21 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
39876
40691
|
ignoredTags: input.ignoredTags
|
|
39877
40692
|
})
|
|
39878
40693
|
])));
|
|
39879
|
-
const
|
|
40694
|
+
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40695
|
+
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40696
|
+
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
39880
40697
|
rootDirectory: scanDirectory,
|
|
39881
40698
|
userConfig: resolvedConfig.config
|
|
39882
|
-
})))
|
|
40699
|
+
}))).pipe(map$3((diagnostics) => ({
|
|
40700
|
+
diagnostics,
|
|
40701
|
+
timedOut: false
|
|
40702
|
+
})), timeout(supplyChainOverlapTimeout), orElseSucceed(() => ({
|
|
40703
|
+
diagnostics: [],
|
|
40704
|
+
timedOut: true
|
|
40705
|
+
}))) : succeed$2({
|
|
40706
|
+
diagnostics: [],
|
|
40707
|
+
timedOut: false
|
|
40708
|
+
}));
|
|
39883
40709
|
const lintFailure = yield* make$13({
|
|
39884
40710
|
didFail: false,
|
|
39885
40711
|
reason: null,
|
|
@@ -39890,12 +40716,49 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
39890
40716
|
didFail: false,
|
|
39891
40717
|
reason: null
|
|
39892
40718
|
});
|
|
39893
|
-
const scanConcurrency = yield* OxlintConcurrency;
|
|
40719
|
+
const scanConcurrency = resolveScanConcurrency(yield* OxlintConcurrency);
|
|
40720
|
+
const lintPhaseTimeoutMs = yield* LintPhaseTimeoutMs;
|
|
40721
|
+
const deadCodePhaseTimeoutMs = yield* DeadCodePhaseTimeoutMs;
|
|
40722
|
+
const resolveDeadCodePhaseTimeoutMs = (scaledPhaseTimeoutMs) => deadCodePhaseTimeoutMs === 15e4 ? scaledPhaseTimeoutMs : deadCodePhaseTimeoutMs;
|
|
39894
40723
|
const workerCountSuffix = scanConcurrency > 1 ? ` ${highlighter.dim(`[~${scanConcurrency} workers]`)}` : "";
|
|
40724
|
+
const shouldRunDeadCode = input.runDeadCode && !isDiffMode && (showWarnings || deadCodeMaySurfaceWhenWarningsHidden(resolvedConfig.config));
|
|
40725
|
+
const deadCodeOverlapMode = yield* DeadCodeOverlap;
|
|
40726
|
+
const shouldOverlapDeadCode = shouldRunDeadCode && deadCodeOverlapMode === "on";
|
|
40727
|
+
const deadCodeParseConcurrency = shouldOverlapDeadCode ? Math.max(1, Math.floor(scanConcurrency * DEAD_CODE_OVERLAP_PARSE_SHARE)) : void 0;
|
|
40728
|
+
const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
|
|
40729
|
+
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
40730
|
+
rootDirectory: scanDirectory,
|
|
40731
|
+
userConfig: resolvedConfig.config,
|
|
40732
|
+
parseConcurrency: deadCodeParseConcurrency,
|
|
40733
|
+
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
40734
|
+
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
40735
|
+
yield* set(deadCodeFailure, {
|
|
40736
|
+
didFail: true,
|
|
40737
|
+
reason: error.message
|
|
40738
|
+
});
|
|
40739
|
+
return empty$4;
|
|
40740
|
+
})))))).pipe(timeoutOption(deadCodeTimeout.phaseTimeoutMs), flatMap$2(match$3({
|
|
40741
|
+
onNone: () => set(deadCodeFailure, {
|
|
40742
|
+
didFail: true,
|
|
40743
|
+
reason: `Dead-code analysis exceeded ${Math.round(deadCodeTimeout.phaseTimeoutMs / MILLISECONDS_PER_SECOND)}s and was skipped.`
|
|
40744
|
+
}).pipe(as([])),
|
|
40745
|
+
onSome: succeed$2
|
|
40746
|
+
})));
|
|
40747
|
+
const overlapDeadCodeTimeout = resolveDeadCodeTimeout({
|
|
40748
|
+
sourceFileCount: project.sourceFileCount,
|
|
40749
|
+
deadCodeConcurrency: deadCodeParseConcurrency ?? scanConcurrency,
|
|
40750
|
+
fullConcurrency: scanConcurrency
|
|
40751
|
+
});
|
|
40752
|
+
const deadCodeFiber = shouldOverlapDeadCode ? yield* forkChild(buildCollectDeadCode({
|
|
40753
|
+
workerTimeoutMs: overlapDeadCodeTimeout.workerTimeoutMs,
|
|
40754
|
+
phaseTimeoutMs: resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs)
|
|
40755
|
+
})) : null;
|
|
39895
40756
|
const scanProgress = yield* progressService.start("Scanning...");
|
|
39896
40757
|
const scanStartTime = Date.now();
|
|
39897
40758
|
let lastReportedTotalFileCount = 0;
|
|
39898
|
-
|
|
40759
|
+
let lintCacheHitFileCount = null;
|
|
40760
|
+
let lintCacheTotalFileCount = null;
|
|
40761
|
+
const baseLintStream = linterService.run({
|
|
39899
40762
|
rootDirectory: scanDirectory,
|
|
39900
40763
|
project,
|
|
39901
40764
|
includePaths: lintIncludePaths ?? void 0,
|
|
@@ -39909,6 +40772,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
39909
40772
|
onFileProgress: (scannedFileCount, totalFileCount) => {
|
|
39910
40773
|
lastReportedTotalFileCount = totalFileCount;
|
|
39911
40774
|
runSync(scanProgress.update(`Scanning files (${scannedFileCount}/${totalFileCount})${workerCountSuffix}...`));
|
|
40775
|
+
},
|
|
40776
|
+
onCacheStats: (cacheHitFileCount, totalConsideredFileCount) => {
|
|
40777
|
+
lintCacheHitFileCount = cacheHitFileCount;
|
|
40778
|
+
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
39912
40779
|
}
|
|
39913
40780
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
39914
40781
|
yield* set(lintFailure, {
|
|
@@ -39918,36 +40785,54 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
39918
40785
|
reasonKind: error.reason._tag === "OxlintUnavailable" ? error.reason.kind : null
|
|
39919
40786
|
});
|
|
39920
40787
|
return empty$4;
|
|
39921
|
-
}))))
|
|
40788
|
+
}))));
|
|
40789
|
+
const lintCollected = yield* runCollect(applyPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
|
|
40790
|
+
onNone: () => set(lintFailure, {
|
|
40791
|
+
didFail: true,
|
|
40792
|
+
reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
|
|
40793
|
+
reasonTag: "OxlintBatchExceeded",
|
|
40794
|
+
reasonKind: null
|
|
40795
|
+
}).pipe(as([])),
|
|
40796
|
+
onSome: succeed$2
|
|
40797
|
+
})));
|
|
39922
40798
|
const lintFailureState = yield* get$2(lintFailure);
|
|
39923
40799
|
yield* afterLint(lintFailureState.didFail);
|
|
39924
40800
|
if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
|
|
39925
40801
|
const totalFileCount = lastReportedTotalFileCount || (lintIncludePaths?.length ?? project.sourceFileCount);
|
|
39926
40802
|
const scannedFilesLabel = `${totalFileCount} ${totalFileCount === 1 ? "file" : "files"}`;
|
|
39927
|
-
|
|
39928
|
-
|
|
39929
|
-
|
|
39930
|
-
|
|
39931
|
-
|
|
39932
|
-
|
|
39933
|
-
|
|
39934
|
-
|
|
40803
|
+
let deadCodeCollected = [];
|
|
40804
|
+
if (lintFailureState.didFail) {
|
|
40805
|
+
if (deadCodeFiber !== null) yield* interrupt(deadCodeFiber);
|
|
40806
|
+
} else if (shouldRunDeadCode) {
|
|
40807
|
+
yield* scanProgress.update(`Scanned ${scannedFilesLabel}, analyzing dead code...`);
|
|
40808
|
+
const sequentialDeadCodeTimeout = resolveDeadCodeTimeout({
|
|
40809
|
+
sourceFileCount: totalFileCount,
|
|
40810
|
+
deadCodeConcurrency: scanConcurrency,
|
|
40811
|
+
fullConcurrency: scanConcurrency
|
|
39935
40812
|
});
|
|
39936
|
-
|
|
39937
|
-
|
|
39938
|
-
|
|
40813
|
+
deadCodeCollected = deadCodeFiber !== null ? yield* join(deadCodeFiber) : yield* buildCollectDeadCode({
|
|
40814
|
+
workerTimeoutMs: sequentialDeadCodeTimeout.workerTimeoutMs,
|
|
40815
|
+
phaseTimeoutMs: resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs)
|
|
40816
|
+
});
|
|
40817
|
+
}
|
|
40818
|
+
const deadCodeFailureState = lintFailureState.didFail ? {
|
|
40819
|
+
didFail: false,
|
|
40820
|
+
reason: null
|
|
40821
|
+
} : yield* get$2(deadCodeFailure);
|
|
39939
40822
|
const scanElapsedMilliseconds = Date.now() - scanStartTime;
|
|
39940
|
-
const scanElapsedSeconds = (scanElapsedMilliseconds /
|
|
40823
|
+
const scanElapsedSeconds = (scanElapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1);
|
|
39941
40824
|
if (!lintFailureState.didFail) if (deadCodeFailureState.didFail) yield* scanProgress.fail(DEAD_CODE_FAIL_TEXT);
|
|
39942
40825
|
else if (input.suppressScanSummary) yield* scanProgress.stop();
|
|
39943
40826
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
40827
|
+
const supplyChainResult = yield* join(supplyChainFiber);
|
|
40828
|
+
const supplyChainCollected = supplyChainResult.diagnostics;
|
|
39944
40829
|
yield* reporterService.finalize;
|
|
39945
|
-
const finalDiagnostics = [
|
|
40830
|
+
const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
|
|
39946
40831
|
...envCollected,
|
|
39947
40832
|
...supplyChainCollected,
|
|
39948
40833
|
...lintCollected,
|
|
39949
40834
|
...deadCodeCollected
|
|
39950
|
-
];
|
|
40835
|
+
]));
|
|
39951
40836
|
const githubViewerPermission = yield* join(githubViewerPermissionFiber);
|
|
39952
40837
|
const scoreMetadata = {
|
|
39953
40838
|
...repo !== null ? { repo } : {},
|
|
@@ -39983,9 +40868,14 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
39983
40868
|
lintPartialFailures,
|
|
39984
40869
|
didDeadCodeFail: deadCodeFailureState.didFail,
|
|
39985
40870
|
deadCodeFailureReason: deadCodeFailureState.reason,
|
|
40871
|
+
deadCodeOverlapped: shouldOverlapDeadCode,
|
|
39986
40872
|
scannedFileCount: totalFileCount,
|
|
39987
40873
|
scannedFilePaths,
|
|
39988
|
-
scanElapsedMilliseconds
|
|
40874
|
+
scanElapsedMilliseconds,
|
|
40875
|
+
scanConcurrency,
|
|
40876
|
+
supplyChainOverlapTimedOut: supplyChainResult.timedOut,
|
|
40877
|
+
lintCacheHitFileCount,
|
|
40878
|
+
lintCacheTotalFileCount
|
|
39989
40879
|
};
|
|
39990
40880
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
39991
40881
|
"inspect.directory": input.directory,
|
|
@@ -39993,7 +40883,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
39993
40883
|
"inspect.runDeadCode": input.runDeadCode,
|
|
39994
40884
|
"inspect.isCi": input.isCi,
|
|
39995
40885
|
"inspect.scoreSurface": input.scoreSurface ?? "score"
|
|
39996
|
-
} }));
|
|
40886
|
+
} }), (scanProgram) => flatMap$2(ScanDeadlineMs, (scanDeadlineMs) => scanProgram.pipe(timeout(scanDeadlineMs), catchTag$1("TimeoutError", () => new ReactDoctorError({ reason: new ScanDeadlineExceeded({ detail: `${scanDeadlineMs / MILLISECONDS_PER_SECOND}s elapsed` }) })))));
|
|
39997
40887
|
const parseNodeVersion = (versionString) => {
|
|
39998
40888
|
const [major = 0, minor = 0, patch = 0] = versionString.replace(/^v/, "").trim().split(".").map(Number);
|
|
39999
40889
|
return {
|
|
@@ -40151,7 +41041,7 @@ const materializeSourceTree = (input) => gen(function* () {
|
|
|
40151
41041
|
static layerNode = effect(StagedFiles, gen(function* () {
|
|
40152
41042
|
const git = yield* Git;
|
|
40153
41043
|
return StagedFiles.of({
|
|
40154
|
-
discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile))),
|
|
41044
|
+
discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile)), withSpan("StagedFiles.discoverSourceFiles")),
|
|
40155
41045
|
materialize: ({ directory, stagedFiles, tempDirectory }) => materializeSourceTree({
|
|
40156
41046
|
directory,
|
|
40157
41047
|
files: stagedFiles,
|
|
@@ -40161,7 +41051,7 @@ const materializeSourceTree = (input) => gen(function* () {
|
|
|
40161
41051
|
tempDirectory: tree.tempDirectory,
|
|
40162
41052
|
stagedFiles: tree.materializedFiles,
|
|
40163
41053
|
cleanup: tree.cleanup
|
|
40164
|
-
})))
|
|
41054
|
+
})), withSpan("StagedFiles.materialize"))
|
|
40165
41055
|
});
|
|
40166
41056
|
}));
|
|
40167
41057
|
/**
|
|
@@ -40229,7 +41119,10 @@ const runEditorScan = async (input) => {
|
|
|
40229
41119
|
isCi: false,
|
|
40230
41120
|
resolveLocalGithubViewerPermission: false,
|
|
40231
41121
|
skipJsxIncludeFilter: true
|
|
40232
|
-
}).pipe(
|
|
41122
|
+
}).pipe(withSpan("runEditorScan", { attributes: {
|
|
41123
|
+
"editor.lint": lint,
|
|
41124
|
+
"editor.runDeadCode": runDeadCode
|
|
41125
|
+
} }), provide(layers), provide(layerOtlp)));
|
|
40233
41126
|
if (isSuccess(exit)) {
|
|
40234
41127
|
const output = exit.value;
|
|
40235
41128
|
return {
|
|
@@ -40259,7 +41152,7 @@ const runEditorScan = async (input) => {
|
|
|
40259
41152
|
didDeadCodeFail: false,
|
|
40260
41153
|
deadCodeFailureReason: null,
|
|
40261
41154
|
lintPartialFailures: [],
|
|
40262
|
-
error:
|
|
41155
|
+
error: messageFromUnknown(error)
|
|
40263
41156
|
};
|
|
40264
41157
|
};
|
|
40265
41158
|
/**
|
|
@@ -40311,7 +41204,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
40311
41204
|
/** Display name used in client-facing messages and progress titles. */
|
|
40312
41205
|
const SERVER_DISPLAY_NAME = "React Doctor";
|
|
40313
41206
|
/** Server version reported in `serverInfo`; injected at build, `dev` from source. */
|
|
40314
|
-
const SERVER_VERSION = "0.5.
|
|
41207
|
+
const SERVER_VERSION = "0.5.7";
|
|
40315
41208
|
/** `Diagnostic.source` shown next to every published diagnostic. */
|
|
40316
41209
|
const DIAGNOSTIC_SOURCE = "react-doctor";
|
|
40317
41210
|
/**
|
|
@@ -40541,7 +41434,6 @@ const toLspDiagnostic = (input) => {
|
|
|
40541
41434
|
data
|
|
40542
41435
|
};
|
|
40543
41436
|
};
|
|
40544
|
-
const toUri = (absoluteFilePath) => fsPathToUri(absoluteFilePath);
|
|
40545
41437
|
/**
|
|
40546
41438
|
* Owns the published-diagnostic state. Maps scan outcomes to LSP
|
|
40547
41439
|
* diagnostics, publishes complete per-URI replacement sets (so the
|
|
@@ -40571,7 +41463,7 @@ var DiagnosticsManager = class {
|
|
|
40571
41463
|
const isProtectedPath = (fsPath) => protectOpen && this.isOpen(fsPath);
|
|
40572
41464
|
for (const [fsPath, coreDiagnostics] of outcome.byFile) {
|
|
40573
41465
|
if (isProtectedPath(fsPath)) continue;
|
|
40574
|
-
const uri =
|
|
41466
|
+
const uri = fsPathToUri(fsPath);
|
|
40575
41467
|
const text = this.textProvider(fsPath);
|
|
40576
41468
|
const lspDiagnostics = coreDiagnostics.map((diagnostic) => toLspDiagnostic({
|
|
40577
41469
|
diagnostic,
|
|
@@ -40593,7 +41485,7 @@ var DiagnosticsManager = class {
|
|
|
40593
41485
|
for (const fsPath of outcome.requestedPaths) {
|
|
40594
41486
|
if (isProtectedPath(fsPath)) continue;
|
|
40595
41487
|
if (outcome.byFile.has(fsPath)) continue;
|
|
40596
|
-
const uri =
|
|
41488
|
+
const uri = fsPathToUri(fsPath);
|
|
40597
41489
|
if (this.byUri.has(uri)) this.byUri.delete(uri);
|
|
40598
41490
|
this.publish(uri, []);
|
|
40599
41491
|
}
|
|
@@ -40618,7 +41510,7 @@ var DiagnosticsManager = class {
|
|
|
40618
41510
|
const set = this.projectUris.get(project) ?? /* @__PURE__ */ new Set();
|
|
40619
41511
|
for (const uri of liveUris) set.add(uri);
|
|
40620
41512
|
for (const fsPath of outcome.requestedPaths) {
|
|
40621
|
-
const uri =
|
|
41513
|
+
const uri = fsPathToUri(fsPath);
|
|
40622
41514
|
if (!liveUris.has(uri)) set.delete(uri);
|
|
40623
41515
|
}
|
|
40624
41516
|
this.projectUris.set(project, set);
|
|
@@ -40646,7 +41538,7 @@ var DiagnosticsManager = class {
|
|
|
40646
41538
|
const tracked = this.projectUris.get(project);
|
|
40647
41539
|
if (!tracked) return;
|
|
40648
41540
|
const liveUris = /* @__PURE__ */ new Set();
|
|
40649
|
-
for (const fsPath of liveFsPaths) liveUris.add(
|
|
41541
|
+
for (const fsPath of liveFsPaths) liveUris.add(fsPathToUri(fsPath));
|
|
40650
41542
|
for (const uri of [...tracked]) {
|
|
40651
41543
|
if (liveUris.has(uri)) continue;
|
|
40652
41544
|
this.byUri.delete(uri);
|
|
@@ -40943,7 +41835,7 @@ const createProjectGraph = (options) => {
|
|
|
40943
41835
|
});
|
|
40944
41836
|
}
|
|
40945
41837
|
} catch (error) {
|
|
40946
|
-
logger.warn(`Project discovery failed for ${root}: ${
|
|
41838
|
+
logger.warn(`Project discovery failed for ${root}: ${messageFromUnknown(error)}`);
|
|
40947
41839
|
}
|
|
40948
41840
|
return [...seen.values()].sort((first, second) => second.directory.length - first.directory.length);
|
|
40949
41841
|
};
|
|
@@ -40967,10 +41859,16 @@ const createProjectGraph = (options) => {
|
|
|
40967
41859
|
clearPackageJsonCache();
|
|
40968
41860
|
clearIgnorePatternsCache();
|
|
40969
41861
|
clearAutoSuppressionCaches();
|
|
41862
|
+
clearMinifiedFileCache();
|
|
40970
41863
|
projects = null;
|
|
40971
41864
|
}
|
|
40972
41865
|
};
|
|
40973
41866
|
};
|
|
41867
|
+
const toProjectRelative = (projectDirectory, filePath) => {
|
|
41868
|
+
const relative = Path.relative(projectDirectory, filePath).replace(/\\/g, "/");
|
|
41869
|
+
if (relative.length === 0 || relative.startsWith("../") || Path.isAbsolute(relative)) return null;
|
|
41870
|
+
return relative;
|
|
41871
|
+
};
|
|
40974
41872
|
const resolveCacheFilePath = (projectDirectory) => {
|
|
40975
41873
|
const nodeModules = path.join(projectDirectory, "node_modules");
|
|
40976
41874
|
if (fs.existsSync(nodeModules)) return path.join(nodeModules, ".cache", "react-doctor", "lint-cache.json");
|
|
@@ -41005,7 +41903,7 @@ const createLintCache = (input) => {
|
|
|
41005
41903
|
fs.writeFileSync(tempPath, JSON.stringify(payload));
|
|
41006
41904
|
fs.renameSync(tempPath, cacheFilePath);
|
|
41007
41905
|
} catch (error) {
|
|
41008
|
-
logger.warn(`Failed to persist lint cache: ${
|
|
41906
|
+
logger.warn(`Failed to persist lint cache: ${messageFromUnknown(error)}`);
|
|
41009
41907
|
}
|
|
41010
41908
|
};
|
|
41011
41909
|
return {
|
|
@@ -41031,11 +41929,6 @@ const createLintCache = (input) => {
|
|
|
41031
41929
|
};
|
|
41032
41930
|
const OVERLAY_TEMP_PREFIX = "react-doctor-lsp-";
|
|
41033
41931
|
const OVERLAY_CONFIG_FILENAMES = [...new Set([...STAGED_FILES_PROJECT_CONFIG_FILENAMES, ...ADOPTABLE_LINT_CONFIG_FILENAMES])];
|
|
41034
|
-
const toProjectRelative$1 = (projectDirectory, filePath) => {
|
|
41035
|
-
const relative = path.relative(projectDirectory, filePath).replace(/\\/g, "/");
|
|
41036
|
-
if (relative.length === 0 || relative.startsWith("../") || path.isAbsolute(relative)) return null;
|
|
41037
|
-
return relative;
|
|
41038
|
-
};
|
|
41039
41932
|
/**
|
|
41040
41933
|
* Writes the live (possibly unsaved) content of the target files into a
|
|
41041
41934
|
* throwaway temp tree that mirrors the project, alongside the well-known
|
|
@@ -41049,7 +41942,7 @@ const materializeOverlay = (input) => {
|
|
|
41049
41942
|
const relativePaths = [];
|
|
41050
41943
|
try {
|
|
41051
41944
|
for (const filePath of input.files) {
|
|
41052
|
-
const relative = toProjectRelative
|
|
41945
|
+
const relative = toProjectRelative(input.projectDirectory, filePath);
|
|
41053
41946
|
if (relative === null) continue;
|
|
41054
41947
|
const content = input.readText(filePath);
|
|
41055
41948
|
if (content === null) continue;
|
|
@@ -41097,11 +41990,6 @@ const materializeOverlay = (input) => {
|
|
|
41097
41990
|
throw error;
|
|
41098
41991
|
}
|
|
41099
41992
|
};
|
|
41100
|
-
const toProjectRelative = (projectDirectory, filePath) => {
|
|
41101
|
-
const relative = path.relative(projectDirectory, filePath).replace(/\\/g, "/");
|
|
41102
|
-
if (relative.length === 0 || relative.startsWith("../") || path.isAbsolute(relative)) return null;
|
|
41103
|
-
return relative;
|
|
41104
|
-
};
|
|
41105
41993
|
/**
|
|
41106
41994
|
* Resolves a diagnostic's (possibly relative, possibly overlay-temp)
|
|
41107
41995
|
* file path back to the canonical absolute path inside the real project.
|
|
@@ -41323,7 +42211,7 @@ const createScheduler = (options) => {
|
|
|
41323
42211
|
if (outcome && !token.isCancelled) options.onResult(outcome);
|
|
41324
42212
|
}).catch((error) => {
|
|
41325
42213
|
if (options.onError) options.onError(error, request);
|
|
41326
|
-
else logger.error(`Scan failed: ${
|
|
42214
|
+
else logger.error(`Scan failed: ${messageFromUnknown(error)}`);
|
|
41327
42215
|
}).finally(() => {
|
|
41328
42216
|
running -= 1;
|
|
41329
42217
|
if (isBackground) runningBackground -= 1;
|
|
@@ -41710,7 +42598,7 @@ const createServer = (connection, options = {}) => {
|
|
|
41710
42598
|
maybeWarnLintUnavailable(outcome);
|
|
41711
42599
|
if (outcome.request.priority === "background") scanTelemetry.accumulate(outcome);
|
|
41712
42600
|
},
|
|
41713
|
-
onError: (error, request) => logger.error(`Scan of ${request.projectDirectory} threw: ${
|
|
42601
|
+
onError: (error, request) => logger.error(`Scan of ${request.projectDirectory} threw: ${messageFromUnknown(error)}`),
|
|
41714
42602
|
onIdleChange: (idle) => {
|
|
41715
42603
|
setBusy(!idle);
|
|
41716
42604
|
if (idle) scanTelemetry.finish();
|
|
@@ -42358,5 +43246,5 @@ const startLanguageServer = () => {
|
|
|
42358
43246
|
};
|
|
42359
43247
|
//#endregion
|
|
42360
43248
|
export { startLanguageServer };
|
|
42361
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
42362
|
-
//# debugId=
|
|
43249
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="c9a5a002-c128-5584-a875-0fe281ab7ae0")}catch(e){}}();
|
|
43250
|
+
//# debugId=c9a5a002-c128-5584-a875-0fe281ab7ae0
|