runtime-checker 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1972 -0
- package/Cargo.toml +46 -0
- package/LICENSE +29 -0
- package/README.md +26 -0
- package/data/mdn/README.md +30 -0
- package/data/mdn/bun.ron +1084 -0
- package/data/mdn/chrome.ron +7814 -0
- package/data/mdn/deno.ron +1497 -0
- package/data/mdn/firefox.ron +6452 -0
- package/data/mdn/node.ron +1311 -0
- package/data/mdn/safari.ron +6267 -0
- package/data/mdn-bcd.version +1 -0
- package/data/node.ron +3561 -0
- package/npm/bin/native/runtime-checker.exe +0 -0
- package/npm/bin/runtime-checker.js +23 -0
- package/npm/postinstall.js +29 -0
- package/npm/prepare-bin.js +11 -0
- package/package.json +42 -0
- package/src/analyzer.rs +405 -0
- package/src/bin/generate-mdn-data.rs +517 -0
- package/src/cli.rs +71 -0
- package/src/data.rs +266 -0
- package/src/engines.rs +154 -0
- package/src/help.rs +192 -0
- package/src/lib.rs +251 -0
- package/src/main.rs +12 -0
- package/src/report.rs +819 -0
- package/src/scanner.rs +404 -0
- package/src/version.rs +101 -0
package/src/lib.rs
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
mod analyzer;
|
|
2
|
+
mod cli;
|
|
3
|
+
mod data;
|
|
4
|
+
mod engines;
|
|
5
|
+
mod help;
|
|
6
|
+
mod report;
|
|
7
|
+
mod scanner;
|
|
8
|
+
mod version;
|
|
9
|
+
|
|
10
|
+
use std::{collections::HashMap, time::Instant};
|
|
11
|
+
|
|
12
|
+
use anyhow::{Context, Result};
|
|
13
|
+
|
|
14
|
+
#[global_allocator]
|
|
15
|
+
static GLOBAL_ALLOCATOR: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
|
16
|
+
|
|
17
|
+
pub use cli::{Cli, RuntimeKind};
|
|
18
|
+
use data::runtime;
|
|
19
|
+
use engines::check_engines;
|
|
20
|
+
pub use help::print_help;
|
|
21
|
+
use report::{ParserMode, Reporter, RuntimeReport};
|
|
22
|
+
use scanner::{DetectedFeature, FffMultiRuntimeScanner, Scanner, SourceDiscovery, SourceScan};
|
|
23
|
+
|
|
24
|
+
pub fn run(cli: Cli) -> Result<()> {
|
|
25
|
+
let started = Instant::now();
|
|
26
|
+
let root = cli
|
|
27
|
+
.dir
|
|
28
|
+
.canonicalize()
|
|
29
|
+
.with_context(|| format!("failed to resolve {}", cli.dir.display()))?;
|
|
30
|
+
if cli.fix && !matches!(cli.runtime, RuntimeKind::All | RuntimeKind::Node) {
|
|
31
|
+
anyhow::bail!("--fix is currently only supported for --runtime node or --runtime all");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let parser = if cli.fast {
|
|
35
|
+
ParserMode::Text
|
|
36
|
+
} else {
|
|
37
|
+
ParserMode::Oxc
|
|
38
|
+
};
|
|
39
|
+
let aggregate = cli.inspect.is_none();
|
|
40
|
+
let targets = cli.runtime.targets();
|
|
41
|
+
let first_runtime = runtime(targets[0])?;
|
|
42
|
+
let sources = SourceDiscovery.scan(&root, first_runtime)?;
|
|
43
|
+
let stats = sources.stats();
|
|
44
|
+
|
|
45
|
+
let reports = if cli.fast {
|
|
46
|
+
scan_fast(&root, &sources, targets, aggregate, cli.fix)?
|
|
47
|
+
} else {
|
|
48
|
+
scan_ast(&root, sources, targets, aggregate, cli.fix)?
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
Reporter::new(cli.summary, cli.inspect, parser).print(
|
|
52
|
+
&root,
|
|
53
|
+
&reports,
|
|
54
|
+
started.elapsed(),
|
|
55
|
+
stats,
|
|
56
|
+
);
|
|
57
|
+
Ok(())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn scan_fast(
|
|
61
|
+
root: &std::path::Path,
|
|
62
|
+
sources: &SourceScan,
|
|
63
|
+
targets: &[RuntimeKind],
|
|
64
|
+
aggregate: bool,
|
|
65
|
+
fix: bool,
|
|
66
|
+
) -> Result<Vec<RuntimeReport>> {
|
|
67
|
+
let target_runtimes = targets
|
|
68
|
+
.iter()
|
|
69
|
+
.copied()
|
|
70
|
+
.map(runtime)
|
|
71
|
+
.collect::<Result<Vec<_>>>()?;
|
|
72
|
+
let scan_plan = ScanPlan::new(targets, &target_runtimes)?;
|
|
73
|
+
let detections_by_runtime =
|
|
74
|
+
FffMultiRuntimeScanner.scan_files(&scan_plan.runtimes, sources.files())?;
|
|
75
|
+
let hidden_node_api_detected = scan_plan
|
|
76
|
+
.hidden_node_index
|
|
77
|
+
.and_then(|index| detections_by_runtime.get(index))
|
|
78
|
+
.is_some_and(|detections| has_node_api_detections(detections));
|
|
79
|
+
let mut reports = Vec::with_capacity(target_runtimes.len());
|
|
80
|
+
|
|
81
|
+
for (runtime, detections) in target_runtimes.into_iter().zip(detections_by_runtime) {
|
|
82
|
+
let node_api_detected = hidden_node_api_detected || has_node_api_detections(&detections);
|
|
83
|
+
reports.push(build_runtime_report(
|
|
84
|
+
root,
|
|
85
|
+
runtime.name(),
|
|
86
|
+
detections,
|
|
87
|
+
node_api_detected,
|
|
88
|
+
aggregate,
|
|
89
|
+
fix,
|
|
90
|
+
)?);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Ok(reports)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn scan_ast(
|
|
97
|
+
root: &std::path::Path,
|
|
98
|
+
sources: SourceScan,
|
|
99
|
+
targets: &[RuntimeKind],
|
|
100
|
+
aggregate: bool,
|
|
101
|
+
fix: bool,
|
|
102
|
+
) -> Result<Vec<RuntimeReport>> {
|
|
103
|
+
let target_runtimes = targets
|
|
104
|
+
.iter()
|
|
105
|
+
.copied()
|
|
106
|
+
.map(runtime)
|
|
107
|
+
.collect::<Result<Vec<_>>>()?;
|
|
108
|
+
let scan_plan = ScanPlan::new(targets, &target_runtimes)?;
|
|
109
|
+
let detections_by_runtime =
|
|
110
|
+
analyzer::analyze_files_for_runtimes(root, sources.files(), &scan_plan.runtimes)?;
|
|
111
|
+
let hidden_node_api_detected = scan_plan
|
|
112
|
+
.hidden_node_index
|
|
113
|
+
.and_then(|index| detections_by_runtime.get(index))
|
|
114
|
+
.is_some_and(|detections| has_node_api_detections(detections));
|
|
115
|
+
let mut reports = Vec::with_capacity(targets.len());
|
|
116
|
+
|
|
117
|
+
for (runtime, detections) in target_runtimes.into_iter().zip(detections_by_runtime) {
|
|
118
|
+
let node_api_detected = hidden_node_api_detected || has_node_api_detections(&detections);
|
|
119
|
+
reports.push(build_runtime_report(
|
|
120
|
+
root,
|
|
121
|
+
runtime.name(),
|
|
122
|
+
detections,
|
|
123
|
+
node_api_detected,
|
|
124
|
+
aggregate,
|
|
125
|
+
fix,
|
|
126
|
+
)?);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
Ok(reports)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fn build_runtime_report(
|
|
133
|
+
root: &std::path::Path,
|
|
134
|
+
runtime_name: &str,
|
|
135
|
+
mut detections: Vec<DetectedFeature>,
|
|
136
|
+
has_node_api_detections: bool,
|
|
137
|
+
aggregate: bool,
|
|
138
|
+
fix: bool,
|
|
139
|
+
) -> Result<RuntimeReport> {
|
|
140
|
+
let minimum = detections
|
|
141
|
+
.iter()
|
|
142
|
+
.map(|detection| detection.version)
|
|
143
|
+
.max()
|
|
144
|
+
.unwrap_or_default();
|
|
145
|
+
|
|
146
|
+
if aggregate {
|
|
147
|
+
collapse_prefix_detections(&mut detections);
|
|
148
|
+
detections = aggregate_feature_detections(detections);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let engines = if runtime_name == "node" {
|
|
152
|
+
check_engines(root, minimum, fix)?
|
|
153
|
+
} else {
|
|
154
|
+
None
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
Ok(RuntimeReport {
|
|
158
|
+
runtime: runtime_name.to_owned(),
|
|
159
|
+
detections,
|
|
160
|
+
minimum,
|
|
161
|
+
engines,
|
|
162
|
+
has_node_api_detections,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
struct ScanPlan<'a> {
|
|
167
|
+
runtimes: Vec<&'a data::RuntimeDb>,
|
|
168
|
+
hidden_node_index: Option<usize>,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
impl<'a> ScanPlan<'a> {
|
|
172
|
+
fn new(targets: &[RuntimeKind], target_runtimes: &[&'a data::RuntimeDb]) -> Result<Self> {
|
|
173
|
+
let needs_hidden_node = targets.iter().any(|target| is_browser_runtime(*target))
|
|
174
|
+
&& !targets.contains(&RuntimeKind::Node);
|
|
175
|
+
let mut runtimes = target_runtimes.to_vec();
|
|
176
|
+
let hidden_node_index = if needs_hidden_node {
|
|
177
|
+
let index = runtimes.len();
|
|
178
|
+
runtimes.push(runtime(RuntimeKind::Node)?);
|
|
179
|
+
Some(index)
|
|
180
|
+
} else {
|
|
181
|
+
None
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
Ok(Self {
|
|
185
|
+
runtimes,
|
|
186
|
+
hidden_node_index,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
fn is_browser_runtime(runtime: RuntimeKind) -> bool {
|
|
192
|
+
matches!(
|
|
193
|
+
runtime,
|
|
194
|
+
RuntimeKind::Safari | RuntimeKind::Chrome | RuntimeKind::Firefox
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
fn has_node_api_detections(detections: &[DetectedFeature]) -> bool {
|
|
199
|
+
detections
|
|
200
|
+
.iter()
|
|
201
|
+
.any(|detection| report::is_node_api_feature(&detection.feature))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
fn aggregate_feature_detections(detections: Vec<DetectedFeature>) -> Vec<DetectedFeature> {
|
|
205
|
+
let mut aggregated: Vec<DetectedFeature> = Vec::new();
|
|
206
|
+
let mut indexes: HashMap<(String, crate::version::RuntimeVersion), usize> = HashMap::new();
|
|
207
|
+
|
|
208
|
+
for detection in detections {
|
|
209
|
+
let key = (detection.feature.clone(), detection.version);
|
|
210
|
+
if let Some(index) = indexes.get(&key).copied() {
|
|
211
|
+
aggregated[index].count += detection.count;
|
|
212
|
+
} else {
|
|
213
|
+
indexes.insert(key, aggregated.len());
|
|
214
|
+
aggregated.push(detection);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
aggregated
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fn collapse_prefix_detections(detections: &mut Vec<DetectedFeature>) {
|
|
222
|
+
let mut by_location: HashMap<_, Vec<usize>> = HashMap::new();
|
|
223
|
+
for (index, detection) in detections.iter().enumerate() {
|
|
224
|
+
by_location
|
|
225
|
+
.entry((detection.path.as_path(), detection.line, detection.column))
|
|
226
|
+
.or_default()
|
|
227
|
+
.push(index);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let mut remove = vec![false; detections.len()];
|
|
231
|
+
for indices in by_location.values() {
|
|
232
|
+
for &left in indices {
|
|
233
|
+
for &right in indices {
|
|
234
|
+
if left == right {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
let prefix = format!("{}.", detections[left].feature);
|
|
238
|
+
if detections[right].feature.starts_with(&prefix) {
|
|
239
|
+
remove[left] = true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let mut index = 0;
|
|
246
|
+
detections.retain(|_| {
|
|
247
|
+
let keep = !remove[index];
|
|
248
|
+
index += 1;
|
|
249
|
+
keep
|
|
250
|
+
});
|
|
251
|
+
}
|
package/src/main.rs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
use clap::Parser;
|
|
2
|
+
|
|
3
|
+
fn main() -> anyhow::Result<()> {
|
|
4
|
+
let args = std::env::args_os().skip(1).collect::<Vec<_>>();
|
|
5
|
+
if args.is_empty() || args.iter().any(|arg| arg == "-h" || arg == "--help") {
|
|
6
|
+
runtime_checker::print_help();
|
|
7
|
+
return Ok(());
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let cli = runtime_checker::Cli::parse();
|
|
11
|
+
runtime_checker::run(cli)
|
|
12
|
+
}
|