@@ -260,6 +260,313 @@ describe('rustResolver.extract', () => {
260260 } ) ;
261261} ) ;
262262
263+ describe ( 'rustResolver.resolve cargo workspace crates' , ( ) => {
264+ it ( 'resolves crate name from workspace member lib.rs' , ( ) => {
265+ const workspaceCargo = `
266+ [workspace]
267+ members = ["crates/mytool-core", "crates/mytool-fetcher"]
268+ ` ;
269+ const coreCargo = `
270+ [package]
271+ name = "mytool-core"
272+ version = "0.1.0"
273+ ` ;
274+ const libNode : Node = {
275+ id : 'module:crates/mytool-core/src/lib.rs:mytool_core:1' ,
276+ kind : 'module' ,
277+ name : 'mytool_core' ,
278+ qualifiedName : 'crates/mytool-core/src/lib.rs::mytool_core' ,
279+ filePath : 'crates/mytool-core/src/lib.rs' ,
280+ language : 'rust' ,
281+ startLine : 1 ,
282+ endLine : 1 ,
283+ startColumn : 0 ,
284+ endColumn : 0 ,
285+ updatedAt : Date . now ( ) ,
286+ } ;
287+
288+ const context = {
289+ getNodesInFile : ( fp : string ) => ( fp === 'crates/mytool-core/src/lib.rs' ? [ libNode ] : [ ] ) ,
290+ getNodesByName : ( ) => [ ] ,
291+ getNodesByQualifiedName : ( ) => [ ] ,
292+ getNodesByKind : ( ) => [ ] ,
293+ fileExists : ( p : string ) => (
294+ p === 'Cargo.toml' ||
295+ p === 'crates/mytool-core/Cargo.toml' ||
296+ p === 'crates/mytool-core/src/lib.rs'
297+ ) ,
298+ readFile : ( p : string ) => {
299+ if ( p === 'Cargo.toml' ) return workspaceCargo ;
300+ if ( p === 'crates/mytool-core/Cargo.toml' ) return coreCargo ;
301+ return null ;
302+ } ,
303+ getProjectRoot : ( ) => '/test' ,
304+ getAllFiles : ( ) => [
305+ 'Cargo.toml' ,
306+ 'crates/mytool-core/Cargo.toml' ,
307+ 'crates/mytool-core/src/lib.rs' ,
308+ ] ,
309+ getNodesByLowerName : ( ) => [ ] ,
310+ getImportMappings : ( ) => [ ] ,
311+ } ;
312+
313+ const ref = {
314+ fromNodeId : 'fn:crates/mytool-fetcher/src/main.rs:main:1' ,
315+ referenceName : 'mytool_core' ,
316+ referenceKind : 'references' as const ,
317+ line : 1 ,
318+ column : 1 ,
319+ filePath : 'crates/mytool-fetcher/src/main.rs' ,
320+ language : 'rust' as const ,
321+ } ;
322+
323+ const result = rustResolver . resolve ( ref , context ) ;
324+ expect ( result ?. targetNodeId ) . toBe ( libNode . id ) ;
325+ expect ( result ?. resolvedBy ) . toBe ( 'framework' ) ;
326+ // Workspace-manifest hits are unambiguous and must beat name-matcher's
327+ // self-file matches (0.7) so cross-crate `imports` edges materialize.
328+ expect ( result ?. confidence ) . toBeGreaterThanOrEqual ( 0.9 ) ;
329+ } ) ;
330+
331+ it ( 'resolves crate name from workspace member main.rs when lib.rs is absent' , ( ) => {
332+ const workspaceCargo = `
333+ [workspace]
334+ members = [
335+ "crates/mytool-runner",
336+ ]
337+ ` ;
338+ const runnerCargo = `
339+ [package]
340+ name = "mytool-runner"
341+ version = "0.1.0"
342+ ` ;
343+ const mainNode : Node = {
344+ id : 'module:crates/mytool-runner/src/main.rs:mytool_runner:1' ,
345+ kind : 'module' ,
346+ name : 'mytool_runner' ,
347+ qualifiedName : 'crates/mytool-runner/src/main.rs::mytool_runner' ,
348+ filePath : 'crates/mytool-runner/src/main.rs' ,
349+ language : 'rust' ,
350+ startLine : 1 ,
351+ endLine : 1 ,
352+ startColumn : 0 ,
353+ endColumn : 0 ,
354+ updatedAt : Date . now ( ) ,
355+ } ;
356+
357+ const context = {
358+ getNodesInFile : ( fp : string ) => ( fp === 'crates/mytool-runner/src/main.rs' ? [ mainNode ] : [ ] ) ,
359+ getNodesByName : ( ) => [ ] ,
360+ getNodesByQualifiedName : ( ) => [ ] ,
361+ getNodesByKind : ( ) => [ ] ,
362+ fileExists : ( p : string ) => (
363+ p === 'Cargo.toml' ||
364+ p === 'crates/mytool-runner/Cargo.toml' ||
365+ p === 'crates/mytool-runner/src/main.rs'
366+ ) ,
367+ readFile : ( p : string ) => {
368+ if ( p === 'Cargo.toml' ) return workspaceCargo ;
369+ if ( p === 'crates/mytool-runner/Cargo.toml' ) return runnerCargo ;
370+ return null ;
371+ } ,
372+ getProjectRoot : ( ) => '/test' ,
373+ getAllFiles : ( ) => [
374+ 'Cargo.toml' ,
375+ 'crates/mytool-runner/Cargo.toml' ,
376+ 'crates/mytool-runner/src/main.rs' ,
377+ ] ,
378+ getNodesByLowerName : ( ) => [ ] ,
379+ getImportMappings : ( ) => [ ] ,
380+ } ;
381+
382+ const ref = {
383+ fromNodeId : 'fn:crates/mytool-runner/src/main.rs:main:1' ,
384+ referenceName : 'mytool_runner' ,
385+ referenceKind : 'references' as const ,
386+ line : 1 ,
387+ column : 1 ,
388+ filePath : 'crates/mytool-runner/src/main.rs' ,
389+ language : 'rust' as const ,
390+ } ;
391+
392+ const result = rustResolver . resolve ( ref , context ) ;
393+ expect ( result ?. targetNodeId ) . toBe ( mainNode . id ) ;
394+ expect ( result ?. resolvedBy ) . toBe ( 'framework' ) ;
395+ } ) ;
396+
397+ it ( 'resolves crate name when members uses a glob (crates/*)' , ( ) => {
398+ const workspaceCargo = `
399+ [workspace]
400+ members = ["crates/*"]
401+ ` ;
402+ const fooCargo = `
403+ [package]
404+ name = "mytool-foo"
405+ version = "0.1.0"
406+ ` ;
407+ const barCargo = `
408+ [package]
409+ name = "mytool-bar"
410+ version = "0.1.0"
411+ ` ;
412+ const fooLib : Node = {
413+ id : 'module:crates/mytool-foo/src/lib.rs:mytool_foo:1' ,
414+ kind : 'module' ,
415+ name : 'mytool_foo' ,
416+ qualifiedName : 'crates/mytool-foo/src/lib.rs::mytool_foo' ,
417+ filePath : 'crates/mytool-foo/src/lib.rs' ,
418+ language : 'rust' ,
419+ startLine : 1 ,
420+ endLine : 1 ,
421+ startColumn : 0 ,
422+ endColumn : 0 ,
423+ updatedAt : Date . now ( ) ,
424+ } ;
425+ const barLib : Node = {
426+ id : 'module:crates/mytool-bar/src/lib.rs:mytool_bar:1' ,
427+ kind : 'module' ,
428+ name : 'mytool_bar' ,
429+ qualifiedName : 'crates/mytool-bar/src/lib.rs::mytool_bar' ,
430+ filePath : 'crates/mytool-bar/src/lib.rs' ,
431+ language : 'rust' ,
432+ startLine : 1 ,
433+ endLine : 1 ,
434+ startColumn : 0 ,
435+ endColumn : 0 ,
436+ updatedAt : Date . now ( ) ,
437+ } ;
438+
439+ const filesByPath : Record < string , string > = {
440+ 'Cargo.toml' : workspaceCargo ,
441+ 'crates/mytool-foo/Cargo.toml' : fooCargo ,
442+ 'crates/mytool-bar/Cargo.toml' : barCargo ,
443+ } ;
444+ const nodesByFile : Record < string , Node [ ] > = {
445+ 'crates/mytool-foo/src/lib.rs' : [ fooLib ] ,
446+ 'crates/mytool-bar/src/lib.rs' : [ barLib ] ,
447+ } ;
448+ const dirsByPath : Record < string , string [ ] > = {
449+ '.' : [ 'crates' ] ,
450+ crates : [ 'mytool-foo' , 'mytool-bar' ] ,
451+ 'crates/mytool-foo' : [ 'src' ] ,
452+ 'crates/mytool-bar' : [ 'src' ] ,
453+ } ;
454+
455+ const context = {
456+ getNodesInFile : ( fp : string ) => nodesByFile [ fp ] ?? [ ] ,
457+ getNodesByName : ( ) => [ ] ,
458+ getNodesByQualifiedName : ( ) => [ ] ,
459+ getNodesByKind : ( ) => [ ] ,
460+ fileExists : ( p : string ) => (
461+ Object . prototype . hasOwnProperty . call ( filesByPath , p ) ||
462+ Object . prototype . hasOwnProperty . call ( nodesByFile , p )
463+ ) ,
464+ readFile : ( p : string ) => filesByPath [ p ] ?? null ,
465+ getProjectRoot : ( ) => '/test' ,
466+ getAllFiles : ( ) => [
467+ 'Cargo.toml' ,
468+ ...Object . keys ( filesByPath ) . filter ( ( p ) => p !== 'Cargo.toml' ) ,
469+ ...Object . keys ( nodesByFile ) ,
470+ ] ,
471+ getNodesByLowerName : ( ) => [ ] ,
472+ getImportMappings : ( ) => [ ] ,
473+ listDirectories : ( rel : string ) => dirsByPath [ rel ] ?? [ ] ,
474+ } ;
475+
476+ const fooRef = {
477+ fromNodeId : 'fn:crates/mytool-bar/src/lib.rs:other:1' ,
478+ referenceName : 'mytool_foo' ,
479+ referenceKind : 'references' as const ,
480+ line : 1 ,
481+ column : 1 ,
482+ filePath : 'crates/mytool-bar/src/lib.rs' ,
483+ language : 'rust' as const ,
484+ } ;
485+ const barRef = {
486+ fromNodeId : 'fn:crates/mytool-foo/src/lib.rs:other:1' ,
487+ referenceName : 'mytool_bar' ,
488+ referenceKind : 'references' as const ,
489+ line : 1 ,
490+ column : 1 ,
491+ filePath : 'crates/mytool-foo/src/lib.rs' ,
492+ language : 'rust' as const ,
493+ } ;
494+
495+ expect ( rustResolver . resolve ( fooRef , context ) ?. targetNodeId ) . toBe ( fooLib . id ) ;
496+ expect ( rustResolver . resolve ( barRef , context ) ?. targetNodeId ) . toBe ( barLib . id ) ;
497+ } ) ;
498+
499+ it ( 'resolves crate name when members uses a name glob at root (helix-*)' , ( ) => {
500+ const workspaceCargo = `
501+ [workspace]
502+ members = ["helix-*"]
503+ ` ;
504+ const coreCargo = `
505+ [package]
506+ name = "helix-core"
507+ version = "0.1.0"
508+ ` ;
509+ const coreLib : Node = {
510+ id : 'module:helix-core/src/lib.rs:helix_core:1' ,
511+ kind : 'module' ,
512+ name : 'helix_core' ,
513+ qualifiedName : 'helix-core/src/lib.rs::helix_core' ,
514+ filePath : 'helix-core/src/lib.rs' ,
515+ language : 'rust' ,
516+ startLine : 1 ,
517+ endLine : 1 ,
518+ startColumn : 0 ,
519+ endColumn : 0 ,
520+ updatedAt : Date . now ( ) ,
521+ } ;
522+
523+ const filesByPath : Record < string , string > = {
524+ 'Cargo.toml' : workspaceCargo ,
525+ 'helix-core/Cargo.toml' : coreCargo ,
526+ } ;
527+ const nodesByFile : Record < string , Node [ ] > = {
528+ 'helix-core/src/lib.rs' : [ coreLib ] ,
529+ } ;
530+ const dirsByPath : Record < string , string [ ] > = {
531+ '.' : [ 'helix-core' , 'docs' , 'target' ] ,
532+ 'helix-core' : [ 'src' ] ,
533+ } ;
534+
535+ const context = {
536+ getNodesInFile : ( fp : string ) => nodesByFile [ fp ] ?? [ ] ,
537+ getNodesByName : ( ) => [ ] ,
538+ getNodesByQualifiedName : ( ) => [ ] ,
539+ getNodesByKind : ( ) => [ ] ,
540+ fileExists : ( p : string ) => (
541+ Object . prototype . hasOwnProperty . call ( filesByPath , p ) ||
542+ Object . prototype . hasOwnProperty . call ( nodesByFile , p )
543+ ) ,
544+ readFile : ( p : string ) => filesByPath [ p ] ?? null ,
545+ getProjectRoot : ( ) => '/test' ,
546+ getAllFiles : ( ) => [
547+ 'Cargo.toml' ,
548+ ...Object . keys ( filesByPath ) . filter ( ( p ) => p !== 'Cargo.toml' ) ,
549+ ...Object . keys ( nodesByFile ) ,
550+ ] ,
551+ getNodesByLowerName : ( ) => [ ] ,
552+ getImportMappings : ( ) => [ ] ,
553+ listDirectories : ( rel : string ) => dirsByPath [ rel ] ?? [ ] ,
554+ } ;
555+
556+ const ref = {
557+ fromNodeId : 'fn:helix-core/src/lib.rs:other:1' ,
558+ referenceName : 'helix_core' ,
559+ referenceKind : 'references' as const ,
560+ line : 1 ,
561+ column : 1 ,
562+ filePath : 'helix-core/src/lib.rs' ,
563+ language : 'rust' as const ,
564+ } ;
565+
566+ expect ( rustResolver . resolve ( ref , context ) ?. targetNodeId ) . toBe ( coreLib . id ) ;
567+ } ) ;
568+ } ) ;
569+
263570import { aspnetResolver } from '../src/resolution/frameworks/csharp' ;
264571
265572describe ( 'aspnetResolver.extract' , ( ) => {
0 commit comments