react-doctor 0.8.1-dev.fe241a9 → 0.8.1
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 +73 -585
- package/dist/index.d.ts +15 -9
- package/dist/index.js +4 -90
- package/dist/lsp.js +4 -90
- package/package.json +2 -2
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]="941df082-c8b4-5084-baae-33ec22d4e55f")}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";
|
|
@@ -42334,35 +42334,6 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
42334
42334
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
42335
42335
|
};
|
|
42336
42336
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
42337
|
-
const parseBaselineDiffPlan = (value) => {
|
|
42338
|
-
const entries = splitNullSeparated(value);
|
|
42339
|
-
const baseFiles = /* @__PURE__ */ new Set();
|
|
42340
|
-
const headFiles = /* @__PURE__ */ new Set();
|
|
42341
|
-
for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 2) {
|
|
42342
|
-
const status = entries[entryIndex];
|
|
42343
|
-
const filePath = entries[entryIndex + 1];
|
|
42344
|
-
if (status === void 0 || filePath === void 0 || status.length !== 1) return null;
|
|
42345
|
-
if (status === "A") {
|
|
42346
|
-
headFiles.add(filePath);
|
|
42347
|
-
continue;
|
|
42348
|
-
}
|
|
42349
|
-
if (status === "D") {
|
|
42350
|
-
baseFiles.add(filePath);
|
|
42351
|
-
continue;
|
|
42352
|
-
}
|
|
42353
|
-
if (status === "M" || status === "T") {
|
|
42354
|
-
baseFiles.add(filePath);
|
|
42355
|
-
headFiles.add(filePath);
|
|
42356
|
-
continue;
|
|
42357
|
-
}
|
|
42358
|
-
return null;
|
|
42359
|
-
}
|
|
42360
|
-
return {
|
|
42361
|
-
baseFiles: [...baseFiles],
|
|
42362
|
-
headFiles: [...headFiles],
|
|
42363
|
-
untrackedFiles: []
|
|
42364
|
-
};
|
|
42365
|
-
};
|
|
42366
42337
|
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
42367
42338
|
/**
|
|
42368
42339
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
@@ -42565,45 +42536,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42565
42536
|
githubViewerPermission,
|
|
42566
42537
|
branchExists,
|
|
42567
42538
|
mergeBase,
|
|
42568
|
-
baselineDiffPlan: (input) => {
|
|
42569
|
-
if (!isSafeGitRevision(input.ref)) return succeed$2(null);
|
|
42570
|
-
return gen(function* () {
|
|
42571
|
-
const unmerged = yield* runGit(input.directory, [
|
|
42572
|
-
"diff",
|
|
42573
|
-
"--no-ext-diff",
|
|
42574
|
-
"-z",
|
|
42575
|
-
"--name-only",
|
|
42576
|
-
"--diff-filter=U",
|
|
42577
|
-
"--relative"
|
|
42578
|
-
]);
|
|
42579
|
-
if (unmerged.status !== 0 || unmerged.stdout.length > 0) return null;
|
|
42580
|
-
const result = yield* runGit(input.directory, [
|
|
42581
|
-
"diff",
|
|
42582
|
-
"--no-ext-diff",
|
|
42583
|
-
"--no-textconv",
|
|
42584
|
-
"--no-renames",
|
|
42585
|
-
"-z",
|
|
42586
|
-
"--name-status",
|
|
42587
|
-
"--relative",
|
|
42588
|
-
input.ref
|
|
42589
|
-
]);
|
|
42590
|
-
if (result.status !== 0) return null;
|
|
42591
|
-
const plan = parseBaselineDiffPlan(result.stdout);
|
|
42592
|
-
if (plan === null) return null;
|
|
42593
|
-
const untracked = yield* runGit(input.directory, [
|
|
42594
|
-
"ls-files",
|
|
42595
|
-
"--others",
|
|
42596
|
-
"--exclude-standard",
|
|
42597
|
-
"-z"
|
|
42598
|
-
]);
|
|
42599
|
-
if (untracked.status !== 0) return null;
|
|
42600
|
-
return {
|
|
42601
|
-
baseFiles: plan.baseFiles,
|
|
42602
|
-
headFiles: plan.headFiles,
|
|
42603
|
-
untrackedFiles: splitNullSeparated(untracked.stdout)
|
|
42604
|
-
};
|
|
42605
|
-
}).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.baselineDiffPlan"));
|
|
42606
|
-
},
|
|
42607
42539
|
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
42608
42540
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
42609
42541
|
if (explicitBaseBranch !== void 0) {
|
|
@@ -42753,7 +42685,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42753
42685
|
githubViewerPermission: () => succeed$2(snapshot.githubViewerPermission ?? null),
|
|
42754
42686
|
branchExists: (_directory, branch) => succeed$2(snapshot.branchExists?.get(branch) ?? false),
|
|
42755
42687
|
mergeBase: ({ ref }) => succeed$2(snapshot.mergeBase?.get(ref) ?? null),
|
|
42756
|
-
baselineDiffPlan: () => succeed$2(snapshot.baselineDiffPlan ?? null),
|
|
42757
42688
|
diffSelection: () => succeed$2(snapshot.diffSelection ?? null),
|
|
42758
42689
|
stagedFilePaths: () => succeed$2(snapshot.stagedFiles ?? []),
|
|
42759
42690
|
showStagedContent: (_directory, relativePath) => succeed$2(snapshot.stagedContent?.get(relativePath) ?? null),
|
|
@@ -44091,17 +44022,12 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
|
|
|
44091
44022
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
44092
44023
|
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
|
|
44093
44024
|
const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
|
|
44094
|
-
const SET_STATE_IN_EFFECT_ACTION = "Prefer deriving or initializing the value before render. If the effect must read a browser API after mount, treat this as advisory or suppress it with `// react-doctor-disable-next-line react-hooks-js/set-state-in-effect`.";
|
|
44095
44025
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
44096
44026
|
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
44097
44027
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
44098
44028
|
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
44099
44029
|
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
44100
44030
|
};
|
|
44101
|
-
const buildSetStateInEffectMessage = (reasonSummary) => {
|
|
44102
|
-
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
44103
|
-
return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
|
|
44104
|
-
};
|
|
44105
44031
|
const PLUGIN_CATEGORY_MAP = {
|
|
44106
44032
|
react: "Bugs",
|
|
44107
44033
|
"react-hooks": "Bugs",
|
|
@@ -44159,10 +44085,6 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
44159
44085
|
message: REACT_ERROR_BOUNDARY_MESSAGE,
|
|
44160
44086
|
help: reasonDetail || help
|
|
44161
44087
|
};
|
|
44162
|
-
if (rule === "set-state-in-effect") return {
|
|
44163
|
-
message: buildSetStateInEffectMessage(reasonSummary.trim()),
|
|
44164
|
-
help: reasonDetail || help
|
|
44165
|
-
};
|
|
44166
44088
|
return {
|
|
44167
44089
|
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
44168
44090
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
@@ -46499,19 +46421,12 @@ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
|
|
|
46499
46421
|
*/
|
|
46500
46422
|
const materializeSourceTree = (input) => gen(function* () {
|
|
46501
46423
|
const materializedFiles = [];
|
|
46502
|
-
const unmaterializedFiles = [];
|
|
46503
46424
|
const resolvedTempDirectory = path.resolve(input.tempDirectory);
|
|
46504
46425
|
for (const relativePath of input.files) {
|
|
46505
46426
|
const content = yield* input.readContent(relativePath).pipe(orElseSucceed(() => null));
|
|
46506
|
-
if (content === null)
|
|
46507
|
-
unmaterializedFiles.push(relativePath);
|
|
46508
|
-
continue;
|
|
46509
|
-
}
|
|
46427
|
+
if (content === null) continue;
|
|
46510
46428
|
const candidateTargetPath = path.resolve(resolvedTempDirectory, relativePath);
|
|
46511
|
-
if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory))
|
|
46512
|
-
unmaterializedFiles.push(relativePath);
|
|
46513
|
-
continue;
|
|
46514
|
-
}
|
|
46429
|
+
if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) continue;
|
|
46515
46430
|
yield* sync(() => {
|
|
46516
46431
|
fs.mkdirSync(path.dirname(candidateTargetPath), { recursive: true });
|
|
46517
46432
|
fs.writeFileSync(candidateTargetPath, content);
|
|
@@ -46528,7 +46443,6 @@ const materializeSourceTree = (input) => gen(function* () {
|
|
|
46528
46443
|
return {
|
|
46529
46444
|
tempDirectory: input.tempDirectory,
|
|
46530
46445
|
materializedFiles,
|
|
46531
|
-
unmaterializedFiles,
|
|
46532
46446
|
cleanup: () => {
|
|
46533
46447
|
try {
|
|
46534
46448
|
fs.rmSync(input.tempDirectory, {
|
|
@@ -46798,80 +46712,54 @@ const buildSkippedChecks = (input) => {
|
|
|
46798
46712
|
skippedCheckReasons
|
|
46799
46713
|
};
|
|
46800
46714
|
};
|
|
46801
|
-
const
|
|
46802
|
-
const normalizeEvidence = (evidence) => evidence.replace(/\s+/g, " ").trim();
|
|
46803
|
-
const getDiagnosticMatchKeys = (diagnostic, evidence) => {
|
|
46715
|
+
const fingerprintDiagnostic = (diagnostic, lineText) => {
|
|
46804
46716
|
const ruleKey = `${diagnostic.plugin}/${diagnostic.rule}`;
|
|
46805
|
-
const
|
|
46806
|
-
|
|
46807
|
-
|
|
46808
|
-
|
|
46809
|
-
|
|
46810
|
-
|
|
46811
|
-
|
|
46812
|
-
|
|
46813
|
-
|
|
46814
|
-
|
|
46815
|
-
|
|
46816
|
-
|
|
46817
|
-
|
|
46818
|
-
|
|
46819
|
-
|
|
46820
|
-
|
|
46821
|
-
|
|
46822
|
-
|
|
46823
|
-
|
|
46824
|
-
|
|
46825
|
-
|
|
46826
|
-
|
|
46827
|
-
|
|
46828
|
-
|
|
46829
|
-
|
|
46830
|
-
|
|
46831
|
-
*
|
|
46832
|
-
* evidence. Stable identities combine plugin/rule, the diagnostic message,
|
|
46833
|
-
* and normalized diagnosed source, so unchanged findings can move across
|
|
46834
|
-
* files while changed constructs or messages remain new. Cardinality is
|
|
46835
|
-
* retained for identical findings. Diagnostics explicitly marked
|
|
46836
|
-
* `matchByOccurrence` may fall back to same-file plugin/rule/message matching
|
|
46837
|
-
* after same-file strict evidence matching. Cross-file evidence matching runs
|
|
46838
|
-
* last so a copy cannot consume a reformatted local occurrence. Unreadable
|
|
46839
|
-
* evidence uses the same conservative fallback rather than matching across
|
|
46840
|
-
* files without proof.
|
|
46717
|
+
const snippet = lineText === null || diagnostic.matchByOccurrence ? "" : createHash("sha1").update(lineText.trim()).digest("hex");
|
|
46718
|
+
return `${diagnostic.filePath}\u0000${ruleKey}\u0000${snippet}`;
|
|
46719
|
+
};
|
|
46720
|
+
/**
|
|
46721
|
+
* Diffs a head scan against a base scan to isolate the diagnostics a change
|
|
46722
|
+
* introduced (and count the ones it resolved). Matching is a multiset over a
|
|
46723
|
+
* position-independent fingerprint — `(filePath, plugin/rule, hash(flagged
|
|
46724
|
+
* line text))` — so inserting lines above an existing issue doesn't make it
|
|
46725
|
+
* look new, while a genuinely new occurrence (new line text, or one more of
|
|
46726
|
+
* the same) surfaces. Identical repeated findings are matched by count.
|
|
46727
|
+
*
|
|
46728
|
+
* Diagnostics carrying `matchByOccurrence` (resolved at diagnostic creation:
|
|
46729
|
+
* every Accessibility-category finding, plus rules opting in via their
|
|
46730
|
+
* `matchByOccurrence` metadata flag) drop the line-text snippet and match by
|
|
46731
|
+
* `(filePath, plugin/rule)` occurrence count alone. Their identity is the
|
|
46732
|
+
* flagged element, not the line's text, so editing the line (reindentation,
|
|
46733
|
+
* prettier reflow, collapsing a multi-line JSX element) doesn't reclassify a
|
|
46734
|
+
* pre-existing finding as new — while one MORE occurrence of the same rule in
|
|
46735
|
+
* the file still surfaces. Expression-level rules keep the line-text snippet:
|
|
46736
|
+
* there the flagged expression IS the finding, so changed text means new +
|
|
46737
|
+
* fixed.
|
|
46738
|
+
*
|
|
46739
|
+
* v1 limitation: the fingerprint keys on the head-relative `filePath`, and base
|
|
46740
|
+
* content is read at that same path. A file renamed by the change therefore has
|
|
46741
|
+
* no base match, so its pre-existing findings are reported as new. This
|
|
46742
|
+
* over-reports (never hides a real issue) and is rare; rename-aware base
|
|
46743
|
+
* resolution is a follow-up.
|
|
46841
46744
|
*/
|
|
46842
46745
|
const computeDiagnosticDelta = (input) => {
|
|
46843
|
-
const
|
|
46844
|
-
const
|
|
46845
|
-
|
|
46846
|
-
|
|
46847
|
-
|
|
46848
|
-
|
|
46849
|
-
|
|
46850
|
-
|
|
46851
|
-
|
|
46852
|
-
|
|
46853
|
-
|
|
46854
|
-
|
|
46855
|
-
|
|
46856
|
-
|
|
46857
|
-
if (matchedHeadDiagnosticIndexes.has(candidate.diagnosticIndex)) continue;
|
|
46858
|
-
const baseDiagnosticIndex = takeMatchingDiagnosticIndex(baseBuckets, getKey(candidate), matchedBaseDiagnosticIndexes);
|
|
46859
|
-
if (baseDiagnosticIndex === null) continue;
|
|
46860
|
-
matchedHeadDiagnosticIndexes.add(candidate.diagnosticIndex);
|
|
46861
|
-
matchedBaseDiagnosticIndexes.add(baseDiagnosticIndex);
|
|
46862
|
-
onMatch?.(candidate.diagnosticIndex, baseDiagnosticIndex);
|
|
46863
|
-
}
|
|
46864
|
-
};
|
|
46865
|
-
matchCandidates(baseBySameFileStableEvidence, (candidate) => candidate.sameFileStableEvidenceKey);
|
|
46866
|
-
matchCandidates(baseBySameFileFallback, (candidate) => candidate.sameFileFallbackKey);
|
|
46867
|
-
let crossFileMatchCount = 0;
|
|
46868
|
-
matchCandidates(baseByStableEvidence, (candidate) => candidate.stableEvidenceKey, (head, base) => {
|
|
46869
|
-
if (input.headDiagnostics[head]?.filePath !== input.baseDiagnostics[base]?.filePath) crossFileMatchCount += 1;
|
|
46870
|
-
});
|
|
46746
|
+
const unmatchedBaseByFingerprint = /* @__PURE__ */ new Map();
|
|
46747
|
+
for (const diagnostic of input.baseDiagnostics) {
|
|
46748
|
+
const key = fingerprintDiagnostic(diagnostic, input.readBaseLine(diagnostic.filePath, diagnostic.line));
|
|
46749
|
+
unmatchedBaseByFingerprint.set(key, (unmatchedBaseByFingerprint.get(key) ?? 0) + 1);
|
|
46750
|
+
}
|
|
46751
|
+
const newDiagnostics = [];
|
|
46752
|
+
for (const diagnostic of input.headDiagnostics) {
|
|
46753
|
+
const key = fingerprintDiagnostic(diagnostic, input.readHeadLine(diagnostic.filePath, diagnostic.line));
|
|
46754
|
+
const availableMatches = unmatchedBaseByFingerprint.get(key) ?? 0;
|
|
46755
|
+
if (availableMatches > 0) unmatchedBaseByFingerprint.set(key, availableMatches - 1);
|
|
46756
|
+
else newDiagnostics.push(diagnostic);
|
|
46757
|
+
}
|
|
46758
|
+
let fixedCount = 0;
|
|
46759
|
+
for (const remaining of unmatchedBaseByFingerprint.values()) fixedCount += remaining;
|
|
46871
46760
|
return {
|
|
46872
|
-
newDiagnostics
|
|
46873
|
-
fixedCount
|
|
46874
|
-
crossFileMatchCount
|
|
46761
|
+
newDiagnostics,
|
|
46762
|
+
fixedCount
|
|
46875
46763
|
};
|
|
46876
46764
|
};
|
|
46877
46765
|
/**
|
|
@@ -46903,12 +46791,6 @@ const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runProm
|
|
|
46903
46791
|
};
|
|
46904
46792
|
}).pipe(provide(Git.layerNode)));
|
|
46905
46793
|
const filterSourceFiles = (filePaths) => filePaths.filter(isLintableSourceFile);
|
|
46906
|
-
const getBaselineDiffPlan = (directory, ref) => runPromise(gen(function* () {
|
|
46907
|
-
return yield* (yield* Git).baselineDiffPlan({
|
|
46908
|
-
directory,
|
|
46909
|
-
ref
|
|
46910
|
-
});
|
|
46911
|
-
}).pipe(provide(Git.layerNode)));
|
|
46912
46794
|
/**
|
|
46913
46795
|
* Programmatic façade over `Git.changedLineRanges` (the `lines` scope). Diffs
|
|
46914
46796
|
* `files` with `--unified=0` against `baseRef` (or the index when `cached`),
|
|
@@ -47365,7 +47247,7 @@ const makeNoopConsole = () => ({
|
|
|
47365
47247
|
});
|
|
47366
47248
|
//#endregion
|
|
47367
47249
|
//#region src/cli/utils/version.ts
|
|
47368
|
-
const VERSION = "0.8.1
|
|
47250
|
+
const VERSION = "0.8.1";
|
|
47369
47251
|
//#endregion
|
|
47370
47252
|
//#region src/cli/utils/json-mode.ts
|
|
47371
47253
|
let context = null;
|
|
@@ -47717,13 +47599,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
47717
47599
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
47718
47600
|
* standard `SENTRY_RELEASE` override.
|
|
47719
47601
|
*/
|
|
47720
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.1
|
|
47602
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.1`;
|
|
47721
47603
|
/**
|
|
47722
47604
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
47723
47605
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
47724
47606
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
47725
47607
|
*/
|
|
47726
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.1
|
|
47608
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.1") ? "development" : "production");
|
|
47727
47609
|
/**
|
|
47728
47610
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
47729
47611
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -60346,7 +60228,6 @@ const buildOutcomeAttributes = (input) => {
|
|
|
60346
60228
|
new: summary.totalDiagnosticCount,
|
|
60347
60229
|
fixed: result.baselineDelta.fixedCount,
|
|
60348
60230
|
baseTotal: result.baselineDelta.baseTotalCount,
|
|
60349
|
-
crossFileMatches: result.baselineDelta.crossFileMatchCount ?? null,
|
|
60350
60231
|
degraded: false
|
|
60351
60232
|
}));
|
|
60352
60233
|
else if (input.gateExempt) attributes["baseline.degraded"] = true;
|
|
@@ -60460,49 +60341,28 @@ const diagnosticIntersectsLineRanges = (diagnostic, lineRanges) => {
|
|
|
60460
60341
|
return lineRanges.some(([rangeStartLine, rangeEndLine]) => diagnostic.line <= rangeEndLine && diagnosticEndLine >= rangeStartLine);
|
|
60461
60342
|
};
|
|
60462
60343
|
//#endregion
|
|
60463
|
-
//#region src/cli/utils/is-materializable-git-source.ts
|
|
60464
|
-
const GIT_LFS_POINTER_HEADER = "version https://git-lfs.github.com/spec/v1";
|
|
60465
|
-
const isMaterializableGitSource = (content) => !content.includes("\0") && !content.startsWith(GIT_LFS_POINTER_HEADER);
|
|
60466
|
-
//#endregion
|
|
60467
60344
|
//#region src/cli/utils/materialize-baseline-files.ts
|
|
60468
60345
|
/**
|
|
60469
|
-
* Materializes the
|
|
60470
|
-
* `
|
|
60471
|
-
*
|
|
60472
|
-
*
|
|
60473
|
-
*
|
|
60474
|
-
*
|
|
60346
|
+
* Materializes the `ref` version of `files` into `tempDirectory` (via
|
|
60347
|
+
* `git show <ref>:<path>`), mirroring the project layout + head's config files
|
|
60348
|
+
* so the base lints under the same rules as head. Reuses the shared,
|
|
60349
|
+
* zip-slip-guarded `materializeSourceTree`. Per-file read misses (e.g. a file
|
|
60350
|
+
* the PR added, absent at base) are skipped, which is exactly what we want:
|
|
60351
|
+
* a brand-new file has no base counterpart, so all its findings are "new".
|
|
60475
60352
|
*/
|
|
60476
60353
|
const materializeBaselineFiles = (input) => runPromise(gen(function* () {
|
|
60477
60354
|
const git = yield* Git;
|
|
60478
|
-
|
|
60355
|
+
return yield* materializeSourceTree({
|
|
60479
60356
|
directory: input.directory,
|
|
60480
|
-
|
|
60481
|
-
});
|
|
60482
|
-
const baseFiles = input.baseFiles ?? diffPlan?.baseFiles;
|
|
60483
|
-
const headFiles = input.headFiles ?? diffPlan?.headFiles;
|
|
60484
|
-
const untrackedFiles = diffPlan?.untrackedFiles ?? [];
|
|
60485
|
-
if (baseFiles === void 0 || headFiles === void 0) return null;
|
|
60486
|
-
const files = [...new Set([...input.files, ...baseFiles])];
|
|
60487
|
-
const tree = yield* materializeSourceTree({
|
|
60488
|
-
directory: input.directory,
|
|
60489
|
-
files,
|
|
60357
|
+
files: input.files,
|
|
60490
60358
|
tempDirectory: input.tempDirectory,
|
|
60491
60359
|
readContent: (relativePath) => git.showRefContent({
|
|
60492
60360
|
directory: input.directory,
|
|
60493
60361
|
ref: input.ref,
|
|
60494
60362
|
relativePath,
|
|
60495
60363
|
options: { maxBufferBytes: GIT_SHOW_MAX_BUFFER_BYTES }
|
|
60496
|
-
})
|
|
60364
|
+
})
|
|
60497
60365
|
});
|
|
60498
|
-
const materializedFiles = new Set(tree.materializedFiles);
|
|
60499
|
-
return {
|
|
60500
|
-
...tree,
|
|
60501
|
-
baseFiles,
|
|
60502
|
-
headFiles,
|
|
60503
|
-
isComplete: baseFiles.every((filePath) => materializedFiles.has(filePath)),
|
|
60504
|
-
untrackedFiles
|
|
60505
|
-
};
|
|
60506
60366
|
}).pipe(provide(Git.layerNode)));
|
|
60507
60367
|
/**
|
|
60508
60368
|
* Resolves the commit a baseline scan should read base content from — the
|
|
@@ -60525,8 +60385,8 @@ const resolveMergeBaseRef = async (directory, ref) => {
|
|
|
60525
60385
|
//#region src/cli/utils/read-source-line.ts
|
|
60526
60386
|
/**
|
|
60527
60387
|
* Builds a cached `(filePath, line) -> source text` reader rooted at
|
|
60528
|
-
* `rootDirectory`, used
|
|
60529
|
-
*
|
|
60388
|
+
* `rootDirectory`, used to fingerprint a diagnostic by the content of its
|
|
60389
|
+
* flagged line (see `computeDiagnosticDelta`). Wraps core's
|
|
60530
60390
|
* `createNodeReadFileLinesSync` with per-file memoization + 1-indexed line
|
|
60531
60391
|
* lookup; unreadable files / out-of-range lines return `null`.
|
|
60532
60392
|
*/
|
|
@@ -60541,338 +60401,6 @@ const createSourceLineReader = (rootDirectory) => {
|
|
|
60541
60401
|
};
|
|
60542
60402
|
};
|
|
60543
60403
|
//#endregion
|
|
60544
|
-
//#region src/cli/utils/read-diagnostic-evidence.ts
|
|
60545
|
-
const SOURCE_EXTENSIONS = [
|
|
60546
|
-
".ts",
|
|
60547
|
-
".tsx",
|
|
60548
|
-
".js",
|
|
60549
|
-
".jsx",
|
|
60550
|
-
".mts",
|
|
60551
|
-
".cts",
|
|
60552
|
-
".mjs",
|
|
60553
|
-
".cjs"
|
|
60554
|
-
];
|
|
60555
|
-
const COMPONENT_WRAPPER_NAMES = new Set(["memo", "forwardRef"]);
|
|
60556
|
-
const getScriptKind = (filePath) => {
|
|
60557
|
-
const extension = path.extname(filePath).toLowerCase();
|
|
60558
|
-
if (extension === ".tsx") return ts.ScriptKind.TSX;
|
|
60559
|
-
if (extension === ".jsx") return ts.ScriptKind.JSX;
|
|
60560
|
-
if (extension === ".js" || extension === ".mjs" || extension === ".cjs") return ts.ScriptKind.JS;
|
|
60561
|
-
return ts.ScriptKind.TS;
|
|
60562
|
-
};
|
|
60563
|
-
const resolveSafeSourcePath = (rootDirectory, filePath) => {
|
|
60564
|
-
try {
|
|
60565
|
-
const resolvedRootDirectory = fs.realpathSync(rootDirectory);
|
|
60566
|
-
const absolutePath = path.resolve(resolvedRootDirectory, filePath);
|
|
60567
|
-
const relativePath = path.relative(resolvedRootDirectory, absolutePath);
|
|
60568
|
-
if (relativePath.length === 0 || relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) return null;
|
|
60569
|
-
const stats = fs.lstatSync(absolutePath);
|
|
60570
|
-
if (!stats.isFile() || stats.isSymbolicLink()) return null;
|
|
60571
|
-
const realPath = fs.realpathSync(absolutePath);
|
|
60572
|
-
const realRelativePath = path.relative(resolvedRootDirectory, realPath);
|
|
60573
|
-
return realRelativePath !== ".." && !realRelativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(realRelativePath) ? realPath : null;
|
|
60574
|
-
} catch {
|
|
60575
|
-
return null;
|
|
60576
|
-
}
|
|
60577
|
-
};
|
|
60578
|
-
const readSourceRecord = (rootDirectory, filePath, state) => {
|
|
60579
|
-
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
60580
|
-
if (state.recordByFilePath.has(normalizedFilePath)) return state.recordByFilePath.get(normalizedFilePath) ?? null;
|
|
60581
|
-
const absolutePath = resolveSafeSourcePath(rootDirectory, normalizedFilePath);
|
|
60582
|
-
if (absolutePath === null) {
|
|
60583
|
-
state.recordByFilePath.set(normalizedFilePath, null);
|
|
60584
|
-
return null;
|
|
60585
|
-
}
|
|
60586
|
-
try {
|
|
60587
|
-
const sourceText = fs.readFileSync(absolutePath, "utf-8");
|
|
60588
|
-
const record = {
|
|
60589
|
-
absolutePath,
|
|
60590
|
-
filePath: normalizedFilePath,
|
|
60591
|
-
sourceFile: ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
60592
|
-
sourceText
|
|
60593
|
-
};
|
|
60594
|
-
state.recordByFilePath.set(normalizedFilePath, record);
|
|
60595
|
-
return record;
|
|
60596
|
-
} catch {
|
|
60597
|
-
state.recordByFilePath.set(normalizedFilePath, null);
|
|
60598
|
-
return null;
|
|
60599
|
-
}
|
|
60600
|
-
};
|
|
60601
|
-
const listSourceRecords = (rootDirectory, state) => {
|
|
60602
|
-
if (state.sourceRecords !== null) return state.sourceRecords;
|
|
60603
|
-
state.sourceRecords = listSourceFiles(rootDirectory).flatMap((filePath) => {
|
|
60604
|
-
const record = readSourceRecord(rootDirectory, filePath, state);
|
|
60605
|
-
return record === null ? [] : [record];
|
|
60606
|
-
});
|
|
60607
|
-
return state.sourceRecords;
|
|
60608
|
-
};
|
|
60609
|
-
const unwrapExpression = (expression) => {
|
|
60610
|
-
let currentExpression = expression;
|
|
60611
|
-
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
60612
|
-
return currentExpression;
|
|
60613
|
-
};
|
|
60614
|
-
const getCalleeName = (expression) => {
|
|
60615
|
-
const unwrappedExpression = unwrapExpression(expression);
|
|
60616
|
-
if (ts.isIdentifier(unwrappedExpression)) return unwrappedExpression.text;
|
|
60617
|
-
if (ts.isPropertyAccessExpression(unwrappedExpression)) return unwrappedExpression.name.text;
|
|
60618
|
-
return null;
|
|
60619
|
-
};
|
|
60620
|
-
const isComponentWrapperCall = (node) => {
|
|
60621
|
-
const wrapperName = getCalleeName(node.expression);
|
|
60622
|
-
return wrapperName !== null && COMPONENT_WRAPPER_NAMES.has(wrapperName);
|
|
60623
|
-
};
|
|
60624
|
-
const getFunctionName = (node) => {
|
|
60625
|
-
if (ts.isFunctionDeclaration(node) && node.name !== void 0) return node.name.text;
|
|
60626
|
-
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
60627
|
-
let componentExpression = node;
|
|
60628
|
-
while (true) {
|
|
60629
|
-
const parent = componentExpression.parent;
|
|
60630
|
-
if (ts.isParenthesizedExpression(parent) || ts.isAsExpression(parent) || ts.isTypeAssertionExpression(parent) || ts.isNonNullExpression(parent) || ts.isSatisfiesExpression(parent)) {
|
|
60631
|
-
componentExpression = parent;
|
|
60632
|
-
continue;
|
|
60633
|
-
}
|
|
60634
|
-
if (ts.isCallExpression(parent) && parent.arguments[0] === componentExpression && isComponentWrapperCall(parent)) {
|
|
60635
|
-
componentExpression = parent;
|
|
60636
|
-
continue;
|
|
60637
|
-
}
|
|
60638
|
-
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) return parent.name.text;
|
|
60639
|
-
return node.name?.text ?? null;
|
|
60640
|
-
}
|
|
60641
|
-
}
|
|
60642
|
-
return null;
|
|
60643
|
-
};
|
|
60644
|
-
const isComponentFunction = (node) => ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node);
|
|
60645
|
-
const isDefaultExportedComponent = (record, node, componentName) => {
|
|
60646
|
-
if (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword)) return true;
|
|
60647
|
-
return record.sourceFile.statements.some((statement) => ts.isExportAssignment(statement) && ts.isIdentifier(statement.expression) && statement.expression.text === componentName);
|
|
60648
|
-
};
|
|
60649
|
-
const findEnclosingComponent = (record, diagnostic) => {
|
|
60650
|
-
if (!Number.isInteger(diagnostic.line) || diagnostic.line < 1) return null;
|
|
60651
|
-
const lineIndex = diagnostic.line - 1;
|
|
60652
|
-
const lineStarts = record.sourceFile.getLineStarts();
|
|
60653
|
-
const lineStart = lineStarts[lineIndex];
|
|
60654
|
-
if (lineStart === void 0) return null;
|
|
60655
|
-
const nextLineStart = lineStarts[lineIndex + 1] ?? record.sourceFile.end;
|
|
60656
|
-
const lineEnd = Math.max(lineStart, nextLineStart - 1);
|
|
60657
|
-
const columnIndex = Number.isInteger(diagnostic.column) ? Math.max(0, diagnostic.column - 1) : 0;
|
|
60658
|
-
const diagnosticPosition = Math.min(lineStart + columnIndex, lineEnd);
|
|
60659
|
-
let component = null;
|
|
60660
|
-
let componentWidth = Number.POSITIVE_INFINITY;
|
|
60661
|
-
const visit = (node) => {
|
|
60662
|
-
if (diagnosticPosition < node.getStart(record.sourceFile) || diagnosticPosition > node.end) return;
|
|
60663
|
-
if (isComponentFunction(node)) {
|
|
60664
|
-
const name = getFunctionName(node);
|
|
60665
|
-
const width = node.end - node.getStart(record.sourceFile);
|
|
60666
|
-
if (name !== null && /^[A-Z]/.test(name) && width < componentWidth) {
|
|
60667
|
-
component = {
|
|
60668
|
-
isDefaultExport: isDefaultExportedComponent(record, node, name),
|
|
60669
|
-
name,
|
|
60670
|
-
node,
|
|
60671
|
-
record
|
|
60672
|
-
};
|
|
60673
|
-
componentWidth = width;
|
|
60674
|
-
}
|
|
60675
|
-
}
|
|
60676
|
-
ts.forEachChild(node, visit);
|
|
60677
|
-
};
|
|
60678
|
-
visit(record.sourceFile);
|
|
60679
|
-
return component;
|
|
60680
|
-
};
|
|
60681
|
-
const moduleSpecifierTargetsComponent = (callerRecord, moduleSpecifier, componentRecord) => {
|
|
60682
|
-
if (!moduleSpecifier.startsWith(".")) return false;
|
|
60683
|
-
const unresolvedPath = path.resolve(path.dirname(callerRecord.absolutePath), moduleSpecifier);
|
|
60684
|
-
const specifiedExtension = path.extname(unresolvedPath).toLowerCase();
|
|
60685
|
-
const sourcePathWithoutExtension = SOURCE_EXTENSIONS.includes(specifiedExtension) ? unresolvedPath.slice(0, -specifiedExtension.length) : unresolvedPath;
|
|
60686
|
-
return [
|
|
60687
|
-
unresolvedPath,
|
|
60688
|
-
...SOURCE_EXTENSIONS.map((extension) => `${sourcePathWithoutExtension}${extension}`),
|
|
60689
|
-
...sourcePathWithoutExtension === unresolvedPath ? SOURCE_EXTENSIONS.map((extension) => path.join(unresolvedPath, `index${extension}`)) : []
|
|
60690
|
-
].includes(componentRecord.absolutePath);
|
|
60691
|
-
};
|
|
60692
|
-
const getComponentTagNames = (record, component) => {
|
|
60693
|
-
if (record.absolutePath === component.record.absolutePath) return new Set([component.name]);
|
|
60694
|
-
const tagNames = /* @__PURE__ */ new Set();
|
|
60695
|
-
for (const statement of record.sourceFile.statements) {
|
|
60696
|
-
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) || !moduleSpecifierTargetsComponent(record, statement.moduleSpecifier.text, component.record)) continue;
|
|
60697
|
-
const importClause = statement.importClause;
|
|
60698
|
-
if (component.isDefaultExport && importClause?.name !== void 0) tagNames.add(importClause.name.text);
|
|
60699
|
-
const namedBindings = importClause?.namedBindings;
|
|
60700
|
-
if (namedBindings !== void 0 && ts.isNamedImports(namedBindings)) {
|
|
60701
|
-
for (const element of namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === component.name) tagNames.add(element.name.text);
|
|
60702
|
-
}
|
|
60703
|
-
}
|
|
60704
|
-
return tagNames;
|
|
60705
|
-
};
|
|
60706
|
-
const getTransparentCall = (functionNode) => {
|
|
60707
|
-
if (functionNode.body === void 0) return null;
|
|
60708
|
-
if (!ts.isBlock(functionNode.body)) {
|
|
60709
|
-
const expression = unwrapExpression(functionNode.body);
|
|
60710
|
-
return ts.isCallExpression(expression) ? expression : null;
|
|
60711
|
-
}
|
|
60712
|
-
if (functionNode.body.statements.length !== 1) return null;
|
|
60713
|
-
const statement = functionNode.body.statements[0];
|
|
60714
|
-
if (statement === void 0) return null;
|
|
60715
|
-
const expression = ts.isExpressionStatement(statement) || ts.isReturnStatement(statement) ? statement.expression : void 0;
|
|
60716
|
-
if (expression === void 0) return null;
|
|
60717
|
-
const unwrappedExpression = unwrapExpression(expression);
|
|
60718
|
-
return ts.isCallExpression(unwrappedExpression) ? unwrappedExpression : null;
|
|
60719
|
-
};
|
|
60720
|
-
const resolveTransparentTarget = (functionNode) => {
|
|
60721
|
-
const call = getTransparentCall(functionNode);
|
|
60722
|
-
if (call === null) return null;
|
|
60723
|
-
const target = unwrapExpression(call.expression);
|
|
60724
|
-
if (!ts.isIdentifier(target) || call.arguments.length !== functionNode.parameters.length) return null;
|
|
60725
|
-
for (const [parameterIndex, parameter] of functionNode.parameters.entries()) {
|
|
60726
|
-
const argument = call.arguments[parameterIndex];
|
|
60727
|
-
const unwrappedArgument = argument === void 0 ? null : unwrapExpression(argument);
|
|
60728
|
-
if (!ts.isIdentifier(parameter.name) || unwrappedArgument === null || !ts.isIdentifier(unwrappedArgument) || unwrappedArgument.text !== parameter.name.text) return null;
|
|
60729
|
-
}
|
|
60730
|
-
return target.text;
|
|
60731
|
-
};
|
|
60732
|
-
const getForwardingFunction = (declaration) => {
|
|
60733
|
-
if (ts.isFunctionDeclaration(declaration)) return declaration;
|
|
60734
|
-
if (!ts.isVariableDeclaration(declaration) || declaration.initializer === void 0) return null;
|
|
60735
|
-
const initializer = unwrapExpression(declaration.initializer);
|
|
60736
|
-
if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) return initializer;
|
|
60737
|
-
if (ts.isCallExpression(initializer) && getCalleeName(initializer.expression) === "useCallback") {
|
|
60738
|
-
const callback = initializer.arguments[0];
|
|
60739
|
-
if (callback !== void 0 && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) return callback;
|
|
60740
|
-
}
|
|
60741
|
-
return null;
|
|
60742
|
-
};
|
|
60743
|
-
const getDeclaredBindingName = (node) => {
|
|
60744
|
-
if ((ts.isVariableDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isFunctionDeclaration(node)) && node.name !== void 0 && ts.isIdentifier(node.name)) return node.name.text;
|
|
60745
|
-
if (ts.isImportClause(node)) return node.name?.text ?? null;
|
|
60746
|
-
if (ts.isImportSpecifier(node) || ts.isNamespaceImport(node)) return node.name.text;
|
|
60747
|
-
return null;
|
|
60748
|
-
};
|
|
60749
|
-
const findBindingScope = (node) => {
|
|
60750
|
-
let currentNode = node;
|
|
60751
|
-
while (currentNode !== void 0) {
|
|
60752
|
-
if (ts.isParameter(currentNode) && ts.isFunctionLike(currentNode.parent)) return currentNode.parent;
|
|
60753
|
-
if (ts.isBlock(currentNode) || ts.isModuleBlock(currentNode) || ts.isSourceFile(currentNode)) return currentNode;
|
|
60754
|
-
currentNode = currentNode.parent;
|
|
60755
|
-
}
|
|
60756
|
-
return node.getSourceFile();
|
|
60757
|
-
};
|
|
60758
|
-
const findBindingDeclaration = (record, binding) => {
|
|
60759
|
-
const usagePosition = binding.getStart(record.sourceFile);
|
|
60760
|
-
const candidates = [];
|
|
60761
|
-
const visit = (node) => {
|
|
60762
|
-
if (getDeclaredBindingName(node) === binding.text) {
|
|
60763
|
-
const scope = findBindingScope(node);
|
|
60764
|
-
const isVisibleScope = usagePosition >= scope.getStart(record.sourceFile) && usagePosition <= scope.end;
|
|
60765
|
-
const isVisibleDeclaration = ts.isFunctionDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isImportClause(node) || ts.isImportSpecifier(node) || ts.isNamespaceImport(node) || node.getStart(record.sourceFile) < usagePosition;
|
|
60766
|
-
if (isVisibleScope && isVisibleDeclaration) candidates.push({
|
|
60767
|
-
declaration: node,
|
|
60768
|
-
scope
|
|
60769
|
-
});
|
|
60770
|
-
}
|
|
60771
|
-
ts.forEachChild(node, visit);
|
|
60772
|
-
};
|
|
60773
|
-
visit(record.sourceFile);
|
|
60774
|
-
let nearestCandidate = null;
|
|
60775
|
-
let nearestScopeWidth = Number.POSITIVE_INFINITY;
|
|
60776
|
-
let isNearestScopeAmbiguous = false;
|
|
60777
|
-
for (const candidate of candidates) {
|
|
60778
|
-
const scopeWidth = candidate.scope.end - candidate.scope.getStart(record.sourceFile);
|
|
60779
|
-
if (scopeWidth < nearestScopeWidth) {
|
|
60780
|
-
nearestCandidate = candidate;
|
|
60781
|
-
nearestScopeWidth = scopeWidth;
|
|
60782
|
-
isNearestScopeAmbiguous = false;
|
|
60783
|
-
} else if (scopeWidth === nearestScopeWidth) isNearestScopeAmbiguous = true;
|
|
60784
|
-
}
|
|
60785
|
-
if (isNearestScopeAmbiguous) return null;
|
|
60786
|
-
const declaration = nearestCandidate?.declaration;
|
|
60787
|
-
return declaration !== void 0 && (ts.isVariableDeclaration(declaration) || ts.isFunctionDeclaration(declaration)) ? declaration : null;
|
|
60788
|
-
};
|
|
60789
|
-
const resolveHandlerBinding = (record, binding) => {
|
|
60790
|
-
const declaration = findBindingDeclaration(record, binding);
|
|
60791
|
-
if (declaration === null) return binding.text;
|
|
60792
|
-
const forwardingFunction = getForwardingFunction(declaration);
|
|
60793
|
-
const target = forwardingFunction === null ? null : resolveTransparentTarget(forwardingFunction);
|
|
60794
|
-
return target === null || target === binding.text ? binding.text : target;
|
|
60795
|
-
};
|
|
60796
|
-
const getJsxTagName = (node) => ts.isIdentifier(node.tagName) ? node.tagName.text : null;
|
|
60797
|
-
const getHandlerBinding = (node, propName) => {
|
|
60798
|
-
for (const property of node.attributes.properties) {
|
|
60799
|
-
if (!ts.isJsxAttribute(property) || property.name.getText() !== propName) continue;
|
|
60800
|
-
const initializer = property.initializer;
|
|
60801
|
-
if (initializer === void 0 || !ts.isJsxExpression(initializer)) return null;
|
|
60802
|
-
if (initializer.expression === void 0) return null;
|
|
60803
|
-
const expression = unwrapExpression(initializer.expression);
|
|
60804
|
-
return ts.isIdentifier(expression) ? expression : null;
|
|
60805
|
-
}
|
|
60806
|
-
return null;
|
|
60807
|
-
};
|
|
60808
|
-
const getForwardedHandlerAliases = (rootDirectory, component, propNames, state) => {
|
|
60809
|
-
const cacheKey = `${component.record.filePath}\0${component.name}\0${[...propNames].sort().join("\0")}`;
|
|
60810
|
-
const cachedAliases = state.aliasesByComponent.get(cacheKey);
|
|
60811
|
-
if (cachedAliases !== void 0) return cachedAliases;
|
|
60812
|
-
const bindingsByPropName = /* @__PURE__ */ new Map();
|
|
60813
|
-
const unresolvedPropNames = /* @__PURE__ */ new Set();
|
|
60814
|
-
let callsiteCount = 0;
|
|
60815
|
-
for (const record of listSourceRecords(rootDirectory, state)) {
|
|
60816
|
-
const tagNames = getComponentTagNames(record, component);
|
|
60817
|
-
if (tagNames.size === 0) continue;
|
|
60818
|
-
const visit = (node) => {
|
|
60819
|
-
if (record === component.record && node === component.node) return;
|
|
60820
|
-
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
60821
|
-
const tagName = getJsxTagName(node);
|
|
60822
|
-
if (tagName !== null && tagNames.has(tagName)) {
|
|
60823
|
-
callsiteCount += 1;
|
|
60824
|
-
for (const propName of propNames) {
|
|
60825
|
-
const binding = getHandlerBinding(node, propName);
|
|
60826
|
-
if (binding === null) {
|
|
60827
|
-
unresolvedPropNames.add(propName);
|
|
60828
|
-
continue;
|
|
60829
|
-
}
|
|
60830
|
-
const bindings = bindingsByPropName.get(propName) ?? /* @__PURE__ */ new Set();
|
|
60831
|
-
bindings.add(resolveHandlerBinding(record, binding));
|
|
60832
|
-
bindingsByPropName.set(propName, bindings);
|
|
60833
|
-
}
|
|
60834
|
-
}
|
|
60835
|
-
}
|
|
60836
|
-
ts.forEachChild(node, visit);
|
|
60837
|
-
};
|
|
60838
|
-
visit(record.sourceFile);
|
|
60839
|
-
}
|
|
60840
|
-
const aliases = /* @__PURE__ */ new Map();
|
|
60841
|
-
if (callsiteCount > 0) for (const [propName, bindings] of bindingsByPropName) {
|
|
60842
|
-
if (bindings.size !== 1 || unresolvedPropNames.has(propName)) continue;
|
|
60843
|
-
const [bindingName] = bindings;
|
|
60844
|
-
if (bindingName !== void 0) aliases.set(propName, bindingName);
|
|
60845
|
-
}
|
|
60846
|
-
state.aliasesByComponent.set(cacheKey, aliases);
|
|
60847
|
-
return aliases;
|
|
60848
|
-
};
|
|
60849
|
-
const normalizeForwardedHandlers = (evidence, aliases) => {
|
|
60850
|
-
let normalizedEvidence = evidence;
|
|
60851
|
-
for (const [propName, bindingName] of aliases) normalizedEvidence = normalizedEvidence.replace(new RegExp(`\\{\\s*${propName}\\s*\\}`, "g"), `{${bindingName}}`).replace(new RegExp(`=>\\s*${propName}\\s*\\(`, "g"), `=> ${bindingName}(`);
|
|
60852
|
-
return normalizedEvidence;
|
|
60853
|
-
};
|
|
60854
|
-
const createDiagnosticEvidenceReader = (rootDirectory, options = {}) => {
|
|
60855
|
-
const state = {
|
|
60856
|
-
aliasesByComponent: /* @__PURE__ */ new Map(),
|
|
60857
|
-
recordByFilePath: /* @__PURE__ */ new Map(),
|
|
60858
|
-
sourceRecords: null
|
|
60859
|
-
};
|
|
60860
|
-
return (diagnostic) => {
|
|
60861
|
-
const record = readSourceRecord(rootDirectory, diagnostic.filePath, state);
|
|
60862
|
-
if (record === null || !Number.isInteger(diagnostic.line)) return null;
|
|
60863
|
-
const sourceLines = record.sourceText.split(/\r?\n/);
|
|
60864
|
-
const startLineIndex = Math.max(0, diagnostic.line - 1);
|
|
60865
|
-
const endLineIndex = Math.max(startLineIndex, (diagnostic.endLine ?? diagnostic.line) - 1);
|
|
60866
|
-
const evidence = sourceLines.slice(startLineIndex, endLineIndex + 1).join("\n");
|
|
60867
|
-
if (!options.resolveForwardedHandlers) return evidence;
|
|
60868
|
-
const propNames = new Set(evidence.match(/\bon[A-Z]\w*\b/g) ?? []);
|
|
60869
|
-
if (propNames.size === 0) return evidence;
|
|
60870
|
-
const component = findEnclosingComponent(record, diagnostic);
|
|
60871
|
-
if (component === null) return evidence;
|
|
60872
|
-
return normalizeForwardedHandlers(evidence, getForwardedHandlerAliases(rootDirectory, component, propNames, state));
|
|
60873
|
-
};
|
|
60874
|
-
};
|
|
60875
|
-
//#endregion
|
|
60876
60404
|
//#region src/cli/utils/build-no-score-message.ts
|
|
60877
60405
|
const ENTERPRISE_CONTACT_HINT = `Want something custom to your company? Contact us at ${ENTERPRISE_CONTACT_URL}.`;
|
|
60878
60406
|
const buildNoScoreMessage = (isScoreDisabled) => {
|
|
@@ -62116,8 +61644,6 @@ const runBaselineComparison = async (params) => {
|
|
|
62116
61644
|
directory: params.directory,
|
|
62117
61645
|
ref: params.baselineRef,
|
|
62118
61646
|
files: params.options.includePaths,
|
|
62119
|
-
baseFiles: params.baseFiles,
|
|
62120
|
-
headFiles: params.headFiles,
|
|
62121
61647
|
tempDirectory
|
|
62122
61648
|
}).catch((error) => {
|
|
62123
61649
|
rmSync(tempDirectory, {
|
|
@@ -62126,24 +61652,7 @@ const runBaselineComparison = async (params) => {
|
|
|
62126
61652
|
});
|
|
62127
61653
|
throw error;
|
|
62128
61654
|
});
|
|
62129
|
-
if (snapshot === null) {
|
|
62130
|
-
rmSync(tempDirectory, {
|
|
62131
|
-
recursive: true,
|
|
62132
|
-
force: true
|
|
62133
|
-
});
|
|
62134
|
-
return null;
|
|
62135
|
-
}
|
|
62136
61655
|
try {
|
|
62137
|
-
if (!snapshot.isComplete) return null;
|
|
62138
|
-
const analyzedHeadFiles = new Set(params.headAnalyzedFiles.map(toForwardSlashes));
|
|
62139
|
-
const baseFiles = new Set(snapshot.baseFiles.map(toForwardSlashes));
|
|
62140
|
-
const trackedHeadFiles = new Set(snapshot.headFiles.map(toForwardSlashes));
|
|
62141
|
-
const expectedHeadFiles = new Set(trackedHeadFiles);
|
|
62142
|
-
for (const filePath of params.options.includePaths) {
|
|
62143
|
-
const normalizedFilePath = toForwardSlashes(filePath);
|
|
62144
|
-
if (!baseFiles.has(normalizedFilePath)) expectedHeadFiles.add(normalizedFilePath);
|
|
62145
|
-
}
|
|
62146
|
-
if (filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
|
|
62147
61656
|
const baseLayers = buildRuntimeLayers({
|
|
62148
61657
|
directory: snapshot.tempDirectory,
|
|
62149
61658
|
hasConfigOverride: true,
|
|
@@ -62159,7 +61668,7 @@ const runBaselineComparison = async (params) => {
|
|
|
62159
61668
|
});
|
|
62160
61669
|
const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
|
|
62161
61670
|
directory: snapshot.tempDirectory,
|
|
62162
|
-
includePaths:
|
|
61671
|
+
includePaths: params.options.includePaths,
|
|
62163
61672
|
customRulesOnly: params.options.customRulesOnly,
|
|
62164
61673
|
respectInlineDisables: params.options.respectInlineDisables,
|
|
62165
61674
|
warnings: params.options.warnings,
|
|
@@ -62176,22 +61685,18 @@ const runBaselineComparison = async (params) => {
|
|
|
62176
61685
|
deadlineEpochMs: params.deadlineEpochMs ?? void 0
|
|
62177
61686
|
}, {}).pipe(provide(baseLayers), provideService$2(PerFileLintCacheEnabled, false), provideService$2(SidecarLintCacheEnabled, false), provideService$2(Console, silentConsole))));
|
|
62178
61687
|
if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
|
|
62179
|
-
const hasUnscannedUntrackedSourceFiles = filterSourceFiles(snapshot.untrackedFiles.map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
|
|
62180
61688
|
const delta = computeDiagnosticDelta({
|
|
62181
61689
|
headDiagnostics: params.headDiagnostics,
|
|
62182
61690
|
baseDiagnostics: baseOutput.diagnostics,
|
|
62183
61691
|
readHeadLine: createSourceLineReader(params.directory),
|
|
62184
|
-
readBaseLine: createSourceLineReader(snapshot.tempDirectory)
|
|
62185
|
-
readHeadEvidence: createDiagnosticEvidenceReader(params.directory, { resolveForwardedHandlers: true }),
|
|
62186
|
-
readBaseEvidence: createDiagnosticEvidenceReader(snapshot.tempDirectory)
|
|
61692
|
+
readBaseLine: createSourceLineReader(snapshot.tempDirectory)
|
|
62187
61693
|
});
|
|
62188
61694
|
return {
|
|
62189
61695
|
displayDiagnostics: delta.newDiagnostics,
|
|
62190
61696
|
baselineDelta: {
|
|
62191
61697
|
baseRef: params.baselineRef,
|
|
62192
|
-
fixedCount:
|
|
62193
|
-
baseTotalCount: baseOutput.diagnostics.length
|
|
62194
|
-
crossFileMatchCount: delta.crossFileMatchCount
|
|
61698
|
+
fixedCount: delta.fixedCount,
|
|
61699
|
+
baseTotalCount: baseOutput.diagnostics.length
|
|
62195
61700
|
}
|
|
62196
61701
|
};
|
|
62197
61702
|
} finally {
|
|
@@ -62298,9 +61803,6 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
62298
61803
|
headDiagnostics: output.diagnostics,
|
|
62299
61804
|
resolvedNodeBinaryPath,
|
|
62300
61805
|
baselineRef: options.baseline.ref,
|
|
62301
|
-
baseFiles: options.baseline.baseFiles,
|
|
62302
|
-
headFiles: options.baseline.headFiles,
|
|
62303
|
-
headAnalyzedFiles: output.analyzedFiles,
|
|
62304
61806
|
deadlineEpochMs
|
|
62305
61807
|
});
|
|
62306
61808
|
if (comparison) {
|
|
@@ -64585,25 +64087,20 @@ const resolveProjectRelativeDirectory = (rootDirectory, projectDirectory) => {
|
|
|
64585
64087
|
return relativeProjectDirectory;
|
|
64586
64088
|
};
|
|
64587
64089
|
//#endregion
|
|
64588
|
-
//#region src/cli/utils/resolve-project-
|
|
64589
|
-
const
|
|
64090
|
+
//#region src/cli/utils/resolve-project-diff-include-paths.ts
|
|
64091
|
+
const resolveProjectDiffIncludePaths = (rootDirectory, projectDirectory, diffInfo) => {
|
|
64590
64092
|
const relativeProjectDirectory = resolveProjectRelativeDirectory(rootDirectory, projectDirectory);
|
|
64591
64093
|
if (relativeProjectDirectory === null) return [];
|
|
64592
|
-
const
|
|
64593
|
-
if (relativeProjectDirectory.length === 0) return
|
|
64094
|
+
const changedSourceFiles = filterSourceFiles(diffInfo.changedFiles);
|
|
64095
|
+
if (relativeProjectDirectory.length === 0) return changedSourceFiles;
|
|
64594
64096
|
const projectPrefix = `${relativeProjectDirectory}/`;
|
|
64595
|
-
return
|
|
64097
|
+
return changedSourceFiles.flatMap((filePath) => {
|
|
64596
64098
|
const normalizedFilePath = toForwardSlashes(filePath);
|
|
64597
64099
|
if (!normalizedFilePath.startsWith(projectPrefix)) return [];
|
|
64598
64100
|
const projectRelativePath = normalizedFilePath.slice(projectPrefix.length);
|
|
64599
64101
|
return projectRelativePath.length > 0 ? [projectRelativePath] : [];
|
|
64600
64102
|
});
|
|
64601
64103
|
};
|
|
64602
|
-
//#endregion
|
|
64603
|
-
//#region src/cli/utils/resolve-project-diff-include-paths.ts
|
|
64604
|
-
const resolveProjectDiffIncludePaths = (rootDirectory, projectDirectory, diffInfo) => {
|
|
64605
|
-
return resolveProjectSourceFilePaths(rootDirectory, projectDirectory, diffInfo.changedFiles);
|
|
64606
|
-
};
|
|
64607
64104
|
/**
|
|
64608
64105
|
* Re-keys repo-relative changed line ranges (from `getChangedLineRanges`) to
|
|
64609
64106
|
* the scanned project, mirroring `resolveProjectDiffIncludePaths`'s boundary
|
|
@@ -65142,7 +64639,6 @@ const inspectAction = async (directory, flags) => {
|
|
|
65142
64639
|
const isDiffMode = scope !== "full";
|
|
65143
64640
|
const comparisonBaseRef = isDiffMode && diffInfo && !diffInfo.isCurrentChanges ? diffInfo.baseSha ? await resolveMergeBaseRef(resolvedDirectory, diffInfo.baseSha) : diffInfo.diffBaseRef ?? await resolveMergeBaseRef(resolvedDirectory, diffInfo.baseBranch) : null;
|
|
65144
64641
|
const baselineRef = scope === "changed" ? comparisonBaseRef : null;
|
|
65145
|
-
const baselineDiffPlan = baselineRef === null ? null : await getBaselineDiffPlan(resolvedDirectory, baselineRef);
|
|
65146
64642
|
const linesBaseRef = diffInfo?.isCurrentChanges ? "HEAD" : comparisonBaseRef;
|
|
65147
64643
|
const changedLineRanges = scope === "lines" && diffInfo !== null && (diffInfo.isCurrentChanges || linesBaseRef !== null) && diffInfo !== null ? await getChangedLineRanges({
|
|
65148
64644
|
directory: resolvedDirectory,
|
|
@@ -65173,13 +64669,10 @@ const inspectAction = async (directory, flags) => {
|
|
|
65173
64669
|
const supplyChainEnabled = flags.supplyChain ?? projectConfig?.supplyChain?.enabled !== false;
|
|
65174
64670
|
let includePaths;
|
|
65175
64671
|
let supplyChainManifestChanged = false;
|
|
65176
|
-
const projectBaselineBaseFiles = baselineDiffPlan === null ? null : resolveProjectSourceFilePaths(resolvedDirectory, scanDirectory, baselineDiffPlan.baseFiles);
|
|
65177
|
-
const projectBaselineHeadFiles = baselineDiffPlan === null ? null : resolveProjectSourceFilePaths(resolvedDirectory, scanDirectory, baselineDiffPlan.headFiles);
|
|
65178
64672
|
if (isDiffMode) {
|
|
65179
64673
|
const changedSourceFiles = diffInfo === null ? [] : resolveProjectDiffIncludePaths(resolvedDirectory, scanDirectory, diffInfo);
|
|
65180
64674
|
supplyChainManifestChanged = supplyChainEnabled && diffInfo !== null && projectManifestChanged(resolvedDirectory, scanDirectory, diffInfo);
|
|
65181
|
-
|
|
65182
|
-
if (changedSourceFiles.length === 0 && !supplyChainManifestChanged && !hasBaselineOnlyFiles) {
|
|
64675
|
+
if (changedSourceFiles.length === 0 && !supplyChainManifestChanged) {
|
|
65183
64676
|
if (!isQuiet) {
|
|
65184
64677
|
cliLogger.dim(`No changed source files in ${scanDirectory}, skipping.`);
|
|
65185
64678
|
cliLogger.break();
|
|
@@ -65187,7 +64680,6 @@ const inspectAction = async (directory, flags) => {
|
|
|
65187
64680
|
return null;
|
|
65188
64681
|
}
|
|
65189
64682
|
includePaths = [...changedSourceFiles];
|
|
65190
|
-
if (includePaths.length === 0 && hasBaselineOnlyFiles) includePaths.push(...projectBaselineBaseFiles ?? []);
|
|
65191
64683
|
if (supplyChainManifestChanged) includePaths.push("package.json");
|
|
65192
64684
|
}
|
|
65193
64685
|
if (!isQuiet && !isMultiProject) cliLogger.dim(" ");
|
|
@@ -65199,11 +64691,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
65199
64691
|
configSourceDirectory: projectConfigSourceDirectory ?? void 0,
|
|
65200
64692
|
suppressRendering: isMultiProject,
|
|
65201
64693
|
concurrentScan: isMultiProject,
|
|
65202
|
-
baseline: baselineRef
|
|
65203
|
-
ref: baselineRef,
|
|
65204
|
-
baseFiles: projectBaselineBaseFiles,
|
|
65205
|
-
headFiles: projectBaselineHeadFiles
|
|
65206
|
-
} : void 0,
|
|
64694
|
+
baseline: baselineRef ? { ref: baselineRef } : void 0,
|
|
65207
64695
|
changedLineRanges: scope === "lines" && changedLineRanges !== null ? resolveProjectChangedLineRanges(resolvedDirectory, scanDirectory, changedLineRanges) : void 0,
|
|
65208
64696
|
supplyChainManifestChanged
|
|
65209
64697
|
});
|
|
@@ -66403,4 +65891,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
66403
65891
|
export {};
|
|
66404
65892
|
|
|
66405
65893
|
//# sourceMappingURL=cli.js.map
|
|
66406
|
-
//# debugId=
|
|
65894
|
+
//# debugId=941df082-c8b4-5084-baae-33ec22d4e55f
|
package/dist/index.d.ts
CHANGED
|
@@ -9030,12 +9030,11 @@ interface Diagnostic {
|
|
|
9030
9030
|
* Set when the finding's identity is the flagged element itself (a missing
|
|
9031
9031
|
* attribute, a wrong element) rather than the flagged line's text — every
|
|
9032
9032
|
* Accessibility-category finding, plus rules that opt in via their
|
|
9033
|
-
* `matchByOccurrence` metadata flag.
|
|
9034
|
-
* `
|
|
9035
|
-
*
|
|
9036
|
-
*
|
|
9037
|
-
*
|
|
9038
|
-
* matching (the default).
|
|
9033
|
+
* `matchByOccurrence` metadata flag. `computeDiagnosticDelta` matches these
|
|
9034
|
+
* by `(filePath, plugin/rule)` occurrence count, so reformatting the flagged
|
|
9035
|
+
* line doesn't reclassify a pre-existing finding as new. Resolved at
|
|
9036
|
+
* diagnostic creation, where rule metadata is available. Absent means
|
|
9037
|
+
* line-text-sensitive matching (the default).
|
|
9039
9038
|
*/
|
|
9040
9039
|
matchByOccurrence?: boolean;
|
|
9041
9040
|
/**
|
|
@@ -9848,8 +9847,7 @@ interface InspectResult {
|
|
|
9848
9847
|
baselineDelta?: {
|
|
9849
9848
|
/** The commit the base content was read from (resolved merge-base). */baseRef: string; /** Findings present at base but gone at head — resolved by the change. */
|
|
9850
9849
|
fixedCount: number; /** Total findings at base (over the same files), for context. */
|
|
9851
|
-
baseTotalCount: number;
|
|
9852
|
-
crossFileMatchCount?: number;
|
|
9850
|
+
baseTotalCount: number;
|
|
9853
9851
|
};
|
|
9854
9852
|
}
|
|
9855
9853
|
/**
|
|
@@ -10265,7 +10263,15 @@ declare const buildJsonReport: (input: BuildJsonReportInput) => JsonReportV3$1;
|
|
|
10265
10263
|
* `GitBaseBranchMissing`) as clean user errors instead of crashes.
|
|
10266
10264
|
*/
|
|
10267
10265
|
declare const getDiffInfo: (directory: string, explicitBaseBranch?: string, includeUntracked?: boolean) => Promise<DiffInfo | null>;
|
|
10268
|
-
declare const filterSourceFiles: (filePaths:
|
|
10266
|
+
declare const filterSourceFiles: (filePaths: string[]) => string[];
|
|
10267
|
+
/**
|
|
10268
|
+
* Programmatic façade over `Git.changedLineRanges` (the `lines` scope). Diffs
|
|
10269
|
+
* `files` with `--unified=0` against `baseRef` (or the index when `cached`),
|
|
10270
|
+
* returning per-file changed line ranges relative to `directory`. Returns
|
|
10271
|
+
* `null` when the ranges can't be computed (git unavailable / unsafe ref /
|
|
10272
|
+
* non-zero exit) so the caller degrades to file-level scope; an empty array
|
|
10273
|
+
* means git succeeded but the files added no lines.
|
|
10274
|
+
*/
|
|
10269
10275
|
//#endregion
|
|
10270
10276
|
//#region src/summarize-diagnostics.d.ts
|
|
10271
10277
|
declare const summarizeDiagnostics: (diagnostics: Diagnostic[], worstScore?: number | null, worstScoreLabel?: string | null) => JsonReportSummary; //#endregion
|
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]="3121a4e4-8a4a-55ea-ade8-47d0bf576a2f")}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";
|
|
@@ -39090,35 +39090,6 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
39090
39090
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
39091
39091
|
};
|
|
39092
39092
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
39093
|
-
const parseBaselineDiffPlan = (value) => {
|
|
39094
|
-
const entries = splitNullSeparated(value);
|
|
39095
|
-
const baseFiles = /* @__PURE__ */ new Set();
|
|
39096
|
-
const headFiles = /* @__PURE__ */ new Set();
|
|
39097
|
-
for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 2) {
|
|
39098
|
-
const status = entries[entryIndex];
|
|
39099
|
-
const filePath = entries[entryIndex + 1];
|
|
39100
|
-
if (status === void 0 || filePath === void 0 || status.length !== 1) return null;
|
|
39101
|
-
if (status === "A") {
|
|
39102
|
-
headFiles.add(filePath);
|
|
39103
|
-
continue;
|
|
39104
|
-
}
|
|
39105
|
-
if (status === "D") {
|
|
39106
|
-
baseFiles.add(filePath);
|
|
39107
|
-
continue;
|
|
39108
|
-
}
|
|
39109
|
-
if (status === "M" || status === "T") {
|
|
39110
|
-
baseFiles.add(filePath);
|
|
39111
|
-
headFiles.add(filePath);
|
|
39112
|
-
continue;
|
|
39113
|
-
}
|
|
39114
|
-
return null;
|
|
39115
|
-
}
|
|
39116
|
-
return {
|
|
39117
|
-
baseFiles: [...baseFiles],
|
|
39118
|
-
headFiles: [...headFiles],
|
|
39119
|
-
untrackedFiles: []
|
|
39120
|
-
};
|
|
39121
|
-
};
|
|
39122
39093
|
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
39123
39094
|
/**
|
|
39124
39095
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
@@ -39321,45 +39292,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
39321
39292
|
githubViewerPermission,
|
|
39322
39293
|
branchExists,
|
|
39323
39294
|
mergeBase,
|
|
39324
|
-
baselineDiffPlan: (input) => {
|
|
39325
|
-
if (!isSafeGitRevision(input.ref)) return succeed$2(null);
|
|
39326
|
-
return gen(function* () {
|
|
39327
|
-
const unmerged = yield* runGit(input.directory, [
|
|
39328
|
-
"diff",
|
|
39329
|
-
"--no-ext-diff",
|
|
39330
|
-
"-z",
|
|
39331
|
-
"--name-only",
|
|
39332
|
-
"--diff-filter=U",
|
|
39333
|
-
"--relative"
|
|
39334
|
-
]);
|
|
39335
|
-
if (unmerged.status !== 0 || unmerged.stdout.length > 0) return null;
|
|
39336
|
-
const result = yield* runGit(input.directory, [
|
|
39337
|
-
"diff",
|
|
39338
|
-
"--no-ext-diff",
|
|
39339
|
-
"--no-textconv",
|
|
39340
|
-
"--no-renames",
|
|
39341
|
-
"-z",
|
|
39342
|
-
"--name-status",
|
|
39343
|
-
"--relative",
|
|
39344
|
-
input.ref
|
|
39345
|
-
]);
|
|
39346
|
-
if (result.status !== 0) return null;
|
|
39347
|
-
const plan = parseBaselineDiffPlan(result.stdout);
|
|
39348
|
-
if (plan === null) return null;
|
|
39349
|
-
const untracked = yield* runGit(input.directory, [
|
|
39350
|
-
"ls-files",
|
|
39351
|
-
"--others",
|
|
39352
|
-
"--exclude-standard",
|
|
39353
|
-
"-z"
|
|
39354
|
-
]);
|
|
39355
|
-
if (untracked.status !== 0) return null;
|
|
39356
|
-
return {
|
|
39357
|
-
baseFiles: plan.baseFiles,
|
|
39358
|
-
headFiles: plan.headFiles,
|
|
39359
|
-
untrackedFiles: splitNullSeparated(untracked.stdout)
|
|
39360
|
-
};
|
|
39361
|
-
}).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.baselineDiffPlan"));
|
|
39362
|
-
},
|
|
39363
39295
|
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
39364
39296
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
39365
39297
|
if (explicitBaseBranch !== void 0) {
|
|
@@ -39509,7 +39441,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
39509
39441
|
githubViewerPermission: () => succeed$2(snapshot.githubViewerPermission ?? null),
|
|
39510
39442
|
branchExists: (_directory, branch) => succeed$2(snapshot.branchExists?.get(branch) ?? false),
|
|
39511
39443
|
mergeBase: ({ ref }) => succeed$2(snapshot.mergeBase?.get(ref) ?? null),
|
|
39512
|
-
baselineDiffPlan: () => succeed$2(snapshot.baselineDiffPlan ?? null),
|
|
39513
39444
|
diffSelection: () => succeed$2(snapshot.diffSelection ?? null),
|
|
39514
39445
|
stagedFilePaths: () => succeed$2(snapshot.stagedFiles ?? []),
|
|
39515
39446
|
showStagedContent: (_directory, relativePath) => succeed$2(snapshot.stagedContent?.get(relativePath) ?? null),
|
|
@@ -40847,17 +40778,12 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
|
|
|
40847
40778
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
40848
40779
|
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
|
|
40849
40780
|
const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
|
|
40850
|
-
const SET_STATE_IN_EFFECT_ACTION = "Prefer deriving or initializing the value before render. If the effect must read a browser API after mount, treat this as advisory or suppress it with `// react-doctor-disable-next-line react-hooks-js/set-state-in-effect`.";
|
|
40851
40781
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
40852
40782
|
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
40853
40783
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
40854
40784
|
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
40855
40785
|
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
40856
40786
|
};
|
|
40857
|
-
const buildSetStateInEffectMessage = (reasonSummary) => {
|
|
40858
|
-
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
40859
|
-
return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
|
|
40860
|
-
};
|
|
40861
40787
|
const PLUGIN_CATEGORY_MAP = {
|
|
40862
40788
|
react: "Bugs",
|
|
40863
40789
|
"react-hooks": "Bugs",
|
|
@@ -40915,10 +40841,6 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
40915
40841
|
message: REACT_ERROR_BOUNDARY_MESSAGE,
|
|
40916
40842
|
help: reasonDetail || help
|
|
40917
40843
|
};
|
|
40918
|
-
if (rule === "set-state-in-effect") return {
|
|
40919
|
-
message: buildSetStateInEffectMessage(reasonSummary.trim()),
|
|
40920
|
-
help: reasonDetail || help
|
|
40921
|
-
};
|
|
40922
40844
|
return {
|
|
40923
40845
|
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
40924
40846
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
@@ -43244,19 +43166,12 @@ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
|
|
|
43244
43166
|
*/
|
|
43245
43167
|
const materializeSourceTree = (input) => gen(function* () {
|
|
43246
43168
|
const materializedFiles = [];
|
|
43247
|
-
const unmaterializedFiles = [];
|
|
43248
43169
|
const resolvedTempDirectory = path.resolve(input.tempDirectory);
|
|
43249
43170
|
for (const relativePath of input.files) {
|
|
43250
43171
|
const content = yield* input.readContent(relativePath).pipe(orElseSucceed(() => null));
|
|
43251
|
-
if (content === null)
|
|
43252
|
-
unmaterializedFiles.push(relativePath);
|
|
43253
|
-
continue;
|
|
43254
|
-
}
|
|
43172
|
+
if (content === null) continue;
|
|
43255
43173
|
const candidateTargetPath = path.resolve(resolvedTempDirectory, relativePath);
|
|
43256
|
-
if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory))
|
|
43257
|
-
unmaterializedFiles.push(relativePath);
|
|
43258
|
-
continue;
|
|
43259
|
-
}
|
|
43174
|
+
if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) continue;
|
|
43260
43175
|
yield* sync(() => {
|
|
43261
43176
|
fs.mkdirSync(path.dirname(candidateTargetPath), { recursive: true });
|
|
43262
43177
|
fs.writeFileSync(candidateTargetPath, content);
|
|
@@ -43273,7 +43188,6 @@ const materializeSourceTree = (input) => gen(function* () {
|
|
|
43273
43188
|
return {
|
|
43274
43189
|
tempDirectory: input.tempDirectory,
|
|
43275
43190
|
materializedFiles,
|
|
43276
|
-
unmaterializedFiles,
|
|
43277
43191
|
cleanup: () => {
|
|
43278
43192
|
try {
|
|
43279
43193
|
fs.rmSync(input.tempDirectory, {
|
|
@@ -43837,4 +43751,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
43837
43751
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
43838
43752
|
|
|
43839
43753
|
//# sourceMappingURL=index.js.map
|
|
43840
|
-
//# debugId=
|
|
43754
|
+
//# debugId=3121a4e4-8a4a-55ea-ade8-47d0bf576a2f
|
package/dist/lsp.js
CHANGED
|
@@ -39068,35 +39068,6 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
39068
39068
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
39069
39069
|
};
|
|
39070
39070
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
39071
|
-
const parseBaselineDiffPlan = (value) => {
|
|
39072
|
-
const entries = splitNullSeparated(value);
|
|
39073
|
-
const baseFiles = /* @__PURE__ */ new Set();
|
|
39074
|
-
const headFiles = /* @__PURE__ */ new Set();
|
|
39075
|
-
for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 2) {
|
|
39076
|
-
const status = entries[entryIndex];
|
|
39077
|
-
const filePath = entries[entryIndex + 1];
|
|
39078
|
-
if (status === void 0 || filePath === void 0 || status.length !== 1) return null;
|
|
39079
|
-
if (status === "A") {
|
|
39080
|
-
headFiles.add(filePath);
|
|
39081
|
-
continue;
|
|
39082
|
-
}
|
|
39083
|
-
if (status === "D") {
|
|
39084
|
-
baseFiles.add(filePath);
|
|
39085
|
-
continue;
|
|
39086
|
-
}
|
|
39087
|
-
if (status === "M" || status === "T") {
|
|
39088
|
-
baseFiles.add(filePath);
|
|
39089
|
-
headFiles.add(filePath);
|
|
39090
|
-
continue;
|
|
39091
|
-
}
|
|
39092
|
-
return null;
|
|
39093
|
-
}
|
|
39094
|
-
return {
|
|
39095
|
-
baseFiles: [...baseFiles],
|
|
39096
|
-
headFiles: [...headFiles],
|
|
39097
|
-
untrackedFiles: []
|
|
39098
|
-
};
|
|
39099
|
-
};
|
|
39100
39071
|
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
39101
39072
|
/**
|
|
39102
39073
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
@@ -39299,45 +39270,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
39299
39270
|
githubViewerPermission,
|
|
39300
39271
|
branchExists,
|
|
39301
39272
|
mergeBase,
|
|
39302
|
-
baselineDiffPlan: (input) => {
|
|
39303
|
-
if (!isSafeGitRevision(input.ref)) return succeed$2(null);
|
|
39304
|
-
return gen(function* () {
|
|
39305
|
-
const unmerged = yield* runGit(input.directory, [
|
|
39306
|
-
"diff",
|
|
39307
|
-
"--no-ext-diff",
|
|
39308
|
-
"-z",
|
|
39309
|
-
"--name-only",
|
|
39310
|
-
"--diff-filter=U",
|
|
39311
|
-
"--relative"
|
|
39312
|
-
]);
|
|
39313
|
-
if (unmerged.status !== 0 || unmerged.stdout.length > 0) return null;
|
|
39314
|
-
const result = yield* runGit(input.directory, [
|
|
39315
|
-
"diff",
|
|
39316
|
-
"--no-ext-diff",
|
|
39317
|
-
"--no-textconv",
|
|
39318
|
-
"--no-renames",
|
|
39319
|
-
"-z",
|
|
39320
|
-
"--name-status",
|
|
39321
|
-
"--relative",
|
|
39322
|
-
input.ref
|
|
39323
|
-
]);
|
|
39324
|
-
if (result.status !== 0) return null;
|
|
39325
|
-
const plan = parseBaselineDiffPlan(result.stdout);
|
|
39326
|
-
if (plan === null) return null;
|
|
39327
|
-
const untracked = yield* runGit(input.directory, [
|
|
39328
|
-
"ls-files",
|
|
39329
|
-
"--others",
|
|
39330
|
-
"--exclude-standard",
|
|
39331
|
-
"-z"
|
|
39332
|
-
]);
|
|
39333
|
-
if (untracked.status !== 0) return null;
|
|
39334
|
-
return {
|
|
39335
|
-
baseFiles: plan.baseFiles,
|
|
39336
|
-
headFiles: plan.headFiles,
|
|
39337
|
-
untrackedFiles: splitNullSeparated(untracked.stdout)
|
|
39338
|
-
};
|
|
39339
|
-
}).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.baselineDiffPlan"));
|
|
39340
|
-
},
|
|
39341
39273
|
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
39342
39274
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
39343
39275
|
if (explicitBaseBranch !== void 0) {
|
|
@@ -39487,7 +39419,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
39487
39419
|
githubViewerPermission: () => succeed$2(snapshot.githubViewerPermission ?? null),
|
|
39488
39420
|
branchExists: (_directory, branch) => succeed$2(snapshot.branchExists?.get(branch) ?? false),
|
|
39489
39421
|
mergeBase: ({ ref }) => succeed$2(snapshot.mergeBase?.get(ref) ?? null),
|
|
39490
|
-
baselineDiffPlan: () => succeed$2(snapshot.baselineDiffPlan ?? null),
|
|
39491
39422
|
diffSelection: () => succeed$2(snapshot.diffSelection ?? null),
|
|
39492
39423
|
stagedFilePaths: () => succeed$2(snapshot.stagedFiles ?? []),
|
|
39493
39424
|
showStagedContent: (_directory, relativePath) => succeed$2(snapshot.stagedContent?.get(relativePath) ?? null),
|
|
@@ -40825,17 +40756,12 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
|
|
|
40825
40756
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
40826
40757
|
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
|
|
40827
40758
|
const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
|
|
40828
|
-
const SET_STATE_IN_EFFECT_ACTION = "Prefer deriving or initializing the value before render. If the effect must read a browser API after mount, treat this as advisory or suppress it with `// react-doctor-disable-next-line react-hooks-js/set-state-in-effect`.";
|
|
40829
40759
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
40830
40760
|
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
40831
40761
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
40832
40762
|
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
40833
40763
|
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
40834
40764
|
};
|
|
40835
|
-
const buildSetStateInEffectMessage = (reasonSummary) => {
|
|
40836
|
-
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
40837
|
-
return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
|
|
40838
|
-
};
|
|
40839
40765
|
const PLUGIN_CATEGORY_MAP = {
|
|
40840
40766
|
react: "Bugs",
|
|
40841
40767
|
"react-hooks": "Bugs",
|
|
@@ -40893,10 +40819,6 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
40893
40819
|
message: REACT_ERROR_BOUNDARY_MESSAGE,
|
|
40894
40820
|
help: reasonDetail || help
|
|
40895
40821
|
};
|
|
40896
|
-
if (rule === "set-state-in-effect") return {
|
|
40897
|
-
message: buildSetStateInEffectMessage(reasonSummary.trim()),
|
|
40898
|
-
help: reasonDetail || help
|
|
40899
|
-
};
|
|
40900
40822
|
return {
|
|
40901
40823
|
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
40902
40824
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
@@ -43222,19 +43144,12 @@ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
|
|
|
43222
43144
|
*/
|
|
43223
43145
|
const materializeSourceTree = (input) => gen(function* () {
|
|
43224
43146
|
const materializedFiles = [];
|
|
43225
|
-
const unmaterializedFiles = [];
|
|
43226
43147
|
const resolvedTempDirectory = path.resolve(input.tempDirectory);
|
|
43227
43148
|
for (const relativePath of input.files) {
|
|
43228
43149
|
const content = yield* input.readContent(relativePath).pipe(orElseSucceed(() => null));
|
|
43229
|
-
if (content === null)
|
|
43230
|
-
unmaterializedFiles.push(relativePath);
|
|
43231
|
-
continue;
|
|
43232
|
-
}
|
|
43150
|
+
if (content === null) continue;
|
|
43233
43151
|
const candidateTargetPath = path.resolve(resolvedTempDirectory, relativePath);
|
|
43234
|
-
if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory))
|
|
43235
|
-
unmaterializedFiles.push(relativePath);
|
|
43236
|
-
continue;
|
|
43237
|
-
}
|
|
43152
|
+
if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) continue;
|
|
43238
43153
|
yield* sync(() => {
|
|
43239
43154
|
fs.mkdirSync(path.dirname(candidateTargetPath), { recursive: true });
|
|
43240
43155
|
fs.writeFileSync(candidateTargetPath, content);
|
|
@@ -43251,7 +43166,6 @@ const materializeSourceTree = (input) => gen(function* () {
|
|
|
43251
43166
|
return {
|
|
43252
43167
|
tempDirectory: input.tempDirectory,
|
|
43253
43168
|
materializedFiles,
|
|
43254
|
-
unmaterializedFiles,
|
|
43255
43169
|
cleanup: () => {
|
|
43256
43170
|
try {
|
|
43257
43171
|
fs.rmSync(input.tempDirectory, {
|
|
@@ -45457,5 +45371,5 @@ const startLanguageServer = () => {
|
|
|
45457
45371
|
};
|
|
45458
45372
|
//#endregion
|
|
45459
45373
|
export { startLanguageServer };
|
|
45460
|
-
!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]="
|
|
45461
|
-
//# debugId=
|
|
45374
|
+
!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]="0f56accc-b639-5b90-a1a3-5faa727907dc")}catch(e){}}();
|
|
45375
|
+
//# debugId=0f56accc-b639-5b90-a1a3-5faa727907dc
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.8.1
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
67
|
"yaml": "^2.9.0",
|
|
68
68
|
"deslop-js": "0.8.1",
|
|
69
|
-
"oxlint-plugin-react-doctor": "0.8.1
|
|
69
|
+
"oxlint-plugin-react-doctor": "0.8.1"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/babel__code-frame": "^7.27.0",
|