@lamentis/naome 1.3.12 → 1.3.13
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 +2 -2
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/architecture/rules/external.rs +59 -0
- package/crates/naome-core/src/architecture/scan/graph_builder.rs +130 -30
- package/crates/naome-core/src/architecture/scan/imports/extractors/swift.rs +48 -0
- package/crates/naome-core/src/architecture/scan/imports/extractors.rs +3 -0
- package/crates/naome-core/src/architecture/scan/imports/resolver.rs +41 -1
- package/crates/naome-core/src/architecture/scan/imports.rs +1 -0
- package/crates/naome-core/src/architecture/scan/manifest/common.rs +102 -0
- package/crates/naome-core/src/architecture/scan/manifest/parsers/json.rs +46 -0
- package/crates/naome-core/src/architecture/scan/manifest/parsers/other.rs +280 -0
- package/crates/naome-core/src/architecture/scan/manifest/parsers/toml.rs +184 -0
- package/crates/naome-core/src/architecture/scan/manifest/parsers.rs +3 -0
- package/crates/naome-core/src/architecture/scan/manifest.rs +33 -0
- package/crates/naome-core/src/architecture/scan.rs +27 -1
- package/crates/naome-core/tests/architecture_manifests.rs +289 -0
- package/crates/naome-core/tests/architecture_support/mod.rs +2 -0
- package/crates/naome-core/tests/architecture_swift.rs +111 -0
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +1 -1
- package/templates/naome-root/.naome/manifest.json +2 -2
- package/templates/naome-root/docs/naome/architecture-fitness.md +17 -7
package/Cargo.lock
CHANGED
|
@@ -76,7 +76,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|
|
76
76
|
|
|
77
77
|
[[package]]
|
|
78
78
|
name = "naome-cli"
|
|
79
|
-
version = "1.3.
|
|
79
|
+
version = "1.3.13"
|
|
80
80
|
dependencies = [
|
|
81
81
|
"naome-core",
|
|
82
82
|
"serde_json",
|
|
@@ -84,7 +84,7 @@ dependencies = [
|
|
|
84
84
|
|
|
85
85
|
[[package]]
|
|
86
86
|
name = "naome-core"
|
|
87
|
-
version = "1.3.
|
|
87
|
+
version = "1.3.13"
|
|
88
88
|
dependencies = [
|
|
89
89
|
"serde",
|
|
90
90
|
"serde_json",
|
|
@@ -73,6 +73,7 @@ fn is_standard_library_dependency(language: Option<&str>, package: &str) -> bool
|
|
|
73
73
|
Some("javascript") | Some("typescript") => NODE_BUILTINS.contains(&root),
|
|
74
74
|
Some("python") => PYTHON_STDLIB.contains(&root),
|
|
75
75
|
Some("rust") => matches!(root, "std" | "core" | "alloc" | "proc_macro" | "test"),
|
|
76
|
+
Some("swift") => SWIFT_APPLE_FRAMEWORKS.contains(&root),
|
|
76
77
|
_ => false,
|
|
77
78
|
}
|
|
78
79
|
}
|
|
@@ -183,3 +184,61 @@ const PYTHON_STDLIB: &[&str] = &[
|
|
|
183
184
|
"xml",
|
|
184
185
|
"zipfile",
|
|
185
186
|
];
|
|
187
|
+
|
|
188
|
+
const SWIFT_APPLE_FRAMEWORKS: &[&str] = &[
|
|
189
|
+
"Accelerate",
|
|
190
|
+
"AppIntents",
|
|
191
|
+
"AppKit",
|
|
192
|
+
"ARKit",
|
|
193
|
+
"AVFoundation",
|
|
194
|
+
"CloudKit",
|
|
195
|
+
"Combine",
|
|
196
|
+
"CoreData",
|
|
197
|
+
"CoreFoundation",
|
|
198
|
+
"CoreBluetooth",
|
|
199
|
+
"CoreGraphics",
|
|
200
|
+
"CoreImage",
|
|
201
|
+
"CoreLocation",
|
|
202
|
+
"CoreML",
|
|
203
|
+
"CoreMotion",
|
|
204
|
+
"CoreNFC",
|
|
205
|
+
"CoreSpotlight",
|
|
206
|
+
"CoreTelephony",
|
|
207
|
+
"CoreText",
|
|
208
|
+
"CryptoKit",
|
|
209
|
+
"Dispatch",
|
|
210
|
+
"EventKit",
|
|
211
|
+
"Foundation",
|
|
212
|
+
"GameKit",
|
|
213
|
+
"HealthKit",
|
|
214
|
+
"LocalAuthentication",
|
|
215
|
+
"MapKit",
|
|
216
|
+
"MessageUI",
|
|
217
|
+
"Metal",
|
|
218
|
+
"MetalKit",
|
|
219
|
+
"NaturalLanguage",
|
|
220
|
+
"Network",
|
|
221
|
+
"Observation",
|
|
222
|
+
"PassKit",
|
|
223
|
+
"PencilKit",
|
|
224
|
+
"Photos",
|
|
225
|
+
"QuartzCore",
|
|
226
|
+
"RealityKit",
|
|
227
|
+
"SafariServices",
|
|
228
|
+
"SceneKit",
|
|
229
|
+
"Security",
|
|
230
|
+
"SpriteKit",
|
|
231
|
+
"StoreKit",
|
|
232
|
+
"Swift",
|
|
233
|
+
"SwiftData",
|
|
234
|
+
"SwiftUI",
|
|
235
|
+
"ThreadNetwork",
|
|
236
|
+
"UIKit",
|
|
237
|
+
"UniformTypeIdentifiers",
|
|
238
|
+
"UserNotifications",
|
|
239
|
+
"Vision",
|
|
240
|
+
"WatchKit",
|
|
241
|
+
"WebKit",
|
|
242
|
+
"WidgetKit",
|
|
243
|
+
"XCTest",
|
|
244
|
+
];
|
|
@@ -5,7 +5,7 @@ use std::path::Path;
|
|
|
5
5
|
use serde_json::json;
|
|
6
6
|
|
|
7
7
|
use super::imports;
|
|
8
|
-
use super::FileFact;
|
|
8
|
+
use super::{FileFact, ManifestFact};
|
|
9
9
|
use crate::architecture::config::ArchitectureConfig;
|
|
10
10
|
use crate::architecture::model::{ArchitectureEdgeKind, ArchitectureGraph, ArchitectureNodeKind};
|
|
11
11
|
|
|
@@ -16,13 +16,16 @@ pub(super) fn build_path_graph(
|
|
|
16
16
|
root: &Path,
|
|
17
17
|
files: Vec<String>,
|
|
18
18
|
config: &ArchitectureConfig,
|
|
19
|
+
manifests: &[ManifestFact],
|
|
19
20
|
) -> (ArchitectureGraph, BTreeMap<String, FileFact>) {
|
|
20
21
|
let mut graph = ArchitectureGraph::default();
|
|
21
22
|
let mut file_facts = BTreeMap::new();
|
|
23
|
+
let mut emitted_external_nodes = BTreeSet::new();
|
|
22
24
|
let file_set = files.iter().cloned().collect::<BTreeSet<_>>();
|
|
23
25
|
|
|
24
26
|
push_repository_and_policy_nodes(&mut graph, config);
|
|
25
27
|
push_directories(&mut graph, &files);
|
|
28
|
+
push_manifests(&mut graph, manifests, &mut emitted_external_nodes);
|
|
26
29
|
|
|
27
30
|
for path in files {
|
|
28
31
|
let content = fs::read_to_string(root.join(&path)).unwrap_or_default();
|
|
@@ -32,7 +35,7 @@ pub(super) fn build_path_graph(
|
|
|
32
35
|
file_facts.insert(path, fact);
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
push_imports(&mut graph, &file_facts);
|
|
38
|
+
push_imports(&mut graph, &file_facts, &mut emitted_external_nodes);
|
|
36
39
|
(graph, file_facts)
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -121,44 +124,117 @@ fn push_file(graph: &mut ArchitectureGraph, fact: &FileFact) {
|
|
|
121
124
|
push_membership_edges(graph, path, "context", &fact.contexts);
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
fn
|
|
125
|
-
|
|
127
|
+
fn push_manifests(
|
|
128
|
+
graph: &mut ArchitectureGraph,
|
|
129
|
+
manifests: &[ManifestFact],
|
|
130
|
+
emitted_external_nodes: &mut BTreeSet<String>,
|
|
131
|
+
) {
|
|
132
|
+
for manifest in manifests {
|
|
133
|
+
let package_id = manifest_package_id(manifest);
|
|
134
|
+
emit::push_node_with_metadata(
|
|
135
|
+
graph,
|
|
136
|
+
&package_id,
|
|
137
|
+
ArchitectureNodeKind::Package,
|
|
138
|
+
&manifest.package_name,
|
|
139
|
+
Some(manifest.path.clone()),
|
|
140
|
+
None,
|
|
141
|
+
manifest.confidence,
|
|
142
|
+
&manifest.extractor,
|
|
143
|
+
json!({
|
|
144
|
+
"path": manifest.path,
|
|
145
|
+
"ecosystem": manifest.ecosystem,
|
|
146
|
+
"packageName": manifest.package_name
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
emit::push_edge(
|
|
150
|
+
graph,
|
|
151
|
+
facts::parent_node_id(&manifest.path)
|
|
152
|
+
.as_deref()
|
|
153
|
+
.unwrap_or("repository:."),
|
|
154
|
+
&package_id,
|
|
155
|
+
ArchitectureEdgeKind::Contains,
|
|
156
|
+
"contains",
|
|
157
|
+
Some(manifest.path.clone()),
|
|
158
|
+
);
|
|
159
|
+
for dependency in &manifest.dependencies {
|
|
160
|
+
let external_id = format!("external:{}", dependency.name);
|
|
161
|
+
push_external_node(
|
|
162
|
+
graph,
|
|
163
|
+
emitted_external_nodes,
|
|
164
|
+
&external_id,
|
|
165
|
+
&dependency.name,
|
|
166
|
+
dependency.confidence,
|
|
167
|
+
&manifest.extractor,
|
|
168
|
+
json!({
|
|
169
|
+
"manifestPath": manifest.path,
|
|
170
|
+
"ecosystem": manifest.ecosystem,
|
|
171
|
+
"dependencyKind": dependency.dependency_kind,
|
|
172
|
+
"version": dependency.version
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
emit::push_edge_with_metadata(
|
|
176
|
+
graph,
|
|
177
|
+
&package_id,
|
|
178
|
+
&external_id,
|
|
179
|
+
ArchitectureEdgeKind::DependsOn,
|
|
180
|
+
"depends_on",
|
|
181
|
+
Some(manifest.path.clone()),
|
|
182
|
+
None,
|
|
183
|
+
None,
|
|
184
|
+
dependency.confidence,
|
|
185
|
+
&manifest.extractor,
|
|
186
|
+
json!({
|
|
187
|
+
"specifier": dependency.name,
|
|
188
|
+
"manifestPath": manifest.path,
|
|
189
|
+
"dependencyKind": dependency.dependency_kind,
|
|
190
|
+
"version": dependency.version
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
fn manifest_package_id(manifest: &ManifestFact) -> String {
|
|
198
|
+
format!(
|
|
199
|
+
"package:{}:{}",
|
|
200
|
+
stable_fragment(&manifest.ecosystem),
|
|
201
|
+
stable_fragment(&manifest.path)
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fn push_imports(
|
|
206
|
+
graph: &mut ArchitectureGraph,
|
|
207
|
+
file_facts: &BTreeMap<String, FileFact>,
|
|
208
|
+
emitted_external_nodes: &mut BTreeSet<String>,
|
|
209
|
+
) {
|
|
126
210
|
for fact in file_facts.values() {
|
|
127
211
|
for import in &fact.imports {
|
|
128
212
|
let target_id = match &import.target {
|
|
129
213
|
super::ImportTarget::File(path) => format!("file:{path}"),
|
|
130
214
|
super::ImportTarget::ExternalDependency(name) => {
|
|
131
215
|
let id = format!("external:{name}");
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
&import.extractor,
|
|
142
|
-
json!({ "specifier": import.specifier, "resolvedAs": "external" }),
|
|
143
|
-
);
|
|
144
|
-
}
|
|
216
|
+
push_external_node(
|
|
217
|
+
graph,
|
|
218
|
+
emitted_external_nodes,
|
|
219
|
+
&id,
|
|
220
|
+
name,
|
|
221
|
+
import.confidence,
|
|
222
|
+
&import.extractor,
|
|
223
|
+
json!({ "specifier": import.specifier, "resolvedAs": "external" }),
|
|
224
|
+
);
|
|
145
225
|
id
|
|
146
226
|
}
|
|
147
227
|
super::ImportTarget::Unknown(specifier) => {
|
|
148
228
|
let id = format!("unknown-import:{}", stable_fragment(specifier));
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
&import.extractor,
|
|
159
|
-
json!({ "specifier": specifier, "resolvedAs": "unknown" }),
|
|
160
|
-
);
|
|
161
|
-
}
|
|
229
|
+
push_external_node(
|
|
230
|
+
graph,
|
|
231
|
+
emitted_external_nodes,
|
|
232
|
+
&id,
|
|
233
|
+
specifier,
|
|
234
|
+
import.confidence,
|
|
235
|
+
&import.extractor,
|
|
236
|
+
json!({ "specifier": specifier, "resolvedAs": "unknown" }),
|
|
237
|
+
);
|
|
162
238
|
id
|
|
163
239
|
}
|
|
164
240
|
};
|
|
@@ -179,6 +255,30 @@ fn push_imports(graph: &mut ArchitectureGraph, file_facts: &BTreeMap<String, Fil
|
|
|
179
255
|
}
|
|
180
256
|
}
|
|
181
257
|
|
|
258
|
+
fn push_external_node(
|
|
259
|
+
graph: &mut ArchitectureGraph,
|
|
260
|
+
emitted_external_nodes: &mut BTreeSet<String>,
|
|
261
|
+
id: &str,
|
|
262
|
+
label: &str,
|
|
263
|
+
confidence: f32,
|
|
264
|
+
extractor: &str,
|
|
265
|
+
raw_origin: serde_json::Value,
|
|
266
|
+
) {
|
|
267
|
+
if emitted_external_nodes.insert(id.to_string()) {
|
|
268
|
+
emit::push_node_with_metadata(
|
|
269
|
+
graph,
|
|
270
|
+
id,
|
|
271
|
+
ArchitectureNodeKind::ExternalDependency,
|
|
272
|
+
label,
|
|
273
|
+
None,
|
|
274
|
+
None,
|
|
275
|
+
confidence,
|
|
276
|
+
extractor,
|
|
277
|
+
raw_origin,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
182
282
|
fn push_membership_edges(
|
|
183
283
|
graph: &mut ArchitectureGraph,
|
|
184
284
|
path: &str,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
use super::{is_comment_only, raw, RawImport};
|
|
2
|
+
|
|
3
|
+
pub(super) fn extract(content: &str) -> Vec<RawImport> {
|
|
4
|
+
let mut imports = Vec::new();
|
|
5
|
+
for (index, line) in content.lines().enumerate() {
|
|
6
|
+
let trimmed = line.trim();
|
|
7
|
+
if is_comment_only(trimmed) {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
let Some(import_value) = swift_import_value(trimmed) else {
|
|
11
|
+
continue;
|
|
12
|
+
};
|
|
13
|
+
if let Some(specifier) = swift_import_specifier(import_value) {
|
|
14
|
+
imports.push(raw(specifier, index, line, 0.88, "import:swift"));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
imports
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fn swift_import_value(line: &str) -> Option<&str> {
|
|
21
|
+
let mut remaining = line.trim();
|
|
22
|
+
while remaining.starts_with('@') {
|
|
23
|
+
let Some((_, after_attribute)) = remaining.split_once(' ') else {
|
|
24
|
+
return None;
|
|
25
|
+
};
|
|
26
|
+
remaining = after_attribute.trim_start();
|
|
27
|
+
}
|
|
28
|
+
remaining.strip_prefix("import ")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fn swift_import_specifier(value: &str) -> Option<String> {
|
|
32
|
+
let mut parts = value.split_whitespace();
|
|
33
|
+
let first = parts.next()?;
|
|
34
|
+
let module = if matches!(
|
|
35
|
+
first,
|
|
36
|
+
"class" | "struct" | "enum" | "protocol" | "func" | "var" | "typealias"
|
|
37
|
+
) {
|
|
38
|
+
parts.next()?
|
|
39
|
+
} else {
|
|
40
|
+
first
|
|
41
|
+
};
|
|
42
|
+
module
|
|
43
|
+
.split('.')
|
|
44
|
+
.next()
|
|
45
|
+
.map(str::trim)
|
|
46
|
+
.filter(|value| !value.is_empty())
|
|
47
|
+
.map(ToString::to_string)
|
|
48
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
use super::language_for_path;
|
|
2
2
|
use crate::architecture::model::SourceRange;
|
|
3
3
|
|
|
4
|
+
mod swift;
|
|
5
|
+
|
|
4
6
|
#[derive(Debug, Clone)]
|
|
5
7
|
pub(super) struct RawImport {
|
|
6
8
|
pub specifier: String,
|
|
@@ -15,6 +17,7 @@ pub(super) fn extract_raw_imports(path: &str, content: &str) -> Vec<RawImport> {
|
|
|
15
17
|
Some("rust") => extract_rust(content),
|
|
16
18
|
Some("python") => extract_python(content),
|
|
17
19
|
Some("go") => extract_go(content),
|
|
20
|
+
Some("swift") => swift::extract(content),
|
|
18
21
|
_ => Vec::new(),
|
|
19
22
|
}
|
|
20
23
|
}
|
|
@@ -35,6 +35,11 @@ pub(super) fn resolve_import(
|
|
|
35
35
|
return ImportTarget::File(path);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
if from_path.ends_with(".swift") {
|
|
39
|
+
if let Some(path) = resolve_swift_module_import(from_path, specifier, repository_files) {
|
|
40
|
+
return ImportTarget::File(path);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
38
43
|
external_target(specifier)
|
|
39
44
|
}
|
|
40
45
|
|
|
@@ -225,6 +230,40 @@ fn go_mod_module(root: &Path, go_mod_path: &str) -> Option<String> {
|
|
|
225
230
|
})
|
|
226
231
|
}
|
|
227
232
|
|
|
233
|
+
fn resolve_swift_module_import(
|
|
234
|
+
from_path: &str,
|
|
235
|
+
specifier: &str,
|
|
236
|
+
repository_files: &BTreeSet<String>,
|
|
237
|
+
) -> Option<String> {
|
|
238
|
+
if specifier.contains('/') || specifier.contains('.') {
|
|
239
|
+
return None;
|
|
240
|
+
}
|
|
241
|
+
let sources_root = swift_sources_root(from_path)?;
|
|
242
|
+
let target_prefix = format!("{sources_root}{specifier}/");
|
|
243
|
+
let conventional_file = format!("{target_prefix}{specifier}.swift");
|
|
244
|
+
if repository_files.contains(&conventional_file) {
|
|
245
|
+
return Some(conventional_file);
|
|
246
|
+
}
|
|
247
|
+
repository_files
|
|
248
|
+
.iter()
|
|
249
|
+
.find(|path| path.starts_with(&target_prefix) && path.ends_with(".swift"))
|
|
250
|
+
.cloned()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
fn swift_sources_root(from_path: &str) -> Option<String> {
|
|
254
|
+
if from_path.starts_with("Sources/") {
|
|
255
|
+
return Some("Sources/".to_string());
|
|
256
|
+
}
|
|
257
|
+
if from_path.starts_with("Tests/") {
|
|
258
|
+
return Some("Sources/".to_string());
|
|
259
|
+
}
|
|
260
|
+
if let Some((prefix, _)) = from_path.split_once("/Sources/") {
|
|
261
|
+
return Some(format!("{prefix}/Sources/"));
|
|
262
|
+
}
|
|
263
|
+
let (prefix, _) = from_path.split_once("/Tests/")?;
|
|
264
|
+
Some(format!("{prefix}/Sources/"))
|
|
265
|
+
}
|
|
266
|
+
|
|
228
267
|
fn resolve_candidates(
|
|
229
268
|
candidate: &str,
|
|
230
269
|
repository_files: &BTreeSet<String>,
|
|
@@ -286,10 +325,11 @@ fn extensions_for_language(language: Option<&str>) -> &'static [&'static str] {
|
|
|
286
325
|
Some("rust") => &["", ".rs"],
|
|
287
326
|
Some("python") => &["", ".py"],
|
|
288
327
|
Some("go") => &["", ".go"],
|
|
328
|
+
Some("swift") => &["", ".swift"],
|
|
289
329
|
Some("typescript") => &["", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
290
330
|
Some("javascript") => &["", ".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"],
|
|
291
331
|
_ => &[
|
|
292
|
-
"", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".rs", ".py", ".go",
|
|
332
|
+
"", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".rs", ".py", ".go", ".swift",
|
|
293
333
|
],
|
|
294
334
|
}
|
|
295
335
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use crate::architecture::scan::{ManifestDependency, ManifestFact};
|
|
4
|
+
|
|
5
|
+
pub(super) enum ManifestKind {
|
|
6
|
+
PackageJson,
|
|
7
|
+
CargoToml,
|
|
8
|
+
PyprojectToml,
|
|
9
|
+
GoMod,
|
|
10
|
+
PomXml,
|
|
11
|
+
BuildGradle,
|
|
12
|
+
PackageSwift,
|
|
13
|
+
XcodeProject,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub(super) fn manifest_kind(path: &str) -> Option<ManifestKind> {
|
|
17
|
+
match path.rsplit('/').next()? {
|
|
18
|
+
"package.json" => Some(ManifestKind::PackageJson),
|
|
19
|
+
"Cargo.toml" => Some(ManifestKind::CargoToml),
|
|
20
|
+
"pyproject.toml" => Some(ManifestKind::PyprojectToml),
|
|
21
|
+
"go.mod" => Some(ManifestKind::GoMod),
|
|
22
|
+
"pom.xml" => Some(ManifestKind::PomXml),
|
|
23
|
+
"build.gradle" | "build.gradle.kts" => Some(ManifestKind::BuildGradle),
|
|
24
|
+
"Package.swift" => Some(ManifestKind::PackageSwift),
|
|
25
|
+
"project.pbxproj" => {
|
|
26
|
+
if path.contains(".xcodeproj/") {
|
|
27
|
+
Some(ManifestKind::XcodeProject)
|
|
28
|
+
} else {
|
|
29
|
+
None
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
_ => None,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub(super) fn fact(
|
|
37
|
+
path: &str,
|
|
38
|
+
ecosystem: &str,
|
|
39
|
+
package_name: String,
|
|
40
|
+
mut dependencies: Vec<ManifestDependency>,
|
|
41
|
+
confidence: f32,
|
|
42
|
+
) -> ManifestFact {
|
|
43
|
+
dependencies.sort_by(|left, right| {
|
|
44
|
+
left.name
|
|
45
|
+
.cmp(&right.name)
|
|
46
|
+
.then(left.dependency_kind.cmp(&right.dependency_kind))
|
|
47
|
+
});
|
|
48
|
+
dependencies.dedup_by(|left, right| {
|
|
49
|
+
left.name == right.name && left.dependency_kind == right.dependency_kind
|
|
50
|
+
});
|
|
51
|
+
ManifestFact {
|
|
52
|
+
path: path.to_string(),
|
|
53
|
+
ecosystem: ecosystem.to_string(),
|
|
54
|
+
package_name,
|
|
55
|
+
dependencies,
|
|
56
|
+
confidence,
|
|
57
|
+
extractor: format!("manifest:{}", manifest_kind_label(path)),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pub(super) fn dependency(
|
|
62
|
+
name: impl Into<String>,
|
|
63
|
+
dependency_kind: impl Into<String>,
|
|
64
|
+
version: Option<String>,
|
|
65
|
+
confidence: f32,
|
|
66
|
+
) -> ManifestDependency {
|
|
67
|
+
ManifestDependency {
|
|
68
|
+
name: name.into(),
|
|
69
|
+
dependency_kind: dependency_kind.into(),
|
|
70
|
+
version,
|
|
71
|
+
confidence,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pub(super) fn fallback_package_name(path: &str, ecosystem: &str) -> String {
|
|
76
|
+
let parent = Path::new(path)
|
|
77
|
+
.parent()
|
|
78
|
+
.map(|path| path.to_string_lossy().replace('\\', "/"))
|
|
79
|
+
.filter(|path| !path.is_empty())
|
|
80
|
+
.unwrap_or_else(|| path.to_string());
|
|
81
|
+
format!("{ecosystem}:{parent}")
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
pub(super) fn quoted_value(value: &str) -> Option<String> {
|
|
85
|
+
let trimmed = value.trim();
|
|
86
|
+
trimmed
|
|
87
|
+
.strip_prefix('"')
|
|
88
|
+
.and_then(|value| value.split_once('"').map(|(value, _)| value.to_string()))
|
|
89
|
+
.or_else(|| {
|
|
90
|
+
trimmed
|
|
91
|
+
.strip_prefix('\'')
|
|
92
|
+
.and_then(|value| value.split_once('\'').map(|(value, _)| value.to_string()))
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pub(super) fn clean_toml_line(line: &str) -> &str {
|
|
97
|
+
line.split('#').next().unwrap_or_default().trim()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fn manifest_kind_label(path: &str) -> &str {
|
|
101
|
+
path.rsplit('/').next().unwrap_or(path)
|
|
102
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
use serde_json::Value;
|
|
2
|
+
|
|
3
|
+
use crate::architecture::scan::{ManifestDependency, ManifestFact};
|
|
4
|
+
|
|
5
|
+
use super::super::common::{dependency, fact, fallback_package_name};
|
|
6
|
+
|
|
7
|
+
pub(in crate::architecture::scan::manifest) fn package_json(
|
|
8
|
+
path: &str,
|
|
9
|
+
content: &str,
|
|
10
|
+
) -> Option<ManifestFact> {
|
|
11
|
+
let value = serde_json::from_str::<Value>(content).ok()?;
|
|
12
|
+
let package_name = value
|
|
13
|
+
.get("name")
|
|
14
|
+
.and_then(Value::as_str)
|
|
15
|
+
.map(str::to_string)
|
|
16
|
+
.unwrap_or_else(|| fallback_package_name(path, "npm"));
|
|
17
|
+
let mut dependencies = Vec::new();
|
|
18
|
+
push_dependency_object(&mut dependencies, &value, "dependencies", "runtime");
|
|
19
|
+
push_dependency_object(&mut dependencies, &value, "devDependencies", "development");
|
|
20
|
+
push_dependency_object(&mut dependencies, &value, "peerDependencies", "peer");
|
|
21
|
+
push_dependency_object(
|
|
22
|
+
&mut dependencies,
|
|
23
|
+
&value,
|
|
24
|
+
"optionalDependencies",
|
|
25
|
+
"optional",
|
|
26
|
+
);
|
|
27
|
+
Some(fact(path, "npm", package_name, dependencies, 0.98))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn push_dependency_object(
|
|
31
|
+
dependencies: &mut Vec<ManifestDependency>,
|
|
32
|
+
value: &Value,
|
|
33
|
+
field: &str,
|
|
34
|
+
kind: &str,
|
|
35
|
+
) {
|
|
36
|
+
if let Some(object) = value.get(field).and_then(Value::as_object) {
|
|
37
|
+
dependencies.extend(object.iter().map(|(name, version)| {
|
|
38
|
+
dependency(
|
|
39
|
+
name.clone(),
|
|
40
|
+
kind,
|
|
41
|
+
version.as_str().map(str::to_string),
|
|
42
|
+
0.98,
|
|
43
|
+
)
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
}
|