react-doctor 0.7.0 → 0.7.1-dev.5809083
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +88 -28
- package/dist/index.d.ts +51 -1
- package/dist/index.js +36 -8
- package/dist/lsp.js +32 -7
- package/package.json +6 -6
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="69a45de3-fe65-53dc-aed2-17f9cea22159")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as NodeChildProcess from "node:child_process";
|
|
5
5
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -22405,6 +22405,7 @@ var Diagnostic = class extends Class("Diagnostic")({
|
|
|
22405
22405
|
endLine: optional(Number$1),
|
|
22406
22406
|
endColumn: optional(Number$1),
|
|
22407
22407
|
category: String$1,
|
|
22408
|
+
matchByOccurrence: optional(Boolean$1),
|
|
22408
22409
|
fileContext: optional(Literals(["test", "story"])),
|
|
22409
22410
|
suppressionHint: optional(String$1),
|
|
22410
22411
|
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation)),
|
|
@@ -22475,6 +22476,16 @@ var JsonReportV1 = class extends Class("JsonReportV1")({
|
|
|
22475
22476
|
* successful `baseline` run (`schemaVersion: 2`) and on non-compare scopes.
|
|
22476
22477
|
*/
|
|
22477
22478
|
baselineDegraded: optional(Boolean$1),
|
|
22479
|
+
/**
|
|
22480
|
+
* Whether any scanned project resolved a React-compatible runtime (React
|
|
22481
|
+
* or Preact). `false` means every React-runtime rule family was gated off,
|
|
22482
|
+
* so an empty `diagnostics` array is vacuous — NOT the same as a clean
|
|
22483
|
+
* React scan. Consumers gating on the report (CI, verifiers, hooks) should
|
|
22484
|
+
* treat `reactDetected === false` as "wrong scan target", not "all clear".
|
|
22485
|
+
* Absent when nothing was scanned (`projects` is empty), on error reports,
|
|
22486
|
+
* and on reports from older CLI versions.
|
|
22487
|
+
*/
|
|
22488
|
+
reactDetected: optional(Boolean$1),
|
|
22478
22489
|
diff: NullOr(JsonReportDiffInfo),
|
|
22479
22490
|
projects: ArraySchema(JsonReportProjectEntry),
|
|
22480
22491
|
diagnostics: ArraySchema(Diagnostic),
|
|
@@ -22500,6 +22511,8 @@ var JsonReportV2 = class extends Class("JsonReportV2")({
|
|
|
22500
22511
|
ok: Boolean$1,
|
|
22501
22512
|
directory: String$1,
|
|
22502
22513
|
mode: JsonReportMode,
|
|
22514
|
+
/** See `JsonReportV1.reactDetected`. */
|
|
22515
|
+
reactDetected: optional(Boolean$1),
|
|
22503
22516
|
diff: NullOr(JsonReportDiffInfo),
|
|
22504
22517
|
baseline: JsonReportBaseline,
|
|
22505
22518
|
projects: ArraySchema(JsonReportProjectEntry),
|
|
@@ -36108,6 +36121,7 @@ const DIAGNOSTIC_CATEGORY_BUCKETS = [
|
|
|
36108
36121
|
"Accessibility",
|
|
36109
36122
|
"Maintainability"
|
|
36110
36123
|
];
|
|
36124
|
+
const OCCURRENCE_MATCHED_CATEGORIES = new Set(["Accessibility"]);
|
|
36111
36125
|
const APP_ONLY_RULE_KEYS = new Set([
|
|
36112
36126
|
"react-hooks-js/static-components",
|
|
36113
36127
|
"react-doctor/no-render-prop-children",
|
|
@@ -36130,7 +36144,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
36130
36144
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
36131
36145
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
36132
36146
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
36133
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
36147
|
+
const CORE_PACKAGE_VERSION = "0.7.1";
|
|
36134
36148
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
36135
36149
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
36136
36150
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -40237,10 +40251,17 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
40237
40251
|
}
|
|
40238
40252
|
}
|
|
40239
40253
|
}
|
|
40254
|
+
/**
|
|
40255
|
+
* Whether project discovery resolved a React-compatible runtime (React or
|
|
40256
|
+
* Preact, which ships the same hooks + JSX model). This is the same
|
|
40257
|
+
* predicate that grants the `react` capability, so `false` means every
|
|
40258
|
+
* React-runtime rule family was gated off for the project.
|
|
40259
|
+
*/
|
|
40260
|
+
const hasReactRuntime = (project) => project.reactVersion !== null || project.preactVersion !== null;
|
|
40240
40261
|
const buildCapabilities = (project) => {
|
|
40241
40262
|
const capabilities = /* @__PURE__ */ new Set();
|
|
40242
40263
|
capabilities.add(project.framework);
|
|
40243
|
-
if (project
|
|
40264
|
+
if (hasReactRuntime(project)) capabilities.add("react");
|
|
40244
40265
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
40245
40266
|
if (project.expoVersion !== null) capabilities.add("expo");
|
|
40246
40267
|
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
|
|
@@ -41786,7 +41807,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
41786
41807
|
})),
|
|
41787
41808
|
showStagedContent: (directory, relativePath, options) => runCommand({
|
|
41788
41809
|
command: "git",
|
|
41789
|
-
args: ["show",
|
|
41810
|
+
args: ["show", `:./${relativePath}`],
|
|
41790
41811
|
directory,
|
|
41791
41812
|
maxStdoutBytes: options?.maxBufferBytes
|
|
41792
41813
|
}).pipe(map$3((result) => result.status === 0 ? result.stdout : null)),
|
|
@@ -43092,6 +43113,7 @@ const parseRuleCode = (code) => {
|
|
|
43092
43113
|
};
|
|
43093
43114
|
};
|
|
43094
43115
|
const resolveDiagnosticCategory = (plugin, rule) => getRuleCategory(rule) ?? lookupOwnString(PLUGIN_CATEGORY_MAP, plugin) ?? "Bugs";
|
|
43116
|
+
const resolveMatchByOccurrence = (rule, category) => OCCURRENCE_MATCHED_CATEGORIES.has(category) || Boolean(reactDoctorPlugin.rules[rule]?.matchByOccurrence);
|
|
43095
43117
|
/**
|
|
43096
43118
|
* Maps oxlint's non-primary labels (`labels[1..]`) into related source
|
|
43097
43119
|
* locations. Editors surface these as a diagnostic's
|
|
@@ -43155,6 +43177,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43155
43177
|
const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
|
|
43156
43178
|
const primarySpan = primaryLabel?.span;
|
|
43157
43179
|
const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
|
|
43180
|
+
const category = resolveDiagnosticCategory(plugin, rule);
|
|
43158
43181
|
return {
|
|
43159
43182
|
filePath: normalizedFilePath,
|
|
43160
43183
|
plugin,
|
|
@@ -43170,7 +43193,8 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43170
43193
|
offset: primarySpan.offset,
|
|
43171
43194
|
length: primarySpan.length
|
|
43172
43195
|
} : {},
|
|
43173
|
-
category
|
|
43196
|
+
category,
|
|
43197
|
+
...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
|
|
43174
43198
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
43175
43199
|
};
|
|
43176
43200
|
});
|
|
@@ -45355,6 +45379,7 @@ const buildJsonReport = (input) => {
|
|
|
45355
45379
|
const worstScoredProject = findWorstScoredProject(projects);
|
|
45356
45380
|
const summary = summarizeDiagnostics(flattenedDiagnostics, worstScoredProject?.score?.score ?? null, worstScoredProject?.score?.label ?? null);
|
|
45357
45381
|
const shared = {
|
|
45382
|
+
...input.scans.length > 0 ? { reactDetected: input.scans.some((scan) => hasReactRuntime(scan.result.project)) } : {},
|
|
45358
45383
|
version: input.version,
|
|
45359
45384
|
ok: true,
|
|
45360
45385
|
directory: input.directory,
|
|
@@ -45405,7 +45430,7 @@ const buildSkippedChecks = (input) => {
|
|
|
45405
45430
|
};
|
|
45406
45431
|
const fingerprintDiagnostic = (diagnostic, lineText) => {
|
|
45407
45432
|
const ruleKey = `${diagnostic.plugin}/${diagnostic.rule}`;
|
|
45408
|
-
const snippet = lineText === null ? "" : createHash("sha1").update(lineText.trim()).digest("hex");
|
|
45433
|
+
const snippet = lineText === null || diagnostic.matchByOccurrence ? "" : createHash("sha1").update(lineText.trim()).digest("hex");
|
|
45409
45434
|
return `${diagnostic.filePath}\u0000${ruleKey}\u0000${snippet}`;
|
|
45410
45435
|
};
|
|
45411
45436
|
/**
|
|
@@ -45416,6 +45441,17 @@ const fingerprintDiagnostic = (diagnostic, lineText) => {
|
|
|
45416
45441
|
* look new, while a genuinely new occurrence (new line text, or one more of
|
|
45417
45442
|
* the same) surfaces. Identical repeated findings are matched by count.
|
|
45418
45443
|
*
|
|
45444
|
+
* Diagnostics carrying `matchByOccurrence` (resolved at diagnostic creation:
|
|
45445
|
+
* every Accessibility-category finding, plus rules opting in via their
|
|
45446
|
+
* `matchByOccurrence` metadata flag) drop the line-text snippet and match by
|
|
45447
|
+
* `(filePath, plugin/rule)` occurrence count alone. Their identity is the
|
|
45448
|
+
* flagged element, not the line's text, so editing the line (reindentation,
|
|
45449
|
+
* prettier reflow, collapsing a multi-line JSX element) doesn't reclassify a
|
|
45450
|
+
* pre-existing finding as new — while one MORE occurrence of the same rule in
|
|
45451
|
+
* the file still surfaces. Expression-level rules keep the line-text snippet:
|
|
45452
|
+
* there the flagged expression IS the finding, so changed text means new +
|
|
45453
|
+
* fixed.
|
|
45454
|
+
*
|
|
45419
45455
|
* v1 limitation: the fingerprint keys on the head-relative `filePath`, and base
|
|
45420
45456
|
* content is read at that same path. A file renamed by the change therefore has
|
|
45421
45457
|
* no base match, so its pre-existing findings are reported as new. This
|
|
@@ -45818,6 +45854,7 @@ const METRIC = {
|
|
|
45818
45854
|
scanScore: "scan.score",
|
|
45819
45855
|
scanClean: "scan.clean",
|
|
45820
45856
|
scanCheckSkipped: "scan.check_skipped",
|
|
45857
|
+
scanNoReactDetected: "scan.no_react_detected",
|
|
45821
45858
|
baselineDegraded: "baseline.degraded",
|
|
45822
45859
|
ruleFired: "rule.fired",
|
|
45823
45860
|
ruleDisabled: "rule.disabled",
|
|
@@ -45887,7 +45924,7 @@ const makeNoopConsole = () => ({
|
|
|
45887
45924
|
});
|
|
45888
45925
|
//#endregion
|
|
45889
45926
|
//#region src/cli/utils/version.ts
|
|
45890
|
-
const VERSION = "0.7.
|
|
45927
|
+
const VERSION = "0.7.1-dev.5809083";
|
|
45891
45928
|
//#endregion
|
|
45892
45929
|
//#region src/cli/utils/json-mode.ts
|
|
45893
45930
|
let context = null;
|
|
@@ -46256,13 +46293,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
46256
46293
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
46257
46294
|
* standard `SENTRY_RELEASE` override.
|
|
46258
46295
|
*/
|
|
46259
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.
|
|
46296
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.1-dev.5809083`;
|
|
46260
46297
|
/**
|
|
46261
46298
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
46262
46299
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
46263
46300
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
46264
46301
|
*/
|
|
46265
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.
|
|
46302
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.1-dev.5809083") ? "development" : "production");
|
|
46266
46303
|
/**
|
|
46267
46304
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
46268
46305
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -58106,7 +58143,7 @@ const buildRuntimeLayers = (input) => {
|
|
|
58106
58143
|
const linterLayer = input.shouldSkipLint ? Linter.layerOf([]) : Linter.layerOxlint;
|
|
58107
58144
|
const deadCodeLayer = input.shouldRunDeadCode ? DeadCode.layerNode : DeadCode.layerOf([]);
|
|
58108
58145
|
const scoreLayer = input.shouldComputeScore ? Score.layerHttp : Score.layerOf(null);
|
|
58109
|
-
const supplyChainLayer = input.
|
|
58146
|
+
const supplyChainLayer = input.shouldRunSupplyChain ? SupplyChain.layerNode : SupplyChain.layerOf([]);
|
|
58110
58147
|
const projectLayer = input.projectInfoOverride === void 0 ? Project.layerNode : Project.layerOf(input.projectInfoOverride);
|
|
58111
58148
|
const progressLayer = input.shouldShowProgressSpinners ? Progress.layerOra(buildSpinnerProgressHandle) : Progress.layerNoop;
|
|
58112
58149
|
const baseLayers = mergeAll$1(projectLayer, input.hasConfigOverride ? Config.layerOf({
|
|
@@ -58462,21 +58499,22 @@ const resolveDotenvFingerprint = (projectDirectory) => {
|
|
|
58462
58499
|
return [];
|
|
58463
58500
|
}
|
|
58464
58501
|
};
|
|
58465
|
-
const
|
|
58502
|
+
const isGitIdentityTrustworthy = (projectDirectory) => {
|
|
58466
58503
|
const output = runGit(projectDirectory, ["ls-files", "-v"]);
|
|
58467
|
-
if (output === null) return
|
|
58468
|
-
|
|
58504
|
+
if (output === null) return false;
|
|
58505
|
+
const entryLines = output.split("\n").filter((line) => line.length > 0);
|
|
58506
|
+
return entryLines.length > 0 && entryLines.every((line) => line[0] === "H");
|
|
58469
58507
|
};
|
|
58470
58508
|
const resolveCacheFilePath = (projectDirectory) => path.join(resolveReactDoctorCacheDir(projectDirectory), SCAN_RESULT_CACHE_FILENAME);
|
|
58471
58509
|
const readPersistedCache = (cacheFilePath) => {
|
|
58472
58510
|
try {
|
|
58473
58511
|
const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
|
|
58474
|
-
if (!isRecord(parsed) || parsed.version !==
|
|
58475
|
-
version:
|
|
58512
|
+
if (!isRecord(parsed) || parsed.version !== 4) return {
|
|
58513
|
+
version: 4,
|
|
58476
58514
|
entries: []
|
|
58477
58515
|
};
|
|
58478
58516
|
if (!Array.isArray(parsed.entries)) return {
|
|
58479
|
-
version:
|
|
58517
|
+
version: 4,
|
|
58480
58518
|
entries: []
|
|
58481
58519
|
};
|
|
58482
58520
|
const entries = [];
|
|
@@ -58486,12 +58524,12 @@ const readPersistedCache = (cacheFilePath) => {
|
|
|
58486
58524
|
entries.push(entry);
|
|
58487
58525
|
}
|
|
58488
58526
|
return {
|
|
58489
|
-
version:
|
|
58527
|
+
version: 4,
|
|
58490
58528
|
entries
|
|
58491
58529
|
};
|
|
58492
58530
|
} catch {
|
|
58493
58531
|
return {
|
|
58494
|
-
version:
|
|
58532
|
+
version: 4,
|
|
58495
58533
|
entries: []
|
|
58496
58534
|
};
|
|
58497
58535
|
}
|
|
@@ -58523,6 +58561,7 @@ const resolveProjectIdentity = (projectDirectory) => {
|
|
|
58523
58561
|
return path.resolve(projectDirectory);
|
|
58524
58562
|
}
|
|
58525
58563
|
};
|
|
58564
|
+
const resolveManifestContentHash = (projectDirectory) => hashFileContents(getPackageJsonPath(projectDirectory));
|
|
58526
58565
|
const fileFingerprint = (filePath) => {
|
|
58527
58566
|
try {
|
|
58528
58567
|
const stat = fs.statSync(filePath);
|
|
@@ -58545,14 +58584,14 @@ const resolveToolchainFingerprint = (nodeBinaryPath) => {
|
|
|
58545
58584
|
};
|
|
58546
58585
|
const buildScanResultCacheKey = (input) => {
|
|
58547
58586
|
if (isCacheGloballyDisabled()) return null;
|
|
58548
|
-
if (
|
|
58587
|
+
if (!isGitIdentityTrustworthy(input.projectDirectory)) return null;
|
|
58549
58588
|
const worktreeFingerprint = buildWorktreeFingerprint(input.projectDirectory);
|
|
58550
58589
|
if (worktreeFingerprint === null) return null;
|
|
58551
58590
|
const headSha = readHeadSha(input.projectDirectory);
|
|
58552
58591
|
if (headSha === null) return null;
|
|
58553
58592
|
if (stringifyStableJson(input.userConfig) === null) return null;
|
|
58554
58593
|
const cacheKeyJson = stringifyStableJson({
|
|
58555
|
-
schemaVersion:
|
|
58594
|
+
schemaVersion: 4,
|
|
58556
58595
|
projectIdentity: resolveProjectIdentity(input.projectDirectory),
|
|
58557
58596
|
headSha,
|
|
58558
58597
|
worktreeFingerprint,
|
|
@@ -58567,6 +58606,7 @@ const buildScanResultCacheKey = (input) => {
|
|
|
58567
58606
|
engineOptions: {
|
|
58568
58607
|
lint: input.options.lint,
|
|
58569
58608
|
deadCode: input.options.deadCode,
|
|
58609
|
+
supplyChain: input.options.supplyChain,
|
|
58570
58610
|
includePaths: [...input.options.includePaths].sort(),
|
|
58571
58611
|
customRulesOnly: input.options.customRulesOnly,
|
|
58572
58612
|
respectInlineDisables: input.options.respectInlineDisables,
|
|
@@ -58592,17 +58632,24 @@ const createScanResultCache = (projectDirectory) => {
|
|
|
58592
58632
|
for (const entry of persistedCache.entries) entries.set(entry.key, entry);
|
|
58593
58633
|
const persist = () => {
|
|
58594
58634
|
writePersistedCache(cacheFilePath, {
|
|
58595
|
-
version:
|
|
58635
|
+
version: 4,
|
|
58596
58636
|
entries: [...entries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
|
|
58597
58637
|
});
|
|
58598
58638
|
};
|
|
58599
58639
|
return {
|
|
58600
|
-
lookup: (key) =>
|
|
58640
|
+
lookup: (key) => {
|
|
58641
|
+
const payload = entries.get(key)?.payload;
|
|
58642
|
+
if (payload === void 0) return null;
|
|
58643
|
+
return resolveProjectIdentity(payload.directory) === resolveProjectIdentity(projectDirectory) && (payload.manifestContentHash ?? null) === resolveManifestContentHash(projectDirectory) ? payload : null;
|
|
58644
|
+
},
|
|
58601
58645
|
store: (key, payload) => {
|
|
58602
58646
|
entries.set(key, {
|
|
58603
58647
|
key,
|
|
58604
58648
|
createdAtMs: Date.now(),
|
|
58605
|
-
payload
|
|
58649
|
+
payload: {
|
|
58650
|
+
...payload,
|
|
58651
|
+
manifestContentHash: resolveManifestContentHash(projectDirectory)
|
|
58652
|
+
}
|
|
58606
58653
|
});
|
|
58607
58654
|
persist();
|
|
58608
58655
|
}
|
|
@@ -58867,6 +58914,7 @@ const buildScanAttributes = (input) => {
|
|
|
58867
58914
|
maxDurationMs: input.maxDurationMs,
|
|
58868
58915
|
lint: input.lint,
|
|
58869
58916
|
deadCode: input.deadCode,
|
|
58917
|
+
supplyChain: input.supplyChain,
|
|
58870
58918
|
scoreOnly: input.scoreOnly,
|
|
58871
58919
|
noScore: input.noScore,
|
|
58872
58920
|
respectInlineDisables: input.respectInlineDisables,
|
|
@@ -60141,6 +60189,7 @@ const buildIgnoredTags = (userConfig) => {
|
|
|
60141
60189
|
const mergeInspectOptions = (inputOptions, userConfig) => ({
|
|
60142
60190
|
lint: inputOptions.lint ?? userConfig?.lint ?? true,
|
|
60143
60191
|
deadCode: inputOptions.deadCode ?? userConfig?.deadCode ?? true,
|
|
60192
|
+
supplyChain: inputOptions.supplyChain ?? userConfig?.supplyChain?.enabled ?? true,
|
|
60144
60193
|
verbose: inputOptions.verbose ?? userConfig?.verbose ?? false,
|
|
60145
60194
|
outputDirectory: inputOptions.outputDirectory || null,
|
|
60146
60195
|
scoreOnly: inputOptions.scoreOnly ?? false,
|
|
@@ -60180,6 +60229,7 @@ const buildRunEventConfig = (options, userConfig, hasCustomConfig, resolvedWorke
|
|
|
60180
60229
|
maxDurationMs: options.maxDurationMs,
|
|
60181
60230
|
lint: options.lint,
|
|
60182
60231
|
deadCode: options.deadCode,
|
|
60232
|
+
supplyChain: options.supplyChain,
|
|
60183
60233
|
scoreOnly: options.scoreOnly,
|
|
60184
60234
|
noScore: options.noScore,
|
|
60185
60235
|
respectInlineDisables: options.respectInlineDisables,
|
|
@@ -60263,6 +60313,7 @@ const runBaselineComparison = async (params) => {
|
|
|
60263
60313
|
projectInfoOverride: params.headProjectInfo,
|
|
60264
60314
|
shouldSkipLint: !params.options.lint || !params.resolvedNodeBinaryPath,
|
|
60265
60315
|
shouldRunDeadCode: false,
|
|
60316
|
+
shouldRunSupplyChain: params.options.supplyChain,
|
|
60266
60317
|
shouldComputeScore: false,
|
|
60267
60318
|
shouldShowProgressSpinners: false,
|
|
60268
60319
|
oxlintConcurrency: params.options.concurrency
|
|
@@ -60351,6 +60402,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
60351
60402
|
configSourceDirectory,
|
|
60352
60403
|
shouldSkipLint: !options.lint || lintBindingMissing,
|
|
60353
60404
|
shouldRunDeadCode: options.deadCode,
|
|
60405
|
+
shouldRunSupplyChain: options.supplyChain,
|
|
60354
60406
|
shouldComputeScore: !options.noScore,
|
|
60355
60407
|
shouldShowProgressSpinners,
|
|
60356
60408
|
oxlintConcurrency: options.concurrency
|
|
@@ -62636,8 +62688,8 @@ const resolveParallelFlag = (parallel) => parallel === false ? 1 : void 0;
|
|
|
62636
62688
|
* accepts. Flag-derived fields only (`scoreOnly`, `noScore`, `silent`,
|
|
62637
62689
|
* `isCi`): every `userConfig` fallback — including `noScore` — lives in
|
|
62638
62690
|
* `inspect()`'s merge layer, which sees each project's module-merged
|
|
62639
|
-
* config. The plain boolean knobs (`lint`, `deadCode`, `
|
|
62640
|
-
* through unchanged for the same reason. This resolver reads
|
|
62691
|
+
* config. The plain boolean knobs (`lint`, `deadCode`, `supplyChain`,
|
|
62692
|
+
* `verbose`) pass through unchanged for the same reason. This resolver reads
|
|
62641
62693
|
* `userConfig` only to decide the `--blocking warning` gate.
|
|
62642
62694
|
*/
|
|
62643
62695
|
const resolveCliInspectOptions = (flags, userConfig) => {
|
|
@@ -62645,6 +62697,7 @@ const resolveCliInspectOptions = (flags, userConfig) => {
|
|
|
62645
62697
|
return {
|
|
62646
62698
|
lint: flags.lint,
|
|
62647
62699
|
deadCode: flags.deadCode,
|
|
62700
|
+
supplyChain: flags.supplyChain,
|
|
62648
62701
|
verbose: flags.verbose,
|
|
62649
62702
|
outputDirectory: flags.outputDir,
|
|
62650
62703
|
respectInlineDisables: flags.respectInlineDisables === false ? false : void 0,
|
|
@@ -62975,6 +63028,11 @@ const finalizeScans = (input) => {
|
|
|
62975
63028
|
const baselineComputed = input.completedScans.length > 0 && input.completedScans.every((scan) => scan.result.baselineDelta !== void 0);
|
|
62976
63029
|
const baselineDegraded = input.baselineIntended && !baselineComputed;
|
|
62977
63030
|
const mode = baselineDegraded ? "diff" : input.mode;
|
|
63031
|
+
const isReactDetected = input.completedScans.some((scan) => hasReactRuntime(scan.result.project));
|
|
63032
|
+
if (input.completedScans.length > 0 && !isReactDetected) {
|
|
63033
|
+
recordCount(METRIC.scanNoReactDetected, 1);
|
|
63034
|
+
cliLogger.warn(`No React project detected at ${input.resolvedDirectory} — React rules were gated off; this is not the same as a clean scan.`);
|
|
63035
|
+
}
|
|
62978
63036
|
const jsonCompletedScans = filterCompletedScansByCategories(input.completedScans, input.categoryFilters);
|
|
62979
63037
|
if (input.isJsonMode) {
|
|
62980
63038
|
const baseline = baselineComputed && baselineDeltas.length > 0 ? {
|
|
@@ -63190,7 +63248,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
63190
63248
|
const scanDirectory = projectScanTarget.resolvedDirectory;
|
|
63191
63249
|
const projectConfig = projectDirectory === resolvedDirectory ? userConfig : mergeReactDoctorConfigs(userConfig, projectScanTarget.userConfig ?? void 0);
|
|
63192
63250
|
const projectConfigSourceDirectory = projectScanTarget.userConfig?.plugins === void 0 ? scanTarget.configSourceDirectory : projectScanTarget.configSourceDirectory;
|
|
63193
|
-
const supplyChainEnabled = projectConfig?.supplyChain?.enabled !== false;
|
|
63251
|
+
const supplyChainEnabled = flags.supplyChain ?? projectConfig?.supplyChain?.enabled !== false;
|
|
63194
63252
|
let includePaths;
|
|
63195
63253
|
let supplyChainManifestChanged = false;
|
|
63196
63254
|
if (isDiffMode) {
|
|
@@ -64110,10 +64168,12 @@ const ROOT_FLAG_SPEC = {
|
|
|
64110
64168
|
"--no-parallel",
|
|
64111
64169
|
"--no-respect-inline-disables",
|
|
64112
64170
|
"--no-score",
|
|
64171
|
+
"--no-supply-chain",
|
|
64113
64172
|
"--no-telemetry",
|
|
64114
64173
|
"--no-warnings",
|
|
64115
64174
|
"--score",
|
|
64116
64175
|
"--staged",
|
|
64176
|
+
"--supply-chain",
|
|
64117
64177
|
"--verbose",
|
|
64118
64178
|
"--version",
|
|
64119
64179
|
"--warnings",
|
|
@@ -64362,7 +64422,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
64362
64422
|
${highlighter.info(CI_URL)}
|
|
64363
64423
|
`;
|
|
64364
64424
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
64365
|
-
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
64425
|
+
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
64366
64426
|
program.action(inspectAction);
|
|
64367
64427
|
program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
|
|
64368
64428
|
program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
|
|
@@ -64412,4 +64472,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
64412
64472
|
export {};
|
|
64413
64473
|
|
|
64414
64474
|
//# sourceMappingURL=cli.js.map
|
|
64415
|
-
//# debugId=
|
|
64475
|
+
//# debugId=69a45de3-fe65-53dc-aed2-17f9cea22159
|
package/dist/index.d.ts
CHANGED
|
@@ -9493,6 +9493,17 @@ interface Diagnostic {
|
|
|
9493
9493
|
/** 1-indexed end column of the primary span, when derivable. */
|
|
9494
9494
|
endColumn?: number;
|
|
9495
9495
|
category: string;
|
|
9496
|
+
/**
|
|
9497
|
+
* Set when the finding's identity is the flagged element itself (a missing
|
|
9498
|
+
* attribute, a wrong element) rather than the flagged line's text — every
|
|
9499
|
+
* Accessibility-category finding, plus rules that opt in via their
|
|
9500
|
+
* `matchByOccurrence` metadata flag. `computeDiagnosticDelta` matches these
|
|
9501
|
+
* by `(filePath, plugin/rule)` occurrence count, so reformatting the flagged
|
|
9502
|
+
* line doesn't reclassify a pre-existing finding as new. Resolved at
|
|
9503
|
+
* diagnostic creation, where rule metadata is available. Absent means
|
|
9504
|
+
* line-text-sensitive matching (the default).
|
|
9505
|
+
*/
|
|
9506
|
+
matchByOccurrence?: boolean;
|
|
9496
9507
|
/**
|
|
9497
9508
|
* Set when the file never ships to users (`"test"` / `"story"`), so
|
|
9498
9509
|
* renderers can label the site instead of implying production impact.
|
|
@@ -9644,6 +9655,17 @@ interface DiagnoseResult {
|
|
|
9644
9655
|
/** See `InspectResult.skippedCheckReasons`. */
|
|
9645
9656
|
skippedCheckReasons?: Record<string, string>;
|
|
9646
9657
|
project: ProjectInfo;
|
|
9658
|
+
/**
|
|
9659
|
+
* Whether the scanned project resolved a React-compatible runtime (React
|
|
9660
|
+
* or Preact). `false` means every React-runtime rule family was gated
|
|
9661
|
+
* off, so an empty `diagnostics` array is vacuous — NOT the same as a
|
|
9662
|
+
* clean React scan. Consumers gating on the result should treat
|
|
9663
|
+
* `reactDetected === false` as "wrong scan target", not "all clear".
|
|
9664
|
+
* Mirrors `JsonReport.reactDetected`; same predicate as
|
|
9665
|
+
* `hasReactRuntime(result.project)`. Always set by `diagnose()`;
|
|
9666
|
+
* optional so hand-constructed results keep compiling.
|
|
9667
|
+
*/
|
|
9668
|
+
reactDetected?: boolean;
|
|
9647
9669
|
elapsedMilliseconds: number;
|
|
9648
9670
|
}
|
|
9649
9671
|
/**
|
|
@@ -9693,6 +9715,13 @@ interface DiagnoseProjectsResult {
|
|
|
9693
9715
|
projects: ProjectResult[];
|
|
9694
9716
|
diagnostics: Diagnostic[];
|
|
9695
9717
|
score: ScoreResult | null;
|
|
9718
|
+
/**
|
|
9719
|
+
* Whether any successfully scanned project resolved a React-compatible
|
|
9720
|
+
* runtime (React or Preact). Absent when no project scanned successfully.
|
|
9721
|
+
* See `DiagnoseResult.reactDetected` for gating guidance; per-project
|
|
9722
|
+
* detail is on each `ProjectResultOk.reactDetected`.
|
|
9723
|
+
*/
|
|
9724
|
+
reactDetected?: boolean;
|
|
9696
9725
|
elapsedMilliseconds: number;
|
|
9697
9726
|
} //#endregion
|
|
9698
9727
|
//#region src/types/handle-error.d.ts
|
|
@@ -9888,6 +9917,17 @@ interface JsonReportV1$1 {
|
|
|
9888
9917
|
ok: boolean;
|
|
9889
9918
|
directory: string;
|
|
9890
9919
|
mode: JsonReportMode;
|
|
9920
|
+
/**
|
|
9921
|
+
* Whether any scanned project resolved a React-compatible runtime (React
|
|
9922
|
+
* or Preact). `false` means every React-runtime rule family was gated off,
|
|
9923
|
+
* so an empty `diagnostics` array is vacuous — NOT the same as a clean
|
|
9924
|
+
* React scan. Consumers gating on the report (CI, verifiers, hooks) should
|
|
9925
|
+
* treat `reactDetected === false` as "wrong scan target", not "all clear"
|
|
9926
|
+
* (per project, `projects[].project.reactVersion` / `preactVersion` say
|
|
9927
|
+
* which roots were non-React). Absent when nothing was scanned (`projects`
|
|
9928
|
+
* is empty), on error reports, and on reports from older CLI versions.
|
|
9929
|
+
*/
|
|
9930
|
+
reactDetected?: boolean;
|
|
9891
9931
|
diff: JsonReportDiffInfo | null;
|
|
9892
9932
|
projects: JsonReportProjectEntry[];
|
|
9893
9933
|
/**
|
|
@@ -10172,6 +10212,16 @@ declare const summarizeDiagnostics: (diagnostics: Diagnostic[], worstScore?: num
|
|
|
10172
10212
|
declare const defineConfig: (config: ReactDoctorConfig) => ReactDoctorConfig; //#endregion
|
|
10173
10213
|
//#region src/utils/detect-ai-training-environment.d.ts
|
|
10174
10214
|
//#endregion
|
|
10215
|
+
//#region src/utils/has-react-runtime.d.ts
|
|
10216
|
+
/**
|
|
10217
|
+
* Whether project discovery resolved a React-compatible runtime (React or
|
|
10218
|
+
* Preact, which ships the same hooks + JSX model). This is the same
|
|
10219
|
+
* predicate that grants the `react` capability, so `false` means every
|
|
10220
|
+
* React-runtime rule family was gated off for the project.
|
|
10221
|
+
*/
|
|
10222
|
+
declare const hasReactRuntime: (project: ProjectInfo) => boolean; //#endregion
|
|
10223
|
+
//#region src/utils/is-errno-exception.d.ts
|
|
10224
|
+
//#endregion
|
|
10175
10225
|
//#region ../api/dist/index.d.ts
|
|
10176
10226
|
//#region src/diagnose.d.ts
|
|
10177
10227
|
interface Diagnose {
|
|
@@ -10198,5 +10248,5 @@ interface ToJsonReportOptions {
|
|
|
10198
10248
|
}
|
|
10199
10249
|
declare const toJsonReport: (result: DiagnoseResult, options: ToJsonReportOptions) => JsonReport;
|
|
10200
10250
|
//#endregion
|
|
10201
|
-
export { AmbiguousProjectError, type DiagnoseOptions, type DiagnoseProjectsInput, type DiagnoseProjectsResult, type DiagnoseResult, type Diagnostic, type DiffInfo, type JsonReport, type JsonReportDiffInfo, type JsonReportError, type JsonReportMode, type JsonReportProjectEntry, type JsonReportSummary, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, type ProjectDefinition, type ProjectInfo, ProjectNotFoundError, type ProjectResult, type ProjectResultError, type ProjectResultOk, type ReactDoctorConfig, ReactDoctorError, type ScoreResult, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
10251
|
+
export { AmbiguousProjectError, type DiagnoseOptions, type DiagnoseProjectsInput, type DiagnoseProjectsResult, type DiagnoseResult, type Diagnostic, type DiffInfo, type JsonReport, type JsonReportDiffInfo, type JsonReportError, type JsonReportMode, type JsonReportProjectEntry, type JsonReportSummary, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, type ProjectDefinition, type ProjectInfo, ProjectNotFoundError, type ProjectResult, type ProjectResultError, type ProjectResultOk, type ReactDoctorConfig, ReactDoctorError, type ScoreResult, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
10202
10252
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="f1919957-a013-5ded-b0ed-3d2e8469d0e2")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as NFS from "node:fs";
|
|
@@ -19304,6 +19304,7 @@ var Diagnostic = class extends Class("Diagnostic")({
|
|
|
19304
19304
|
endLine: optional(Number$1),
|
|
19305
19305
|
endColumn: optional(Number$1),
|
|
19306
19306
|
category: String$1,
|
|
19307
|
+
matchByOccurrence: optional(Boolean$1),
|
|
19307
19308
|
fileContext: optional(Literals(["test", "story"])),
|
|
19308
19309
|
suppressionHint: optional(String$1),
|
|
19309
19310
|
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation)),
|
|
@@ -19374,6 +19375,16 @@ var JsonReportV1 = class extends Class("JsonReportV1")({
|
|
|
19374
19375
|
* successful `baseline` run (`schemaVersion: 2`) and on non-compare scopes.
|
|
19375
19376
|
*/
|
|
19376
19377
|
baselineDegraded: optional(Boolean$1),
|
|
19378
|
+
/**
|
|
19379
|
+
* Whether any scanned project resolved a React-compatible runtime (React
|
|
19380
|
+
* or Preact). `false` means every React-runtime rule family was gated off,
|
|
19381
|
+
* so an empty `diagnostics` array is vacuous — NOT the same as a clean
|
|
19382
|
+
* React scan. Consumers gating on the report (CI, verifiers, hooks) should
|
|
19383
|
+
* treat `reactDetected === false` as "wrong scan target", not "all clear".
|
|
19384
|
+
* Absent when nothing was scanned (`projects` is empty), on error reports,
|
|
19385
|
+
* and on reports from older CLI versions.
|
|
19386
|
+
*/
|
|
19387
|
+
reactDetected: optional(Boolean$1),
|
|
19377
19388
|
diff: NullOr(JsonReportDiffInfo),
|
|
19378
19389
|
projects: ArraySchema(JsonReportProjectEntry),
|
|
19379
19390
|
diagnostics: ArraySchema(Diagnostic),
|
|
@@ -19399,6 +19410,8 @@ var JsonReportV2 = class extends Class("JsonReportV2")({
|
|
|
19399
19410
|
ok: Boolean$1,
|
|
19400
19411
|
directory: String$1,
|
|
19401
19412
|
mode: JsonReportMode,
|
|
19413
|
+
/** See `JsonReportV1.reactDetected`. */
|
|
19414
|
+
reactDetected: optional(Boolean$1),
|
|
19402
19415
|
diff: NullOr(JsonReportDiffInfo),
|
|
19403
19416
|
baseline: JsonReportBaseline,
|
|
19404
19417
|
projects: ArraySchema(JsonReportProjectEntry),
|
|
@@ -32907,6 +32920,7 @@ const DIAGNOSTIC_CATEGORY_BUCKETS = [
|
|
|
32907
32920
|
"Accessibility",
|
|
32908
32921
|
"Maintainability"
|
|
32909
32922
|
];
|
|
32923
|
+
const OCCURRENCE_MATCHED_CATEGORIES = new Set(["Accessibility"]);
|
|
32910
32924
|
const APP_ONLY_RULE_KEYS = new Set([
|
|
32911
32925
|
"react-hooks-js/static-components",
|
|
32912
32926
|
"react-doctor/no-render-prop-children",
|
|
@@ -32929,7 +32943,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
32929
32943
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32930
32944
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32931
32945
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32932
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
32946
|
+
const CORE_PACKAGE_VERSION = "0.7.1";
|
|
32933
32947
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32934
32948
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
32935
32949
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -37010,10 +37024,17 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
37010
37024
|
}
|
|
37011
37025
|
}
|
|
37012
37026
|
}
|
|
37027
|
+
/**
|
|
37028
|
+
* Whether project discovery resolved a React-compatible runtime (React or
|
|
37029
|
+
* Preact, which ships the same hooks + JSX model). This is the same
|
|
37030
|
+
* predicate that grants the `react` capability, so `false` means every
|
|
37031
|
+
* React-runtime rule family was gated off for the project.
|
|
37032
|
+
*/
|
|
37033
|
+
const hasReactRuntime = (project) => project.reactVersion !== null || project.preactVersion !== null;
|
|
37013
37034
|
const buildCapabilities = (project) => {
|
|
37014
37035
|
const capabilities = /* @__PURE__ */ new Set();
|
|
37015
37036
|
capabilities.add(project.framework);
|
|
37016
|
-
if (project
|
|
37037
|
+
if (hasReactRuntime(project)) capabilities.add("react");
|
|
37017
37038
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
37018
37039
|
if (project.expoVersion !== null) capabilities.add("expo");
|
|
37019
37040
|
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
|
|
@@ -38543,7 +38564,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38543
38564
|
})),
|
|
38544
38565
|
showStagedContent: (directory, relativePath, options) => runCommand({
|
|
38545
38566
|
command: "git",
|
|
38546
|
-
args: ["show",
|
|
38567
|
+
args: ["show", `:./${relativePath}`],
|
|
38547
38568
|
directory,
|
|
38548
38569
|
maxStdoutBytes: options?.maxBufferBytes
|
|
38549
38570
|
}).pipe(map$3((result) => result.status === 0 ? result.stdout : null)),
|
|
@@ -39849,6 +39870,7 @@ const parseRuleCode = (code) => {
|
|
|
39849
39870
|
};
|
|
39850
39871
|
};
|
|
39851
39872
|
const resolveDiagnosticCategory = (plugin, rule) => getRuleCategory(rule) ?? lookupOwnString(PLUGIN_CATEGORY_MAP, plugin) ?? "Bugs";
|
|
39873
|
+
const resolveMatchByOccurrence = (rule, category) => OCCURRENCE_MATCHED_CATEGORIES.has(category) || Boolean(reactDoctorPlugin.rules[rule]?.matchByOccurrence);
|
|
39852
39874
|
/**
|
|
39853
39875
|
* Maps oxlint's non-primary labels (`labels[1..]`) into related source
|
|
39854
39876
|
* locations. Editors surface these as a diagnostic's
|
|
@@ -39912,6 +39934,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
39912
39934
|
const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
|
|
39913
39935
|
const primarySpan = primaryLabel?.span;
|
|
39914
39936
|
const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
|
|
39937
|
+
const category = resolveDiagnosticCategory(plugin, rule);
|
|
39915
39938
|
return {
|
|
39916
39939
|
filePath: normalizedFilePath,
|
|
39917
39940
|
plugin,
|
|
@@ -39927,7 +39950,8 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
39927
39950
|
offset: primarySpan.offset,
|
|
39928
39951
|
length: primarySpan.length
|
|
39929
39952
|
} : {},
|
|
39930
|
-
category
|
|
39953
|
+
category,
|
|
39954
|
+
...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
|
|
39931
39955
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
39932
39956
|
};
|
|
39933
39957
|
});
|
|
@@ -42088,6 +42112,7 @@ const buildJsonReport = (input) => {
|
|
|
42088
42112
|
const worstScoredProject = findWorstScoredProject(projects);
|
|
42089
42113
|
const summary = summarizeDiagnostics(flattenedDiagnostics, worstScoredProject?.score?.score ?? null, worstScoredProject?.score?.label ?? null);
|
|
42090
42114
|
const shared = {
|
|
42115
|
+
...input.scans.length > 0 ? { reactDetected: input.scans.some((scan) => hasReactRuntime(scan.result.project)) } : {},
|
|
42091
42116
|
version: input.version,
|
|
42092
42117
|
ok: true,
|
|
42093
42118
|
directory: input.directory,
|
|
@@ -42317,6 +42342,7 @@ const outputToDiagnoseResult = (output, elapsedMilliseconds) => {
|
|
|
42317
42342
|
skippedChecks,
|
|
42318
42343
|
...Object.keys(skippedCheckReasons).length > 0 ? { skippedCheckReasons } : {},
|
|
42319
42344
|
project: output.project,
|
|
42345
|
+
reactDetected: hasReactRuntime(output.project),
|
|
42320
42346
|
elapsedMilliseconds
|
|
42321
42347
|
};
|
|
42322
42348
|
};
|
|
@@ -42372,10 +42398,12 @@ const diagnoseProjectBatch = async (input) => {
|
|
|
42372
42398
|
const startTime = globalThis.performance.now();
|
|
42373
42399
|
const { projects, concurrency, config: batchConfig, ...baseOptions } = input;
|
|
42374
42400
|
const projectResults = await mapWithConcurrency(projects, concurrency ?? 4, (projectDefinition) => diagnoseProject(projectDefinition, baseOptions, batchConfig));
|
|
42401
|
+
const succeededProjects = projectResults.filter((projectResult) => projectResult.ok);
|
|
42375
42402
|
return {
|
|
42376
42403
|
projects: projectResults,
|
|
42377
|
-
diagnostics:
|
|
42404
|
+
diagnostics: succeededProjects.flatMap((projectResult) => projectResult.diagnostics),
|
|
42378
42405
|
score: findWorstScore(projectResults),
|
|
42406
|
+
...succeededProjects.length > 0 ? { reactDetected: succeededProjects.some((projectResult) => projectResult.reactDetected) } : {},
|
|
42379
42407
|
elapsedMilliseconds: globalThis.performance.now() - startTime
|
|
42380
42408
|
};
|
|
42381
42409
|
};
|
|
@@ -42410,7 +42438,7 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
42410
42438
|
totalElapsedMilliseconds: result.elapsedMilliseconds
|
|
42411
42439
|
});
|
|
42412
42440
|
//#endregion
|
|
42413
|
-
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
42441
|
+
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
42414
42442
|
|
|
42415
42443
|
//# sourceMappingURL=index.js.map
|
|
42416
|
-
//# debugId=
|
|
42444
|
+
//# debugId=f1919957-a013-5ded-b0ed-3d2e8469d0e2
|
package/dist/lsp.js
CHANGED
|
@@ -19333,6 +19333,7 @@ var Diagnostic = class extends Class("Diagnostic")({
|
|
|
19333
19333
|
endLine: optional(Number$1),
|
|
19334
19334
|
endColumn: optional(Number$1),
|
|
19335
19335
|
category: String$1,
|
|
19336
|
+
matchByOccurrence: optional(Boolean$1),
|
|
19336
19337
|
fileContext: optional(Literals(["test", "story"])),
|
|
19337
19338
|
suppressionHint: optional(String$1),
|
|
19338
19339
|
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation)),
|
|
@@ -19410,6 +19411,16 @@ var JsonReportV1 = class extends Class("JsonReportV1")({
|
|
|
19410
19411
|
* successful `baseline` run (`schemaVersion: 2`) and on non-compare scopes.
|
|
19411
19412
|
*/
|
|
19412
19413
|
baselineDegraded: optional(Boolean$1),
|
|
19414
|
+
/**
|
|
19415
|
+
* Whether any scanned project resolved a React-compatible runtime (React
|
|
19416
|
+
* or Preact). `false` means every React-runtime rule family was gated off,
|
|
19417
|
+
* so an empty `diagnostics` array is vacuous — NOT the same as a clean
|
|
19418
|
+
* React scan. Consumers gating on the report (CI, verifiers, hooks) should
|
|
19419
|
+
* treat `reactDetected === false` as "wrong scan target", not "all clear".
|
|
19420
|
+
* Absent when nothing was scanned (`projects` is empty), on error reports,
|
|
19421
|
+
* and on reports from older CLI versions.
|
|
19422
|
+
*/
|
|
19423
|
+
reactDetected: optional(Boolean$1),
|
|
19413
19424
|
diff: NullOr(JsonReportDiffInfo),
|
|
19414
19425
|
projects: ArraySchema(JsonReportProjectEntry),
|
|
19415
19426
|
diagnostics: ArraySchema(Diagnostic),
|
|
@@ -19435,6 +19446,8 @@ var JsonReportV2 = class extends Class("JsonReportV2")({
|
|
|
19435
19446
|
ok: Boolean$1,
|
|
19436
19447
|
directory: String$1,
|
|
19437
19448
|
mode: JsonReportMode,
|
|
19449
|
+
/** See `JsonReportV1.reactDetected`. */
|
|
19450
|
+
reactDetected: optional(Boolean$1),
|
|
19438
19451
|
diff: NullOr(JsonReportDiffInfo),
|
|
19439
19452
|
baseline: JsonReportBaseline,
|
|
19440
19453
|
projects: ArraySchema(JsonReportProjectEntry),
|
|
@@ -32966,6 +32979,7 @@ const DIAGNOSTIC_CATEGORY_BUCKETS = [
|
|
|
32966
32979
|
"Accessibility",
|
|
32967
32980
|
"Maintainability"
|
|
32968
32981
|
];
|
|
32982
|
+
const OCCURRENCE_MATCHED_CATEGORIES = new Set(["Accessibility"]);
|
|
32969
32983
|
const APP_ONLY_RULE_KEYS = new Set([
|
|
32970
32984
|
"react-hooks-js/static-components",
|
|
32971
32985
|
"react-doctor/no-render-prop-children",
|
|
@@ -32988,7 +33002,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
32988
33002
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32989
33003
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32990
33004
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32991
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
33005
|
+
const CORE_PACKAGE_VERSION = "0.7.1";
|
|
32992
33006
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32993
33007
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
32994
33008
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -36995,10 +37009,17 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
36995
37009
|
}
|
|
36996
37010
|
}
|
|
36997
37011
|
}
|
|
37012
|
+
/**
|
|
37013
|
+
* Whether project discovery resolved a React-compatible runtime (React or
|
|
37014
|
+
* Preact, which ships the same hooks + JSX model). This is the same
|
|
37015
|
+
* predicate that grants the `react` capability, so `false` means every
|
|
37016
|
+
* React-runtime rule family was gated off for the project.
|
|
37017
|
+
*/
|
|
37018
|
+
const hasReactRuntime = (project) => project.reactVersion !== null || project.preactVersion !== null;
|
|
36998
37019
|
const buildCapabilities = (project) => {
|
|
36999
37020
|
const capabilities = /* @__PURE__ */ new Set();
|
|
37000
37021
|
capabilities.add(project.framework);
|
|
37001
|
-
if (project
|
|
37022
|
+
if (hasReactRuntime(project)) capabilities.add("react");
|
|
37002
37023
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
37003
37024
|
if (project.expoVersion !== null) capabilities.add("expo");
|
|
37004
37025
|
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
|
|
@@ -38528,7 +38549,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38528
38549
|
})),
|
|
38529
38550
|
showStagedContent: (directory, relativePath, options) => runCommand({
|
|
38530
38551
|
command: "git",
|
|
38531
|
-
args: ["show",
|
|
38552
|
+
args: ["show", `:./${relativePath}`],
|
|
38532
38553
|
directory,
|
|
38533
38554
|
maxStdoutBytes: options?.maxBufferBytes
|
|
38534
38555
|
}).pipe(map$3((result) => result.status === 0 ? result.stdout : null)),
|
|
@@ -39834,6 +39855,7 @@ const parseRuleCode = (code) => {
|
|
|
39834
39855
|
};
|
|
39835
39856
|
};
|
|
39836
39857
|
const resolveDiagnosticCategory = (plugin, rule) => getRuleCategory(rule) ?? lookupOwnString(PLUGIN_CATEGORY_MAP, plugin) ?? "Bugs";
|
|
39858
|
+
const resolveMatchByOccurrence = (rule, category) => OCCURRENCE_MATCHED_CATEGORIES.has(category) || Boolean(reactDoctorPlugin.rules[rule]?.matchByOccurrence);
|
|
39837
39859
|
/**
|
|
39838
39860
|
* Maps oxlint's non-primary labels (`labels[1..]`) into related source
|
|
39839
39861
|
* locations. Editors surface these as a diagnostic's
|
|
@@ -39897,6 +39919,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
39897
39919
|
const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
|
|
39898
39920
|
const primarySpan = primaryLabel?.span;
|
|
39899
39921
|
const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
|
|
39922
|
+
const category = resolveDiagnosticCategory(plugin, rule);
|
|
39900
39923
|
return {
|
|
39901
39924
|
filePath: normalizedFilePath,
|
|
39902
39925
|
plugin,
|
|
@@ -39912,7 +39935,8 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
39912
39935
|
offset: primarySpan.offset,
|
|
39913
39936
|
length: primarySpan.length
|
|
39914
39937
|
} : {},
|
|
39915
|
-
category
|
|
39938
|
+
category,
|
|
39939
|
+
...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
|
|
39916
39940
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
39917
39941
|
};
|
|
39918
39942
|
});
|
|
@@ -42085,7 +42109,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
42085
42109
|
/** Display name used in client-facing messages and progress titles. */
|
|
42086
42110
|
const SERVER_DISPLAY_NAME = "React Doctor";
|
|
42087
42111
|
/** Server version reported in `serverInfo`; injected at build, `dev` from source. */
|
|
42088
|
-
const SERVER_VERSION = "0.7.
|
|
42112
|
+
const SERVER_VERSION = "0.7.1";
|
|
42089
42113
|
/** `Diagnostic.source` shown next to every published diagnostic. */
|
|
42090
42114
|
const DIAGNOSTIC_SOURCE = "react-doctor";
|
|
42091
42115
|
/**
|
|
@@ -43792,6 +43816,7 @@ const METRIC = {
|
|
|
43792
43816
|
scanScore: "scan.score",
|
|
43793
43817
|
scanClean: "scan.clean",
|
|
43794
43818
|
scanCheckSkipped: "scan.check_skipped",
|
|
43819
|
+
scanNoReactDetected: "scan.no_react_detected",
|
|
43795
43820
|
baselineDegraded: "baseline.degraded",
|
|
43796
43821
|
ruleFired: "rule.fired",
|
|
43797
43822
|
ruleDisabled: "rule.disabled",
|
|
@@ -44135,5 +44160,5 @@ const startLanguageServer = () => {
|
|
|
44135
44160
|
};
|
|
44136
44161
|
//#endregion
|
|
44137
44162
|
export { startLanguageServer };
|
|
44138
|
-
!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]="
|
|
44139
|
-
//# debugId=
|
|
44163
|
+
!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]="0cab34bf-65ac-5994-9a93-41f1b5670387")}catch(e){}}();
|
|
44164
|
+
//# debugId=0cab34bf-65ac-5994-9a93-41f1b5670387
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1-dev.5809083",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
67
|
"yaml": "^2.9.0",
|
|
68
|
-
"deslop-js": "0.7.
|
|
69
|
-
"oxlint-plugin-react-doctor": "0.7.
|
|
68
|
+
"deslop-js": "0.7.1",
|
|
69
|
+
"oxlint-plugin-react-doctor": "0.7.1-dev.5809083"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
"@xterm/headless": "^6.0.0",
|
|
75
75
|
"commander": "^14.0.3",
|
|
76
76
|
"ora": "^9.4.0",
|
|
77
|
-
"@react-doctor/api": "0.7.
|
|
78
|
-
"@react-doctor/core": "0.7.
|
|
79
|
-
"@react-doctor/language-server": "0.7.
|
|
77
|
+
"@react-doctor/api": "0.7.1",
|
|
78
|
+
"@react-doctor/core": "0.7.1",
|
|
79
|
+
"@react-doctor/language-server": "0.7.1"
|
|
80
80
|
},
|
|
81
81
|
"engines": {
|
|
82
82
|
"node": "^20.19.0 || >=22.13.0"
|