gitnexus 1.6.13-rc.38 → 1.6.13-rc.39
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/core/ingestion/languages/dart/import-target.d.ts +4 -5
- package/dist/core/ingestion/languages/dart/import-target.js +19 -17
- package/dist/core/ingestion/languages/dart/index.d.ts +4 -1
- package/dist/core/ingestion/languages/dart/index.js +4 -1
- package/dist/core/ingestion/languages/dart/package-config.d.ts +42 -0
- package/dist/core/ingestion/languages/dart/package-config.js +398 -0
- package/dist/core/ingestion/languages/dart/package-dependencies.d.ts +6 -0
- package/dist/core/ingestion/languages/dart/package-dependencies.js +86 -0
- package/dist/core/ingestion/languages/dart/package-uri.d.ts +7 -0
- package/dist/core/ingestion/languages/dart/package-uri.js +14 -0
- package/dist/core/ingestion/languages/dart/scope-resolver.js +7 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +6 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +6 -0
- package/dist/mcp/local/local-backend.js +8 -3
- package/package.json +1 -1
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `resolveImportTarget` adapter for the Dart `ScopeResolver`. Ports the
|
|
3
|
-
*
|
|
3
|
+
* Dart import logic:
|
|
4
4
|
*
|
|
5
5
|
* - `dart:` SDK imports → `null` (external, no edge)
|
|
6
|
-
* - `package:pkg/path` →
|
|
7
|
-
* against the workspace file set
|
|
6
|
+
* - `package:pkg/path` → declared package's exact `lib/path`
|
|
8
7
|
* - relative `'foo/bar.dart'` → resolved against the importer's dir
|
|
9
8
|
* - `__heritage__:` markers → `null` (synthetic heritage carrier,
|
|
10
9
|
* consumed by `emitDartHeritageEdges`)
|
|
11
10
|
*
|
|
12
|
-
*
|
|
11
|
+
* Package identity comes from the workspace's pubspec resolution config;
|
|
13
12
|
* `targetRaw` arrives already quote-stripped from `interpretDartImport`.
|
|
14
13
|
*/
|
|
15
|
-
export declare function resolveDartImportTarget(targetRaw: string, fromFile: string, allFilePaths: ReadonlySet<string
|
|
14
|
+
export declare function resolveDartImportTarget(targetRaw: string, fromFile: string, allFilePaths: ReadonlySet<string>, resolutionConfig?: unknown): string | readonly string[] | null;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `resolveImportTarget` adapter for the Dart `ScopeResolver`. Ports the
|
|
3
|
-
*
|
|
3
|
+
* Dart import logic:
|
|
4
4
|
*
|
|
5
5
|
* - `dart:` SDK imports → `null` (external, no edge)
|
|
6
|
-
* - `package:pkg/path` →
|
|
7
|
-
* against the workspace file set
|
|
6
|
+
* - `package:pkg/path` → declared package's exact `lib/path`
|
|
8
7
|
* - relative `'foo/bar.dart'` → resolved against the importer's dir
|
|
9
8
|
* - `__heritage__:` markers → `null` (synthetic heritage carrier,
|
|
10
9
|
* consumed by `emitDartHeritageEdges`)
|
|
11
10
|
*
|
|
12
|
-
*
|
|
11
|
+
* Package identity comes from the workspace's pubspec resolution config;
|
|
13
12
|
* `targetRaw` arrives already quote-stripped from `interpretDartImport`.
|
|
14
13
|
*/
|
|
15
14
|
import { perFileSet } from '../../import-resolvers/per-file-set.js';
|
|
16
15
|
import { DART_HERITAGE_PREFIX } from './interpret.js';
|
|
16
|
+
import { DART_PACKAGE_SCHEME, dartPackageImportName } from './package-uri.js';
|
|
17
17
|
const getDartFileIndex = perFileSet((allFilePaths) => {
|
|
18
18
|
const byBasename = new Map();
|
|
19
19
|
for (const fp of allFilePaths) {
|
|
@@ -60,7 +60,7 @@ function resolveRelative(rel, fromFile, allFilePaths) {
|
|
|
60
60
|
// Suffix fallback for absolute/rooted workspace paths.
|
|
61
61
|
return findByPathSuffix(allFilePaths, target);
|
|
62
62
|
}
|
|
63
|
-
export function resolveDartImportTarget(targetRaw, fromFile, allFilePaths) {
|
|
63
|
+
export function resolveDartImportTarget(targetRaw, fromFile, allFilePaths, resolutionConfig) {
|
|
64
64
|
if (targetRaw.startsWith(DART_HERITAGE_PREFIX))
|
|
65
65
|
return null;
|
|
66
66
|
// `targetRaw` already arrives quote-stripped from `interpretDartImport`.
|
|
@@ -69,19 +69,21 @@ export function resolveDartImportTarget(targetRaw, fromFile, allFilePaths) {
|
|
|
69
69
|
// Dart SDK imports never resolve to a repo file.
|
|
70
70
|
if (targetRaw.startsWith('dart:'))
|
|
71
71
|
return null;
|
|
72
|
-
//
|
|
73
|
-
if (targetRaw.startsWith(
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
72
|
+
// A package URI never falls back to another package's same-named file.
|
|
73
|
+
if (targetRaw.startsWith(DART_PACKAGE_SCHEME)) {
|
|
74
|
+
const packageName = dartPackageImportName(targetRaw);
|
|
75
|
+
if (packageName === null)
|
|
76
76
|
return null;
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
const config = resolutionConfig;
|
|
78
|
+
const lib = config?.packages?.get(packageName);
|
|
79
|
+
if (lib === undefined)
|
|
80
|
+
return null;
|
|
81
|
+
const relPath = targetRaw.slice(DART_PACKAGE_SCHEME.length + packageName.length + 1);
|
|
82
|
+
if (/[\\%?#:]/.test(relPath) ||
|
|
83
|
+
relPath.split('/').some((part) => part === '' || part === '.' || part === '..'))
|
|
84
|
+
return null;
|
|
85
|
+
const candidate = `${lib}/${relPath}`;
|
|
86
|
+
return allFilePaths.has(candidate) ? candidate : null;
|
|
85
87
|
}
|
|
86
88
|
// Relative import.
|
|
87
89
|
return resolveRelative(targetRaw, fromFile, allFilePaths);
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
* - `query.ts` — tree-sitter scope query + lazy parser/query
|
|
10
10
|
* - `captures.ts` — `emitDartScopeCaptures` orchestrator
|
|
11
11
|
* - `interpret.ts` — capture-match → `ParsedImport` / `ParsedTypeBinding`
|
|
12
|
-
* - `import-target.ts` —
|
|
12
|
+
* - `import-target.ts` — exact declared-package / relative import targets
|
|
13
|
+
* - `package-uri.ts` — shared `package:name/...` parse
|
|
14
|
+
* - `package-config.ts` — bounded, ignore-aware in-repo pubspec discovery
|
|
15
|
+
* - `package-dependencies.ts` — manifest dependencies for incremental writeback
|
|
13
16
|
* - `receiver-binding.ts` — synthesize `this` / `super` type-bindings
|
|
14
17
|
* - `signature-bindings.ts`— synthesize parameter / return type-bindings
|
|
15
18
|
* - `arity.ts` — Dart arity compatibility (count-primary)
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
* - `query.ts` — tree-sitter scope query + lazy parser/query
|
|
10
10
|
* - `captures.ts` — `emitDartScopeCaptures` orchestrator
|
|
11
11
|
* - `interpret.ts` — capture-match → `ParsedImport` / `ParsedTypeBinding`
|
|
12
|
-
* - `import-target.ts` —
|
|
12
|
+
* - `import-target.ts` — exact declared-package / relative import targets
|
|
13
|
+
* - `package-uri.ts` — shared `package:name/...` parse
|
|
14
|
+
* - `package-config.ts` — bounded, ignore-aware in-repo pubspec discovery
|
|
15
|
+
* - `package-dependencies.ts` — manifest dependencies for incremental writeback
|
|
13
16
|
* - `receiver-binding.ts` — synthesize `this` / `super` type-bindings
|
|
14
17
|
* - `signature-bindings.ts`— synthesize parameter / return type-bindings
|
|
15
18
|
* - `arity.ts` — Dart arity compatibility (count-primary)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Dirent } from 'node:fs';
|
|
2
|
+
export interface DartPackageConfig {
|
|
3
|
+
readonly packages: ReadonlyMap<string, string>;
|
|
4
|
+
readonly manifestsByName: ReadonlyMap<string, readonly string[]>;
|
|
5
|
+
}
|
|
6
|
+
export interface DartPackageConfigOptions {
|
|
7
|
+
/** Test seam. Production calls omit it and use the module directory limit. */
|
|
8
|
+
readonly directoryLimit?: number;
|
|
9
|
+
/** Test seam. Production calls omit it and use the open-directory cap. */
|
|
10
|
+
readonly directoryDepthLimit?: number;
|
|
11
|
+
/** Test seam. Production calls omit it and use the per-directory entry cap. */
|
|
12
|
+
readonly directoryEntryLimit?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Test seam. Production calls omit it. Invoked after the directory inode
|
|
15
|
+
* is listed and before its entries are opened.
|
|
16
|
+
*/
|
|
17
|
+
readonly beforeEntryOpen?: (relativePath: string) => void | Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
/** Linux anchors child opens. macOS verifies them. Every other platform does neither. */
|
|
20
|
+
export declare function pubspecWalkAnchored(): boolean;
|
|
21
|
+
export declare function directoryOpenFlags(): number;
|
|
22
|
+
/**
|
|
23
|
+
* Path that lists the directory inode already open on `fd`.
|
|
24
|
+
* Linux uses `/proc/self/fd/N` and macOS uses `/dev/fd/N`.
|
|
25
|
+
* Other platforms have no such path; callers refuse instead of listing by name.
|
|
26
|
+
*/
|
|
27
|
+
export declare function descriptorDirectoryPath(fd: number): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* One entry of the directory inode open on `fd`.
|
|
30
|
+
* Linux looks up `/proc/self/fd/N/<name>` in that inode. macOS `/dev/fd/N/<name>`
|
|
31
|
+
* does not resolve a child, so this returns null and the walker verifies the
|
|
32
|
+
* pinned parent chain instead. Every other platform returns null and is not walked.
|
|
33
|
+
*/
|
|
34
|
+
export declare function descriptorEntryPath(fd: number, name: string): string | null;
|
|
35
|
+
/**
|
|
36
|
+
* Open `directory` without following a final symlink, then list that inode.
|
|
37
|
+
* A path swapped for a symlink after the parent listing fails this open
|
|
38
|
+
* (`ENOTDIR` / `ELOOP`) instead of being traversed.
|
|
39
|
+
*/
|
|
40
|
+
export declare function readDirectoryNoFollow(directory: string): Promise<Dirent[]>;
|
|
41
|
+
/** Discover only in-repository packages; never follow dependency paths or symlinks. */
|
|
42
|
+
export declare function loadDartPackageConfig(repoPath: string, options?: DartPackageConfigOptions): Promise<DartPackageConfig>;
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { constants, lstat, open, opendir } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { JSON_SCHEMA, load } from 'js-yaml';
|
|
4
|
+
import { createWatchIgnorePredicate } from '../../../../config/ignore-service.js';
|
|
5
|
+
import { logger } from '../../../logger.js';
|
|
6
|
+
import { getMaxFileSizeBytes } from '../../utils/max-file-size.js';
|
|
7
|
+
/** An incomplete walk cannot prove package names are unique. */
|
|
8
|
+
const DART_PUBSPEC_DIRECTORY_LIMIT = 20_000;
|
|
9
|
+
/**
|
|
10
|
+
* Directory descriptors one walk may hold at once. The visit budget is far
|
|
11
|
+
* above a process file-descriptor limit, so a deep chain is refused before
|
|
12
|
+
* the next open instead of failing later with EMFILE.
|
|
13
|
+
*/
|
|
14
|
+
const DART_PUBSPEC_OPEN_DIRECTORY_LIMIT = 64;
|
|
15
|
+
/**
|
|
16
|
+
* Names read from one directory. `readdir` would retain every entry before the
|
|
17
|
+
* visit budget can run, so the walk counts names as it reads and stops there.
|
|
18
|
+
*/
|
|
19
|
+
const DART_PUBSPEC_DIRECTORY_ENTRY_LIMIT = 100_000;
|
|
20
|
+
/**
|
|
21
|
+
* Read at most `maxManifestSize` bytes from a descriptor already opened
|
|
22
|
+
* with `O_NOFOLLOW`. A size check after the read rejects a file that grew
|
|
23
|
+
* past the captured bytes, including past the cap. The caller closes nothing;
|
|
24
|
+
* this function owns `handle`.
|
|
25
|
+
*/
|
|
26
|
+
async function readManifestBounded(handle, maxManifestSize) {
|
|
27
|
+
try {
|
|
28
|
+
const info = await handle.stat();
|
|
29
|
+
if (!info.isFile())
|
|
30
|
+
return { ok: false, reason: 'read-pubspec' };
|
|
31
|
+
if (info.size > maxManifestSize)
|
|
32
|
+
return { ok: false, reason: 'manifest-size' };
|
|
33
|
+
const toRead = Math.min(maxManifestSize + 1, info.size + 1);
|
|
34
|
+
const buffer = Buffer.allocUnsafe(toRead);
|
|
35
|
+
let bytesRead = 0;
|
|
36
|
+
while (bytesRead < toRead) {
|
|
37
|
+
const chunk = await handle.read(buffer, bytesRead, toRead - bytesRead, bytesRead);
|
|
38
|
+
if (chunk.bytesRead === 0)
|
|
39
|
+
break;
|
|
40
|
+
bytesRead += chunk.bytesRead;
|
|
41
|
+
}
|
|
42
|
+
if (bytesRead > maxManifestSize)
|
|
43
|
+
return { ok: false, reason: 'manifest-size' };
|
|
44
|
+
const after = await handle.stat();
|
|
45
|
+
if (!after.isFile())
|
|
46
|
+
return { ok: false, reason: 'read-pubspec' };
|
|
47
|
+
if (after.size > maxManifestSize)
|
|
48
|
+
return { ok: false, reason: 'manifest-size' };
|
|
49
|
+
if (after.size !== bytesRead)
|
|
50
|
+
return { ok: false, reason: 'read-pubspec' };
|
|
51
|
+
return { ok: true, content: buffer.subarray(0, bytesRead).toString('utf8') };
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { ok: false, reason: 'read-pubspec' };
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await handle.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function requireNoFollowFlag() {
|
|
61
|
+
const noFollow = constants.O_NOFOLLOW;
|
|
62
|
+
if (typeof noFollow !== 'number' || noFollow === 0) {
|
|
63
|
+
throw Object.assign(new Error('O_NOFOLLOW is unavailable'), { code: 'ENOTSUP' });
|
|
64
|
+
}
|
|
65
|
+
return noFollow;
|
|
66
|
+
}
|
|
67
|
+
/** Linux anchors child opens. macOS verifies them. Every other platform does neither. */
|
|
68
|
+
export function pubspecWalkAnchored() {
|
|
69
|
+
const noFollow = constants.O_NOFOLLOW;
|
|
70
|
+
return ((process.platform === 'linux' || process.platform === 'darwin') &&
|
|
71
|
+
typeof noFollow === 'number' &&
|
|
72
|
+
noFollow !== 0);
|
|
73
|
+
}
|
|
74
|
+
export function directoryOpenFlags() {
|
|
75
|
+
let flags = constants.O_RDONLY | requireNoFollowFlag();
|
|
76
|
+
if (typeof constants.O_DIRECTORY === 'number')
|
|
77
|
+
flags |= constants.O_DIRECTORY;
|
|
78
|
+
return flags;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Read-only, no-follow, and non-blocking. `O_NONBLOCK` does not change a
|
|
82
|
+
* regular-file read. Without it, a FIFO blocks inside `open` until a writer
|
|
83
|
+
* connects, so the later file-type check never runs.
|
|
84
|
+
*/
|
|
85
|
+
function fileOpenFlags() {
|
|
86
|
+
const nonBlock = constants.O_NONBLOCK;
|
|
87
|
+
if (typeof nonBlock !== 'number' || nonBlock === 0) {
|
|
88
|
+
throw Object.assign(new Error('O_NONBLOCK is unavailable'), { code: 'ENOTSUP' });
|
|
89
|
+
}
|
|
90
|
+
return constants.O_RDONLY | requireNoFollowFlag() | nonBlock;
|
|
91
|
+
}
|
|
92
|
+
function directoryIdentity(stat) {
|
|
93
|
+
return `${stat.dev}:${stat.ino}:${stat.mode}`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Path that lists the directory inode already open on `fd`.
|
|
97
|
+
* Linux uses `/proc/self/fd/N` and macOS uses `/dev/fd/N`.
|
|
98
|
+
* Other platforms have no such path; callers refuse instead of listing by name.
|
|
99
|
+
*/
|
|
100
|
+
export function descriptorDirectoryPath(fd) {
|
|
101
|
+
if (process.platform === 'linux')
|
|
102
|
+
return `/proc/self/fd/${fd}`;
|
|
103
|
+
if (process.platform === 'darwin')
|
|
104
|
+
return `/dev/fd/${fd}`;
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* One entry of the directory inode open on `fd`.
|
|
109
|
+
* Linux looks up `/proc/self/fd/N/<name>` in that inode. macOS `/dev/fd/N/<name>`
|
|
110
|
+
* does not resolve a child, so this returns null and the walker verifies the
|
|
111
|
+
* pinned parent chain instead. Every other platform returns null and is not walked.
|
|
112
|
+
*/
|
|
113
|
+
export function descriptorEntryPath(fd, name) {
|
|
114
|
+
if (process.platform !== 'linux')
|
|
115
|
+
return null;
|
|
116
|
+
if (!isSingleDirectoryEntry(name))
|
|
117
|
+
return null;
|
|
118
|
+
return `/proc/self/fd/${fd}/${name}`;
|
|
119
|
+
}
|
|
120
|
+
function isSingleDirectoryEntry(name) {
|
|
121
|
+
return (name !== '' && name !== '.' && name !== '..' && !name.includes('/') && !name.includes('\0'));
|
|
122
|
+
}
|
|
123
|
+
async function openVerifiedDirectory(directory) {
|
|
124
|
+
const handle = await open(directory, directoryOpenFlags());
|
|
125
|
+
try {
|
|
126
|
+
const info = await handle.stat({ bigint: true });
|
|
127
|
+
if (!info.isDirectory()) {
|
|
128
|
+
throw Object.assign(new Error('not a directory'), { code: 'ENOTDIR' });
|
|
129
|
+
}
|
|
130
|
+
return { handle, identity: directoryIdentity(info) };
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
await handle.close();
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Re-stat each pinned directory and its path. A replaced inode or a symlink fails the walk. */
|
|
138
|
+
async function assertPinnedChain(frames) {
|
|
139
|
+
for (const frame of frames) {
|
|
140
|
+
const pinned = await frame.handle.stat({ bigint: true });
|
|
141
|
+
if (!pinned.isDirectory() || directoryIdentity(pinned) !== frame.identity) {
|
|
142
|
+
throw Object.assign(new Error('parent descriptor changed'), { code: 'ELOOP' });
|
|
143
|
+
}
|
|
144
|
+
let lexical;
|
|
145
|
+
try {
|
|
146
|
+
lexical = await lstat(frame.absolute, { bigint: true });
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
throw Object.assign(new Error('parent path changed'), { code: 'ELOOP' });
|
|
150
|
+
}
|
|
151
|
+
if (lexical.isSymbolicLink() ||
|
|
152
|
+
!lexical.isDirectory() ||
|
|
153
|
+
directoryIdentity(lexical) !== frame.identity) {
|
|
154
|
+
throw Object.assign(new Error('parent path changed'), { code: 'ELOOP' });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* macOS has no descriptor-relative child lookup. Re-check the pinned parents,
|
|
160
|
+
* open the child with O_NOFOLLOW, then re-check the parents. The opened
|
|
161
|
+
* descriptor is the only child metadata consulted.
|
|
162
|
+
*/
|
|
163
|
+
async function openLexicalDirectory(frames, lexicalPath) {
|
|
164
|
+
await assertPinnedChain(frames);
|
|
165
|
+
const opened = await openVerifiedDirectory(lexicalPath);
|
|
166
|
+
try {
|
|
167
|
+
await assertPinnedChain(frames);
|
|
168
|
+
return opened;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
await opened.handle.close();
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function openLexicalFile(frames, lexicalPath) {
|
|
176
|
+
await assertPinnedChain(frames);
|
|
177
|
+
const handle = await open(lexicalPath, fileOpenFlags());
|
|
178
|
+
try {
|
|
179
|
+
const opened = await handle.stat({ bigint: true });
|
|
180
|
+
if (!opened.isFile()) {
|
|
181
|
+
throw Object.assign(new Error('not a file'), { code: 'ELOOP' });
|
|
182
|
+
}
|
|
183
|
+
await assertPinnedChain(frames);
|
|
184
|
+
return handle;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
await handle.close();
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function openChildDirectory(frames, parentFd, name, lexicalPath) {
|
|
192
|
+
const anchored = descriptorEntryPath(parentFd, name);
|
|
193
|
+
if (anchored !== null)
|
|
194
|
+
return openVerifiedDirectory(anchored);
|
|
195
|
+
if (process.platform === 'darwin')
|
|
196
|
+
return openLexicalDirectory(frames, lexicalPath);
|
|
197
|
+
throw Object.assign(new Error('no descriptor anchor'), { code: 'ENOTSUP' });
|
|
198
|
+
}
|
|
199
|
+
async function openChildFile(frames, parentFd, name, lexicalPath) {
|
|
200
|
+
const anchored = descriptorEntryPath(parentFd, name);
|
|
201
|
+
if (anchored !== null)
|
|
202
|
+
return open(anchored, fileOpenFlags());
|
|
203
|
+
if (process.platform === 'darwin')
|
|
204
|
+
return openLexicalFile(frames, lexicalPath);
|
|
205
|
+
throw Object.assign(new Error('no descriptor anchor'), { code: 'ENOTSUP' });
|
|
206
|
+
}
|
|
207
|
+
async function listOpenedDirectory(handle, entryLimit) {
|
|
208
|
+
const listing = descriptorDirectoryPath(handle.fd);
|
|
209
|
+
if (listing === null) {
|
|
210
|
+
throw Object.assign(new Error('no descriptor listing'), { code: 'ENOTSUP' });
|
|
211
|
+
}
|
|
212
|
+
const dir = await opendir(listing);
|
|
213
|
+
const entries = [];
|
|
214
|
+
try {
|
|
215
|
+
let count = 0;
|
|
216
|
+
while (true) {
|
|
217
|
+
const entry = await dir.read();
|
|
218
|
+
if (entry === null)
|
|
219
|
+
break;
|
|
220
|
+
count += 1;
|
|
221
|
+
if (count > entryLimit) {
|
|
222
|
+
throw Object.assign(new Error('directory entry limit'), { code: 'E2BIG' });
|
|
223
|
+
}
|
|
224
|
+
entries.push(entry);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
await dir.close().catch(() => undefined);
|
|
229
|
+
}
|
|
230
|
+
return entries;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Open `directory` without following a final symlink, then list that inode.
|
|
234
|
+
* A path swapped for a symlink after the parent listing fails this open
|
|
235
|
+
* (`ENOTDIR` / `ELOOP`) instead of being traversed.
|
|
236
|
+
*/
|
|
237
|
+
export async function readDirectoryNoFollow(directory) {
|
|
238
|
+
const opened = await openVerifiedDirectory(directory);
|
|
239
|
+
try {
|
|
240
|
+
return await listOpenedDirectory(opened.handle, DART_PUBSPEC_DIRECTORY_ENTRY_LIMIT);
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
await opened.handle.close();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/** Discover only in-repository packages; never follow dependency paths or symlinks. */
|
|
247
|
+
export async function loadDartPackageConfig(repoPath, options) {
|
|
248
|
+
const warn = (reason, relativePath = '.') => {
|
|
249
|
+
logger.warn({ reason, relativePath }, 'Dart pubspec discovery could not read a valid package declaration.');
|
|
250
|
+
};
|
|
251
|
+
const incomplete = (reason, relativePath = '.') => {
|
|
252
|
+
warn(reason, relativePath);
|
|
253
|
+
throw new Error(`Dart pubspec discovery failed (${reason}): ${relativePath}`);
|
|
254
|
+
};
|
|
255
|
+
if (!pubspecWalkAnchored()) {
|
|
256
|
+
warn('nofollow-anchor');
|
|
257
|
+
return { packages: new Map(), manifestsByName: new Map() };
|
|
258
|
+
}
|
|
259
|
+
let isIgnored;
|
|
260
|
+
try {
|
|
261
|
+
isIgnored = await createWatchIgnorePredicate(repoPath);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return incomplete('ignore-rules');
|
|
265
|
+
}
|
|
266
|
+
const packages = new Map();
|
|
267
|
+
const manifestsByName = new Map();
|
|
268
|
+
const maxManifestSize = Math.min(1024 * 1024, getMaxFileSizeBytes());
|
|
269
|
+
const directoryLimit = options?.directoryLimit ?? DART_PUBSPEC_DIRECTORY_LIMIT;
|
|
270
|
+
const directoryDepthLimit = options?.directoryDepthLimit ?? DART_PUBSPEC_OPEN_DIRECTORY_LIMIT;
|
|
271
|
+
const directoryEntryLimit = options?.directoryEntryLimit ?? DART_PUBSPEC_DIRECTORY_ENTRY_LIMIT;
|
|
272
|
+
const ambiguous = new Set();
|
|
273
|
+
const stack = [];
|
|
274
|
+
let visited = 0;
|
|
275
|
+
const closeStack = async () => {
|
|
276
|
+
const frames = stack.splice(0);
|
|
277
|
+
await Promise.all(frames.map((frame) => frame.handle.close().catch(() => undefined)));
|
|
278
|
+
};
|
|
279
|
+
const fillFrame = async (frame) => {
|
|
280
|
+
if (++visited > directoryLimit)
|
|
281
|
+
return incomplete('directory-limit', frame.relative || '.');
|
|
282
|
+
try {
|
|
283
|
+
frame.entries = await listOpenedDirectory(frame.handle, directoryEntryLimit);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
const reason = error.code === 'E2BIG' ? 'directory-entries' : 'read-directory';
|
|
287
|
+
return incomplete(reason, frame.relative || '.');
|
|
288
|
+
}
|
|
289
|
+
if (options?.beforeEntryOpen)
|
|
290
|
+
await options.beforeEntryOpen(frame.relative);
|
|
291
|
+
};
|
|
292
|
+
try {
|
|
293
|
+
let rootOpened;
|
|
294
|
+
try {
|
|
295
|
+
rootOpened = await openVerifiedDirectory(repoPath);
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
return incomplete('read-directory', '.');
|
|
299
|
+
}
|
|
300
|
+
const root = {
|
|
301
|
+
relative: '',
|
|
302
|
+
absolute: repoPath,
|
|
303
|
+
handle: rootOpened.handle,
|
|
304
|
+
identity: rootOpened.identity,
|
|
305
|
+
entries: [],
|
|
306
|
+
next: 0,
|
|
307
|
+
};
|
|
308
|
+
stack.push(root);
|
|
309
|
+
await fillFrame(root);
|
|
310
|
+
while (stack.length > 0) {
|
|
311
|
+
const frame = stack[stack.length - 1];
|
|
312
|
+
if (frame === undefined)
|
|
313
|
+
break;
|
|
314
|
+
if (frame.next >= frame.entries.length) {
|
|
315
|
+
stack.pop();
|
|
316
|
+
await frame.handle.close().catch(() => undefined);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const entry = frame.entries[frame.next];
|
|
320
|
+
frame.next += 1;
|
|
321
|
+
if (entry === undefined || !isSingleDirectoryEntry(entry.name) || entry.isSymbolicLink()) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const childRelative = frame.relative ? `${frame.relative}/${entry.name}` : entry.name;
|
|
325
|
+
const entryPath = path.join(repoPath, childRelative);
|
|
326
|
+
if (entry.isDirectory()) {
|
|
327
|
+
if (entry.name.startsWith('.') || isIgnored(entryPath, true))
|
|
328
|
+
continue;
|
|
329
|
+
if (stack.length >= directoryDepthLimit) {
|
|
330
|
+
return incomplete('directory-depth', childRelative);
|
|
331
|
+
}
|
|
332
|
+
let childOpened;
|
|
333
|
+
try {
|
|
334
|
+
childOpened = await openChildDirectory(stack, frame.handle.fd, entry.name, entryPath);
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
return incomplete('read-directory', childRelative);
|
|
338
|
+
}
|
|
339
|
+
const child = {
|
|
340
|
+
relative: childRelative,
|
|
341
|
+
absolute: entryPath,
|
|
342
|
+
handle: childOpened.handle,
|
|
343
|
+
identity: childOpened.identity,
|
|
344
|
+
entries: [],
|
|
345
|
+
next: 0,
|
|
346
|
+
};
|
|
347
|
+
stack.push(child);
|
|
348
|
+
await fillFrame(child);
|
|
349
|
+
}
|
|
350
|
+
else if (entry.isFile() && entry.name === 'pubspec.yaml' && !isIgnored(entryPath, false)) {
|
|
351
|
+
const manifestPath = frame.relative ? `${frame.relative}/pubspec.yaml` : 'pubspec.yaml';
|
|
352
|
+
let manifestHandle;
|
|
353
|
+
try {
|
|
354
|
+
manifestHandle = await openChildFile(stack, frame.handle.fd, entry.name, entryPath);
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
return incomplete('read-pubspec', manifestPath);
|
|
358
|
+
}
|
|
359
|
+
const read = await readManifestBounded(manifestHandle, maxManifestSize);
|
|
360
|
+
if (read.ok === false)
|
|
361
|
+
return incomplete(read.reason, manifestPath);
|
|
362
|
+
try {
|
|
363
|
+
const manifest = load(read.content, {
|
|
364
|
+
schema: JSON_SCHEMA,
|
|
365
|
+
});
|
|
366
|
+
if (manifest === null || typeof manifest !== 'object' || Array.isArray(manifest))
|
|
367
|
+
continue;
|
|
368
|
+
const name = manifest.name;
|
|
369
|
+
if (typeof name !== 'string' || !/^[a-z_][a-z0-9_]*$/.test(name))
|
|
370
|
+
continue;
|
|
371
|
+
const manifests = manifestsByName.get(name) ?? [];
|
|
372
|
+
manifests.push(manifestPath);
|
|
373
|
+
// Two deterministic witnesses suffice to prove ambiguity. Retaining
|
|
374
|
+
// more would create unbounded duplicate-package dependency fanout.
|
|
375
|
+
manifests.sort();
|
|
376
|
+
if (manifests.length > 2)
|
|
377
|
+
manifests.length = 2;
|
|
378
|
+
manifestsByName.set(name, manifests);
|
|
379
|
+
if (packages.has(name) || ambiguous.has(name)) {
|
|
380
|
+
packages.delete(name);
|
|
381
|
+
ambiguous.add(name);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
packages.set(name, frame.relative ? `${frame.relative}/lib` : 'lib');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
// Invalid YAML cannot declare a package. Other valid packages remain usable.
|
|
389
|
+
warn('invalid-yaml', manifestPath);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
finally {
|
|
395
|
+
await closeStack();
|
|
396
|
+
}
|
|
397
|
+
return { packages, manifestsByName };
|
|
398
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ParsedFile } from '../../../../_shared/index.js';
|
|
2
|
+
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
3
|
+
import { DART_PACKAGE_IDENTITY_REASON } from '../../scope-resolution/graph-bridge/imports-to-edges.js';
|
|
4
|
+
export { DART_PACKAGE_IDENTITY_REASON };
|
|
5
|
+
/** Package identity is an input dependency, including unresolved/ambiguous imports. */
|
|
6
|
+
export declare function emitDartPackageDependencies(graph: KnowledgeGraph, parsedFiles: readonly ParsedFile[], resolutionConfig?: unknown): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { generateId } from '../../../../lib/utils.js';
|
|
2
|
+
import { DART_PACKAGE_IDENTITY_REASON } from '../../scope-resolution/graph-bridge/imports-to-edges.js';
|
|
3
|
+
import { dartPackageImportName } from './package-uri.js';
|
|
4
|
+
export { DART_PACKAGE_IDENTITY_REASON };
|
|
5
|
+
const MAX_DEPENDENCIES = 100_000;
|
|
6
|
+
const MAX_TRAVERSALS = 1_000_000;
|
|
7
|
+
/** Package identity is an input dependency, including unresolved/ambiguous imports. */
|
|
8
|
+
export function emitDartPackageDependencies(graph, parsedFiles, resolutionConfig) {
|
|
9
|
+
const config = resolutionConfig;
|
|
10
|
+
if (!config?.manifestsByName)
|
|
11
|
+
return;
|
|
12
|
+
const consumersByManifest = new Map();
|
|
13
|
+
let dependencyCount = 0;
|
|
14
|
+
const addConsumer = (consumers, sourceId) => {
|
|
15
|
+
if (consumers.has(sourceId))
|
|
16
|
+
return;
|
|
17
|
+
if (++dependencyCount > MAX_DEPENDENCIES) {
|
|
18
|
+
throw new Error('Dart package dependency limit exceeded; narrow the workspace with .gitnexusignore.');
|
|
19
|
+
}
|
|
20
|
+
consumers.add(sourceId);
|
|
21
|
+
};
|
|
22
|
+
const fileIds = new Set(parsedFiles.map((parsed) => generateId('File', parsed.filePath)));
|
|
23
|
+
for (const parsed of parsedFiles) {
|
|
24
|
+
const sourceId = generateId('File', parsed.filePath);
|
|
25
|
+
if (!graph.getNode(sourceId))
|
|
26
|
+
continue;
|
|
27
|
+
for (const imp of parsed.parsedImports) {
|
|
28
|
+
const raw = imp.targetRaw;
|
|
29
|
+
if (typeof raw !== 'string')
|
|
30
|
+
continue;
|
|
31
|
+
const packageName = dartPackageImportName(raw);
|
|
32
|
+
if (packageName === null)
|
|
33
|
+
continue;
|
|
34
|
+
for (const manifest of config.manifestsByName.get(packageName) ?? []) {
|
|
35
|
+
let consumers = consumersByManifest.get(manifest);
|
|
36
|
+
if (!consumers)
|
|
37
|
+
consumersByManifest.set(manifest, (consumers = new Set()));
|
|
38
|
+
addConsumer(consumers, sourceId);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (consumersByManifest.size === 0)
|
|
43
|
+
return;
|
|
44
|
+
// New manifests are not present in the old DB's importer closure. Carry
|
|
45
|
+
// identity dependencies through the freshly resolved imports as well, so
|
|
46
|
+
// one-hop incremental boundary expansion rewrites downstream consumers.
|
|
47
|
+
const importers = new Map();
|
|
48
|
+
for (const edge of graph.iterRelationshipsByType('IMPORTS')) {
|
|
49
|
+
if (!fileIds.has(edge.sourceId) || !fileIds.has(edge.targetId))
|
|
50
|
+
continue;
|
|
51
|
+
let sources = importers.get(edge.targetId);
|
|
52
|
+
if (!sources)
|
|
53
|
+
importers.set(edge.targetId, (sources = new Set()));
|
|
54
|
+
sources.add(edge.sourceId);
|
|
55
|
+
}
|
|
56
|
+
let traversals = 0;
|
|
57
|
+
for (const [manifest, consumers] of consumersByManifest) {
|
|
58
|
+
const targetId = generateId('File', manifest);
|
|
59
|
+
if (!graph.getNode(targetId)) {
|
|
60
|
+
throw new Error(`Dart package manifest is missing from the indexed file set: ${manifest}`);
|
|
61
|
+
}
|
|
62
|
+
// Set iteration visits additions and deduplicates cycles without recursion.
|
|
63
|
+
for (const sourceId of consumers) {
|
|
64
|
+
for (const importer of importers.get(sourceId) ?? []) {
|
|
65
|
+
if (++traversals > MAX_TRAVERSALS) {
|
|
66
|
+
throw new Error('Dart package dependency traversal limit exceeded; narrow the workspace with .gitnexusignore.');
|
|
67
|
+
}
|
|
68
|
+
addConsumer(consumers, importer);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Validate the complete closure before publishing any dependency edges.
|
|
73
|
+
for (const [manifest, consumers] of consumersByManifest) {
|
|
74
|
+
const targetId = generateId('File', manifest);
|
|
75
|
+
for (const sourceId of consumers) {
|
|
76
|
+
graph.addRelationship({
|
|
77
|
+
id: generateId('IMPORTS', `${sourceId}->${targetId}`),
|
|
78
|
+
sourceId,
|
|
79
|
+
targetId,
|
|
80
|
+
type: 'IMPORTS',
|
|
81
|
+
confidence: 1,
|
|
82
|
+
reason: DART_PACKAGE_IDENTITY_REASON,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const DART_PACKAGE_SCHEME = "package:";
|
|
2
|
+
/**
|
|
3
|
+
* Name from `package:name/library.dart`.
|
|
4
|
+
* A bare `package:name` or `package:name/` has no library path, so it is not
|
|
5
|
+
* an import target and it does not create a package-identity dependency.
|
|
6
|
+
*/
|
|
7
|
+
export declare function dartPackageImportName(targetRaw: string): string | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const DART_PACKAGE_SCHEME = 'package:';
|
|
2
|
+
/**
|
|
3
|
+
* Name from `package:name/library.dart`.
|
|
4
|
+
* A bare `package:name` or `package:name/` has no library path, so it is not
|
|
5
|
+
* an import target and it does not create a package-identity dependency.
|
|
6
|
+
*/
|
|
7
|
+
export function dartPackageImportName(targetRaw) {
|
|
8
|
+
if (!targetRaw.startsWith(DART_PACKAGE_SCHEME))
|
|
9
|
+
return null;
|
|
10
|
+
const slash = targetRaw.indexOf('/', DART_PACKAGE_SCHEME.length);
|
|
11
|
+
if (slash <= DART_PACKAGE_SCHEME.length || slash === targetRaw.length - 1)
|
|
12
|
+
return null;
|
|
13
|
+
return targetRaw.slice(DART_PACKAGE_SCHEME.length, slash);
|
|
14
|
+
}
|
|
@@ -36,6 +36,8 @@ import { decodeMarker } from '../../utils/heritage-marker.js';
|
|
|
36
36
|
import { typeApplicationArguments } from '../../utils/template-arguments.js';
|
|
37
37
|
import { expandDartWildcardNames } from './expand-wildcards.js';
|
|
38
38
|
import { dartIsGlobalNameFallbackPlausible } from './name-fallback-visibility.js';
|
|
39
|
+
import { loadDartPackageConfig } from './package-config.js';
|
|
40
|
+
import { emitDartPackageDependencies } from './package-dependencies.js';
|
|
39
41
|
/**
|
|
40
42
|
* Resolve a class simple name to a graph id with FILE AFFINITY: prefer a
|
|
41
43
|
* definition in `preferredFile`, then a workspace-unique match, otherwise
|
|
@@ -179,7 +181,11 @@ export const dartScopeResolver = {
|
|
|
179
181
|
language: SupportedLanguages.Dart,
|
|
180
182
|
languageProvider: dartProvider,
|
|
181
183
|
importEdgeReason: 'dart-scope: import',
|
|
182
|
-
|
|
184
|
+
loadResolutionConfig: loadDartPackageConfig,
|
|
185
|
+
// Package dependencies use cached ParsedFile facts, never raw source text.
|
|
186
|
+
postExtractSourceTextPolicy: 'uncached-files',
|
|
187
|
+
emitPostResolutionEdges: (graph, parsedFiles, _nodeLookup, _indexes, ctx) => emitDartPackageDependencies(graph, parsedFiles, ctx.resolutionConfig),
|
|
188
|
+
resolveImportTarget: (targetRaw, fromFile, allFilePaths, resolutionConfig) => resolveDartImportTarget(targetRaw, fromFile, allFilePaths, resolutionConfig),
|
|
183
189
|
// Dart `import` is whole-library: every public top-level symbol of the
|
|
184
190
|
// target enters scope. Enumerating them lets `propagateImportedReturnTypes`
|
|
185
191
|
// mirror imported functions' return types into the importer.
|
|
@@ -137,4 +137,10 @@ export declare const DEFERRED_IMPORT_REASON_SUFFIX = " (deferred)";
|
|
|
137
137
|
* place the graph records which one this pair is.
|
|
138
138
|
*/
|
|
139
139
|
export declare const TYPE_ONLY_IMPORT_REASON_SUFFIX = " (type-only)";
|
|
140
|
+
/**
|
|
141
|
+
* Dart pubspec identity edges are incremental metadata, not initialization
|
|
142
|
+
* edges. Kept here so the cycle query can name the reason without importing
|
|
143
|
+
* a language provider into MCP startup.
|
|
144
|
+
*/
|
|
145
|
+
export declare const DART_PACKAGE_IDENTITY_REASON = "dart-scope: package identity dependency";
|
|
140
146
|
export declare function emitImportEdges(graph: KnowledgeGraph, imports: ReadonlyMap<ScopeId, readonly ImportEdge[]>, scopeTree: ScopeResolutionIndexes['scopeTree'], reason?: string): number;
|
|
@@ -135,6 +135,12 @@ export const DEFERRED_IMPORT_REASON_SUFFIX = ' (deferred)';
|
|
|
135
135
|
* place the graph records which one this pair is.
|
|
136
136
|
*/
|
|
137
137
|
export const TYPE_ONLY_IMPORT_REASON_SUFFIX = ' (type-only)';
|
|
138
|
+
/**
|
|
139
|
+
* Dart pubspec identity edges are incremental metadata, not initialization
|
|
140
|
+
* edges. Kept here so the cycle query can name the reason without importing
|
|
141
|
+
* a language provider into MCP startup.
|
|
142
|
+
*/
|
|
143
|
+
export const DART_PACKAGE_IDENTITY_REASON = 'dart-scope: package identity dependency';
|
|
138
144
|
/**
|
|
139
145
|
* How much of an import survives to run time. Lower is stronger; a pair takes
|
|
140
146
|
* the minimum over every edge that reaches it. See the precedence section in
|
|
@@ -69,7 +69,7 @@ import { lookupExternalCallCount, lookupUnresolvedCallCount, } from '../../core/
|
|
|
69
69
|
import { scopeExtractionFailureTotal } from '../../core/ingestion/scope-resolution/scope-extraction-failures.js';
|
|
70
70
|
import { lookupCount } from '../../core/ingestion/scope-resolution/summary-maps.js';
|
|
71
71
|
import { VALUE_REF_EDGE_REASON } from '../../core/ingestion/scope-resolution/value-ref-edges.js';
|
|
72
|
-
import { DEFERRED_IMPORT_REASON_SUFFIX, TYPE_ONLY_IMPORT_REASON_SUFFIX, } from '../../core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js';
|
|
72
|
+
import { DART_PACKAGE_IDENTITY_REASON, DEFERRED_IMPORT_REASON_SUFFIX, TYPE_ONLY_IMPORT_REASON_SUFFIX, } from '../../core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js';
|
|
73
73
|
import { fnLineOf, isPdgDegradedLayerStatus, makePdgImpactErrorResult, makePdgLayerDegradedResult, pdgLayerStatus, pdgStampForMode, runImpactPDG, validateImpactMode, pdgBridgeEvidenceForImpact, betterBridgeEvidence, composeUnifiedPdgImpactResult, splitCalleeIds, } from './pdg-impact.js';
|
|
74
74
|
/**
|
|
75
75
|
* Candidate `type`s that label enrichment newly populates (#2687). Before that,
|
|
@@ -2230,20 +2230,25 @@ export class LocalBackend {
|
|
|
2230
2230
|
// and the whole result is REPLACED by an error, so a truncated page never reaches a caller.
|
|
2231
2231
|
const rows = await executeParameterized(repo.lbugPath,
|
|
2232
2232
|
// A cycle here means "these modules cannot be initialized in any order".
|
|
2233
|
-
// Only edges that force initialization count, so
|
|
2233
|
+
// Only edges that force initialization count, so five kinds are excluded:
|
|
2234
2234
|
// Swift implicit module visibility and markdown links (never code
|
|
2235
|
-
// dependencies at all);
|
|
2235
|
+
// dependencies at all); Dart package-identity edges, which point at
|
|
2236
|
+
// pubspec.yaml so a manifest edit invalidates importers and cannot
|
|
2237
|
+
// form an init cycle; imports reachable only through `import()` or a
|
|
2236
2238
|
// function body, which are deferred by construction — deferring is the
|
|
2237
2239
|
// standard idiom for BREAKING an init cycle, so counting it reports the
|
|
2238
2240
|
// fix as the bug; and imports reachable only through TypeScript
|
|
2239
2241
|
// `import type`, which `tsc` erases outright, so no module load exists
|
|
2240
2242
|
// to order. `imports-to-edges.ts` tags the last two with
|
|
2241
2243
|
// DEFERRED_IMPORT_REASON_SUFFIX / TYPE_ONLY_IMPORT_REASON_SUFFIX.
|
|
2244
|
+
// The Dart exclusion has to sit in this filter, before LIMIT, or the
|
|
2245
|
+
// identity edges consume the 100000-row admission cap on a cycle-free graph.
|
|
2242
2246
|
`MATCH (source:File)-[r:CodeRelation]->(target:File)
|
|
2243
2247
|
WHERE r.type = 'IMPORTS'
|
|
2244
2248
|
AND (r.reason IS NULL OR (
|
|
2245
2249
|
r.reason <> 'swift-scope: implicit module visibility'
|
|
2246
2250
|
AND r.reason <> 'markdown-link'
|
|
2251
|
+
AND r.reason <> '${DART_PACKAGE_IDENTITY_REASON}'
|
|
2247
2252
|
AND NOT r.reason ENDS WITH '${DEFERRED_IMPORT_REASON_SUFFIX}'
|
|
2248
2253
|
AND NOT r.reason ENDS WITH '${TYPE_ONLY_IMPORT_REASON_SUFFIX}'
|
|
2249
2254
|
))
|
package/package.json
CHANGED