react-doctor 0.9.11 → 0.9.12-dev.025d69d
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/README.md +28 -1
- package/bin/react-doctor.js +1 -9
- package/dist/active-run-trace-dlIZeEvb.js +18 -0
- package/dist/active-scan-abort-registry-DwG6tSPy.js +28 -0
- package/dist/{build-section-divider-sLcWe1hO.js → build-section-divider-Rtaiowhy.js} +10 -25
- package/dist/{ci-DsrrD52i.js → ci-BB3u5Gt7.js} +13 -12
- package/dist/cli.js +84 -32
- package/dist/{design-CP25gyyg.js → design-DndYvifI.js} +4 -4
- package/dist/{devtools-DPRVRu3S.js → devtools-BiC-AP3B.js} +4 -4
- package/dist/{exit-gracefully-CVVkBxDS.js → exit-gracefully-DShLWQuA.js} +15 -9
- package/dist/get-east-asian-width-Cglw13G7.js +444 -0
- package/dist/{report-error-CAEixMg1.js → handle-error-D1wDDJF-.js} +18 -68
- package/dist/index.d.ts +70 -61
- package/dist/index.js +55252 -54707
- package/dist/{inspect-D6y8oLtg.js → inspect-FuGxonlI.js} +26 -24
- package/dist/{install-afMFGeTB.js → install-DMK7JRVX.js} +9 -8
- package/dist/{install-doctor-script-Be7Mv3-l.js → install-doctor-script-JE3JnnQc.js} +7 -449
- package/dist/{install-github-workflow-Dev2F6Ea.js → install-github-workflow-BdY3qY2f.js} +10 -6
- package/dist/{install-react-doctor-DIPVunY4.js → install-react-doctor-BMn0_WV6.js} +47 -45
- package/dist/{run-scan-app-BGzlyiIB.js → jsx-runtime-pWsmwZGj.js} +281 -2726
- package/dist/{print-branded-header-D5jLdo4p.js → print-branded-header-BmI9tLH6.js} +4 -4
- package/dist/project-analysis-worker.d.ts +1 -0
- package/dist/project-analysis-worker.js +51901 -0
- package/dist/prompt-runtime-scan-url-BfBBYAfw.js +240 -0
- package/dist/{read-object-file-CEh1CqnD.js → read-object-file-CRpKojJA.js} +4 -4
- package/dist/{record-metric-CJxSHfJD.js → record-metric-BM82PBSe.js} +3338 -6056
- package/dist/report-error-CZHQUNh4.js +64 -0
- package/dist/{report-workflow-result-C0jbKH8p.js → report-workflow-result-xEGmAMA7.js} +5 -5
- package/dist/{resolve-scope-DS7rcRRz.js → resolve-scope-Bnf4Y4EW.js} +6 -6
- package/dist/{rules-BJynymuU.js → rules-DzOjQMX6.js} +6 -6
- package/dist/run-scan-app-BV3v34-X.js +2510 -0
- package/dist/runtime-scan/browser-probe.iife.js +855 -0
- package/dist/runtime-scan-B85-DJZM.js +770 -0
- package/dist/sanitize-runtime-url-CPjeveTJ.js +76 -0
- package/dist/{scan-CgPRG6_z.js → scan-DuP6gdzt.js} +10 -22
- package/dist/semver-DFtnEmN-.js +3219 -0
- package/dist/{should-fail-scan-gate-4jf7ZRdL.js → should-fail-scan-gate-DXLurKbC.js} +199 -195
- package/dist/{should-skip-prompts-C78r7Ww7.js → should-skip-prompts-CQyO96OW.js} +4 -4
- package/dist/should-use-tui-CNPwNNtA.js +25 -0
- package/dist/skills/improve-threejs/SKILL.md +102 -0
- package/dist/skills/performance/SKILL.md +113 -0
- package/dist/skills/react-doctor/SKILL.md +6 -0
- package/dist/{unref-stdin-Dc8ipkaw.js → telemetry-runtime-DbSLLSa0.js} +57 -33
- package/dist/unref-stdin-BKT_s38a.js +12 -0
- package/dist/{version-DDrwsTas.js → version-Zw_S1mCX.js} +4 -4
- package/dist/{why-BRNrKY1b.js → why-B-WcOS0C.js} +4 -4
- package/dist/with-run-span-CVyFHX3Z.js +110 -0
- package/package.json +9 -12
- package/dist/chunk-q7NCDQ7-.js +0 -28
- package/dist/lsp.js +0 -81114
|
@@ -1,23 +1,25 @@
|
|
|
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]="
|
|
3
|
-
import { $
|
|
4
|
-
import {
|
|
5
|
-
import { n as buildFooterLinkLines,
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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]="180703bb-aaa0-51d3-84ce-75244afe4feb")}catch(e){}}();
|
|
3
|
+
import { $ as Linter, A as SCAN_RESULT_CACHE_FILENAME, An as gen, At as discoverSupportedSubprojects, B as isNonInteractiveEnvironment, Bn as sync, Cn as error, Ct as collectSourceFileCountsByDirectory, Dn as Diagnostic, Dt as createNodeReadFileLinesSync, En as warn, Et as countOptInProjectRuleSelections, Fn as provide, G as isCiEnvironment, Gt as hashFileContents, Ht as groupBy, In as provideContext, J as Config, Jt as isPathInsideDirectory, K as isCiOrCodingAgentEnvironment, Kn as succeed, Kt as highlighter, Ln as provideService, Mn as map, Mt as filterPathsOutsideDirectories, Nn as orElseSucceed, Nt as filterSourceFiles, On as decodeUnknownSync, Ot as createOxlintSpawnSlots, Pn as promise, Q as LintPartialFailures, Qt as isScanComplete, Rn as runPromise, S as BASELINE_FILES_TEMP_DIR_PREFIX, Sn as Console, T as METRIC, Tn as log, Tt as computeDiagnosticDelta, U as ACTION_INPUT_ENVIRONMENT_VARIABLES, Ut as hasPublishedFixRecipe, Vn as try_, W as detectRunnerOs, Wn as mergeAll, X as Git, Xt as isReactDoctorError, Y as Files, Zn as ParentSpan, _ as VERSION, _n as shouldUseMaintainabilityLayer, _t as buildRuleSeverityControls, an as materializeSourceTree, ar as DIAGNOSTIC_CATEGORY_BUCKETS, at as Progress, bt as canonicalizeUserRuleKey, cn as resolveGithubActionsScoreMetadata, ct as Score, d as isJsonModeActive, dn as resolveScanConcurrency, dr as JSX_DUPLICATION_SOURCE_FILE_PATTERN, dt as SupplyChain, en as listSourceFiles, et as Maintainability, fn as resolveScanTarget, fr as JSX_FILE_PATTERN, gt as buildRuleDocsUrl, hr as SHARE_BASE_URL, in as mapWithConcurrency, it as PerFileLintCacheEnabled, jt as filterDiagnosticsForSurface, l as getRunId, ln as resolveLintBatchOrdering, lr as GIT_SHOW_MAX_BUFFER_BYTES, lt as SidecarLintCacheEnabled, mn as runInspect, mr as OXLINT_NODE_REQUIREMENT, mt as atomicWriteJson, n as recordDistribution, nn as listWorkspacePackages, nt as OxlintConcurrency, on as mergeReactDoctorConfigs, ot as Project, pn as restoreLegacyThrow, pr as MILLISECONDS_PER_SECOND, qt as isMonorepoRoot, r as toSpanAttributes, rt as OxlintSpawnSlots, sn as remainingDeadlineBudgetMs, sr as ENTERPRISE_CONTACT_URL, st as Reporter, t as recordCount, tn as listSourceFilesCooperative, tr as CI_URL, tt as NodeResolver, un as resolveReactDoctorCacheDir, ur as HTML_FILE_PATTERN, v as makeNoopConsole, vn as summarizeDiagnostics, vt as buildSkippedChecks, wt as computeConfigFingerprint, xn as yieldToEventLoop, xr as isFile, xt as classifyFileContext, yr as isDirectory, yt as calculateScore, z as CliInputError, zn as runSync, zt as getDiagnosticRuleIdentity } from "./record-metric-BM82PBSe.js";
|
|
4
|
+
import { t as getTelemetryContext } from "./telemetry-runtime-DbSLLSa0.js";
|
|
5
|
+
import { n as buildFooterLinkLines, t as buildSectionDivider } from "./build-section-divider-Rtaiowhy.js";
|
|
6
|
+
import { t as activeScanAbortRegistry } from "./active-scan-abort-registry-DwG6tSPy.js";
|
|
7
|
+
import { C as isRecord, M as cliLogger, S as getPackageJsonPath, a as installReactDoctorScriptStep, c as spinner, k as runGit, n as findNearestPackageDirectory, o as isSpinnerSilent, s as setSpinnerSilent } from "./install-doctor-script-JE3JnnQc.js";
|
|
8
|
+
import { d as detectDefaultBranch, l as prompts, r as installReactDoctorWorkflow, u as isCommandAvailable } from "./install-github-workflow-BdY3qY2f.js";
|
|
9
|
+
import { a as isValidBlockingLevel, c as openWorkflowPullRequest, l as stageWorkflowFile, o as pickBlockingLevel, u as toForwardSlashes } from "./resolve-scope-Bnf4Y4EW.js";
|
|
10
|
+
import { n as resetSentryRunState, r as withRunSpan, t as recordSentryProjectContext } from "./with-run-span-CVyFHX3Z.js";
|
|
11
|
+
import { h as detectAvailableAgents, t as reportWorkflowResult } from "./report-workflow-result-xEGmAMA7.js";
|
|
10
12
|
import { createRequire } from "node:module";
|
|
11
13
|
import { spawn } from "node:child_process";
|
|
12
14
|
import * as path$1 from "node:path";
|
|
13
15
|
import path from "node:path";
|
|
14
|
-
import * as fs$
|
|
16
|
+
import * as fs$2 from "node:fs";
|
|
15
17
|
import fs, { mkdtempSync, rmSync } from "node:fs";
|
|
16
|
-
import crypto, { randomUUID } from "node:crypto";
|
|
17
18
|
import ts from "typescript";
|
|
19
|
+
import crypto, { randomUUID } from "node:crypto";
|
|
18
20
|
import { tmpdir } from "node:os";
|
|
21
|
+
import * as fs$1 from "node:fs/promises";
|
|
19
22
|
import { performance } from "node:perf_hooks";
|
|
20
|
-
import { codeFrameColumns } from "@babel/code-frame";
|
|
21
23
|
//#region src/cli/utils/apply-observability.ts
|
|
22
24
|
/**
|
|
23
25
|
* Installs the tracing backend for the inspect program.
|
|
@@ -84,7 +86,10 @@ const buildSpinnerProgressHandle = (text) => {
|
|
|
84
86
|
*/
|
|
85
87
|
const buildRuntimeLayers = (input) => {
|
|
86
88
|
const linterLayer = input.shouldSkipLint ? Linter.layerOf([]) : Linter.layerOxlint;
|
|
87
|
-
const
|
|
89
|
+
const maintainabilityLayer = shouldUseMaintainabilityLayer({
|
|
90
|
+
shouldRunDuplicateJsx: input.shouldRunDeadCode,
|
|
91
|
+
userConfig: input.userConfig
|
|
92
|
+
}) ? Maintainability.layerNode : Maintainability.layerOf([]);
|
|
88
93
|
const scoreLayer = input.shouldComputeScore ? Score.layerHttp : Score.layerOf(null);
|
|
89
94
|
const supplyChainLayer = input.shouldRunSupplyChain ? SupplyChain.layerNode : SupplyChain.layerOf([]);
|
|
90
95
|
const projectLayer = input.projectInfoOverride === void 0 ? Project.layerNode : Project.layerOf(input.projectInfoOverride);
|
|
@@ -94,112 +99,11 @@ const buildRuntimeLayers = (input) => {
|
|
|
94
99
|
config: input.userConfig,
|
|
95
100
|
resolvedDirectory: input.directory,
|
|
96
101
|
configSourceDirectory: input.configSourceDirectory
|
|
97
|
-
}) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive,
|
|
102
|
+
}) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, maintainabilityLayer, progressLayer, reporterLayer, scoreLayer, supplyChainLayer);
|
|
98
103
|
const layersWithConcurrency = input.oxlintConcurrency === void 0 ? baseLayers : mergeAll(baseLayers, succeed(OxlintConcurrency, input.oxlintConcurrency));
|
|
99
104
|
return input.oxlintSpawnSlots === void 0 ? layersWithConcurrency : mergeAll(layersWithConcurrency, succeed(OxlintSpawnSlots, input.oxlintSpawnSlots));
|
|
100
105
|
};
|
|
101
106
|
//#endregion
|
|
102
|
-
//#region src/cli/utils/with-run-span.ts
|
|
103
|
-
/**
|
|
104
|
-
* Ends the run span without letting a telemetry failure change the run's
|
|
105
|
-
* outcome — the invariant every other emit site here already holds.
|
|
106
|
-
*
|
|
107
|
-
* The failure path is the reason this matters most: a throw from `end` there
|
|
108
|
-
* would replace the user's original scan error, so a project-config mistake
|
|
109
|
-
* could surface as an exporter stack trace, be reclassified as a crash, and get
|
|
110
|
-
* reported as one.
|
|
111
|
-
*/
|
|
112
|
-
const endSpanSafely = (span, exit) => {
|
|
113
|
-
try {
|
|
114
|
-
span.end(BigInt(Date.now()) * NANOSECONDS_PER_MILLISECOND, exit);
|
|
115
|
-
} catch {}
|
|
116
|
-
};
|
|
117
|
-
/**
|
|
118
|
-
* Clears the module-level run-scoped telemetry state — the current scanned
|
|
119
|
-
* project and the active run trace. `inspect()` calls this at the start of every
|
|
120
|
-
* run and again after a clean one, so a prior or just-finished scan can't attach
|
|
121
|
-
* its project tags / trace to a later run or to a non-scan error (e.g.
|
|
122
|
-
* inspectAction's post-loop finalize/handoff steps). A thrown scan error skips
|
|
123
|
-
* the post-run reset, leaving the state for the command catch to attribute and
|
|
124
|
-
* link the crash. Concurrent batch members (`concurrentScan`) never touch this
|
|
125
|
-
* state — they neither write nor reset it. Safe to call when telemetry is off
|
|
126
|
-
* (the refs are read only when an event is built).
|
|
127
|
-
*/
|
|
128
|
-
const resetSentryRunState = () => {
|
|
129
|
-
setSentryProjectInfo(null);
|
|
130
|
-
setActiveRunTrace(null);
|
|
131
|
-
};
|
|
132
|
-
/**
|
|
133
|
-
* Runs an inspect invocation inside a root span so each `react-doctor` run is a
|
|
134
|
-
* first-class trace with timing and the run snapshot as attributes. The span is
|
|
135
|
-
* handed to `run` so the scan's Effect spans parent under it and so the wide
|
|
136
|
-
* event can be stamped onto it once the outcome is known.
|
|
137
|
-
*
|
|
138
|
-
* A no-op pass-through when telemetry is off (`--no-telemetry`, tests, or an
|
|
139
|
-
* unconfigured build) — `run` receives `undefined` and no span is created, so
|
|
140
|
-
* there's no added exit latency.
|
|
141
|
-
*
|
|
142
|
-
* The span is created against the shared telemetry context rather than a
|
|
143
|
-
* freshly-provided layer, so it lives in the same tracer as the scan program's
|
|
144
|
-
* spans; see `telemetry-runtime.ts` for why that matters. It is started and
|
|
145
|
-
* ended manually because the body it wraps is a promise-returning imperative
|
|
146
|
-
* function, not an Effect — the same shape the Sentry inactive span had.
|
|
147
|
-
*
|
|
148
|
-
* While the span runs, its trace context is recorded as the active run trace so
|
|
149
|
-
* `reportErrorToSentry` can attach a crash thrown during the scan back to this
|
|
150
|
-
* trace (errors surface in the command catch, after the span has ended).
|
|
151
|
-
*
|
|
152
|
-
* A `concurrentScan` (one member of the CLI's multi-project pool) still gets its
|
|
153
|
-
* own root span, but skips recording the active run trace — the module-level
|
|
154
|
-
* handle has single-scan semantics, and overlapping writers would link a crash
|
|
155
|
-
* to an arbitrary sibling's trace.
|
|
156
|
-
*/
|
|
157
|
-
const withRunSpan = async (run, options = {}) => {
|
|
158
|
-
const telemetryContext = getTelemetryContext();
|
|
159
|
-
if (telemetryContext === null) return run(void 0);
|
|
160
|
-
const { tags } = buildSentryScope();
|
|
161
|
-
const command = typeof tags.command === "string" ? tags.command : "inspect";
|
|
162
|
-
let rootSpan;
|
|
163
|
-
try {
|
|
164
|
-
rootSpan = runSync(makeSpan(`react-doctor ${command}`, { attributes: toSpanAttributes(tags) }).pipe(provideContext(telemetryContext)));
|
|
165
|
-
} catch {
|
|
166
|
-
return run(void 0);
|
|
167
|
-
}
|
|
168
|
-
recordRunTraceId(rootSpan.traceId);
|
|
169
|
-
if (options.concurrentScan !== true) setActiveRunTrace({
|
|
170
|
-
traceId: rootSpan.traceId,
|
|
171
|
-
spanId: rootSpan.spanId,
|
|
172
|
-
sampled: rootSpan.sampled
|
|
173
|
-
});
|
|
174
|
-
try {
|
|
175
|
-
const result = await run(rootSpan);
|
|
176
|
-
endSpanSafely(rootSpan, void_);
|
|
177
|
-
return result;
|
|
178
|
-
} catch (error) {
|
|
179
|
-
endSpanSafely(rootSpan, fail(error));
|
|
180
|
-
throw error;
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
/**
|
|
184
|
-
* Records the scanned project (discovered in the `beforeLint` hook): remembers
|
|
185
|
-
* it for the lazy Sentry error-capture path (`buildSentryScope` folds it into
|
|
186
|
-
* exception events) and for per-emit metric attributes, and sets it on the run's
|
|
187
|
-
* root span so the trace carries the project shape too. Always cheap — the span
|
|
188
|
-
* attributes are skipped when `rootSpan` is absent (telemetry off), and storing
|
|
189
|
-
* the info is a plain assignment.
|
|
190
|
-
*
|
|
191
|
-
* A `concurrentScan` only sets the span attributes: the module-level project ref
|
|
192
|
-
* has single-scan semantics, and overlapping writers would stamp events and
|
|
193
|
-
* metrics with an arbitrary sibling's project. Wide events keep full attribution
|
|
194
|
-
* (they ride the span); per-emit metrics simply omit the project shape during a
|
|
195
|
-
* concurrent batch (absent, never wrong).
|
|
196
|
-
*/
|
|
197
|
-
const recordSentryProjectContext = (projectInfo, rootSpan, options = {}) => {
|
|
198
|
-
if (options.concurrentScan !== true) setSentryProjectInfo(projectInfo);
|
|
199
|
-
if (rootSpan === void 0) return;
|
|
200
|
-
for (const [key, value] of Object.entries(toSpanAttributes(buildSentryProjectContext(projectInfo).tags))) rootSpan.attribute(key, value);
|
|
201
|
-
};
|
|
202
|
-
//#endregion
|
|
203
107
|
//#region src/cli/utils/diagnostic-grouping.ts
|
|
204
108
|
const buildRulePriorityMap = (scores) => {
|
|
205
109
|
const rulePriority = /* @__PURE__ */ new Map();
|
|
@@ -310,7 +214,7 @@ const recordScanMetrics = (input) => {
|
|
|
310
214
|
recordCount(METRIC.scanCompleted, 1, {
|
|
311
215
|
mode: input.mode,
|
|
312
216
|
lint: input.lint,
|
|
313
|
-
|
|
217
|
+
maintainability: input.deadCode,
|
|
314
218
|
parallel: input.parallel,
|
|
315
219
|
scoreOnly: input.scoreOnly,
|
|
316
220
|
didLintFail: input.didLintFail,
|
|
@@ -350,7 +254,7 @@ const recordScanMetrics = (input) => {
|
|
|
350
254
|
else if (!input.noScore && !input.didLintFail && !input.didDeadCodeFail) recordCount(METRIC.scoreUnavailable, 1, { mode: input.mode });
|
|
351
255
|
if (input.baselineDegraded) recordCount(METRIC.baselineDegraded, 1);
|
|
352
256
|
if (input.didLintFail) recordCount(METRIC.lintFailed, 1, { reasonKind: input.lintFailureReasonKind });
|
|
353
|
-
if (input.didDeadCodeFail) recordCount(METRIC.
|
|
257
|
+
if (input.didDeadCodeFail) recordCount(METRIC.maintainabilityFailed, 1);
|
|
354
258
|
for (const check of result.skippedChecks) recordCount(METRIC.scanCheckSkipped, 1, {
|
|
355
259
|
check,
|
|
356
260
|
reason: result.skippedCheckReasons?.[check] ?? null
|
|
@@ -494,10 +398,16 @@ const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
|
494
398
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
495
399
|
"oxlint/package.json",
|
|
496
400
|
"oxlint-plugin-react-doctor/package.json",
|
|
497
|
-
"deslop-js/package.json",
|
|
498
401
|
"eslint-plugin-react-hooks/package.json"
|
|
499
402
|
];
|
|
500
403
|
const bundledRequire = createRequire(import.meta.url);
|
|
404
|
+
const RULE_PLUGIN_CONTENT_FINGERPRINT = (() => {
|
|
405
|
+
try {
|
|
406
|
+
return `oxlint-plugin-react-doctor#fingerprint=${hashFileContents(bundledRequire.resolve("oxlint-plugin-react-doctor")) ?? "unreadable"}`;
|
|
407
|
+
} catch {
|
|
408
|
+
return "oxlint-plugin-react-doctor#fingerprint=unresolved";
|
|
409
|
+
}
|
|
410
|
+
})();
|
|
501
411
|
const normalizeForStableJson = (value) => {
|
|
502
412
|
if (value === null) return null;
|
|
503
413
|
if (value === void 0) return void 0;
|
|
@@ -633,12 +543,12 @@ const resolveCacheFilePath = (projectDirectory) => path.join(resolveReactDoctorC
|
|
|
633
543
|
const readPersistedCache = (cacheFilePath) => {
|
|
634
544
|
try {
|
|
635
545
|
const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
|
|
636
|
-
if (!isRecord(parsed) || parsed.version !==
|
|
637
|
-
version:
|
|
546
|
+
if (!isRecord(parsed) || parsed.version !== 7) return {
|
|
547
|
+
version: 7,
|
|
638
548
|
entries: []
|
|
639
549
|
};
|
|
640
550
|
if (!Array.isArray(parsed.entries)) return {
|
|
641
|
-
version:
|
|
551
|
+
version: 7,
|
|
642
552
|
entries: []
|
|
643
553
|
};
|
|
644
554
|
const entries = [];
|
|
@@ -653,12 +563,12 @@ const readPersistedCache = (cacheFilePath) => {
|
|
|
653
563
|
});
|
|
654
564
|
}
|
|
655
565
|
return {
|
|
656
|
-
version:
|
|
566
|
+
version: 7,
|
|
657
567
|
entries
|
|
658
568
|
};
|
|
659
569
|
} catch {
|
|
660
570
|
return {
|
|
661
|
-
version:
|
|
571
|
+
version: 7,
|
|
662
572
|
entries: []
|
|
663
573
|
};
|
|
664
574
|
}
|
|
@@ -666,7 +576,7 @@ const readPersistedCache = (cacheFilePath) => {
|
|
|
666
576
|
/**
|
|
667
577
|
* The global cache off-switch (`REACT_DOCTOR_NO_CACHE`), which disables every
|
|
668
578
|
* cache subsystem: this whole-repo scan cache plus core's per-file lint,
|
|
669
|
-
* sidecar
|
|
579
|
+
* sidecar caches (their `Context.Reference` defaults read the
|
|
670
580
|
* same variable). Exported for the wide event's `cache.temperature`
|
|
671
581
|
* derivation, which reports `"disabled"` instead of `"cold"` when the switch
|
|
672
582
|
* is on. Granular knobs (`REACT_DOCTOR_NO_FILE_CACHE`, …) are deliberately
|
|
@@ -689,7 +599,7 @@ const fileFingerprint = (filePath) => {
|
|
|
689
599
|
return null;
|
|
690
600
|
}
|
|
691
601
|
};
|
|
692
|
-
const
|
|
602
|
+
const resolveScanResultToolchainFingerprint = (nodeBinaryPath) => {
|
|
693
603
|
const fingerprints = [];
|
|
694
604
|
if (nodeBinaryPath !== null) fingerprints.push(nodeBinaryPath === process.execPath ? `node=${process.version}` : `node=${fileFingerprint(nodeBinaryPath) ?? "unreadable"}`);
|
|
695
605
|
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
@@ -699,6 +609,7 @@ const resolveToolchainFingerprint = (nodeBinaryPath) => {
|
|
|
699
609
|
} catch {
|
|
700
610
|
fingerprints.push(`${specifier}=missing`);
|
|
701
611
|
}
|
|
612
|
+
fingerprints.push(RULE_PLUGIN_CONTENT_FINGERPRINT);
|
|
702
613
|
return fingerprints;
|
|
703
614
|
};
|
|
704
615
|
const buildScanResultCacheKey = (input) => {
|
|
@@ -708,14 +619,14 @@ const buildScanResultCacheKey = (input) => {
|
|
|
708
619
|
if (repositoryIdentity === null) return null;
|
|
709
620
|
if (stringifyStableJson(input.userConfig) === null) return null;
|
|
710
621
|
const cacheKeyJson = stringifyStableJson({
|
|
711
|
-
schemaVersion:
|
|
622
|
+
schemaVersion: 7,
|
|
712
623
|
projectIdentity: resolveProjectIdentity(input.projectDirectory),
|
|
713
624
|
headSha: repositoryIdentity.headSha,
|
|
714
625
|
worktreeFingerprint: repositoryIdentity.worktreeFingerprint,
|
|
715
626
|
dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
|
|
716
627
|
reactDoctorVersion: input.version,
|
|
717
628
|
nodeVersion: process.version,
|
|
718
|
-
toolchainFingerprint:
|
|
629
|
+
toolchainFingerprint: resolveScanResultToolchainFingerprint(input.nodeBinaryPath),
|
|
719
630
|
configFingerprint: computeConfigFingerprint(input.projectDirectory, input.version),
|
|
720
631
|
hasConfigOverride: input.hasConfigOverride,
|
|
721
632
|
configSourceDirectory: input.configSourceDirectory,
|
|
@@ -756,7 +667,7 @@ const createScanResultCache = (projectDirectory) => {
|
|
|
756
667
|
for (const entry of readPersistedCache(cacheFilePath).entries) mergedEntries.set(entry.key, entry);
|
|
757
668
|
for (const entry of entries.values()) mergedEntries.set(entry.key, entry);
|
|
758
669
|
atomicWriteJson(cacheFilePath, {
|
|
759
|
-
version:
|
|
670
|
+
version: 7,
|
|
760
671
|
entries: [...mergedEntries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
|
|
761
672
|
});
|
|
762
673
|
};
|
|
@@ -791,8 +702,7 @@ const shouldBlockCi = (diagnostics, blockingLevel) => {
|
|
|
791
702
|
/**
|
|
792
703
|
* Lowercase, key-safe form of a rule category for the `diag.category.*`
|
|
793
704
|
* telemetry attribute namespace (categories carry spaces / capitals, e.g.
|
|
794
|
-
* "Performance" → `performance`, "Dead Code" → `dead_code`).
|
|
795
|
-
* CLI run event and the editor LSP wide event so both namespace identically.
|
|
705
|
+
* "Performance" → `performance`, "Dead Code" → `dead_code`).
|
|
796
706
|
*/
|
|
797
707
|
const toCategoryKey = (category) => category.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
798
708
|
//#endregion
|
|
@@ -822,12 +732,6 @@ const readEnvBoolean = (name) => {
|
|
|
822
732
|
return value.toLowerCase() === "true" || value === "1";
|
|
823
733
|
};
|
|
824
734
|
const ratioOf = (numerator, denominator) => denominator != null && denominator > 0 ? (numerator ?? 0) / denominator : null;
|
|
825
|
-
const resolveDeadCodeReuseRatio = (result) => {
|
|
826
|
-
if (result.deadCodeCacheHit === true) return 1;
|
|
827
|
-
const summaryTotal = (result.deadCodeSummaryCacheHits ?? 0) + (result.deadCodeSummaryCacheMisses ?? 0);
|
|
828
|
-
if (summaryTotal > 0) return (result.deadCodeSummaryCacheHits ?? 0) / summaryTotal;
|
|
829
|
-
return result.deadCodeCacheHit === false ? 0 : null;
|
|
830
|
-
};
|
|
831
735
|
/**
|
|
832
736
|
* One queryable cache temperature per scan, derived from the whole stack:
|
|
833
737
|
*
|
|
@@ -835,8 +739,7 @@ const resolveDeadCodeReuseRatio = (result) => {
|
|
|
835
739
|
* `wholeRepoCacheHit` flag from the CLI's cachedPayload
|
|
836
740
|
* branch; no scan work ran).
|
|
837
741
|
* - `"warm"` — any incremental reuse: per-file lint hits, sidecar
|
|
838
|
-
* replays
|
|
839
|
-
* summary-cache hits.
|
|
742
|
+
* replays.
|
|
840
743
|
* - `"disabled"` — zero reuse because the global `REACT_DOCTOR_NO_CACHE`
|
|
841
744
|
* off-switch is on. Granular per-cache opt-outs
|
|
842
745
|
* (`REACT_DOCTOR_NO_FILE_CACHE`, …) still read warm/cold,
|
|
@@ -845,7 +748,7 @@ const resolveDeadCodeReuseRatio = (result) => {
|
|
|
845
748
|
*
|
|
846
749
|
* `cache.warmth` is the headline reuse magnitude in [0, 1]: the plain mean of
|
|
847
750
|
* the subsystem reuse fractions known this run (lint hit ratio, sidecar
|
|
848
|
-
* replay ratio
|
|
751
|
+
* replay ratio), skipping subsystems that never consulted a
|
|
849
752
|
* cache; `1` on turbo, dropped when nothing consulted any cache. Deliberately
|
|
850
753
|
* unweighted — the per-subsystem dims stay the precise signal; warmth is the
|
|
851
754
|
* p50/p90-able summary. Emitted only on the success path.
|
|
@@ -858,11 +761,7 @@ const buildCacheAttributes = (input) => {
|
|
|
858
761
|
warmth: 1
|
|
859
762
|
});
|
|
860
763
|
const result = input.result;
|
|
861
|
-
const subsystemReuseRatios = [
|
|
862
|
-
ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
|
|
863
|
-
ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount),
|
|
864
|
-
resolveDeadCodeReuseRatio(result)
|
|
865
|
-
];
|
|
764
|
+
const subsystemReuseRatios = [ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount), ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)];
|
|
866
765
|
let knownSubsystemCount = 0;
|
|
867
766
|
let reuseRatioSum = 0;
|
|
868
767
|
for (const reuseRatio of subsystemReuseRatios) {
|
|
@@ -1014,13 +913,7 @@ const buildOutcomeAttributes = (input) => {
|
|
|
1014
913
|
sidecarTotalFiles: result.lintSidecarTotalFileCount,
|
|
1015
914
|
sidecarReplayRatio: ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)
|
|
1016
915
|
}),
|
|
1017
|
-
...withNamespace("
|
|
1018
|
-
failed: input.didDeadCodeFail,
|
|
1019
|
-
overlapped: input.deadCodeOverlapped,
|
|
1020
|
-
cacheHit: result.deadCodeCacheHit,
|
|
1021
|
-
summaryCacheHits: result.deadCodeSummaryCacheHits,
|
|
1022
|
-
summaryCacheMisses: result.deadCodeSummaryCacheMisses
|
|
1023
|
-
}),
|
|
916
|
+
...withNamespace("maintainability", { failed: input.didDeadCodeFail }),
|
|
1024
917
|
...withNamespace("supplyChain", { overlapTimedOut: input.supplyChainOverlapTimedOut }),
|
|
1025
918
|
...withNamespace("securityScan", { failed: input.securityScanFailed }),
|
|
1026
919
|
...withNamespace("timing", {
|
|
@@ -1063,7 +956,7 @@ const buildScanAttributes = (input) => {
|
|
|
1063
956
|
workerCount: input.workerCount,
|
|
1064
957
|
maxDurationMs: input.maxDurationMs,
|
|
1065
958
|
lint: input.lint,
|
|
1066
|
-
|
|
959
|
+
maintainability: input.deadCode,
|
|
1067
960
|
supplyChain: input.supplyChain,
|
|
1068
961
|
scoreOnly: input.scoreOnly,
|
|
1069
962
|
noScore: input.noScore,
|
|
@@ -1074,6 +967,7 @@ const buildScanAttributes = (input) => {
|
|
|
1074
967
|
hasCustomConfig: input.hasCustomConfig,
|
|
1075
968
|
rulesConfigured: ruleKeys.length,
|
|
1076
969
|
rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
|
|
970
|
+
projectAnalysisRuleCount: countOptInProjectRuleSelections(buildRuleSeverityControls(input.userConfig)),
|
|
1077
971
|
fileCount: input.result?.scannedFileCount,
|
|
1078
972
|
nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length,
|
|
1079
973
|
htmlFileCount: input.result?.analyzedFiles?.filter((filePath) => HTML_FILE_PATTERN.test(filePath)).length,
|
|
@@ -1084,7 +978,7 @@ const buildScanAttributes = (input) => {
|
|
|
1084
978
|
* Projects a scan into the namespaced attribute set for its root span — the
|
|
1085
979
|
* canonical per-scan "wide event". Every attribute carries a dotted namespace
|
|
1086
980
|
* that groups it by concept (`scan.*` config, `action.*` CI knobs, `outcome.*`
|
|
1087
|
-
* verdict, `diag.*` findings, `score.*`, `lint.*`, `
|
|
981
|
+
* verdict, `diag.*` findings, `score.*`, `lint.*`, `maintainability.*`, `cache.*`,
|
|
1088
982
|
* `supplyChain.*`, `timing.*`, `migration.*`, `baseline.*`) so the attributes
|
|
1089
983
|
* tree up in Sentry's attribute browser and stay filter-/group-/aggregate-able
|
|
1090
984
|
* in the Spans dataset. Pure and exported so the projection (outcome
|
|
@@ -1323,7 +1217,7 @@ const buildNoScoreMessage = (input) => {
|
|
|
1323
1217
|
reason = input.disabledMessage ?? "Score disabled by --no-score.";
|
|
1324
1218
|
break;
|
|
1325
1219
|
case "analysis-incomplete":
|
|
1326
|
-
reason = "Score not shown because lint or
|
|
1220
|
+
reason = "Score not shown because lint or maintainability analysis could not complete.";
|
|
1327
1221
|
break;
|
|
1328
1222
|
case "api-unavailable":
|
|
1329
1223
|
reason = "Score unavailable (could not reach the score API).";
|
|
@@ -1371,7 +1265,7 @@ const formatRuleSummary = (ruleKey, ruleDiagnostics) => {
|
|
|
1371
1265
|
const ruleDumpFileName = (ruleKey) => ruleKey.replace(/\//g, "--") + ".txt";
|
|
1372
1266
|
const readPreviousRuleDumpFileNames = (directory) => {
|
|
1373
1267
|
try {
|
|
1374
|
-
const previous = JSON.parse(fs$
|
|
1268
|
+
const previous = JSON.parse(fs$2.readFileSync(path$1.join(directory, "diagnostics.json"), "utf8"));
|
|
1375
1269
|
return new Set(previous.filter((d) => typeof d?.plugin === "string" && typeof d?.rule === "string").map((d) => ruleDumpFileName(`${d.plugin}/${d.rule}`)));
|
|
1376
1270
|
} catch {
|
|
1377
1271
|
return /* @__PURE__ */ new Set();
|
|
@@ -1379,10 +1273,10 @@ const readPreviousRuleDumpFileNames = (directory) => {
|
|
|
1379
1273
|
};
|
|
1380
1274
|
const writeDiagnosticsDirectory = (diagnostics, outputDirectory) => {
|
|
1381
1275
|
const directory = outputDirectory ? path$1.resolve(outputDirectory) : path$1.join(tmpdir(), `react-doctor-${randomUUID()}`);
|
|
1382
|
-
if (outputDirectory) for (const fileName of readPreviousRuleDumpFileNames(directory)) fs$
|
|
1383
|
-
fs$
|
|
1384
|
-
for (const [ruleKey, ruleDiagnostics] of buildSortedRuleGroups(diagnostics)) fs$
|
|
1385
|
-
fs$
|
|
1276
|
+
if (outputDirectory) for (const fileName of readPreviousRuleDumpFileNames(directory)) fs$2.rmSync(path$1.join(directory, fileName), { force: true });
|
|
1277
|
+
fs$2.mkdirSync(directory, { recursive: true });
|
|
1278
|
+
for (const [ruleKey, ruleDiagnostics] of buildSortedRuleGroups(diagnostics)) fs$2.writeFileSync(path$1.join(directory, ruleDumpFileName(ruleKey)), formatRuleSummary(ruleKey, ruleDiagnostics));
|
|
1279
|
+
fs$2.writeFileSync(path$1.join(directory, "diagnostics.json"), JSON.stringify(diagnostics));
|
|
1386
1280
|
return directory;
|
|
1387
1281
|
};
|
|
1388
1282
|
//#endregion
|
|
@@ -1426,6 +1320,9 @@ const formatElapsedTime = (elapsedMilliseconds) => {
|
|
|
1426
1320
|
return `${(elapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1)}s`;
|
|
1427
1321
|
};
|
|
1428
1322
|
//#endregion
|
|
1323
|
+
//#region src/cli/utils/format-skipped-check-label.ts
|
|
1324
|
+
const formatSkippedCheckLabel = (skippedCheck) => skippedCheck === "dead-code" ? "maintainability" : skippedCheck;
|
|
1325
|
+
//#endregion
|
|
1429
1326
|
//#region src/cli/utils/pluralize.ts
|
|
1430
1327
|
const pluralize = (count, singularNoun) => `${count} ${singularNoun}${count === 1 ? "" : "s"}`;
|
|
1431
1328
|
//#endregion
|
|
@@ -1474,7 +1371,7 @@ const printHeadlessReport = (input) => gen(function* () {
|
|
|
1474
1371
|
}
|
|
1475
1372
|
if (input.skippedChecks.length > 0) {
|
|
1476
1373
|
yield* log("");
|
|
1477
|
-
yield* warn(highlighter.warn(`Results are incomplete: ${input.skippedChecks.join(" and ")} checks failed.`));
|
|
1374
|
+
yield* warn(highlighter.warn(`Results are incomplete: ${input.skippedChecks.map(formatSkippedCheckLabel).join(" and ")} checks failed.`));
|
|
1478
1375
|
}
|
|
1479
1376
|
});
|
|
1480
1377
|
//#endregion
|
|
@@ -1674,12 +1571,39 @@ const renderAndRecordScan = async (input) => {
|
|
|
1674
1571
|
didDeadCodeFail: input.payload.didDeadCodeFail,
|
|
1675
1572
|
supplyChainOverlapTimedOut: input.payload.supplyChainOverlapTimedOut,
|
|
1676
1573
|
securityScanFailed: input.payload.securityScanFailed,
|
|
1677
|
-
deadCodeOverlapped: input.payload.deadCodeOverlapped,
|
|
1678
1574
|
suppressedRuleCounts: input.payload.suppressedRuleCounts ?? []
|
|
1679
1575
|
});
|
|
1680
1576
|
return result;
|
|
1681
1577
|
};
|
|
1682
1578
|
//#endregion
|
|
1579
|
+
//#region src/cli/utils/copy-unchanged-baseline-sources.ts
|
|
1580
|
+
const copyUnchangedBaselineSources = async (input) => {
|
|
1581
|
+
const baseMaterializedFiles = new Set(input.baseMaterializedFiles.map(toForwardSlashes));
|
|
1582
|
+
const headChangedFiles = new Set(input.headChangedFiles.map(toForwardSlashes));
|
|
1583
|
+
const untrackedFiles = new Set(input.untrackedFiles.map(toForwardSlashes));
|
|
1584
|
+
const sourceDirectory = path$1.resolve(input.directory);
|
|
1585
|
+
const targetDirectory = path$1.resolve(input.tempDirectory);
|
|
1586
|
+
return (await mapWithConcurrency(input.sourceFiles.filter((filePath) => {
|
|
1587
|
+
const normalizedPath = toForwardSlashes(filePath);
|
|
1588
|
+
return !baseMaterializedFiles.has(normalizedPath) && !headChangedFiles.has(normalizedPath) && !untrackedFiles.has(normalizedPath);
|
|
1589
|
+
}), 32, async (filePath) => {
|
|
1590
|
+
input.signal?.throwIfAborted();
|
|
1591
|
+
if (input.deadlineEpochMs !== null && Date.now() >= input.deadlineEpochMs) return false;
|
|
1592
|
+
const sourcePath = path$1.resolve(sourceDirectory, filePath);
|
|
1593
|
+
const targetPath = path$1.resolve(targetDirectory, filePath);
|
|
1594
|
+
if (!isPathInsideDirectory(sourcePath, sourceDirectory) || !isPathInsideDirectory(targetPath, targetDirectory)) return false;
|
|
1595
|
+
try {
|
|
1596
|
+
if (!(await fs$1.lstat(sourcePath)).isFile()) return false;
|
|
1597
|
+
await fs$1.mkdir(path$1.dirname(targetPath), { recursive: true });
|
|
1598
|
+
await fs$1.copyFile(sourcePath, targetPath);
|
|
1599
|
+
return true;
|
|
1600
|
+
} catch {
|
|
1601
|
+
input.signal?.throwIfAborted();
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1604
|
+
})).every(Boolean);
|
|
1605
|
+
};
|
|
1606
|
+
//#endregion
|
|
1683
1607
|
//#region src/cli/utils/read-diagnostic-evidence.ts
|
|
1684
1608
|
const SOURCE_EXTENSIONS = [
|
|
1685
1609
|
".ts",
|
|
@@ -2097,7 +2021,6 @@ const resolveMergeBaseRef = async (directory, ref) => {
|
|
|
2097
2021
|
const silentConsole$1 = makeNoopConsole();
|
|
2098
2022
|
const countIncompleteLintFiles = (lintPartialFailures) => countDroppedLintFiles(lintPartialFailures) + countDeadlineSkippedFiles(lintPartialFailures);
|
|
2099
2023
|
const runBaselineComparison = async (input) => {
|
|
2100
|
-
const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
|
|
2101
2024
|
const baselineIncludePaths = filterPathsOutsideDirectories({
|
|
2102
2025
|
rootDirectory: input.directory,
|
|
2103
2026
|
relativePaths: input.options.includePaths,
|
|
@@ -2113,12 +2036,24 @@ const runBaselineComparison = async (input) => {
|
|
|
2113
2036
|
relativePaths: input.headFiles,
|
|
2114
2037
|
excludedDirectories: input.options.excludedProjectDirectories
|
|
2115
2038
|
}) : void 0;
|
|
2039
|
+
const remainingBaselineBudgetMs = input.deadlineEpochMs === null ? null : remainingDeadlineBudgetMs(input.deadlineEpochMs);
|
|
2040
|
+
if (remainingBaselineBudgetMs === 0) return null;
|
|
2041
|
+
const baselineDeadlineSignal = remainingBaselineBudgetMs === null ? void 0 : AbortSignal.timeout(remainingBaselineBudgetMs);
|
|
2042
|
+
const baselineListingSignal = baselineDeadlineSignal === void 0 ? input.oxlintRuntime.abortSignal : input.oxlintRuntime.abortSignal === void 0 ? baselineDeadlineSignal : AbortSignal.any([input.oxlintRuntime.abortSignal, baselineDeadlineSignal]);
|
|
2043
|
+
let maintainabilitySourceFiles = [];
|
|
2044
|
+
if (input.options.deadCode) try {
|
|
2045
|
+
maintainabilitySourceFiles = (await listSourceFilesCooperative(input.directory, baselineListingSignal)).filter((filePath) => JSX_DUPLICATION_SOURCE_FILE_PATTERN.test(filePath) && classifyFileContext(filePath) === "production");
|
|
2046
|
+
} catch (error) {
|
|
2047
|
+
if (baselineDeadlineSignal?.aborted) return null;
|
|
2048
|
+
throw error;
|
|
2049
|
+
}
|
|
2050
|
+
const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
|
|
2116
2051
|
const snapshot = await materializeBaselineFiles({
|
|
2117
2052
|
directory: input.directory,
|
|
2118
2053
|
ref: input.baselineRef,
|
|
2119
|
-
files: baselineIncludePaths,
|
|
2120
|
-
baseFiles: baselineBaseFiles,
|
|
2121
|
-
headFiles: baselineHeadFiles,
|
|
2054
|
+
files: input.options.deadCode ? input.options.includePaths : baselineIncludePaths,
|
|
2055
|
+
baseFiles: input.options.deadCode ? input.baseFiles : baselineBaseFiles,
|
|
2056
|
+
headFiles: input.options.deadCode ? input.headFiles : baselineHeadFiles,
|
|
2122
2057
|
tempDirectory: temporaryDirectory
|
|
2123
2058
|
}).catch((error) => {
|
|
2124
2059
|
rmSync(temporaryDirectory, {
|
|
@@ -2136,22 +2071,47 @@ const runBaselineComparison = async (input) => {
|
|
|
2136
2071
|
}
|
|
2137
2072
|
try {
|
|
2138
2073
|
if (!snapshot.isComplete) return null;
|
|
2074
|
+
if (input.options.deadCode && !await copyUnchangedBaselineSources({
|
|
2075
|
+
directory: input.directory,
|
|
2076
|
+
sourceFiles: maintainabilitySourceFiles,
|
|
2077
|
+
baseMaterializedFiles: snapshot.materializedFiles,
|
|
2078
|
+
headChangedFiles: snapshot.headFiles,
|
|
2079
|
+
untrackedFiles: snapshot.untrackedFiles,
|
|
2080
|
+
tempDirectory: snapshot.tempDirectory,
|
|
2081
|
+
deadlineEpochMs: input.deadlineEpochMs,
|
|
2082
|
+
signal: input.oxlintRuntime.abortSignal
|
|
2083
|
+
})) return null;
|
|
2084
|
+
const filteredSnapshotBaseFiles = filterPathsOutsideDirectories({
|
|
2085
|
+
rootDirectory: input.directory,
|
|
2086
|
+
relativePaths: snapshot.baseFiles,
|
|
2087
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
2088
|
+
});
|
|
2089
|
+
const filteredSnapshotHeadFiles = filterPathsOutsideDirectories({
|
|
2090
|
+
rootDirectory: input.directory,
|
|
2091
|
+
relativePaths: snapshot.headFiles,
|
|
2092
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
2093
|
+
});
|
|
2139
2094
|
const analyzedHeadFiles = new Set(input.headAnalyzedFiles.map(toForwardSlashes));
|
|
2140
|
-
const baseFiles = new Set(
|
|
2141
|
-
const expectedHeadFiles = new Set(
|
|
2095
|
+
const baseFiles = new Set((baselineBaseFiles ?? filteredSnapshotBaseFiles).map(toForwardSlashes));
|
|
2096
|
+
const expectedHeadFiles = new Set((baselineHeadFiles ?? filteredSnapshotHeadFiles).map(toForwardSlashes));
|
|
2142
2097
|
for (const filePath of baselineIncludePaths) {
|
|
2143
2098
|
const normalizedFilePath = toForwardSlashes(filePath);
|
|
2144
2099
|
if (!baseFiles.has(normalizedFilePath)) expectedHeadFiles.add(normalizedFilePath);
|
|
2145
2100
|
}
|
|
2146
|
-
if (filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
|
|
2101
|
+
if (input.options.lint && filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
|
|
2102
|
+
const baselineLintPaths = new Set([...baselineIncludePaths, ...filteredSnapshotBaseFiles].map(toForwardSlashes));
|
|
2103
|
+
const materializedLintPaths = snapshot.materializedFiles.filter((filePath) => baselineLintPaths.has(toForwardSlashes(filePath)));
|
|
2104
|
+
const maintainabilityFocusPaths = [...new Set([...input.options.includePaths, ...snapshot.baseFiles].map(toForwardSlashes))];
|
|
2105
|
+
const baselineExcludedProjectDirectories = input.options.excludedProjectDirectories.map((excludedDirectory) => path.resolve(excludedDirectory)).filter((excludedDirectory) => isPathInsideDirectory(excludedDirectory, input.directory)).map((excludedDirectory) => path.resolve(snapshot.tempDirectory, path.relative(input.directory, excludedDirectory)));
|
|
2106
|
+
const baseIncludePaths = materializedLintPaths.length > 0 ? materializedLintPaths : maintainabilityFocusPaths;
|
|
2147
2107
|
const runtimeLayers = buildRuntimeLayers({
|
|
2148
2108
|
directory: snapshot.tempDirectory,
|
|
2149
2109
|
hasConfigOverride: true,
|
|
2150
2110
|
userConfig: input.userConfig,
|
|
2151
2111
|
configSourceDirectory: input.configSourceDirectory,
|
|
2152
2112
|
projectInfoOverride: input.headProjectInfo,
|
|
2153
|
-
shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath,
|
|
2154
|
-
shouldRunDeadCode:
|
|
2113
|
+
shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath || materializedLintPaths.length === 0,
|
|
2114
|
+
shouldRunDeadCode: input.options.deadCode,
|
|
2155
2115
|
shouldRunSupplyChain: input.options.supplyChain,
|
|
2156
2116
|
shouldComputeScore: false,
|
|
2157
2117
|
shouldShowProgressSpinners: false,
|
|
@@ -2160,7 +2120,8 @@ const runBaselineComparison = async (input) => {
|
|
|
2160
2120
|
});
|
|
2161
2121
|
const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
|
|
2162
2122
|
directory: snapshot.tempDirectory,
|
|
2163
|
-
includePaths:
|
|
2123
|
+
includePaths: baseIncludePaths,
|
|
2124
|
+
maintainabilityFocusPaths,
|
|
2164
2125
|
customRulesOnly: input.options.customRulesOnly,
|
|
2165
2126
|
respectInlineDisables: input.options.respectInlineDisables,
|
|
2166
2127
|
warnings: input.options.warnings,
|
|
@@ -2169,7 +2130,7 @@ const runBaselineComparison = async (input) => {
|
|
|
2169
2130
|
includedTags: input.options.includedTags,
|
|
2170
2131
|
includeTagDefaults: input.options.includeTagDefaults,
|
|
2171
2132
|
nodeBinaryPath: input.resolvedNodeBinaryPath ?? void 0,
|
|
2172
|
-
runDeadCode:
|
|
2133
|
+
runDeadCode: input.options.deadCode,
|
|
2173
2134
|
isCi: input.options.isCi,
|
|
2174
2135
|
doctorVersion: VERSION,
|
|
2175
2136
|
runId: getRunId(),
|
|
@@ -2177,10 +2138,16 @@ const runBaselineComparison = async (input) => {
|
|
|
2177
2138
|
suppressScanSummary: true,
|
|
2178
2139
|
supplyChainManifestChanged: input.options.supplyChainManifestChanged,
|
|
2179
2140
|
deadlineEpochMs: input.deadlineEpochMs ?? void 0,
|
|
2180
|
-
signal: input.oxlintRuntime.abortSignal
|
|
2141
|
+
signal: input.oxlintRuntime.abortSignal,
|
|
2142
|
+
excludedProjectDirectories: baselineExcludedProjectDirectories,
|
|
2143
|
+
retainExcludedProjectDeadCodeDiagnostics: input.options.retainExcludedProjectDeadCodeDiagnostics
|
|
2181
2144
|
}, {}).pipe(provide(runtimeLayers), provideService(PerFileLintCacheEnabled, false), provideService(SidecarLintCacheEnabled, false), provideService(Console, silentConsole$1))), { signal: input.oxlintRuntime.abortSignal });
|
|
2182
|
-
if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
|
|
2183
|
-
const hasUnscannedUntrackedSourceFiles = filterSourceFiles(
|
|
2145
|
+
if (baseOutput.didLintFail || baseOutput.didDeadCodeFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
|
|
2146
|
+
const hasUnscannedUntrackedSourceFiles = filterSourceFiles(filterPathsOutsideDirectories({
|
|
2147
|
+
rootDirectory: input.directory,
|
|
2148
|
+
relativePaths: snapshot.untrackedFiles,
|
|
2149
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
2150
|
+
}).map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
|
|
2184
2151
|
const diagnosticDelta = computeDiagnosticDelta({
|
|
2185
2152
|
headDiagnostics: input.headDiagnostics,
|
|
2186
2153
|
baseDiagnostics: baseOutput.diagnostics,
|
|
@@ -2353,6 +2320,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
2353
2320
|
directory,
|
|
2354
2321
|
precomputedSourceFileCount: options.precomputedSourceFileCount,
|
|
2355
2322
|
includePaths: options.includePaths,
|
|
2323
|
+
changedLineRanges: options.changedLineRanges ?? void 0,
|
|
2356
2324
|
customRulesOnly: options.customRulesOnly,
|
|
2357
2325
|
respectInlineDisables: options.respectInlineDisables,
|
|
2358
2326
|
warnings: options.warnings,
|
|
@@ -2383,7 +2351,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
2383
2351
|
else runSync(error(highlighter.error(lintFailureReason)));
|
|
2384
2352
|
let inspectDiagnostics = output.diagnostics;
|
|
2385
2353
|
let baselineDelta;
|
|
2386
|
-
if (options.baseline && isDiffMode && !didLintFail && countIncompleteLintFiles(output.lintPartialFailures) === 0) {
|
|
2354
|
+
if (options.baseline && isDiffMode && !didLintFail && !output.didDeadCodeFail && countIncompleteLintFiles(output.lintPartialFailures) === 0) {
|
|
2387
2355
|
const comparison = await runBaselineComparison({
|
|
2388
2356
|
directory,
|
|
2389
2357
|
options,
|
|
@@ -2536,13 +2504,13 @@ const resolveCursorBundledNode = () => {
|
|
|
2536
2504
|
if (!localAppData) return null;
|
|
2537
2505
|
const cursorAgentRoot = path$1.join(localAppData, "cursor-agent", "versions");
|
|
2538
2506
|
try {
|
|
2539
|
-
const versions = fs$
|
|
2507
|
+
const versions = fs$2.readdirSync(cursorAgentRoot);
|
|
2540
2508
|
if (versions.length === 0) return null;
|
|
2541
2509
|
const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
|
|
2542
2510
|
for (const version of sortedVersions) try {
|
|
2543
2511
|
const bundledNodePath = path$1.join(cursorAgentRoot, version, "node.exe");
|
|
2544
2512
|
const entryScriptPath = path$1.join(cursorAgentRoot, version, "index.js");
|
|
2545
|
-
if (fs$
|
|
2513
|
+
if (fs$2.statSync(bundledNodePath).isFile() && fs$2.statSync(entryScriptPath).isFile()) return {
|
|
2546
2514
|
command: bundledNodePath,
|
|
2547
2515
|
args: [entryScriptPath]
|
|
2548
2516
|
};
|
|
@@ -2556,15 +2524,15 @@ const resolveWindowsCmdEntryScript = (command) => {
|
|
|
2556
2524
|
for (const directory of pathDirectories) {
|
|
2557
2525
|
const cmdFilePath = path$1.join(directory, `${command}.cmd`);
|
|
2558
2526
|
try {
|
|
2559
|
-
if (!fs$
|
|
2527
|
+
if (!fs$2.statSync(cmdFilePath).isFile()) continue;
|
|
2560
2528
|
} catch {
|
|
2561
2529
|
continue;
|
|
2562
2530
|
}
|
|
2563
2531
|
try {
|
|
2564
|
-
const entryScriptMatch = fs$
|
|
2532
|
+
const entryScriptMatch = fs$2.readFileSync(cmdFilePath, "utf8").match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:m?js|cjs))"/);
|
|
2565
2533
|
if (!entryScriptMatch) continue;
|
|
2566
2534
|
const resolvedScriptPath = path$1.resolve(directory, entryScriptMatch[1]);
|
|
2567
|
-
if (fs$
|
|
2535
|
+
if (fs$2.statSync(resolvedScriptPath).isFile()) return resolvedScriptPath;
|
|
2568
2536
|
} catch {}
|
|
2569
2537
|
}
|
|
2570
2538
|
return null;
|
|
@@ -2829,6 +2797,45 @@ const resolveProjectScan = async (rootScanTarget, projectDirectory) => {
|
|
|
2829
2797
|
};
|
|
2830
2798
|
};
|
|
2831
2799
|
//#endregion
|
|
2800
|
+
//#region src/cli/utils/highlight-code-line.ts
|
|
2801
|
+
const CODE_TOKEN_PATTERN = /\/\/.*|\/\*.*?\*\/|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`|\b(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|type|typeof|undefined|var|void|while|with|yield)\b|\b(?:0[xob][\da-f]+|\d+(?:\.\d+)?)\b/giu;
|
|
2802
|
+
const highlightCodeLine = (codeLine) => codeLine.replace(CODE_TOKEN_PATTERN, (token) => {
|
|
2803
|
+
if (token.startsWith("/")) return highlighter.gray(token);
|
|
2804
|
+
if (token.startsWith("\"") || token.startsWith("'") || token.startsWith("`")) return highlighter.success(token);
|
|
2805
|
+
if (/^\d|^0[xob]/i.test(token)) return highlighter.warn(token);
|
|
2806
|
+
return highlighter.info(token);
|
|
2807
|
+
});
|
|
2808
|
+
//#endregion
|
|
2809
|
+
//#region src/cli/utils/render-code-frame.ts
|
|
2810
|
+
const SOURCE_LINE_BREAK_PATTERN = /\r\n|[\n\r\u2028\u2029]/;
|
|
2811
|
+
const renderCodeFrame = ({ source, line, column, endLine, message, linesAbove, linesBelow, maximumLineLength }) => {
|
|
2812
|
+
const sourceLines = source.split(SOURCE_LINE_BREAK_PATTERN);
|
|
2813
|
+
if (line < 1 || line > sourceLines.length) return null;
|
|
2814
|
+
const offendingLine = sourceLines[line - 1];
|
|
2815
|
+
if (offendingLine === void 0 || offendingLine.length > maximumLineLength) return null;
|
|
2816
|
+
const lastMarkedLine = Math.min(Math.max(endLine ?? line, line), sourceLines.length);
|
|
2817
|
+
const firstDisplayedLine = Math.max(line - linesAbove, 1);
|
|
2818
|
+
const lastDisplayedLine = Math.min(lastMarkedLine + linesBelow, sourceLines.length);
|
|
2819
|
+
const lineNumberWidth = String(lastDisplayedLine).length;
|
|
2820
|
+
const hasCaret = lastMarkedLine === line && column !== void 0;
|
|
2821
|
+
const outputLines = [];
|
|
2822
|
+
if (message && !hasCaret) outputLines.push(`${" ".repeat(lineNumberWidth + 2)}${highlighter.error(message)}`);
|
|
2823
|
+
for (let displayedLineNumber = firstDisplayedLine; displayedLineNumber <= lastDisplayedLine; displayedLineNumber += 1) {
|
|
2824
|
+
const sourceLine = sourceLines[displayedLineNumber - 1] ?? "";
|
|
2825
|
+
const isMarked = displayedLineNumber >= line && displayedLineNumber <= lastMarkedLine;
|
|
2826
|
+
const gutter = ` ${String(displayedLineNumber).padStart(lineNumberWidth)} |`;
|
|
2827
|
+
const marker = isMarked ? highlighter.error(">") : " ";
|
|
2828
|
+
outputLines.push(`${marker}${highlighter.dim(gutter)}${sourceLine.length > 0 ? ` ${highlightCodeLine(sourceLine)}` : ""}`);
|
|
2829
|
+
if (isMarked && hasCaret) {
|
|
2830
|
+
const markerSpacing = sourceLine.slice(0, Math.max(column - 1, 0)).replace(/[^\t]/g, " ");
|
|
2831
|
+
const emptyGutter = gutter.replace(/\d/g, " ");
|
|
2832
|
+
const markerMessage = message ? ` ${highlighter.error(message)}` : "";
|
|
2833
|
+
outputLines.push(` ${highlighter.dim(emptyGutter)} ${markerSpacing}${highlighter.error("^")}${markerMessage}`);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
return outputLines.join("\n");
|
|
2837
|
+
};
|
|
2838
|
+
//#endregion
|
|
2832
2839
|
//#region src/cli/utils/resolve-absolute-path.ts
|
|
2833
2840
|
/**
|
|
2834
2841
|
* Resolves a diagnostic's `filePath` (relative to its project root, or
|
|
@@ -2851,23 +2858,20 @@ const buildCodeFrame = (input) => {
|
|
|
2851
2858
|
const absolutePath = resolveAbsolutePath(input.filePath, input.rootDirectory);
|
|
2852
2859
|
let source;
|
|
2853
2860
|
try {
|
|
2854
|
-
source = fs$
|
|
2861
|
+
source = fs$2.readFileSync(absolutePath, "utf8");
|
|
2855
2862
|
} catch {
|
|
2856
2863
|
return null;
|
|
2857
2864
|
}
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
end: { line: input.endLine }
|
|
2862
|
-
} : { start: {
|
|
2865
|
+
const endLine = input.endLine != null && input.endLine > input.line ? input.endLine : void 0;
|
|
2866
|
+
return renderCodeFrame({
|
|
2867
|
+
source,
|
|
2863
2868
|
line: input.line,
|
|
2864
|
-
column: input.column > 0 ? input.column : void 0
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
highlightCode: true,
|
|
2869
|
+
column: endLine === void 0 && input.column > 0 ? input.column : void 0,
|
|
2870
|
+
endLine,
|
|
2871
|
+
message: input.message,
|
|
2868
2872
|
linesAbove: 1,
|
|
2869
2873
|
linesBelow: 1,
|
|
2870
|
-
|
|
2874
|
+
maximumLineLength: 200
|
|
2871
2875
|
});
|
|
2872
2876
|
};
|
|
2873
2877
|
//#endregion
|
|
@@ -2875,7 +2879,7 @@ const buildCodeFrame = (input) => {
|
|
|
2875
2879
|
const discoverWorkspacePackages = (rootDirectory) => {
|
|
2876
2880
|
const hasRootPackageJson = isFile(path$1.join(rootDirectory, "package.json"));
|
|
2877
2881
|
const packages = listWorkspacePackages(rootDirectory);
|
|
2878
|
-
if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) return
|
|
2882
|
+
if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) return discoverSupportedSubprojects(rootDirectory);
|
|
2879
2883
|
return packages;
|
|
2880
2884
|
};
|
|
2881
2885
|
const selectProjects = async (rootDirectory, projectFlag, skipPrompts, configProjects) => {
|
|
@@ -2991,7 +2995,7 @@ const shouldFailScanGate = (input) => {
|
|
|
2991
2995
|
return shouldBlockCi(filterScansForSurface(input.scans, "ciFailure"), input.blockingLevel);
|
|
2992
2996
|
};
|
|
2993
2997
|
//#endregion
|
|
2994
|
-
export { resolveMergeBaseRef as A,
|
|
2998
|
+
export { resolveMergeBaseRef as A, buildNoScoreMessage as B, copyToClipboard as C, deduplicateProjectScans as D, collectProjectSourceFileCounts as E, printFooter as F, formatFixRecipeLine as G, hasLintHardFailure as H, printDiagnosticsDump as I, formatLearnMoreLine as K, formatDiagnosticSite as L, pluralize as M, formatSkippedCheckLabel as N, createInvocationInspect as O, formatElapsedTime as P, hasIncompleteScoreAnalysis as R, CLI_AGENT_BINARIES as S, buildHandoffPayload as T, buildRulePriorityMap as U, buildEmptyReportMessage as V, buildSortedRuleGroups as W, resolveCliInspectOptions as _, projectManifestChanged as a, setUpGitHubActions as b, selectProjects as c, resolveProjectChangedLineRanges as d, resolveProjectDiffIncludePaths as f, resolveWorkspaceDeadCodeOwner as g, retryMissingProjectScores as h, validateModeFlags as i, printHeadlessReport as j, inspect as k, buildCodeFrame as l, resolveProjectRelativeDirectory as m, formatSkippedProjectsMessage as n, partitionProjectScanOutcomes as o, resolveProjectSourceFilePaths as p, validateIncludeUntrackedScope as r, discoverWorkspacePackages as s, shouldFailScanGate as t, resolveProjectScan as u, filterScansForSurface as v, launchCliAgent as w, detectLaunchableAgents as x, countUniqueScannedFiles as y, filterDiagnosticsByCategories as z };
|
|
2995
2999
|
|
|
2996
|
-
//# sourceMappingURL=should-fail-scan-gate-
|
|
2997
|
-
//# debugId=
|
|
3000
|
+
//# sourceMappingURL=should-fail-scan-gate-DXLurKbC.js.map
|
|
3001
|
+
//# debugId=180703bb-aaa0-51d3-84ce-75244afe4feb
|