mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
fix(go): gate imports by module path (#2984)
This commit is contained in:
parent
5708db87d3
commit
87dc6c4d00
7 changed files with 66 additions and 27 deletions
|
|
@ -164,7 +164,7 @@
|
|||
"files_small": 8000,
|
||||
"files_large": 32000,
|
||||
"path_segments": 14,
|
||||
"probe": "github.com/org/repo0/pkg/util"
|
||||
"probe": "example.com/mod/repo0/pkg/util"
|
||||
},
|
||||
"_measured": {
|
||||
"collide_ms": 7.15,
|
||||
|
|
|
|||
|
|
@ -2016,8 +2016,8 @@ const HEAP_PROBE_TARGET = {
|
|||
// budgeted ones above: a spelling `uniqueTarget` already mints for that language, and
|
||||
// one that MISSES, so the reading is the index and the cascade runs to the
|
||||
// end. Chosen from the miss family that reaches furthest into each cascade:
|
||||
// - `go` takes the GOPATH fallback, one `filesDirectlyInPkgDir` per path
|
||||
// segment, which is the leg that forces `PackageDirIndex`;
|
||||
// - `go` names a missing package inside GO_MODULE, which reaches the
|
||||
// package-directory lookup and forces `PackageDirIndex`;
|
||||
// - `dart` is an external package, so BOTH candidate paths miss and both
|
||||
// walk the basename bucket to completion;
|
||||
// - `kotlin` misses in `suffixByStem`, the map its four-tier cascade builds;
|
||||
|
|
@ -2028,7 +2028,7 @@ const HEAP_PROBE_TARGET = {
|
|||
// `javascript` and `c` arms they are excluded as duplicates OF, so the
|
||||
// bound compares like with like. `vue`'s is bare rather than `@/…`
|
||||
// because the alias branch rewrites to `src/` and would resolve.
|
||||
go: 'github.com/org/repo0/pkg/util',
|
||||
go: 'example.com/mod/repo0/pkg/util',
|
||||
dart: 'package:ext0/src/thing.dart',
|
||||
kotlin: 'com.ghost0.deep.Missing',
|
||||
cobol: 'VENDOR0',
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ import { perFileSet } from '../../import-resolvers/per-file-set.js';
|
|||
* IMPORTS edge fanout AND binding materialization for every exported symbol in
|
||||
* the package.
|
||||
*
|
||||
* Strategy (first match wins):
|
||||
* 1. go.mod-based: strip module prefix, match package directory
|
||||
* 2. Non-go.mod / GOPATH: progressively shorter directory suffixes
|
||||
* Strategy:
|
||||
* 1. With go.mod: resolve only imports owned by that module
|
||||
* 2. Without go.mod / GOPATH: progressively shorter directory suffixes
|
||||
*/
|
||||
export function resolveGoImportTarget(
|
||||
targetRaw: string,
|
||||
|
|
@ -30,11 +30,14 @@ export function resolveGoImportTarget(
|
|||
|
||||
const goModule = resolutionConfig as GoModuleConfig | undefined;
|
||||
|
||||
// 1) go.mod-based: strip module prefix, match directory
|
||||
if (
|
||||
goModule != null &&
|
||||
(targetRaw === goModule.modulePath || targetRaw.startsWith(`${goModule.modulePath}/`))
|
||||
) {
|
||||
// 1) go.mod is authoritative: only this module's exact path or subpackages
|
||||
// can name files in the workspace. Standard-library and third-party
|
||||
// imports must not fall through to the suffix matcher below.
|
||||
if (goModule != null) {
|
||||
const ownedByModule =
|
||||
targetRaw === goModule.modulePath || targetRaw.startsWith(`${goModule.modulePath}/`);
|
||||
if (!ownedByModule) return null;
|
||||
|
||||
const relativePkg =
|
||||
targetRaw === goModule.modulePath ? '' : targetRaw.slice(goModule.modulePath.length + 1); // e.g. "internal/models"
|
||||
const files =
|
||||
|
|
@ -42,6 +45,7 @@ export function resolveGoImportTarget(
|
|||
? findRootPackageFiles(allFilePaths)
|
||||
: findAllFilesInPkgDir(allFilePaths, relativePkg);
|
||||
if (files.length > 0) return files;
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2) Non-go.mod / GOPATH: progressively shorter directory suffixes.
|
||||
|
|
@ -66,10 +70,9 @@ function isGoPackageFile(normalized: string): boolean {
|
|||
* Package index over the file set, memoized on the Set's identity (#2877).
|
||||
*
|
||||
* Every leg above used to walk all of `allFilePaths`, and the GOPATH fallback
|
||||
* walks once per path segment — so a single unresolved import (which is most of
|
||||
* them: stdlib and third-party module paths run the whole cascade to completion
|
||||
* before returning null) cost several full workspace scans, making resolution
|
||||
* O(imports × files).
|
||||
* walks once per path segment — so without go.mod a single unresolved stdlib or
|
||||
* third-party import ran the whole cascade before returning null and cost
|
||||
* several full workspace scans, making resolution O(imports × files).
|
||||
*
|
||||
* The orchestrator hands the same Set to every import in a pass, so the index
|
||||
* is built once per run. `resolveGoImportTarget` must therefore never copy the
|
||||
|
|
|
|||
|
|
@ -60,10 +60,8 @@ describe('Go import resolution — index reuse across imports (#2877)', () => {
|
|||
const resolved: (string | readonly string[] | null)[] = [];
|
||||
|
||||
for (let i = 0; i < 200; i++) {
|
||||
// Three shapes that between them reach every leg: the module-relative
|
||||
// package leg, the root-package leg, and a third-party path that misses
|
||||
// and so runs the whole GOPATH suffix cascade to completion — the case
|
||||
// that used to cost one full workspace scan per path segment.
|
||||
// Three module-mode shapes: the module-relative package leg, the
|
||||
// root-package leg, and a third-party path rejected by the go.mod gate.
|
||||
resolved.push(
|
||||
resolveImportTarget('example.com/mod/internal/models', FROM_FILE, files, GO_MODULE),
|
||||
);
|
||||
|
|
@ -83,6 +81,19 @@ describe('Go import resolution — index reuse across imports (#2877)', () => {
|
|||
expect(resolved[2]).toBeNull();
|
||||
});
|
||||
|
||||
it('rejects foreign and stdlib imports before building the package index', () => {
|
||||
const files = buildWorkspace(300);
|
||||
|
||||
for (let i = 0; i < 200; i++) {
|
||||
expect(
|
||||
resolveImportTarget(`github.com/vendor/dep${i}/sub`, FROM_FILE, files, GO_MODULE),
|
||||
).toBeNull();
|
||||
expect(resolveImportTarget('fmt', FROM_FILE, files, GO_MODULE)).toBeNull();
|
||||
}
|
||||
|
||||
expect(files.scans).toBe(0);
|
||||
});
|
||||
|
||||
it('a distinct file set gets its own index (no stale cross-run reuse)', () => {
|
||||
expectDistinctFileSetsGetOwnIndex({
|
||||
resolveImportTarget,
|
||||
|
|
|
|||
|
|
@ -326,7 +326,6 @@ const CASES: ReadonlyMap<SupportedLanguages, ConformanceCase> = new Map([
|
|||
*/
|
||||
const KNOWN_GAPS: ReadonlyMap<SupportedLanguages, string> = new Map<SupportedLanguages, string>([
|
||||
[SupportedLanguages.Kotlin, '`org.junit.Assert` -> `src/main/kotlin/vendor/Assert.kt`'],
|
||||
[SupportedLanguages.Go, '`github.com/vendor/dep/internal/models` -> `internal/models/user.go`'],
|
||||
[SupportedLanguages.Ruby, '`rails/generators` -> `lib/generators.rb`'],
|
||||
[SupportedLanguages.PHP, '`Vendor\\Ghost\\Missing` -> `lib/Legacy/Missing.php`'],
|
||||
[SupportedLanguages.Dart, '`package:http/http.dart` -> `lib/http.dart`'],
|
||||
|
|
|
|||
|
|
@ -126,6 +126,31 @@ describe('Go import target resolution', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it.each(['github.com/vendor/dep/internal/models', 'fmt', 'example.com/modular/internal/models'])(
|
||||
'rejects imports outside the go.mod module: %s',
|
||||
(targetRaw) => {
|
||||
const result = resolveGoImportTarget(
|
||||
targetRaw,
|
||||
'main.go',
|
||||
new Set(['internal/models/user.go', 'main.go']),
|
||||
{ modulePath: 'example.com/mod' },
|
||||
);
|
||||
|
||||
expect(result).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
it('treats a semantic import-version suffix as part of the module path', () => {
|
||||
const result = resolveGoImportTarget(
|
||||
'example.com/mod/v2/internal/models',
|
||||
'cmd/app/main.go',
|
||||
new Set(['internal/models/user.go']),
|
||||
{ modulePath: 'example.com/mod/v2' },
|
||||
);
|
||||
|
||||
expect(result).toEqual(['internal/models/user.go']);
|
||||
});
|
||||
|
||||
it('rejects single-segment GOPATH suffix that collides with a local dir', () => {
|
||||
// "github.com/other/team/pkg" suffix-stripped would eventually
|
||||
// reach "pkg" which matches the local pkg/ dir — but we require
|
||||
|
|
|
|||
|
|
@ -99,16 +99,17 @@ function legacyResolveGoImportTarget(
|
|||
modulePath: string | undefined,
|
||||
): string | readonly string[] | null {
|
||||
if (!targetRaw) return null;
|
||||
if (
|
||||
modulePath !== undefined &&
|
||||
(targetRaw === modulePath || targetRaw.startsWith(`${modulePath}/`))
|
||||
) {
|
||||
if (modulePath !== undefined) {
|
||||
const ownedByModule = targetRaw === modulePath || targetRaw.startsWith(`${modulePath}/`);
|
||||
if (!ownedByModule) return null;
|
||||
|
||||
const relativePkg = targetRaw === modulePath ? '' : targetRaw.slice(modulePath.length + 1);
|
||||
const files =
|
||||
relativePkg === ''
|
||||
? legacyFindRootPackageFiles(allFilePaths)
|
||||
: legacyFindAllFilesInPkgDir(allFilePaths, relativePkg);
|
||||
if (files.length > 0) return files;
|
||||
return null;
|
||||
}
|
||||
const parts = targetRaw.split('/').filter(Boolean);
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
|
|
@ -717,7 +718,7 @@ describe('import-target index hoist — output parity with the pre-change scans'
|
|||
for (let repo = 0; repo < 40; repo++) {
|
||||
const go = corpus(repo, '.go', 6 + (repo % 25));
|
||||
for (const t of GO_TARGETS) {
|
||||
if (resolveGoImportTarget(t, 'main.go', go, { modulePath: 'example.com/mod' }) !== null) {
|
||||
if (resolveGoImportTarget(t, 'main.go', go) !== null) {
|
||||
hits.go++;
|
||||
}
|
||||
}
|
||||
|
|
@ -782,7 +783,7 @@ describe('import-target index hoist — built once per file set, not once per im
|
|||
it('go builds one index for many imports (#2877)', () => {
|
||||
const files = countingCorpus(1, '.go');
|
||||
for (let i = 0; i < 200; i++) {
|
||||
resolveGoImportTarget(`github.com/org/repo${i}/pkg`, 'main.go', files, {
|
||||
resolveGoImportTarget(`example.com/mod/repo${i}/pkg`, 'main.go', files, {
|
||||
modulePath: 'example.com/mod',
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue