22using System . Linq ;
33using Rubberduck . Inspections . Abstract ;
44using Rubberduck . Inspections . Results ;
5+ using Rubberduck . Parsing ;
56using Rubberduck . Parsing . Grammar ;
67using Rubberduck . Parsing . Inspections . Abstract ;
78using Rubberduck . Resources . Inspections ;
@@ -34,7 +35,8 @@ protected override IEnumerable<IInspectionResult> DoGetInspectionResults()
3435 var unassigned = ( from function in functions
3536 let isUdt = IsReturningUserDefinedType ( function )
3637 let inScopeRefs = function . References . Where ( r => r . ParentScoping . Equals ( function ) )
37- where ( ! isUdt && ( ! inScopeRefs . Any ( r => r . IsAssignment ) ) )
38+ where ( ! isUdt && ( ! inScopeRefs . Any ( r => r . IsAssignment ) &&
39+ ! inScopeRefs . Any ( reference => IsAssignedByRefArgument ( function , reference ) ) ) )
3840 || ( isUdt && ! IsUserDefinedTypeAssigned ( function ) )
3941 select function )
4042 . ToList ( ) ;
@@ -46,6 +48,61 @@ protected override IEnumerable<IInspectionResult> DoGetInspectionResults()
4648 issue ) ) ;
4749 }
4850
51+ private bool IsAssignedByRefArgument ( Declaration enclosingProcedure , IdentifierReference reference )
52+ {
53+ var argExpression = reference . Context . GetAncestor < VBAParser . ArgumentExpressionContext > ( ) ;
54+ if ( argExpression ? . GetDescendent < VBAParser . ParenthesizedExprContext > ( ) != null || argExpression ? . BYVAL ( ) != null )
55+ {
56+ // not an argument, or argument is parenthesized and thus passed ByVal
57+ return false ;
58+ }
59+
60+ var callStmt = argExpression ? . GetAncestor < VBAParser . CallStmtContext > ( ) ;
61+ var procedureName = callStmt ? . GetDescendent < VBAParser . LExpressionContext > ( )
62+ . GetDescendents < VBAParser . IdentifierContext > ( )
63+ . LastOrDefault ( ) ? . GetText ( ) ;
64+ if ( procedureName == null )
65+ {
66+ // if we don't know what we're calling, we can't dig any further
67+ return false ;
68+ }
69+
70+ var procedure = State . DeclarationFinder . MatchName ( procedureName )
71+ . Where ( p => AccessibilityCheck . IsAccessible ( enclosingProcedure , p ) )
72+ . SingleOrDefault ( p => ! p . DeclarationType . HasFlag ( DeclarationType . Property ) || p . DeclarationType . HasFlag ( DeclarationType . PropertyGet ) ) ;
73+ var parameters = State . DeclarationFinder . Parameters ( procedure ) ;
74+
75+ ParameterDeclaration parameter ;
76+ var namedArg = argExpression . GetAncestor < VBAParser . NamedArgumentContext > ( ) ;
77+ if ( namedArg != null )
78+ {
79+ // argument is named: we're lucky
80+ var parameterName = namedArg . unrestrictedIdentifier ( ) . GetText ( ) ;
81+ parameter = parameters . SingleOrDefault ( p => p . IdentifierName == parameterName ) ;
82+ }
83+ else
84+ {
85+ // argument is positional: work out its index
86+ var argList = callStmt . GetDescendent < VBAParser . ArgumentListContext > ( ) ;
87+ var args = argList . GetDescendents < VBAParser . PositionalArgumentContext > ( ) . ToArray ( ) ;
88+ var parameterIndex = args . Select ( ( a , i ) =>
89+ a . GetDescendent < VBAParser . ArgumentExpressionContext > ( ) == argExpression ? ( a , i ) : ( null , - 1 ) )
90+ . SingleOrDefault ( item => item . a != null ) . i ;
91+ parameter = parameters . OrderBy ( p => p . Selection ) . Select ( ( p , i ) => ( p , i ) )
92+ . SingleOrDefault ( item => item . i == parameterIndex ) . p ;
93+ }
94+
95+ if ( parameter == null )
96+ {
97+ // couldn't locate parameter
98+ return false ;
99+ }
100+
101+ // note: not recursive, by design.
102+ return ( parameter . IsImplicitByRef || parameter . IsByRef )
103+ && parameter . References . Any ( r => r . IsAssignment ) ;
104+ }
105+
49106 private bool IsReturningUserDefinedType ( Declaration member )
50107 {
51108 return member . AsTypeDeclaration != null &&
0 commit comments