11# ' Function Interdependency Reporter
2- # '
2+ # '
33# ' @description
44# ' This reporter looks at the network of interdependencies of its
55# ' defined functions. Measures of centrality from graph theory can indicate
66# ' which function is most important to a package. Combined with unit test
77# ' coverage information---also provided by this reporter--- it can be used
88# ' as a powerful tool to prioritize test writing.
9- # '
9+ # '
1010# ' @details
1111# ' \subsection{R6 Method Support:}{
1212# ' R6 classes are supported, with their methods treated as functions by the
@@ -396,35 +396,37 @@ FunctionReporter <- R6::R6Class(
396396.parse_function <- function (x ) {
397397 # If expression x is not an atomic value or symbol (i.e., name of object) or
398398 # an environment pointer then we can break x up into list of components
399- listable <- ( ! is.atomic( x ) && ! is.symbol( x ) && ! is.environment( x ) )
399+ listable <- .is_listable_expr( x )
400400 if (! is.list(x ) && listable ) {
401- x <- as.list(x )
401+ result <- .try_as_list(x )
402+ x <- result $ value
403+ listable <- result $ listable
402404
403- if (length(x ) > 0 ){
405+ if (listable && length(x ) > 0 ){
404406 # Check for expression of the form foo$bar
405407 # We still want to split it up because foo might be a function
406408 # but we want to get rid of bar, because it's a symbol in foo's namespace
407409 # and not a symbol that could be reliably matched to the package namespace
408410 if (identical(x [[1 ]], quote(`$` ))) {
409411 x <- x [1 : 2 ]
410412 }
411- } else {
413+ } else if ( listable ) {
412414 # make empty lists "not listable" so recursion stops
413- listable <- FALSE
415+ listable <- FALSE
414416 }
415417 }
416418
417419
418420
419421 if (listable ){
420-
422+
421423 # If do.call and first argument is string (atomic), covert to call
422424 if (length(x ) > = 2 ){
423425 if (deparse(x [[1 ]])[1 ] == " do.call" & is.character(x [[2 ]])){
424426 x [[2 ]] <- parse(text = x [[2 ]])
425427 }
426428 }
427-
429+
428430 # Filter out atomic values because we don't care about them
429431 x <- Filter(f = Negate(is.atomic ), x = x )
430432
@@ -439,6 +441,50 @@ FunctionReporter <- R6::R6Class(
439441 return (out )
440442}
441443
444+ # [description] check if expression can be expanded into a list of components
445+ .is_listable_expr <- function (x ) {
446+ # Atomic value
447+ if (is.atomic(x )){return (FALSE )}
448+ # Symbol (i.e., name of object)
449+ if (is.symbol(x )){return (FALSE )}
450+ # Environment
451+ if (is.environment(x )){return (FALSE )}
452+ # Raw external pointer to non-R memory/state (e.g., for C/C++ code)
453+ if (typeof(x ) == " externalptr" ){return (FALSE )}
454+
455+ return (TRUE )
456+ }
457+
458+ # [description] attempt to coerce an expression to a list, returning a list
459+ # containing the result as `value` and a `listable` flag; on
460+ # error, log a warning and return the original object as
461+ # unlistable.
462+ .try_as_list <- function (x ) {
463+ tryCatch(
464+ list (
465+ value = as.list(x ),
466+ listable = TRUE
467+ ),
468+ error = function (e ) {
469+ log_warn(sprintf(
470+ paste0(
471+ " Expression parsing: as.list() failed for " ,
472+ " typeof=%s class=%s; treating as unlistable. " ,
473+ " Please report to pkgnet maintainers in an issue. " ,
474+ " Error: %s"
475+ ),
476+ typeof(x ),
477+ paste(class(x ), collapse = " ," ),
478+ conditionMessage(e )
479+ ))
480+ list (
481+ value = x ,
482+ listable = FALSE
483+ )
484+ }
485+ )
486+ }
487+
442488# [description] given an R6 class, returns a data.table
443489# enumerating all of its public, active binding, and private methods
444490# ' @importFrom assertthat assert_that
@@ -648,13 +694,15 @@ FunctionReporter <- R6::R6Class(
648694
649695 # If expression x is not an atomic value or symbol (i.e., name of object) or
650696 # an environment pointer then we can break x up into list of components
651- listable <- ( ! is.atomic( x ) && ! is.symbol( x ) && ! is.environment( x ) )
697+ listable <- .is_listable_expr( x )
652698
653699 # If it is not a list but listable...
654700 if (! is.list(x ) && listable ) {
655701 # Convert to list
656- xList <- as.list(x )
657- if (length(xList ) > 0 ){
702+ result <- .try_as_list(x )
703+ xList <- result $ value
704+ listable <- result $ listable
705+ if (listable && length(xList ) > 0 ){
658706 # Check if expression x is from _$_
659707 if (identical(xList [[1 ]], quote(`$` ))) {
660708 # Check if expression x is of form self$foo, private$foo, or super$foo
@@ -673,14 +721,14 @@ FunctionReporter <- R6::R6Class(
673721 # Left Hand is not a _$_. Proceed as normal list.
674722 x <- xList
675723 }
676- } else {
724+ } else if ( listable ) {
677725 # List is zero length. This might occur when encountering a "break" command.
678726 # Make empty list "non-listable" so recursion stops in following step.
679727 listable <- FALSE
680- }
728+ }
681729 }
682730
683-
731+
684732
685733 if (listable ){
686734 # Filter out atomic values because we don't care about them
@@ -695,4 +743,3 @@ FunctionReporter <- R6::R6Class(
695743 }
696744 return (out )
697745}
698-
0 commit comments