rshiny

How to Debug when the Line Number of Error is not Provided?


I am using shiny and shinydashboard to create a dashboard. The minimum example codes are as below:

library(shiny)
library(shinydashboard)

ui <- dashboardPage(
    dashboardHeader(title = "test"),
    dashboardSidebar(
        sidebarMenu(
            menuItem(text = "Tab One",tabName = "tab1"),
            menuItem(text = "Tab Two",tabName = "tab2"),
            id = "sidebar"), # an extra comma here!
    ),
    dashboardBody()
)


server <- function(input,output){}


shinyApp(ui,server)

When I run this App, there is an error message:

Error in tag("section", list(...)) : argument is missing, with no default

I know I got this error because I have an extra comma at the end of line 10. But the problem is that:

I have a similar error in my app, but the app contains more than 20 different R files sourcing each other and more than 2000 lines of code. It's impossible for me to go over each file and try to spot where I put an extra comma.

My question is:

Is there an easier way to let R print error message with line number and file source? Or is there a better way to debug this kind of errors where no detail is provided?


Ideally, I want the error message to be something similar to this:

Error in source: <folder>/<file.R> 9:10: argument is missing, with no default
9:      menuItem(text = "Tab Two",tabName = "tab2"),
10:     id = "sidebar"), # an extra comma here!
                       ^

Solution

  • Source the main file rather than running it, and after the error happens run

    traceback()
    

    in the console. When I do that with your simple example, I see this:

    > traceback()
    11: tag("section", list(...))
    10: tags$section(id = "sidebarItemExpanded", class = "sidebar", `data-disable` = if (disable) 1 else NULL, 
            list(...))
    9: tag("aside", list(...))
    8: tags$aside(id = "sidebarCollapsed", class = "main-sidebar", `data-collapsed` = dataValueString, 
           custom_css, tags$section(id = "sidebarItemExpanded", class = "sidebar", 
               `data-disable` = if (disable) 1 else NULL, list(...)))
    7: dashboardSidebar(sidebarMenu(menuItem(text = "Tab One", tabName = "tab1"), 
           menuItem(text = "Tab Two", tabName = "tab2"), id = "sidebar"), 
           )
    6: tagAssert(sidebar, type = "aside", class = "main-sidebar")
    5: dashboardPage(dashboardHeader(title = "test"), dashboardSidebar(sidebarMenu(menuItem(text = "Tab One", 
           tabName = "tab1"), menuItem(text = "Tab Two", tabName = "tab2"), 
           id = "sidebar"), ), dashboardBody()) at .active-rstudio-document#4
    4: eval(ei, envir)
    3: eval(ei, envir)
    2: withVisible(eval(ei, envir))
    1: source("~/.active-rstudio-document", echo = TRUE)
    

    Notice that the expression labelled 5: has line number information: .active-rstudio-document#4. That's the file RStudio saved before sourcing, and the #4 part says the issue is on line 4. Line 4 is the big call to dashboardPage. You're not going to get any finer detail than that unless you break up your code into smaller expressions. This will feel really unnatural, and hopefully won't be necessary, but you could write the original as

    library(shiny)
    library(shinydashboard)
    
    header <- dashboardHeader(title = "test")
    sidebar <- dashboardSidebar(
      sidebarMenu(
        menuItem(text = "Tab One",tabName = "tab1"),
        menuItem(text = "Tab Two",tabName = "tab2"),
        id = "sidebar"), # an extra comma here!
    )
    body <- dashboardBody()
    ui <- dashboardPage(
      header,
      sidebar,
      body
    )
    
    server <- function(input,output){}
    
    shinyApp(ui,server)
    

    When I do that, the traceback() includes this line:

    5: dashboardSidebar(sidebarMenu(menuItem(text = "Tab One", tabName = "tab1"), 
           menuItem(text = "Tab Two", tabName = "tab2"), id = "sidebar"), 
           ) at .active-rstudio-document#5
    

    which tells me where the problem came from.

    Edited to add: Another way to see the traceback is to run

    options(error = recover)
    

    before sourcing everything. The run will die with a display like the traceback() display, but formatted differently. It also lets you examine variables in each of the evaluation frames, which probably isn't helpful in this example, but sometimes is very helpful.