react-doctor 0.6.2 → 0.6.3-dev.1571119
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 +116 -27
- package/dist/index.js +110 -23
- package/dist/lsp.js +111 -24
- package/package.json +6 -6
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]="8532f6fd-f5ce-59ae-940d-7bab36e5937d")}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"]);
|
|
@@ -40170,6 +40172,7 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
40170
40172
|
isGeneratedBundleByName: classification.isGeneratedBundleByName
|
|
40171
40173
|
});
|
|
40172
40174
|
}
|
|
40175
|
+
yield null;
|
|
40173
40176
|
}
|
|
40174
40177
|
for (const candidates of [
|
|
40175
40178
|
priorityCandidates,
|
|
@@ -40295,9 +40298,18 @@ const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
|
40295
40298
|
const session = createSecurityScanSession(rootDirectory, options);
|
|
40296
40299
|
if (session === null) return [];
|
|
40297
40300
|
let sliceStartedAt = performance.now();
|
|
40298
|
-
for (const file of collectSecurityScanFiles(rootDirectory))
|
|
40299
|
-
|
|
40300
|
-
|
|
40301
|
+
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
40302
|
+
if (file === null) {
|
|
40303
|
+
if (performance.now() - sliceStartedAt >= 12) {
|
|
40304
|
+
await yieldToEventLoop();
|
|
40305
|
+
sliceStartedAt = performance.now();
|
|
40306
|
+
}
|
|
40307
|
+
continue;
|
|
40308
|
+
}
|
|
40309
|
+
for (const _ruleStep of session.scanFileByRule(file)) if (performance.now() - sliceStartedAt >= 12) {
|
|
40310
|
+
await yieldToEventLoop();
|
|
40311
|
+
sliceStartedAt = performance.now();
|
|
40312
|
+
}
|
|
40301
40313
|
}
|
|
40302
40314
|
return session.diagnostics;
|
|
40303
40315
|
};
|
|
@@ -43075,6 +43087,70 @@ const hashFileContents = (filePath) => {
|
|
|
43075
43087
|
return null;
|
|
43076
43088
|
}
|
|
43077
43089
|
};
|
|
43090
|
+
/**
|
|
43091
|
+
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
43092
|
+
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
43093
|
+
* remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
|
|
43094
|
+
* is set by whichever 100-file chunk happened to collect the most work while
|
|
43095
|
+
* the remainder's worker idles), this planner keeps the SAME mandatory batch
|
|
43096
|
+
* count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
|
|
43097
|
+
* perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
|
|
43098
|
+
* each to the least-loaded batch by cumulative byte size. Every batch ends up
|
|
43099
|
+
* with an even share of files AND bytes, so no batch is a straggler; heavy
|
|
43100
|
+
* files are SPREAD across batches (the documented precondition for `cost`
|
|
43101
|
+
* ordering to beat `arrival`) instead of concentrated in one.
|
|
43102
|
+
*
|
|
43103
|
+
* Deliberately NOT worker-count-aware: splitting into extra batches to feed
|
|
43104
|
+
* idle workers was measured (619-file corpus, 10 cores) to REGRESS the lint
|
|
43105
|
+
* wall — each additional concurrent subprocess pays a contended cold start
|
|
43106
|
+
* (node boot + plugin import + native dlopen, ~180 ms uncontended and 2-3×
|
|
43107
|
+
* that in a full wave) that outweighs the smaller per-batch file share.
|
|
43108
|
+
*
|
|
43109
|
+
* Every batch also respects the spawn-args char budget; a file that fits no
|
|
43110
|
+
* existing batch opens a new one, mirroring `batchIncludePaths`. Within a
|
|
43111
|
+
* batch, files return in arrival order, and the assignment is fully
|
|
43112
|
+
* deterministic (ties broken by lowest batch index), so a plan is
|
|
43113
|
+
* reproducible for a fixed (files, sizes) input. Batch composition cannot
|
|
43114
|
+
* change diagnostics — rules that look beyond one file (`CROSS_FILE_RULE_IDS`)
|
|
43115
|
+
* read the OTHER files via the filesystem, never via batch-mates — so the
|
|
43116
|
+
* plan only moves wall-clock time.
|
|
43117
|
+
*
|
|
43118
|
+
* O(files × batches); the linear least-loaded scan is fine at real-world
|
|
43119
|
+
* scale (a 3,500-file monorepo is 36 batches ≈ 126k comparisons).
|
|
43120
|
+
*/
|
|
43121
|
+
const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
43122
|
+
if (files.length === 0) return [];
|
|
43123
|
+
const batchCount = Math.ceil(files.length / 100);
|
|
43124
|
+
const maxFilesPerBatch = Math.ceil(files.length / batchCount);
|
|
43125
|
+
const baseArgsLengthChars = estimateArgsLength(baseArgs);
|
|
43126
|
+
const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
|
|
43127
|
+
const batches = Array.from({ length: batchCount }, () => ({
|
|
43128
|
+
fileIndexes: [],
|
|
43129
|
+
totalSizeBytes: 0,
|
|
43130
|
+
argsLengthChars: baseArgsLengthChars
|
|
43131
|
+
}));
|
|
43132
|
+
for (const fileIndex of descendingSizeIndexes) {
|
|
43133
|
+
const entryLengthChars = files[fileIndex].length + 1;
|
|
43134
|
+
let targetBatch = null;
|
|
43135
|
+
for (const batch of batches) {
|
|
43136
|
+
if (batch.fileIndexes.length >= maxFilesPerBatch) continue;
|
|
43137
|
+
if (batch.fileIndexes.length > 0 && batch.argsLengthChars + entryLengthChars > 24e3) continue;
|
|
43138
|
+
if (targetBatch === null || batch.totalSizeBytes < targetBatch.totalSizeBytes || batch.totalSizeBytes === targetBatch.totalSizeBytes && batch.fileIndexes.length < targetBatch.fileIndexes.length) targetBatch = batch;
|
|
43139
|
+
}
|
|
43140
|
+
if (targetBatch === null) {
|
|
43141
|
+
targetBatch = {
|
|
43142
|
+
fileIndexes: [],
|
|
43143
|
+
totalSizeBytes: 0,
|
|
43144
|
+
argsLengthChars: baseArgsLengthChars
|
|
43145
|
+
};
|
|
43146
|
+
batches.push(targetBatch);
|
|
43147
|
+
}
|
|
43148
|
+
targetBatch.fileIndexes.push(fileIndex);
|
|
43149
|
+
targetBatch.totalSizeBytes += sizeByFile.get(files[fileIndex]) ?? 0;
|
|
43150
|
+
targetBatch.argsLengthChars += entryLengthChars;
|
|
43151
|
+
}
|
|
43152
|
+
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
43153
|
+
};
|
|
43078
43154
|
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
43079
43155
|
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
43080
43156
|
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
@@ -43090,7 +43166,6 @@ const resolveUserCacheScope = () => {
|
|
|
43090
43166
|
return "shared";
|
|
43091
43167
|
}
|
|
43092
43168
|
};
|
|
43093
|
-
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
43094
43169
|
/**
|
|
43095
43170
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
43096
43171
|
* the runner: once for the primary scan, once for the
|
|
@@ -43149,7 +43224,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
43149
43224
|
* 6. always restore disable directives + clean up the temp dir
|
|
43150
43225
|
*/
|
|
43151
43226
|
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 = "
|
|
43227
|
+
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
43228
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
43154
43229
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
43155
43230
|
validateRuleRegistration();
|
|
@@ -43201,8 +43276,15 @@ const runOxlint = async (options) => {
|
|
|
43201
43276
|
"json",
|
|
43202
43277
|
...sharedArgs
|
|
43203
43278
|
];
|
|
43204
|
-
|
|
43205
|
-
const
|
|
43279
|
+
await yieldToEventLoop();
|
|
43280
|
+
const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
|
|
43281
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
|
|
43282
|
+
const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
|
|
43283
|
+
const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
|
|
43284
|
+
baseArgs: passBaseArgs,
|
|
43285
|
+
files: passFiles,
|
|
43286
|
+
sizeByFile
|
|
43287
|
+
}) : batchIncludePaths(passBaseArgs, passFiles);
|
|
43206
43288
|
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
43207
43289
|
if (files.length === 0) return {
|
|
43208
43290
|
diagnostics: [],
|
|
@@ -43216,7 +43298,7 @@ const runOxlint = async (options) => {
|
|
|
43216
43298
|
};
|
|
43217
43299
|
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
43218
43300
|
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
43219
|
-
const passFileBatches =
|
|
43301
|
+
const passFileBatches = buildFileBatches(passBaseArgs, files);
|
|
43220
43302
|
const spawnPass = () => spawnLintBatches({
|
|
43221
43303
|
baseArgs: passBaseArgs,
|
|
43222
43304
|
fileBatches: passFileBatches,
|
|
@@ -43265,6 +43347,7 @@ const runOxlint = async (options) => {
|
|
|
43265
43347
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
43266
43348
|
const missFiles = [];
|
|
43267
43349
|
const replayedDiagnostics = [];
|
|
43350
|
+
let hashSliceStartedAt = performance.now();
|
|
43268
43351
|
for (const candidateFile of candidateFiles) {
|
|
43269
43352
|
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
43270
43353
|
if (contentHash === null) {
|
|
@@ -43276,6 +43359,10 @@ const runOxlint = async (options) => {
|
|
|
43276
43359
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
43277
43360
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
43278
43361
|
else replayedDiagnostics.push(...cachedDiagnostics);
|
|
43362
|
+
if (performance.now() - hashSliceStartedAt >= 12) {
|
|
43363
|
+
await yieldToEventLoop();
|
|
43364
|
+
hashSliceStartedAt = performance.now();
|
|
43365
|
+
}
|
|
43279
43366
|
}
|
|
43280
43367
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
43281
43368
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
@@ -43318,7 +43405,7 @@ const runOxlint = async (options) => {
|
|
|
43318
43405
|
]);
|
|
43319
43406
|
}
|
|
43320
43407
|
const baseArgs = makeBaseArgs(configPath);
|
|
43321
|
-
const fileBatches =
|
|
43408
|
+
const fileBatches = buildFileBatches(baseArgs, candidateFiles);
|
|
43322
43409
|
const runBatches = () => spawnLintBatches({
|
|
43323
43410
|
baseArgs,
|
|
43324
43411
|
fileBatches,
|
|
@@ -45143,6 +45230,7 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
|
|
|
45143
45230
|
const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
|
|
45144
45231
|
const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
|
|
45145
45232
|
const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
|
|
45233
|
+
const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
45146
45234
|
const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
|
|
45147
45235
|
const CI_TRUST_COMPANIES = "PayPal, Rippling, and Alibaba";
|
|
45148
45236
|
const WELCOME_EXPLANATION_HOLD_MS = 1e3;
|
|
@@ -45233,7 +45321,7 @@ const makeNoopConsole = () => ({
|
|
|
45233
45321
|
});
|
|
45234
45322
|
//#endregion
|
|
45235
45323
|
//#region src/cli/utils/version.ts
|
|
45236
|
-
const VERSION = "0.6.
|
|
45324
|
+
const VERSION = "0.6.3-dev.1571119";
|
|
45237
45325
|
//#endregion
|
|
45238
45326
|
//#region src/cli/utils/json-mode.ts
|
|
45239
45327
|
let context = null;
|
|
@@ -45602,13 +45690,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
45602
45690
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
45603
45691
|
* standard `SENTRY_RELEASE` override.
|
|
45604
45692
|
*/
|
|
45605
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.
|
|
45693
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.3-dev.1571119`;
|
|
45606
45694
|
/**
|
|
45607
45695
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
45608
45696
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
45609
45697
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
45610
45698
|
*/
|
|
45611
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.
|
|
45699
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.3-dev.1571119") ? "development" : "production");
|
|
45612
45700
|
/**
|
|
45613
45701
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
45614
45702
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -45809,7 +45897,8 @@ const runGit = (projectRoot, args) => {
|
|
|
45809
45897
|
"ignore",
|
|
45810
45898
|
"pipe",
|
|
45811
45899
|
"ignore"
|
|
45812
|
-
]
|
|
45900
|
+
],
|
|
45901
|
+
maxBuffer: RUN_GIT_MAX_BUFFER_BYTES
|
|
45813
45902
|
}).trim();
|
|
45814
45903
|
} catch {
|
|
45815
45904
|
return null;
|
|
@@ -63591,4 +63680,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
63591
63680
|
export {};
|
|
63592
63681
|
|
|
63593
63682
|
//# sourceMappingURL=cli.js.map
|
|
63594
|
-
//# debugId=
|
|
63683
|
+
//# debugId=8532f6fd-f5ce-59ae-940d-7bab36e5937d
|
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]="8565c6ea-39f3-51e6-895a-ae9f7c026d0e")}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"]);
|
|
@@ -36943,6 +36945,7 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
36943
36945
|
isGeneratedBundleByName: classification.isGeneratedBundleByName
|
|
36944
36946
|
});
|
|
36945
36947
|
}
|
|
36948
|
+
yield null;
|
|
36946
36949
|
}
|
|
36947
36950
|
for (const candidates of [
|
|
36948
36951
|
priorityCandidates,
|
|
@@ -37068,9 +37071,18 @@ const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
|
37068
37071
|
const session = createSecurityScanSession(rootDirectory, options);
|
|
37069
37072
|
if (session === null) return [];
|
|
37070
37073
|
let sliceStartedAt = performance.now();
|
|
37071
|
-
for (const file of collectSecurityScanFiles(rootDirectory))
|
|
37072
|
-
|
|
37073
|
-
|
|
37074
|
+
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
37075
|
+
if (file === null) {
|
|
37076
|
+
if (performance.now() - sliceStartedAt >= 12) {
|
|
37077
|
+
await yieldToEventLoop();
|
|
37078
|
+
sliceStartedAt = performance.now();
|
|
37079
|
+
}
|
|
37080
|
+
continue;
|
|
37081
|
+
}
|
|
37082
|
+
for (const _ruleStep of session.scanFileByRule(file)) if (performance.now() - sliceStartedAt >= 12) {
|
|
37083
|
+
await yieldToEventLoop();
|
|
37084
|
+
sliceStartedAt = performance.now();
|
|
37085
|
+
}
|
|
37074
37086
|
}
|
|
37075
37087
|
return session.diagnostics;
|
|
37076
37088
|
};
|
|
@@ -39832,6 +39844,70 @@ const hashFileContents = (filePath) => {
|
|
|
39832
39844
|
return null;
|
|
39833
39845
|
}
|
|
39834
39846
|
};
|
|
39847
|
+
/**
|
|
39848
|
+
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39849
|
+
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
39850
|
+
* remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
|
|
39851
|
+
* is set by whichever 100-file chunk happened to collect the most work while
|
|
39852
|
+
* the remainder's worker idles), this planner keeps the SAME mandatory batch
|
|
39853
|
+
* count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
|
|
39854
|
+
* perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
|
|
39855
|
+
* each to the least-loaded batch by cumulative byte size. Every batch ends up
|
|
39856
|
+
* with an even share of files AND bytes, so no batch is a straggler; heavy
|
|
39857
|
+
* files are SPREAD across batches (the documented precondition for `cost`
|
|
39858
|
+
* ordering to beat `arrival`) instead of concentrated in one.
|
|
39859
|
+
*
|
|
39860
|
+
* Deliberately NOT worker-count-aware: splitting into extra batches to feed
|
|
39861
|
+
* idle workers was measured (619-file corpus, 10 cores) to REGRESS the lint
|
|
39862
|
+
* wall — each additional concurrent subprocess pays a contended cold start
|
|
39863
|
+
* (node boot + plugin import + native dlopen, ~180 ms uncontended and 2-3×
|
|
39864
|
+
* that in a full wave) that outweighs the smaller per-batch file share.
|
|
39865
|
+
*
|
|
39866
|
+
* Every batch also respects the spawn-args char budget; a file that fits no
|
|
39867
|
+
* existing batch opens a new one, mirroring `batchIncludePaths`. Within a
|
|
39868
|
+
* batch, files return in arrival order, and the assignment is fully
|
|
39869
|
+
* deterministic (ties broken by lowest batch index), so a plan is
|
|
39870
|
+
* reproducible for a fixed (files, sizes) input. Batch composition cannot
|
|
39871
|
+
* change diagnostics — rules that look beyond one file (`CROSS_FILE_RULE_IDS`)
|
|
39872
|
+
* read the OTHER files via the filesystem, never via batch-mates — so the
|
|
39873
|
+
* plan only moves wall-clock time.
|
|
39874
|
+
*
|
|
39875
|
+
* O(files × batches); the linear least-loaded scan is fine at real-world
|
|
39876
|
+
* scale (a 3,500-file monorepo is 36 batches ≈ 126k comparisons).
|
|
39877
|
+
*/
|
|
39878
|
+
const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
39879
|
+
if (files.length === 0) return [];
|
|
39880
|
+
const batchCount = Math.ceil(files.length / 100);
|
|
39881
|
+
const maxFilesPerBatch = Math.ceil(files.length / batchCount);
|
|
39882
|
+
const baseArgsLengthChars = estimateArgsLength(baseArgs);
|
|
39883
|
+
const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
|
|
39884
|
+
const batches = Array.from({ length: batchCount }, () => ({
|
|
39885
|
+
fileIndexes: [],
|
|
39886
|
+
totalSizeBytes: 0,
|
|
39887
|
+
argsLengthChars: baseArgsLengthChars
|
|
39888
|
+
}));
|
|
39889
|
+
for (const fileIndex of descendingSizeIndexes) {
|
|
39890
|
+
const entryLengthChars = files[fileIndex].length + 1;
|
|
39891
|
+
let targetBatch = null;
|
|
39892
|
+
for (const batch of batches) {
|
|
39893
|
+
if (batch.fileIndexes.length >= maxFilesPerBatch) continue;
|
|
39894
|
+
if (batch.fileIndexes.length > 0 && batch.argsLengthChars + entryLengthChars > 24e3) continue;
|
|
39895
|
+
if (targetBatch === null || batch.totalSizeBytes < targetBatch.totalSizeBytes || batch.totalSizeBytes === targetBatch.totalSizeBytes && batch.fileIndexes.length < targetBatch.fileIndexes.length) targetBatch = batch;
|
|
39896
|
+
}
|
|
39897
|
+
if (targetBatch === null) {
|
|
39898
|
+
targetBatch = {
|
|
39899
|
+
fileIndexes: [],
|
|
39900
|
+
totalSizeBytes: 0,
|
|
39901
|
+
argsLengthChars: baseArgsLengthChars
|
|
39902
|
+
};
|
|
39903
|
+
batches.push(targetBatch);
|
|
39904
|
+
}
|
|
39905
|
+
targetBatch.fileIndexes.push(fileIndex);
|
|
39906
|
+
targetBatch.totalSizeBytes += sizeByFile.get(files[fileIndex]) ?? 0;
|
|
39907
|
+
targetBatch.argsLengthChars += entryLengthChars;
|
|
39908
|
+
}
|
|
39909
|
+
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
39910
|
+
};
|
|
39835
39911
|
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
39836
39912
|
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
39837
39913
|
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
@@ -39847,7 +39923,6 @@ const resolveUserCacheScope = () => {
|
|
|
39847
39923
|
return "shared";
|
|
39848
39924
|
}
|
|
39849
39925
|
};
|
|
39850
|
-
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39851
39926
|
/**
|
|
39852
39927
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
39853
39928
|
* the runner: once for the primary scan, once for the
|
|
@@ -39906,7 +39981,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
39906
39981
|
* 6. always restore disable directives + clean up the temp dir
|
|
39907
39982
|
*/
|
|
39908
39983
|
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 = "
|
|
39984
|
+
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
39985
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
39911
39986
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
39912
39987
|
validateRuleRegistration();
|
|
@@ -39958,8 +40033,15 @@ const runOxlint = async (options) => {
|
|
|
39958
40033
|
"json",
|
|
39959
40034
|
...sharedArgs
|
|
39960
40035
|
];
|
|
39961
|
-
|
|
39962
|
-
const
|
|
40036
|
+
await yieldToEventLoop();
|
|
40037
|
+
const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
|
|
40038
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
|
|
40039
|
+
const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
|
|
40040
|
+
const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
|
|
40041
|
+
baseArgs: passBaseArgs,
|
|
40042
|
+
files: passFiles,
|
|
40043
|
+
sizeByFile
|
|
40044
|
+
}) : batchIncludePaths(passBaseArgs, passFiles);
|
|
39963
40045
|
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
39964
40046
|
if (files.length === 0) return {
|
|
39965
40047
|
diagnostics: [],
|
|
@@ -39973,7 +40055,7 @@ const runOxlint = async (options) => {
|
|
|
39973
40055
|
};
|
|
39974
40056
|
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
39975
40057
|
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
39976
|
-
const passFileBatches =
|
|
40058
|
+
const passFileBatches = buildFileBatches(passBaseArgs, files);
|
|
39977
40059
|
const spawnPass = () => spawnLintBatches({
|
|
39978
40060
|
baseArgs: passBaseArgs,
|
|
39979
40061
|
fileBatches: passFileBatches,
|
|
@@ -40022,6 +40104,7 @@ const runOxlint = async (options) => {
|
|
|
40022
40104
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40023
40105
|
const missFiles = [];
|
|
40024
40106
|
const replayedDiagnostics = [];
|
|
40107
|
+
let hashSliceStartedAt = performance.now();
|
|
40025
40108
|
for (const candidateFile of candidateFiles) {
|
|
40026
40109
|
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
40027
40110
|
if (contentHash === null) {
|
|
@@ -40033,6 +40116,10 @@ const runOxlint = async (options) => {
|
|
|
40033
40116
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
40034
40117
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
40035
40118
|
else replayedDiagnostics.push(...cachedDiagnostics);
|
|
40119
|
+
if (performance.now() - hashSliceStartedAt >= 12) {
|
|
40120
|
+
await yieldToEventLoop();
|
|
40121
|
+
hashSliceStartedAt = performance.now();
|
|
40122
|
+
}
|
|
40036
40123
|
}
|
|
40037
40124
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40038
40125
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
@@ -40075,7 +40162,7 @@ const runOxlint = async (options) => {
|
|
|
40075
40162
|
]);
|
|
40076
40163
|
}
|
|
40077
40164
|
const baseArgs = makeBaseArgs(configPath);
|
|
40078
|
-
const fileBatches =
|
|
40165
|
+
const fileBatches = buildFileBatches(baseArgs, candidateFiles);
|
|
40079
40166
|
const runBatches = () => spawnLintBatches({
|
|
40080
40167
|
baseArgs,
|
|
40081
40168
|
fileBatches,
|
|
@@ -41759,4 +41846,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
41759
41846
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
41760
41847
|
|
|
41761
41848
|
//# sourceMappingURL=index.js.map
|
|
41762
|
-
//# debugId=
|
|
41849
|
+
//# debugId=8565c6ea-39f3-51e6-895a-ae9f7c026d0e
|
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"]);
|
|
@@ -36928,6 +36930,7 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
36928
36930
|
isGeneratedBundleByName: classification.isGeneratedBundleByName
|
|
36929
36931
|
});
|
|
36930
36932
|
}
|
|
36933
|
+
yield null;
|
|
36931
36934
|
}
|
|
36932
36935
|
for (const candidates of [
|
|
36933
36936
|
priorityCandidates,
|
|
@@ -37053,9 +37056,18 @@ const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
|
37053
37056
|
const session = createSecurityScanSession(rootDirectory, options);
|
|
37054
37057
|
if (session === null) return [];
|
|
37055
37058
|
let sliceStartedAt = performance.now();
|
|
37056
|
-
for (const file of collectSecurityScanFiles(rootDirectory))
|
|
37057
|
-
|
|
37058
|
-
|
|
37059
|
+
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
37060
|
+
if (file === null) {
|
|
37061
|
+
if (performance.now() - sliceStartedAt >= 12) {
|
|
37062
|
+
await yieldToEventLoop();
|
|
37063
|
+
sliceStartedAt = performance.now();
|
|
37064
|
+
}
|
|
37065
|
+
continue;
|
|
37066
|
+
}
|
|
37067
|
+
for (const _ruleStep of session.scanFileByRule(file)) if (performance.now() - sliceStartedAt >= 12) {
|
|
37068
|
+
await yieldToEventLoop();
|
|
37069
|
+
sliceStartedAt = performance.now();
|
|
37070
|
+
}
|
|
37059
37071
|
}
|
|
37060
37072
|
return session.diagnostics;
|
|
37061
37073
|
};
|
|
@@ -39817,6 +39829,70 @@ const hashFileContents = (filePath) => {
|
|
|
39817
39829
|
return null;
|
|
39818
39830
|
}
|
|
39819
39831
|
};
|
|
39832
|
+
/**
|
|
39833
|
+
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39834
|
+
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
39835
|
+
* remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
|
|
39836
|
+
* is set by whichever 100-file chunk happened to collect the most work while
|
|
39837
|
+
* the remainder's worker idles), this planner keeps the SAME mandatory batch
|
|
39838
|
+
* count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
|
|
39839
|
+
* perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
|
|
39840
|
+
* each to the least-loaded batch by cumulative byte size. Every batch ends up
|
|
39841
|
+
* with an even share of files AND bytes, so no batch is a straggler; heavy
|
|
39842
|
+
* files are SPREAD across batches (the documented precondition for `cost`
|
|
39843
|
+
* ordering to beat `arrival`) instead of concentrated in one.
|
|
39844
|
+
*
|
|
39845
|
+
* Deliberately NOT worker-count-aware: splitting into extra batches to feed
|
|
39846
|
+
* idle workers was measured (619-file corpus, 10 cores) to REGRESS the lint
|
|
39847
|
+
* wall — each additional concurrent subprocess pays a contended cold start
|
|
39848
|
+
* (node boot + plugin import + native dlopen, ~180 ms uncontended and 2-3×
|
|
39849
|
+
* that in a full wave) that outweighs the smaller per-batch file share.
|
|
39850
|
+
*
|
|
39851
|
+
* Every batch also respects the spawn-args char budget; a file that fits no
|
|
39852
|
+
* existing batch opens a new one, mirroring `batchIncludePaths`. Within a
|
|
39853
|
+
* batch, files return in arrival order, and the assignment is fully
|
|
39854
|
+
* deterministic (ties broken by lowest batch index), so a plan is
|
|
39855
|
+
* reproducible for a fixed (files, sizes) input. Batch composition cannot
|
|
39856
|
+
* change diagnostics — rules that look beyond one file (`CROSS_FILE_RULE_IDS`)
|
|
39857
|
+
* read the OTHER files via the filesystem, never via batch-mates — so the
|
|
39858
|
+
* plan only moves wall-clock time.
|
|
39859
|
+
*
|
|
39860
|
+
* O(files × batches); the linear least-loaded scan is fine at real-world
|
|
39861
|
+
* scale (a 3,500-file monorepo is 36 batches ≈ 126k comparisons).
|
|
39862
|
+
*/
|
|
39863
|
+
const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
39864
|
+
if (files.length === 0) return [];
|
|
39865
|
+
const batchCount = Math.ceil(files.length / 100);
|
|
39866
|
+
const maxFilesPerBatch = Math.ceil(files.length / batchCount);
|
|
39867
|
+
const baseArgsLengthChars = estimateArgsLength(baseArgs);
|
|
39868
|
+
const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
|
|
39869
|
+
const batches = Array.from({ length: batchCount }, () => ({
|
|
39870
|
+
fileIndexes: [],
|
|
39871
|
+
totalSizeBytes: 0,
|
|
39872
|
+
argsLengthChars: baseArgsLengthChars
|
|
39873
|
+
}));
|
|
39874
|
+
for (const fileIndex of descendingSizeIndexes) {
|
|
39875
|
+
const entryLengthChars = files[fileIndex].length + 1;
|
|
39876
|
+
let targetBatch = null;
|
|
39877
|
+
for (const batch of batches) {
|
|
39878
|
+
if (batch.fileIndexes.length >= maxFilesPerBatch) continue;
|
|
39879
|
+
if (batch.fileIndexes.length > 0 && batch.argsLengthChars + entryLengthChars > 24e3) continue;
|
|
39880
|
+
if (targetBatch === null || batch.totalSizeBytes < targetBatch.totalSizeBytes || batch.totalSizeBytes === targetBatch.totalSizeBytes && batch.fileIndexes.length < targetBatch.fileIndexes.length) targetBatch = batch;
|
|
39881
|
+
}
|
|
39882
|
+
if (targetBatch === null) {
|
|
39883
|
+
targetBatch = {
|
|
39884
|
+
fileIndexes: [],
|
|
39885
|
+
totalSizeBytes: 0,
|
|
39886
|
+
argsLengthChars: baseArgsLengthChars
|
|
39887
|
+
};
|
|
39888
|
+
batches.push(targetBatch);
|
|
39889
|
+
}
|
|
39890
|
+
targetBatch.fileIndexes.push(fileIndex);
|
|
39891
|
+
targetBatch.totalSizeBytes += sizeByFile.get(files[fileIndex]) ?? 0;
|
|
39892
|
+
targetBatch.argsLengthChars += entryLengthChars;
|
|
39893
|
+
}
|
|
39894
|
+
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
39895
|
+
};
|
|
39820
39896
|
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
39821
39897
|
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
39822
39898
|
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
@@ -39832,7 +39908,6 @@ const resolveUserCacheScope = () => {
|
|
|
39832
39908
|
return "shared";
|
|
39833
39909
|
}
|
|
39834
39910
|
};
|
|
39835
|
-
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39836
39911
|
/**
|
|
39837
39912
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
39838
39913
|
* the runner: once for the primary scan, once for the
|
|
@@ -39891,7 +39966,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
39891
39966
|
* 6. always restore disable directives + clean up the temp dir
|
|
39892
39967
|
*/
|
|
39893
39968
|
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 = "
|
|
39969
|
+
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
39970
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
39896
39971
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
39897
39972
|
validateRuleRegistration();
|
|
@@ -39943,8 +40018,15 @@ const runOxlint = async (options) => {
|
|
|
39943
40018
|
"json",
|
|
39944
40019
|
...sharedArgs
|
|
39945
40020
|
];
|
|
39946
|
-
|
|
39947
|
-
const
|
|
40021
|
+
await yieldToEventLoop();
|
|
40022
|
+
const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
|
|
40023
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
|
|
40024
|
+
const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
|
|
40025
|
+
const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
|
|
40026
|
+
baseArgs: passBaseArgs,
|
|
40027
|
+
files: passFiles,
|
|
40028
|
+
sizeByFile
|
|
40029
|
+
}) : batchIncludePaths(passBaseArgs, passFiles);
|
|
39948
40030
|
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
39949
40031
|
if (files.length === 0) return {
|
|
39950
40032
|
diagnostics: [],
|
|
@@ -39958,7 +40040,7 @@ const runOxlint = async (options) => {
|
|
|
39958
40040
|
};
|
|
39959
40041
|
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
39960
40042
|
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
39961
|
-
const passFileBatches =
|
|
40043
|
+
const passFileBatches = buildFileBatches(passBaseArgs, files);
|
|
39962
40044
|
const spawnPass = () => spawnLintBatches({
|
|
39963
40045
|
baseArgs: passBaseArgs,
|
|
39964
40046
|
fileBatches: passFileBatches,
|
|
@@ -40007,6 +40089,7 @@ const runOxlint = async (options) => {
|
|
|
40007
40089
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40008
40090
|
const missFiles = [];
|
|
40009
40091
|
const replayedDiagnostics = [];
|
|
40092
|
+
let hashSliceStartedAt = performance.now();
|
|
40010
40093
|
for (const candidateFile of candidateFiles) {
|
|
40011
40094
|
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
40012
40095
|
if (contentHash === null) {
|
|
@@ -40018,6 +40101,10 @@ const runOxlint = async (options) => {
|
|
|
40018
40101
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
40019
40102
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
40020
40103
|
else replayedDiagnostics.push(...cachedDiagnostics);
|
|
40104
|
+
if (performance.now() - hashSliceStartedAt >= 12) {
|
|
40105
|
+
await yieldToEventLoop();
|
|
40106
|
+
hashSliceStartedAt = performance.now();
|
|
40107
|
+
}
|
|
40021
40108
|
}
|
|
40022
40109
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40023
40110
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
@@ -40060,7 +40147,7 @@ const runOxlint = async (options) => {
|
|
|
40060
40147
|
]);
|
|
40061
40148
|
}
|
|
40062
40149
|
const baseArgs = makeBaseArgs(configPath);
|
|
40063
|
-
const fileBatches =
|
|
40150
|
+
const fileBatches = buildFileBatches(baseArgs, candidateFiles);
|
|
40064
40151
|
const runBatches = () => spawnLintBatches({
|
|
40065
40152
|
baseArgs,
|
|
40066
40153
|
fileBatches,
|
|
@@ -41433,7 +41520,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
41433
41520
|
/** Display name used in client-facing messages and progress titles. */
|
|
41434
41521
|
const SERVER_DISPLAY_NAME = "React Doctor";
|
|
41435
41522
|
/** Server version reported in `serverInfo`; injected at build, `dev` from source. */
|
|
41436
|
-
const SERVER_VERSION = "0.6.
|
|
41523
|
+
const SERVER_VERSION = "0.6.3";
|
|
41437
41524
|
/** `Diagnostic.source` shown next to every published diagnostic. */
|
|
41438
41525
|
const DIAGNOSTIC_SOURCE = "react-doctor";
|
|
41439
41526
|
/**
|
|
@@ -43483,5 +43570,5 @@ const startLanguageServer = () => {
|
|
|
43483
43570
|
};
|
|
43484
43571
|
//#endregion
|
|
43485
43572
|
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=
|
|
43573
|
+
!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]="94a57ec3-d5a9-52ea-a743-e91d996065b8")}catch(e){}}();
|
|
43574
|
+
//# debugId=94a57ec3-d5a9-52ea-a743-e91d996065b8
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3-dev.1571119",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
67
|
"yaml": "^2.9.0",
|
|
68
|
-
"deslop-js": "0.6.
|
|
69
|
-
"oxlint-plugin-react-doctor": "0.6.
|
|
68
|
+
"deslop-js": "0.6.3",
|
|
69
|
+
"oxlint-plugin-react-doctor": "0.6.3-dev.1571119"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
"@xterm/headless": "^6.0.0",
|
|
75
75
|
"commander": "^14.0.3",
|
|
76
76
|
"ora": "^9.4.0",
|
|
77
|
-
"@react-doctor/
|
|
78
|
-
"@react-doctor/
|
|
79
|
-
"@react-doctor/language-server": "0.6.
|
|
77
|
+
"@react-doctor/core": "0.6.3",
|
|
78
|
+
"@react-doctor/api": "0.6.3",
|
|
79
|
+
"@react-doctor/language-server": "0.6.3"
|
|
80
80
|
},
|
|
81
81
|
"engines": {
|
|
82
82
|
"node": "^20.19.0 || >=22.13.0"
|