

























@@ -1,3 +1,4 @@
1+import { spawnSync } from "node:child_process";
12import fs from "node:fs";
23import os from "node:os";
34import path from "node:path";
@@ -741,13 +742,154 @@ function resolveImportSpecifier(importer, specifier, fileSet) {
741742742743let cachedImportGraph = null;
743744let cachedImportGraphCwd = null;
745+let cachedImportGraphFiles = null;
746+let cachedImportGraphFilesCwd = null;
747+748+function isImportableGraphFile(relative) {
749+return IMPORTABLE_FILE_EXTENSIONS.some((ext) => relative.endsWith(ext));
750+}
751+752+function listImportGraphFilesFromGit(cwd) {
753+const result = spawnSync("git", ["ls-files", "--", ...SOURCE_ROOTS_FOR_IMPORT_GRAPH], {
754+ cwd,
755+encoding: "utf8",
756+stdio: ["ignore", "pipe", "pipe"],
757+});
758+if (result.status !== 0) {
759+return null;
760+}
761+return result.stdout
762+.split("\n")
763+.map((line) => normalizePathPattern(line.trim()))
764+.filter((line) => line.length > 0 && isImportableGraphFile(line));
765+}
766+767+function listImportGraphFilesForCwd(cwd) {
768+if (cachedImportGraphFiles && cachedImportGraphFilesCwd === cwd) {
769+return cachedImportGraphFiles;
770+}
771+772+cachedImportGraphFiles =
773+listImportGraphFilesFromGit(cwd) ??
774+SOURCE_ROOTS_FOR_IMPORT_GRAPH.flatMap((root) => listImportGraphFiles(cwd, root));
775+cachedImportGraphFilesCwd = cwd;
776+return cachedImportGraphFiles;
777+}
778+779+function stripImportableGraphExtension(relative) {
780+for (const ext of IMPORTABLE_FILE_EXTENSIONS) {
781+if (relative.endsWith(ext)) {
782+return relative.slice(0, -ext.length);
783+}
784+}
785+return relative;
786+}
787+788+function resolveImportGraphSearchTerm(relative) {
789+const basename = path.posix.basename(stripImportableGraphExtension(relative));
790+if (basename === "index" || basename.length < 3) {
791+return null;
792+}
793+return basename;
794+}
795+796+function listImportGraphGrepMatches(cwd, term) {
797+const result = spawnSync(
798+"git",
799+["grep", "-l", "--fixed-strings", term, "--", ...SOURCE_ROOTS_FOR_IMPORT_GRAPH],
800+{
801+ cwd,
802+encoding: "utf8",
803+stdio: ["ignore", "pipe", "pipe"],
804+},
805+);
806+if (result.status === 1) {
807+return [];
808+}
809+if (result.status !== 0) {
810+return null;
811+}
812+return result.stdout
813+.split("\n")
814+.map((line) => normalizePathPattern(line.trim()))
815+.filter((line) => line.length > 0 && isImportableGraphFile(line));
816+}
817+818+function findDirectImportersWithGitGrep(cwd, importedFile, fileSet) {
819+const term = resolveImportGraphSearchTerm(importedFile);
820+if (!term) {
821+return null;
822+}
823+824+const candidates = listImportGraphGrepMatches(cwd, term);
825+if (!candidates || candidates.length > 800) {
826+return null;
827+}
828+829+const importers = [];
830+for (const file of candidates) {
831+if (file === importedFile || !fileSet.has(file)) {
832+continue;
833+}
834+let source = "";
835+try {
836+source = fs.readFileSync(path.join(cwd, file), "utf8");
837+} catch {
838+continue;
839+}
840+for (const match of source.matchAll(IMPORT_SPECIFIER_PATTERN)) {
841+const imported = resolveImportSpecifier(file, match[1] ?? match[2] ?? "", fileSet);
842+if (imported === importedFile) {
843+importers.push(file);
844+break;
845+}
846+}
847+}
848+return importers;
849+}
850+851+function resolveAffectedTestsFromTargetedImportScan(changedPath, cwd) {
852+const normalized = normalizePathPattern(changedPath);
853+const files = listImportGraphFilesForCwd(cwd);
854+const fileSet = new Set(files);
855+if (!fileSet.has(normalized)) {
856+return [];
857+}
858+859+const testFiles = new Set(
860+files.filter((file) => isTestFileTarget(file) && !file.endsWith(".live.test.ts")),
861+);
862+const queue = [normalized];
863+const seen = new Set(queue);
864+const targets = [];
865+866+for (let index = 0; index < queue.length; index += 1) {
867+const current = queue[index];
868+const importers = findDirectImportersWithGitGrep(cwd, current, fileSet);
869+if (importers === null) {
870+return null;
871+}
872+for (const importer of importers) {
873+if (seen.has(importer)) {
874+continue;
875+}
876+seen.add(importer);
877+if (testFiles.has(importer)) {
878+targets.push(importer);
879+}
880+queue.push(importer);
881+}
882+}
883+884+return [...new Set(targets)].toSorted((left, right) => left.localeCompare(right));
885+}
744886745887function getImportGraph(cwd) {
746888if (cachedImportGraph && cachedImportGraphCwd === cwd) {
747889return cachedImportGraph;
748890}
749891750-const files = SOURCE_ROOTS_FOR_IMPORT_GRAPH.flatMap((root) => listImportGraphFiles(cwd, root));
892+const files = listImportGraphFilesForCwd(cwd);
751893const fileSet = new Set(files);
752894const reverseImports = new Map();
753895const testFiles = new Set(
@@ -779,6 +921,11 @@ function getImportGraph(cwd) {
779921780922function resolveAffectedTestsFromImportGraph(changedPath, cwd) {
781923const normalized = normalizePathPattern(changedPath);
924+const targetedTargets = resolveAffectedTestsFromTargetedImportScan(normalized, cwd);
925+if (targetedTargets !== null) {
926+return targetedTargets;
927+}
928+782929const { reverseImports, testFiles } = getImportGraph(cwd);
783930const queue = [normalized];
784931const seen = new Set(queue);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。