-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathInspect_Code_Notebook.qmd
More file actions
401 lines (278 loc) · 16.3 KB
/
Inspect_Code_Notebook.qmd
File metadata and controls
401 lines (278 loc) · 16.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
---
title: "R/Quarto Code"
author: "Daniel Manrique-Castano"
date: "2025-12-08"
format:
html:
toc: true
toc-location: left
code-fold: true
bibliography: references.bib
params:
target_dir: "."
---
## Overview // Aperçu
This notebook provides a static analysis framework for auditing R code (`.R`) and literate programming documents (`.qmd`, `.Rmd`).
//
Ce cahier de notes fournit un cadre d'analyse statique permettant de vérifier le code R (`.R`) et les documents de programmation littéraire (`.qmd`, `.Rmd`).
::: {.callout-note title="Curation Goal // Objectif de la curation"}
Static analysis refers to the examination of source code without executing it. Our objective is to safely assess code quality, reproducibility, and potential security risks without triggering harmful scripts. // L'analyse statique consiste à examiner le code source sans l'exécuter. Notre objectif est d'évaluer en toute sécurité la qualité du code, sa reproductibilité et les risques potentiels pour la sécurité sans déclencher de scripts nuisibles.
:::
::: {.callout-warning title="Identifying Risks // Identification des risques"}
"Code rot" is a pervasive issue in research. Scripts that run perfectly on a researcher's laptop often fail in other environments due to undocumented dependencies, absolute paths (e.g., `setwd()`), or outdated package versions [@stodden2010]. // La « dégradation du code » est un problème très répandu dans le domaine de la recherche. Les scripts qui fonctionnent parfaitement sur l'ordinateur portable d'un chercheur échouent souvent dans d'autres environnements en raison de dépendances non documentées, de chemins absolus (par exemple, `setwd()`) ou de versions obsolètes des paquets [@stodden2010].
:::
**This notebook evaluates code files on three levels:**
1. **Structural/syntax Validation:** Verify that script files contain syntactically valid R code.
2. **Dependency Mapping:** Extract explicit and implicit package calls to assist in environment reconstruction.
3. **Risk Assessment:** Detect commands that threaten reproducibility or security.
//
**Ce cahier d'exercices évalue les fichiers de code à trois niveaux :**
1. **Validation structurelle/syntaxique :** Vérifier que les fichiers de script contiennent du code R syntaxiquement valide.
2. **Cartographie des dépendances :** Extraire les appels explicites et implicites aux paquets afin de faciliter la reconstitution de l'environnement.
3. **Évaluation des risques :** Détecter les commandes qui compromettent la reproductibilité ou la sécurité.
------------------------------------------------------------------------
## Setup // Configuration
We use **`tidyverse`** for data manipulation and **`rstudioapi`** for interactive directory selection.
//
Nous utilisons **`tidyverse`** pour la manipulation des données et **`rstudioapi`** pour la sélection interactive des répertoires.
### R Packages // Packages R
```{r}
# install.packages(c("DT", "tidyverse", "rstudioapi", "readr", "tools"))
```
### Load libraries // Charger les bibliothèques
```{r}
#| label: load-libraries
#| message: false
library(tidyverse)
library(DT)
library(rstudioapi)
library(readr)
library(tools)
```
## Select a target directory // Sélectionnez un répertoire de destination
This block allows for interactive selection of the image directory. If running in a non-interactive environment, it defaults to the path defined in the YAML header.
//
Ce bloc permet de sélectionner de manière interactive le répertoire contenant les images. En cas d'exécution dans un environnement non interactif, le chemin par défaut est celui défini dans l'en-tête YAML.
```{r}
#| label: select-target-dir
# 1. Try to select interactively if in RStudio
if (interactive() && .Platform$OS.type == "windows") {
selected_dir <- rstudioapi::selectDirectory(caption = "Select Code Directory")
} else {
selected_dir <- NULL
}
# 2. Logic to determine final directory
if (!is.null(selected_dir)) {
target_dir <- selected_dir
} else {
target_dir <- params$target_dir
}
print(paste("Analyzing directory:", target_dir))
```
## Find Code Files // Rechercher des fichier de code
We scan the directory for `.R`, `.qmd`, and `.Rmd` files. Other file extensions can be added by adding the extension inside the parentheses of the pattern line.
//
Nous analysons le répertoire à la recherche de fichiers `.R`, `.qmd` et `.Rmd`. D'autres extensions de fichiers peuvent être ajoutées en les indiquant entre parenthèses dans la ligne de motif.
```{r}
#| label: find-files
code_files <- list.files(
path = target_dir,
pattern = "\\.(R|qmd|Rmd)$",
recursive = TRUE,
full.names = TRUE,
ignore.case = TRUE
)
print(paste("Found", length(code_files), "code files."))
head(code_files)
```
## Analyze Code Function // Fonction d'analyse de code
Static Analysis involves examining the source code without executing it. This is safer for curators than running untrusted scripts. We parse the code to check for syntax errors and use Regular Expressions (Regex) to identify dependencies and risks. This function performs four checks per file:
- Safe Reading: Uses readr::read_lines to handle encoding nuances.
- Syntax Checking: Uses R's `parse()` function to verify structural integrity..
- Comment Stripping: Removes comments to prevent false positives (e.g., commented-out libraries).
- Pattern Matching: Scans for dependencies, risky commands, and secrets.
//
L'analyse statique consiste à examiner le code source sans l'exécuter. Cette méthode est plus sûre pour les modérateurs que l'exécution de scripts non fiables. Nous analysons le code pour détecter les erreurs de syntaxe et utilisons des expressions régulières (Regex) pour identifier les dépendances et les risques. Cette fonction effectue quatre vérifications par fichier :
- Lecture sécurisée : utilise readr::read_lines pour gérer les nuances d'encodage.
- Vérification syntaxique : utilise la fonction `parse()` de R pour vérifier l'intégrité structurelle.
- Suppression des commentaires : supprime les commentaires pour éviter les faux positifs (par exemple, les bibliothèques commentées).
- Correspondance de motifs : recherche les dépendances, les commandes à risque et les secrets.
```{r}
#| label: define-analysis-function
analyze_r_file <- function(file_path) {
fname <- basename(file_path)
# Regex Patterns
patterns <- list(
# Explicit loading: library(pkg), require(pkg), p_load(pkg)
library_call = "(?:library|require|p_load)\\s*\\(\\s*[\"']?([a-zA-Z0-9\\.]+)[\"']?\\s*\\)",
# Implicit loading: package::function
implicit_call = "([a-zA-Z0-9\\.]+)::[a-zA-Z0-9_\\.]+",
# API Tokens (Heuristics for GitHub, Slack, etc.)
tokens = "(?:ghp_|sk-|xoxb-|xoxp-)[a-zA-Z0-9]+"
)
# Risk Patterns (Bryan, 2017)
risk_patterns <- list(
"Hard Setwd" = "setwd\\s*\\(",
"System Call" = "(?:system|shell|system2)\\s*\\(",
"Web Download" = "(?:download\\.file|curl_download)\\s*\\(",
"Source File" = "source\\s*\\("
)
# Absolute Path Pattern (Windows/Unix roots)
abs_path_pattern <- "(?:[a-zA-Z]:\\\\|/Users/|/home/|/scratch/)"
tryCatch({
# 1. Read File Content
raw_lines <- readr::read_lines(file_path, lazy = FALSE)
# 2. Syntax Validation
syntax_status <- "Valid"
tryCatch({
parse(file = file_path, keep.source = FALSE)
}, error = function(e) {
clean_msg <- gsub("[\r\n]+", " ", e$message)
syntax_status <<- paste("Error:", clean_msg)
})
# 3. Strip Comments for Analysis
clean_lines <- gsub("#.*", "", raw_lines)
content_str <- paste(clean_lines, collapse = "\n")
# 4. Extract Dependencies
lib_matches <- str_match_all(content_str, patterns$library_call)[[1]]
explicit_pkgs <- if (length(lib_matches) > 0) lib_matches[, 2] else character(0)
colon_matches <- str_match_all(content_str, patterns$implicit_call)[[1]]
implicit_pkgs <- if (length(colon_matches) > 0) colon_matches[, 2] else character(0)
all_pkgs <- unique(c(explicit_pkgs, implicit_pkgs))
all_pkgs <- setdiff(all_pkgs, "base") # Exclude base R
packages_str <- paste(sort(all_pkgs), collapse = ", ")
# 5. Identify Risks
risks_found <- names(risk_patterns) %>%
map_chr(function(risk_name) {
if (any(str_detect(clean_lines, risk_patterns[[risk_name]]))) return(risk_name) else return(NA)
}) %>%
discard(is.na) %>%
paste(collapse = "; ")
# 6. Count Absolute Paths
num_abs_paths <- sum(str_count(clean_lines, abs_path_pattern))
# 7. Scan for Secrets (on raw lines)
num_tokens <- sum(str_count(raw_lines, patterns$tokens))
tibble(
FileName = fname,
FileType = tools::file_ext(fname),
Syntax_Check = syntax_status,
Packages = substr(packages_str, 1, 150),
AbsPathsFound = num_abs_paths,
Other_Risks = risks_found,
Potential_Secrets = num_tokens,
Status = "Success"
)
}, error = function(e) {
tibble(
FileName = fname,
FileType = tools::file_ext(fname),
Syntax_Check = paste("Read Failed:", e$message),
Packages = "", AbsPathsFound = NA, Other_Risks = "", Potential_Secrets = NA,
Status = "Failed"
)
})
}
```
## Execute Analysis // Exécuter l'analyse
We map the analysis function over the list of files detected in the previous step.
//
Nous appliquons la fonction d'analyse à la liste des fichiers détectés à l'étape précédente.
```{r}
#| label: run-analysis
#| message: false
#| warning: false
if (length(code_files) > 0) {
report <- purrr::map_dfr(code_files, analyze_r_file)
datatable(report,
caption = "Table 1: Code Inspection Report",
options = list(scrollX = TRUE))
} else {
message("No code files found.")
}
```
## Visualization: Dependency Ecosystem // Visualisation : Écosystème des dépendances
Understanding the software environment is critical for long-term preservation. This chart illustrates the most frequently used packages across the analyzed codebase, helping curators prioritize which libraries to document in renv.lock or DESCRIPTION files.
//
Il est essentiel de bien comprendre l'environnement logiciel pour assurer la conservation à long terme. Ce graphique présente les paquets les plus fréquemment utilisés dans l'ensemble du code analysé, ce qui aide les conservateurs à déterminer quelles bibliothèques documenter en priorité dans les fichiers renv.lock ou DESCRIPTION.
```{r}
#| label: viz-dependencies
#| fig-cap: "Top 10 Package Dependencies in Project"
#| warning: false
if (nrow(report) > 0 && any(report$Packages != "")) {
dependency_counts <- report %>%
filter(Packages != "") %>%
separate_rows(Packages, sep = ", ") %>%
count(Packages, sort = TRUE) %>%
head(10)
ggplot(dependency_counts, aes(x = reorder(Packages, n), y = n)) +
geom_col(fill = "#4C78A8") + # Standard formal blue
coord_flip() +
labs(
title = "Most Frequent Package Dependencies",
x = "Package Name",
y = "Frequency (Script Count)"
) +
theme_minimal() +
theme(
panel.grid.major.y = element_blank(),
axis.text = element_text(size = 10)
)
} else {
message("No packages detected to visualize.")
}
```
## Save Results // Enregistrer les résultats
```{r}
#| label: save-results
#| label: save-results
output_dir <- file.path("Results", "Inspect_rCode")
if (!dir.exists(output_dir)) dir.create(output_dir, recursive = TRUE)
output_file <- file.path(output_dir, paste0("Code_Inspection_", Sys.Date(), ".csv"))
write_csv(report, output_file)
message("Report successfully saved to: ", output_file)
```
## Curation Insights // Aperçu de la curation
Use the report to prioritize fixes:
- Reproducibility (Packages): If Packages is empty for a script, verify if it relies on base R only or if the researcher assumes packages are pre-loaded.
- Portability (AbsPathsFound): Any file with AbsPathsFound \> 0 requires attention. Ask the researcher to replace paths like C:/Users/Dan/Project/Data with relative paths like ./Data or use the here package.
- Security (PotentialTokens): If Potential_Secrets \> 0, MANUALLY inspect the file. Do not publish code with active API keys or credentials.
- Syntax Integrity (Syntax_Check): If the column contains an error message (e.g., "unexpected symbol"), the script is broken and will not run. These files should be flagged for immediate correction by the author.
- Environment Reconstruction: The Packages column provides the raw material for building a DESCRIPTION file or renv.lock. Without this list, future users must guess which software versions to install.
//
Utilisez le rapport pour hiérarchiser les corrections :
- Reproductibilité (Packages) : si le champ « Packages » est vide pour un script, vérifiez s'il repose uniquement sur R de base ou si le chercheur part du principe que les paquets sont préchargés.
- Portabilité (AbsPathsFound) : tout fichier pour lequel AbsPathsFound est supérieur à 0 nécessite une attention particulière. Demandez au chercheur de remplacer les chemins d'accès tels que C:/Users/Dan/Project/Data par des chemins relatifs tels que ./Data ou d'utiliser le package here.
- Sécurité (PotentialTokens) : si Potential_Secrets \> 0, inspectez MANUELLEMENT le fichier. Ne publiez pas de code contenant des clés API ou des identifiants actifs.
- Intégrité syntaxique (Syntax_Check) : Si la colonne contient un message d'erreur (par exemple, « symbole inattendu »), le script est corrompu et ne s'exécutera pas. Ces fichiers doivent être signalés pour une correction immédiate par l'auteur.
- Reconstruction de l'environnement : la colonne Packages fournit les données brutes nécessaires à la création d'un fichier DESCRIPTION ou renv.lock. Sans cette liste, les futurs utilisateurs devront deviner quelles versions logicielles installer.
## Additional Tools // Outils supplémentaires
To prevent these issues before curation, researchers may consider adopting the following tools:
- `renv` (Package Management): A tool for creating reproducible environments. It generates a lockfile (renv.lock) recording the exact version of every package used, ensuring the project can be restored on another machine [@renv] .
- `lintr` (Static Analysis): A package that automatically checks code for syntax errors, style violations, and potential bugs as you write it [@lintr].
- Docker (Containerization): A technology that packages the entire operating system, code, and data into a single executable unit, providing the gold standard for reproducibility [@boettiger2015].
//
Pour prévenir ces problèmes avant la curation, les chercheurs peuvent envisager d'adopter les outils suivants :
- `renv` (gestion des paquets) : un outil permettant de créer des environnements reproductibles. Il génère un fichier de verrouillage (renv.lock) qui enregistre la version exacte de chaque paquet utilisé, garantissant ainsi que le projet puisse être restauré sur une autre machine [@renv].
- `lintr` (analyse statique) : un paquet qui vérifie automatiquement le code à la recherche d'erreurs de syntaxe, de violations de style et de bogues potentiels au fur et à mesure que vous l'écrivez [@lintr].
- Docker (conteneurisation) : une technologie qui regroupe l'ensemble du système d'exploitation, du code et des données en une seule unité exécutable, offrant ainsi la référence absolue en matière de reproductibilité [@boettiger2015].
## Using the Non-Interactive R Script // Utilisation du script R non interactif
For users who want to run this analysis on a server, in a batch job, or from the command line, here is a pure R script that performs the same process.
Download the **R Script:** [**`Inspect_Code_Script.R`**](Scripts/Inspect_Code_Script.R)
//
Pour les utilisateurs qui souhaitent exécuter cette analyse sur un serveur, dans le cadre d'un traitement par lots ou depuis la ligne de commande, voici un script R pur qui effectue le même processus.
Télécharger le **script R :** [**`Inspect_Code_Script.R`**](Scripts/Inspect_Code_Script.R)
### Example HPC Submission Script // Exemple de script de soumission HPC
`Inspect_Code_submit.sh`
``` bash
#!/bin/bash
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --time=00:10:00
#SBATCH --job-name=code_check
# Load R module
module load R
# Define data directory
DATA_DIR="/scratch/your_user/your_project/code_dir"
# Run Script
Rscript Scripts/Inspect_Code_Script.R $DATA_DIR
```