react-doctor 0.6.2-dev.397816a → 0.6.2-dev.ac71a3b
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 +94 -23
- package/dist/index.js +91 -20
- package/dist/lsp.js +91 -20
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!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]="
|
|
2
|
+
!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]="d9bbcb80-9ea3-576e-8f35-8215ed87eca7")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as NodeChildProcess from "node:child_process";
|
|
5
5
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -38678,7 +38678,7 @@ const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
|
38678
38678
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
38679
38679
|
};
|
|
38680
38680
|
const resolveLintBatchOrdering = () => {
|
|
38681
|
-
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "
|
|
38681
|
+
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "arrival" ? "arrival" : "cost";
|
|
38682
38682
|
};
|
|
38683
38683
|
/**
|
|
38684
38684
|
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
@@ -38775,17 +38775,19 @@ var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", {
|
|
|
38775
38775
|
return "auto";
|
|
38776
38776
|
} }) {};
|
|
38777
38777
|
/**
|
|
38778
|
-
* How the full-scan lint pass
|
|
38779
|
-
*
|
|
38780
|
-
*
|
|
38781
|
-
*
|
|
38782
|
-
* the
|
|
38783
|
-
*
|
|
38784
|
-
*
|
|
38785
|
-
*
|
|
38786
|
-
* `
|
|
38787
|
-
*
|
|
38788
|
-
*
|
|
38778
|
+
* How the full-scan lint pass plans its file batches. `"cost"` (the default)
|
|
38779
|
+
* builds size-balanced LPT batches (`planLintBatches`): the same mandatory
|
|
38780
|
+
* batch count as greedy chunking (`ceil(files / 100)`), but every batch gets
|
|
38781
|
+
* an even share of files AND bytes, so no 100-file chunk is a straggler while
|
|
38782
|
+
* the remainder-batch worker idles — and the heavy files are SPREAD across
|
|
38783
|
+
* batches, the precondition the old sort-desc-then-chunk-100 `cost` mode
|
|
38784
|
+
* lacked (it packed the heaviest files into one wave-1 straggler batch,
|
|
38785
|
+
* measurably regressing size-skewed repos, which is why it never earned the
|
|
38786
|
+
* default). `"arrival"` (`REACT_DOCTOR_LINT_BATCH_ORDERING=arrival`) is the
|
|
38787
|
+
* rollback hatch to plain greedy 100-file chunking in discovery order. Tests
|
|
38788
|
+
* override via `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans
|
|
38789
|
+
* never reach this — they pass user-scoped `includePaths` that skip discovery
|
|
38790
|
+
* and stay in arrival order; only the full-scan branch reads it.
|
|
38789
38791
|
*/
|
|
38790
38792
|
var LintBatchOrdering = class extends Reference("react-doctor/LintBatchOrdering", { defaultValue: resolveLintBatchOrdering }) {};
|
|
38791
38793
|
const CACHE_DISABLED_VALUES$1 = new Set(["1", "true"]);
|
|
@@ -43075,6 +43077,70 @@ const hashFileContents = (filePath) => {
|
|
|
43075
43077
|
return null;
|
|
43076
43078
|
}
|
|
43077
43079
|
};
|
|
43080
|
+
/**
|
|
43081
|
+
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
43082
|
+
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
43083
|
+
* remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
|
|
43084
|
+
* is set by whichever 100-file chunk happened to collect the most work while
|
|
43085
|
+
* the remainder's worker idles), this planner keeps the SAME mandatory batch
|
|
43086
|
+
* count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
|
|
43087
|
+
* perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
|
|
43088
|
+
* each to the least-loaded batch by cumulative byte size. Every batch ends up
|
|
43089
|
+
* with an even share of files AND bytes, so no batch is a straggler; heavy
|
|
43090
|
+
* files are SPREAD across batches (the documented precondition for `cost`
|
|
43091
|
+
* ordering to beat `arrival`) instead of concentrated in one.
|
|
43092
|
+
*
|
|
43093
|
+
* Deliberately NOT worker-count-aware: splitting into extra batches to feed
|
|
43094
|
+
* idle workers was measured (619-file corpus, 10 cores) to REGRESS the lint
|
|
43095
|
+
* wall — each additional concurrent subprocess pays a contended cold start
|
|
43096
|
+
* (node boot + plugin import + native dlopen, ~180 ms uncontended and 2-3×
|
|
43097
|
+
* that in a full wave) that outweighs the smaller per-batch file share.
|
|
43098
|
+
*
|
|
43099
|
+
* Every batch also respects the spawn-args char budget; a file that fits no
|
|
43100
|
+
* existing batch opens a new one, mirroring `batchIncludePaths`. Within a
|
|
43101
|
+
* batch, files return in arrival order, and the assignment is fully
|
|
43102
|
+
* deterministic (ties broken by lowest batch index), so a plan is
|
|
43103
|
+
* reproducible for a fixed (files, sizes) input. Batch composition cannot
|
|
43104
|
+
* change diagnostics — rules that look beyond one file (`CROSS_FILE_RULE_IDS`)
|
|
43105
|
+
* read the OTHER files via the filesystem, never via batch-mates — so the
|
|
43106
|
+
* plan only moves wall-clock time.
|
|
43107
|
+
*
|
|
43108
|
+
* O(files × batches); the linear least-loaded scan is fine at real-world
|
|
43109
|
+
* scale (a 3,500-file monorepo is 36 batches ≈ 126k comparisons).
|
|
43110
|
+
*/
|
|
43111
|
+
const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
43112
|
+
if (files.length === 0) return [];
|
|
43113
|
+
const batchCount = Math.ceil(files.length / 100);
|
|
43114
|
+
const maxFilesPerBatch = Math.ceil(files.length / batchCount);
|
|
43115
|
+
const baseArgsLengthChars = estimateArgsLength(baseArgs);
|
|
43116
|
+
const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
|
|
43117
|
+
const batches = Array.from({ length: batchCount }, () => ({
|
|
43118
|
+
fileIndexes: [],
|
|
43119
|
+
totalSizeBytes: 0,
|
|
43120
|
+
argsLengthChars: baseArgsLengthChars
|
|
43121
|
+
}));
|
|
43122
|
+
for (const fileIndex of descendingSizeIndexes) {
|
|
43123
|
+
const entryLengthChars = files[fileIndex].length + 1;
|
|
43124
|
+
let targetBatch = null;
|
|
43125
|
+
for (const batch of batches) {
|
|
43126
|
+
if (batch.fileIndexes.length >= maxFilesPerBatch) continue;
|
|
43127
|
+
if (batch.fileIndexes.length > 0 && batch.argsLengthChars + entryLengthChars > 24e3) continue;
|
|
43128
|
+
if (targetBatch === null || batch.totalSizeBytes < targetBatch.totalSizeBytes || batch.totalSizeBytes === targetBatch.totalSizeBytes && batch.fileIndexes.length < targetBatch.fileIndexes.length) targetBatch = batch;
|
|
43129
|
+
}
|
|
43130
|
+
if (targetBatch === null) {
|
|
43131
|
+
targetBatch = {
|
|
43132
|
+
fileIndexes: [],
|
|
43133
|
+
totalSizeBytes: 0,
|
|
43134
|
+
argsLengthChars: baseArgsLengthChars
|
|
43135
|
+
};
|
|
43136
|
+
batches.push(targetBatch);
|
|
43137
|
+
}
|
|
43138
|
+
targetBatch.fileIndexes.push(fileIndex);
|
|
43139
|
+
targetBatch.totalSizeBytes += sizeByFile.get(files[fileIndex]) ?? 0;
|
|
43140
|
+
targetBatch.argsLengthChars += entryLengthChars;
|
|
43141
|
+
}
|
|
43142
|
+
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
43143
|
+
};
|
|
43078
43144
|
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
43079
43145
|
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
43080
43146
|
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
@@ -43090,7 +43156,6 @@ const resolveUserCacheScope = () => {
|
|
|
43090
43156
|
return "shared";
|
|
43091
43157
|
}
|
|
43092
43158
|
};
|
|
43093
|
-
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
43094
43159
|
/**
|
|
43095
43160
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
43096
43161
|
* the runner: once for the primary scan, once for the
|
|
@@ -43149,7 +43214,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
43149
43214
|
* 6. always restore disable directives + clean up the temp dir
|
|
43150
43215
|
*/
|
|
43151
43216
|
const runOxlint = async (options) => {
|
|
43152
|
-
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 = "
|
|
43217
|
+
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 = "cost" } = options;
|
|
43153
43218
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
43154
43219
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
43155
43220
|
validateRuleRegistration();
|
|
@@ -43201,8 +43266,14 @@ const runOxlint = async (options) => {
|
|
|
43201
43266
|
"json",
|
|
43202
43267
|
...sharedArgs
|
|
43203
43268
|
];
|
|
43204
|
-
const
|
|
43205
|
-
const candidateFiles = includePaths !== void 0 ? includePaths :
|
|
43269
|
+
const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
|
|
43270
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
|
|
43271
|
+
const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
|
|
43272
|
+
const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
|
|
43273
|
+
baseArgs: passBaseArgs,
|
|
43274
|
+
files: passFiles,
|
|
43275
|
+
sizeByFile
|
|
43276
|
+
}) : batchIncludePaths(passBaseArgs, passFiles);
|
|
43206
43277
|
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
43207
43278
|
if (files.length === 0) return {
|
|
43208
43279
|
diagnostics: [],
|
|
@@ -43216,7 +43287,7 @@ const runOxlint = async (options) => {
|
|
|
43216
43287
|
};
|
|
43217
43288
|
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
43218
43289
|
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
43219
|
-
const passFileBatches =
|
|
43290
|
+
const passFileBatches = buildFileBatches(passBaseArgs, files);
|
|
43220
43291
|
const spawnPass = () => spawnLintBatches({
|
|
43221
43292
|
baseArgs: passBaseArgs,
|
|
43222
43293
|
fileBatches: passFileBatches,
|
|
@@ -43318,7 +43389,7 @@ const runOxlint = async (options) => {
|
|
|
43318
43389
|
]);
|
|
43319
43390
|
}
|
|
43320
43391
|
const baseArgs = makeBaseArgs(configPath);
|
|
43321
|
-
const fileBatches =
|
|
43392
|
+
const fileBatches = buildFileBatches(baseArgs, candidateFiles);
|
|
43322
43393
|
const runBatches = () => spawnLintBatches({
|
|
43323
43394
|
baseArgs,
|
|
43324
43395
|
fileBatches,
|
|
@@ -45233,7 +45304,7 @@ const makeNoopConsole = () => ({
|
|
|
45233
45304
|
});
|
|
45234
45305
|
//#endregion
|
|
45235
45306
|
//#region src/cli/utils/version.ts
|
|
45236
|
-
const VERSION = "0.6.2-dev.
|
|
45307
|
+
const VERSION = "0.6.2-dev.ac71a3b";
|
|
45237
45308
|
//#endregion
|
|
45238
45309
|
//#region src/cli/utils/json-mode.ts
|
|
45239
45310
|
let context = null;
|
|
@@ -45602,13 +45673,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
45602
45673
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
45603
45674
|
* standard `SENTRY_RELEASE` override.
|
|
45604
45675
|
*/
|
|
45605
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.2-dev.
|
|
45676
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.2-dev.ac71a3b`;
|
|
45606
45677
|
/**
|
|
45607
45678
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
45608
45679
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
45609
45680
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
45610
45681
|
*/
|
|
45611
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.2-dev.
|
|
45682
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.2-dev.ac71a3b") ? "development" : "production");
|
|
45612
45683
|
/**
|
|
45613
45684
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
45614
45685
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -63591,4 +63662,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
63591
63662
|
export {};
|
|
63592
63663
|
|
|
63593
63664
|
//# sourceMappingURL=cli.js.map
|
|
63594
|
-
//# debugId=
|
|
63665
|
+
//# debugId=d9bbcb80-9ea3-576e-8f35-8215ed87eca7
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!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]="
|
|
2
|
+
!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]="1b2a9bb2-d6bc-5c9b-afbb-5b639438c617")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as NFS from "node:fs";
|
|
@@ -35479,7 +35479,7 @@ const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
|
35479
35479
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
35480
35480
|
};
|
|
35481
35481
|
const resolveLintBatchOrdering = () => {
|
|
35482
|
-
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "
|
|
35482
|
+
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "arrival" ? "arrival" : "cost";
|
|
35483
35483
|
};
|
|
35484
35484
|
/**
|
|
35485
35485
|
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
@@ -35576,17 +35576,19 @@ var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", {
|
|
|
35576
35576
|
return "auto";
|
|
35577
35577
|
} }) {};
|
|
35578
35578
|
/**
|
|
35579
|
-
* How the full-scan lint pass
|
|
35580
|
-
*
|
|
35581
|
-
*
|
|
35582
|
-
*
|
|
35583
|
-
* the
|
|
35584
|
-
*
|
|
35585
|
-
*
|
|
35586
|
-
*
|
|
35587
|
-
* `
|
|
35588
|
-
*
|
|
35589
|
-
*
|
|
35579
|
+
* How the full-scan lint pass plans its file batches. `"cost"` (the default)
|
|
35580
|
+
* builds size-balanced LPT batches (`planLintBatches`): the same mandatory
|
|
35581
|
+
* batch count as greedy chunking (`ceil(files / 100)`), but every batch gets
|
|
35582
|
+
* an even share of files AND bytes, so no 100-file chunk is a straggler while
|
|
35583
|
+
* the remainder-batch worker idles — and the heavy files are SPREAD across
|
|
35584
|
+
* batches, the precondition the old sort-desc-then-chunk-100 `cost` mode
|
|
35585
|
+
* lacked (it packed the heaviest files into one wave-1 straggler batch,
|
|
35586
|
+
* measurably regressing size-skewed repos, which is why it never earned the
|
|
35587
|
+
* default). `"arrival"` (`REACT_DOCTOR_LINT_BATCH_ORDERING=arrival`) is the
|
|
35588
|
+
* rollback hatch to plain greedy 100-file chunking in discovery order. Tests
|
|
35589
|
+
* override via `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans
|
|
35590
|
+
* never reach this — they pass user-scoped `includePaths` that skip discovery
|
|
35591
|
+
* and stay in arrival order; only the full-scan branch reads it.
|
|
35590
35592
|
*/
|
|
35591
35593
|
var LintBatchOrdering = class extends Reference("react-doctor/LintBatchOrdering", { defaultValue: resolveLintBatchOrdering }) {};
|
|
35592
35594
|
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
@@ -39832,6 +39834,70 @@ const hashFileContents = (filePath) => {
|
|
|
39832
39834
|
return null;
|
|
39833
39835
|
}
|
|
39834
39836
|
};
|
|
39837
|
+
/**
|
|
39838
|
+
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39839
|
+
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
39840
|
+
* remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
|
|
39841
|
+
* is set by whichever 100-file chunk happened to collect the most work while
|
|
39842
|
+
* the remainder's worker idles), this planner keeps the SAME mandatory batch
|
|
39843
|
+
* count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
|
|
39844
|
+
* perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
|
|
39845
|
+
* each to the least-loaded batch by cumulative byte size. Every batch ends up
|
|
39846
|
+
* with an even share of files AND bytes, so no batch is a straggler; heavy
|
|
39847
|
+
* files are SPREAD across batches (the documented precondition for `cost`
|
|
39848
|
+
* ordering to beat `arrival`) instead of concentrated in one.
|
|
39849
|
+
*
|
|
39850
|
+
* Deliberately NOT worker-count-aware: splitting into extra batches to feed
|
|
39851
|
+
* idle workers was measured (619-file corpus, 10 cores) to REGRESS the lint
|
|
39852
|
+
* wall — each additional concurrent subprocess pays a contended cold start
|
|
39853
|
+
* (node boot + plugin import + native dlopen, ~180 ms uncontended and 2-3×
|
|
39854
|
+
* that in a full wave) that outweighs the smaller per-batch file share.
|
|
39855
|
+
*
|
|
39856
|
+
* Every batch also respects the spawn-args char budget; a file that fits no
|
|
39857
|
+
* existing batch opens a new one, mirroring `batchIncludePaths`. Within a
|
|
39858
|
+
* batch, files return in arrival order, and the assignment is fully
|
|
39859
|
+
* deterministic (ties broken by lowest batch index), so a plan is
|
|
39860
|
+
* reproducible for a fixed (files, sizes) input. Batch composition cannot
|
|
39861
|
+
* change diagnostics — rules that look beyond one file (`CROSS_FILE_RULE_IDS`)
|
|
39862
|
+
* read the OTHER files via the filesystem, never via batch-mates — so the
|
|
39863
|
+
* plan only moves wall-clock time.
|
|
39864
|
+
*
|
|
39865
|
+
* O(files × batches); the linear least-loaded scan is fine at real-world
|
|
39866
|
+
* scale (a 3,500-file monorepo is 36 batches ≈ 126k comparisons).
|
|
39867
|
+
*/
|
|
39868
|
+
const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
39869
|
+
if (files.length === 0) return [];
|
|
39870
|
+
const batchCount = Math.ceil(files.length / 100);
|
|
39871
|
+
const maxFilesPerBatch = Math.ceil(files.length / batchCount);
|
|
39872
|
+
const baseArgsLengthChars = estimateArgsLength(baseArgs);
|
|
39873
|
+
const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
|
|
39874
|
+
const batches = Array.from({ length: batchCount }, () => ({
|
|
39875
|
+
fileIndexes: [],
|
|
39876
|
+
totalSizeBytes: 0,
|
|
39877
|
+
argsLengthChars: baseArgsLengthChars
|
|
39878
|
+
}));
|
|
39879
|
+
for (const fileIndex of descendingSizeIndexes) {
|
|
39880
|
+
const entryLengthChars = files[fileIndex].length + 1;
|
|
39881
|
+
let targetBatch = null;
|
|
39882
|
+
for (const batch of batches) {
|
|
39883
|
+
if (batch.fileIndexes.length >= maxFilesPerBatch) continue;
|
|
39884
|
+
if (batch.fileIndexes.length > 0 && batch.argsLengthChars + entryLengthChars > 24e3) continue;
|
|
39885
|
+
if (targetBatch === null || batch.totalSizeBytes < targetBatch.totalSizeBytes || batch.totalSizeBytes === targetBatch.totalSizeBytes && batch.fileIndexes.length < targetBatch.fileIndexes.length) targetBatch = batch;
|
|
39886
|
+
}
|
|
39887
|
+
if (targetBatch === null) {
|
|
39888
|
+
targetBatch = {
|
|
39889
|
+
fileIndexes: [],
|
|
39890
|
+
totalSizeBytes: 0,
|
|
39891
|
+
argsLengthChars: baseArgsLengthChars
|
|
39892
|
+
};
|
|
39893
|
+
batches.push(targetBatch);
|
|
39894
|
+
}
|
|
39895
|
+
targetBatch.fileIndexes.push(fileIndex);
|
|
39896
|
+
targetBatch.totalSizeBytes += sizeByFile.get(files[fileIndex]) ?? 0;
|
|
39897
|
+
targetBatch.argsLengthChars += entryLengthChars;
|
|
39898
|
+
}
|
|
39899
|
+
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
39900
|
+
};
|
|
39835
39901
|
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
39836
39902
|
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
39837
39903
|
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
@@ -39847,7 +39913,6 @@ const resolveUserCacheScope = () => {
|
|
|
39847
39913
|
return "shared";
|
|
39848
39914
|
}
|
|
39849
39915
|
};
|
|
39850
|
-
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39851
39916
|
/**
|
|
39852
39917
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
39853
39918
|
* the runner: once for the primary scan, once for the
|
|
@@ -39906,7 +39971,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
39906
39971
|
* 6. always restore disable directives + clean up the temp dir
|
|
39907
39972
|
*/
|
|
39908
39973
|
const runOxlint = async (options) => {
|
|
39909
|
-
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 = "
|
|
39974
|
+
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 = "cost" } = options;
|
|
39910
39975
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
39911
39976
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
39912
39977
|
validateRuleRegistration();
|
|
@@ -39958,8 +40023,14 @@ const runOxlint = async (options) => {
|
|
|
39958
40023
|
"json",
|
|
39959
40024
|
...sharedArgs
|
|
39960
40025
|
];
|
|
39961
|
-
const
|
|
39962
|
-
const candidateFiles = includePaths !== void 0 ? includePaths :
|
|
40026
|
+
const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
|
|
40027
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
|
|
40028
|
+
const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
|
|
40029
|
+
const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
|
|
40030
|
+
baseArgs: passBaseArgs,
|
|
40031
|
+
files: passFiles,
|
|
40032
|
+
sizeByFile
|
|
40033
|
+
}) : batchIncludePaths(passBaseArgs, passFiles);
|
|
39963
40034
|
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
39964
40035
|
if (files.length === 0) return {
|
|
39965
40036
|
diagnostics: [],
|
|
@@ -39973,7 +40044,7 @@ const runOxlint = async (options) => {
|
|
|
39973
40044
|
};
|
|
39974
40045
|
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
39975
40046
|
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
39976
|
-
const passFileBatches =
|
|
40047
|
+
const passFileBatches = buildFileBatches(passBaseArgs, files);
|
|
39977
40048
|
const spawnPass = () => spawnLintBatches({
|
|
39978
40049
|
baseArgs: passBaseArgs,
|
|
39979
40050
|
fileBatches: passFileBatches,
|
|
@@ -40075,7 +40146,7 @@ const runOxlint = async (options) => {
|
|
|
40075
40146
|
]);
|
|
40076
40147
|
}
|
|
40077
40148
|
const baseArgs = makeBaseArgs(configPath);
|
|
40078
|
-
const fileBatches =
|
|
40149
|
+
const fileBatches = buildFileBatches(baseArgs, candidateFiles);
|
|
40079
40150
|
const runBatches = () => spawnLintBatches({
|
|
40080
40151
|
baseArgs,
|
|
40081
40152
|
fileBatches,
|
|
@@ -41759,4 +41830,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
41759
41830
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
41760
41831
|
|
|
41761
41832
|
//# sourceMappingURL=index.js.map
|
|
41762
|
-
//# debugId=
|
|
41833
|
+
//# debugId=1b2a9bb2-d6bc-5c9b-afbb-5b639438c617
|
package/dist/lsp.js
CHANGED
|
@@ -35512,7 +35512,7 @@ const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
|
35512
35512
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
35513
35513
|
};
|
|
35514
35514
|
const resolveLintBatchOrdering = () => {
|
|
35515
|
-
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "
|
|
35515
|
+
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "arrival" ? "arrival" : "cost";
|
|
35516
35516
|
};
|
|
35517
35517
|
/**
|
|
35518
35518
|
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
@@ -35609,17 +35609,19 @@ var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", {
|
|
|
35609
35609
|
return "auto";
|
|
35610
35610
|
} }) {};
|
|
35611
35611
|
/**
|
|
35612
|
-
* How the full-scan lint pass
|
|
35613
|
-
*
|
|
35614
|
-
*
|
|
35615
|
-
*
|
|
35616
|
-
* the
|
|
35617
|
-
*
|
|
35618
|
-
*
|
|
35619
|
-
*
|
|
35620
|
-
* `
|
|
35621
|
-
*
|
|
35622
|
-
*
|
|
35612
|
+
* How the full-scan lint pass plans its file batches. `"cost"` (the default)
|
|
35613
|
+
* builds size-balanced LPT batches (`planLintBatches`): the same mandatory
|
|
35614
|
+
* batch count as greedy chunking (`ceil(files / 100)`), but every batch gets
|
|
35615
|
+
* an even share of files AND bytes, so no 100-file chunk is a straggler while
|
|
35616
|
+
* the remainder-batch worker idles — and the heavy files are SPREAD across
|
|
35617
|
+
* batches, the precondition the old sort-desc-then-chunk-100 `cost` mode
|
|
35618
|
+
* lacked (it packed the heaviest files into one wave-1 straggler batch,
|
|
35619
|
+
* measurably regressing size-skewed repos, which is why it never earned the
|
|
35620
|
+
* default). `"arrival"` (`REACT_DOCTOR_LINT_BATCH_ORDERING=arrival`) is the
|
|
35621
|
+
* rollback hatch to plain greedy 100-file chunking in discovery order. Tests
|
|
35622
|
+
* override via `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans
|
|
35623
|
+
* never reach this — they pass user-scoped `includePaths` that skip discovery
|
|
35624
|
+
* and stay in arrival order; only the full-scan branch reads it.
|
|
35623
35625
|
*/
|
|
35624
35626
|
var LintBatchOrdering = class extends Reference("react-doctor/LintBatchOrdering", { defaultValue: resolveLintBatchOrdering }) {};
|
|
35625
35627
|
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
@@ -39817,6 +39819,70 @@ const hashFileContents = (filePath) => {
|
|
|
39817
39819
|
return null;
|
|
39818
39820
|
}
|
|
39819
39821
|
};
|
|
39822
|
+
/**
|
|
39823
|
+
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39824
|
+
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
39825
|
+
* remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
|
|
39826
|
+
* is set by whichever 100-file chunk happened to collect the most work while
|
|
39827
|
+
* the remainder's worker idles), this planner keeps the SAME mandatory batch
|
|
39828
|
+
* count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
|
|
39829
|
+
* perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
|
|
39830
|
+
* each to the least-loaded batch by cumulative byte size. Every batch ends up
|
|
39831
|
+
* with an even share of files AND bytes, so no batch is a straggler; heavy
|
|
39832
|
+
* files are SPREAD across batches (the documented precondition for `cost`
|
|
39833
|
+
* ordering to beat `arrival`) instead of concentrated in one.
|
|
39834
|
+
*
|
|
39835
|
+
* Deliberately NOT worker-count-aware: splitting into extra batches to feed
|
|
39836
|
+
* idle workers was measured (619-file corpus, 10 cores) to REGRESS the lint
|
|
39837
|
+
* wall — each additional concurrent subprocess pays a contended cold start
|
|
39838
|
+
* (node boot + plugin import + native dlopen, ~180 ms uncontended and 2-3×
|
|
39839
|
+
* that in a full wave) that outweighs the smaller per-batch file share.
|
|
39840
|
+
*
|
|
39841
|
+
* Every batch also respects the spawn-args char budget; a file that fits no
|
|
39842
|
+
* existing batch opens a new one, mirroring `batchIncludePaths`. Within a
|
|
39843
|
+
* batch, files return in arrival order, and the assignment is fully
|
|
39844
|
+
* deterministic (ties broken by lowest batch index), so a plan is
|
|
39845
|
+
* reproducible for a fixed (files, sizes) input. Batch composition cannot
|
|
39846
|
+
* change diagnostics — rules that look beyond one file (`CROSS_FILE_RULE_IDS`)
|
|
39847
|
+
* read the OTHER files via the filesystem, never via batch-mates — so the
|
|
39848
|
+
* plan only moves wall-clock time.
|
|
39849
|
+
*
|
|
39850
|
+
* O(files × batches); the linear least-loaded scan is fine at real-world
|
|
39851
|
+
* scale (a 3,500-file monorepo is 36 batches ≈ 126k comparisons).
|
|
39852
|
+
*/
|
|
39853
|
+
const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
39854
|
+
if (files.length === 0) return [];
|
|
39855
|
+
const batchCount = Math.ceil(files.length / 100);
|
|
39856
|
+
const maxFilesPerBatch = Math.ceil(files.length / batchCount);
|
|
39857
|
+
const baseArgsLengthChars = estimateArgsLength(baseArgs);
|
|
39858
|
+
const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
|
|
39859
|
+
const batches = Array.from({ length: batchCount }, () => ({
|
|
39860
|
+
fileIndexes: [],
|
|
39861
|
+
totalSizeBytes: 0,
|
|
39862
|
+
argsLengthChars: baseArgsLengthChars
|
|
39863
|
+
}));
|
|
39864
|
+
for (const fileIndex of descendingSizeIndexes) {
|
|
39865
|
+
const entryLengthChars = files[fileIndex].length + 1;
|
|
39866
|
+
let targetBatch = null;
|
|
39867
|
+
for (const batch of batches) {
|
|
39868
|
+
if (batch.fileIndexes.length >= maxFilesPerBatch) continue;
|
|
39869
|
+
if (batch.fileIndexes.length > 0 && batch.argsLengthChars + entryLengthChars > 24e3) continue;
|
|
39870
|
+
if (targetBatch === null || batch.totalSizeBytes < targetBatch.totalSizeBytes || batch.totalSizeBytes === targetBatch.totalSizeBytes && batch.fileIndexes.length < targetBatch.fileIndexes.length) targetBatch = batch;
|
|
39871
|
+
}
|
|
39872
|
+
if (targetBatch === null) {
|
|
39873
|
+
targetBatch = {
|
|
39874
|
+
fileIndexes: [],
|
|
39875
|
+
totalSizeBytes: 0,
|
|
39876
|
+
argsLengthChars: baseArgsLengthChars
|
|
39877
|
+
};
|
|
39878
|
+
batches.push(targetBatch);
|
|
39879
|
+
}
|
|
39880
|
+
targetBatch.fileIndexes.push(fileIndex);
|
|
39881
|
+
targetBatch.totalSizeBytes += sizeByFile.get(files[fileIndex]) ?? 0;
|
|
39882
|
+
targetBatch.argsLengthChars += entryLengthChars;
|
|
39883
|
+
}
|
|
39884
|
+
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
39885
|
+
};
|
|
39820
39886
|
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
39821
39887
|
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
39822
39888
|
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
@@ -39832,7 +39898,6 @@ const resolveUserCacheScope = () => {
|
|
|
39832
39898
|
return "shared";
|
|
39833
39899
|
}
|
|
39834
39900
|
};
|
|
39835
|
-
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39836
39901
|
/**
|
|
39837
39902
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
39838
39903
|
* the runner: once for the primary scan, once for the
|
|
@@ -39891,7 +39956,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
39891
39956
|
* 6. always restore disable directives + clean up the temp dir
|
|
39892
39957
|
*/
|
|
39893
39958
|
const runOxlint = async (options) => {
|
|
39894
|
-
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 = "
|
|
39959
|
+
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 = "cost" } = options;
|
|
39895
39960
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
39896
39961
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
39897
39962
|
validateRuleRegistration();
|
|
@@ -39943,8 +40008,14 @@ const runOxlint = async (options) => {
|
|
|
39943
40008
|
"json",
|
|
39944
40009
|
...sharedArgs
|
|
39945
40010
|
];
|
|
39946
|
-
const
|
|
39947
|
-
const candidateFiles = includePaths !== void 0 ? includePaths :
|
|
40011
|
+
const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
|
|
40012
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
|
|
40013
|
+
const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
|
|
40014
|
+
const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
|
|
40015
|
+
baseArgs: passBaseArgs,
|
|
40016
|
+
files: passFiles,
|
|
40017
|
+
sizeByFile
|
|
40018
|
+
}) : batchIncludePaths(passBaseArgs, passFiles);
|
|
39948
40019
|
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
39949
40020
|
if (files.length === 0) return {
|
|
39950
40021
|
diagnostics: [],
|
|
@@ -39958,7 +40029,7 @@ const runOxlint = async (options) => {
|
|
|
39958
40029
|
};
|
|
39959
40030
|
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
39960
40031
|
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
39961
|
-
const passFileBatches =
|
|
40032
|
+
const passFileBatches = buildFileBatches(passBaseArgs, files);
|
|
39962
40033
|
const spawnPass = () => spawnLintBatches({
|
|
39963
40034
|
baseArgs: passBaseArgs,
|
|
39964
40035
|
fileBatches: passFileBatches,
|
|
@@ -40060,7 +40131,7 @@ const runOxlint = async (options) => {
|
|
|
40060
40131
|
]);
|
|
40061
40132
|
}
|
|
40062
40133
|
const baseArgs = makeBaseArgs(configPath);
|
|
40063
|
-
const fileBatches =
|
|
40134
|
+
const fileBatches = buildFileBatches(baseArgs, candidateFiles);
|
|
40064
40135
|
const runBatches = () => spawnLintBatches({
|
|
40065
40136
|
baseArgs,
|
|
40066
40137
|
fileBatches,
|
|
@@ -43483,5 +43554,5 @@ const startLanguageServer = () => {
|
|
|
43483
43554
|
};
|
|
43484
43555
|
//#endregion
|
|
43485
43556
|
export { startLanguageServer };
|
|
43486
|
-
!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]="
|
|
43487
|
-
//# debugId=
|
|
43557
|
+
!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]="75579803-66de-5f78-8ded-3ed584f2e100")}catch(e){}}();
|
|
43558
|
+
//# debugId=75579803-66de-5f78-8ded-3ed584f2e100
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.6.2-dev.
|
|
3
|
+
"version": "0.6.2-dev.ac71a3b",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
67
|
"yaml": "^2.9.0",
|
|
68
68
|
"deslop-js": "0.6.2",
|
|
69
|
-
"oxlint-plugin-react-doctor": "0.6.2-dev.
|
|
69
|
+
"oxlint-plugin-react-doctor": "0.6.2-dev.ac71a3b"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/babel__code-frame": "^7.27.0",
|