react-doctor 0.7.6-dev.1f14fa1 → 0.7.6-dev.3f22fe3
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 +214 -70
- package/dist/index.d.ts +1 -1
- package/dist/index.js +86 -49
- package/dist/lsp.js +84 -47
- package/package.json +3 -3
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]="4c1e681e-4dee-5ffd-a4ba-69b876f47147")}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";
|
|
@@ -39594,6 +39594,49 @@ const assignFixGroups = (diagnostics) => {
|
|
|
39594
39594
|
};
|
|
39595
39595
|
});
|
|
39596
39596
|
};
|
|
39597
|
+
const DERIVED_STATE_RULE_PRIORITY = new Map([
|
|
39598
|
+
["no-initialize-state", 0],
|
|
39599
|
+
["no-adjust-state-on-prop-change", 1],
|
|
39600
|
+
["no-derived-state", 2],
|
|
39601
|
+
["no-derived-state-effect", 3]
|
|
39602
|
+
]);
|
|
39603
|
+
const buildDiagnosticSiteKey = (diagnostic) => `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}`;
|
|
39604
|
+
const isReactDoctorRulesOfHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-doctor" && diagnostic.rule === "rules-of-hooks";
|
|
39605
|
+
const isReactCompilerHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-hooks-js" && diagnostic.rule === "hooks";
|
|
39606
|
+
const doDiagnosticSpansOverlap = (first, second) => {
|
|
39607
|
+
if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
|
|
39608
|
+
if (first.offset !== void 0 && second.offset !== void 0) {
|
|
39609
|
+
const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
|
|
39610
|
+
const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
|
|
39611
|
+
return first.offset < secondEnd && second.offset < firstEnd;
|
|
39612
|
+
}
|
|
39613
|
+
const firstEndLine = first.endLine ?? first.line;
|
|
39614
|
+
const secondEndLine = second.endLine ?? second.line;
|
|
39615
|
+
return first.line <= secondEndLine && second.line <= firstEndLine;
|
|
39616
|
+
};
|
|
39617
|
+
const keepHighestSeverity = (survivingDiagnostic, collapsedDiagnostic) => collapsedDiagnostic.severity === "error" && survivingDiagnostic.severity !== "error" ? {
|
|
39618
|
+
...survivingDiagnostic,
|
|
39619
|
+
severity: "error"
|
|
39620
|
+
} : survivingDiagnostic;
|
|
39621
|
+
const dedupeRelatedDiagnostics = (diagnostics) => {
|
|
39622
|
+
const reactDoctorHookSites = new Set(diagnostics.filter(isReactDoctorRulesOfHooksDiagnostic).map(buildDiagnosticSiteKey));
|
|
39623
|
+
const uniqueDiagnostics = [];
|
|
39624
|
+
for (const diagnostic of diagnostics) {
|
|
39625
|
+
if (isReactCompilerHooksDiagnostic(diagnostic) && reactDoctorHookSites.has(buildDiagnosticSiteKey(diagnostic))) continue;
|
|
39626
|
+
const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
|
|
39627
|
+
if (priority !== void 0) {
|
|
39628
|
+
const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
|
|
39629
|
+
if (overlappingDiagnosticIndex >= 0) {
|
|
39630
|
+
const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
|
|
39631
|
+
const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
|
|
39632
|
+
if (existingDiagnostic && existingPriority !== void 0) uniqueDiagnostics[overlappingDiagnosticIndex] = priority < existingPriority ? keepHighestSeverity(diagnostic, existingDiagnostic) : keepHighestSeverity(existingDiagnostic, diagnostic);
|
|
39633
|
+
continue;
|
|
39634
|
+
}
|
|
39635
|
+
}
|
|
39636
|
+
uniqueDiagnostics.push(diagnostic);
|
|
39637
|
+
}
|
|
39638
|
+
return uniqueDiagnostics;
|
|
39639
|
+
};
|
|
39597
39640
|
const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
39598
39641
|
const sortDiagnosticsStable = (diagnostics) => [...diagnostics].sort((left, right) => compareStrings(left.filePath, right.filePath) || left.line - right.line || left.column - right.column || compareStrings(left.plugin, right.plugin) || compareStrings(left.rule, right.rule) || compareStrings(left.severity, right.severity) || compareStrings(left.message, right.message));
|
|
39599
39642
|
const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
|
|
@@ -41823,6 +41866,7 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
41823
41866
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
41824
41867
|
};
|
|
41825
41868
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
41869
|
+
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
41826
41870
|
/**
|
|
41827
41871
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
41828
41872
|
* behind a `Context.Service`. The production layer (`layerNode`)
|
|
@@ -41893,6 +41937,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
41893
41937
|
args,
|
|
41894
41938
|
directory
|
|
41895
41939
|
});
|
|
41940
|
+
const listUntrackedFilePaths = (directory, includePaths = []) => runGit(directory, [
|
|
41941
|
+
"ls-files",
|
|
41942
|
+
"-z",
|
|
41943
|
+
"--others",
|
|
41944
|
+
"--exclude-standard",
|
|
41945
|
+
...includePaths.length > 0 ? ["--", ...includePaths] : []
|
|
41946
|
+
]).pipe(map$3((result) => result.status === 0 ? splitNullSeparated(result.stdout) : null));
|
|
41947
|
+
const mergeUntracked = (directory, trackedFilePaths, includeUntracked) => includeUntracked ? listUntrackedFilePaths(directory).pipe(map$3((untracked) => untracked === null ? trackedFilePaths : [...new Set([...trackedFilePaths, ...untracked])])) : succeed$2(trackedFilePaths);
|
|
41896
41948
|
const currentBranch = (directory) => runGit(directory, [
|
|
41897
41949
|
"rev-parse",
|
|
41898
41950
|
"--abbrev-ref",
|
|
@@ -42015,7 +42067,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42015
42067
|
githubViewerPermission,
|
|
42016
42068
|
branchExists,
|
|
42017
42069
|
mergeBase,
|
|
42018
|
-
diffSelection: ({ directory, explicitBaseBranch }) => gen(function* () {
|
|
42070
|
+
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
42019
42071
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
42020
42072
|
if (explicitBaseBranch !== void 0) {
|
|
42021
42073
|
const range = parseGitDiffRange(explicitBaseBranch);
|
|
@@ -42044,7 +42096,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42044
42096
|
"HEAD"
|
|
42045
42097
|
]);
|
|
42046
42098
|
if (uncommitted.status !== 0) return null;
|
|
42047
|
-
const files = splitNullSeparated(uncommitted.stdout);
|
|
42099
|
+
const files = yield* mergeUntracked(directory, splitNullSeparated(uncommitted.stdout), includeUntracked);
|
|
42048
42100
|
if (files.length === 0) return null;
|
|
42049
42101
|
return {
|
|
42050
42102
|
currentBranch: resolvedCurrentBranch,
|
|
@@ -42074,7 +42126,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42074
42126
|
currentBranch: resolvedCurrentBranch,
|
|
42075
42127
|
baseBranch,
|
|
42076
42128
|
diffBaseRef: mergeBaseRef,
|
|
42077
|
-
changedFiles: splitNullSeparated(diff.stdout),
|
|
42129
|
+
changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
|
|
42078
42130
|
isCurrentChanges: false
|
|
42079
42131
|
};
|
|
42080
42132
|
}).pipe(withSpan("Git.diffSelection")),
|
|
@@ -42120,7 +42172,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42120
42172
|
stdout: result.stdout
|
|
42121
42173
|
};
|
|
42122
42174
|
}).pipe(withSpan("Git.grep")),
|
|
42123
|
-
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
42175
|
+
changedLineRanges: ({ directory, baseRef, cached, files, includeUntracked = false }) => gen(function* () {
|
|
42124
42176
|
if (files.length === 0) return [];
|
|
42125
42177
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
42126
42178
|
const result = yield* runGit(directory, [
|
|
@@ -42134,7 +42186,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42134
42186
|
...files
|
|
42135
42187
|
]);
|
|
42136
42188
|
if (result.status !== 0) return null;
|
|
42137
|
-
|
|
42189
|
+
const changedLineRanges = parseChangedLineRanges(result.stdout);
|
|
42190
|
+
if (cached || !includeUntracked) return changedLineRanges;
|
|
42191
|
+
const untrackedFilePaths = yield* listUntrackedFilePaths(directory, files);
|
|
42192
|
+
if (untrackedFilePaths === null) return changedLineRanges;
|
|
42193
|
+
return [...changedLineRanges, ...untrackedFilePaths.map((file) => ({
|
|
42194
|
+
file,
|
|
42195
|
+
ranges: [[1, UNTRACKED_FILE_LAST_LINE]]
|
|
42196
|
+
}))];
|
|
42138
42197
|
}).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
|
|
42139
42198
|
});
|
|
42140
42199
|
})).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
|
|
@@ -42719,13 +42778,13 @@ const encodeSidecarFileEntry = (entry) => ({
|
|
|
42719
42778
|
diagnostics: entry.diagnostics
|
|
42720
42779
|
});
|
|
42721
42780
|
const emptyCache = () => ({
|
|
42722
|
-
version:
|
|
42781
|
+
version: 2,
|
|
42723
42782
|
rulesets: {}
|
|
42724
42783
|
});
|
|
42725
42784
|
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
42726
42785
|
const entries = /* @__PURE__ */ new Map();
|
|
42727
42786
|
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
42728
|
-
if (persisted.version !==
|
|
42787
|
+
if (persisted.version !== 2 || !isRecord$1(persisted.rulesets)) return entries;
|
|
42729
42788
|
const bucket = persisted.rulesets[rulesetHash];
|
|
42730
42789
|
if (!isRecord$1(bucket) || !isRecord$1(bucket.files)) return entries;
|
|
42731
42790
|
for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
|
|
@@ -42745,7 +42804,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
42745
42804
|
},
|
|
42746
42805
|
persist: () => {
|
|
42747
42806
|
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
42748
|
-
const rulesets = onDisk.version ===
|
|
42807
|
+
const rulesets = onDisk.version === 2 && isRecord$1(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
42749
42808
|
const existingBucket = rulesets[rulesetHash];
|
|
42750
42809
|
const existingFiles = isRecord$1(existingBucket) && isRecord$1(existingBucket.files) ? existingBucket.files : {};
|
|
42751
42810
|
const ourFiles = {};
|
|
@@ -42759,7 +42818,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
42759
42818
|
files: Object.fromEntries(cappedEntries)
|
|
42760
42819
|
};
|
|
42761
42820
|
atomicWriteJson(cacheFilePath, {
|
|
42762
|
-
version:
|
|
42821
|
+
version: 2,
|
|
42763
42822
|
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord$1(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
42764
42823
|
});
|
|
42765
42824
|
}
|
|
@@ -42810,23 +42869,6 @@ const resolveTsConfigRelativePath = (rootDirectory) => {
|
|
|
42810
42869
|
for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
|
|
42811
42870
|
return null;
|
|
42812
42871
|
};
|
|
42813
|
-
const DERIVED_STATE_RULE_PRIORITY = new Map([
|
|
42814
|
-
["no-initialize-state", 0],
|
|
42815
|
-
["no-adjust-state-on-prop-change", 1],
|
|
42816
|
-
["no-derived-state", 2],
|
|
42817
|
-
["no-derived-state-effect", 3]
|
|
42818
|
-
]);
|
|
42819
|
-
const doDiagnosticSpansOverlap = (first, second) => {
|
|
42820
|
-
if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
|
|
42821
|
-
if (first.offset !== void 0 && second.offset !== void 0) {
|
|
42822
|
-
const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
|
|
42823
|
-
const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
|
|
42824
|
-
return first.offset < secondEnd && second.offset < firstEnd;
|
|
42825
|
-
}
|
|
42826
|
-
const firstEndLine = first.endLine ?? first.line;
|
|
42827
|
-
const secondEndLine = second.endLine ?? second.line;
|
|
42828
|
-
return first.line <= secondEndLine && second.line <= firstEndLine;
|
|
42829
|
-
};
|
|
42830
42872
|
const dedupeDiagnostics = (diagnostics) => {
|
|
42831
42873
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
42832
42874
|
const uniqueDiagnostics = [];
|
|
@@ -42834,16 +42876,6 @@ const dedupeDiagnostics = (diagnostics) => {
|
|
|
42834
42876
|
const key = `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}\u0000${diagnostic.plugin}\u0000${diagnostic.rule}\u0000${diagnostic.severity}\u0000${diagnostic.message}`;
|
|
42835
42877
|
if (seenKeys.has(key)) continue;
|
|
42836
42878
|
seenKeys.add(key);
|
|
42837
|
-
const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
|
|
42838
|
-
if (priority !== void 0) {
|
|
42839
|
-
const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && candidate.severity === diagnostic.severity && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
|
|
42840
|
-
if (overlappingDiagnosticIndex >= 0) {
|
|
42841
|
-
const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
|
|
42842
|
-
const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
|
|
42843
|
-
if (existingPriority !== void 0 && priority < existingPriority) uniqueDiagnostics[overlappingDiagnosticIndex] = diagnostic;
|
|
42844
|
-
continue;
|
|
42845
|
-
}
|
|
42846
|
-
}
|
|
42847
42879
|
uniqueDiagnostics.push(diagnostic);
|
|
42848
42880
|
}
|
|
42849
42881
|
return uniqueDiagnostics;
|
|
@@ -43643,11 +43675,11 @@ const buildRelatedLocations = (labels, filePath) => {
|
|
|
43643
43675
|
}
|
|
43644
43676
|
return related;
|
|
43645
43677
|
};
|
|
43646
|
-
const
|
|
43647
|
-
|
|
43648
|
-
|
|
43649
|
-
|
|
43650
|
-
|
|
43678
|
+
const isOxlintSpan = (value) => isRecord$1(value) && typeof value.offset === "number" && typeof value.length === "number" && typeof value.line === "number" && typeof value.column === "number";
|
|
43679
|
+
const isOxlintLabel = (value) => isRecord$1(value) && isOxlintSpan(value.span);
|
|
43680
|
+
const isMappableOxlintDiagnostic = (value) => isRecord$1(value) && typeof value.code === "string" && value.code.length > 0 && typeof value.filename === "string" && value.filename.length > 0 && (value.severity === "warning" || value.severity === "error") && Array.isArray(value.labels) && value.labels.every(isOxlintLabel);
|
|
43681
|
+
const isEngineFailureDiagnostic = (value) => !isRecord$1(value) || typeof value.filename !== "string" || value.filename.length === 0;
|
|
43682
|
+
const isOxlintOutput = (value) => isRecord$1(value) && Array.isArray(value.diagnostics);
|
|
43651
43683
|
/**
|
|
43652
43684
|
* Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
|
|
43653
43685
|
* Tolerates the leading-notice-line shape oxlint sometimes prints
|
|
@@ -43666,6 +43698,8 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43666
43698
|
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
43667
43699
|
}
|
|
43668
43700
|
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
43701
|
+
const engineFailureDiagnostic = parsed.diagnostics.find(isEngineFailureDiagnostic);
|
|
43702
|
+
if (engineFailureDiagnostic !== void 0) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: JSON.stringify(engineFailureDiagnostic).slice(0, 600) }) });
|
|
43669
43703
|
const minifiedFileCache = /* @__PURE__ */ new Map();
|
|
43670
43704
|
const sourceBufferCache = /* @__PURE__ */ new Map();
|
|
43671
43705
|
const sourceLinesCache = /* @__PURE__ */ new Map();
|
|
@@ -43700,7 +43734,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43700
43734
|
minifiedFileCache.set(absolutePath, minified);
|
|
43701
43735
|
return minified;
|
|
43702
43736
|
};
|
|
43703
|
-
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic
|
|
43737
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
43704
43738
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
43705
43739
|
const primaryLabel = diagnostic.labels[0];
|
|
43706
43740
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
@@ -45535,7 +45569,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
45535
45569
|
respectInlineDisables: input.respectInlineDisables,
|
|
45536
45570
|
showWarnings
|
|
45537
45571
|
});
|
|
45538
|
-
const
|
|
45572
|
+
const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)));
|
|
45573
|
+
const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
|
|
45539
45574
|
const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
|
|
45540
45575
|
...checkReducedMotion(scanDirectory),
|
|
45541
45576
|
...checkPnpmHardening(scanDirectory),
|
|
@@ -45664,7 +45699,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
45664
45699
|
});
|
|
45665
45700
|
return empty$4;
|
|
45666
45701
|
}))));
|
|
45667
|
-
const lintCollected = yield* runCollect(
|
|
45702
|
+
const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
|
|
45668
45703
|
onNone: () => set(lintFailure, {
|
|
45669
45704
|
didFail: true,
|
|
45670
45705
|
reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
|
|
@@ -45672,7 +45707,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
45672
45707
|
reasonKind: null
|
|
45673
45708
|
}).pipe(as([])),
|
|
45674
45709
|
onSome: succeed$2
|
|
45675
|
-
})));
|
|
45710
|
+
}))));
|
|
45711
|
+
yield* forEach$1(lintCollected, reporterService.emit, { discard: true });
|
|
45676
45712
|
const lintFailureState = yield* get$2(lintFailure);
|
|
45677
45713
|
yield* afterLint(lintFailureState.didFail);
|
|
45678
45714
|
if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
|
|
@@ -46266,10 +46302,11 @@ const computeDiagnosticDelta = (input) => {
|
|
|
46266
46302
|
* render diff-base mistakes (`GitBaseBranchInvalid` /
|
|
46267
46303
|
* `GitBaseBranchMissing`) as clean user errors instead of crashes.
|
|
46268
46304
|
*/
|
|
46269
|
-
const getDiffInfo = (directory, explicitBaseBranch) => runPromise(gen(function* () {
|
|
46305
|
+
const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runPromise(gen(function* () {
|
|
46270
46306
|
const selection = yield* (yield* Git).diffSelection({
|
|
46271
46307
|
directory,
|
|
46272
|
-
explicitBaseBranch
|
|
46308
|
+
explicitBaseBranch,
|
|
46309
|
+
includeUntracked
|
|
46273
46310
|
});
|
|
46274
46311
|
if (selection === null) return null;
|
|
46275
46312
|
return {
|
|
@@ -46605,6 +46642,43 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
|
|
|
46605
46642
|
//#region src/cli/utils/constants.ts
|
|
46606
46643
|
const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
|
|
46607
46644
|
const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
|
|
46645
|
+
const STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES = [
|
|
46646
|
+
".babelrc",
|
|
46647
|
+
".babelrc.json",
|
|
46648
|
+
".eslintignore",
|
|
46649
|
+
".eslintrc.json",
|
|
46650
|
+
".oxlintignore",
|
|
46651
|
+
"app.config.cjs",
|
|
46652
|
+
"app.config.js",
|
|
46653
|
+
"app.config.json",
|
|
46654
|
+
"app.config.mjs",
|
|
46655
|
+
"app.config.ts",
|
|
46656
|
+
"app.json",
|
|
46657
|
+
"babel.config.cjs",
|
|
46658
|
+
"babel.config.js",
|
|
46659
|
+
"babel.config.json",
|
|
46660
|
+
"babel.config.mjs",
|
|
46661
|
+
"eslint.config.cjs",
|
|
46662
|
+
"eslint.config.cts",
|
|
46663
|
+
"eslint.config.js",
|
|
46664
|
+
"eslint.config.mjs",
|
|
46665
|
+
"eslint.config.mts",
|
|
46666
|
+
"eslint.config.ts",
|
|
46667
|
+
"next.config.cjs",
|
|
46668
|
+
"next.config.js",
|
|
46669
|
+
"next.config.mjs",
|
|
46670
|
+
"next.config.ts",
|
|
46671
|
+
"oxlint.config.json",
|
|
46672
|
+
"react-doctor.config.json",
|
|
46673
|
+
"vite.config.cjs",
|
|
46674
|
+
"vite.config.cts",
|
|
46675
|
+
"vite.config.js",
|
|
46676
|
+
"vite.config.mjs",
|
|
46677
|
+
"vite.config.mts",
|
|
46678
|
+
"vite.config.ts",
|
|
46679
|
+
"vitest.config.js",
|
|
46680
|
+
"vitest.config.ts"
|
|
46681
|
+
];
|
|
46608
46682
|
const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
|
|
46609
46683
|
const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
|
|
46610
46684
|
const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
@@ -46619,6 +46693,7 @@ const METRIC = {
|
|
|
46619
46693
|
cliInvoked: "cli.invoked",
|
|
46620
46694
|
cliError: "cli.error",
|
|
46621
46695
|
cliEnvironmentError: "cli.env_error",
|
|
46696
|
+
stagedSnapshotDivergence: "staged.snapshot_divergence",
|
|
46622
46697
|
projectDetected: "project.detected",
|
|
46623
46698
|
projectPathSelected: "project.path_selected",
|
|
46624
46699
|
projectConfigSelected: "project.config_selected",
|
|
@@ -46699,7 +46774,7 @@ const makeNoopConsole = () => ({
|
|
|
46699
46774
|
});
|
|
46700
46775
|
//#endregion
|
|
46701
46776
|
//#region src/cli/utils/version.ts
|
|
46702
|
-
const VERSION = "0.7.6-dev.
|
|
46777
|
+
const VERSION = "0.7.6-dev.3f22fe3";
|
|
46703
46778
|
//#endregion
|
|
46704
46779
|
//#region src/cli/utils/json-mode.ts
|
|
46705
46780
|
let context = null;
|
|
@@ -47068,13 +47143,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
47068
47143
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
47069
47144
|
* standard `SENTRY_RELEASE` override.
|
|
47070
47145
|
*/
|
|
47071
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.
|
|
47146
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.3f22fe3`;
|
|
47072
47147
|
/**
|
|
47073
47148
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
47074
47149
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
47075
47150
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
47076
47151
|
*/
|
|
47077
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.
|
|
47152
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.3f22fe3") ? "development" : "production");
|
|
47078
47153
|
/**
|
|
47079
47154
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
47080
47155
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -47266,7 +47341,7 @@ const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
|
|
|
47266
47341
|
"fi"
|
|
47267
47342
|
].join(" ");
|
|
47268
47343
|
const PACKAGE_JSON_FILE_NAME = "package.json";
|
|
47269
|
-
const
|
|
47344
|
+
const runGitRaw = (projectRoot, args) => {
|
|
47270
47345
|
try {
|
|
47271
47346
|
return execFileSync("git", [...args], {
|
|
47272
47347
|
cwd: projectRoot,
|
|
@@ -47277,11 +47352,12 @@ const runGit = (projectRoot, args) => {
|
|
|
47277
47352
|
"ignore"
|
|
47278
47353
|
],
|
|
47279
47354
|
maxBuffer: RUN_GIT_MAX_BUFFER_BYTES
|
|
47280
|
-
})
|
|
47355
|
+
});
|
|
47281
47356
|
} catch {
|
|
47282
47357
|
return null;
|
|
47283
47358
|
}
|
|
47284
47359
|
};
|
|
47360
|
+
const runGit = (projectRoot, args) => runGitRaw(projectRoot, args)?.trim() ?? null;
|
|
47285
47361
|
const resolveGitPath = (baseDirectory, value) => Path.isAbsolute(value) ? value : Path.resolve(baseDirectory, value);
|
|
47286
47362
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
47287
47363
|
const getPackageJsonPath = (projectRoot) => Path.join(projectRoot, PACKAGE_JSON_FILE_NAME);
|
|
@@ -59152,6 +59228,17 @@ const buildRuleBlastRadii = (diagnostics) => buildSortedRuleGroups(diagnostics).
|
|
|
59152
59228
|
})).toSorted((left, right) => right.fileCount - left.fileCount);
|
|
59153
59229
|
const findMigrationScaleBuckets = (diagnostics) => buildRuleBlastRadii(diagnostics).filter((bucket) => bucket.fileCount >= 40);
|
|
59154
59230
|
//#endregion
|
|
59231
|
+
//#region src/cli/utils/has-lint-hard-failure.ts
|
|
59232
|
+
const hasLintHardFailure = (result) => result.skippedChecks.includes("lint");
|
|
59233
|
+
//#endregion
|
|
59234
|
+
//#region src/cli/utils/is-inspect-result-complete.ts
|
|
59235
|
+
const isInspectResultComplete = (result) => isScanComplete({
|
|
59236
|
+
analyzedFileCount: result.analyzedFiles?.length,
|
|
59237
|
+
scannedFileCount: result.scannedFileCount,
|
|
59238
|
+
skippedCheckCount: result.skippedChecks.length,
|
|
59239
|
+
skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
|
|
59240
|
+
});
|
|
59241
|
+
//#endregion
|
|
59155
59242
|
//#region src/cli/utils/scan-result-cache.ts
|
|
59156
59243
|
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
59157
59244
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
@@ -59557,15 +59644,12 @@ const buildOutcomeAttributes = (input) => {
|
|
|
59557
59644
|
const summary = summarizeDiagnostics(result.diagnostics);
|
|
59558
59645
|
const blockingLevel = resolveTelemetryBlocking(input.userConfig);
|
|
59559
59646
|
const gateDiagnostics = filterDiagnosticsForSurface(result.diagnostics, "ciFailure", input.userConfig);
|
|
59647
|
+
const complete = isInspectResultComplete(result);
|
|
59648
|
+
const didLintHardFail = hasLintHardFailure(result);
|
|
59649
|
+
const failsOnHardFailure = didLintHardFail && blockingLevel !== "none";
|
|
59560
59650
|
const wouldBlock = !input.scoreOnly && !input.gateExempt && shouldBlockCi(gateDiagnostics, blockingLevel);
|
|
59561
|
-
const complete = isScanComplete({
|
|
59562
|
-
analyzedFileCount: result.analyzedFiles?.length,
|
|
59563
|
-
scannedFileCount: result.scannedFileCount,
|
|
59564
|
-
skippedCheckCount: result.skippedChecks.length,
|
|
59565
|
-
skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
|
|
59566
|
-
});
|
|
59567
59651
|
const isClean = result.diagnostics.length === 0 && complete;
|
|
59568
|
-
const outcome = wouldBlock ? "blocked" : isClean ? "clean" : "ok";
|
|
59652
|
+
const outcome = didLintHardFail ? "error" : wouldBlock ? "blocked" : isClean ? "clean" : "ok";
|
|
59569
59653
|
const firings = summarizeRuleFirings(result.diagnostics);
|
|
59570
59654
|
const countByRule = /* @__PURE__ */ new Map();
|
|
59571
59655
|
const countByCategory = /* @__PURE__ */ new Map();
|
|
@@ -59610,7 +59694,7 @@ const buildOutcomeAttributes = (input) => {
|
|
|
59610
59694
|
const attributes = {
|
|
59611
59695
|
...withNamespace("outcome", {
|
|
59612
59696
|
status: outcome,
|
|
59613
|
-
exitCode: wouldBlock ? 1 : 0,
|
|
59697
|
+
exitCode: failsOnHardFailure || wouldBlock ? 1 : 0,
|
|
59614
59698
|
wouldBlock,
|
|
59615
59699
|
blocking: blockingLevel,
|
|
59616
59700
|
clean: isClean,
|
|
@@ -63792,10 +63876,15 @@ const warnIfAiTrainingEnvironment = () => {
|
|
|
63792
63876
|
//#region src/cli/utils/validate-mode-flags.ts
|
|
63793
63877
|
const usedDiffAlias = (flags) => flags.diff !== void 0 && flags.diff !== false && flags.diff !== "false" && flags.diff !== "";
|
|
63794
63878
|
const usedScope = (flags) => typeof flags.scope === "string" && flags.scope.length > 0;
|
|
63879
|
+
const validateIncludeUntrackedScope = (includeUntracked, scope) => {
|
|
63880
|
+
if (!includeUntracked || scope !== void 0 && scope !== "full") return;
|
|
63881
|
+
throw new CliInputError("--include-untracked requires the files, changed, or lines scope (via --scope, --diff, or config).");
|
|
63882
|
+
};
|
|
63795
63883
|
const validateModeFlags = (flags) => {
|
|
63796
63884
|
if (usedScope(flags) && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --scope and --diff; --diff is the deprecated alias.");
|
|
63797
63885
|
if (flags.staged && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --staged and --diff; pick one mode.");
|
|
63798
63886
|
if (flags.staged && (flags.scope === "full" || flags.scope === "changed")) throw new CliInputError(`Cannot combine --staged with --scope ${flags.scope}; use --scope files or --scope lines, or drop --scope.`);
|
|
63887
|
+
if (flags.includeUntracked && flags.staged) throw new CliInputError("Cannot combine --include-untracked with --staged; the git index never holds untracked files.");
|
|
63799
63888
|
if (flags.score && flags.json) throw new CliInputError("Cannot combine --score and --json; pick one output mode.");
|
|
63800
63889
|
if (flags.score && flags.telemetry === false) throw new CliInputError("Cannot combine --score with --no-telemetry; --score prints the score that --no-telemetry disables.");
|
|
63801
63890
|
if (flags.debug && (flags.score === false || flags.telemetry === false)) {
|
|
@@ -63804,6 +63893,39 @@ const validateModeFlags = (flags) => {
|
|
|
63804
63893
|
}
|
|
63805
63894
|
};
|
|
63806
63895
|
//#endregion
|
|
63896
|
+
//#region src/cli/utils/parse-staged-snapshot-divergences.ts
|
|
63897
|
+
const SNAPSHOT_CONFIG_FILENAMES = new Set([...STAGED_FILES_PROJECT_CONFIG_FILENAMES, ...STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES]);
|
|
63898
|
+
const parseStagedSnapshotDivergences = (statusOutput) => {
|
|
63899
|
+
const divergentConfigFiles = /* @__PURE__ */ new Set();
|
|
63900
|
+
const statusEntries = statusOutput.split("\0").filter((entry) => entry.length > 0);
|
|
63901
|
+
for (let entryIndex = 0; entryIndex < statusEntries.length; entryIndex += 1) {
|
|
63902
|
+
const entry = statusEntries[entryIndex];
|
|
63903
|
+
const [indexStatus, worktreeStatus] = entry;
|
|
63904
|
+
const filePath = entry.slice(3);
|
|
63905
|
+
const hasRenameOrCopySource = indexStatus === "R" || indexStatus === "C" || worktreeStatus === "R" || worktreeStatus === "C";
|
|
63906
|
+
const sourcePath = hasRenameOrCopySource ? statusEntries[entryIndex + 1] : void 0;
|
|
63907
|
+
const worktreePaths = worktreeStatus === "R" || worktreeStatus === "C" ? [filePath, sourcePath] : [filePath];
|
|
63908
|
+
if (worktreeStatus !== " ") {
|
|
63909
|
+
for (const worktreePath of worktreePaths) if (worktreePath && SNAPSHOT_CONFIG_FILENAMES.has(Path.basename(worktreePath))) divergentConfigFiles.add(worktreePath);
|
|
63910
|
+
}
|
|
63911
|
+
if (hasRenameOrCopySource) entryIndex += 1;
|
|
63912
|
+
}
|
|
63913
|
+
return [...divergentConfigFiles].sort();
|
|
63914
|
+
};
|
|
63915
|
+
//#endregion
|
|
63916
|
+
//#region src/cli/utils/find-staged-snapshot-divergences.ts
|
|
63917
|
+
const findStagedSnapshotDivergences = (directory) => {
|
|
63918
|
+
const statusOutput = runGitRaw(directory, [
|
|
63919
|
+
"status",
|
|
63920
|
+
"--porcelain=v1",
|
|
63921
|
+
"-z",
|
|
63922
|
+
"--untracked-files=all",
|
|
63923
|
+
"--ignored=matching"
|
|
63924
|
+
]);
|
|
63925
|
+
if (statusOutput === null) return null;
|
|
63926
|
+
return parseStagedSnapshotDivergences(statusOutput);
|
|
63927
|
+
};
|
|
63928
|
+
//#endregion
|
|
63807
63929
|
//#region src/cli/commands/inspect.ts
|
|
63808
63930
|
const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
|
|
63809
63931
|
if (categoryFilters.size === 0) return [...completedScans];
|
|
@@ -63818,9 +63940,14 @@ const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
|
|
|
63818
63940
|
/**
|
|
63819
63941
|
* Post-scan finalization shared by the staged-arm and project-loop
|
|
63820
63942
|
* paths of `inspectAction`: emit the JSON report (when in JSON mode)
|
|
63821
|
-
* and set `process.exitCode = 1` when
|
|
63822
|
-
*
|
|
63823
|
-
*
|
|
63943
|
+
* and set `process.exitCode = 1` when any scan's lint pass hard-failed
|
|
63944
|
+
* (an engine/plugin/binding failure destroys the findings, so success
|
|
63945
|
+
* would be a false clean) or a diagnostic at or above the `--blocking`
|
|
63946
|
+
* threshold (default `"error"`) reaches the `ciFailure` surface.
|
|
63947
|
+
* `--blocking none` keeps the scan advisory (always exits 0), and
|
|
63948
|
+
* fail-open degradations — `--no-lint`, `--max-duration` truncation,
|
|
63949
|
+
* supply-chain/security skips — stay advisory too, surfaced through
|
|
63950
|
+
* `complete: false` in the JSON report.
|
|
63824
63951
|
*/
|
|
63825
63952
|
const finalizeScans = (input) => {
|
|
63826
63953
|
const baselineDeltas = input.completedScans.flatMap((scan) => scan.result.baselineDelta ? [scan.result.baselineDelta] : []);
|
|
@@ -63850,8 +63977,13 @@ const finalizeScans = (input) => {
|
|
|
63850
63977
|
baselineDegraded
|
|
63851
63978
|
}));
|
|
63852
63979
|
}
|
|
63980
|
+
const blockingLevel = resolveBlockingLevel(input.flags, input.userConfig);
|
|
63981
|
+
if (input.completedScans.some(({ result }) => hasLintHardFailure(result)) && blockingLevel !== "none") {
|
|
63982
|
+
process.exitCode = 1;
|
|
63983
|
+
return;
|
|
63984
|
+
}
|
|
63853
63985
|
if (input.isScoreOnly || baselineDegraded) return;
|
|
63854
|
-
if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"),
|
|
63986
|
+
if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), blockingLevel)) process.exitCode = 1;
|
|
63855
63987
|
};
|
|
63856
63988
|
const buildChangedFilesDiffInfo = (changedFiles) => ({
|
|
63857
63989
|
currentBranch: process.env.GITHUB_HEAD_REF?.trim() || null,
|
|
@@ -63889,6 +64021,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
63889
64021
|
recordCount(METRIC.cliInvoked, 1, { command: "inspect" });
|
|
63890
64022
|
try {
|
|
63891
64023
|
validateModeFlags(flags);
|
|
64024
|
+
if (flags.staged) setJsonReportMode("staged");
|
|
63892
64025
|
await maybeMigrateLegacyConfig(requestedDirectory, {
|
|
63893
64026
|
isQuiet,
|
|
63894
64027
|
isStaged: Boolean(flags.staged)
|
|
@@ -63904,6 +64037,14 @@ const inspectAction = async (directory, flags) => {
|
|
|
63904
64037
|
cliLogger.dim(`Redirected to ${highlighter.info(toRelativePath(resolvedDirectory, requestedDirectory))} via react-doctor config "rootDir".`);
|
|
63905
64038
|
cliLogger.break();
|
|
63906
64039
|
}
|
|
64040
|
+
if (flags.staged) {
|
|
64041
|
+
const divergentConfigFiles = findStagedSnapshotDivergences(resolvedDirectory);
|
|
64042
|
+
if (divergentConfigFiles === null) throw new CliInputError("Could not verify that staged configuration matches the worktree. Run the command from a Git worktree with Git available.");
|
|
64043
|
+
if (divergentConfigFiles.length > 0) {
|
|
64044
|
+
recordCount(METRIC.stagedSnapshotDivergence, 1, { divergentInputCount: divergentConfigFiles.length });
|
|
64045
|
+
throw new CliInputError(`Cannot scan staged files while configuration differs between the index and worktree: ${divergentConfigFiles.join(", ")}. Stage or restore those files, then rerun react-doctor --staged.`);
|
|
64046
|
+
}
|
|
64047
|
+
}
|
|
63907
64048
|
const explainArgument = flags.explain;
|
|
63908
64049
|
if (explainArgument !== void 0) {
|
|
63909
64050
|
await runExplain(explainArgument, {
|
|
@@ -63924,7 +64065,6 @@ const inspectAction = async (directory, flags) => {
|
|
|
63924
64065
|
json: flags.json
|
|
63925
64066
|
});
|
|
63926
64067
|
if (flags.staged) {
|
|
63927
|
-
setJsonReportMode("staged");
|
|
63928
64068
|
const stagedFiles = await getStagedSourceFiles(resolvedDirectory);
|
|
63929
64069
|
if (stagedFiles.length === 0) {
|
|
63930
64070
|
if (isJsonMode) writeJsonReport(buildJsonReport({
|
|
@@ -64005,13 +64145,15 @@ const inspectAction = async (directory, flags) => {
|
|
|
64005
64145
|
const projectDirectories = await selectProjects(resolvedDirectory, flags.project, skipPrompts, userConfig?.projects);
|
|
64006
64146
|
const changedFilesDiffInfo = flags.changedFilesFrom ? buildChangedFilesDiffInfo(readChangedFilesFrom(Path.resolve(flags.changedFilesFrom))) : null;
|
|
64007
64147
|
const requestedScope = resolveScope(flags, userConfig);
|
|
64148
|
+
const includeUntracked = flags.includeUntracked ?? false;
|
|
64008
64149
|
const scopeRequest = requestedScope.scope === void 0 && changedFilesDiffInfo !== null ? {
|
|
64009
64150
|
...requestedScope,
|
|
64010
64151
|
scope: "changed"
|
|
64011
64152
|
} : requestedScope;
|
|
64153
|
+
validateIncludeUntrackedScope(includeUntracked, scopeRequest.scope);
|
|
64012
64154
|
const wantsDiffMode = scopeRequest.scope !== void 0 && scopeRequest.scope !== "full";
|
|
64013
64155
|
const shouldDetectDiff = changedFilesDiffInfo === null && (wantsDiffMode || scopeRequest.scope === void 0 && !skipPrompts && !isQuiet);
|
|
64014
|
-
const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base) : null);
|
|
64156
|
+
const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base, includeUntracked) : null);
|
|
64015
64157
|
const scope = await finalizeScope({
|
|
64016
64158
|
requested: scopeRequest,
|
|
64017
64159
|
diffInfo,
|
|
@@ -64025,7 +64167,8 @@ const inspectAction = async (directory, flags) => {
|
|
|
64025
64167
|
const changedLineRanges = scope === "lines" && diffInfo !== null && (diffInfo.isCurrentChanges || linesBaseRef !== null) && diffInfo !== null ? await getChangedLineRanges({
|
|
64026
64168
|
directory: resolvedDirectory,
|
|
64027
64169
|
baseRef: linesBaseRef ?? void 0,
|
|
64028
|
-
files: [...diffInfo.changedFiles]
|
|
64170
|
+
files: [...diffInfo.changedFiles],
|
|
64171
|
+
includeUntracked
|
|
64029
64172
|
}) : null;
|
|
64030
64173
|
if (scope === "lines" && changedLineRanges === null && !isQuiet) {
|
|
64031
64174
|
cliLogger.warn("Could not determine changed lines (no base ref or git diff failed); reporting all issues in changed files.");
|
|
@@ -64958,6 +65101,7 @@ const ROOT_FLAG_SPEC = {
|
|
|
64958
65101
|
"--dead-code",
|
|
64959
65102
|
"--debug",
|
|
64960
65103
|
"--help",
|
|
65104
|
+
"--include-untracked",
|
|
64961
65105
|
"--json",
|
|
64962
65106
|
"--json-compact",
|
|
64963
65107
|
"--lint",
|
|
@@ -65221,7 +65365,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
65221
65365
|
${highlighter.info(CI_URL)}
|
|
65222
65366
|
`;
|
|
65223
65367
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
65224
|
-
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 supported JS/TS source to scan/report: full (default), files, changed (only new issues vs base), or lines (issues whose source spans touch 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);
|
|
65368
|
+
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 supported JS/TS source to scan/report: full (default), files, changed (only new issues vs base), or lines (issues whose source spans touch changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").option("--include-untracked", "with --scope files/changed/lines, also scan ordinary untracked files (respects .gitignore)").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);
|
|
65225
65369
|
program.action(inspectAction);
|
|
65226
65370
|
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));
|
|
65227
65371
|
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);
|
|
@@ -65271,4 +65415,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
65271
65415
|
export {};
|
|
65272
65416
|
|
|
65273
65417
|
//# sourceMappingURL=cli.js.map
|
|
65274
|
-
//# debugId=
|
|
65418
|
+
//# debugId=4c1e681e-4dee-5ffd-a4ba-69b876f47147
|
package/dist/index.d.ts
CHANGED
|
@@ -10256,7 +10256,7 @@ declare const buildJsonReport: (input: BuildJsonReportInput) => JsonReportV3$1;
|
|
|
10256
10256
|
* render diff-base mistakes (`GitBaseBranchInvalid` /
|
|
10257
10257
|
* `GitBaseBranchMissing`) as clean user errors instead of crashes.
|
|
10258
10258
|
*/
|
|
10259
|
-
declare const getDiffInfo: (directory: string, explicitBaseBranch?: string) => Promise<DiffInfo | null>;
|
|
10259
|
+
declare const getDiffInfo: (directory: string, explicitBaseBranch?: string, includeUntracked?: boolean) => Promise<DiffInfo | null>;
|
|
10260
10260
|
declare const filterSourceFiles: (filePaths: string[]) => string[];
|
|
10261
10261
|
/**
|
|
10262
10262
|
* Programmatic façade over `Git.changedLineRanges` (the `lines` scope). Diffs
|
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]="4da79386-f10f-5bae-95ac-7333b2d6c4e8")}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 reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, FRAMEWORK_TOKENS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
@@ -36366,6 +36366,49 @@ const assignFixGroups = (diagnostics) => {
|
|
|
36366
36366
|
};
|
|
36367
36367
|
});
|
|
36368
36368
|
};
|
|
36369
|
+
const DERIVED_STATE_RULE_PRIORITY = new Map([
|
|
36370
|
+
["no-initialize-state", 0],
|
|
36371
|
+
["no-adjust-state-on-prop-change", 1],
|
|
36372
|
+
["no-derived-state", 2],
|
|
36373
|
+
["no-derived-state-effect", 3]
|
|
36374
|
+
]);
|
|
36375
|
+
const buildDiagnosticSiteKey = (diagnostic) => `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}`;
|
|
36376
|
+
const isReactDoctorRulesOfHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-doctor" && diagnostic.rule === "rules-of-hooks";
|
|
36377
|
+
const isReactCompilerHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-hooks-js" && diagnostic.rule === "hooks";
|
|
36378
|
+
const doDiagnosticSpansOverlap = (first, second) => {
|
|
36379
|
+
if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
|
|
36380
|
+
if (first.offset !== void 0 && second.offset !== void 0) {
|
|
36381
|
+
const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
|
|
36382
|
+
const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
|
|
36383
|
+
return first.offset < secondEnd && second.offset < firstEnd;
|
|
36384
|
+
}
|
|
36385
|
+
const firstEndLine = first.endLine ?? first.line;
|
|
36386
|
+
const secondEndLine = second.endLine ?? second.line;
|
|
36387
|
+
return first.line <= secondEndLine && second.line <= firstEndLine;
|
|
36388
|
+
};
|
|
36389
|
+
const keepHighestSeverity = (survivingDiagnostic, collapsedDiagnostic) => collapsedDiagnostic.severity === "error" && survivingDiagnostic.severity !== "error" ? {
|
|
36390
|
+
...survivingDiagnostic,
|
|
36391
|
+
severity: "error"
|
|
36392
|
+
} : survivingDiagnostic;
|
|
36393
|
+
const dedupeRelatedDiagnostics = (diagnostics) => {
|
|
36394
|
+
const reactDoctorHookSites = new Set(diagnostics.filter(isReactDoctorRulesOfHooksDiagnostic).map(buildDiagnosticSiteKey));
|
|
36395
|
+
const uniqueDiagnostics = [];
|
|
36396
|
+
for (const diagnostic of diagnostics) {
|
|
36397
|
+
if (isReactCompilerHooksDiagnostic(diagnostic) && reactDoctorHookSites.has(buildDiagnosticSiteKey(diagnostic))) continue;
|
|
36398
|
+
const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
|
|
36399
|
+
if (priority !== void 0) {
|
|
36400
|
+
const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
|
|
36401
|
+
if (overlappingDiagnosticIndex >= 0) {
|
|
36402
|
+
const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
|
|
36403
|
+
const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
|
|
36404
|
+
if (existingDiagnostic && existingPriority !== void 0) uniqueDiagnostics[overlappingDiagnosticIndex] = priority < existingPriority ? keepHighestSeverity(diagnostic, existingDiagnostic) : keepHighestSeverity(existingDiagnostic, diagnostic);
|
|
36405
|
+
continue;
|
|
36406
|
+
}
|
|
36407
|
+
}
|
|
36408
|
+
uniqueDiagnostics.push(diagnostic);
|
|
36409
|
+
}
|
|
36410
|
+
return uniqueDiagnostics;
|
|
36411
|
+
};
|
|
36369
36412
|
const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
36370
36413
|
const sortDiagnosticsStable = (diagnostics) => [...diagnostics].sort((left, right) => compareStrings(left.filePath, right.filePath) || left.line - right.line || left.column - right.column || compareStrings(left.plugin, right.plugin) || compareStrings(left.rule, right.rule) || compareStrings(left.severity, right.severity) || compareStrings(left.message, right.message));
|
|
36371
36414
|
const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
|
|
@@ -38579,6 +38622,7 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
38579
38622
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
38580
38623
|
};
|
|
38581
38624
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
38625
|
+
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
38582
38626
|
/**
|
|
38583
38627
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
38584
38628
|
* behind a `Context.Service`. The production layer (`layerNode`)
|
|
@@ -38649,6 +38693,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38649
38693
|
args,
|
|
38650
38694
|
directory
|
|
38651
38695
|
});
|
|
38696
|
+
const listUntrackedFilePaths = (directory, includePaths = []) => runGit(directory, [
|
|
38697
|
+
"ls-files",
|
|
38698
|
+
"-z",
|
|
38699
|
+
"--others",
|
|
38700
|
+
"--exclude-standard",
|
|
38701
|
+
...includePaths.length > 0 ? ["--", ...includePaths] : []
|
|
38702
|
+
]).pipe(map$3((result) => result.status === 0 ? splitNullSeparated(result.stdout) : null));
|
|
38703
|
+
const mergeUntracked = (directory, trackedFilePaths, includeUntracked) => includeUntracked ? listUntrackedFilePaths(directory).pipe(map$3((untracked) => untracked === null ? trackedFilePaths : [...new Set([...trackedFilePaths, ...untracked])])) : succeed$2(trackedFilePaths);
|
|
38652
38704
|
const currentBranch = (directory) => runGit(directory, [
|
|
38653
38705
|
"rev-parse",
|
|
38654
38706
|
"--abbrev-ref",
|
|
@@ -38771,7 +38823,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38771
38823
|
githubViewerPermission,
|
|
38772
38824
|
branchExists,
|
|
38773
38825
|
mergeBase,
|
|
38774
|
-
diffSelection: ({ directory, explicitBaseBranch }) => gen(function* () {
|
|
38826
|
+
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
38775
38827
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
38776
38828
|
if (explicitBaseBranch !== void 0) {
|
|
38777
38829
|
const range = parseGitDiffRange(explicitBaseBranch);
|
|
@@ -38800,7 +38852,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38800
38852
|
"HEAD"
|
|
38801
38853
|
]);
|
|
38802
38854
|
if (uncommitted.status !== 0) return null;
|
|
38803
|
-
const files = splitNullSeparated(uncommitted.stdout);
|
|
38855
|
+
const files = yield* mergeUntracked(directory, splitNullSeparated(uncommitted.stdout), includeUntracked);
|
|
38804
38856
|
if (files.length === 0) return null;
|
|
38805
38857
|
return {
|
|
38806
38858
|
currentBranch: resolvedCurrentBranch,
|
|
@@ -38830,7 +38882,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38830
38882
|
currentBranch: resolvedCurrentBranch,
|
|
38831
38883
|
baseBranch,
|
|
38832
38884
|
diffBaseRef: mergeBaseRef,
|
|
38833
|
-
changedFiles: splitNullSeparated(diff.stdout),
|
|
38885
|
+
changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
|
|
38834
38886
|
isCurrentChanges: false
|
|
38835
38887
|
};
|
|
38836
38888
|
}).pipe(withSpan("Git.diffSelection")),
|
|
@@ -38876,7 +38928,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38876
38928
|
stdout: result.stdout
|
|
38877
38929
|
};
|
|
38878
38930
|
}).pipe(withSpan("Git.grep")),
|
|
38879
|
-
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
38931
|
+
changedLineRanges: ({ directory, baseRef, cached, files, includeUntracked = false }) => gen(function* () {
|
|
38880
38932
|
if (files.length === 0) return [];
|
|
38881
38933
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
38882
38934
|
const result = yield* runGit(directory, [
|
|
@@ -38890,7 +38942,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38890
38942
|
...files
|
|
38891
38943
|
]);
|
|
38892
38944
|
if (result.status !== 0) return null;
|
|
38893
|
-
|
|
38945
|
+
const changedLineRanges = parseChangedLineRanges(result.stdout);
|
|
38946
|
+
if (cached || !includeUntracked) return changedLineRanges;
|
|
38947
|
+
const untrackedFilePaths = yield* listUntrackedFilePaths(directory, files);
|
|
38948
|
+
if (untrackedFilePaths === null) return changedLineRanges;
|
|
38949
|
+
return [...changedLineRanges, ...untrackedFilePaths.map((file) => ({
|
|
38950
|
+
file,
|
|
38951
|
+
ranges: [[1, UNTRACKED_FILE_LAST_LINE]]
|
|
38952
|
+
}))];
|
|
38894
38953
|
}).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
|
|
38895
38954
|
});
|
|
38896
38955
|
})).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
|
|
@@ -39475,13 +39534,13 @@ const encodeSidecarFileEntry = (entry) => ({
|
|
|
39475
39534
|
diagnostics: entry.diagnostics
|
|
39476
39535
|
});
|
|
39477
39536
|
const emptyCache = () => ({
|
|
39478
|
-
version:
|
|
39537
|
+
version: 2,
|
|
39479
39538
|
rulesets: {}
|
|
39480
39539
|
});
|
|
39481
39540
|
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
39482
39541
|
const entries = /* @__PURE__ */ new Map();
|
|
39483
39542
|
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39484
|
-
if (persisted.version !==
|
|
39543
|
+
if (persisted.version !== 2 || !isRecord(persisted.rulesets)) return entries;
|
|
39485
39544
|
const bucket = persisted.rulesets[rulesetHash];
|
|
39486
39545
|
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
39487
39546
|
for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
|
|
@@ -39501,7 +39560,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
39501
39560
|
},
|
|
39502
39561
|
persist: () => {
|
|
39503
39562
|
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39504
|
-
const rulesets = onDisk.version ===
|
|
39563
|
+
const rulesets = onDisk.version === 2 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
39505
39564
|
const existingBucket = rulesets[rulesetHash];
|
|
39506
39565
|
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
39507
39566
|
const ourFiles = {};
|
|
@@ -39515,7 +39574,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
39515
39574
|
files: Object.fromEntries(cappedEntries)
|
|
39516
39575
|
};
|
|
39517
39576
|
atomicWriteJson(cacheFilePath, {
|
|
39518
|
-
version:
|
|
39577
|
+
version: 2,
|
|
39519
39578
|
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
39520
39579
|
});
|
|
39521
39580
|
}
|
|
@@ -39566,23 +39625,6 @@ const resolveTsConfigRelativePath = (rootDirectory) => {
|
|
|
39566
39625
|
for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
|
|
39567
39626
|
return null;
|
|
39568
39627
|
};
|
|
39569
|
-
const DERIVED_STATE_RULE_PRIORITY = new Map([
|
|
39570
|
-
["no-initialize-state", 0],
|
|
39571
|
-
["no-adjust-state-on-prop-change", 1],
|
|
39572
|
-
["no-derived-state", 2],
|
|
39573
|
-
["no-derived-state-effect", 3]
|
|
39574
|
-
]);
|
|
39575
|
-
const doDiagnosticSpansOverlap = (first, second) => {
|
|
39576
|
-
if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
|
|
39577
|
-
if (first.offset !== void 0 && second.offset !== void 0) {
|
|
39578
|
-
const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
|
|
39579
|
-
const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
|
|
39580
|
-
return first.offset < secondEnd && second.offset < firstEnd;
|
|
39581
|
-
}
|
|
39582
|
-
const firstEndLine = first.endLine ?? first.line;
|
|
39583
|
-
const secondEndLine = second.endLine ?? second.line;
|
|
39584
|
-
return first.line <= secondEndLine && second.line <= firstEndLine;
|
|
39585
|
-
};
|
|
39586
39628
|
const dedupeDiagnostics = (diagnostics) => {
|
|
39587
39629
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
39588
39630
|
const uniqueDiagnostics = [];
|
|
@@ -39590,16 +39632,6 @@ const dedupeDiagnostics = (diagnostics) => {
|
|
|
39590
39632
|
const key = `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}\u0000${diagnostic.plugin}\u0000${diagnostic.rule}\u0000${diagnostic.severity}\u0000${diagnostic.message}`;
|
|
39591
39633
|
if (seenKeys.has(key)) continue;
|
|
39592
39634
|
seenKeys.add(key);
|
|
39593
|
-
const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
|
|
39594
|
-
if (priority !== void 0) {
|
|
39595
|
-
const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && candidate.severity === diagnostic.severity && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
|
|
39596
|
-
if (overlappingDiagnosticIndex >= 0) {
|
|
39597
|
-
const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
|
|
39598
|
-
const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
|
|
39599
|
-
if (existingPriority !== void 0 && priority < existingPriority) uniqueDiagnostics[overlappingDiagnosticIndex] = diagnostic;
|
|
39600
|
-
continue;
|
|
39601
|
-
}
|
|
39602
|
-
}
|
|
39603
39635
|
uniqueDiagnostics.push(diagnostic);
|
|
39604
39636
|
}
|
|
39605
39637
|
return uniqueDiagnostics;
|
|
@@ -40399,11 +40431,11 @@ const buildRelatedLocations = (labels, filePath) => {
|
|
|
40399
40431
|
}
|
|
40400
40432
|
return related;
|
|
40401
40433
|
};
|
|
40402
|
-
const
|
|
40403
|
-
|
|
40404
|
-
|
|
40405
|
-
|
|
40406
|
-
|
|
40434
|
+
const isOxlintSpan = (value) => isRecord(value) && typeof value.offset === "number" && typeof value.length === "number" && typeof value.line === "number" && typeof value.column === "number";
|
|
40435
|
+
const isOxlintLabel = (value) => isRecord(value) && isOxlintSpan(value.span);
|
|
40436
|
+
const isMappableOxlintDiagnostic = (value) => isRecord(value) && typeof value.code === "string" && value.code.length > 0 && typeof value.filename === "string" && value.filename.length > 0 && (value.severity === "warning" || value.severity === "error") && Array.isArray(value.labels) && value.labels.every(isOxlintLabel);
|
|
40437
|
+
const isEngineFailureDiagnostic = (value) => !isRecord(value) || typeof value.filename !== "string" || value.filename.length === 0;
|
|
40438
|
+
const isOxlintOutput = (value) => isRecord(value) && Array.isArray(value.diagnostics);
|
|
40407
40439
|
/**
|
|
40408
40440
|
* Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
|
|
40409
40441
|
* Tolerates the leading-notice-line shape oxlint sometimes prints
|
|
@@ -40422,6 +40454,8 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40422
40454
|
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
40423
40455
|
}
|
|
40424
40456
|
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
40457
|
+
const engineFailureDiagnostic = parsed.diagnostics.find(isEngineFailureDiagnostic);
|
|
40458
|
+
if (engineFailureDiagnostic !== void 0) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: JSON.stringify(engineFailureDiagnostic).slice(0, 600) }) });
|
|
40425
40459
|
const minifiedFileCache = /* @__PURE__ */ new Map();
|
|
40426
40460
|
const sourceBufferCache = /* @__PURE__ */ new Map();
|
|
40427
40461
|
const sourceLinesCache = /* @__PURE__ */ new Map();
|
|
@@ -40456,7 +40490,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40456
40490
|
minifiedFileCache.set(absolutePath, minified);
|
|
40457
40491
|
return minified;
|
|
40458
40492
|
};
|
|
40459
|
-
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic
|
|
40493
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
40460
40494
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
40461
40495
|
const primaryLabel = diagnostic.labels[0];
|
|
40462
40496
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
@@ -42291,7 +42325,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
42291
42325
|
respectInlineDisables: input.respectInlineDisables,
|
|
42292
42326
|
showWarnings
|
|
42293
42327
|
});
|
|
42294
|
-
const
|
|
42328
|
+
const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)));
|
|
42329
|
+
const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
|
|
42295
42330
|
const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
|
|
42296
42331
|
...checkReducedMotion(scanDirectory),
|
|
42297
42332
|
...checkPnpmHardening(scanDirectory),
|
|
@@ -42420,7 +42455,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
42420
42455
|
});
|
|
42421
42456
|
return empty$4;
|
|
42422
42457
|
}))));
|
|
42423
|
-
const lintCollected = yield* runCollect(
|
|
42458
|
+
const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
|
|
42424
42459
|
onNone: () => set(lintFailure, {
|
|
42425
42460
|
didFail: true,
|
|
42426
42461
|
reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
|
|
@@ -42428,7 +42463,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
42428
42463
|
reasonKind: null
|
|
42429
42464
|
}).pipe(as([])),
|
|
42430
42465
|
onSome: succeed$2
|
|
42431
|
-
})));
|
|
42466
|
+
}))));
|
|
42467
|
+
yield* forEach$1(lintCollected, reporterService.emit, { discard: true });
|
|
42432
42468
|
const lintFailureState = yield* get$2(lintFailure);
|
|
42433
42469
|
yield* afterLint(lintFailureState.didFail);
|
|
42434
42470
|
if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
|
|
@@ -42948,10 +42984,11 @@ const buildSkippedChecks = (input) => {
|
|
|
42948
42984
|
* render diff-base mistakes (`GitBaseBranchInvalid` /
|
|
42949
42985
|
* `GitBaseBranchMissing`) as clean user errors instead of crashes.
|
|
42950
42986
|
*/
|
|
42951
|
-
const getDiffInfo = (directory, explicitBaseBranch) => runPromise(gen(function* () {
|
|
42987
|
+
const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runPromise(gen(function* () {
|
|
42952
42988
|
const selection = yield* (yield* Git).diffSelection({
|
|
42953
42989
|
directory,
|
|
42954
|
-
explicitBaseBranch
|
|
42990
|
+
explicitBaseBranch,
|
|
42991
|
+
includeUntracked
|
|
42955
42992
|
});
|
|
42956
42993
|
if (selection === null) return null;
|
|
42957
42994
|
return {
|
|
@@ -43241,4 +43278,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
43241
43278
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
43242
43279
|
|
|
43243
43280
|
//# sourceMappingURL=index.js.map
|
|
43244
|
-
//# debugId=
|
|
43281
|
+
//# debugId=4da79386-f10f-5bae-95ac-7333b2d6c4e8
|
package/dist/lsp.js
CHANGED
|
@@ -36344,6 +36344,49 @@ const assignFixGroups = (diagnostics) => {
|
|
|
36344
36344
|
};
|
|
36345
36345
|
});
|
|
36346
36346
|
};
|
|
36347
|
+
const DERIVED_STATE_RULE_PRIORITY = new Map([
|
|
36348
|
+
["no-initialize-state", 0],
|
|
36349
|
+
["no-adjust-state-on-prop-change", 1],
|
|
36350
|
+
["no-derived-state", 2],
|
|
36351
|
+
["no-derived-state-effect", 3]
|
|
36352
|
+
]);
|
|
36353
|
+
const buildDiagnosticSiteKey = (diagnostic) => `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}`;
|
|
36354
|
+
const isReactDoctorRulesOfHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-doctor" && diagnostic.rule === "rules-of-hooks";
|
|
36355
|
+
const isReactCompilerHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-hooks-js" && diagnostic.rule === "hooks";
|
|
36356
|
+
const doDiagnosticSpansOverlap = (first, second) => {
|
|
36357
|
+
if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
|
|
36358
|
+
if (first.offset !== void 0 && second.offset !== void 0) {
|
|
36359
|
+
const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
|
|
36360
|
+
const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
|
|
36361
|
+
return first.offset < secondEnd && second.offset < firstEnd;
|
|
36362
|
+
}
|
|
36363
|
+
const firstEndLine = first.endLine ?? first.line;
|
|
36364
|
+
const secondEndLine = second.endLine ?? second.line;
|
|
36365
|
+
return first.line <= secondEndLine && second.line <= firstEndLine;
|
|
36366
|
+
};
|
|
36367
|
+
const keepHighestSeverity = (survivingDiagnostic, collapsedDiagnostic) => collapsedDiagnostic.severity === "error" && survivingDiagnostic.severity !== "error" ? {
|
|
36368
|
+
...survivingDiagnostic,
|
|
36369
|
+
severity: "error"
|
|
36370
|
+
} : survivingDiagnostic;
|
|
36371
|
+
const dedupeRelatedDiagnostics = (diagnostics) => {
|
|
36372
|
+
const reactDoctorHookSites = new Set(diagnostics.filter(isReactDoctorRulesOfHooksDiagnostic).map(buildDiagnosticSiteKey));
|
|
36373
|
+
const uniqueDiagnostics = [];
|
|
36374
|
+
for (const diagnostic of diagnostics) {
|
|
36375
|
+
if (isReactCompilerHooksDiagnostic(diagnostic) && reactDoctorHookSites.has(buildDiagnosticSiteKey(diagnostic))) continue;
|
|
36376
|
+
const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
|
|
36377
|
+
if (priority !== void 0) {
|
|
36378
|
+
const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
|
|
36379
|
+
if (overlappingDiagnosticIndex >= 0) {
|
|
36380
|
+
const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
|
|
36381
|
+
const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
|
|
36382
|
+
if (existingDiagnostic && existingPriority !== void 0) uniqueDiagnostics[overlappingDiagnosticIndex] = priority < existingPriority ? keepHighestSeverity(diagnostic, existingDiagnostic) : keepHighestSeverity(existingDiagnostic, diagnostic);
|
|
36383
|
+
continue;
|
|
36384
|
+
}
|
|
36385
|
+
}
|
|
36386
|
+
uniqueDiagnostics.push(diagnostic);
|
|
36387
|
+
}
|
|
36388
|
+
return uniqueDiagnostics;
|
|
36389
|
+
};
|
|
36347
36390
|
const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
36348
36391
|
const sortDiagnosticsStable = (diagnostics) => [...diagnostics].sort((left, right) => compareStrings(left.filePath, right.filePath) || left.line - right.line || left.column - right.column || compareStrings(left.plugin, right.plugin) || compareStrings(left.rule, right.rule) || compareStrings(left.severity, right.severity) || compareStrings(left.message, right.message));
|
|
36349
36392
|
const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
|
|
@@ -38557,6 +38600,7 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
38557
38600
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
38558
38601
|
};
|
|
38559
38602
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
38603
|
+
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
38560
38604
|
/**
|
|
38561
38605
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
38562
38606
|
* behind a `Context.Service`. The production layer (`layerNode`)
|
|
@@ -38627,6 +38671,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38627
38671
|
args,
|
|
38628
38672
|
directory
|
|
38629
38673
|
});
|
|
38674
|
+
const listUntrackedFilePaths = (directory, includePaths = []) => runGit(directory, [
|
|
38675
|
+
"ls-files",
|
|
38676
|
+
"-z",
|
|
38677
|
+
"--others",
|
|
38678
|
+
"--exclude-standard",
|
|
38679
|
+
...includePaths.length > 0 ? ["--", ...includePaths] : []
|
|
38680
|
+
]).pipe(map$3((result) => result.status === 0 ? splitNullSeparated(result.stdout) : null));
|
|
38681
|
+
const mergeUntracked = (directory, trackedFilePaths, includeUntracked) => includeUntracked ? listUntrackedFilePaths(directory).pipe(map$3((untracked) => untracked === null ? trackedFilePaths : [...new Set([...trackedFilePaths, ...untracked])])) : succeed$2(trackedFilePaths);
|
|
38630
38682
|
const currentBranch = (directory) => runGit(directory, [
|
|
38631
38683
|
"rev-parse",
|
|
38632
38684
|
"--abbrev-ref",
|
|
@@ -38749,7 +38801,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38749
38801
|
githubViewerPermission,
|
|
38750
38802
|
branchExists,
|
|
38751
38803
|
mergeBase,
|
|
38752
|
-
diffSelection: ({ directory, explicitBaseBranch }) => gen(function* () {
|
|
38804
|
+
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
38753
38805
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
38754
38806
|
if (explicitBaseBranch !== void 0) {
|
|
38755
38807
|
const range = parseGitDiffRange(explicitBaseBranch);
|
|
@@ -38778,7 +38830,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38778
38830
|
"HEAD"
|
|
38779
38831
|
]);
|
|
38780
38832
|
if (uncommitted.status !== 0) return null;
|
|
38781
|
-
const files = splitNullSeparated(uncommitted.stdout);
|
|
38833
|
+
const files = yield* mergeUntracked(directory, splitNullSeparated(uncommitted.stdout), includeUntracked);
|
|
38782
38834
|
if (files.length === 0) return null;
|
|
38783
38835
|
return {
|
|
38784
38836
|
currentBranch: resolvedCurrentBranch,
|
|
@@ -38808,7 +38860,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38808
38860
|
currentBranch: resolvedCurrentBranch,
|
|
38809
38861
|
baseBranch,
|
|
38810
38862
|
diffBaseRef: mergeBaseRef,
|
|
38811
|
-
changedFiles: splitNullSeparated(diff.stdout),
|
|
38863
|
+
changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
|
|
38812
38864
|
isCurrentChanges: false
|
|
38813
38865
|
};
|
|
38814
38866
|
}).pipe(withSpan("Git.diffSelection")),
|
|
@@ -38854,7 +38906,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38854
38906
|
stdout: result.stdout
|
|
38855
38907
|
};
|
|
38856
38908
|
}).pipe(withSpan("Git.grep")),
|
|
38857
|
-
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
38909
|
+
changedLineRanges: ({ directory, baseRef, cached, files, includeUntracked = false }) => gen(function* () {
|
|
38858
38910
|
if (files.length === 0) return [];
|
|
38859
38911
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
38860
38912
|
const result = yield* runGit(directory, [
|
|
@@ -38868,7 +38920,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38868
38920
|
...files
|
|
38869
38921
|
]);
|
|
38870
38922
|
if (result.status !== 0) return null;
|
|
38871
|
-
|
|
38923
|
+
const changedLineRanges = parseChangedLineRanges(result.stdout);
|
|
38924
|
+
if (cached || !includeUntracked) return changedLineRanges;
|
|
38925
|
+
const untrackedFilePaths = yield* listUntrackedFilePaths(directory, files);
|
|
38926
|
+
if (untrackedFilePaths === null) return changedLineRanges;
|
|
38927
|
+
return [...changedLineRanges, ...untrackedFilePaths.map((file) => ({
|
|
38928
|
+
file,
|
|
38929
|
+
ranges: [[1, UNTRACKED_FILE_LAST_LINE]]
|
|
38930
|
+
}))];
|
|
38872
38931
|
}).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
|
|
38873
38932
|
});
|
|
38874
38933
|
})).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
|
|
@@ -39453,13 +39512,13 @@ const encodeSidecarFileEntry = (entry) => ({
|
|
|
39453
39512
|
diagnostics: entry.diagnostics
|
|
39454
39513
|
});
|
|
39455
39514
|
const emptyCache = () => ({
|
|
39456
|
-
version:
|
|
39515
|
+
version: 2,
|
|
39457
39516
|
rulesets: {}
|
|
39458
39517
|
});
|
|
39459
39518
|
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
39460
39519
|
const entries = /* @__PURE__ */ new Map();
|
|
39461
39520
|
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39462
|
-
if (persisted.version !==
|
|
39521
|
+
if (persisted.version !== 2 || !isRecord(persisted.rulesets)) return entries;
|
|
39463
39522
|
const bucket = persisted.rulesets[rulesetHash];
|
|
39464
39523
|
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
39465
39524
|
for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
|
|
@@ -39479,7 +39538,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
39479
39538
|
},
|
|
39480
39539
|
persist: () => {
|
|
39481
39540
|
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39482
|
-
const rulesets = onDisk.version ===
|
|
39541
|
+
const rulesets = onDisk.version === 2 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
39483
39542
|
const existingBucket = rulesets[rulesetHash];
|
|
39484
39543
|
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
39485
39544
|
const ourFiles = {};
|
|
@@ -39493,7 +39552,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
39493
39552
|
files: Object.fromEntries(cappedEntries)
|
|
39494
39553
|
};
|
|
39495
39554
|
atomicWriteJson(cacheFilePath, {
|
|
39496
|
-
version:
|
|
39555
|
+
version: 2,
|
|
39497
39556
|
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
39498
39557
|
});
|
|
39499
39558
|
}
|
|
@@ -39544,23 +39603,6 @@ const resolveTsConfigRelativePath = (rootDirectory) => {
|
|
|
39544
39603
|
for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
|
|
39545
39604
|
return null;
|
|
39546
39605
|
};
|
|
39547
|
-
const DERIVED_STATE_RULE_PRIORITY = new Map([
|
|
39548
|
-
["no-initialize-state", 0],
|
|
39549
|
-
["no-adjust-state-on-prop-change", 1],
|
|
39550
|
-
["no-derived-state", 2],
|
|
39551
|
-
["no-derived-state-effect", 3]
|
|
39552
|
-
]);
|
|
39553
|
-
const doDiagnosticSpansOverlap = (first, second) => {
|
|
39554
|
-
if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
|
|
39555
|
-
if (first.offset !== void 0 && second.offset !== void 0) {
|
|
39556
|
-
const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
|
|
39557
|
-
const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
|
|
39558
|
-
return first.offset < secondEnd && second.offset < firstEnd;
|
|
39559
|
-
}
|
|
39560
|
-
const firstEndLine = first.endLine ?? first.line;
|
|
39561
|
-
const secondEndLine = second.endLine ?? second.line;
|
|
39562
|
-
return first.line <= secondEndLine && second.line <= firstEndLine;
|
|
39563
|
-
};
|
|
39564
39606
|
const dedupeDiagnostics = (diagnostics) => {
|
|
39565
39607
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
39566
39608
|
const uniqueDiagnostics = [];
|
|
@@ -39568,16 +39610,6 @@ const dedupeDiagnostics = (diagnostics) => {
|
|
|
39568
39610
|
const key = `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}\u0000${diagnostic.plugin}\u0000${diagnostic.rule}\u0000${diagnostic.severity}\u0000${diagnostic.message}`;
|
|
39569
39611
|
if (seenKeys.has(key)) continue;
|
|
39570
39612
|
seenKeys.add(key);
|
|
39571
|
-
const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
|
|
39572
|
-
if (priority !== void 0) {
|
|
39573
|
-
const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && candidate.severity === diagnostic.severity && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
|
|
39574
|
-
if (overlappingDiagnosticIndex >= 0) {
|
|
39575
|
-
const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
|
|
39576
|
-
const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
|
|
39577
|
-
if (existingPriority !== void 0 && priority < existingPriority) uniqueDiagnostics[overlappingDiagnosticIndex] = diagnostic;
|
|
39578
|
-
continue;
|
|
39579
|
-
}
|
|
39580
|
-
}
|
|
39581
39613
|
uniqueDiagnostics.push(diagnostic);
|
|
39582
39614
|
}
|
|
39583
39615
|
return uniqueDiagnostics;
|
|
@@ -40377,11 +40409,11 @@ const buildRelatedLocations = (labels, filePath) => {
|
|
|
40377
40409
|
}
|
|
40378
40410
|
return related;
|
|
40379
40411
|
};
|
|
40380
|
-
const
|
|
40381
|
-
|
|
40382
|
-
|
|
40383
|
-
|
|
40384
|
-
|
|
40412
|
+
const isOxlintSpan = (value) => isRecord(value) && typeof value.offset === "number" && typeof value.length === "number" && typeof value.line === "number" && typeof value.column === "number";
|
|
40413
|
+
const isOxlintLabel = (value) => isRecord(value) && isOxlintSpan(value.span);
|
|
40414
|
+
const isMappableOxlintDiagnostic = (value) => isRecord(value) && typeof value.code === "string" && value.code.length > 0 && typeof value.filename === "string" && value.filename.length > 0 && (value.severity === "warning" || value.severity === "error") && Array.isArray(value.labels) && value.labels.every(isOxlintLabel);
|
|
40415
|
+
const isEngineFailureDiagnostic = (value) => !isRecord(value) || typeof value.filename !== "string" || value.filename.length === 0;
|
|
40416
|
+
const isOxlintOutput = (value) => isRecord(value) && Array.isArray(value.diagnostics);
|
|
40385
40417
|
/**
|
|
40386
40418
|
* Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
|
|
40387
40419
|
* Tolerates the leading-notice-line shape oxlint sometimes prints
|
|
@@ -40400,6 +40432,8 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40400
40432
|
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
40401
40433
|
}
|
|
40402
40434
|
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
40435
|
+
const engineFailureDiagnostic = parsed.diagnostics.find(isEngineFailureDiagnostic);
|
|
40436
|
+
if (engineFailureDiagnostic !== void 0) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: JSON.stringify(engineFailureDiagnostic).slice(0, 600) }) });
|
|
40403
40437
|
const minifiedFileCache = /* @__PURE__ */ new Map();
|
|
40404
40438
|
const sourceBufferCache = /* @__PURE__ */ new Map();
|
|
40405
40439
|
const sourceLinesCache = /* @__PURE__ */ new Map();
|
|
@@ -40434,7 +40468,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40434
40468
|
minifiedFileCache.set(absolutePath, minified);
|
|
40435
40469
|
return minified;
|
|
40436
40470
|
};
|
|
40437
|
-
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic
|
|
40471
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
40438
40472
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
40439
40473
|
const primaryLabel = diagnostic.labels[0];
|
|
40440
40474
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
@@ -42269,7 +42303,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
42269
42303
|
respectInlineDisables: input.respectInlineDisables,
|
|
42270
42304
|
showWarnings
|
|
42271
42305
|
});
|
|
42272
|
-
const
|
|
42306
|
+
const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)));
|
|
42307
|
+
const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
|
|
42273
42308
|
const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
|
|
42274
42309
|
...checkReducedMotion(scanDirectory),
|
|
42275
42310
|
...checkPnpmHardening(scanDirectory),
|
|
@@ -42398,7 +42433,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
42398
42433
|
});
|
|
42399
42434
|
return empty$4;
|
|
42400
42435
|
}))));
|
|
42401
|
-
const lintCollected = yield* runCollect(
|
|
42436
|
+
const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
|
|
42402
42437
|
onNone: () => set(lintFailure, {
|
|
42403
42438
|
didFail: true,
|
|
42404
42439
|
reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
|
|
@@ -42406,7 +42441,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
42406
42441
|
reasonKind: null
|
|
42407
42442
|
}).pipe(as([])),
|
|
42408
42443
|
onSome: succeed$2
|
|
42409
|
-
})));
|
|
42444
|
+
}))));
|
|
42445
|
+
yield* forEach$1(lintCollected, reporterService.emit, { discard: true });
|
|
42410
42446
|
const lintFailureState = yield* get$2(lintFailure);
|
|
42411
42447
|
yield* afterLint(lintFailureState.didFail);
|
|
42412
42448
|
if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
|
|
@@ -44524,6 +44560,7 @@ const METRIC = {
|
|
|
44524
44560
|
cliInvoked: "cli.invoked",
|
|
44525
44561
|
cliError: "cli.error",
|
|
44526
44562
|
cliEnvironmentError: "cli.env_error",
|
|
44563
|
+
stagedSnapshotDivergence: "staged.snapshot_divergence",
|
|
44527
44564
|
projectDetected: "project.detected",
|
|
44528
44565
|
projectPathSelected: "project.path_selected",
|
|
44529
44566
|
projectConfigSelected: "project.config_selected",
|
|
@@ -44878,5 +44915,5 @@ const startLanguageServer = () => {
|
|
|
44878
44915
|
};
|
|
44879
44916
|
//#endregion
|
|
44880
44917
|
export { startLanguageServer };
|
|
44881
|
-
!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]="
|
|
44882
|
-
//# debugId=
|
|
44918
|
+
!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]="b75c36e9-f19d-5127-be1a-89ac93910ff0")}catch(e){}}();
|
|
44919
|
+
//# debugId=b75c36e9-f19d-5127-be1a-89ac93910ff0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.7.6-dev.
|
|
3
|
+
"version": "0.7.6-dev.3f22fe3",
|
|
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
|
-
"
|
|
69
|
-
"
|
|
68
|
+
"deslop-js": "0.7.6",
|
|
69
|
+
"oxlint-plugin-react-doctor": "0.7.6-dev.3f22fe3"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/babel__code-frame": "^7.27.0",
|