36  Checking Local Files with repo_check and code_check

36.1 Overview

Metacheck has two modules that work together to check research code:

  • repo_check lists all files in a repository and checks for basic organisational quality: is there a README? Are there archives whose contents cannot be listed? Do the file names follow good practice, and are there files that do not belong in a repository? How many code, data, and other files are there?

  • code_check reads the actual code files and checks them for best-practice issues: comments, absolute file paths, setwd() and install.packages() calls, grouped library loading, missing data files, code that cannot be parsed, and whether the package versions are recorded.

Normally both modules fetch files automatically from online repositories (OSF, GitHub, GitLab, ResearchBox, Zenodo, Dataverse, Figshare, Dryad, and several others — see Repository Check for the full list) by following links found in a paper. This vignette shows how to point them at a local folder instead — useful when:

  • you want to check your own code before sharing it
  • you are reviewing a paper whose files were sent as a zip attachment
  • you downloaded a repository’s files yourself, or the authors used a repository service Metacheck does not support automatically
Tip

If you just want a report on a folder — no manuscript involved — report_repository() wraps the pattern this chapter walks through by hand: give it a path, and it runs repo_check, code_check, data_check, and codebook_check on it and writes a report named after the folder. See Downloading OSF files to check locally below for an example, and Creating a Report for the full function reference.

NoteWithout R

Metacheck’s website checks a local folder too: under Run Metacheck on a Repository, choose the folder or a .zip file, and it runs repo_check, code_check, and data_check, and can also run the R and Python code. The folder is then uploaded to the server, which deletes it once it has been read. To keep files on your own computer, for example files sent to you for review, use the Docker installation, which offers the same website locally.


36.2 Before you start

Load the package and note the path format for your operating system:

Operating system Example path
Windows "C:/Users/researcher/Documents/my_study"
macOS / Linux "/Users/researcher/Documents/my_study"

Both forward slashes / and backslashes \\ work on Windows inside R.

When you provide a local path, the folder is treated as a repository in exactly the same way as an OSF project or GitHub repo. Each folder is a repository of its own, named by its path in the repo_url column of the file table. The summary table has one row per paper, however many folders you check, and the module gives one traffic light for all of them together.


36.3 Part 1: repo_check on local files

36.3.1 What repo_check checks

For each folder you provide, repo_check will:

  • List all files and classify them by type (code, data, archive, readme, …)
  • Check for a README — a README file is good practice and helps others understand the contents of a folder
  • Look inside zip archives — the files inside a .zip file are listed in place of the archive. Other archive formats, such as .7z, .rar or .tar.gz, cannot be listed this way, so repo_check flags them and recommends .zip
  • Check file names — for example names or paths that are too long, or characters that cause problems on some computers
  • Flag files that do not belong in a repository — files that software creates by itself, such as .DS_Store, .Rhistory or editor backups, and version-control or editor folders such as .git/. They are left out of the file list and named in the report
  • Report counts — how many code files, data files, and so on

The traffic light is "red" when the folder holds files that software creates by itself (such as .DS_Store or .Rhistory). It is "green" when every repository has a README, no archive is left that could not be listed, every file could be classified, no file name breaks a naming rule, there are no version-control or editor folders, every repository could be read, and (when there is a manuscript) the studies it names match how the files are grouped. Otherwise it is "yellow".

36.3.2 Checking a single folder

repo <- module_run(test_paper(), "repo_check",
                   local_path = "C:/projects/reaction_time_study_2024")

# Plain-text summary
repo$summary_text

# Summary counts (one row per paper)
repo$summary_table

# "green", "yellow" or "red"
repo$traffic_light

# Full file list — one row per file, with file_type and file_size columns
repo$table

test_paper() creates a minimal paper object with no repository links. The module pipeline always requires a paper argument, but when you are only checking a local folder there is no manuscript — test_paper() gives you a valid object that causes the online repository search to find nothing, so only the local files are checked.

Note on cloud-sync folders (OneDrive, iCloud, Dropbox): If the folder is stored online and files have not been downloaded locally, the check will be slow because each code file triggers a download when it is read. Before running, right-click the folder in your file browser and choose “Always keep on this device” (OneDrive) or “Download Now” (iCloud) to make all files available locally first.

A typical $summary_text when the folder is missing a README might look like:

- We found 8 files in 1 repository.
- We found 0 README files and 1 repository without READMEs.

And $summary_table will have columns: paper_id, repo_n, repo_unchecked, files_n, files_data, files_code, files_readme, files_zip, files_unknown, naming_issues, roster_mismatch.

36.3.3 Checking multiple folders

Pass a vector of paths to check several folders at once. Each is treated as a separate repository. The summary table still has one row, for the one paper, with repo_n counting the folders; the file table names each file’s folder in repo_url:

paths <- c(
  "C:/projects/study_1_power_analysis",
  "C:/projects/study_2_replication",
  "C:/projects/study_3_meta_analysis"
)

repo <- module_run(test_paper(), "repo_check", local_path = paths)

# One row, with repo_n = 3
repo$summary_table

# All files from all three folders, with repo_url identifying the source
repo$table

# Number of files of each type (code, data, readme, ...) per folder
table(repo$table$repo_url, repo$table$file_type)

This is a quick way to compare whether several projects all have READMEs and no archives that cannot be listed before you submit or share them.


36.4 Part 2: code_check on local files

36.4.1 What code_check checks

code_check builds on repo_check: it first runs the file inventory, then reads every code file (R, Rmd, Qmd, Python and Jupyter notebooks, SAS, SPSS, Stata, Mplus, and MATLAB; JASP files are counted but not read) and checks for:

  • Comments — is the code explained? A file with no comment lines at all is flagged.
  • Absolute paths — paths like C:/myname/files/data.csv only work on one specific computer and will break for anyone else trying to reproduce the analysis.
  • setwd() and install.packages() calls — setwd() ties the script to one computer’s folders, and install.packages() installs software on the computer of whoever runs the script, without asking.
  • Library loading — are all library() / require() calls grouped together near the top of the script, or are they scattered throughout?
  • Missing data files — if the script loads read.csv("data.csv") but data.csv is not in the folder, that is flagged.
  • Parsing — can the R code (.R, .Rmd, .qmd) be read without syntax errors?
  • Recorded package versions — is there an renv.lock file, a sessionInfo() record, or a groundhog/checkpoint call for R code, and a package list such as requirements.txt for Python code?

36.4.2 Checking your own code

You have finished a project and want to review the quality of your scripts before sharing:

result <- module_run(test_paper(), "code_check",
                     local_path = "C:/projects/reaction_time_study_2024")

# Short summary of all findings
result$summary_text

# One row per code file with detailed metrics
result$table

# "green" (no issues), "yellow" (some issues), or "na" (no code files read)
result$traffic_light

# Full formatted report
result$report

Note on cloud-sync folders: If your files are on OneDrive, iCloud, or another cloud-sync service and are not fully downloaded, the check will be slow, because every file has to be downloaded when it is read; Metacheck does not warn you about this. Right-click the folder and choose “Always keep on this device” (OneDrive) or “Download Now” (iCloud) before running the check.

36.4.3 Checking multiple projects

paths <- c(
  "C:/projects/study_1_power_analysis",
  "C:/projects/study_2_replication",
  "C:/projects/study_3_meta_analysis"
)

result <- module_run(test_paper(), "code_check", local_path = paths)

result$summary_text

# Filter to one folder using dplyr
library(dplyr)

result$table |>
  filter(grepl("study_2", repo_url)) |>
  select(file_name, percentage_comment, code_abs_path, loaded_files_missing)

The repo_url column contains the folder path, so you can always identify which folder a code file came from. Every code file in a folder is checked.

36.4.4 Peer review — files sent as a zip archive

When reviewing a paper, authors sometimes supply code and data as a zip file attached to the submission. Once you unzip it, you have a local folder:

# After unzipping the submission to a folder:
result <- module_run(test_paper(), "code_check",
                     local_path = "C:/peer_review/garcia_2024_submission")

result$report   # copy this into your review

36.4.5 Code hosted on an unsupported repository

Metacheck currently retrieves files automatically from OSF, GitHub, GitLab, ResearchBox, PsychArchives and other DSpace repositories, Zenodo, Dataverse, Figshare, Dryad, ReShare, 4TU.ResearchData, Mendeley Data, DataONE, and LDbase. Other services, such as Bitbucket or Google Drive, are not supported. If the paper links to one of these, download the files manually and point code_check at the downloaded folder.

The workflow is the same for all of them:

  1. Go to the link in the paper
  2. Download the files (usually a “Download all” button, or clone with git)
  3. Unzip if needed
  4. Run code_check
# Author shared code on Bitbucket — you downloaded or cloned it locally
result <- module_run(test_paper(), "code_check",
                     local_path = "C:/downloads/lee_2023_bitbucket_code")

result$summary_text
result$traffic_light

36.5 Part 3: Combining a paper with local files

Sometimes a paper links to an online repository that Metacheck supports, but there are also supplementary files that were shared separately — for example, the main data is on OSF but additional analysis scripts were sent as a zip by the editor. You can check both at once:

paper <- read("garcia_2024.pdf")

# repo_check fetches the OSF files AND lists the local folder
result <- module_run(paper, "code_check",
                 local_path = "C:/peer_review/garcia_2024_extra_scripts")

# Online files have a URL in repo_url; local files have the folder path
unique(result$table$repo_url)

repo_check treats the online repository and the local folder as two repositories. The summary table counts both in one row for the paper, and the repo_url column of the file table shows which files, README files, and archives are in the online repo and which in the local folder:

repo <- module_run(paper, "repo_check",
                   local_path = "C:/peer_review/garcia_2024_extra_scripts")

# One row for the paper, with repo_n = 2 (the OSF project and the local folder)
repo$summary_table
repo$traffic_light

# Number of files of each type per repository
table(repo$table$repo_url, repo$table$file_type)

36.6 Part 4: Running repo_check and code_check as a two-step pipeline

By default, code_check runs repo_check internally and you do not see its output. If you want to inspect the file inventory before the code analysis, or save the repo_check results separately, run them as two steps. When you pass a repo_check result as the first argument to code_check, the file list is reused — the folder is not re-read:

path <- "C:/projects/reaction_time_study_2024"

# Step 1: inventory and organisational checks
repo <- module_run(test_paper(), "repo_check", local_path = path)

repo$summary_text    # how many files, README present?
repo$traffic_light   # green / yellow
repo$table           # full file list with types and sizes

# Step 2: code quality analysis (reuses the file list from step 1)
code <- module_run(repo, "code_check")

code$summary_text    # code-specific summary
code$table           # one row per code file
code$traffic_light

This is especially useful when the folder is on a cloud-sync drive: any slow file downloads happen once (during code_check), not twice.


36.7 Understanding the output

36.7.1 repo_check output

Element What it contains
$traffic_light "green" (no issues), "yellow" (issues, such as a missing README or an archive that cannot be listed), "red" (files that software creates by itself, such as .DS_Store), or "na" (no repos)
$summary_text Plain-text summary: file counts, README status, archives, file names, files that do not belong
$summary_table One row per paper: paper_id, repo_n, repo_unchecked, files_n, files_data, files_code, files_readme, files_zip, files_unknown, naming_issues, roster_mismatch
$table One row per file, with columns including paper_id, repo_url, file_name, file_path, file_url, file_location, file_size, file_type, and group (the study a file belongs to)
$report Formatted report explaining any README, archive, file-name, or other issues

36.7.2 code_check output

Element What it contains
$traffic_light "green" (no issues), "yellow" (issues found), or "na" (no code files read)
$summary_text Plain-text summary of all checks across all files
$summary_table One row per paper: paper_id, code_n, code_checked, code_abs_path, code_setwd, code_install_packages, code_missing_files, code_min_comments, code_parse_errors, code_packages_n, code_version_pinned
$table One row per code file with detailed metrics (see below)
$report Full formatted report suitable for pasting into a review

Key columns in code_check’s $table:

Column What it means
file_name Script filename
repo_url Which folder (or online repo) the file came from
language Detected language: R, Python, SAS, SPSS, Stata, Mplus, MATLAB, or JASP
percentage_comment Share of non-blank lines that are comments (0 = no comments at all)
loaded_files_missing Number of data files the script tries to load that are not in the folder
loaded_files_missing_names The names of those missing files
code_abs_path Number of absolute file paths detected
library_max_between Largest gap in lines between consecutive library() calls

36.8 Downloading OSF files to check locally

Often the files you want to check live in an OSF project rather than on your own disk. You can download them first, then point repo_check / code_check at the downloaded folder using the local-file options above.

OSF projects let you organise information into nested components, and files within those components. To retrieve all of the files associated with a project by hand, you may need to navigate to several components, download a zip for each, then reorganise and rename the downloaded folders.

The function osf_file_download() does all of this for you, recreating a folder structure based on the component names. By default (mode = "all") it fetches every component’s files as a single compressed archive per component, with no size filtering — max_file_size and max_download_size are both NULL (no limit) by default, and neither one applies in this mode, because there is no file list to filter against. To filter by size instead, pass mode = "select", which lists every file individually through the OSF API before downloading, and only then applies max_file_size/max_download_size (in megabytes). See the Archiving OSF Files appendix for the full comparison between the two modes, and for report_repository(), which can take over from here once the files are on disk.

osf_file_download(osf_id = "pngda",
                  download_to = ".",
                  mode = "select",
                  max_file_size = 1,
                  max_download_size = 10)
Starting retrieval for pngda
- omitting metacheck.png (1.5MB)
Downloading files [=====================] 24/24 00:00:35

The files are written into a folder named after the OSF id, preserving the project’s nested structure:

list.files("pngda", recursive = TRUE)
#>  [1] "Data/Individual/data-01.csv"                         
#>  [2] "Data/Individual/data-02.csv"                         
#>  [3] "Data/Individual/data-03.csv"                         
#>  [4] "Data/Individual/data-04.csv"                         
#>  [5] "Data/Individual/data-05.csv"                         
#>  [6] "Data/Individual/data-06.csv"                         
#>  [7] "Data/Individual/data-07.csv"                         
#>  [8] "Data/Individual/data-08.csv"                         
#>  [9] "Data/Individual/data-09.csv"                         
#> [10] "Data/Individual/data-10.csv"                         
#> [11] "Data/Individual/data-11.csv"                         
#> [12] "Data/Individual/data-12.csv"                         
#> [13] "Data/Individual/data-13.csv"                         
#> [14] "Data/Individual/data-14.csv"                         
#> [15] "Data/Processed Data/processed-data.csv"              
#> [16] "Data/Raw Data/data.xlsx"                             
#> [17] "Data/Raw Data/nest-1/nest-2/nest-3/nest-4/test-4.txt"
#> [18] "Data/Raw Data/nest-1/nest-2/nest-3/test-3.txt"       
#> [19] "Data/Raw Data/nest-1/nest-2/test-2.txt"              
#> [20] "Data/Raw Data/nest-1/README"                         
#> [21] "Data/Raw Data/nest-1/test-1.txt"                     
#> [22] "Data/Raw Data/README"                                
#> [23] "README"

Once downloaded, you can run the checks on that folder exactly as shown earlier:

module_run(test_paper(), "code_check", local_path = "pngda", local_only = TRUE)

Or, since there is no manuscript involved at this point, skip the manual test_paper()/module_run() steps and call report_repository() instead, which runs repo_check, code_check, data_check, and codebook_check on the folder in one step and writes a report named after it:


36.9 Preparing your own folder before you share it: FAIR_repo()

The checks above tell you what could be improved in a folder. FAIR_repo() goes one step further for your own work: it writes a reorganised copy of the folder that you can share. Nothing is downloaded and no link is followed, and the folder you name is not changed. The checks of report_repository() run on a temporary copy of it (repo_check, code_check, data_check, codebook_check, and reproducibility_check without running any code, unless you set execute = TRUE), and the new copy is written to output_dir, which you must name:

FAIR_repo("my_project", output_dir = "my_project_FAIR")

# the demo repository that comes with Metacheck, with its paper for the
# title, authors and funding
FAIR_repo(demofile("repo"),
          output_dir = file.path(tempdir(), "stroop_effect_paper_FAIR"),
          paper = demopaper("stroop_effect"))

In the copy, every file is placed where the Psych-DS layout puts it: data in data/, code in analysis/, materials in materials/, documentation in documentation/, and one study-<group>/ folder per study when there are several. A data file that needs particular software, such as an Excel or SPSS file, is also written as a CSV file. R and Python scripts are changed so that they still find their files in the new places, and a setwd() line that sets a folder written out in the script is replaced by a line that does nothing, with the original line kept in a comment. Next to the files, FAIR_repo() writes a description of the data (dataset_description.json), an RO-Crate metadata file, a README (unless the folder has its own), a requirements.txt with the Python packages the code imports (when it has Python code), the results of the checks in logs/, and CHANGES.md, which lists every change it made. Read CHANGES.md and the README before you share the copy: where only you can give the answer, such as the licence, the authors’ roles, or how the data were collected, the metadata say “unknown”.