react-doctor 0.9.13-dev.ff7dd67 → 0.9.14-dev.499a020
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/_react-doctor_require-typescript-DXQDYeWK.js +16 -0
- package/dist/{active-run-trace-dlIZeEvb.js → active-run-trace-Dewycxda.js} +3 -3
- package/dist/{active-scan-abort-registry-DwG6tSPy.js → active-scan-abort-registry-CCTgal0O.js} +3 -3
- package/dist/build-section-divider-BsKw6rbA.js +81 -0
- package/dist/{ci-ByM_SFTq.js → ci-Y7ntr_X7.js} +14 -13
- package/dist/cli.js +31 -30
- package/dist/constants-CW_854PH.js +1035 -0
- package/dist/constants-DNh19qg9.js +23 -0
- package/dist/{design-2hjOmtV1.js → design-CZbXwzWh.js} +4 -4
- package/dist/{devtools-CC2PAZ85.js → devtools-D20sefkm.js} +3 -3
- package/dist/duplicate-jsx-worker.d.ts +1 -0
- package/dist/duplicate-jsx-worker.js +636 -0
- package/dist/{exit-gracefully-niPFXq9Y.js → exit-gracefully-Bq8kCmar.js} +7 -7
- package/dist/fs-utils-C88IbqFa.js +45 -0
- package/dist/{get-east-asian-width-Bv2TD3Xj.js → get-east-asian-width-DRPy7dqz.js} +4 -4
- package/dist/{handle-error-D-4lexec.js → handle-error-DkZtH4y1.js} +6 -5
- package/dist/index.d.ts +8511 -8509
- package/dist/index.js +61693 -60580
- package/dist/{inspect-BrctG7ql.js → inspect-4R70DaBp.js} +38 -27
- package/dist/{install-CcFc56OK.js → install-DSOkcCn6.js} +10 -9
- package/dist/{install-doctor-script-Coa2ErCg.js → install-doctor-script-k5jxcUWB.js} +17 -8
- package/dist/{install-github-workflow-DlfdrYkQ.js → install-github-workflow-Dh3nS6ms.js} +10 -10
- package/dist/{install-react-doctor-BWxqZMX-.js → install-react-doctor-NmiRJQtp.js} +15 -11
- package/dist/is-cache-globally-disabled-CoHiYz2i.js +10 -0
- package/dist/{jsx-runtime-Njm7vAoN.js → jsx-runtime-B1PPqZBs.js} +11 -10
- package/dist/oxlint-worker.d.ts +1 -0
- package/dist/oxlint-worker.js +225 -0
- package/dist/{path-format-DnHpmJwG.js → path-format-By20mo8A.js} +3 -3
- package/dist/{print-branded-header-DFkeG4BS.js → print-branded-header-ydu9MCpt.js} +4 -4
- package/dist/project-analysis-worker.js +461 -480
- package/dist/{prompt-runtime-scan-url-BenBNq25.js → prompt-runtime-scan-url-BEFbrKze.js} +6 -6
- package/dist/react-compiler-detection-worker.d.ts +1 -0
- package/dist/react-compiler-detection-worker.js +1029 -0
- package/dist/{read-object-file-CqMtxcfF.js → read-object-file-f9AWbDOo.js} +4 -4
- package/dist/{record-metric-Bkorhx43.js → record-metric-BeiisdDK.js} +12998 -12770
- package/dist/{report-error-DPjmfRi9.js → report-error-CwReeGgL.js} +9 -8
- package/dist/{report-workflow-result-CBL6CrLY.js → report-workflow-result-CRhHHcHp.js} +24 -11
- package/dist/{resolve-scope-BFloAClV.js → resolve-scope-DsrT6gaf.js} +7 -7
- package/dist/{rules-BKNy9YIl.js → rules-Bhk46JNb.js} +8 -7
- package/dist/{run-scan-app-Bc70ozp-.js → run-scan-app-B_r_jHg-.js} +30 -23
- package/dist/{runtime-scan-ODrdp5mV.js → runtime-scan-B6v3G55b.js} +13 -12
- package/dist/{sanitize-runtime-url-BYOmPSdm.js → sanitize-runtime-url-CVRMHksi.js} +4 -4
- package/dist/{scan-BfcGHHkI.js → scan-BJsk-x4c.js} +10 -9
- package/dist/{semver-DFtnEmN-.js → semver-BZIuy1gP.js} +3 -3
- package/dist/{should-fail-scan-gate-CCPDoHaM.js → should-fail-scan-gate-B8cvpr6W.js} +1339 -1283
- package/dist/{should-skip-prompts-D-ClPHHR.js → should-skip-prompts-CupUff2k.js} +4 -4
- package/dist/{should-use-tui-BaCVIPPN.js → should-use-tui-t0DIGfJ0.js} +4 -4
- package/dist/start-scan-preamble-D0QepYZ_.js +68 -0
- package/dist/start-scan-preamble-DVhXIPc0.d.ts +1 -0
- package/dist/start-scan-preamble.d.ts +1 -0
- package/dist/start-scan-preamble.js +4 -0
- package/dist/{telemetry-runtime-BGWa_GB9.js → telemetry-runtime-DfK-XYFO.js} +154 -5
- package/dist/{unref-stdin-SK3WfwxS.js → unref-stdin-D-NZBFpg.js} +3 -3
- package/dist/unwrap-typescript-expression-CrEPQUWz.js +14 -0
- package/dist/{version-DN1N-oZy.js → version-DpQFTCI_.js} +5 -4
- package/dist/{why-CyioXUF2.js → why-DA3u09OY.js} +4 -4
- package/dist/{with-run-span-GsCWW2_X.js → with-run-span-DP84SJbD.js} +7 -6
- package/package.json +9 -7
- package/dist/build-section-divider-BRuN44fL.js +0 -216
|
@@ -1,24 +1,26 @@
|
|
|
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 { t as
|
|
5
|
-
import { n as
|
|
6
|
-
import { t as
|
|
7
|
-
import { t as
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { n as
|
|
12
|
-
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]="bb74300d-38db-5b33-86a5-4ad923dfbe6a")}catch(e){}}();
|
|
3
|
+
import { E as TRACKED_FILE_FLAGS_GIT_ARGUMENTS, Ft as HTML_FILE_PATTERN, Ht as MAINTAINABILITY_SOURCE_FILE_PATTERN, Jt as OXLINT_NODE_REQUIREMENT, K as resolveReactDoctorCacheDir, Nt as GIT_SHOW_MAX_BUFFER_BYTES, Rt as JSX_FILE_PATTERN, St as ENTERPRISE_CONTACT_URL, Wt as MILLISECONDS_PER_SECOND, _t as DIAGNOSTIC_CATEGORY_BUCKETS, c as HEAD_SHA_GIT_ARGUMENTS, dn as SHARE_BASE_URL, h as REPOSITORY_ROOT_GIT_ARGUMENTS, i as BASELINE_FILES_TEMP_DIR_PREFIX, k as WORKTREE_STATUS_GIT_ARGUMENTS, q as resolveScanConcurrency, u as METRIC, ut as CI_URL, v as SCAN_RESULT_CACHE_FILENAME } from "./constants-CW_854PH.js";
|
|
4
|
+
import { t as isCacheGloballyDisabled } from "./is-cache-globally-disabled-CoHiYz2i.js";
|
|
5
|
+
import { $ as buildSkippedChecks, A as Git, B as Progress, Bt as materializeSourceTree, C as ACTION_INPUT_ENVIRONMENT_VARIABLES, Dt as hashFileContents, E as isCiOrCodingAgentEnvironment, En as succeed, F as Maintainability, Ft as listSourceFiles, Gt as resolveScanTarget, H as Reporter, Ht as remainingDeadlineBudgetMs, I as NodeResolver, In as isDirectory, It as listSourceFilesCooperative, K as SupplyChain, Kt as restoreLegacyThrow, L as OxlintConcurrency, Lt as listWorkspacePackages, Mn as isMonorepoRoot, N as LintPartialFailures, Nn as yieldToEventLoop, Nt as isScanComplete, O as Config, Ot as highlighter, P as Linter, Q as buildRuleSeverityControls, R as OxlintSpawnSlots, Rn as isFile, T as isCiEnvironment, Tt as hasPublishedFixRecipe, U as Score, Ut as resolveGithubActionsScoreMetadata, V as Project, Vt as mergeReactDoctorConfigs, W as SidecarLintCacheEnabled, Wt as resolveLintBatchOrdering, Xt as shouldUseMaintainabilityLayer, Y as atomicWriteJson, Z as buildRuleDocsUrl, Zt as summarizeDiagnostics, _ as VERSION, _n as provideService, an as log, at as computeConfigFingerprint, b as isNonInteractiveEnvironment, bn as sync, cn as decodeUnknownSync, ct as createInvocationCaches, d as isJsonModeActive, en as warmDuplicateJsxWorker, et as calculateScore, fn as map, ft as discoverSupportedSubprojects, gn as provideContext, hn as provide, ht as filterSourceFiles, it as collectSourceFilesByDirectory, j as InvocationCaches, jn as ParentSpan, jt as isReactDoctorError, k as Files, kt as isPathInsideDirectory, l as getRunId, lt as createNodeReadFileLinesSync, mn as promise, mt as filterPathsOutsideDirectories, n as recordDistribution, nn as Console, nt as classifyFileContext, on as warn, ot as computeDiagnosticDelta, pn as orElseSucceed, pt as filterDiagnosticsForSurface, qt as runInspect, r as toSpanAttributes, rn as error, sn as Diagnostic, st as countOptInProjectRuleSelections, t as recordCount, tn as warmOxlintWorkerPool, tt as canonicalizeUserRuleKey, un as gen, ut as createOxlintSpawnSlots, v as makeNoopConsole, vn as runPromise, w as detectRunnerOs, wn as mergeAll, wt as groupBy, xn as try_, xt as getDiagnosticRuleIdentity, y as CliInputError, yn as runSync, z as PerFileLintCacheEnabled, zt as mapWithConcurrency } from "./record-metric-BeiisdDK.js";
|
|
6
|
+
import { t as getTelemetryContext } from "./telemetry-runtime-DfK-XYFO.js";
|
|
7
|
+
import { t as activeScanAbortRegistry } from "./active-scan-abort-registry-CCTgal0O.js";
|
|
8
|
+
import { n as buildFooterLinkLines, t as buildSectionDivider } from "./build-section-divider-BsKw6rbA.js";
|
|
9
|
+
import { t as toForwardSlashes } from "./path-format-By20mo8A.js";
|
|
10
|
+
import { t as typescriptBinding } from "./_react-doctor_require-typescript-DXQDYeWK.js";
|
|
11
|
+
import { A as runGitAsync, C as isRecord, N as cliLogger, S as getPackageJsonPath, a as installReactDoctorScriptStep, c as spinner, n as findNearestPackageDirectory, o as isSpinnerSilent, s as setSpinnerSilent } from "./install-doctor-script-k5jxcUWB.js";
|
|
12
|
+
import { d as detectDefaultBranch, l as prompts, r as installReactDoctorWorkflow, u as isCommandAvailable } from "./install-github-workflow-Dh3nS6ms.js";
|
|
13
|
+
import { a as isValidBlockingLevel, c as openWorkflowPullRequest, l as stageWorkflowFile, o as pickBlockingLevel } from "./resolve-scope-DsrT6gaf.js";
|
|
14
|
+
import { n as resetSentryRunState, r as withRunSpan, t as recordSentryProjectContext } from "./with-run-span-DP84SJbD.js";
|
|
15
|
+
import { h as detectAvailableAgents, t as reportWorkflowResult } from "./report-workflow-result-CRhHHcHp.js";
|
|
13
16
|
import { createRequire } from "node:module";
|
|
14
|
-
import { spawn } from "node:child_process";
|
|
15
|
-
import * as path$1 from "node:path";
|
|
16
|
-
import path from "node:path";
|
|
17
17
|
import * as fs$2 from "node:fs";
|
|
18
18
|
import fs, { mkdtempSync, rmSync } from "node:fs";
|
|
19
|
-
import
|
|
20
|
-
import
|
|
19
|
+
import * as path$1 from "node:path";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { spawn } from "node:child_process";
|
|
21
22
|
import { tmpdir } from "node:os";
|
|
23
|
+
import crypto, { randomUUID } from "node:crypto";
|
|
22
24
|
import * as fs$1 from "node:fs/promises";
|
|
23
25
|
import { performance } from "node:perf_hooks";
|
|
24
26
|
//#region src/cli/utils/apply-observability.ts
|
|
@@ -102,7 +104,8 @@ const buildRuntimeLayers = (input) => {
|
|
|
102
104
|
configSourceDirectory: input.configSourceDirectory
|
|
103
105
|
}) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, maintainabilityLayer, progressLayer, reporterLayer, scoreLayer, supplyChainLayer);
|
|
104
106
|
const layersWithConcurrency = input.oxlintConcurrency === void 0 ? baseLayers : mergeAll(baseLayers, succeed(OxlintConcurrency, input.oxlintConcurrency));
|
|
105
|
-
|
|
107
|
+
const layersWithSpawnSlots = input.oxlintSpawnSlots === void 0 ? layersWithConcurrency : mergeAll(layersWithConcurrency, succeed(OxlintSpawnSlots, input.oxlintSpawnSlots));
|
|
108
|
+
return input.invocationCaches === void 0 ? layersWithSpawnSlots : mergeAll(layersWithSpawnSlots, succeed(InvocationCaches, input.invocationCaches));
|
|
106
109
|
};
|
|
107
110
|
//#endregion
|
|
108
111
|
//#region src/cli/utils/diagnostic-grouping.ts
|
|
@@ -268,762 +271,347 @@ const resolveScoreUnavailableReason = (input) => {
|
|
|
268
271
|
return input.isAnalysisIncomplete ? "analysis-incomplete" : "api-unavailable";
|
|
269
272
|
};
|
|
270
273
|
//#endregion
|
|
271
|
-
//#region src/cli/utils/
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const isNumber = (value) => typeof value === "number";
|
|
277
|
-
const isString = (value) => typeof value === "string";
|
|
278
|
-
const isDiagnosticArray = (value) => {
|
|
279
|
-
if (!Array.isArray(value)) return false;
|
|
280
|
-
try {
|
|
281
|
-
for (const entry of value) decodeDiagnostic(entry);
|
|
282
|
-
return true;
|
|
283
|
-
} catch {
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
274
|
+
//#region src/cli/utils/should-block-ci.ts
|
|
275
|
+
const shouldBlockCi = (diagnostics, blockingLevel) => {
|
|
276
|
+
if (blockingLevel === "none") return false;
|
|
277
|
+
if (blockingLevel === "warning") return diagnostics.length > 0;
|
|
278
|
+
return diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
286
279
|
};
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
key: "lintFailureReason",
|
|
314
|
-
isValid: isNullableString
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
key: "lintPartialFailures",
|
|
318
|
-
isValid: isStringArray
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
key: "didDeadCodeFail",
|
|
322
|
-
isValid: isBoolean
|
|
323
|
-
},
|
|
324
|
-
{
|
|
325
|
-
key: "deadCodeFailureReason",
|
|
326
|
-
isValid: isNullableString
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
key: "deadCodeOverlapped",
|
|
330
|
-
isValid: isBoolean
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
key: "directory",
|
|
334
|
-
isValid: isString
|
|
335
|
-
},
|
|
336
|
-
{
|
|
337
|
-
key: "scannedFileCount",
|
|
338
|
-
isValid: isNumber
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
key: "scannedFilePaths",
|
|
342
|
-
isValid: isStringArray
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
key: "scanElapsedMilliseconds",
|
|
346
|
-
isValid: isNumber
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
key: "lintFailureReasonKind",
|
|
350
|
-
isValid: isNullableString
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
key: "supplyChainOverlapTimedOut",
|
|
354
|
-
isValid: isBoolean
|
|
355
|
-
}
|
|
356
|
-
];
|
|
357
|
-
const OPTIONAL_CACHED_SCAN_PAYLOAD_FIELDS = [
|
|
358
|
-
{
|
|
359
|
-
key: "analyzedFiles",
|
|
360
|
-
isValid: isStringArray
|
|
361
|
-
},
|
|
362
|
-
{
|
|
363
|
-
key: "baselineDelta",
|
|
364
|
-
isValid: isBaselineDelta
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
key: "scanConcurrency",
|
|
368
|
-
isValid: isNumber
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
key: "securityScanFailed",
|
|
372
|
-
isValid: isBoolean
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
key: "securityScanFailureReason",
|
|
376
|
-
isValid: isNullableString
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
key: "suppressedRuleCounts",
|
|
380
|
-
isValid: isSuppressedRuleCountArray
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
key: "manifestContentHash",
|
|
384
|
-
isValid: isNullableString
|
|
385
|
-
}
|
|
386
|
-
];
|
|
387
|
-
const hasValidRequiredCachedScanPayloadFields = (value) => REQUIRED_CACHED_SCAN_PAYLOAD_FIELDS.every((field) => field.isValid(value[field.key]));
|
|
388
|
-
const hasValidOptionalCachedScanPayloadFields = (value) => OPTIONAL_CACHED_SCAN_PAYLOAD_FIELDS.every((field) => value[field.key] === void 0 || field.isValid(value[field.key]));
|
|
389
|
-
const isCachedScanPayload = (value) => {
|
|
390
|
-
if (!isRecord(value)) return false;
|
|
391
|
-
return hasValidRequiredCachedScanPayloadFields(value) && hasValidOptionalCachedScanPayloadFields(value);
|
|
280
|
+
//#endregion
|
|
281
|
+
//#region src/cli/utils/to-category-key.ts
|
|
282
|
+
/**
|
|
283
|
+
* Lowercase, key-safe form of a rule category for the `diag.category.*`
|
|
284
|
+
* telemetry attribute namespace (categories carry spaces / capitals, e.g.
|
|
285
|
+
* "Performance" → `performance`, "Dead Code" → `dead_code`).
|
|
286
|
+
*/
|
|
287
|
+
const toCategoryKey = (category) => category.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/cli/utils/with-namespace.ts
|
|
290
|
+
/**
|
|
291
|
+
* Prefixes every key in a flat attribute group with `namespace.`, so a logical
|
|
292
|
+
* group (scan config, diagnostics rollup, score, …) carries one dotted
|
|
293
|
+
* namespace applied in a single place rather than hand-spelled into each key —
|
|
294
|
+
* which is how the wide event drifted into a flat, half-namespaced soup. The
|
|
295
|
+
* dotted prefix is what makes the attributes tree up in Sentry's attribute
|
|
296
|
+
* browser and stay group-/filter-/aggregate-able in the Spans dataset.
|
|
297
|
+
*
|
|
298
|
+
* Value types are preserved verbatim (numbers stay numbers so Sentry infers a
|
|
299
|
+
* numeric attribute and `p75(...)`/`avg(...)` work; `null`/`undefined` are kept
|
|
300
|
+
* so `toSpanAttributes` can drop absent signals rather than coerce them).
|
|
301
|
+
*/
|
|
302
|
+
const withNamespace = (namespace, attributes) => {
|
|
303
|
+
const namespaced = {};
|
|
304
|
+
for (const [key, value] of Object.entries(attributes)) namespaced[`${namespace}.${key}`] = value;
|
|
305
|
+
return namespaced;
|
|
392
306
|
};
|
|
393
|
-
const decodeCachedScanPayload = (value) => isCachedScanPayload(value) ? value : null;
|
|
394
|
-
const shouldStoreScanPayload = (payload) => !payload.didLintFail && !payload.didDeadCodeFail && payload.lintPartialFailures.length === 0 && !payload.supplyChainOverlapTimedOut && payload.securityScanFailed !== true;
|
|
395
307
|
//#endregion
|
|
396
|
-
//#region src/cli/utils/
|
|
397
|
-
const
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
308
|
+
//#region src/cli/utils/build-run-event.ts
|
|
309
|
+
const readEnvBoolean = (name) => {
|
|
310
|
+
const value = process.env[name];
|
|
311
|
+
if (value === void 0) return null;
|
|
312
|
+
return value.toLowerCase() === "true" || value === "1";
|
|
313
|
+
};
|
|
314
|
+
const ratioOf = (numerator, denominator) => denominator != null && denominator > 0 ? (numerator ?? 0) / denominator : null;
|
|
315
|
+
/**
|
|
316
|
+
* One queryable cache temperature per scan, derived from the whole stack:
|
|
317
|
+
*
|
|
318
|
+
* - `"turbo"` — whole-repo scan-result replay (the explicit
|
|
319
|
+
* `wholeRepoCacheHit` flag from the CLI's cachedPayload
|
|
320
|
+
* branch; no scan work ran).
|
|
321
|
+
* - `"warm"` — any incremental reuse: per-file lint hits, sidecar
|
|
322
|
+
* replays.
|
|
323
|
+
* - `"disabled"` — zero reuse because the global `REACT_DOCTOR_NO_CACHE`
|
|
324
|
+
* off-switch is on. Granular per-cache opt-outs
|
|
325
|
+
* (`REACT_DOCTOR_NO_FILE_CACHE`, …) still read warm/cold,
|
|
326
|
+
* since the other subsystems stay live.
|
|
327
|
+
* - `"cold"` — caches on, zero reuse (first scan / everything changed).
|
|
328
|
+
*
|
|
329
|
+
* `cache.warmth` is the headline reuse magnitude in [0, 1]: the plain mean of
|
|
330
|
+
* the subsystem reuse fractions known this run (lint hit ratio, sidecar
|
|
331
|
+
* replay ratio), skipping subsystems that never consulted a
|
|
332
|
+
* cache; `1` on turbo, dropped when nothing consulted any cache. Deliberately
|
|
333
|
+
* unweighted — the per-subsystem dims stay the precise signal; warmth is the
|
|
334
|
+
* p50/p90-able summary. Emitted only on the success path.
|
|
335
|
+
*/
|
|
336
|
+
const buildCacheAttributes = (input) => {
|
|
337
|
+
if (input.result === void 0) return {};
|
|
338
|
+
if (input.wholeRepoCacheHit) return withNamespace("cache", {
|
|
339
|
+
wholeRepoHit: true,
|
|
340
|
+
temperature: "turbo",
|
|
341
|
+
warmth: 1
|
|
421
342
|
});
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
for (const
|
|
427
|
-
|
|
428
|
-
|
|
343
|
+
const result = input.result;
|
|
344
|
+
const subsystemReuseRatios = [ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount), ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)];
|
|
345
|
+
let knownSubsystemCount = 0;
|
|
346
|
+
let reuseRatioSum = 0;
|
|
347
|
+
for (const reuseRatio of subsystemReuseRatios) {
|
|
348
|
+
if (reuseRatio === null) continue;
|
|
349
|
+
knownSubsystemCount += 1;
|
|
350
|
+
reuseRatioSum += reuseRatio;
|
|
429
351
|
}
|
|
430
|
-
|
|
352
|
+
const warmth = knownSubsystemCount > 0 ? reuseRatioSum / knownSubsystemCount : null;
|
|
353
|
+
const temperature = warmth !== null && warmth > 0 ? "warm" : isCacheGloballyDisabled() ? "disabled" : "cold";
|
|
354
|
+
return withNamespace("cache", {
|
|
355
|
+
wholeRepoHit: input.wholeRepoCacheHit,
|
|
356
|
+
temperature,
|
|
357
|
+
warmth
|
|
358
|
+
});
|
|
431
359
|
};
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
360
|
+
const resolveVersionPin = (versionInput) => {
|
|
361
|
+
if (versionInput === void 0 || versionInput.trim() === "") return null;
|
|
362
|
+
if (versionInput === "latest") return "latest";
|
|
363
|
+
if (/^(\.\.?\/|\/)/.test(versionInput)) return "local";
|
|
364
|
+
return "pinned";
|
|
438
365
|
};
|
|
439
|
-
const
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if (statusCode.includes("R") || statusCode.includes("C")) {
|
|
455
|
-
tokenIndex += 1;
|
|
456
|
-
const originPath = tokens[tokenIndex];
|
|
457
|
-
if (originPath === void 0 || originPath.length === 0) return null;
|
|
458
|
-
entries.push({
|
|
459
|
-
statusCode,
|
|
460
|
-
path: recordPath,
|
|
461
|
-
originPath
|
|
462
|
-
});
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
entries.push({
|
|
466
|
-
statusCode,
|
|
467
|
-
path: recordPath
|
|
366
|
+
const resolveTelemetryBlocking = (userConfig) => {
|
|
367
|
+
const fromAction = process.env[ACTION_INPUT_ENVIRONMENT_VARIABLES.blocking];
|
|
368
|
+
if (fromAction !== void 0 && isValidBlockingLevel(fromAction)) return fromAction;
|
|
369
|
+
return userConfig?.blocking ?? userConfig?.failOn ?? "error";
|
|
370
|
+
};
|
|
371
|
+
const buildOutcomeAttributes = (input) => {
|
|
372
|
+
if (input.result === void 0) {
|
|
373
|
+
const error = input.error;
|
|
374
|
+
const known = isReactDoctorError(error);
|
|
375
|
+
return withNamespace("outcome", {
|
|
376
|
+
status: "error",
|
|
377
|
+
exitCode: 1,
|
|
378
|
+
complete: false,
|
|
379
|
+
knownError: known,
|
|
380
|
+
errorTag: known ? error.reason._tag : error instanceof Error ? error.name : null
|
|
468
381
|
});
|
|
469
382
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
383
|
+
const result = input.result;
|
|
384
|
+
const summary = summarizeDiagnostics(result.diagnostics);
|
|
385
|
+
const blockingLevel = resolveTelemetryBlocking(input.userConfig);
|
|
386
|
+
const gateDiagnostics = filterDiagnosticsForSurface(result.diagnostics, "ciFailure", input.userConfig);
|
|
387
|
+
const gateDiagnosticSet = new Set(gateDiagnostics);
|
|
388
|
+
const complete = isInspectResultComplete(result);
|
|
389
|
+
const didLintHardFail = hasLintHardFailure(result);
|
|
390
|
+
const failsOnHardFailure = didLintHardFail && blockingLevel !== "none";
|
|
391
|
+
const wouldBlock = !input.scoreOnly && !input.gateExempt && shouldBlockCi(gateDiagnostics, blockingLevel);
|
|
392
|
+
const isClean = result.diagnostics.length === 0 && complete;
|
|
393
|
+
const outcome = didLintHardFail ? "error" : wouldBlock ? "blocked" : isClean ? "clean" : "ok";
|
|
394
|
+
const firings = summarizeRuleFirings(result.diagnostics);
|
|
395
|
+
const countByRule = /* @__PURE__ */ new Map();
|
|
396
|
+
const countByCategory = /* @__PURE__ */ new Map();
|
|
397
|
+
for (const firing of firings) {
|
|
398
|
+
countByRule.set(firing.rule, (countByRule.get(firing.rule) ?? 0) + firing.count);
|
|
399
|
+
countByCategory.set(firing.category, (countByCategory.get(firing.category) ?? 0) + firing.count);
|
|
478
400
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
401
|
+
let topRule = null;
|
|
402
|
+
let topRuleCount = 0;
|
|
403
|
+
for (const [rule, count] of countByRule) if (count > topRuleCount) {
|
|
404
|
+
topRule = rule;
|
|
405
|
+
topRuleCount = count;
|
|
483
406
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
"
|
|
492
|
-
"
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if (contentFingerprint === null) return null;
|
|
504
|
-
dirtyEntries.push({
|
|
505
|
-
...statusEntry,
|
|
506
|
-
contentFingerprint
|
|
507
|
-
});
|
|
407
|
+
const largestRuleBucket = buildRuleBlastRadii(result.diagnostics)[0] ?? null;
|
|
408
|
+
let diagnosticsInTestFiles = 0;
|
|
409
|
+
let diagnosticsInStoryFiles = 0;
|
|
410
|
+
let nonProductionGateExcluded = 0;
|
|
411
|
+
const diagnosticSiteCounts = /* @__PURE__ */ new Map();
|
|
412
|
+
const findingsPerFixGroup = /* @__PURE__ */ new Map();
|
|
413
|
+
for (const diagnostic of result.diagnostics) {
|
|
414
|
+
if (diagnostic.fileContext === "test") diagnosticsInTestFiles += 1;
|
|
415
|
+
if (diagnostic.fileContext === "story") diagnosticsInStoryFiles += 1;
|
|
416
|
+
if (diagnostic.fileContext !== void 0 && !gateDiagnosticSet.has(diagnostic)) nonProductionGateExcluded += 1;
|
|
417
|
+
const diagnosticSite = JSON.stringify([
|
|
418
|
+
diagnostic.filePath,
|
|
419
|
+
diagnostic.line,
|
|
420
|
+
diagnostic.column,
|
|
421
|
+
diagnostic.plugin,
|
|
422
|
+
diagnostic.rule
|
|
423
|
+
]);
|
|
424
|
+
diagnosticSiteCounts.set(diagnosticSite, (diagnosticSiteCounts.get(diagnosticSite) ?? 0) + 1);
|
|
425
|
+
if (diagnostic.fixGroupId) findingsPerFixGroup.set(diagnostic.fixGroupId, (findingsPerFixGroup.get(diagnostic.fixGroupId) ?? 0) + 1);
|
|
508
426
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const DOTENV_FILE_NAME_PATTERN = /^\.env(\.|$)/;
|
|
530
|
-
const resolveDotenvFingerprint = (projectDirectory) => {
|
|
531
|
-
try {
|
|
532
|
-
return fs.readdirSync(projectDirectory).filter((entryName) => DOTENV_FILE_NAME_PATTERN.test(entryName)).sort().map((entryName) => `${entryName}=${hashFileContents(path.join(projectDirectory, entryName)) ?? "unreadable"}`);
|
|
533
|
-
} catch {
|
|
534
|
-
return [];
|
|
427
|
+
let fixGroupedFindings = 0;
|
|
428
|
+
for (const count of findingsPerFixGroup.values()) fixGroupedFindings += count;
|
|
429
|
+
let sameSiteOccurrences = 0;
|
|
430
|
+
for (const count of diagnosticSiteCounts.values()) sameSiteOccurrences += Math.max(0, count - 1);
|
|
431
|
+
const categoryRollup = {};
|
|
432
|
+
for (const [category, count] of countByCategory) categoryRollup[`category.${toCategoryKey(category)}`] = count;
|
|
433
|
+
const suppressionRollup = {};
|
|
434
|
+
if (input.suppressedRuleCounts) {
|
|
435
|
+
const countBySource = {
|
|
436
|
+
config: 0,
|
|
437
|
+
override: 0,
|
|
438
|
+
inline: 0,
|
|
439
|
+
"foreign-inline": 0
|
|
440
|
+
};
|
|
441
|
+
for (const suppression of input.suppressedRuleCounts) countBySource[suppression.source] += suppression.count;
|
|
442
|
+
suppressionRollup.suppressed = countBySource.config + countBySource.override + countBySource.inline + countBySource["foreign-inline"];
|
|
443
|
+
suppressionRollup.suppressedConfig = countBySource.config;
|
|
444
|
+
suppressionRollup.suppressedOverride = countBySource.override;
|
|
445
|
+
suppressionRollup.suppressedInline = countBySource.inline;
|
|
446
|
+
suppressionRollup.suppressedForeignInline = countBySource["foreign-inline"];
|
|
535
447
|
}
|
|
448
|
+
const attributes = {
|
|
449
|
+
...withNamespace("outcome", {
|
|
450
|
+
status: outcome,
|
|
451
|
+
exitCode: failsOnHardFailure || wouldBlock ? 1 : 0,
|
|
452
|
+
wouldBlock,
|
|
453
|
+
blocking: blockingLevel,
|
|
454
|
+
clean: isClean,
|
|
455
|
+
complete,
|
|
456
|
+
skippedChecks: result.skippedChecks.length
|
|
457
|
+
}),
|
|
458
|
+
...withNamespace("diag", {
|
|
459
|
+
total: summary.totalDiagnosticCount,
|
|
460
|
+
errors: summary.errorCount,
|
|
461
|
+
warnings: summary.warningCount,
|
|
462
|
+
affectedFiles: summary.affectedFileCount,
|
|
463
|
+
inTestFiles: diagnosticsInTestFiles,
|
|
464
|
+
inStoryFiles: diagnosticsInStoryFiles,
|
|
465
|
+
nonProductionGateExcluded,
|
|
466
|
+
distinctRules: countByRule.size,
|
|
467
|
+
topRule,
|
|
468
|
+
sameSiteOccurrences,
|
|
469
|
+
fixGroups: findingsPerFixGroup.size,
|
|
470
|
+
fixGroupedFindings,
|
|
471
|
+
...categoryRollup,
|
|
472
|
+
...suppressionRollup
|
|
473
|
+
}),
|
|
474
|
+
...withNamespace("score", {
|
|
475
|
+
value: result.score ? result.score.score : null,
|
|
476
|
+
label: result.score ? result.score.label : null,
|
|
477
|
+
available: result.score !== null,
|
|
478
|
+
unavailableReason: result.score === null ? resolveScoreUnavailableReason({
|
|
479
|
+
isScoreDisabled: input.noScore,
|
|
480
|
+
isAnalysisIncomplete: input.didLintFail === true || input.didDeadCodeFail === true
|
|
481
|
+
}) : null
|
|
482
|
+
}),
|
|
483
|
+
...withNamespace("lint", {
|
|
484
|
+
failed: input.didLintFail,
|
|
485
|
+
failureReasonKind: input.lintFailureReasonKind,
|
|
486
|
+
partialFailureCount: input.lintPartialFailureCount,
|
|
487
|
+
droppedFileCount: input.lintDroppedFileCount,
|
|
488
|
+
deadlineSkippedFileCount: input.lintDeadlineSkippedFileCount,
|
|
489
|
+
cacheHitFiles: result.lintCacheHitFileCount,
|
|
490
|
+
cacheTotalFiles: result.lintCacheTotalFileCount,
|
|
491
|
+
cacheHitRatio: ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
|
|
492
|
+
sidecarReplayedFiles: result.lintSidecarReplayedFileCount,
|
|
493
|
+
sidecarTotalFiles: result.lintSidecarTotalFileCount,
|
|
494
|
+
sidecarReplayRatio: ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)
|
|
495
|
+
}),
|
|
496
|
+
...withNamespace("maintainability", { failed: input.didDeadCodeFail }),
|
|
497
|
+
...withNamespace("supplyChain", { overlapTimedOut: input.supplyChainOverlapTimedOut }),
|
|
498
|
+
...withNamespace("securityScan", { failed: input.securityScanFailed }),
|
|
499
|
+
...withNamespace("timing", {
|
|
500
|
+
elapsedMs: result.elapsedMilliseconds,
|
|
501
|
+
scanMs: result.scanElapsedMilliseconds
|
|
502
|
+
}),
|
|
503
|
+
...withNamespace("migration", {
|
|
504
|
+
largestRuleBucketFiles: largestRuleBucket ? largestRuleBucket.fileCount : null,
|
|
505
|
+
largestRuleBucketSites: largestRuleBucket ? largestRuleBucket.siteCount : null,
|
|
506
|
+
largestRuleBucketRule: largestRuleBucket ? largestRuleBucket.ruleKey : null
|
|
507
|
+
})
|
|
508
|
+
};
|
|
509
|
+
if (result.baselineDelta) Object.assign(attributes, withNamespace("baseline", {
|
|
510
|
+
new: summary.totalDiagnosticCount,
|
|
511
|
+
fixed: result.baselineDelta.fixedCount,
|
|
512
|
+
baseTotal: result.baselineDelta.baseTotalCount,
|
|
513
|
+
crossFileMatches: result.baselineDelta.crossFileMatchCount,
|
|
514
|
+
degraded: false
|
|
515
|
+
}));
|
|
516
|
+
else if (input.gateExempt) attributes["baseline.degraded"] = true;
|
|
517
|
+
return attributes;
|
|
536
518
|
};
|
|
537
|
-
const
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
519
|
+
const buildActionAttributes = () => {
|
|
520
|
+
const { githubActorAssociation } = resolveGithubActionsScoreMetadata();
|
|
521
|
+
return withNamespace("action", {
|
|
522
|
+
actorAssociation: githubActorAssociation,
|
|
523
|
+
runnerOs: detectRunnerOs(),
|
|
524
|
+
comment: readEnvBoolean(ACTION_INPUT_ENVIRONMENT_VARIABLES.comment),
|
|
525
|
+
reviewComments: readEnvBoolean(ACTION_INPUT_ENVIRONMENT_VARIABLES.reviewComments),
|
|
526
|
+
versionPin: resolveVersionPin(process.env[ACTION_INPUT_ENVIRONMENT_VARIABLES.version])
|
|
527
|
+
});
|
|
542
528
|
};
|
|
543
|
-
const
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
} catch {
|
|
571
|
-
return {
|
|
572
|
-
version: 7,
|
|
573
|
-
entries: []
|
|
574
|
-
};
|
|
575
|
-
}
|
|
529
|
+
const buildScanAttributes = (input) => {
|
|
530
|
+
const ruleOverrides = input.userConfig?.rules ?? {};
|
|
531
|
+
const ruleKeys = Object.keys(ruleOverrides);
|
|
532
|
+
return withNamespace("scan", {
|
|
533
|
+
mode: input.mode,
|
|
534
|
+
scope: input.scope,
|
|
535
|
+
parallel: input.parallel,
|
|
536
|
+
workerCount: input.workerCount,
|
|
537
|
+
maxDurationMs: input.maxDurationMs,
|
|
538
|
+
lint: input.lint,
|
|
539
|
+
maintainability: input.deadCode,
|
|
540
|
+
supplyChain: input.supplyChain,
|
|
541
|
+
scoreOnly: input.scoreOnly,
|
|
542
|
+
noScore: input.noScore,
|
|
543
|
+
respectInlineDisables: input.respectInlineDisables,
|
|
544
|
+
showWarnings: input.showWarnings,
|
|
545
|
+
usedOutputDir: input.usedOutputDir,
|
|
546
|
+
ignoredTagCount: input.ignoredTagCount,
|
|
547
|
+
hasCustomConfig: input.hasCustomConfig,
|
|
548
|
+
rulesConfigured: ruleKeys.length,
|
|
549
|
+
rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
|
|
550
|
+
projectAnalysisRuleCount: countOptInProjectRuleSelections(buildRuleSeverityControls(input.userConfig)),
|
|
551
|
+
fileCount: input.result?.scannedFileCount,
|
|
552
|
+
nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length,
|
|
553
|
+
htmlFileCount: input.result?.analyzedFiles?.filter((filePath) => HTML_FILE_PATTERN.test(filePath)).length,
|
|
554
|
+
multilineDiagnosticCount: input.result?.diagnostics.filter((diagnostic) => (diagnostic.endLine ?? diagnostic.line) > diagnostic.line).length
|
|
555
|
+
});
|
|
576
556
|
};
|
|
577
557
|
/**
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
558
|
+
* Projects a scan into the namespaced attribute set for its root span — the
|
|
559
|
+
* canonical per-scan "wide event". Every attribute carries a dotted namespace
|
|
560
|
+
* that groups it by concept (`scan.*` config, `action.*` CI knobs, `outcome.*`
|
|
561
|
+
* verdict, `diag.*` findings, `score.*`, `lint.*`, `maintainability.*`, `cache.*`,
|
|
562
|
+
* `supplyChain.*`, `timing.*`, `migration.*`, `baseline.*`) so the attributes
|
|
563
|
+
* tree up in Sentry's attribute browser and stay filter-/group-/aggregate-able
|
|
564
|
+
* in the Spans dataset. Pure and exported so the projection (outcome
|
|
565
|
+
* precedence, rule/category rollups, CI knobs, config shape) is unit-testable
|
|
566
|
+
* without a live Sentry client. `null`/`undefined` values are dropped so absent
|
|
567
|
+
* signals never become misleading `"null"`/`"undefined"` attributes. The run +
|
|
568
|
+
* project base context (version, command, ci/provider, framework, …) is already
|
|
569
|
+
* on the span from `withSentryRunSpan` / `recordSentryProjectContext`, so this
|
|
570
|
+
* adds only what those don't carry.
|
|
585
571
|
*/
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
572
|
+
const buildRunEventAttributes = (input) => toSpanAttributes({
|
|
573
|
+
...buildScanAttributes(input),
|
|
574
|
+
...buildActionAttributes(),
|
|
575
|
+
...buildOutcomeAttributes(input),
|
|
576
|
+
...buildCacheAttributes(input)
|
|
577
|
+
});
|
|
578
|
+
/**
|
|
579
|
+
* Stamps the wide-event attributes onto the run's root span. A guarded no-op
|
|
580
|
+
* when tracing is off (no `rootSpan`) and swallow-on-throw, so telemetry can
|
|
581
|
+
* never break the run.
|
|
582
|
+
*/
|
|
583
|
+
const recordRunEvent = (rootSpan, input) => {
|
|
584
|
+
if (!rootSpan) return;
|
|
596
585
|
try {
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
} catch {
|
|
600
|
-
return null;
|
|
601
|
-
}
|
|
602
|
-
};
|
|
603
|
-
const resolveScanResultToolchainFingerprint = (nodeBinaryPath) => {
|
|
604
|
-
const fingerprints = [];
|
|
605
|
-
if (nodeBinaryPath !== null) fingerprints.push(nodeBinaryPath === process.execPath ? `node=${process.version}` : `node=${fileFingerprint(nodeBinaryPath) ?? "unreadable"}`);
|
|
606
|
-
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
607
|
-
const packageJson = bundledRequire(specifier);
|
|
608
|
-
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
609
|
-
fingerprints.push(`${specifier}=${version}`);
|
|
610
|
-
} catch {
|
|
611
|
-
fingerprints.push(`${specifier}=missing`);
|
|
612
|
-
}
|
|
613
|
-
fingerprints.push(RULE_PLUGIN_CONTENT_FINGERPRINT);
|
|
614
|
-
return fingerprints;
|
|
615
|
-
};
|
|
616
|
-
const buildScanResultCacheKey = (input) => {
|
|
617
|
-
if (isCacheGloballyDisabled()) return null;
|
|
618
|
-
if (!isGitIdentityTrustworthy(input.projectDirectory)) return null;
|
|
619
|
-
const repositoryIdentity = resolveRepositoryCacheIdentity(input.projectDirectory, input.invocationState);
|
|
620
|
-
if (repositoryIdentity === null) return null;
|
|
621
|
-
if (stringifyStableJson(input.userConfig) === null) return null;
|
|
622
|
-
const cacheKeyJson = stringifyStableJson({
|
|
623
|
-
schemaVersion: 7,
|
|
624
|
-
projectIdentity: resolveProjectIdentity(input.projectDirectory),
|
|
625
|
-
headSha: repositoryIdentity.headSha,
|
|
626
|
-
worktreeFingerprint: repositoryIdentity.worktreeFingerprint,
|
|
627
|
-
dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
|
|
628
|
-
reactDoctorVersion: input.version,
|
|
629
|
-
nodeVersion: process.version,
|
|
630
|
-
toolchainFingerprint: resolveScanResultToolchainFingerprint(input.nodeBinaryPath),
|
|
631
|
-
configFingerprint: computeConfigFingerprint(input.projectDirectory, input.version),
|
|
632
|
-
hasConfigOverride: input.hasConfigOverride,
|
|
633
|
-
configSourceDirectory: input.configSourceDirectory,
|
|
634
|
-
userConfig: input.userConfig,
|
|
635
|
-
engineOptions: {
|
|
636
|
-
lint: input.options.lint,
|
|
637
|
-
deadCode: input.options.deadCode,
|
|
638
|
-
supplyChain: input.options.supplyChain,
|
|
639
|
-
includePaths: [...input.options.includePaths].sort(),
|
|
640
|
-
excludedProjectDirectories: [...input.options.excludedProjectDirectories ?? []].sort(),
|
|
641
|
-
retainExcludedProjectDeadCodeDiagnostics: input.options.retainExcludedProjectDeadCodeDiagnostics ?? false,
|
|
642
|
-
customRulesOnly: input.options.customRulesOnly,
|
|
643
|
-
respectInlineDisables: input.options.respectInlineDisables,
|
|
644
|
-
warnings: input.options.warnings,
|
|
645
|
-
adoptExistingLintConfig: input.options.adoptExistingLintConfig,
|
|
646
|
-
ignoredTags: [...input.options.ignoredTags].sort(),
|
|
647
|
-
includedTags: [...input.options.includedTags].sort(),
|
|
648
|
-
includeTagDefaults: input.options.includeTagDefaults,
|
|
649
|
-
concurrency: input.options.concurrency,
|
|
650
|
-
lintBatchOrdering: resolveLintBatchOrdering(),
|
|
651
|
-
baselineRef: input.options.baseline?.ref,
|
|
652
|
-
changedLineRanges: input.options.changedLineRanges ?? void 0,
|
|
653
|
-
noScore: input.options.noScore,
|
|
654
|
-
isCi: input.options.isCi,
|
|
655
|
-
suppressRendering: input.options.suppressRendering,
|
|
656
|
-
supplyChainManifestChanged: input.options.supplyChainManifestChanged
|
|
657
|
-
}
|
|
658
|
-
});
|
|
659
|
-
return cacheKeyJson === null ? null : hashString(cacheKeyJson);
|
|
660
|
-
};
|
|
661
|
-
const createScanResultCache = (projectDirectory) => {
|
|
662
|
-
const cacheFilePath = resolveCacheFilePath(projectDirectory);
|
|
663
|
-
const persistedCache = readPersistedCache(cacheFilePath);
|
|
664
|
-
const entries = /* @__PURE__ */ new Map();
|
|
665
|
-
for (const entry of persistedCache.entries) entries.set(entry.key, entry);
|
|
666
|
-
const persist = () => {
|
|
667
|
-
const mergedEntries = /* @__PURE__ */ new Map();
|
|
668
|
-
for (const entry of readPersistedCache(cacheFilePath).entries) mergedEntries.set(entry.key, entry);
|
|
669
|
-
for (const entry of entries.values()) mergedEntries.set(entry.key, entry);
|
|
670
|
-
atomicWriteJson(cacheFilePath, {
|
|
671
|
-
version: 7,
|
|
672
|
-
entries: [...mergedEntries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
|
|
673
|
-
});
|
|
674
|
-
};
|
|
675
|
-
return {
|
|
676
|
-
lookup: (key) => {
|
|
677
|
-
const payload = entries.get(key)?.payload;
|
|
678
|
-
if (payload === void 0) return null;
|
|
679
|
-
return resolveProjectIdentity(payload.directory) === resolveProjectIdentity(projectDirectory) && (payload.manifestContentHash ?? null) === resolveManifestContentHash(projectDirectory) ? payload : null;
|
|
680
|
-
},
|
|
681
|
-
store: (key, payload) => {
|
|
682
|
-
entries.set(key, {
|
|
683
|
-
key,
|
|
684
|
-
createdAtMs: Date.now(),
|
|
685
|
-
payload: {
|
|
686
|
-
...payload,
|
|
687
|
-
manifestContentHash: resolveManifestContentHash(projectDirectory)
|
|
688
|
-
}
|
|
689
|
-
});
|
|
690
|
-
persist();
|
|
691
|
-
}
|
|
692
|
-
};
|
|
586
|
+
for (const [key, value] of Object.entries(buildRunEventAttributes(input))) rootSpan.attribute(key, value);
|
|
587
|
+
} catch {}
|
|
693
588
|
};
|
|
694
589
|
//#endregion
|
|
695
|
-
//#region src/cli/utils/
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
return diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
590
|
+
//#region src/cli/utils/diagnostic-intersects-line-ranges.ts
|
|
591
|
+
const diagnosticIntersectsLineRanges = (diagnostic, lineRanges) => {
|
|
592
|
+
const diagnosticEndLine = Math.max(diagnostic.line, diagnostic.endLine ?? diagnostic.line);
|
|
593
|
+
return lineRanges.some(([rangeStartLine, rangeEndLine]) => diagnostic.line <= rangeEndLine && diagnosticEndLine >= rangeStartLine);
|
|
700
594
|
};
|
|
701
595
|
//#endregion
|
|
702
|
-
//#region src/cli/utils/
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
/**
|
|
712
|
-
* Prefixes every key in a flat attribute group with `namespace.`, so a logical
|
|
713
|
-
* group (scan config, diagnostics rollup, score, …) carries one dotted
|
|
714
|
-
* namespace applied in a single place rather than hand-spelled into each key —
|
|
715
|
-
* which is how the wide event drifted into a flat, half-namespaced soup. The
|
|
716
|
-
* dotted prefix is what makes the attributes tree up in Sentry's attribute
|
|
717
|
-
* browser and stay group-/filter-/aggregate-able in the Spans dataset.
|
|
718
|
-
*
|
|
719
|
-
* Value types are preserved verbatim (numbers stay numbers so Sentry infers a
|
|
720
|
-
* numeric attribute and `p75(...)`/`avg(...)` work; `null`/`undefined` are kept
|
|
721
|
-
* so `toSpanAttributes` can drop absent signals rather than coerce them).
|
|
722
|
-
*/
|
|
723
|
-
const withNamespace = (namespace, attributes) => {
|
|
724
|
-
const namespaced = {};
|
|
725
|
-
for (const [key, value] of Object.entries(attributes)) namespaced[`${namespace}.${key}`] = value;
|
|
726
|
-
return namespaced;
|
|
596
|
+
//#region src/cli/utils/filter-diagnostics-by-changed-lines.ts
|
|
597
|
+
const filterDiagnosticsByChangedLines = (input) => {
|
|
598
|
+
const rangesByFile = /* @__PURE__ */ new Map();
|
|
599
|
+
for (const entry of input.changedLineRanges) rangesByFile.set(toForwardSlashes(entry.file), entry.ranges);
|
|
600
|
+
return input.diagnostics.filter((diagnostic) => {
|
|
601
|
+
const relativePath = toForwardSlashes(path.isAbsolute(diagnostic.filePath) ? path.relative(input.directory, diagnostic.filePath) : diagnostic.filePath);
|
|
602
|
+
const ranges = rangesByFile.get(relativePath);
|
|
603
|
+
return ranges !== void 0 && diagnosticIntersectsLineRanges(diagnostic, ranges);
|
|
604
|
+
});
|
|
727
605
|
};
|
|
728
606
|
//#endregion
|
|
729
|
-
//#region src/cli/utils/
|
|
730
|
-
const readEnvBoolean = (name) => {
|
|
731
|
-
const value = process.env[name];
|
|
732
|
-
if (value === void 0) return null;
|
|
733
|
-
return value.toLowerCase() === "true" || value === "1";
|
|
734
|
-
};
|
|
735
|
-
const ratioOf = (numerator, denominator) => denominator != null && denominator > 0 ? (numerator ?? 0) / denominator : null;
|
|
607
|
+
//#region src/cli/utils/resolve-invocation-oxlint-concurrency.ts
|
|
736
608
|
/**
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
* branch; no scan work ran).
|
|
742
|
-
* - `"warm"` — any incremental reuse: per-file lint hits, sidecar
|
|
743
|
-
* replays.
|
|
744
|
-
* - `"disabled"` — zero reuse because the global `REACT_DOCTOR_NO_CACHE`
|
|
745
|
-
* off-switch is on. Granular per-cache opt-outs
|
|
746
|
-
* (`REACT_DOCTOR_NO_FILE_CACHE`, …) still read warm/cold,
|
|
747
|
-
* since the other subsystems stay live.
|
|
748
|
-
* - `"cold"` — caches on, zero reuse (first scan / everything changed).
|
|
749
|
-
*
|
|
750
|
-
* `cache.warmth` is the headline reuse magnitude in [0, 1]: the plain mean of
|
|
751
|
-
* the subsystem reuse fractions known this run (lint hit ratio, sidecar
|
|
752
|
-
* replay ratio), skipping subsystems that never consulted a
|
|
753
|
-
* cache; `1` on turbo, dropped when nothing consulted any cache. Deliberately
|
|
754
|
-
* unweighted — the per-subsystem dims stay the precise signal; warmth is the
|
|
755
|
-
* p50/p90-able summary. Emitted only on the success path.
|
|
609
|
+
* The invocation-wide oxlint worker count: an explicit `--concurrency` pin
|
|
610
|
+
* when given, else the env-seeded / auto-budgeted `OxlintConcurrency`, clamped
|
|
611
|
+
* to the spawn ceiling. Every project of a workspace scan shares this one
|
|
612
|
+
* pool, so it also sizes how many projects may be in flight at once.
|
|
756
613
|
*/
|
|
757
|
-
const
|
|
758
|
-
if (input.result === void 0) return {};
|
|
759
|
-
if (input.wholeRepoCacheHit) return withNamespace("cache", {
|
|
760
|
-
wholeRepoHit: true,
|
|
761
|
-
temperature: "turbo",
|
|
762
|
-
warmth: 1
|
|
763
|
-
});
|
|
764
|
-
const result = input.result;
|
|
765
|
-
const subsystemReuseRatios = [ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount), ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)];
|
|
766
|
-
let knownSubsystemCount = 0;
|
|
767
|
-
let reuseRatioSum = 0;
|
|
768
|
-
for (const reuseRatio of subsystemReuseRatios) {
|
|
769
|
-
if (reuseRatio === null) continue;
|
|
770
|
-
knownSubsystemCount += 1;
|
|
771
|
-
reuseRatioSum += reuseRatio;
|
|
772
|
-
}
|
|
773
|
-
const warmth = knownSubsystemCount > 0 ? reuseRatioSum / knownSubsystemCount : null;
|
|
774
|
-
const temperature = warmth !== null && warmth > 0 ? "warm" : isCacheGloballyDisabled() ? "disabled" : "cold";
|
|
775
|
-
return withNamespace("cache", {
|
|
776
|
-
wholeRepoHit: input.wholeRepoCacheHit,
|
|
777
|
-
temperature,
|
|
778
|
-
warmth
|
|
779
|
-
});
|
|
780
|
-
};
|
|
781
|
-
const resolveVersionPin = (versionInput) => {
|
|
782
|
-
if (versionInput === void 0 || versionInput.trim() === "") return null;
|
|
783
|
-
if (versionInput === "latest") return "latest";
|
|
784
|
-
if (/^(\.\.?\/|\/)/.test(versionInput)) return "local";
|
|
785
|
-
return "pinned";
|
|
786
|
-
};
|
|
787
|
-
const resolveTelemetryBlocking = (userConfig) => {
|
|
788
|
-
const fromAction = process.env[ACTION_INPUT_ENVIRONMENT_VARIABLES.blocking];
|
|
789
|
-
if (fromAction !== void 0 && isValidBlockingLevel(fromAction)) return fromAction;
|
|
790
|
-
return userConfig?.blocking ?? userConfig?.failOn ?? "error";
|
|
791
|
-
};
|
|
792
|
-
const buildOutcomeAttributes = (input) => {
|
|
793
|
-
if (input.result === void 0) {
|
|
794
|
-
const error = input.error;
|
|
795
|
-
const known = isReactDoctorError(error);
|
|
796
|
-
return withNamespace("outcome", {
|
|
797
|
-
status: "error",
|
|
798
|
-
exitCode: 1,
|
|
799
|
-
complete: false,
|
|
800
|
-
knownError: known,
|
|
801
|
-
errorTag: known ? error.reason._tag : error instanceof Error ? error.name : null
|
|
802
|
-
});
|
|
803
|
-
}
|
|
804
|
-
const result = input.result;
|
|
805
|
-
const summary = summarizeDiagnostics(result.diagnostics);
|
|
806
|
-
const blockingLevel = resolveTelemetryBlocking(input.userConfig);
|
|
807
|
-
const gateDiagnostics = filterDiagnosticsForSurface(result.diagnostics, "ciFailure", input.userConfig);
|
|
808
|
-
const gateDiagnosticSet = new Set(gateDiagnostics);
|
|
809
|
-
const complete = isInspectResultComplete(result);
|
|
810
|
-
const didLintHardFail = hasLintHardFailure(result);
|
|
811
|
-
const failsOnHardFailure = didLintHardFail && blockingLevel !== "none";
|
|
812
|
-
const wouldBlock = !input.scoreOnly && !input.gateExempt && shouldBlockCi(gateDiagnostics, blockingLevel);
|
|
813
|
-
const isClean = result.diagnostics.length === 0 && complete;
|
|
814
|
-
const outcome = didLintHardFail ? "error" : wouldBlock ? "blocked" : isClean ? "clean" : "ok";
|
|
815
|
-
const firings = summarizeRuleFirings(result.diagnostics);
|
|
816
|
-
const countByRule = /* @__PURE__ */ new Map();
|
|
817
|
-
const countByCategory = /* @__PURE__ */ new Map();
|
|
818
|
-
for (const firing of firings) {
|
|
819
|
-
countByRule.set(firing.rule, (countByRule.get(firing.rule) ?? 0) + firing.count);
|
|
820
|
-
countByCategory.set(firing.category, (countByCategory.get(firing.category) ?? 0) + firing.count);
|
|
821
|
-
}
|
|
822
|
-
let topRule = null;
|
|
823
|
-
let topRuleCount = 0;
|
|
824
|
-
for (const [rule, count] of countByRule) if (count > topRuleCount) {
|
|
825
|
-
topRule = rule;
|
|
826
|
-
topRuleCount = count;
|
|
827
|
-
}
|
|
828
|
-
const largestRuleBucket = buildRuleBlastRadii(result.diagnostics)[0] ?? null;
|
|
829
|
-
let diagnosticsInTestFiles = 0;
|
|
830
|
-
let diagnosticsInStoryFiles = 0;
|
|
831
|
-
let nonProductionGateExcluded = 0;
|
|
832
|
-
const diagnosticSiteCounts = /* @__PURE__ */ new Map();
|
|
833
|
-
const findingsPerFixGroup = /* @__PURE__ */ new Map();
|
|
834
|
-
for (const diagnostic of result.diagnostics) {
|
|
835
|
-
if (diagnostic.fileContext === "test") diagnosticsInTestFiles += 1;
|
|
836
|
-
if (diagnostic.fileContext === "story") diagnosticsInStoryFiles += 1;
|
|
837
|
-
if (diagnostic.fileContext !== void 0 && !gateDiagnosticSet.has(diagnostic)) nonProductionGateExcluded += 1;
|
|
838
|
-
const diagnosticSite = JSON.stringify([
|
|
839
|
-
diagnostic.filePath,
|
|
840
|
-
diagnostic.line,
|
|
841
|
-
diagnostic.column,
|
|
842
|
-
diagnostic.plugin,
|
|
843
|
-
diagnostic.rule
|
|
844
|
-
]);
|
|
845
|
-
diagnosticSiteCounts.set(diagnosticSite, (diagnosticSiteCounts.get(diagnosticSite) ?? 0) + 1);
|
|
846
|
-
if (diagnostic.fixGroupId) findingsPerFixGroup.set(diagnostic.fixGroupId, (findingsPerFixGroup.get(diagnostic.fixGroupId) ?? 0) + 1);
|
|
847
|
-
}
|
|
848
|
-
let fixGroupedFindings = 0;
|
|
849
|
-
for (const count of findingsPerFixGroup.values()) fixGroupedFindings += count;
|
|
850
|
-
let sameSiteOccurrences = 0;
|
|
851
|
-
for (const count of diagnosticSiteCounts.values()) sameSiteOccurrences += Math.max(0, count - 1);
|
|
852
|
-
const categoryRollup = {};
|
|
853
|
-
for (const [category, count] of countByCategory) categoryRollup[`category.${toCategoryKey(category)}`] = count;
|
|
854
|
-
const suppressionRollup = {};
|
|
855
|
-
if (input.suppressedRuleCounts) {
|
|
856
|
-
const countBySource = {
|
|
857
|
-
config: 0,
|
|
858
|
-
override: 0,
|
|
859
|
-
inline: 0,
|
|
860
|
-
"foreign-inline": 0
|
|
861
|
-
};
|
|
862
|
-
for (const suppression of input.suppressedRuleCounts) countBySource[suppression.source] += suppression.count;
|
|
863
|
-
suppressionRollup.suppressed = countBySource.config + countBySource.override + countBySource.inline + countBySource["foreign-inline"];
|
|
864
|
-
suppressionRollup.suppressedConfig = countBySource.config;
|
|
865
|
-
suppressionRollup.suppressedOverride = countBySource.override;
|
|
866
|
-
suppressionRollup.suppressedInline = countBySource.inline;
|
|
867
|
-
suppressionRollup.suppressedForeignInline = countBySource["foreign-inline"];
|
|
868
|
-
}
|
|
869
|
-
const attributes = {
|
|
870
|
-
...withNamespace("outcome", {
|
|
871
|
-
status: outcome,
|
|
872
|
-
exitCode: failsOnHardFailure || wouldBlock ? 1 : 0,
|
|
873
|
-
wouldBlock,
|
|
874
|
-
blocking: blockingLevel,
|
|
875
|
-
clean: isClean,
|
|
876
|
-
complete,
|
|
877
|
-
skippedChecks: result.skippedChecks.length
|
|
878
|
-
}),
|
|
879
|
-
...withNamespace("diag", {
|
|
880
|
-
total: summary.totalDiagnosticCount,
|
|
881
|
-
errors: summary.errorCount,
|
|
882
|
-
warnings: summary.warningCount,
|
|
883
|
-
affectedFiles: summary.affectedFileCount,
|
|
884
|
-
inTestFiles: diagnosticsInTestFiles,
|
|
885
|
-
inStoryFiles: diagnosticsInStoryFiles,
|
|
886
|
-
nonProductionGateExcluded,
|
|
887
|
-
distinctRules: countByRule.size,
|
|
888
|
-
topRule,
|
|
889
|
-
sameSiteOccurrences,
|
|
890
|
-
fixGroups: findingsPerFixGroup.size,
|
|
891
|
-
fixGroupedFindings,
|
|
892
|
-
...categoryRollup,
|
|
893
|
-
...suppressionRollup
|
|
894
|
-
}),
|
|
895
|
-
...withNamespace("score", {
|
|
896
|
-
value: result.score ? result.score.score : null,
|
|
897
|
-
label: result.score ? result.score.label : null,
|
|
898
|
-
available: result.score !== null,
|
|
899
|
-
unavailableReason: result.score === null ? resolveScoreUnavailableReason({
|
|
900
|
-
isScoreDisabled: input.noScore,
|
|
901
|
-
isAnalysisIncomplete: input.didLintFail === true || input.didDeadCodeFail === true
|
|
902
|
-
}) : null
|
|
903
|
-
}),
|
|
904
|
-
...withNamespace("lint", {
|
|
905
|
-
failed: input.didLintFail,
|
|
906
|
-
failureReasonKind: input.lintFailureReasonKind,
|
|
907
|
-
partialFailureCount: input.lintPartialFailureCount,
|
|
908
|
-
droppedFileCount: input.lintDroppedFileCount,
|
|
909
|
-
deadlineSkippedFileCount: input.lintDeadlineSkippedFileCount,
|
|
910
|
-
cacheHitFiles: result.lintCacheHitFileCount,
|
|
911
|
-
cacheTotalFiles: result.lintCacheTotalFileCount,
|
|
912
|
-
cacheHitRatio: ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
|
|
913
|
-
sidecarReplayedFiles: result.lintSidecarReplayedFileCount,
|
|
914
|
-
sidecarTotalFiles: result.lintSidecarTotalFileCount,
|
|
915
|
-
sidecarReplayRatio: ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)
|
|
916
|
-
}),
|
|
917
|
-
...withNamespace("maintainability", { failed: input.didDeadCodeFail }),
|
|
918
|
-
...withNamespace("supplyChain", { overlapTimedOut: input.supplyChainOverlapTimedOut }),
|
|
919
|
-
...withNamespace("securityScan", { failed: input.securityScanFailed }),
|
|
920
|
-
...withNamespace("timing", {
|
|
921
|
-
elapsedMs: result.elapsedMilliseconds,
|
|
922
|
-
scanMs: result.scanElapsedMilliseconds
|
|
923
|
-
}),
|
|
924
|
-
...withNamespace("migration", {
|
|
925
|
-
largestRuleBucketFiles: largestRuleBucket ? largestRuleBucket.fileCount : null,
|
|
926
|
-
largestRuleBucketSites: largestRuleBucket ? largestRuleBucket.siteCount : null,
|
|
927
|
-
largestRuleBucketRule: largestRuleBucket ? largestRuleBucket.ruleKey : null
|
|
928
|
-
})
|
|
929
|
-
};
|
|
930
|
-
if (result.baselineDelta) Object.assign(attributes, withNamespace("baseline", {
|
|
931
|
-
new: summary.totalDiagnosticCount,
|
|
932
|
-
fixed: result.baselineDelta.fixedCount,
|
|
933
|
-
baseTotal: result.baselineDelta.baseTotalCount,
|
|
934
|
-
crossFileMatches: result.baselineDelta.crossFileMatchCount,
|
|
935
|
-
degraded: false
|
|
936
|
-
}));
|
|
937
|
-
else if (input.gateExempt) attributes["baseline.degraded"] = true;
|
|
938
|
-
return attributes;
|
|
939
|
-
};
|
|
940
|
-
const buildActionAttributes = () => {
|
|
941
|
-
const { githubActorAssociation } = resolveGithubActionsScoreMetadata();
|
|
942
|
-
return withNamespace("action", {
|
|
943
|
-
actorAssociation: githubActorAssociation,
|
|
944
|
-
runnerOs: detectRunnerOs(),
|
|
945
|
-
comment: readEnvBoolean(ACTION_INPUT_ENVIRONMENT_VARIABLES.comment),
|
|
946
|
-
reviewComments: readEnvBoolean(ACTION_INPUT_ENVIRONMENT_VARIABLES.reviewComments),
|
|
947
|
-
versionPin: resolveVersionPin(process.env[ACTION_INPUT_ENVIRONMENT_VARIABLES.version])
|
|
948
|
-
});
|
|
949
|
-
};
|
|
950
|
-
const buildScanAttributes = (input) => {
|
|
951
|
-
const ruleOverrides = input.userConfig?.rules ?? {};
|
|
952
|
-
const ruleKeys = Object.keys(ruleOverrides);
|
|
953
|
-
return withNamespace("scan", {
|
|
954
|
-
mode: input.mode,
|
|
955
|
-
scope: input.scope,
|
|
956
|
-
parallel: input.parallel,
|
|
957
|
-
workerCount: input.workerCount,
|
|
958
|
-
maxDurationMs: input.maxDurationMs,
|
|
959
|
-
lint: input.lint,
|
|
960
|
-
maintainability: input.deadCode,
|
|
961
|
-
supplyChain: input.supplyChain,
|
|
962
|
-
scoreOnly: input.scoreOnly,
|
|
963
|
-
noScore: input.noScore,
|
|
964
|
-
respectInlineDisables: input.respectInlineDisables,
|
|
965
|
-
showWarnings: input.showWarnings,
|
|
966
|
-
usedOutputDir: input.usedOutputDir,
|
|
967
|
-
ignoredTagCount: input.ignoredTagCount,
|
|
968
|
-
hasCustomConfig: input.hasCustomConfig,
|
|
969
|
-
rulesConfigured: ruleKeys.length,
|
|
970
|
-
rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
|
|
971
|
-
projectAnalysisRuleCount: countOptInProjectRuleSelections(buildRuleSeverityControls(input.userConfig)),
|
|
972
|
-
fileCount: input.result?.scannedFileCount,
|
|
973
|
-
nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length,
|
|
974
|
-
htmlFileCount: input.result?.analyzedFiles?.filter((filePath) => HTML_FILE_PATTERN.test(filePath)).length,
|
|
975
|
-
multilineDiagnosticCount: input.result?.diagnostics.filter((diagnostic) => (diagnostic.endLine ?? diagnostic.line) > diagnostic.line).length
|
|
976
|
-
});
|
|
977
|
-
};
|
|
978
|
-
/**
|
|
979
|
-
* Projects a scan into the namespaced attribute set for its root span — the
|
|
980
|
-
* canonical per-scan "wide event". Every attribute carries a dotted namespace
|
|
981
|
-
* that groups it by concept (`scan.*` config, `action.*` CI knobs, `outcome.*`
|
|
982
|
-
* verdict, `diag.*` findings, `score.*`, `lint.*`, `maintainability.*`, `cache.*`,
|
|
983
|
-
* `supplyChain.*`, `timing.*`, `migration.*`, `baseline.*`) so the attributes
|
|
984
|
-
* tree up in Sentry's attribute browser and stay filter-/group-/aggregate-able
|
|
985
|
-
* in the Spans dataset. Pure and exported so the projection (outcome
|
|
986
|
-
* precedence, rule/category rollups, CI knobs, config shape) is unit-testable
|
|
987
|
-
* without a live Sentry client. `null`/`undefined` values are dropped so absent
|
|
988
|
-
* signals never become misleading `"null"`/`"undefined"` attributes. The run +
|
|
989
|
-
* project base context (version, command, ci/provider, framework, …) is already
|
|
990
|
-
* on the span from `withSentryRunSpan` / `recordSentryProjectContext`, so this
|
|
991
|
-
* adds only what those don't carry.
|
|
992
|
-
*/
|
|
993
|
-
const buildRunEventAttributes = (input) => toSpanAttributes({
|
|
994
|
-
...buildScanAttributes(input),
|
|
995
|
-
...buildActionAttributes(),
|
|
996
|
-
...buildOutcomeAttributes(input),
|
|
997
|
-
...buildCacheAttributes(input)
|
|
998
|
-
});
|
|
999
|
-
/**
|
|
1000
|
-
* Stamps the wide-event attributes onto the run's root span. A guarded no-op
|
|
1001
|
-
* when tracing is off (no `rootSpan`) and swallow-on-throw, so telemetry can
|
|
1002
|
-
* never break the run.
|
|
1003
|
-
*/
|
|
1004
|
-
const recordRunEvent = (rootSpan, input) => {
|
|
1005
|
-
if (!rootSpan) return;
|
|
1006
|
-
try {
|
|
1007
|
-
for (const [key, value] of Object.entries(buildRunEventAttributes(input))) rootSpan.attribute(key, value);
|
|
1008
|
-
} catch {}
|
|
1009
|
-
};
|
|
1010
|
-
//#endregion
|
|
1011
|
-
//#region src/cli/utils/diagnostic-intersects-line-ranges.ts
|
|
1012
|
-
const diagnosticIntersectsLineRanges = (diagnostic, lineRanges) => {
|
|
1013
|
-
const diagnosticEndLine = Math.max(diagnostic.line, diagnostic.endLine ?? diagnostic.line);
|
|
1014
|
-
return lineRanges.some(([rangeStartLine, rangeEndLine]) => diagnostic.line <= rangeEndLine && diagnosticEndLine >= rangeStartLine);
|
|
1015
|
-
};
|
|
1016
|
-
//#endregion
|
|
1017
|
-
//#region src/cli/utils/filter-diagnostics-by-changed-lines.ts
|
|
1018
|
-
const filterDiagnosticsByChangedLines = (input) => {
|
|
1019
|
-
const rangesByFile = /* @__PURE__ */ new Map();
|
|
1020
|
-
for (const entry of input.changedLineRanges) rangesByFile.set(toForwardSlashes(entry.file), entry.ranges);
|
|
1021
|
-
return input.diagnostics.filter((diagnostic) => {
|
|
1022
|
-
const relativePath = toForwardSlashes(path.isAbsolute(diagnostic.filePath) ? path.relative(input.directory, diagnostic.filePath) : diagnostic.filePath);
|
|
1023
|
-
const ranges = rangesByFile.get(relativePath);
|
|
1024
|
-
return ranges !== void 0 && diagnosticIntersectsLineRanges(diagnostic, ranges);
|
|
1025
|
-
});
|
|
1026
|
-
};
|
|
614
|
+
const resolveInvocationOxlintConcurrency = (requestedConcurrency) => resolveScanConcurrency(requestedConcurrency ?? runSync(OxlintConcurrency));
|
|
1027
615
|
//#endregion
|
|
1028
616
|
//#region src/cli/utils/resolve-oxlint-node.ts
|
|
1029
617
|
const consoleWarn = (message) => warn(highlighter.warn(message));
|
|
@@ -1138,7 +726,8 @@ const resolveInspectOptions = (inputOptions, userConfig) => {
|
|
|
1138
726
|
supplyChainManifestChanged: inputOptions.supplyChainManifestChanged ?? false,
|
|
1139
727
|
excludedProjectDirectories: inputOptions.excludedProjectDirectories ?? [],
|
|
1140
728
|
retainExcludedProjectDeadCodeDiagnostics: inputOptions.retainExcludedProjectDeadCodeDiagnostics ?? false,
|
|
1141
|
-
precomputedSourceFileCount: inputOptions.precomputedSourceFileCount
|
|
729
|
+
precomputedSourceFileCount: inputOptions.precomputedSourceFileCount,
|
|
730
|
+
precomputedSourceFiles: inputOptions.precomputedSourceFiles
|
|
1142
731
|
};
|
|
1143
732
|
};
|
|
1144
733
|
//#endregion
|
|
@@ -1605,575 +1194,1019 @@ const copyUnchangedBaselineSources = async (input) => {
|
|
|
1605
1194
|
})).every(Boolean);
|
|
1606
1195
|
};
|
|
1607
1196
|
//#endregion
|
|
1608
|
-
//#region src/cli/utils/read-diagnostic-evidence.ts
|
|
1609
|
-
const SOURCE_EXTENSIONS = [
|
|
1610
|
-
".ts",
|
|
1611
|
-
".tsx",
|
|
1612
|
-
".js",
|
|
1613
|
-
".jsx",
|
|
1614
|
-
".mts",
|
|
1615
|
-
".cts",
|
|
1616
|
-
".mjs",
|
|
1617
|
-
".cjs"
|
|
1197
|
+
//#region src/cli/utils/read-diagnostic-evidence.ts
|
|
1198
|
+
const SOURCE_EXTENSIONS = [
|
|
1199
|
+
".ts",
|
|
1200
|
+
".tsx",
|
|
1201
|
+
".js",
|
|
1202
|
+
".jsx",
|
|
1203
|
+
".mts",
|
|
1204
|
+
".cts",
|
|
1205
|
+
".mjs",
|
|
1206
|
+
".cjs"
|
|
1207
|
+
];
|
|
1208
|
+
const COMPONENT_WRAPPER_NAMES = new Set(["memo", "forwardRef"]);
|
|
1209
|
+
const getScriptKind = (filePath) => {
|
|
1210
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
1211
|
+
if (extension === ".tsx") return typescriptBinding.ScriptKind.TSX;
|
|
1212
|
+
if (extension === ".jsx") return typescriptBinding.ScriptKind.JSX;
|
|
1213
|
+
if (extension === ".js" || extension === ".mjs" || extension === ".cjs") return typescriptBinding.ScriptKind.JS;
|
|
1214
|
+
return typescriptBinding.ScriptKind.TS;
|
|
1215
|
+
};
|
|
1216
|
+
const resolveSafeSourcePath = (rootDirectory, filePath) => {
|
|
1217
|
+
try {
|
|
1218
|
+
const resolvedRootDirectory = fs.realpathSync(rootDirectory);
|
|
1219
|
+
const absolutePath = path.resolve(resolvedRootDirectory, filePath);
|
|
1220
|
+
const relativePath = path.relative(resolvedRootDirectory, absolutePath);
|
|
1221
|
+
if (relativePath.length === 0 || relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) return null;
|
|
1222
|
+
const stats = fs.lstatSync(absolutePath);
|
|
1223
|
+
if (!stats.isFile() || stats.isSymbolicLink()) return null;
|
|
1224
|
+
const realPath = fs.realpathSync(absolutePath);
|
|
1225
|
+
const realRelativePath = path.relative(resolvedRootDirectory, realPath);
|
|
1226
|
+
return realRelativePath !== ".." && !realRelativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(realRelativePath) ? realPath : null;
|
|
1227
|
+
} catch {
|
|
1228
|
+
return null;
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
const readSourceRecord = (rootDirectory, filePath, state) => {
|
|
1232
|
+
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
1233
|
+
if (state.recordByFilePath.has(normalizedFilePath)) return state.recordByFilePath.get(normalizedFilePath) ?? null;
|
|
1234
|
+
const absolutePath = resolveSafeSourcePath(rootDirectory, normalizedFilePath);
|
|
1235
|
+
if (absolutePath === null) {
|
|
1236
|
+
state.recordByFilePath.set(normalizedFilePath, null);
|
|
1237
|
+
return null;
|
|
1238
|
+
}
|
|
1239
|
+
try {
|
|
1240
|
+
const sourceText = fs.readFileSync(absolutePath, "utf-8");
|
|
1241
|
+
const record = {
|
|
1242
|
+
absolutePath,
|
|
1243
|
+
filePath: normalizedFilePath,
|
|
1244
|
+
sourceFile: typescriptBinding.createSourceFile(absolutePath, sourceText, typescriptBinding.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
1245
|
+
sourceText
|
|
1246
|
+
};
|
|
1247
|
+
state.recordByFilePath.set(normalizedFilePath, record);
|
|
1248
|
+
return record;
|
|
1249
|
+
} catch {
|
|
1250
|
+
state.recordByFilePath.set(normalizedFilePath, null);
|
|
1251
|
+
return null;
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
const listSourceRecords = (rootDirectory, state) => {
|
|
1255
|
+
if (state.sourceRecords !== null) return state.sourceRecords;
|
|
1256
|
+
state.sourceRecords = listSourceFiles(rootDirectory).flatMap((filePath) => {
|
|
1257
|
+
const record = readSourceRecord(rootDirectory, filePath, state);
|
|
1258
|
+
return record === null ? [] : [record];
|
|
1259
|
+
});
|
|
1260
|
+
return state.sourceRecords;
|
|
1261
|
+
};
|
|
1262
|
+
const unwrapExpression = (expression) => {
|
|
1263
|
+
let currentExpression = expression;
|
|
1264
|
+
while (typescriptBinding.isParenthesizedExpression(currentExpression) || typescriptBinding.isAsExpression(currentExpression) || typescriptBinding.isTypeAssertionExpression(currentExpression) || typescriptBinding.isNonNullExpression(currentExpression) || typescriptBinding.isSatisfiesExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
1265
|
+
return currentExpression;
|
|
1266
|
+
};
|
|
1267
|
+
const getCalleeName = (expression) => {
|
|
1268
|
+
const unwrappedExpression = unwrapExpression(expression);
|
|
1269
|
+
if (typescriptBinding.isIdentifier(unwrappedExpression)) return unwrappedExpression.text;
|
|
1270
|
+
if (typescriptBinding.isPropertyAccessExpression(unwrappedExpression)) return unwrappedExpression.name.text;
|
|
1271
|
+
return null;
|
|
1272
|
+
};
|
|
1273
|
+
const isComponentWrapperCall = (node) => {
|
|
1274
|
+
const wrapperName = getCalleeName(node.expression);
|
|
1275
|
+
return wrapperName !== null && COMPONENT_WRAPPER_NAMES.has(wrapperName);
|
|
1276
|
+
};
|
|
1277
|
+
const getFunctionName = (node) => {
|
|
1278
|
+
if (typescriptBinding.isFunctionDeclaration(node) && node.name !== void 0) return node.name.text;
|
|
1279
|
+
if (typescriptBinding.isArrowFunction(node) || typescriptBinding.isFunctionExpression(node)) {
|
|
1280
|
+
let componentExpression = node;
|
|
1281
|
+
while (true) {
|
|
1282
|
+
const parent = componentExpression.parent;
|
|
1283
|
+
if (typescriptBinding.isParenthesizedExpression(parent) || typescriptBinding.isAsExpression(parent) || typescriptBinding.isTypeAssertionExpression(parent) || typescriptBinding.isNonNullExpression(parent) || typescriptBinding.isSatisfiesExpression(parent)) {
|
|
1284
|
+
componentExpression = parent;
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
if (typescriptBinding.isCallExpression(parent) && parent.arguments[0] === componentExpression && isComponentWrapperCall(parent)) {
|
|
1288
|
+
componentExpression = parent;
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
if (typescriptBinding.isVariableDeclaration(parent) && typescriptBinding.isIdentifier(parent.name)) return parent.name.text;
|
|
1292
|
+
return node.name?.text ?? null;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
return null;
|
|
1296
|
+
};
|
|
1297
|
+
const isComponentFunction = (node) => typescriptBinding.isFunctionDeclaration(node) || typescriptBinding.isFunctionExpression(node) || typescriptBinding.isArrowFunction(node);
|
|
1298
|
+
const isDefaultExportedComponent = (record, node, componentName) => {
|
|
1299
|
+
if (typescriptBinding.canHaveModifiers(node) && typescriptBinding.getModifiers(node)?.some((modifier) => modifier.kind === typescriptBinding.SyntaxKind.DefaultKeyword)) return true;
|
|
1300
|
+
return record.sourceFile.statements.some((statement) => typescriptBinding.isExportAssignment(statement) && typescriptBinding.isIdentifier(statement.expression) && statement.expression.text === componentName);
|
|
1301
|
+
};
|
|
1302
|
+
const findEnclosingComponent = (record, diagnostic) => {
|
|
1303
|
+
if (!Number.isInteger(diagnostic.line) || diagnostic.line < 1) return null;
|
|
1304
|
+
const lineIndex = diagnostic.line - 1;
|
|
1305
|
+
const lineStarts = record.sourceFile.getLineStarts();
|
|
1306
|
+
const lineStart = lineStarts[lineIndex];
|
|
1307
|
+
if (lineStart === void 0) return null;
|
|
1308
|
+
const nextLineStart = lineStarts[lineIndex + 1] ?? record.sourceFile.end;
|
|
1309
|
+
const lineEnd = Math.max(lineStart, nextLineStart - 1);
|
|
1310
|
+
const columnIndex = Number.isInteger(diagnostic.column) ? Math.max(0, diagnostic.column - 1) : 0;
|
|
1311
|
+
const diagnosticPosition = Math.min(lineStart + columnIndex, lineEnd);
|
|
1312
|
+
let component = null;
|
|
1313
|
+
let componentWidth = Number.POSITIVE_INFINITY;
|
|
1314
|
+
const visit = (node) => {
|
|
1315
|
+
if (diagnosticPosition < node.getStart(record.sourceFile) || diagnosticPosition > node.end) return;
|
|
1316
|
+
if (isComponentFunction(node)) {
|
|
1317
|
+
const name = getFunctionName(node);
|
|
1318
|
+
const width = node.end - node.getStart(record.sourceFile);
|
|
1319
|
+
if (name !== null && /^[A-Z]/.test(name) && width < componentWidth) {
|
|
1320
|
+
component = {
|
|
1321
|
+
isDefaultExport: isDefaultExportedComponent(record, node, name),
|
|
1322
|
+
name,
|
|
1323
|
+
node,
|
|
1324
|
+
record
|
|
1325
|
+
};
|
|
1326
|
+
componentWidth = width;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
typescriptBinding.forEachChild(node, visit);
|
|
1330
|
+
};
|
|
1331
|
+
visit(record.sourceFile);
|
|
1332
|
+
return component;
|
|
1333
|
+
};
|
|
1334
|
+
const moduleSpecifierTargetsComponent = (callerRecord, moduleSpecifier, componentRecord) => {
|
|
1335
|
+
if (!moduleSpecifier.startsWith(".")) return false;
|
|
1336
|
+
const unresolvedPath = path.resolve(path.dirname(callerRecord.absolutePath), moduleSpecifier);
|
|
1337
|
+
const specifiedExtension = path.extname(unresolvedPath).toLowerCase();
|
|
1338
|
+
const sourcePathWithoutExtension = SOURCE_EXTENSIONS.includes(specifiedExtension) ? unresolvedPath.slice(0, -specifiedExtension.length) : unresolvedPath;
|
|
1339
|
+
return [
|
|
1340
|
+
unresolvedPath,
|
|
1341
|
+
...SOURCE_EXTENSIONS.map((extension) => `${sourcePathWithoutExtension}${extension}`),
|
|
1342
|
+
...sourcePathWithoutExtension === unresolvedPath ? SOURCE_EXTENSIONS.map((extension) => path.join(unresolvedPath, `index${extension}`)) : []
|
|
1343
|
+
].includes(componentRecord.absolutePath);
|
|
1344
|
+
};
|
|
1345
|
+
const getComponentTagNames = (record, component) => {
|
|
1346
|
+
if (record.absolutePath === component.record.absolutePath) return new Set([component.name]);
|
|
1347
|
+
const tagNames = /* @__PURE__ */ new Set();
|
|
1348
|
+
for (const statement of record.sourceFile.statements) {
|
|
1349
|
+
if (!typescriptBinding.isImportDeclaration(statement) || !typescriptBinding.isStringLiteral(statement.moduleSpecifier) || !moduleSpecifierTargetsComponent(record, statement.moduleSpecifier.text, component.record)) continue;
|
|
1350
|
+
const importClause = statement.importClause;
|
|
1351
|
+
if (component.isDefaultExport && importClause?.name !== void 0) tagNames.add(importClause.name.text);
|
|
1352
|
+
const namedBindings = importClause?.namedBindings;
|
|
1353
|
+
if (namedBindings !== void 0 && typescriptBinding.isNamedImports(namedBindings)) {
|
|
1354
|
+
for (const element of namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === component.name) tagNames.add(element.name.text);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return tagNames;
|
|
1358
|
+
};
|
|
1359
|
+
const getTransparentCall = (functionNode) => {
|
|
1360
|
+
if (functionNode.body === void 0) return null;
|
|
1361
|
+
if (!typescriptBinding.isBlock(functionNode.body)) {
|
|
1362
|
+
const expression = unwrapExpression(functionNode.body);
|
|
1363
|
+
return typescriptBinding.isCallExpression(expression) ? expression : null;
|
|
1364
|
+
}
|
|
1365
|
+
if (functionNode.body.statements.length !== 1) return null;
|
|
1366
|
+
const statement = functionNode.body.statements[0];
|
|
1367
|
+
if (statement === void 0) return null;
|
|
1368
|
+
const expression = typescriptBinding.isExpressionStatement(statement) || typescriptBinding.isReturnStatement(statement) ? statement.expression : void 0;
|
|
1369
|
+
if (expression === void 0) return null;
|
|
1370
|
+
const unwrappedExpression = unwrapExpression(expression);
|
|
1371
|
+
return typescriptBinding.isCallExpression(unwrappedExpression) ? unwrappedExpression : null;
|
|
1372
|
+
};
|
|
1373
|
+
const resolveTransparentTarget = (functionNode) => {
|
|
1374
|
+
const call = getTransparentCall(functionNode);
|
|
1375
|
+
if (call === null) return null;
|
|
1376
|
+
const target = unwrapExpression(call.expression);
|
|
1377
|
+
if (!typescriptBinding.isIdentifier(target) || call.arguments.length !== functionNode.parameters.length) return null;
|
|
1378
|
+
for (const [parameterIndex, parameter] of functionNode.parameters.entries()) {
|
|
1379
|
+
const argument = call.arguments[parameterIndex];
|
|
1380
|
+
const unwrappedArgument = argument === void 0 ? null : unwrapExpression(argument);
|
|
1381
|
+
if (!typescriptBinding.isIdentifier(parameter.name) || unwrappedArgument === null || !typescriptBinding.isIdentifier(unwrappedArgument) || unwrappedArgument.text !== parameter.name.text) return null;
|
|
1382
|
+
}
|
|
1383
|
+
return target.text;
|
|
1384
|
+
};
|
|
1385
|
+
const getForwardingFunction = (declaration) => {
|
|
1386
|
+
if (typescriptBinding.isFunctionDeclaration(declaration)) return declaration;
|
|
1387
|
+
if (!typescriptBinding.isVariableDeclaration(declaration) || declaration.initializer === void 0) return null;
|
|
1388
|
+
const initializer = unwrapExpression(declaration.initializer);
|
|
1389
|
+
if (typescriptBinding.isArrowFunction(initializer) || typescriptBinding.isFunctionExpression(initializer)) return initializer;
|
|
1390
|
+
if (typescriptBinding.isCallExpression(initializer) && getCalleeName(initializer.expression) === "useCallback") {
|
|
1391
|
+
const callback = initializer.arguments[0];
|
|
1392
|
+
if (callback !== void 0 && (typescriptBinding.isArrowFunction(callback) || typescriptBinding.isFunctionExpression(callback))) return callback;
|
|
1393
|
+
}
|
|
1394
|
+
return null;
|
|
1395
|
+
};
|
|
1396
|
+
const getDeclaredBindingName = (node) => {
|
|
1397
|
+
if ((typescriptBinding.isVariableDeclaration(node) || typescriptBinding.isParameter(node) || typescriptBinding.isBindingElement(node) || typescriptBinding.isFunctionDeclaration(node)) && node.name !== void 0 && typescriptBinding.isIdentifier(node.name)) return node.name.text;
|
|
1398
|
+
if (typescriptBinding.isImportClause(node)) return node.name?.text ?? null;
|
|
1399
|
+
if (typescriptBinding.isImportSpecifier(node) || typescriptBinding.isNamespaceImport(node)) return node.name.text;
|
|
1400
|
+
return null;
|
|
1401
|
+
};
|
|
1402
|
+
const findBindingScope = (node) => {
|
|
1403
|
+
let currentNode = node;
|
|
1404
|
+
while (currentNode !== void 0) {
|
|
1405
|
+
if (typescriptBinding.isParameter(currentNode) && typescriptBinding.isFunctionLike(currentNode.parent)) return currentNode.parent;
|
|
1406
|
+
if (typescriptBinding.isBlock(currentNode) || typescriptBinding.isModuleBlock(currentNode) || typescriptBinding.isSourceFile(currentNode)) return currentNode;
|
|
1407
|
+
currentNode = currentNode.parent;
|
|
1408
|
+
}
|
|
1409
|
+
return node.getSourceFile();
|
|
1410
|
+
};
|
|
1411
|
+
const findBindingDeclaration = (record, binding) => {
|
|
1412
|
+
const usagePosition = binding.getStart(record.sourceFile);
|
|
1413
|
+
const candidates = [];
|
|
1414
|
+
const visit = (node) => {
|
|
1415
|
+
if (getDeclaredBindingName(node) === binding.text) {
|
|
1416
|
+
const scope = findBindingScope(node);
|
|
1417
|
+
const isVisibleScope = usagePosition >= scope.getStart(record.sourceFile) && usagePosition <= scope.end;
|
|
1418
|
+
const isVisibleDeclaration = typescriptBinding.isFunctionDeclaration(node) || typescriptBinding.isParameter(node) || typescriptBinding.isBindingElement(node) || typescriptBinding.isImportClause(node) || typescriptBinding.isImportSpecifier(node) || typescriptBinding.isNamespaceImport(node) || node.getStart(record.sourceFile) < usagePosition;
|
|
1419
|
+
if (isVisibleScope && isVisibleDeclaration) candidates.push({
|
|
1420
|
+
declaration: node,
|
|
1421
|
+
scope
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
typescriptBinding.forEachChild(node, visit);
|
|
1425
|
+
};
|
|
1426
|
+
visit(record.sourceFile);
|
|
1427
|
+
let nearestCandidate = null;
|
|
1428
|
+
let nearestScopeWidth = Number.POSITIVE_INFINITY;
|
|
1429
|
+
let isNearestScopeAmbiguous = false;
|
|
1430
|
+
for (const candidate of candidates) {
|
|
1431
|
+
const scopeWidth = candidate.scope.end - candidate.scope.getStart(record.sourceFile);
|
|
1432
|
+
if (scopeWidth < nearestScopeWidth) {
|
|
1433
|
+
nearestCandidate = candidate;
|
|
1434
|
+
nearestScopeWidth = scopeWidth;
|
|
1435
|
+
isNearestScopeAmbiguous = false;
|
|
1436
|
+
} else if (scopeWidth === nearestScopeWidth) isNearestScopeAmbiguous = true;
|
|
1437
|
+
}
|
|
1438
|
+
if (isNearestScopeAmbiguous) return null;
|
|
1439
|
+
const declaration = nearestCandidate?.declaration;
|
|
1440
|
+
return declaration !== void 0 && (typescriptBinding.isVariableDeclaration(declaration) || typescriptBinding.isFunctionDeclaration(declaration)) ? declaration : null;
|
|
1441
|
+
};
|
|
1442
|
+
const resolveHandlerBinding = (record, binding) => {
|
|
1443
|
+
const declaration = findBindingDeclaration(record, binding);
|
|
1444
|
+
if (declaration === null) return binding.text;
|
|
1445
|
+
const forwardingFunction = getForwardingFunction(declaration);
|
|
1446
|
+
const target = forwardingFunction === null ? null : resolveTransparentTarget(forwardingFunction);
|
|
1447
|
+
return target === null || target === binding.text ? binding.text : target;
|
|
1448
|
+
};
|
|
1449
|
+
const getJsxTagName = (node) => typescriptBinding.isIdentifier(node.tagName) ? node.tagName.text : null;
|
|
1450
|
+
const getHandlerBinding = (node, propName) => {
|
|
1451
|
+
for (const property of node.attributes.properties) {
|
|
1452
|
+
if (!typescriptBinding.isJsxAttribute(property) || property.name.getText() !== propName) continue;
|
|
1453
|
+
const initializer = property.initializer;
|
|
1454
|
+
if (initializer === void 0 || !typescriptBinding.isJsxExpression(initializer)) return null;
|
|
1455
|
+
if (initializer.expression === void 0) return null;
|
|
1456
|
+
const expression = unwrapExpression(initializer.expression);
|
|
1457
|
+
return typescriptBinding.isIdentifier(expression) ? expression : null;
|
|
1458
|
+
}
|
|
1459
|
+
return null;
|
|
1460
|
+
};
|
|
1461
|
+
const getForwardedHandlerAliases = (rootDirectory, component, propNames, state) => {
|
|
1462
|
+
const cacheKey = `${component.record.filePath}\0${component.name}\0${[...propNames].sort().join("\0")}`;
|
|
1463
|
+
const cachedAliases = state.aliasesByComponent.get(cacheKey);
|
|
1464
|
+
if (cachedAliases !== void 0) return cachedAliases;
|
|
1465
|
+
const bindingsByPropName = /* @__PURE__ */ new Map();
|
|
1466
|
+
const unresolvedPropNames = /* @__PURE__ */ new Set();
|
|
1467
|
+
let callsiteCount = 0;
|
|
1468
|
+
for (const record of listSourceRecords(rootDirectory, state)) {
|
|
1469
|
+
const tagNames = getComponentTagNames(record, component);
|
|
1470
|
+
if (tagNames.size === 0) continue;
|
|
1471
|
+
const visit = (node) => {
|
|
1472
|
+
if (record === component.record && node === component.node) return;
|
|
1473
|
+
if (typescriptBinding.isJsxOpeningElement(node) || typescriptBinding.isJsxSelfClosingElement(node)) {
|
|
1474
|
+
const tagName = getJsxTagName(node);
|
|
1475
|
+
if (tagName !== null && tagNames.has(tagName)) {
|
|
1476
|
+
callsiteCount += 1;
|
|
1477
|
+
for (const propName of propNames) {
|
|
1478
|
+
const binding = getHandlerBinding(node, propName);
|
|
1479
|
+
if (binding === null) {
|
|
1480
|
+
unresolvedPropNames.add(propName);
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
const bindings = bindingsByPropName.get(propName) ?? /* @__PURE__ */ new Set();
|
|
1484
|
+
bindings.add(resolveHandlerBinding(record, binding));
|
|
1485
|
+
bindingsByPropName.set(propName, bindings);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
typescriptBinding.forEachChild(node, visit);
|
|
1490
|
+
};
|
|
1491
|
+
visit(record.sourceFile);
|
|
1492
|
+
}
|
|
1493
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
1494
|
+
if (callsiteCount > 0) for (const [propName, bindings] of bindingsByPropName) {
|
|
1495
|
+
if (bindings.size !== 1 || unresolvedPropNames.has(propName)) continue;
|
|
1496
|
+
const [bindingName] = bindings;
|
|
1497
|
+
if (bindingName !== void 0) aliases.set(propName, bindingName);
|
|
1498
|
+
}
|
|
1499
|
+
state.aliasesByComponent.set(cacheKey, aliases);
|
|
1500
|
+
return aliases;
|
|
1501
|
+
};
|
|
1502
|
+
const normalizeForwardedHandlers = (evidence, aliases) => {
|
|
1503
|
+
let normalizedEvidence = evidence;
|
|
1504
|
+
for (const [propName, bindingName] of aliases) normalizedEvidence = normalizedEvidence.replace(new RegExp(`\\{\\s*${propName}\\s*\\}`, "g"), `{${bindingName}}`).replace(new RegExp(`=>\\s*${propName}\\s*\\(`, "g"), `=> ${bindingName}(`);
|
|
1505
|
+
return normalizedEvidence;
|
|
1506
|
+
};
|
|
1507
|
+
const createDiagnosticEvidenceReader = (rootDirectory, options = {}) => {
|
|
1508
|
+
const state = {
|
|
1509
|
+
aliasesByComponent: /* @__PURE__ */ new Map(),
|
|
1510
|
+
recordByFilePath: /* @__PURE__ */ new Map(),
|
|
1511
|
+
sourceRecords: null
|
|
1512
|
+
};
|
|
1513
|
+
return (diagnostic) => {
|
|
1514
|
+
const record = readSourceRecord(rootDirectory, diagnostic.filePath, state);
|
|
1515
|
+
if (record === null || !Number.isInteger(diagnostic.line)) return null;
|
|
1516
|
+
const sourceLines = record.sourceText.split(/\r?\n/);
|
|
1517
|
+
const startLineIndex = Math.max(0, diagnostic.line - 1);
|
|
1518
|
+
const endLineIndex = Math.max(startLineIndex, (diagnostic.endLine ?? diagnostic.line) - 1);
|
|
1519
|
+
const evidence = sourceLines.slice(startLineIndex, endLineIndex + 1).join("\n");
|
|
1520
|
+
if (!options.resolveForwardedHandlers) return evidence;
|
|
1521
|
+
const propNames = new Set(evidence.match(/\bon[A-Z]\w*\b/g) ?? []);
|
|
1522
|
+
if (propNames.size === 0) return evidence;
|
|
1523
|
+
const component = findEnclosingComponent(record, diagnostic);
|
|
1524
|
+
if (component === null) return evidence;
|
|
1525
|
+
return normalizeForwardedHandlers(evidence, getForwardedHandlerAliases(rootDirectory, component, propNames, state));
|
|
1526
|
+
};
|
|
1527
|
+
};
|
|
1528
|
+
//#endregion
|
|
1529
|
+
//#region src/cli/utils/read-source-line.ts
|
|
1530
|
+
/**
|
|
1531
|
+
* Builds a cached `(filePath, line) -> source text` reader rooted at
|
|
1532
|
+
* `rootDirectory`, used as the fallback when construct-level diagnostic
|
|
1533
|
+
* evidence cannot be read. Wraps core's
|
|
1534
|
+
* `createNodeReadFileLinesSync` with per-file memoization + 1-indexed line
|
|
1535
|
+
* lookup; unreadable files / out-of-range lines return `null`.
|
|
1536
|
+
*/
|
|
1537
|
+
const createSourceLineReader = (rootDirectory) => {
|
|
1538
|
+
const readFileLines = createNodeReadFileLinesSync(rootDirectory);
|
|
1539
|
+
const fileLinesCache = /* @__PURE__ */ new Map();
|
|
1540
|
+
return (filePath, line) => {
|
|
1541
|
+
if (!fileLinesCache.has(filePath)) fileLinesCache.set(filePath, readFileLines(filePath));
|
|
1542
|
+
const lines = fileLinesCache.get(filePath) ?? null;
|
|
1543
|
+
if (lines === null || line < 1 || line > lines.length) return null;
|
|
1544
|
+
return lines[line - 1] ?? null;
|
|
1545
|
+
};
|
|
1546
|
+
};
|
|
1547
|
+
//#endregion
|
|
1548
|
+
//#region src/cli/utils/is-materializable-git-source.ts
|
|
1549
|
+
const GIT_LFS_POINTER_HEADER = "version https://git-lfs.github.com/spec/v1";
|
|
1550
|
+
const isMaterializableGitSource = (content) => !content.includes("\0") && !content.startsWith(GIT_LFS_POINTER_HEADER);
|
|
1551
|
+
//#endregion
|
|
1552
|
+
//#region src/cli/utils/materialize-baseline-files.ts
|
|
1553
|
+
/**
|
|
1554
|
+
* Materializes the base side of the `ref` → worktree diff into
|
|
1555
|
+
* `tempDirectory`, mirroring the project layout plus head's config files so
|
|
1556
|
+
* both sides lint under the same rules. Git rename heuristics are disabled by
|
|
1557
|
+
* the diff planner, so an old path is retained as a base deletion while its
|
|
1558
|
+
* new path is a head addition. Missing head-only additions are expected, and a
|
|
1559
|
+
* base-only deletion that cannot be read has nothing at head to compare
|
|
1560
|
+
* against; only a missing path that the plan says exists on both sides makes
|
|
1561
|
+
* `isComplete` false.
|
|
1562
|
+
*/
|
|
1563
|
+
const materializeBaselineFiles = (input) => runPromise(gen(function* () {
|
|
1564
|
+
const git = yield* Git;
|
|
1565
|
+
const diffPlan = yield* git.baselineDiffPlan({
|
|
1566
|
+
directory: input.directory,
|
|
1567
|
+
ref: input.ref
|
|
1568
|
+
});
|
|
1569
|
+
const baseFiles = input.baseFiles ?? diffPlan?.baseFiles;
|
|
1570
|
+
const headFiles = input.headFiles ?? diffPlan?.headFiles;
|
|
1571
|
+
const untrackedFiles = diffPlan?.untrackedFiles ?? [];
|
|
1572
|
+
if (baseFiles === void 0 || headFiles === void 0) return null;
|
|
1573
|
+
const files = [...new Set([...input.files, ...baseFiles])];
|
|
1574
|
+
const tree = yield* materializeSourceTree({
|
|
1575
|
+
directory: input.directory,
|
|
1576
|
+
files,
|
|
1577
|
+
tempDirectory: input.tempDirectory,
|
|
1578
|
+
readContent: (relativePath) => git.showRefContent({
|
|
1579
|
+
directory: input.directory,
|
|
1580
|
+
ref: input.ref,
|
|
1581
|
+
relativePath,
|
|
1582
|
+
options: { maxBufferBytes: GIT_SHOW_MAX_BUFFER_BYTES }
|
|
1583
|
+
}).pipe(map((content) => content !== null && isMaterializableGitSource(content) ? content : null))
|
|
1584
|
+
});
|
|
1585
|
+
const materializedFiles = new Set(tree.materializedFiles);
|
|
1586
|
+
const headFileSet = new Set(headFiles);
|
|
1587
|
+
return {
|
|
1588
|
+
...tree,
|
|
1589
|
+
baseFiles,
|
|
1590
|
+
headFiles,
|
|
1591
|
+
isComplete: baseFiles.every((filePath) => materializedFiles.has(filePath) || !headFileSet.has(filePath)),
|
|
1592
|
+
untrackedFiles
|
|
1593
|
+
};
|
|
1594
|
+
}).pipe(provide(Git.layerNode)));
|
|
1595
|
+
/**
|
|
1596
|
+
* Resolves the commit a baseline scan should read base content from — the
|
|
1597
|
+
* merge-base of `ref` and HEAD, so "introduced" is measured against the branch
|
|
1598
|
+
* point. `null` when unresolvable (ref missing/unsafe, no merge-base).
|
|
1599
|
+
*/
|
|
1600
|
+
const resolveMergeBaseRef = async (directory, ref) => {
|
|
1601
|
+
try {
|
|
1602
|
+
return await runPromise(gen(function* () {
|
|
1603
|
+
return yield* (yield* Git).mergeBase({
|
|
1604
|
+
directory,
|
|
1605
|
+
ref
|
|
1606
|
+
});
|
|
1607
|
+
}).pipe(provide(Git.layerNode)));
|
|
1608
|
+
} catch {
|
|
1609
|
+
return null;
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
//#endregion
|
|
1613
|
+
//#region src/cli/utils/has-unlinted-surviving-base-file.ts
|
|
1614
|
+
const hasUnlintedSurvivingBaseFile = (input) => {
|
|
1615
|
+
const analyzedBaseFiles = new Set(input.analyzedBaseFiles.map(toForwardSlashes));
|
|
1616
|
+
return filterSourceFiles(input.baseLintPaths.map(toForwardSlashes)).some((filePath) => input.headFiles.has(filePath) && !analyzedBaseFiles.has(filePath));
|
|
1617
|
+
};
|
|
1618
|
+
//#endregion
|
|
1619
|
+
//#region src/cli/utils/run-baseline-comparison.ts
|
|
1620
|
+
const silentConsole$1 = makeNoopConsole();
|
|
1621
|
+
const countIncompleteLintFiles = (lintPartialFailures) => countDroppedLintFiles(lintPartialFailures) + countDeadlineSkippedFiles(lintPartialFailures);
|
|
1622
|
+
const runBaselineComparison = async (input) => {
|
|
1623
|
+
const baselineIncludePaths = filterPathsOutsideDirectories({
|
|
1624
|
+
rootDirectory: input.directory,
|
|
1625
|
+
relativePaths: input.options.includePaths,
|
|
1626
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
1627
|
+
});
|
|
1628
|
+
const baselineBaseFiles = input.baseFiles ? filterPathsOutsideDirectories({
|
|
1629
|
+
rootDirectory: input.directory,
|
|
1630
|
+
relativePaths: input.baseFiles,
|
|
1631
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
1632
|
+
}) : void 0;
|
|
1633
|
+
const baselineHeadFiles = input.headFiles ? filterPathsOutsideDirectories({
|
|
1634
|
+
rootDirectory: input.directory,
|
|
1635
|
+
relativePaths: input.headFiles,
|
|
1636
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
1637
|
+
}) : void 0;
|
|
1638
|
+
const remainingBaselineBudgetMs = input.deadlineEpochMs === null ? null : remainingDeadlineBudgetMs(input.deadlineEpochMs);
|
|
1639
|
+
if (remainingBaselineBudgetMs === 0) return null;
|
|
1640
|
+
const baselineDeadlineSignal = remainingBaselineBudgetMs === null ? void 0 : AbortSignal.timeout(remainingBaselineBudgetMs);
|
|
1641
|
+
const baselineListingSignal = baselineDeadlineSignal === void 0 ? input.oxlintRuntime.abortSignal : input.oxlintRuntime.abortSignal === void 0 ? baselineDeadlineSignal : AbortSignal.any([input.oxlintRuntime.abortSignal, baselineDeadlineSignal]);
|
|
1642
|
+
let maintainabilitySourceFiles = [];
|
|
1643
|
+
if (input.options.deadCode) try {
|
|
1644
|
+
maintainabilitySourceFiles = (await listSourceFilesCooperative(input.directory, baselineListingSignal)).filter((filePath) => MAINTAINABILITY_SOURCE_FILE_PATTERN.test(filePath) && classifyFileContext(filePath) === "production");
|
|
1645
|
+
} catch (error) {
|
|
1646
|
+
if (baselineDeadlineSignal?.aborted) return null;
|
|
1647
|
+
throw error;
|
|
1648
|
+
}
|
|
1649
|
+
const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
|
|
1650
|
+
const snapshot = await materializeBaselineFiles({
|
|
1651
|
+
directory: input.directory,
|
|
1652
|
+
ref: input.baselineRef,
|
|
1653
|
+
files: input.options.deadCode ? input.options.includePaths : baselineIncludePaths,
|
|
1654
|
+
baseFiles: input.options.deadCode ? input.baseFiles : baselineBaseFiles,
|
|
1655
|
+
headFiles: input.options.deadCode ? input.headFiles : baselineHeadFiles,
|
|
1656
|
+
tempDirectory: temporaryDirectory
|
|
1657
|
+
}).catch((error) => {
|
|
1658
|
+
rmSync(temporaryDirectory, {
|
|
1659
|
+
recursive: true,
|
|
1660
|
+
force: true
|
|
1661
|
+
});
|
|
1662
|
+
throw error;
|
|
1663
|
+
});
|
|
1664
|
+
if (snapshot === null) {
|
|
1665
|
+
rmSync(temporaryDirectory, {
|
|
1666
|
+
recursive: true,
|
|
1667
|
+
force: true
|
|
1668
|
+
});
|
|
1669
|
+
return null;
|
|
1670
|
+
}
|
|
1671
|
+
try {
|
|
1672
|
+
if (!snapshot.isComplete) return null;
|
|
1673
|
+
if (input.options.deadCode && !await copyUnchangedBaselineSources({
|
|
1674
|
+
directory: input.directory,
|
|
1675
|
+
sourceFiles: maintainabilitySourceFiles,
|
|
1676
|
+
baseMaterializedFiles: snapshot.materializedFiles,
|
|
1677
|
+
headChangedFiles: snapshot.headFiles,
|
|
1678
|
+
untrackedFiles: snapshot.untrackedFiles,
|
|
1679
|
+
tempDirectory: snapshot.tempDirectory,
|
|
1680
|
+
deadlineEpochMs: input.deadlineEpochMs,
|
|
1681
|
+
signal: input.oxlintRuntime.abortSignal
|
|
1682
|
+
})) return null;
|
|
1683
|
+
const filteredSnapshotBaseFiles = filterPathsOutsideDirectories({
|
|
1684
|
+
rootDirectory: input.directory,
|
|
1685
|
+
relativePaths: snapshot.baseFiles,
|
|
1686
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
1687
|
+
});
|
|
1688
|
+
const filteredSnapshotHeadFiles = filterPathsOutsideDirectories({
|
|
1689
|
+
rootDirectory: input.directory,
|
|
1690
|
+
relativePaths: snapshot.headFiles,
|
|
1691
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
1692
|
+
});
|
|
1693
|
+
const analyzedHeadFiles = new Set(input.headAnalyzedFiles.map(toForwardSlashes));
|
|
1694
|
+
const baseFiles = new Set((baselineBaseFiles ?? filteredSnapshotBaseFiles).map(toForwardSlashes));
|
|
1695
|
+
const expectedHeadFiles = new Set((baselineHeadFiles ?? filteredSnapshotHeadFiles).map(toForwardSlashes));
|
|
1696
|
+
for (const filePath of baselineIncludePaths) {
|
|
1697
|
+
const normalizedFilePath = toForwardSlashes(filePath);
|
|
1698
|
+
if (!baseFiles.has(normalizedFilePath)) expectedHeadFiles.add(normalizedFilePath);
|
|
1699
|
+
}
|
|
1700
|
+
if (input.options.lint && filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
|
|
1701
|
+
const baselineLintPaths = new Set([...baselineIncludePaths, ...filteredSnapshotBaseFiles].map(toForwardSlashes));
|
|
1702
|
+
const materializedLintPaths = snapshot.materializedFiles.filter((filePath) => baselineLintPaths.has(toForwardSlashes(filePath)));
|
|
1703
|
+
const maintainabilityFocusPaths = [...new Set([...input.options.includePaths, ...snapshot.baseFiles].map(toForwardSlashes))];
|
|
1704
|
+
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)));
|
|
1705
|
+
const baseIncludePaths = materializedLintPaths.length > 0 ? materializedLintPaths : maintainabilityFocusPaths;
|
|
1706
|
+
const runtimeLayers = buildRuntimeLayers({
|
|
1707
|
+
directory: snapshot.tempDirectory,
|
|
1708
|
+
hasConfigOverride: true,
|
|
1709
|
+
userConfig: input.userConfig,
|
|
1710
|
+
configSourceDirectory: input.configSourceDirectory,
|
|
1711
|
+
projectInfoOverride: input.headProjectInfo,
|
|
1712
|
+
shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath || materializedLintPaths.length === 0,
|
|
1713
|
+
shouldRunDeadCode: input.options.deadCode,
|
|
1714
|
+
shouldRunSupplyChain: input.options.supplyChain,
|
|
1715
|
+
shouldComputeScore: false,
|
|
1716
|
+
shouldShowProgressSpinners: false,
|
|
1717
|
+
oxlintConcurrency: input.oxlintRuntime.concurrency,
|
|
1718
|
+
oxlintSpawnSlots: input.oxlintRuntime.spawnSlots
|
|
1719
|
+
});
|
|
1720
|
+
const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
|
|
1721
|
+
directory: snapshot.tempDirectory,
|
|
1722
|
+
includePaths: baseIncludePaths,
|
|
1723
|
+
maintainabilityFocusPaths,
|
|
1724
|
+
customRulesOnly: input.options.customRulesOnly,
|
|
1725
|
+
respectInlineDisables: input.options.respectInlineDisables,
|
|
1726
|
+
warnings: input.options.warnings,
|
|
1727
|
+
adoptExistingLintConfig: input.options.adoptExistingLintConfig,
|
|
1728
|
+
ignoredTags: input.options.ignoredTags,
|
|
1729
|
+
includedTags: input.options.includedTags,
|
|
1730
|
+
includeTagDefaults: input.options.includeTagDefaults,
|
|
1731
|
+
nodeBinaryPath: input.resolvedNodeBinaryPath ?? void 0,
|
|
1732
|
+
runDeadCode: input.options.deadCode,
|
|
1733
|
+
isCi: input.options.isCi,
|
|
1734
|
+
doctorVersion: VERSION,
|
|
1735
|
+
runId: getRunId(),
|
|
1736
|
+
resolveLocalGithubViewerPermission: false,
|
|
1737
|
+
suppressScanSummary: true,
|
|
1738
|
+
supplyChainManifestChanged: input.options.supplyChainManifestChanged,
|
|
1739
|
+
deadlineEpochMs: input.deadlineEpochMs ?? void 0,
|
|
1740
|
+
signal: input.oxlintRuntime.abortSignal,
|
|
1741
|
+
excludedProjectDirectories: baselineExcludedProjectDirectories,
|
|
1742
|
+
retainExcludedProjectDeadCodeDiagnostics: input.options.retainExcludedProjectDeadCodeDiagnostics
|
|
1743
|
+
}, {}).pipe(provide(runtimeLayers), provideService(PerFileLintCacheEnabled, false), provideService(SidecarLintCacheEnabled, false), provideService(Console, silentConsole$1))), { signal: input.oxlintRuntime.abortSignal });
|
|
1744
|
+
if (baseOutput.didLintFail || baseOutput.didDeadCodeFail) return null;
|
|
1745
|
+
if (countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0 && hasUnlintedSurvivingBaseFile({
|
|
1746
|
+
baseLintPaths: materializedLintPaths,
|
|
1747
|
+
headFiles: expectedHeadFiles,
|
|
1748
|
+
analyzedBaseFiles: baseOutput.analyzedFiles
|
|
1749
|
+
})) return null;
|
|
1750
|
+
const hasUnscannedUntrackedSourceFiles = filterSourceFiles(filterPathsOutsideDirectories({
|
|
1751
|
+
rootDirectory: input.directory,
|
|
1752
|
+
relativePaths: snapshot.untrackedFiles,
|
|
1753
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
1754
|
+
}).map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
|
|
1755
|
+
const diagnosticDelta = computeDiagnosticDelta({
|
|
1756
|
+
headDiagnostics: input.headDiagnostics,
|
|
1757
|
+
baseDiagnostics: baseOutput.diagnostics,
|
|
1758
|
+
readHeadLine: createSourceLineReader(input.directory),
|
|
1759
|
+
readBaseLine: createSourceLineReader(snapshot.tempDirectory),
|
|
1760
|
+
readHeadEvidence: createDiagnosticEvidenceReader(input.directory, { resolveForwardedHandlers: true }),
|
|
1761
|
+
readBaseEvidence: createDiagnosticEvidenceReader(snapshot.tempDirectory)
|
|
1762
|
+
});
|
|
1763
|
+
return {
|
|
1764
|
+
displayDiagnostics: diagnosticDelta.newDiagnostics,
|
|
1765
|
+
baselineDelta: {
|
|
1766
|
+
baseRef: input.baselineRef,
|
|
1767
|
+
fixedCount: hasUnscannedUntrackedSourceFiles ? 0 : diagnosticDelta.fixedCount,
|
|
1768
|
+
baseTotalCount: baseOutput.diagnostics.length,
|
|
1769
|
+
crossFileMatchCount: diagnosticDelta.crossFileMatchCount
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
} finally {
|
|
1773
|
+
snapshot.cleanup();
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
//#endregion
|
|
1777
|
+
//#region src/cli/utils/scan-result-cache-payload.ts
|
|
1778
|
+
const decodeDiagnostic = decodeUnknownSync(Diagnostic);
|
|
1779
|
+
const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
1780
|
+
const isNullableString = (value) => value === null || typeof value === "string";
|
|
1781
|
+
const isBoolean = (value) => typeof value === "boolean";
|
|
1782
|
+
const isNumber = (value) => typeof value === "number";
|
|
1783
|
+
const isString = (value) => typeof value === "string";
|
|
1784
|
+
const isDiagnosticArray = (value) => {
|
|
1785
|
+
if (!Array.isArray(value)) return false;
|
|
1786
|
+
try {
|
|
1787
|
+
for (const entry of value) decodeDiagnostic(entry);
|
|
1788
|
+
return true;
|
|
1789
|
+
} catch {
|
|
1790
|
+
return false;
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
const isScoreResult = (value) => value === null || isRecord(value) && typeof value.score === "number" && typeof value.label === "string";
|
|
1794
|
+
const isProjectInfo = (value) => isRecord(value) && typeof value.rootDirectory === "string" && typeof value.projectName === "string" && typeof value.framework === "string" && typeof value.sourceFileCount === "number";
|
|
1795
|
+
const isBaselineDelta = (value) => isRecord(value) && typeof value.baseRef === "string" && typeof value.fixedCount === "number" && typeof value.baseTotalCount === "number" && (value.crossFileMatchCount === void 0 || typeof value.crossFileMatchCount === "number");
|
|
1796
|
+
const isSuppressedRuleCountArray = (value) => Array.isArray(value) && value.every((entry) => isRecord(entry) && typeof entry.rule === "string" && typeof entry.source === "string" && typeof entry.count === "number");
|
|
1797
|
+
const REQUIRED_CACHED_SCAN_PAYLOAD_FIELDS = [
|
|
1798
|
+
{
|
|
1799
|
+
key: "diagnostics",
|
|
1800
|
+
isValid: isDiagnosticArray
|
|
1801
|
+
},
|
|
1802
|
+
{
|
|
1803
|
+
key: "score",
|
|
1804
|
+
isValid: isScoreResult
|
|
1805
|
+
},
|
|
1806
|
+
{
|
|
1807
|
+
key: "project",
|
|
1808
|
+
isValid: isProjectInfo
|
|
1809
|
+
},
|
|
1810
|
+
{
|
|
1811
|
+
key: "userConfig",
|
|
1812
|
+
isValid: (value) => value === null || isRecord(value)
|
|
1813
|
+
},
|
|
1814
|
+
{
|
|
1815
|
+
key: "didLintFail",
|
|
1816
|
+
isValid: isBoolean
|
|
1817
|
+
},
|
|
1818
|
+
{
|
|
1819
|
+
key: "lintFailureReason",
|
|
1820
|
+
isValid: isNullableString
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
key: "lintPartialFailures",
|
|
1824
|
+
isValid: isStringArray
|
|
1825
|
+
},
|
|
1826
|
+
{
|
|
1827
|
+
key: "didDeadCodeFail",
|
|
1828
|
+
isValid: isBoolean
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
key: "deadCodeFailureReason",
|
|
1832
|
+
isValid: isNullableString
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
key: "deadCodeOverlapped",
|
|
1836
|
+
isValid: isBoolean
|
|
1837
|
+
},
|
|
1838
|
+
{
|
|
1839
|
+
key: "directory",
|
|
1840
|
+
isValid: isString
|
|
1841
|
+
},
|
|
1842
|
+
{
|
|
1843
|
+
key: "scannedFileCount",
|
|
1844
|
+
isValid: isNumber
|
|
1845
|
+
},
|
|
1846
|
+
{
|
|
1847
|
+
key: "scannedFilePaths",
|
|
1848
|
+
isValid: isStringArray
|
|
1849
|
+
},
|
|
1850
|
+
{
|
|
1851
|
+
key: "scanElapsedMilliseconds",
|
|
1852
|
+
isValid: isNumber
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
key: "lintFailureReasonKind",
|
|
1856
|
+
isValid: isNullableString
|
|
1857
|
+
},
|
|
1858
|
+
{
|
|
1859
|
+
key: "supplyChainOverlapTimedOut",
|
|
1860
|
+
isValid: isBoolean
|
|
1861
|
+
}
|
|
1862
|
+
];
|
|
1863
|
+
const OPTIONAL_CACHED_SCAN_PAYLOAD_FIELDS = [
|
|
1864
|
+
{
|
|
1865
|
+
key: "analyzedFiles",
|
|
1866
|
+
isValid: isStringArray
|
|
1867
|
+
},
|
|
1868
|
+
{
|
|
1869
|
+
key: "baselineDelta",
|
|
1870
|
+
isValid: isBaselineDelta
|
|
1871
|
+
},
|
|
1872
|
+
{
|
|
1873
|
+
key: "scanConcurrency",
|
|
1874
|
+
isValid: isNumber
|
|
1875
|
+
},
|
|
1876
|
+
{
|
|
1877
|
+
key: "securityScanFailed",
|
|
1878
|
+
isValid: isBoolean
|
|
1879
|
+
},
|
|
1880
|
+
{
|
|
1881
|
+
key: "securityScanFailureReason",
|
|
1882
|
+
isValid: isNullableString
|
|
1883
|
+
},
|
|
1884
|
+
{
|
|
1885
|
+
key: "suppressedRuleCounts",
|
|
1886
|
+
isValid: isSuppressedRuleCountArray
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
key: "manifestContentHash",
|
|
1890
|
+
isValid: isNullableString
|
|
1891
|
+
}
|
|
1892
|
+
];
|
|
1893
|
+
const hasValidRequiredCachedScanPayloadFields = (value) => REQUIRED_CACHED_SCAN_PAYLOAD_FIELDS.every((field) => field.isValid(value[field.key]));
|
|
1894
|
+
const hasValidOptionalCachedScanPayloadFields = (value) => OPTIONAL_CACHED_SCAN_PAYLOAD_FIELDS.every((field) => value[field.key] === void 0 || field.isValid(value[field.key]));
|
|
1895
|
+
const isCachedScanPayload = (value) => {
|
|
1896
|
+
if (!isRecord(value)) return false;
|
|
1897
|
+
return hasValidRequiredCachedScanPayloadFields(value) && hasValidOptionalCachedScanPayloadFields(value);
|
|
1898
|
+
};
|
|
1899
|
+
const decodeCachedScanPayload = (value) => isCachedScanPayload(value) ? value : null;
|
|
1900
|
+
const shouldStoreScanPayload = (payload) => !payload.didLintFail && !payload.didDeadCodeFail && payload.lintPartialFailures.length === 0 && !payload.supplyChainOverlapTimedOut && payload.securityScanFailed !== true;
|
|
1901
|
+
//#endregion
|
|
1902
|
+
//#region src/cli/utils/scan-result-cache.ts
|
|
1903
|
+
const createScanResultCacheInvocationState = () => ({
|
|
1904
|
+
repositoryIdentityByRoot: /* @__PURE__ */ new Map(),
|
|
1905
|
+
firstProjectIdentity: null
|
|
1906
|
+
});
|
|
1907
|
+
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
1908
|
+
"oxlint/package.json",
|
|
1909
|
+
"oxlint-plugin-react-doctor/package.json",
|
|
1910
|
+
"eslint-plugin-react-hooks/package.json"
|
|
1618
1911
|
];
|
|
1619
|
-
const
|
|
1620
|
-
const
|
|
1621
|
-
const extension = path.extname(filePath).toLowerCase();
|
|
1622
|
-
if (extension === ".tsx") return ts.ScriptKind.TSX;
|
|
1623
|
-
if (extension === ".jsx") return ts.ScriptKind.JSX;
|
|
1624
|
-
if (extension === ".js" || extension === ".mjs" || extension === ".cjs") return ts.ScriptKind.JS;
|
|
1625
|
-
return ts.ScriptKind.TS;
|
|
1626
|
-
};
|
|
1627
|
-
const resolveSafeSourcePath = (rootDirectory, filePath) => {
|
|
1912
|
+
const bundledRequire = createRequire(import.meta.url);
|
|
1913
|
+
const RULE_PLUGIN_CONTENT_FINGERPRINT = (() => {
|
|
1628
1914
|
try {
|
|
1629
|
-
|
|
1630
|
-
const absolutePath = path.resolve(resolvedRootDirectory, filePath);
|
|
1631
|
-
const relativePath = path.relative(resolvedRootDirectory, absolutePath);
|
|
1632
|
-
if (relativePath.length === 0 || relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) return null;
|
|
1633
|
-
const stats = fs.lstatSync(absolutePath);
|
|
1634
|
-
if (!stats.isFile() || stats.isSymbolicLink()) return null;
|
|
1635
|
-
const realPath = fs.realpathSync(absolutePath);
|
|
1636
|
-
const realRelativePath = path.relative(resolvedRootDirectory, realPath);
|
|
1637
|
-
return realRelativePath !== ".." && !realRelativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(realRelativePath) ? realPath : null;
|
|
1915
|
+
return `oxlint-plugin-react-doctor#fingerprint=${hashFileContents(bundledRequire.resolve("oxlint-plugin-react-doctor")) ?? "unreadable"}`;
|
|
1638
1916
|
} catch {
|
|
1639
|
-
return
|
|
1917
|
+
return "oxlint-plugin-react-doctor#fingerprint=unresolved";
|
|
1640
1918
|
}
|
|
1641
|
-
};
|
|
1642
|
-
const
|
|
1643
|
-
|
|
1644
|
-
if (
|
|
1645
|
-
const
|
|
1646
|
-
if (
|
|
1647
|
-
|
|
1648
|
-
|
|
1919
|
+
})();
|
|
1920
|
+
const normalizeForStableJson = (value) => {
|
|
1921
|
+
if (value === null) return null;
|
|
1922
|
+
if (value === void 0) return void 0;
|
|
1923
|
+
const valueType = typeof value;
|
|
1924
|
+
if (valueType === "string" || valueType === "number" || valueType === "boolean") return value;
|
|
1925
|
+
if (valueType === "bigint" || valueType === "function" || valueType === "symbol") throw new Error("Unsupported cache key value");
|
|
1926
|
+
if (Array.isArray(value)) return value.map((item) => {
|
|
1927
|
+
const normalized = normalizeForStableJson(item);
|
|
1928
|
+
return normalized === void 0 ? null : normalized;
|
|
1929
|
+
});
|
|
1930
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1931
|
+
if (prototype !== Object.prototype && prototype !== null) throw new Error("Unsupported cache key object");
|
|
1932
|
+
if (!isRecord(value)) throw new Error("Unsupported cache key object");
|
|
1933
|
+
const normalizedRecord = {};
|
|
1934
|
+
for (const key of Object.keys(value).sort()) {
|
|
1935
|
+
const normalized = normalizeForStableJson(value[key]);
|
|
1936
|
+
if (normalized !== void 0) normalizedRecord[key] = normalized;
|
|
1649
1937
|
}
|
|
1938
|
+
return normalizedRecord;
|
|
1939
|
+
};
|
|
1940
|
+
const stringifyStableJson = (value) => {
|
|
1650
1941
|
try {
|
|
1651
|
-
|
|
1652
|
-
const record = {
|
|
1653
|
-
absolutePath,
|
|
1654
|
-
filePath: normalizedFilePath,
|
|
1655
|
-
sourceFile: ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
1656
|
-
sourceText
|
|
1657
|
-
};
|
|
1658
|
-
state.recordByFilePath.set(normalizedFilePath, record);
|
|
1659
|
-
return record;
|
|
1942
|
+
return JSON.stringify(normalizeForStableJson(value));
|
|
1660
1943
|
} catch {
|
|
1661
|
-
state.recordByFilePath.set(normalizedFilePath, null);
|
|
1662
1944
|
return null;
|
|
1663
1945
|
}
|
|
1664
1946
|
};
|
|
1665
|
-
const
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
return currentExpression;
|
|
1677
|
-
};
|
|
1678
|
-
const getCalleeName = (expression) => {
|
|
1679
|
-
const unwrappedExpression = unwrapExpression(expression);
|
|
1680
|
-
if (ts.isIdentifier(unwrappedExpression)) return unwrappedExpression.text;
|
|
1681
|
-
if (ts.isPropertyAccessExpression(unwrappedExpression)) return unwrappedExpression.name.text;
|
|
1682
|
-
return null;
|
|
1683
|
-
};
|
|
1684
|
-
const isComponentWrapperCall = (node) => {
|
|
1685
|
-
const wrapperName = getCalleeName(node.expression);
|
|
1686
|
-
return wrapperName !== null && COMPONENT_WRAPPER_NAMES.has(wrapperName);
|
|
1687
|
-
};
|
|
1688
|
-
const getFunctionName = (node) => {
|
|
1689
|
-
if (ts.isFunctionDeclaration(node) && node.name !== void 0) return node.name.text;
|
|
1690
|
-
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
1691
|
-
let componentExpression = node;
|
|
1692
|
-
while (true) {
|
|
1693
|
-
const parent = componentExpression.parent;
|
|
1694
|
-
if (ts.isParenthesizedExpression(parent) || ts.isAsExpression(parent) || ts.isTypeAssertionExpression(parent) || ts.isNonNullExpression(parent) || ts.isSatisfiesExpression(parent)) {
|
|
1695
|
-
componentExpression = parent;
|
|
1696
|
-
continue;
|
|
1697
|
-
}
|
|
1698
|
-
if (ts.isCallExpression(parent) && parent.arguments[0] === componentExpression && isComponentWrapperCall(parent)) {
|
|
1699
|
-
componentExpression = parent;
|
|
1700
|
-
continue;
|
|
1701
|
-
}
|
|
1702
|
-
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) return parent.name.text;
|
|
1703
|
-
return node.name?.text ?? null;
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
return null;
|
|
1707
|
-
};
|
|
1708
|
-
const isComponentFunction = (node) => ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node);
|
|
1709
|
-
const isDefaultExportedComponent = (record, node, componentName) => {
|
|
1710
|
-
if (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword)) return true;
|
|
1711
|
-
return record.sourceFile.statements.some((statement) => ts.isExportAssignment(statement) && ts.isIdentifier(statement.expression) && statement.expression.text === componentName);
|
|
1712
|
-
};
|
|
1713
|
-
const findEnclosingComponent = (record, diagnostic) => {
|
|
1714
|
-
if (!Number.isInteger(diagnostic.line) || diagnostic.line < 1) return null;
|
|
1715
|
-
const lineIndex = diagnostic.line - 1;
|
|
1716
|
-
const lineStarts = record.sourceFile.getLineStarts();
|
|
1717
|
-
const lineStart = lineStarts[lineIndex];
|
|
1718
|
-
if (lineStart === void 0) return null;
|
|
1719
|
-
const nextLineStart = lineStarts[lineIndex + 1] ?? record.sourceFile.end;
|
|
1720
|
-
const lineEnd = Math.max(lineStart, nextLineStart - 1);
|
|
1721
|
-
const columnIndex = Number.isInteger(diagnostic.column) ? Math.max(0, diagnostic.column - 1) : 0;
|
|
1722
|
-
const diagnosticPosition = Math.min(lineStart + columnIndex, lineEnd);
|
|
1723
|
-
let component = null;
|
|
1724
|
-
let componentWidth = Number.POSITIVE_INFINITY;
|
|
1725
|
-
const visit = (node) => {
|
|
1726
|
-
if (diagnosticPosition < node.getStart(record.sourceFile) || diagnosticPosition > node.end) return;
|
|
1727
|
-
if (isComponentFunction(node)) {
|
|
1728
|
-
const name = getFunctionName(node);
|
|
1729
|
-
const width = node.end - node.getStart(record.sourceFile);
|
|
1730
|
-
if (name !== null && /^[A-Z]/.test(name) && width < componentWidth) {
|
|
1731
|
-
component = {
|
|
1732
|
-
isDefaultExport: isDefaultExportedComponent(record, node, name),
|
|
1733
|
-
name,
|
|
1734
|
-
node,
|
|
1735
|
-
record
|
|
1736
|
-
};
|
|
1737
|
-
componentWidth = width;
|
|
1738
|
-
}
|
|
1947
|
+
const hashString = (value) => crypto.createHash("sha1").update(value).digest("hex");
|
|
1948
|
+
const readHeadSha = (projectDirectory) => runGitAsync(projectDirectory, HEAD_SHA_GIT_ARGUMENTS);
|
|
1949
|
+
const parseWorktreeStatusRecords = (statusOutput) => {
|
|
1950
|
+
const entries = [];
|
|
1951
|
+
const tokens = statusOutput.split("\0");
|
|
1952
|
+
for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
|
|
1953
|
+
let record = tokens[tokenIndex];
|
|
1954
|
+
if (record.length === 0) continue;
|
|
1955
|
+
if (record.length < 3 || record[2] !== " ") {
|
|
1956
|
+
if (tokenIndex !== 0) return null;
|
|
1957
|
+
record = ` ${record}`;
|
|
1739
1958
|
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
const
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
...sourcePathWithoutExtension === unresolvedPath ? SOURCE_EXTENSIONS.map((extension) => path.join(unresolvedPath, `index${extension}`)) : []
|
|
1754
|
-
].includes(componentRecord.absolutePath);
|
|
1755
|
-
};
|
|
1756
|
-
const getComponentTagNames = (record, component) => {
|
|
1757
|
-
if (record.absolutePath === component.record.absolutePath) return new Set([component.name]);
|
|
1758
|
-
const tagNames = /* @__PURE__ */ new Set();
|
|
1759
|
-
for (const statement of record.sourceFile.statements) {
|
|
1760
|
-
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) || !moduleSpecifierTargetsComponent(record, statement.moduleSpecifier.text, component.record)) continue;
|
|
1761
|
-
const importClause = statement.importClause;
|
|
1762
|
-
if (component.isDefaultExport && importClause?.name !== void 0) tagNames.add(importClause.name.text);
|
|
1763
|
-
const namedBindings = importClause?.namedBindings;
|
|
1764
|
-
if (namedBindings !== void 0 && ts.isNamedImports(namedBindings)) {
|
|
1765
|
-
for (const element of namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === component.name) tagNames.add(element.name.text);
|
|
1959
|
+
if (record.length < 4 || record[2] !== " ") return null;
|
|
1960
|
+
const statusCode = record.slice(0, 2);
|
|
1961
|
+
const recordPath = record.slice(3);
|
|
1962
|
+
if (statusCode.includes("R") || statusCode.includes("C")) {
|
|
1963
|
+
tokenIndex += 1;
|
|
1964
|
+
const originPath = tokens[tokenIndex];
|
|
1965
|
+
if (originPath === void 0 || originPath.length === 0) return null;
|
|
1966
|
+
entries.push({
|
|
1967
|
+
statusCode,
|
|
1968
|
+
path: recordPath,
|
|
1969
|
+
originPath
|
|
1970
|
+
});
|
|
1971
|
+
continue;
|
|
1766
1972
|
}
|
|
1973
|
+
entries.push({
|
|
1974
|
+
statusCode,
|
|
1975
|
+
path: recordPath
|
|
1976
|
+
});
|
|
1767
1977
|
}
|
|
1768
|
-
return
|
|
1769
|
-
};
|
|
1770
|
-
const getTransparentCall = (functionNode) => {
|
|
1771
|
-
if (functionNode.body === void 0) return null;
|
|
1772
|
-
if (!ts.isBlock(functionNode.body)) {
|
|
1773
|
-
const expression = unwrapExpression(functionNode.body);
|
|
1774
|
-
return ts.isCallExpression(expression) ? expression : null;
|
|
1775
|
-
}
|
|
1776
|
-
if (functionNode.body.statements.length !== 1) return null;
|
|
1777
|
-
const statement = functionNode.body.statements[0];
|
|
1778
|
-
if (statement === void 0) return null;
|
|
1779
|
-
const expression = ts.isExpressionStatement(statement) || ts.isReturnStatement(statement) ? statement.expression : void 0;
|
|
1780
|
-
if (expression === void 0) return null;
|
|
1781
|
-
const unwrappedExpression = unwrapExpression(expression);
|
|
1782
|
-
return ts.isCallExpression(unwrappedExpression) ? unwrappedExpression : null;
|
|
1783
|
-
};
|
|
1784
|
-
const resolveTransparentTarget = (functionNode) => {
|
|
1785
|
-
const call = getTransparentCall(functionNode);
|
|
1786
|
-
if (call === null) return null;
|
|
1787
|
-
const target = unwrapExpression(call.expression);
|
|
1788
|
-
if (!ts.isIdentifier(target) || call.arguments.length !== functionNode.parameters.length) return null;
|
|
1789
|
-
for (const [parameterIndex, parameter] of functionNode.parameters.entries()) {
|
|
1790
|
-
const argument = call.arguments[parameterIndex];
|
|
1791
|
-
const unwrappedArgument = argument === void 0 ? null : unwrapExpression(argument);
|
|
1792
|
-
if (!ts.isIdentifier(parameter.name) || unwrappedArgument === null || !ts.isIdentifier(unwrappedArgument) || unwrappedArgument.text !== parameter.name.text) return null;
|
|
1793
|
-
}
|
|
1794
|
-
return target.text;
|
|
1978
|
+
return entries;
|
|
1795
1979
|
};
|
|
1796
|
-
const
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
const callback = initializer.arguments[0];
|
|
1803
|
-
if (callback !== void 0 && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) return callback;
|
|
1980
|
+
const buildDirtyPathContentFingerprint = (absolutePath, statusCode) => {
|
|
1981
|
+
let stats;
|
|
1982
|
+
try {
|
|
1983
|
+
stats = fs.lstatSync(absolutePath);
|
|
1984
|
+
} catch {
|
|
1985
|
+
return statusCode.includes("D") ? "deleted" : null;
|
|
1804
1986
|
}
|
|
1805
|
-
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
if (
|
|
1811
|
-
return null;
|
|
1987
|
+
if (stats.isSymbolicLink()) try {
|
|
1988
|
+
return `link:${fs.readlinkSync(absolutePath)}`;
|
|
1989
|
+
} catch {
|
|
1990
|
+
return null;
|
|
1991
|
+
}
|
|
1992
|
+
if (!stats.isFile()) return null;
|
|
1993
|
+
if (stats.size > 10485760) return null;
|
|
1994
|
+
return hashFileContents(absolutePath);
|
|
1812
1995
|
};
|
|
1813
|
-
const
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1996
|
+
const buildWorktreeFingerprint = (repositoryRoot, statusOutput) => {
|
|
1997
|
+
if (statusOutput === null) return null;
|
|
1998
|
+
if (statusOutput.length === 0) return [];
|
|
1999
|
+
const statusEntries = parseWorktreeStatusRecords(statusOutput);
|
|
2000
|
+
if (statusEntries === null) return null;
|
|
2001
|
+
if (statusEntries.length > 300) return null;
|
|
2002
|
+
const dirtyEntries = [];
|
|
2003
|
+
for (const statusEntry of statusEntries) {
|
|
2004
|
+
const contentFingerprint = buildDirtyPathContentFingerprint(path.join(repositoryRoot, statusEntry.path), statusEntry.statusCode);
|
|
2005
|
+
if (contentFingerprint === null) return null;
|
|
2006
|
+
dirtyEntries.push({
|
|
2007
|
+
...statusEntry,
|
|
2008
|
+
contentFingerprint
|
|
2009
|
+
});
|
|
1819
2010
|
}
|
|
1820
|
-
return
|
|
2011
|
+
return dirtyEntries.sort((firstEntry, secondEntry) => {
|
|
2012
|
+
const firstSortKey = `${firstEntry.path}${firstEntry.statusCode}`;
|
|
2013
|
+
const secondSortKey = `${secondEntry.path}${secondEntry.statusCode}`;
|
|
2014
|
+
return firstSortKey < secondSortKey ? -1 : firstSortKey > secondSortKey ? 1 : 0;
|
|
2015
|
+
});
|
|
1821
2016
|
};
|
|
1822
|
-
const
|
|
1823
|
-
const
|
|
1824
|
-
const
|
|
1825
|
-
const
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
const isVisibleScope = usagePosition >= scope.getStart(record.sourceFile) && usagePosition <= scope.end;
|
|
1829
|
-
const isVisibleDeclaration = ts.isFunctionDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isImportClause(node) || ts.isImportSpecifier(node) || ts.isNamespaceImport(node) || node.getStart(record.sourceFile) < usagePosition;
|
|
1830
|
-
if (isVisibleScope && isVisibleDeclaration) candidates.push({
|
|
1831
|
-
declaration: node,
|
|
1832
|
-
scope
|
|
1833
|
-
});
|
|
1834
|
-
}
|
|
1835
|
-
ts.forEachChild(node, visit);
|
|
2017
|
+
const resolveIdentityFromProbes = async (repositoryRoot, statusPromise, headShaPromise, invocationState) => {
|
|
2018
|
+
const [statusOutput, headSha] = await Promise.all([statusPromise, headShaPromise]);
|
|
2019
|
+
const worktreeFingerprint = buildWorktreeFingerprint(repositoryRoot, statusOutput);
|
|
2020
|
+
const identity = worktreeFingerprint === null || headSha === null ? null : {
|
|
2021
|
+
headSha,
|
|
2022
|
+
worktreeFingerprint
|
|
1836
2023
|
};
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
let nearestScopeWidth = Number.POSITIVE_INFINITY;
|
|
1840
|
-
let isNearestScopeAmbiguous = false;
|
|
1841
|
-
for (const candidate of candidates) {
|
|
1842
|
-
const scopeWidth = candidate.scope.end - candidate.scope.getStart(record.sourceFile);
|
|
1843
|
-
if (scopeWidth < nearestScopeWidth) {
|
|
1844
|
-
nearestCandidate = candidate;
|
|
1845
|
-
nearestScopeWidth = scopeWidth;
|
|
1846
|
-
isNearestScopeAmbiguous = false;
|
|
1847
|
-
} else if (scopeWidth === nearestScopeWidth) isNearestScopeAmbiguous = true;
|
|
1848
|
-
}
|
|
1849
|
-
if (isNearestScopeAmbiguous) return null;
|
|
1850
|
-
const declaration = nearestCandidate?.declaration;
|
|
1851
|
-
return declaration !== void 0 && (ts.isVariableDeclaration(declaration) || ts.isFunctionDeclaration(declaration)) ? declaration : null;
|
|
2024
|
+
invocationState.repositoryIdentityByRoot.set(repositoryRoot, identity);
|
|
2025
|
+
return identity;
|
|
1852
2026
|
};
|
|
1853
|
-
const
|
|
1854
|
-
const
|
|
1855
|
-
if (
|
|
1856
|
-
const
|
|
1857
|
-
|
|
1858
|
-
return
|
|
2027
|
+
const resolveSiblingIdentity = async (projectDirectory, invocationState, repositoryRootPromise, firstProjectIdentity) => {
|
|
2028
|
+
const [repositoryRoot] = await Promise.all([repositoryRootPromise, firstProjectIdentity]);
|
|
2029
|
+
if (repositoryRoot === null) return null;
|
|
2030
|
+
const cachedIdentity = invocationState.repositoryIdentityByRoot.get(repositoryRoot);
|
|
2031
|
+
if (cachedIdentity !== void 0) return cachedIdentity;
|
|
2032
|
+
return resolveIdentityFromProbes(repositoryRoot, runGitAsync(projectDirectory, WORKTREE_STATUS_GIT_ARGUMENTS), readHeadSha(repositoryRoot), invocationState);
|
|
1859
2033
|
};
|
|
1860
|
-
const
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
if (initializer.expression === void 0) return null;
|
|
1867
|
-
const expression = unwrapExpression(initializer.expression);
|
|
1868
|
-
return ts.isIdentifier(expression) ? expression : null;
|
|
1869
|
-
}
|
|
1870
|
-
return null;
|
|
2034
|
+
const resolveRepositoryCacheIdentity = (projectDirectory, invocationState, repositoryRootPromise) => {
|
|
2035
|
+
if (invocationState.firstProjectIdentity !== null) return resolveSiblingIdentity(projectDirectory, invocationState, repositoryRootPromise, invocationState.firstProjectIdentity);
|
|
2036
|
+
const speculativeStatus = runGitAsync(projectDirectory, WORKTREE_STATUS_GIT_ARGUMENTS);
|
|
2037
|
+
const speculativeHeadSha = readHeadSha(projectDirectory);
|
|
2038
|
+
invocationState.firstProjectIdentity = repositoryRootPromise.then((repositoryRoot) => repositoryRoot === null ? null : resolveIdentityFromProbes(repositoryRoot, speculativeStatus, speculativeHeadSha, invocationState));
|
|
2039
|
+
return invocationState.firstProjectIdentity;
|
|
1871
2040
|
};
|
|
1872
|
-
const
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
let callsiteCount = 0;
|
|
1879
|
-
for (const record of listSourceRecords(rootDirectory, state)) {
|
|
1880
|
-
const tagNames = getComponentTagNames(record, component);
|
|
1881
|
-
if (tagNames.size === 0) continue;
|
|
1882
|
-
const visit = (node) => {
|
|
1883
|
-
if (record === component.record && node === component.node) return;
|
|
1884
|
-
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
1885
|
-
const tagName = getJsxTagName(node);
|
|
1886
|
-
if (tagName !== null && tagNames.has(tagName)) {
|
|
1887
|
-
callsiteCount += 1;
|
|
1888
|
-
for (const propName of propNames) {
|
|
1889
|
-
const binding = getHandlerBinding(node, propName);
|
|
1890
|
-
if (binding === null) {
|
|
1891
|
-
unresolvedPropNames.add(propName);
|
|
1892
|
-
continue;
|
|
1893
|
-
}
|
|
1894
|
-
const bindings = bindingsByPropName.get(propName) ?? /* @__PURE__ */ new Set();
|
|
1895
|
-
bindings.add(resolveHandlerBinding(record, binding));
|
|
1896
|
-
bindingsByPropName.set(propName, bindings);
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
ts.forEachChild(node, visit);
|
|
1901
|
-
};
|
|
1902
|
-
visit(record.sourceFile);
|
|
1903
|
-
}
|
|
1904
|
-
const aliases = /* @__PURE__ */ new Map();
|
|
1905
|
-
if (callsiteCount > 0) for (const [propName, bindings] of bindingsByPropName) {
|
|
1906
|
-
if (bindings.size !== 1 || unresolvedPropNames.has(propName)) continue;
|
|
1907
|
-
const [bindingName] = bindings;
|
|
1908
|
-
if (bindingName !== void 0) aliases.set(propName, bindingName);
|
|
2041
|
+
const DOTENV_FILE_NAME_PATTERN = /^\.env(\.|$)/;
|
|
2042
|
+
const resolveDotenvFingerprint = (projectDirectory) => {
|
|
2043
|
+
try {
|
|
2044
|
+
return fs.readdirSync(projectDirectory).filter((entryName) => DOTENV_FILE_NAME_PATTERN.test(entryName)).sort().map((entryName) => `${entryName}=${hashFileContents(path.join(projectDirectory, entryName)) ?? "unreadable"}`);
|
|
2045
|
+
} catch {
|
|
2046
|
+
return [];
|
|
1909
2047
|
}
|
|
1910
|
-
state.aliasesByComponent.set(cacheKey, aliases);
|
|
1911
|
-
return aliases;
|
|
1912
|
-
};
|
|
1913
|
-
const normalizeForwardedHandlers = (evidence, aliases) => {
|
|
1914
|
-
let normalizedEvidence = evidence;
|
|
1915
|
-
for (const [propName, bindingName] of aliases) normalizedEvidence = normalizedEvidence.replace(new RegExp(`\\{\\s*${propName}\\s*\\}`, "g"), `{${bindingName}}`).replace(new RegExp(`=>\\s*${propName}\\s*\\(`, "g"), `=> ${bindingName}(`);
|
|
1916
|
-
return normalizedEvidence;
|
|
1917
|
-
};
|
|
1918
|
-
const createDiagnosticEvidenceReader = (rootDirectory, options = {}) => {
|
|
1919
|
-
const state = {
|
|
1920
|
-
aliasesByComponent: /* @__PURE__ */ new Map(),
|
|
1921
|
-
recordByFilePath: /* @__PURE__ */ new Map(),
|
|
1922
|
-
sourceRecords: null
|
|
1923
|
-
};
|
|
1924
|
-
return (diagnostic) => {
|
|
1925
|
-
const record = readSourceRecord(rootDirectory, diagnostic.filePath, state);
|
|
1926
|
-
if (record === null || !Number.isInteger(diagnostic.line)) return null;
|
|
1927
|
-
const sourceLines = record.sourceText.split(/\r?\n/);
|
|
1928
|
-
const startLineIndex = Math.max(0, diagnostic.line - 1);
|
|
1929
|
-
const endLineIndex = Math.max(startLineIndex, (diagnostic.endLine ?? diagnostic.line) - 1);
|
|
1930
|
-
const evidence = sourceLines.slice(startLineIndex, endLineIndex + 1).join("\n");
|
|
1931
|
-
if (!options.resolveForwardedHandlers) return evidence;
|
|
1932
|
-
const propNames = new Set(evidence.match(/\bon[A-Z]\w*\b/g) ?? []);
|
|
1933
|
-
if (propNames.size === 0) return evidence;
|
|
1934
|
-
const component = findEnclosingComponent(record, diagnostic);
|
|
1935
|
-
if (component === null) return evidence;
|
|
1936
|
-
return normalizeForwardedHandlers(evidence, getForwardedHandlerAliases(rootDirectory, component, propNames, state));
|
|
1937
|
-
};
|
|
1938
2048
|
};
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
const
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
2049
|
+
const isGitIdentityTrustworthy = async (projectDirectory) => {
|
|
2050
|
+
const output = await runGitAsync(projectDirectory, TRACKED_FILE_FLAGS_GIT_ARGUMENTS);
|
|
2051
|
+
if (output === null) return false;
|
|
2052
|
+
const entryLines = output.split("\n").filter((line) => line.length > 0);
|
|
2053
|
+
return entryLines.length > 0 && entryLines.every((line) => line[0] === "H");
|
|
2054
|
+
};
|
|
2055
|
+
const resolveCacheFilePath = (projectDirectory) => path.join(resolveReactDoctorCacheDir(projectDirectory), SCAN_RESULT_CACHE_FILENAME);
|
|
2056
|
+
const readPersistedCache = (cacheFilePath) => {
|
|
2057
|
+
try {
|
|
2058
|
+
const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
|
|
2059
|
+
if (!isRecord(parsed) || parsed.version !== 7) return {
|
|
2060
|
+
version: 7,
|
|
2061
|
+
entries: []
|
|
2062
|
+
};
|
|
2063
|
+
if (!Array.isArray(parsed.entries)) return {
|
|
2064
|
+
version: 7,
|
|
2065
|
+
entries: []
|
|
2066
|
+
};
|
|
2067
|
+
const entries = [];
|
|
2068
|
+
for (const entry of parsed.entries) {
|
|
2069
|
+
if (!isRecord(entry) || typeof entry.key !== "string" || typeof entry.createdAtMs !== "number") continue;
|
|
2070
|
+
const payload = decodeCachedScanPayload(entry.payload);
|
|
2071
|
+
if (payload === null) continue;
|
|
2072
|
+
entries.push({
|
|
2073
|
+
key: entry.key,
|
|
2074
|
+
createdAtMs: entry.createdAtMs,
|
|
2075
|
+
payload
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
return {
|
|
2079
|
+
version: 7,
|
|
2080
|
+
entries
|
|
2081
|
+
};
|
|
2082
|
+
} catch {
|
|
2083
|
+
return {
|
|
2084
|
+
version: 7,
|
|
2085
|
+
entries: []
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
1957
2088
|
};
|
|
1958
|
-
//#endregion
|
|
1959
|
-
//#region src/cli/utils/is-materializable-git-source.ts
|
|
1960
|
-
const GIT_LFS_POINTER_HEADER = "version https://git-lfs.github.com/spec/v1";
|
|
1961
|
-
const isMaterializableGitSource = (content) => !content.includes("\0") && !content.startsWith(GIT_LFS_POINTER_HEADER);
|
|
1962
|
-
//#endregion
|
|
1963
|
-
//#region src/cli/utils/materialize-baseline-files.ts
|
|
1964
|
-
/**
|
|
1965
|
-
* Materializes the base side of the `ref` → worktree diff into
|
|
1966
|
-
* `tempDirectory`, mirroring the project layout plus head's config files so
|
|
1967
|
-
* both sides lint under the same rules. Git rename heuristics are disabled by
|
|
1968
|
-
* the diff planner, so an old path is retained as a base deletion while its
|
|
1969
|
-
* new path is a head addition. Missing head-only additions are expected;
|
|
1970
|
-
* missing paths that the plan says must exist at base make `isComplete` false.
|
|
1971
|
-
*/
|
|
1972
|
-
const materializeBaselineFiles = (input) => runPromise(gen(function* () {
|
|
1973
|
-
const git = yield* Git;
|
|
1974
|
-
const diffPlan = yield* git.baselineDiffPlan({
|
|
1975
|
-
directory: input.directory,
|
|
1976
|
-
ref: input.ref
|
|
1977
|
-
});
|
|
1978
|
-
const baseFiles = input.baseFiles ?? diffPlan?.baseFiles;
|
|
1979
|
-
const headFiles = input.headFiles ?? diffPlan?.headFiles;
|
|
1980
|
-
const untrackedFiles = diffPlan?.untrackedFiles ?? [];
|
|
1981
|
-
if (baseFiles === void 0 || headFiles === void 0) return null;
|
|
1982
|
-
const files = [...new Set([...input.files, ...baseFiles])];
|
|
1983
|
-
const tree = yield* materializeSourceTree({
|
|
1984
|
-
directory: input.directory,
|
|
1985
|
-
files,
|
|
1986
|
-
tempDirectory: input.tempDirectory,
|
|
1987
|
-
readContent: (relativePath) => git.showRefContent({
|
|
1988
|
-
directory: input.directory,
|
|
1989
|
-
ref: input.ref,
|
|
1990
|
-
relativePath,
|
|
1991
|
-
options: { maxBufferBytes: GIT_SHOW_MAX_BUFFER_BYTES }
|
|
1992
|
-
}).pipe(map((content) => content !== null && isMaterializableGitSource(content) ? content : null))
|
|
1993
|
-
});
|
|
1994
|
-
const materializedFiles = new Set(tree.materializedFiles);
|
|
1995
|
-
return {
|
|
1996
|
-
...tree,
|
|
1997
|
-
baseFiles,
|
|
1998
|
-
headFiles,
|
|
1999
|
-
isComplete: baseFiles.every((filePath) => materializedFiles.has(filePath)),
|
|
2000
|
-
untrackedFiles
|
|
2001
|
-
};
|
|
2002
|
-
}).pipe(provide(Git.layerNode)));
|
|
2003
2089
|
/**
|
|
2004
|
-
*
|
|
2005
|
-
*
|
|
2006
|
-
*
|
|
2090
|
+
* The global cache off-switch (`REACT_DOCTOR_NO_CACHE`), which disables every
|
|
2091
|
+
* cache subsystem: this whole-repo scan cache plus core's per-file lint,
|
|
2092
|
+
* sidecar caches (their `Context.Reference` defaults read the
|
|
2093
|
+
* same variable). Exported for the wide event's `cache.temperature`
|
|
2094
|
+
* derivation, which reports `"disabled"` instead of `"cold"` when the switch
|
|
2095
|
+
* is on. Granular knobs (`REACT_DOCTOR_NO_FILE_CACHE`, …) are deliberately
|
|
2096
|
+
* not consulted — they leave the other subsystems live.
|
|
2007
2097
|
*/
|
|
2008
|
-
const
|
|
2098
|
+
const resolveProjectIdentity = (projectDirectory) => {
|
|
2009
2099
|
try {
|
|
2010
|
-
return
|
|
2011
|
-
return yield* (yield* Git).mergeBase({
|
|
2012
|
-
directory,
|
|
2013
|
-
ref
|
|
2014
|
-
});
|
|
2015
|
-
}).pipe(provide(Git.layerNode)));
|
|
2100
|
+
return fs.realpathSync.native(projectDirectory);
|
|
2016
2101
|
} catch {
|
|
2017
|
-
return
|
|
2102
|
+
return path.resolve(projectDirectory);
|
|
2018
2103
|
}
|
|
2019
2104
|
};
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
const
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
rootDirectory: input.directory,
|
|
2027
|
-
relativePaths: input.options.includePaths,
|
|
2028
|
-
excludedDirectories: input.options.excludedProjectDirectories
|
|
2029
|
-
});
|
|
2030
|
-
const baselineBaseFiles = input.baseFiles ? filterPathsOutsideDirectories({
|
|
2031
|
-
rootDirectory: input.directory,
|
|
2032
|
-
relativePaths: input.baseFiles,
|
|
2033
|
-
excludedDirectories: input.options.excludedProjectDirectories
|
|
2034
|
-
}) : void 0;
|
|
2035
|
-
const baselineHeadFiles = input.headFiles ? filterPathsOutsideDirectories({
|
|
2036
|
-
rootDirectory: input.directory,
|
|
2037
|
-
relativePaths: input.headFiles,
|
|
2038
|
-
excludedDirectories: input.options.excludedProjectDirectories
|
|
2039
|
-
}) : void 0;
|
|
2040
|
-
const remainingBaselineBudgetMs = input.deadlineEpochMs === null ? null : remainingDeadlineBudgetMs(input.deadlineEpochMs);
|
|
2041
|
-
if (remainingBaselineBudgetMs === 0) return null;
|
|
2042
|
-
const baselineDeadlineSignal = remainingBaselineBudgetMs === null ? void 0 : AbortSignal.timeout(remainingBaselineBudgetMs);
|
|
2043
|
-
const baselineListingSignal = baselineDeadlineSignal === void 0 ? input.oxlintRuntime.abortSignal : input.oxlintRuntime.abortSignal === void 0 ? baselineDeadlineSignal : AbortSignal.any([input.oxlintRuntime.abortSignal, baselineDeadlineSignal]);
|
|
2044
|
-
let maintainabilitySourceFiles = [];
|
|
2045
|
-
if (input.options.deadCode) try {
|
|
2046
|
-
maintainabilitySourceFiles = (await listSourceFilesCooperative(input.directory, baselineListingSignal)).filter((filePath) => JSX_DUPLICATION_SOURCE_FILE_PATTERN.test(filePath) && classifyFileContext(filePath) === "production");
|
|
2047
|
-
} catch (error) {
|
|
2048
|
-
if (baselineDeadlineSignal?.aborted) return null;
|
|
2049
|
-
throw error;
|
|
2050
|
-
}
|
|
2051
|
-
const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
|
|
2052
|
-
const snapshot = await materializeBaselineFiles({
|
|
2053
|
-
directory: input.directory,
|
|
2054
|
-
ref: input.baselineRef,
|
|
2055
|
-
files: input.options.deadCode ? input.options.includePaths : baselineIncludePaths,
|
|
2056
|
-
baseFiles: input.options.deadCode ? input.baseFiles : baselineBaseFiles,
|
|
2057
|
-
headFiles: input.options.deadCode ? input.headFiles : baselineHeadFiles,
|
|
2058
|
-
tempDirectory: temporaryDirectory
|
|
2059
|
-
}).catch((error) => {
|
|
2060
|
-
rmSync(temporaryDirectory, {
|
|
2061
|
-
recursive: true,
|
|
2062
|
-
force: true
|
|
2063
|
-
});
|
|
2064
|
-
throw error;
|
|
2065
|
-
});
|
|
2066
|
-
if (snapshot === null) {
|
|
2067
|
-
rmSync(temporaryDirectory, {
|
|
2068
|
-
recursive: true,
|
|
2069
|
-
force: true
|
|
2070
|
-
});
|
|
2105
|
+
const resolveManifestContentHash = (projectDirectory) => hashFileContents(getPackageJsonPath(projectDirectory));
|
|
2106
|
+
const fileFingerprint = (filePath) => {
|
|
2107
|
+
try {
|
|
2108
|
+
const stat = fs.statSync(filePath);
|
|
2109
|
+
return `${filePath}:${stat.mtimeMs}:${stat.size}`;
|
|
2110
|
+
} catch {
|
|
2071
2111
|
return null;
|
|
2072
2112
|
}
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath || materializedLintPaths.length === 0,
|
|
2115
|
-
shouldRunDeadCode: input.options.deadCode,
|
|
2116
|
-
shouldRunSupplyChain: input.options.supplyChain,
|
|
2117
|
-
shouldComputeScore: false,
|
|
2118
|
-
shouldShowProgressSpinners: false,
|
|
2119
|
-
oxlintConcurrency: input.oxlintRuntime.concurrency,
|
|
2120
|
-
oxlintSpawnSlots: input.oxlintRuntime.spawnSlots
|
|
2121
|
-
});
|
|
2122
|
-
const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
|
|
2123
|
-
directory: snapshot.tempDirectory,
|
|
2124
|
-
includePaths: baseIncludePaths,
|
|
2125
|
-
maintainabilityFocusPaths,
|
|
2113
|
+
};
|
|
2114
|
+
const resolveScanResultToolchainFingerprint = (nodeBinaryPath) => {
|
|
2115
|
+
const fingerprints = [];
|
|
2116
|
+
if (nodeBinaryPath !== null) fingerprints.push(nodeBinaryPath === process.execPath ? `node=${process.version}` : `node=${fileFingerprint(nodeBinaryPath) ?? "unreadable"}`);
|
|
2117
|
+
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
2118
|
+
const packageJson = bundledRequire(specifier);
|
|
2119
|
+
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
2120
|
+
fingerprints.push(`${specifier}=${version}`);
|
|
2121
|
+
} catch {
|
|
2122
|
+
fingerprints.push(`${specifier}=missing`);
|
|
2123
|
+
}
|
|
2124
|
+
fingerprints.push(RULE_PLUGIN_CONTENT_FINGERPRINT);
|
|
2125
|
+
return fingerprints;
|
|
2126
|
+
};
|
|
2127
|
+
const buildScanResultCacheKey = async (input) => {
|
|
2128
|
+
if (isCacheGloballyDisabled()) return null;
|
|
2129
|
+
const isTrustworthyPromise = isGitIdentityTrustworthy(input.projectDirectory);
|
|
2130
|
+
const repositoryIdentityPromise = resolveRepositoryCacheIdentity(input.projectDirectory, input.invocationState, runGitAsync(input.projectDirectory, REPOSITORY_ROOT_GIT_ARGUMENTS));
|
|
2131
|
+
const [isTrustworthy, repositoryIdentity] = await Promise.all([isTrustworthyPromise, repositoryIdentityPromise]);
|
|
2132
|
+
if (!isTrustworthy || repositoryIdentity === null) return null;
|
|
2133
|
+
if (stringifyStableJson(input.userConfig) === null) return null;
|
|
2134
|
+
const cacheKeyJson = stringifyStableJson({
|
|
2135
|
+
schemaVersion: 7,
|
|
2136
|
+
projectIdentity: resolveProjectIdentity(input.projectDirectory),
|
|
2137
|
+
headSha: repositoryIdentity.headSha,
|
|
2138
|
+
worktreeFingerprint: repositoryIdentity.worktreeFingerprint,
|
|
2139
|
+
dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
|
|
2140
|
+
reactDoctorVersion: input.version,
|
|
2141
|
+
nodeVersion: process.version,
|
|
2142
|
+
toolchainFingerprint: resolveScanResultToolchainFingerprint(input.nodeBinaryPath),
|
|
2143
|
+
configFingerprint: computeConfigFingerprint(input.projectDirectory, input.version),
|
|
2144
|
+
hasConfigOverride: input.hasConfigOverride,
|
|
2145
|
+
configSourceDirectory: input.configSourceDirectory,
|
|
2146
|
+
userConfig: input.userConfig,
|
|
2147
|
+
engineOptions: {
|
|
2148
|
+
lint: input.options.lint,
|
|
2149
|
+
deadCode: input.options.deadCode,
|
|
2150
|
+
supplyChain: input.options.supplyChain,
|
|
2151
|
+
includePaths: [...input.options.includePaths].sort(),
|
|
2152
|
+
excludedProjectDirectories: [...input.options.excludedProjectDirectories ?? []].sort(),
|
|
2153
|
+
retainExcludedProjectDeadCodeDiagnostics: input.options.retainExcludedProjectDeadCodeDiagnostics ?? false,
|
|
2126
2154
|
customRulesOnly: input.options.customRulesOnly,
|
|
2127
2155
|
respectInlineDisables: input.options.respectInlineDisables,
|
|
2128
2156
|
warnings: input.options.warnings,
|
|
2129
2157
|
adoptExistingLintConfig: input.options.adoptExistingLintConfig,
|
|
2130
|
-
ignoredTags: input.options.ignoredTags,
|
|
2131
|
-
includedTags: input.options.includedTags,
|
|
2158
|
+
ignoredTags: [...input.options.ignoredTags].sort(),
|
|
2159
|
+
includedTags: [...input.options.includedTags].sort(),
|
|
2132
2160
|
includeTagDefaults: input.options.includeTagDefaults,
|
|
2133
|
-
|
|
2134
|
-
|
|
2161
|
+
concurrency: input.options.concurrency,
|
|
2162
|
+
lintBatchOrdering: resolveLintBatchOrdering(),
|
|
2163
|
+
baselineRef: input.options.baseline?.ref,
|
|
2164
|
+
changedLineRanges: input.options.changedLineRanges ?? void 0,
|
|
2165
|
+
noScore: input.options.noScore,
|
|
2135
2166
|
isCi: input.options.isCi,
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
baseDiagnostics: baseOutput.diagnostics,
|
|
2155
|
-
readHeadLine: createSourceLineReader(input.directory),
|
|
2156
|
-
readBaseLine: createSourceLineReader(snapshot.tempDirectory),
|
|
2157
|
-
readHeadEvidence: createDiagnosticEvidenceReader(input.directory, { resolveForwardedHandlers: true }),
|
|
2158
|
-
readBaseEvidence: createDiagnosticEvidenceReader(snapshot.tempDirectory)
|
|
2167
|
+
suppressRendering: input.options.suppressRendering,
|
|
2168
|
+
supplyChainManifestChanged: input.options.supplyChainManifestChanged
|
|
2169
|
+
}
|
|
2170
|
+
});
|
|
2171
|
+
return cacheKeyJson === null ? null : hashString(cacheKeyJson);
|
|
2172
|
+
};
|
|
2173
|
+
const createScanResultCache = (projectDirectory) => {
|
|
2174
|
+
const cacheFilePath = resolveCacheFilePath(projectDirectory);
|
|
2175
|
+
const persistedCache = readPersistedCache(cacheFilePath);
|
|
2176
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2177
|
+
for (const entry of persistedCache.entries) entries.set(entry.key, entry);
|
|
2178
|
+
const persist = () => {
|
|
2179
|
+
const mergedEntries = /* @__PURE__ */ new Map();
|
|
2180
|
+
for (const entry of readPersistedCache(cacheFilePath).entries) mergedEntries.set(entry.key, entry);
|
|
2181
|
+
for (const entry of entries.values()) mergedEntries.set(entry.key, entry);
|
|
2182
|
+
atomicWriteJson(cacheFilePath, {
|
|
2183
|
+
version: 7,
|
|
2184
|
+
entries: [...mergedEntries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
|
|
2159
2185
|
});
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2186
|
+
};
|
|
2187
|
+
return {
|
|
2188
|
+
lookup: (key) => {
|
|
2189
|
+
const payload = entries.get(key)?.payload;
|
|
2190
|
+
if (payload === void 0) return null;
|
|
2191
|
+
return resolveProjectIdentity(payload.directory) === resolveProjectIdentity(projectDirectory) && (payload.manifestContentHash ?? null) === resolveManifestContentHash(projectDirectory) ? payload : null;
|
|
2192
|
+
},
|
|
2193
|
+
store: (key, payload) => {
|
|
2194
|
+
entries.set(key, {
|
|
2195
|
+
key,
|
|
2196
|
+
createdAtMs: Date.now(),
|
|
2197
|
+
payload: {
|
|
2198
|
+
...payload,
|
|
2199
|
+
manifestContentHash: resolveManifestContentHash(projectDirectory)
|
|
2200
|
+
}
|
|
2201
|
+
});
|
|
2202
|
+
persist();
|
|
2203
|
+
}
|
|
2204
|
+
};
|
|
2172
2205
|
};
|
|
2173
2206
|
//#endregion
|
|
2174
2207
|
//#region src/cli/utils/scan-result-cache-lifecycle.ts
|
|
2175
|
-
const createScanResultCacheLifecycle = (input) => {
|
|
2176
|
-
const cacheKey = buildScanResultCacheKey({
|
|
2208
|
+
const createScanResultCacheLifecycle = async (input) => {
|
|
2209
|
+
const cacheKey = await buildScanResultCacheKey({
|
|
2177
2210
|
projectDirectory: input.directory,
|
|
2178
2211
|
version: VERSION,
|
|
2179
2212
|
nodeBinaryPath: input.resolvedNodeBinaryPath,
|
|
@@ -2240,7 +2273,10 @@ const inspectWithOxlintRuntime = async (directory, inputOptions, oxlintRuntime)
|
|
|
2240
2273
|
userConfig = scanTarget.userConfig;
|
|
2241
2274
|
configSourceDirectory = scanTarget.configSourceDirectory;
|
|
2242
2275
|
}
|
|
2243
|
-
const options = resolveInspectOptions(inputOptions
|
|
2276
|
+
const options = resolveInspectOptions(path$1.resolve(directory) === path$1.resolve(scanDirectory) ? inputOptions : {
|
|
2277
|
+
...inputOptions,
|
|
2278
|
+
precomputedSourceFiles: void 0
|
|
2279
|
+
}, userConfig);
|
|
2244
2280
|
const ownsSpinnerSilence = options.silent && !isConcurrentScan;
|
|
2245
2281
|
const wasSpinnerSilent = isSpinnerSilent();
|
|
2246
2282
|
if (ownsSpinnerSilence) setSpinnerSilent(true);
|
|
@@ -2264,9 +2300,10 @@ const inspectWithOxlintRuntime = async (directory, inputOptions, oxlintRuntime)
|
|
|
2264
2300
|
}
|
|
2265
2301
|
};
|
|
2266
2302
|
const createInvocationInspect = (requestedOxlintConcurrency) => {
|
|
2267
|
-
const concurrency =
|
|
2303
|
+
const concurrency = resolveInvocationOxlintConcurrency(requestedOxlintConcurrency);
|
|
2268
2304
|
const spawnSlots = createOxlintSpawnSlots(concurrency);
|
|
2269
2305
|
const scanResultCacheInvocationState = createScanResultCacheInvocationState();
|
|
2306
|
+
const invocationCaches = createInvocationCaches();
|
|
2270
2307
|
return async (directory, inputOptions = {}) => {
|
|
2271
2308
|
const abortController = new AbortController();
|
|
2272
2309
|
const unregisterAbortController = activeScanAbortRegistry.register(abortController);
|
|
@@ -2275,7 +2312,8 @@ const createInvocationInspect = (requestedOxlintConcurrency) => {
|
|
|
2275
2312
|
concurrency,
|
|
2276
2313
|
spawnSlots,
|
|
2277
2314
|
abortSignal: abortController.signal,
|
|
2278
|
-
scanResultCacheInvocationState
|
|
2315
|
+
scanResultCacheInvocationState,
|
|
2316
|
+
invocationCaches
|
|
2279
2317
|
});
|
|
2280
2318
|
} finally {
|
|
2281
2319
|
unregisterAbortController();
|
|
@@ -2288,7 +2326,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
2288
2326
|
const resolvedNodeBinaryPath = await resolveOxlintNode(options.lint, options.scoreOnly || options.silent);
|
|
2289
2327
|
const lintBindingMissing = options.lint && !resolvedNodeBinaryPath;
|
|
2290
2328
|
await yieldToEventLoop();
|
|
2291
|
-
const scanResultCacheLifecycle = createScanResultCacheLifecycle({
|
|
2329
|
+
const scanResultCacheLifecycle = await createScanResultCacheLifecycle({
|
|
2292
2330
|
directory,
|
|
2293
2331
|
options,
|
|
2294
2332
|
userConfig,
|
|
@@ -2301,6 +2339,11 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
2301
2339
|
});
|
|
2302
2340
|
const cachedResult = scanResultCacheLifecycle.replay();
|
|
2303
2341
|
if (cachedResult !== null) return cachedResult;
|
|
2342
|
+
if (options.lint && resolvedNodeBinaryPath) warmOxlintWorkerPool(resolvedNodeBinaryPath, oxlintRuntime.concurrency);
|
|
2343
|
+
if (options.deadCode && shouldUseMaintainabilityLayer({
|
|
2344
|
+
shouldRunDuplicateJsx: options.deadCode,
|
|
2345
|
+
userConfig
|
|
2346
|
+
})) warmDuplicateJsxWorker();
|
|
2304
2347
|
const shouldShowProgressSpinners = !options.isCiOrCodingAgentEnvironment && !options.silent && !options.scoreOnly && !options.suppressRendering && options.lint && Boolean(resolvedNodeBinaryPath);
|
|
2305
2348
|
const layers = buildRuntimeLayers({
|
|
2306
2349
|
directory,
|
|
@@ -2314,12 +2357,14 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
2314
2357
|
shouldShowProgressSpinners,
|
|
2315
2358
|
oxlintConcurrency: oxlintRuntime.concurrency,
|
|
2316
2359
|
oxlintSpawnSlots: oxlintRuntime.spawnSlots,
|
|
2360
|
+
invocationCaches: oxlintRuntime.invocationCaches,
|
|
2317
2361
|
reporterLayer: options.uiLayers?.reporter,
|
|
2318
2362
|
progressLayer: options.uiLayers?.progress
|
|
2319
2363
|
});
|
|
2320
2364
|
const program = runInspect({
|
|
2321
2365
|
directory,
|
|
2322
2366
|
precomputedSourceFileCount: options.precomputedSourceFileCount,
|
|
2367
|
+
precomputedSourceFiles: options.precomputedSourceFiles,
|
|
2323
2368
|
includePaths: options.includePaths,
|
|
2324
2369
|
changedLineRanges: options.changedLineRanges ?? void 0,
|
|
2325
2370
|
customRulesOnly: options.customRulesOnly,
|
|
@@ -2431,12 +2476,12 @@ const deduplicateProjectScans = (projectScans) => {
|
|
|
2431
2476
|
return uniqueProjectScans;
|
|
2432
2477
|
};
|
|
2433
2478
|
//#endregion
|
|
2434
|
-
//#region src/cli/utils/collect-project-source-
|
|
2435
|
-
const
|
|
2479
|
+
//#region src/cli/utils/collect-project-source-files.ts
|
|
2480
|
+
const collectProjectSourceFiles = async (rootDirectory, projectDirectories) => {
|
|
2436
2481
|
const abortController = new AbortController();
|
|
2437
2482
|
const unregisterAbortController = activeScanAbortRegistry.register(abortController);
|
|
2438
2483
|
try {
|
|
2439
|
-
return await
|
|
2484
|
+
return await collectSourceFilesByDirectory(rootDirectory, projectDirectories, abortController.signal);
|
|
2440
2485
|
} finally {
|
|
2441
2486
|
unregisterAbortController();
|
|
2442
2487
|
}
|
|
@@ -2965,6 +3010,17 @@ const projectManifestChanged = (rootDirectory, projectDirectory, diffInfo) => {
|
|
|
2965
3010
|
return diffInfo.changedFiles.some((filePath) => toForwardSlashes(filePath) === manifestPath);
|
|
2966
3011
|
};
|
|
2967
3012
|
//#endregion
|
|
3013
|
+
//#region src/cli/utils/resolve-excluded-project-directories.ts
|
|
3014
|
+
const resolveExcludedProjectDirectories = (scanDirectory, projectDirectories) => {
|
|
3015
|
+
const excludedDirectories = /* @__PURE__ */ new Map();
|
|
3016
|
+
for (const projectDirectory of projectDirectories) {
|
|
3017
|
+
if (!isPathInsideDirectory(projectDirectory, scanDirectory)) continue;
|
|
3018
|
+
const resolvedDirectory = path$1.resolve(projectDirectory);
|
|
3019
|
+
if (!excludedDirectories.has(resolvedDirectory)) excludedDirectories.set(resolvedDirectory, projectDirectory);
|
|
3020
|
+
}
|
|
3021
|
+
return [...excludedDirectories.values()];
|
|
3022
|
+
};
|
|
3023
|
+
//#endregion
|
|
2968
3024
|
//#region src/cli/utils/validate-mode-flags.ts
|
|
2969
3025
|
const usedDiffAlias = (flags) => flags.diff !== void 0 && flags.diff !== false && flags.diff !== "false" && flags.diff !== "";
|
|
2970
3026
|
const usedScope = (flags) => typeof flags.scope === "string" && flags.scope.length > 0;
|
|
@@ -3002,7 +3058,7 @@ const shouldFailScanGate = (input) => {
|
|
|
3002
3058
|
return shouldBlockCi(filterScansForSurface(input.scans, "ciFailure"), input.blockingLevel);
|
|
3003
3059
|
};
|
|
3004
3060
|
//#endregion
|
|
3005
|
-
export {
|
|
3061
|
+
export { createInvocationInspect as A, hasIncompleteScoreAnalysis as B, detectLaunchableAgents as C, buildHandoffPayload as D, launchCliAgent as E, formatSkippedCheckLabel as F, hasLintHardFailure as G, buildNoScoreMessage as H, formatElapsedTime as I, formatFixRecipeLine as J, buildRulePriorityMap as K, printFooter as L, resolveMergeBaseRef as M, printHeadlessReport as N, collectProjectSourceFiles as O, pluralize as P, printDiagnosticsDump as R, setUpGitHubActions as S, copyToClipboard as T, buildEmptyReportMessage as U, filterDiagnosticsByCategories as V, resolveInvocationOxlintConcurrency as W, formatLearnMoreLine as Y, retryMissingProjectScores as _, validateModeFlags as a, filterScansForSurface as b, partitionProjectScanOutcomes as c, buildCodeFrame as d, resolveProjectScan as f, resolveProjectRelativeDirectory as g, resolveProjectSourceFilePaths as h, validateIncludeUntrackedScope as i, inspect as j, deduplicateProjectScans as k, discoverWorkspacePackages as l, resolveProjectDiffIncludePaths as m, formatSkippedProjectsMessage as n, resolveExcludedProjectDirectories as o, resolveProjectChangedLineRanges as p, buildSortedRuleGroups as q, validateFilePathSelectionFlags as r, projectManifestChanged as s, shouldFailScanGate as t, selectProjects as u, resolveWorkspaceDeadCodeOwner as v, CLI_AGENT_BINARIES as w, countUniqueScannedFiles as x, resolveCliInspectOptions as y, formatDiagnosticSite as z };
|
|
3006
3062
|
|
|
3007
|
-
//# sourceMappingURL=should-fail-scan-gate-
|
|
3008
|
-
//# debugId=
|
|
3063
|
+
//# sourceMappingURL=should-fail-scan-gate-B8cvpr6W.js.map
|
|
3064
|
+
//# debugId=bb74300d-38db-5b33-86a5-4ad923dfbe6a
|