react-doctor 0.7.5 → 0.7.6-dev.0a8d30b
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 +151 -47
- package/dist/index.d.ts +8 -7
- package/dist/index.js +124 -38
- package/dist/lsp.js +141 -63
- package/package.json +6 -6
package/dist/lsp.js
CHANGED
|
@@ -32959,7 +32959,6 @@ const IGNORED_DIRECTORIES = new Set([
|
|
|
32959
32959
|
"out",
|
|
32960
32960
|
"storybook-static"
|
|
32961
32961
|
]);
|
|
32962
|
-
const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
32963
32962
|
const MILLISECONDS_PER_SECOND = 1e3;
|
|
32964
32963
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
32965
32964
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
@@ -33092,7 +33091,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
33092
33091
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
33093
33092
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
33094
33093
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
33095
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
33094
|
+
const CORE_PACKAGE_VERSION = "0.7.6";
|
|
33096
33095
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
33097
33096
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
33098
33097
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -33195,13 +33194,15 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
33195
33194
|
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
|
|
33196
33195
|
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
33197
33196
|
const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
|
|
33197
|
+
const UTF8_BOM_CHARACTER = "";
|
|
33198
|
+
const stripUtf8Bom = (content) => content.startsWith(UTF8_BOM_CHARACTER) ? content.slice(1) : content;
|
|
33198
33199
|
const cachedPackageJsons = /* @__PURE__ */ new Map();
|
|
33199
33200
|
const clearPackageJsonCache = () => {
|
|
33200
33201
|
cachedPackageJsons.clear();
|
|
33201
33202
|
};
|
|
33202
33203
|
const readPackageJsonUncached = (packageJsonPath) => {
|
|
33203
33204
|
try {
|
|
33204
|
-
return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
33205
|
+
return JSON.parse(stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8")));
|
|
33205
33206
|
} catch (error) {
|
|
33206
33207
|
if (error instanceof SyntaxError) return {};
|
|
33207
33208
|
if (isErrnoException(error)) {
|
|
@@ -36768,7 +36769,6 @@ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
|
36768
36769
|
const PNPM_LOCKFILE = "pnpm-lock.yaml";
|
|
36769
36770
|
const PACKAGE_JSON_FILE = "package.json";
|
|
36770
36771
|
const PNPM_HARDENING_RULE_KEY = "require-pnpm-hardening";
|
|
36771
|
-
const UTF8_BOM_CHAR = "";
|
|
36772
36772
|
const HARDENING_SETTING_KEYS = new Set([
|
|
36773
36773
|
"minimumReleaseAge",
|
|
36774
36774
|
"blockExoticSubdeps",
|
|
@@ -36793,12 +36793,11 @@ const stripInlineComment = (rawValue) => {
|
|
|
36793
36793
|
return rawValue;
|
|
36794
36794
|
};
|
|
36795
36795
|
const unquote = (rawValue) => rawValue.replace(/^["']|["']$/g, "");
|
|
36796
|
-
const stripBom = (rawContent) => rawContent.startsWith(UTF8_BOM_CHAR) ? rawContent.slice(1) : rawContent;
|
|
36797
36796
|
const parseHardeningSettings = (content) => {
|
|
36798
36797
|
let minimumReleaseAge = null;
|
|
36799
36798
|
let blockExoticSubdeps = null;
|
|
36800
36799
|
let trustPolicy = null;
|
|
36801
|
-
const lines =
|
|
36800
|
+
const lines = stripUtf8Bom(content).split(/\r?\n/);
|
|
36802
36801
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
36803
36802
|
const lineText = lines[lineIndex];
|
|
36804
36803
|
if (lineText === void 0) continue;
|
|
@@ -36832,7 +36831,7 @@ const isPnpmManagedProject = (rootDirectory) => {
|
|
|
36832
36831
|
const packageJsonPath = Path.join(rootDirectory, PACKAGE_JSON_FILE);
|
|
36833
36832
|
if (!isFile(packageJsonPath)) return false;
|
|
36834
36833
|
try {
|
|
36835
|
-
const packageJsonRaw = NFS.readFileSync(packageJsonPath, "utf-8");
|
|
36834
|
+
const packageJsonRaw = stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
36836
36835
|
const packageJson = JSON.parse(packageJsonRaw);
|
|
36837
36836
|
if (packageJson !== null && typeof packageJson === "object" && "packageManager" in packageJson && typeof packageJson.packageManager === "string" && packageJson.packageManager.startsWith("pnpm@")) return true;
|
|
36838
36837
|
} catch {
|
|
@@ -37494,9 +37493,7 @@ const highlighter = {
|
|
|
37494
37493
|
gray: import_picocolors.default.gray,
|
|
37495
37494
|
bold: import_picocolors.default.bold
|
|
37496
37495
|
};
|
|
37497
|
-
const
|
|
37498
|
-
const shouldIncludeExplicitLintPath = (filePath, project) => JSX_FILE_PATTERN.test(filePath) || project?.framework === "nextjs" && NEXT_ENTRY_FILE_PATTERN.test(filePath);
|
|
37499
|
-
const computeExplicitLintIncludePaths = (includePaths, project) => includePaths.length > 0 ? includePaths.filter((filePath) => shouldIncludeExplicitLintPath(filePath, project)) : void 0;
|
|
37496
|
+
const computeExplicitLintIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter(isLintableSourceFile) : void 0;
|
|
37500
37497
|
const LINGUIST_ATTRIBUTE_PATTERN = /^linguist-(?:vendored|generated)(?:=([a-zA-Z0-9]+))?$/i;
|
|
37501
37498
|
const FALSY_VALUES = new Set([
|
|
37502
37499
|
"false",
|
|
@@ -38338,11 +38335,11 @@ const resolveDeadCodeTimeout = (input) => {
|
|
|
38338
38335
|
phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
|
|
38339
38336
|
};
|
|
38340
38337
|
};
|
|
38341
|
-
const resolveLintIncludePaths = (rootDirectory, userConfig
|
|
38338
|
+
const resolveLintIncludePaths = (rootDirectory, userConfig) => {
|
|
38342
38339
|
if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
|
|
38343
38340
|
const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
|
|
38344
38341
|
return listSourceFiles(rootDirectory).filter((filePath) => {
|
|
38345
|
-
if (!
|
|
38342
|
+
if (!isLintableSourceFile(filePath)) return false;
|
|
38346
38343
|
return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
|
|
38347
38344
|
});
|
|
38348
38345
|
};
|
|
@@ -38560,6 +38557,7 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
38560
38557
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
38561
38558
|
};
|
|
38562
38559
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
38560
|
+
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
38563
38561
|
/**
|
|
38564
38562
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
38565
38563
|
* behind a `Context.Service`. The production layer (`layerNode`)
|
|
@@ -38630,6 +38628,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38630
38628
|
args,
|
|
38631
38629
|
directory
|
|
38632
38630
|
});
|
|
38631
|
+
const listUntrackedFilePaths = (directory, includePaths = []) => runGit(directory, [
|
|
38632
|
+
"ls-files",
|
|
38633
|
+
"-z",
|
|
38634
|
+
"--others",
|
|
38635
|
+
"--exclude-standard",
|
|
38636
|
+
...includePaths.length > 0 ? ["--", ...includePaths] : []
|
|
38637
|
+
]).pipe(map$3((result) => result.status === 0 ? splitNullSeparated(result.stdout) : null));
|
|
38638
|
+
const mergeUntracked = (directory, trackedFilePaths, includeUntracked) => includeUntracked ? listUntrackedFilePaths(directory).pipe(map$3((untracked) => untracked === null ? trackedFilePaths : [...new Set([...trackedFilePaths, ...untracked])])) : succeed$2(trackedFilePaths);
|
|
38633
38639
|
const currentBranch = (directory) => runGit(directory, [
|
|
38634
38640
|
"rev-parse",
|
|
38635
38641
|
"--abbrev-ref",
|
|
@@ -38752,7 +38758,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38752
38758
|
githubViewerPermission,
|
|
38753
38759
|
branchExists,
|
|
38754
38760
|
mergeBase,
|
|
38755
|
-
diffSelection: ({ directory, explicitBaseBranch }) => gen(function* () {
|
|
38761
|
+
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
38756
38762
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
38757
38763
|
if (explicitBaseBranch !== void 0) {
|
|
38758
38764
|
const range = parseGitDiffRange(explicitBaseBranch);
|
|
@@ -38781,7 +38787,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38781
38787
|
"HEAD"
|
|
38782
38788
|
]);
|
|
38783
38789
|
if (uncommitted.status !== 0) return null;
|
|
38784
|
-
const files = splitNullSeparated(uncommitted.stdout);
|
|
38790
|
+
const files = yield* mergeUntracked(directory, splitNullSeparated(uncommitted.stdout), includeUntracked);
|
|
38785
38791
|
if (files.length === 0) return null;
|
|
38786
38792
|
return {
|
|
38787
38793
|
currentBranch: resolvedCurrentBranch,
|
|
@@ -38811,7 +38817,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38811
38817
|
currentBranch: resolvedCurrentBranch,
|
|
38812
38818
|
baseBranch,
|
|
38813
38819
|
diffBaseRef: mergeBaseRef,
|
|
38814
|
-
changedFiles: splitNullSeparated(diff.stdout),
|
|
38820
|
+
changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
|
|
38815
38821
|
isCurrentChanges: false
|
|
38816
38822
|
};
|
|
38817
38823
|
}).pipe(withSpan("Git.diffSelection")),
|
|
@@ -38857,7 +38863,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38857
38863
|
stdout: result.stdout
|
|
38858
38864
|
};
|
|
38859
38865
|
}).pipe(withSpan("Git.grep")),
|
|
38860
|
-
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
38866
|
+
changedLineRanges: ({ directory, baseRef, cached, files, includeUntracked = false }) => gen(function* () {
|
|
38861
38867
|
if (files.length === 0) return [];
|
|
38862
38868
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
38863
38869
|
const result = yield* runGit(directory, [
|
|
@@ -38871,7 +38877,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38871
38877
|
...files
|
|
38872
38878
|
]);
|
|
38873
38879
|
if (result.status !== 0) return null;
|
|
38874
|
-
|
|
38880
|
+
const changedLineRanges = parseChangedLineRanges(result.stdout);
|
|
38881
|
+
if (cached || !includeUntracked) return changedLineRanges;
|
|
38882
|
+
const untrackedFilePaths = yield* listUntrackedFilePaths(directory, files);
|
|
38883
|
+
if (untrackedFilePaths === null) return changedLineRanges;
|
|
38884
|
+
return [...changedLineRanges, ...untrackedFilePaths.map((file) => ({
|
|
38885
|
+
file,
|
|
38886
|
+
ranges: [[1, UNTRACKED_FILE_LAST_LINE]]
|
|
38887
|
+
}))];
|
|
38875
38888
|
}).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
|
|
38876
38889
|
});
|
|
38877
38890
|
})).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
|
|
@@ -39103,7 +39116,7 @@ const normalizeConfigForHash = (config) => {
|
|
|
39103
39116
|
if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
|
|
39104
39117
|
return clone;
|
|
39105
39118
|
};
|
|
39106
|
-
const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update("\0").update(input.tsconfigContent ?? "").digest("hex");
|
|
39119
|
+
const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update("\0").update(input.tsconfigContent ?? "").update("\0").update(input.respectInlineDisables ? "respect-inline-disables" : "audit-mode").digest("hex");
|
|
39107
39120
|
/**
|
|
39108
39121
|
* Loads a plugin module via the local require resolver and extracts
|
|
39109
39122
|
* `(name, ruleNames)` from either `module.exports.meta + rules` or
|
|
@@ -39139,7 +39152,7 @@ const resolveReactHooksJsPlugin = (hasReactCompiler, customRulesOnly) => {
|
|
|
39139
39152
|
} catch {
|
|
39140
39153
|
return null;
|
|
39141
39154
|
}
|
|
39142
|
-
const { ruleNames } = readPluginShape(pluginSpecifier, (
|
|
39155
|
+
const { ruleNames } = readPluginShape(pluginSpecifier, (specifier) => bundledRequire$1(specifier));
|
|
39143
39156
|
return {
|
|
39144
39157
|
entry: {
|
|
39145
39158
|
name: "react-hooks-js",
|
|
@@ -39547,6 +39560,23 @@ const resolveTsConfigRelativePath = (rootDirectory) => {
|
|
|
39547
39560
|
for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
|
|
39548
39561
|
return null;
|
|
39549
39562
|
};
|
|
39563
|
+
const DERIVED_STATE_RULE_PRIORITY = new Map([
|
|
39564
|
+
["no-initialize-state", 0],
|
|
39565
|
+
["no-adjust-state-on-prop-change", 1],
|
|
39566
|
+
["no-derived-state", 2],
|
|
39567
|
+
["no-derived-state-effect", 3]
|
|
39568
|
+
]);
|
|
39569
|
+
const doDiagnosticSpansOverlap = (first, second) => {
|
|
39570
|
+
if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
|
|
39571
|
+
if (first.offset !== void 0 && second.offset !== void 0) {
|
|
39572
|
+
const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
|
|
39573
|
+
const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
|
|
39574
|
+
return first.offset < secondEnd && second.offset < firstEnd;
|
|
39575
|
+
}
|
|
39576
|
+
const firstEndLine = first.endLine ?? first.line;
|
|
39577
|
+
const secondEndLine = second.endLine ?? second.line;
|
|
39578
|
+
return first.line <= secondEndLine && second.line <= firstEndLine;
|
|
39579
|
+
};
|
|
39550
39580
|
const dedupeDiagnostics = (diagnostics) => {
|
|
39551
39581
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
39552
39582
|
const uniqueDiagnostics = [];
|
|
@@ -39554,6 +39584,16 @@ const dedupeDiagnostics = (diagnostics) => {
|
|
|
39554
39584
|
const key = `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}\u0000${diagnostic.plugin}\u0000${diagnostic.rule}\u0000${diagnostic.severity}\u0000${diagnostic.message}`;
|
|
39555
39585
|
if (seenKeys.has(key)) continue;
|
|
39556
39586
|
seenKeys.add(key);
|
|
39587
|
+
const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
|
|
39588
|
+
if (priority !== void 0) {
|
|
39589
|
+
const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && candidate.severity === diagnostic.severity && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
|
|
39590
|
+
if (overlappingDiagnosticIndex >= 0) {
|
|
39591
|
+
const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
|
|
39592
|
+
const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
|
|
39593
|
+
if (existingPriority !== void 0 && priority < existingPriority) uniqueDiagnostics[overlappingDiagnosticIndex] = diagnostic;
|
|
39594
|
+
continue;
|
|
39595
|
+
}
|
|
39596
|
+
}
|
|
39557
39597
|
uniqueDiagnostics.push(diagnostic);
|
|
39558
39598
|
}
|
|
39559
39599
|
return uniqueDiagnostics;
|
|
@@ -39596,6 +39636,12 @@ const mapWithConcurrency = async (items, concurrency, task) => {
|
|
|
39596
39636
|
if (errors.length > 0) throw errors[0];
|
|
39597
39637
|
return results;
|
|
39598
39638
|
};
|
|
39639
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
39640
|
+
let lineNumber = 1;
|
|
39641
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
39642
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === 10) lineNumber++;
|
|
39643
|
+
return lineNumber;
|
|
39644
|
+
};
|
|
39599
39645
|
const REANIMATED_SHARED_VALUE_HINT = "If this is a Reanimated shared value, prefer its React Compiler-compatible `.get()` / `.set()` accessors over `.value` — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support";
|
|
39600
39646
|
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
39601
39647
|
const supportsAccessors = (reanimatedVersion) => {
|
|
@@ -40167,13 +40213,6 @@ const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
|
40167
40213
|
visit(sourceFile);
|
|
40168
40214
|
return foundRange;
|
|
40169
40215
|
};
|
|
40170
|
-
const NEWLINE_BYTE = 10;
|
|
40171
|
-
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
40172
|
-
let lineNumber = 1;
|
|
40173
|
-
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
40174
|
-
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
40175
|
-
return lineNumber;
|
|
40176
|
-
};
|
|
40177
40216
|
/**
|
|
40178
40217
|
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
40179
40218
|
* whose enclosing function also carries a `react-hooks-js` (React
|
|
@@ -40378,8 +40417,33 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40378
40417
|
}
|
|
40379
40418
|
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
40380
40419
|
const minifiedFileCache = /* @__PURE__ */ new Map();
|
|
40420
|
+
const sourceBufferCache = /* @__PURE__ */ new Map();
|
|
40421
|
+
const sourceLinesCache = /* @__PURE__ */ new Map();
|
|
40422
|
+
const resolveAbsolutePath = (filename) => Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
|
|
40423
|
+
const readSourceBuffer = (filename) => {
|
|
40424
|
+
const absolutePath = resolveAbsolutePath(filename);
|
|
40425
|
+
const cached = sourceBufferCache.get(absolutePath);
|
|
40426
|
+
if (cached !== void 0) return cached;
|
|
40427
|
+
let sourceBuffer;
|
|
40428
|
+
try {
|
|
40429
|
+
sourceBuffer = NFS.readFileSync(absolutePath);
|
|
40430
|
+
} catch {
|
|
40431
|
+
sourceBuffer = null;
|
|
40432
|
+
}
|
|
40433
|
+
sourceBufferCache.set(absolutePath, sourceBuffer);
|
|
40434
|
+
return sourceBuffer;
|
|
40435
|
+
};
|
|
40436
|
+
const readSourceLines = (filename) => {
|
|
40437
|
+
const absolutePath = resolveAbsolutePath(filename);
|
|
40438
|
+
const cached = sourceLinesCache.get(absolutePath);
|
|
40439
|
+
if (cached !== void 0) return cached;
|
|
40440
|
+
const sourceBuffer = readSourceBuffer(filename);
|
|
40441
|
+
const sourceLines = sourceBuffer ? sourceBuffer.toString("utf8").split("\n") : null;
|
|
40442
|
+
sourceLinesCache.set(absolutePath, sourceLines);
|
|
40443
|
+
return sourceLines;
|
|
40444
|
+
};
|
|
40381
40445
|
const isMinifiedDiagnosticFile = (filename) => {
|
|
40382
|
-
const absolutePath =
|
|
40446
|
+
const absolutePath = resolveAbsolutePath(filename);
|
|
40383
40447
|
const cached = minifiedFileCache.get(absolutePath);
|
|
40384
40448
|
if (cached !== void 0) return cached;
|
|
40385
40449
|
const minified = isMinifiedSource(absolutePath);
|
|
@@ -40392,8 +40456,17 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40392
40456
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
40393
40457
|
const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
|
|
40394
40458
|
const primarySpan = primaryLabel?.span;
|
|
40459
|
+
const sourceBuffer = primarySpan ? readSourceBuffer(diagnostic.filename) : null;
|
|
40395
40460
|
const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
|
|
40396
40461
|
const category = resolveDiagnosticCategory(plugin, rule);
|
|
40462
|
+
const matchByOccurrence = resolveMatchByOccurrence(rule, category);
|
|
40463
|
+
const sourceLines = primarySpan ? readSourceLines(diagnostic.filename) : null;
|
|
40464
|
+
const primaryLineIndex = primarySpan ? primarySpan.line - 1 : -1;
|
|
40465
|
+
const primaryLine = sourceLines?.[primaryLineIndex];
|
|
40466
|
+
const primaryColumnIndex = primarySpan ? primarySpan.column - 1 : -1;
|
|
40467
|
+
const isJsxTagLabel = primaryLine !== void 0 && primaryColumnIndex >= 0 && (primaryLine[primaryColumnIndex] === "<" || primaryLine[primaryColumnIndex - 1] === "<");
|
|
40468
|
+
const jsxOpenerEndLineIndex = sourceLines && isJsxTagLabel ? findJsxOpenerSpan(sourceLines, primaryLineIndex) : null;
|
|
40469
|
+
const primarySpanEndLine = jsxOpenerEndLineIndex !== null ? jsxOpenerEndLineIndex + 1 : primarySpan && sourceBuffer ? lineOfUtf8Offset(sourceBuffer, primarySpan.offset + Math.max(primarySpan.length - 1, 0)) : void 0;
|
|
40397
40470
|
return {
|
|
40398
40471
|
filePath: normalizedFilePath,
|
|
40399
40472
|
plugin,
|
|
@@ -40407,10 +40480,11 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40407
40480
|
column: primarySpan?.column ?? 0,
|
|
40408
40481
|
...primarySpan ? {
|
|
40409
40482
|
offset: primarySpan.offset,
|
|
40410
|
-
length: primarySpan.length
|
|
40483
|
+
length: primarySpan.length,
|
|
40484
|
+
...primarySpanEndLine !== void 0 ? { endLine: primarySpanEndLine } : {}
|
|
40411
40485
|
} : {},
|
|
40412
40486
|
category,
|
|
40413
|
-
...
|
|
40487
|
+
...matchByOccurrence ? { matchByOccurrence: true } : {},
|
|
40414
40488
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
40415
40489
|
};
|
|
40416
40490
|
}), rootDirectory);
|
|
@@ -40427,6 +40501,11 @@ const buildOxlintChildEnv = (sourceEnv) => {
|
|
|
40427
40501
|
if (!isCompileCacheDisabled && !isCompileCacheAlreadySet) childEnv.NODE_COMPILE_CACHE = Path.join(os.tmpdir(), NODE_COMPILE_CACHE_DIR_NAME);
|
|
40428
40502
|
return childEnv;
|
|
40429
40503
|
};
|
|
40504
|
+
const buildProfiledNodeArguments = (input) => [
|
|
40505
|
+
...input.cpuProfileDirectory ? ["--cpu-prof", `--cpu-prof-dir=${input.cpuProfileDirectory}`] : [],
|
|
40506
|
+
...input.heapProfileDirectory ? ["--heap-prof", `--heap-prof-dir=${input.heapProfileDirectory}`] : [],
|
|
40507
|
+
...input.argumentsList
|
|
40508
|
+
];
|
|
40430
40509
|
const SANITIZED_ENV = buildOxlintChildEnv(process.env);
|
|
40431
40510
|
/**
|
|
40432
40511
|
* Spawn one oxlint subprocess with hard ceilings on wall time and
|
|
@@ -40451,7 +40530,11 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
40451
40530
|
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
|
|
40452
40531
|
return;
|
|
40453
40532
|
}
|
|
40454
|
-
const child = spawn(nodeBinaryPath,
|
|
40533
|
+
const child = spawn(nodeBinaryPath, buildProfiledNodeArguments({
|
|
40534
|
+
argumentsList: args,
|
|
40535
|
+
cpuProfileDirectory: SANITIZED_ENV.REACT_DOCTOR_CPU_PROFILE_DIR,
|
|
40536
|
+
heapProfileDirectory: SANITIZED_ENV.REACT_DOCTOR_HEAP_PROFILE_DIR
|
|
40537
|
+
}), {
|
|
40455
40538
|
cwd: rootDirectory,
|
|
40456
40539
|
env: SANITIZED_ENV,
|
|
40457
40540
|
stdio: [
|
|
@@ -41095,7 +41178,7 @@ const runOxlint = async (options) => {
|
|
|
41095
41178
|
};
|
|
41096
41179
|
}
|
|
41097
41180
|
};
|
|
41098
|
-
if (perFileLintCacheEnabled &&
|
|
41181
|
+
if (perFileLintCacheEnabled && !project.hasReactCompiler && extendsPaths.length === 0 && userPlugins.length === 0) {
|
|
41099
41182
|
const rulesetHash = computeRulesetHash({
|
|
41100
41183
|
config: buildConfig({
|
|
41101
41184
|
extendsPaths: [],
|
|
@@ -41103,7 +41186,8 @@ const runOxlint = async (options) => {
|
|
|
41103
41186
|
}),
|
|
41104
41187
|
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
41105
41188
|
ignorePatterns: combinedPatterns,
|
|
41106
|
-
tsconfigContent
|
|
41189
|
+
tsconfigContent,
|
|
41190
|
+
respectInlineDisables
|
|
41107
41191
|
});
|
|
41108
41192
|
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
41109
41193
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
@@ -41146,7 +41230,8 @@ const runOxlint = async (options) => {
|
|
|
41146
41230
|
}),
|
|
41147
41231
|
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
41148
41232
|
ignorePatterns: combinedPatterns,
|
|
41149
|
-
tsconfigContent
|
|
41233
|
+
tsconfigContent,
|
|
41234
|
+
respectInlineDisables
|
|
41150
41235
|
})) : null;
|
|
41151
41236
|
const probeAnswers = createSidecarProbeAnswerResolver({
|
|
41152
41237
|
rootDirectory,
|
|
@@ -42186,7 +42271,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
42186
42271
|
directory: scanDirectory,
|
|
42187
42272
|
repo
|
|
42188
42273
|
}).pipe(orElseSucceed(() => null)) : succeed$2(null));
|
|
42189
|
-
const lintIncludePaths = (input.
|
|
42274
|
+
const lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
|
|
42190
42275
|
const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
|
|
42191
42276
|
const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
|
|
42192
42277
|
const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
|
|
@@ -42681,7 +42766,7 @@ const runEditorScan = async (input) => {
|
|
|
42681
42766
|
warnings,
|
|
42682
42767
|
isCi: false,
|
|
42683
42768
|
resolveLocalGithubViewerPermission: false,
|
|
42684
|
-
|
|
42769
|
+
skipExplicitIncludePathFilter: true
|
|
42685
42770
|
}).pipe(withSpan("runEditorScan", { attributes: {
|
|
42686
42771
|
"editor.lint": lint,
|
|
42687
42772
|
"editor.runDeadCode": runDeadCode
|
|
@@ -42765,7 +42850,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
42765
42850
|
/** Display name used in client-facing messages and progress titles. */
|
|
42766
42851
|
const SERVER_DISPLAY_NAME = "React Doctor";
|
|
42767
42852
|
/** Server version reported in `serverInfo`; injected at build, `dev` from source. */
|
|
42768
|
-
const SERVER_VERSION = "0.7.
|
|
42853
|
+
const SERVER_VERSION = "0.7.6";
|
|
42769
42854
|
/** `Diagnostic.source` shown next to every published diagnostic. */
|
|
42770
42855
|
const DIAGNOSTIC_SOURCE = "react-doctor";
|
|
42771
42856
|
/**
|
|
@@ -43444,7 +43529,7 @@ const createLintCache = (input) => {
|
|
|
43444
43529
|
let persistTimer = null;
|
|
43445
43530
|
try {
|
|
43446
43531
|
const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
|
|
43447
|
-
if (parsed.version ===
|
|
43532
|
+
if (parsed.version === 2 && parsed.fingerprint === input.fingerprint) for (const [fsPath, entry] of Object.entries(parsed.entries)) entries.set(fsPath, entry);
|
|
43448
43533
|
} catch {}
|
|
43449
43534
|
const persist = () => {
|
|
43450
43535
|
if (persistTimer) {
|
|
@@ -43455,7 +43540,7 @@ const createLintCache = (input) => {
|
|
|
43455
43540
|
dirty = false;
|
|
43456
43541
|
try {
|
|
43457
43542
|
const payload = {
|
|
43458
|
-
version:
|
|
43543
|
+
version: 2,
|
|
43459
43544
|
fingerprint: input.fingerprint,
|
|
43460
43545
|
entries: Object.fromEntries(entries)
|
|
43461
43546
|
};
|
|
@@ -43468,14 +43553,14 @@ const createLintCache = (input) => {
|
|
|
43468
43553
|
}
|
|
43469
43554
|
};
|
|
43470
43555
|
return {
|
|
43471
|
-
lookup: (fsPath,
|
|
43556
|
+
lookup: (fsPath, identity) => {
|
|
43472
43557
|
const entry = entries.get(fsPath);
|
|
43473
|
-
if (entry !== void 0 && entry.
|
|
43558
|
+
if (entry !== void 0 && entry.contentHash === identity.contentHash) return entry.diagnostics;
|
|
43474
43559
|
return null;
|
|
43475
43560
|
},
|
|
43476
|
-
store: (fsPath,
|
|
43561
|
+
store: (fsPath, identity, diagnostics) => {
|
|
43477
43562
|
entries.set(fsPath, {
|
|
43478
|
-
...
|
|
43563
|
+
...identity,
|
|
43479
43564
|
diagnostics
|
|
43480
43565
|
});
|
|
43481
43566
|
dirty = true;
|
|
@@ -43564,16 +43649,9 @@ const resolveDiagnosticFsPath = (rawFilePath, scanDirectory, projectDirectory, o
|
|
|
43564
43649
|
}
|
|
43565
43650
|
return normalizeFsPath(absolute);
|
|
43566
43651
|
};
|
|
43567
|
-
const
|
|
43568
|
-
|
|
43569
|
-
|
|
43570
|
-
return {
|
|
43571
|
-
mtimeMs: stat.mtimeMs,
|
|
43572
|
-
size: stat.size
|
|
43573
|
-
};
|
|
43574
|
-
} catch {
|
|
43575
|
-
return null;
|
|
43576
|
-
}
|
|
43652
|
+
const readFileIdentity = (fsPath) => {
|
|
43653
|
+
const contentHash = hashFileContents(fsPath);
|
|
43654
|
+
return contentHash === null ? null : { contentHash };
|
|
43577
43655
|
};
|
|
43578
43656
|
/**
|
|
43579
43657
|
* Outcome for a per-file request served without running oxlint (every
|
|
@@ -43599,7 +43677,7 @@ const outcomeWithoutScan = (request, byFile, requestedPaths) => ({
|
|
|
43599
43677
|
* overlay tree (unsaved buffers) or disk, groups diagnostics by canonical
|
|
43600
43678
|
* absolute path, and reports stale-detection metadata.
|
|
43601
43679
|
*
|
|
43602
|
-
* A persistent per-file lint cache (keyed by
|
|
43680
|
+
* A persistent per-file lint cache (keyed by content, namespaced by a
|
|
43603
43681
|
* config fingerprint) short-circuits unchanged files so a re-opened editor
|
|
43604
43682
|
* or repeated workspace scan skips the oxlint subprocess for everything it
|
|
43605
43683
|
* hasn't edited. The cache applies only to disk-based, lint-only, per-file
|
|
@@ -43629,15 +43707,15 @@ const createScanRunner = (options) => {
|
|
|
43629
43707
|
const requestedPaths = request.priority === "background" && !request.useOverlay && options.isOpen ? allRequested.filter((fsPath) => !options.isOpen?.(fsPath)) : allRequested;
|
|
43630
43708
|
const cache = cacheEnabled && !isWholeProject && !request.useOverlay && !request.runDeadCode ? getCache(projectDirectory) : null;
|
|
43631
43709
|
const byFile = /* @__PURE__ */ new Map();
|
|
43632
|
-
const
|
|
43710
|
+
const identityByPath = /* @__PURE__ */ new Map();
|
|
43633
43711
|
let filesToScan = requestedPaths;
|
|
43634
43712
|
if (cache) {
|
|
43635
43713
|
const uncached = [];
|
|
43636
43714
|
for (const fsPath of requestedPaths) {
|
|
43637
|
-
const
|
|
43638
|
-
if (
|
|
43639
|
-
|
|
43640
|
-
const hit = cache.lookup(fsPath,
|
|
43715
|
+
const identity = readFileIdentity(fsPath);
|
|
43716
|
+
if (identity) {
|
|
43717
|
+
identityByPath.set(fsPath, identity);
|
|
43718
|
+
const hit = cache.lookup(fsPath, identity);
|
|
43641
43719
|
if (hit !== null) {
|
|
43642
43720
|
if (hit.length > 0) byFile.set(fsPath, hit);
|
|
43643
43721
|
continue;
|
|
@@ -43680,9 +43758,9 @@ const createScanRunner = (options) => {
|
|
|
43680
43758
|
}
|
|
43681
43759
|
if (cache && result.ok && !result.skipped && !result.didLintFail && result.lintPartialFailures.length === 0) {
|
|
43682
43760
|
for (const fsPath of filesToScan) {
|
|
43683
|
-
const
|
|
43684
|
-
if (!
|
|
43685
|
-
cache.store(fsPath,
|
|
43761
|
+
const identity = identityByPath.get(fsPath);
|
|
43762
|
+
if (!identity) continue;
|
|
43763
|
+
cache.store(fsPath, identity, byFile.get(fsPath) ?? []);
|
|
43686
43764
|
}
|
|
43687
43765
|
cache.schedulePersist();
|
|
43688
43766
|
}
|
|
@@ -44816,5 +44894,5 @@ const startLanguageServer = () => {
|
|
|
44816
44894
|
};
|
|
44817
44895
|
//#endregion
|
|
44818
44896
|
export { startLanguageServer };
|
|
44819
|
-
!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]="
|
|
44820
|
-
//# debugId=
|
|
44897
|
+
!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]="288f7240-2ee1-5bcb-a56e-bca2b972818c")}catch(e){}}();
|
|
44898
|
+
//# debugId=288f7240-2ee1-5bcb-a56e-bca2b972818c
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6-dev.0a8d30b",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
67
|
"yaml": "^2.9.0",
|
|
68
|
-
"deslop-js": "0.7.
|
|
69
|
-
"oxlint-plugin-react-doctor": "0.7.
|
|
68
|
+
"deslop-js": "0.7.6",
|
|
69
|
+
"oxlint-plugin-react-doctor": "0.7.6-dev.0a8d30b"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
"@xterm/headless": "^6.0.0",
|
|
75
75
|
"commander": "^14.0.3",
|
|
76
76
|
"ora": "^9.4.0",
|
|
77
|
-
"@react-doctor/api": "0.7.
|
|
78
|
-
"@react-doctor/
|
|
79
|
-
"@react-doctor/
|
|
77
|
+
"@react-doctor/api": "0.7.6",
|
|
78
|
+
"@react-doctor/language-server": "0.7.6",
|
|
79
|
+
"@react-doctor/core": "0.7.6"
|
|
80
80
|
},
|
|
81
81
|
"engines": {
|
|
82
82
|
"node": "^20.19.0 || >=22.13.0"
|