Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `codegraph_explore` and `codegraph context` now find your project's busiest file, and favor the code around it, even when twenty or more test or generated files are busier still, such as a package of protobuf stubs. Before, the search looked only at the 20 busiest files and then set the tests and generated ones aside, so it could find no file at all, or judge one far ahead of the next when it was not. `getTopRouteFile()` in the library counts every file's routes the same way. Thanks @inth3shadows for the report. (#2578)
- In JavaScript and TypeScript, a call written as the body of an `if`, `while`, `for` or `do` without braces, like `if (e.key === "Escape") setOpen(false);`, no longer links to a same-named property or method in another file when the name is the file's own, such as a `setOpen` from `useState` (#2585).
- `codegraph sync` no longer fails with "FOREIGN KEY constraint failed" in a project whose code lazy-loads a workspace package, like Supabase Studio's `dynamic(() => import('icons').then((mod) => mod.EdgeFunctions))` or vben's `() => import('@vben/layouts').then((m) => m.IFrameView)`. Before, every sync after an edit in such a project rolled back, so the index stopped following your changes until a full re-index. Thanks @bompus for the report and @inth3shadows. (#2565)
- In Dart, a call to a private (`_`-prefixed) member declared in another file of the same library — the library file or one of its `part` files — is linked again. Since 1.6 these calls were dropped, so a library split into `part` files lost most calls between its own files from callers and impact. A private member of another library is still not linked. Thanks @libin1528 for the report. (#2466)
- In VB.NET, every member of a `Structure` is now indexed, including its fields, properties, methods, constructors and nested enums. Before, only the first member was, so the rest could not be found and their callers looked empty.
- In Delphi, a routine with an attribute on one of its parameters, like `const [ref] AValue: Integer` or DelphiMVCFramework's `const [MVCFromBody] Person: TPerson`, is now indexed with its calls, and so is every class member declared after it. Before, the routine was lost or its calls were credited to the file, the parameter showed up as a field of the class, and the members after it were missing. Thanks @stalexteam for the report. (#2545)
- In Delphi, a `for … in` loop that declares its variable inline, like `for var lItem in AList do` or `for var lHandle: THandle in lHandles do`, now has its calls indexed, and a call on a typed loop variable reaches that type's method. Before, the whole loop was skipped, so a routine calling a method only from such a loop was missing from that method's callers. Thanks @stalexteam for the report. (#2546)
Expand Down
80 changes: 80 additions & 0 deletions __tests__/dart-part-directives.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,83 @@ void main() {}
expect(part.dependents).toEqual(['lib/src/framework.dart']);
});
});

/**
* Dart privacy is per library: a `_name` declared in one file of a library is
* visible in its `part` files and the other way round, but not in another
* library that imports it (#2466).
*/
describe('Dart private members across part files', () => {
let root = '';
let cg: CodeGraph;

beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-dart-part-private-'));
writeTree(root, {
'pubspec.yaml': 'name: app\n',
'lib/greeter.dart': `part 'greeter_format.dart';
part 'greeter_shout.dart';
part 'src/_greeter_whisper.dart';

class Greeter with _Formatting, _Shouting, _Whispering {
String greet(String name) => _format(name);
String hello() => _local();
String _local() => 'hi';
}

void _trace(String s) => print(s);
`,
'lib/greeter_format.dart': `part of 'greeter.dart';

mixin _Formatting {
String _format(String name) {
_trace(name);
return 'Hello, $name';
}
}
`,
'lib/greeter_shout.dart': `part of 'greeter.dart';

mixin _Shouting on _Formatting {
String shout(String name) => _format(name).toUpperCase();
}
`,
'lib/src/_greeter_whisper.dart': `part of '../greeter.dart';

mixin _Whispering on _Formatting {
String whisper(String name) => _format(name).toLowerCase();
}
`,
// Another library: the greeter's privates are not its to call.
'lib/caller.dart': `import 'greeter.dart';

void outsider() => _trace('x');
`,
});
cg = await CodeGraph.init(root, { index: true });
});

afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});

function callEdges(): string[] {
const files = ['lib/greeter.dart', 'lib/greeter_format.dart', 'lib/greeter_shout.dart', 'lib/src/_greeter_whisper.dart', 'lib/caller.dart'];
const ids = files.flatMap((f) => cg.getNodesInFile(f).map((n) => n.id));
return cg.getOutgoingEdgesFrom(ids, ['calls'])
.map((e) => `${cg.getNode(e.source)!.qualifiedName} -> ${cg.getNode(e.target)!.qualifiedName}`)
.sort();
}

it('links a call to a private member declared in another file of the same library', () => {
// `outsider` in lib/caller.dart gets no edge: _trace is private to the greeter library.
expect(callEdges()).toEqual([
'Greeter::greet -> _Formatting::_format',
'Greeter::hello -> Greeter::_local',
'_Formatting::_format -> _trace',
'_Shouting::shout -> _Formatting::_format',
'_Whispering::whisper -> _Formatting::_format',
]);
});
});
6 changes: 5 additions & 1 deletion src/resolution/name-matcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2150,7 +2150,9 @@ function luaModuleFile(name: string, decl: Node, context: ResolutionContext): st
/**
* Languages in which `visibility: 'private'` on a definition means no other
* FILE can name it: a Kotlin `private fun` is file- or class-local, and the
* same holds for Java, C#, Swift, Scala, Dart and PHP members.
* same holds for Java, C#, Swift, Scala, Dart and PHP members. A Dart
* `_name` is the exception's exception: it is private to its library, which
* spans that library's `part` files, and is handled before this set is consulted.
*/
const PRIVATE_IS_FILE_LOCAL = new Set<string>(['kotlin', 'java', 'csharp', 'swift', 'scala', 'dart', 'php']);

Expand Down Expand Up @@ -3496,6 +3498,8 @@ export function isVisibleAcrossFiles(candidate: Node, ref: UnresolvedRef, contex
const owner = rustModuleDir(candidate.filePath);
return ref.filePath.startsWith(owner + '/');
}
// Dart privacy is per library: a library's `part` files share its `_names`.
if (lang === 'dart' && candidate.visibility === 'private') return inSameDartLibrary(ref.filePath, candidate.filePath, context);
if (PRIVATE_IS_FILE_LOCAL.has(lang)) return candidate.visibility !== 'private';
// An R test file runs in an environment of its own (testthat): its top-level
// `c <- ggplot(…)` is not what the package's 2,843 `c(…)` calls mean. The
Expand Down