@groma/scanner-rust 0.1.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/LICENSE +21 -0
- package/THIRD_PARTY_NOTICES.md +28928 -0
- package/dist/bin/darwin-arm64/groma-rust-scanner +0 -0
- package/dist/bin/linux-arm64/groma-rust-scanner +0 -0
- package/dist/bin/linux-x64/groma-rust-scanner +0 -0
- package/dist/bin/win32-arm64/groma-rust-scanner.exe +0 -0
- package/dist/bin/win32-x64/groma-rust-scanner.exe +0 -0
- package/package.json +64 -0
- package/src/index.js +364 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@groma/scanner-rust",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Rust project evidence through established semantic tooling.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"os": [
|
|
9
|
+
"win32",
|
|
10
|
+
"linux",
|
|
11
|
+
"darwin"
|
|
12
|
+
],
|
|
13
|
+
"cpu": [
|
|
14
|
+
"arm64",
|
|
15
|
+
"x64"
|
|
16
|
+
],
|
|
17
|
+
"groma": {
|
|
18
|
+
"scanner": {
|
|
19
|
+
"id": "rust",
|
|
20
|
+
"entry": "./src/index.js",
|
|
21
|
+
"discovery": {
|
|
22
|
+
"technologies": [
|
|
23
|
+
"rust"
|
|
24
|
+
],
|
|
25
|
+
"rules": [
|
|
26
|
+
{
|
|
27
|
+
"type": "toml",
|
|
28
|
+
"files": [
|
|
29
|
+
"**/Cargo.toml"
|
|
30
|
+
],
|
|
31
|
+
"technology": "rust",
|
|
32
|
+
"kind": "language",
|
|
33
|
+
"tables": [
|
|
34
|
+
"package",
|
|
35
|
+
"workspace"
|
|
36
|
+
],
|
|
37
|
+
"versionPath": [
|
|
38
|
+
"package",
|
|
39
|
+
"rust-version"
|
|
40
|
+
],
|
|
41
|
+
"declaration": "Cargo package or workspace"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"compatibility": {
|
|
45
|
+
"groma": "^0.3.0",
|
|
46
|
+
"technologyVersions": {}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"src",
|
|
53
|
+
"dist/bin",
|
|
54
|
+
"LICENSE",
|
|
55
|
+
"THIRD_PARTY_NOTICES.md"
|
|
56
|
+
],
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "https://github.com/MrLesk/Groma.md.git"
|
|
60
|
+
},
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"access": "public"
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
// plugins/scanners/rust/src/index.ts
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import path2 from "node:path";
|
|
4
|
+
|
|
5
|
+
// packages/scanner/src/index.ts
|
|
6
|
+
function compare(...values) {
|
|
7
|
+
return values.join("\x00");
|
|
8
|
+
}
|
|
9
|
+
function uniqueBy(values, key, label) {
|
|
10
|
+
const seen = new Set;
|
|
11
|
+
for (const value of values) {
|
|
12
|
+
const id = key(value);
|
|
13
|
+
if (seen.has(id))
|
|
14
|
+
throw new Error(`duplicate ${label}: ${id}`);
|
|
15
|
+
seen.add(id);
|
|
16
|
+
}
|
|
17
|
+
return [...values].sort((left, right) => key(left).localeCompare(key(right)));
|
|
18
|
+
}
|
|
19
|
+
function createScanObservation(input) {
|
|
20
|
+
const roots = validateRoots(input.roots);
|
|
21
|
+
const rootIds = new Set(roots.map((root) => root.id));
|
|
22
|
+
const files = uniqueBy(input.files.map((file) => ({
|
|
23
|
+
...file,
|
|
24
|
+
roots: [...new Set(file.roots)].sort(),
|
|
25
|
+
symbols: [...new Map(file.symbols.map((symbol) => [
|
|
26
|
+
compare(symbol.id, symbol.kind),
|
|
27
|
+
symbol
|
|
28
|
+
])).values()].sort((left, right) => {
|
|
29
|
+
return compare(left.id, left.kind).localeCompare(compare(right.id, right.kind));
|
|
30
|
+
})
|
|
31
|
+
})), (file) => file.file, "file path");
|
|
32
|
+
const filePaths = new Set(files.map((file) => file.file));
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
if (file.roots.length === 0)
|
|
35
|
+
throw new Error(`file has no root: ${file.file}`);
|
|
36
|
+
for (const root of file.roots) {
|
|
37
|
+
if (!rootIds.has(root))
|
|
38
|
+
throw new Error(`file references unknown root: ${root}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const diagnostic of input.diagnostics)
|
|
42
|
+
diagnosticLocation(diagnostic);
|
|
43
|
+
return {
|
|
44
|
+
schemaVersion: 1,
|
|
45
|
+
scanner: input.scanner,
|
|
46
|
+
roots,
|
|
47
|
+
files,
|
|
48
|
+
...operationEvidence(input, filePaths),
|
|
49
|
+
diagnostics: [...new Map(input.diagnostics.map((diagnostic) => [
|
|
50
|
+
diagnosticKey(diagnostic),
|
|
51
|
+
diagnostic
|
|
52
|
+
])).values()].sort((left, right) => {
|
|
53
|
+
return diagnosticKey(left).localeCompare(diagnosticKey(right));
|
|
54
|
+
})
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function validateRoots(input) {
|
|
58
|
+
const roots = uniqueBy(input, (root) => root.id, "root id");
|
|
59
|
+
const byId = new Map(roots.map((root) => [root.id, root]));
|
|
60
|
+
for (const root of roots) {
|
|
61
|
+
const visited = new Set([root.id]);
|
|
62
|
+
let parent = root.parent;
|
|
63
|
+
while (parent !== undefined) {
|
|
64
|
+
if (visited.has(parent))
|
|
65
|
+
throw new Error(`root hierarchy contains a cycle: ${parent}`);
|
|
66
|
+
visited.add(parent);
|
|
67
|
+
const ancestor = byId.get(parent);
|
|
68
|
+
if (ancestor === undefined)
|
|
69
|
+
throw new Error(`root references unknown parent: ${parent}`);
|
|
70
|
+
parent = ancestor.parent;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return roots;
|
|
74
|
+
}
|
|
75
|
+
function diagnosticKey(diagnostic) {
|
|
76
|
+
return compare(diagnostic.severity, diagnostic.code, diagnostic.message, diagnostic.file ?? "", String(diagnostic.line ?? ""));
|
|
77
|
+
}
|
|
78
|
+
function diagnosticLocation(value) {
|
|
79
|
+
if (value.line !== undefined && (!Number.isInteger(value.line) || Number(value.line) < 1)) {
|
|
80
|
+
throw new Error("diagnostic.line must be a positive integer");
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
...value.file === undefined ? {} : { file: string(value.file, "diagnostic.file") },
|
|
84
|
+
...value.line === undefined ? {} : { line: Number(value.line) }
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function object(value, label) {
|
|
88
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
89
|
+
throw new Error(`${label} must be an object`);
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
function string(value, label) {
|
|
94
|
+
if (typeof value !== "string")
|
|
95
|
+
throw new Error(`${label} must be a string`);
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
function array(value, label) {
|
|
99
|
+
if (!Array.isArray(value))
|
|
100
|
+
throw new Error(`${label} must be an array`);
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
function parseScanObservation(source) {
|
|
104
|
+
const value = object(JSON.parse(source), "observation");
|
|
105
|
+
if (value.schemaVersion !== 1)
|
|
106
|
+
throw new Error("unsupported scanner schema");
|
|
107
|
+
const scanner = object(value.scanner, "scanner");
|
|
108
|
+
return createScanObservation({
|
|
109
|
+
scanner: {
|
|
110
|
+
id: string(scanner.id, "scanner.id"),
|
|
111
|
+
technology: string(scanner.technology, "scanner.technology"),
|
|
112
|
+
engine: string(scanner.engine, "scanner.engine"),
|
|
113
|
+
engineVersion: string(scanner.engineVersion, "scanner.engineVersion")
|
|
114
|
+
},
|
|
115
|
+
...parseOperations(value),
|
|
116
|
+
roots: array(value.roots, "roots").map((entry) => {
|
|
117
|
+
const root = object(entry, "root");
|
|
118
|
+
return {
|
|
119
|
+
id: string(root.id, "root.id"),
|
|
120
|
+
kind: string(root.kind, "root.kind"),
|
|
121
|
+
name: string(root.name, "root.name"),
|
|
122
|
+
...root.file === undefined ? {} : { file: string(root.file, "root.file") },
|
|
123
|
+
...root.parent === undefined ? {} : { parent: string(root.parent, "root.parent") }
|
|
124
|
+
};
|
|
125
|
+
}),
|
|
126
|
+
files: array(value.files, "files").map((entry, index) => {
|
|
127
|
+
const file = object(entry, `files[${index}]`);
|
|
128
|
+
return {
|
|
129
|
+
file: string(file.file, `files[${index}].file`),
|
|
130
|
+
roots: array(file.roots, "file.roots").map((root) => string(root, "file.root")),
|
|
131
|
+
symbols: array(file.symbols, `files[${index}].symbols`).map((entry, symbolIndex) => {
|
|
132
|
+
const symbol = object(entry, `files[${index}].symbols[${symbolIndex}]`);
|
|
133
|
+
return {
|
|
134
|
+
id: string(symbol.id, "symbol.id"),
|
|
135
|
+
name: string(symbol.name, "symbol.name"),
|
|
136
|
+
kind: string(symbol.kind, "symbol.kind")
|
|
137
|
+
};
|
|
138
|
+
})
|
|
139
|
+
};
|
|
140
|
+
}),
|
|
141
|
+
diagnostics: array(value.diagnostics, "diagnostics").map((entry, index) => {
|
|
142
|
+
const diagnostic = object(entry, `diagnostics[${index}]`);
|
|
143
|
+
return {
|
|
144
|
+
severity: string(diagnostic.severity, `diagnostics[${index}].severity`),
|
|
145
|
+
code: string(diagnostic.code, `diagnostics[${index}].code`),
|
|
146
|
+
message: string(diagnostic.message, `diagnostics[${index}].message`),
|
|
147
|
+
...diagnosticLocation(diagnostic)
|
|
148
|
+
};
|
|
149
|
+
})
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function parseOperations(value) {
|
|
153
|
+
if (value.operations === undefined) {
|
|
154
|
+
if (value.invocations !== undefined)
|
|
155
|
+
throw new Error("invocations require operation declarations");
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
const operations = array(value.operations, "operations").map((entry) => {
|
|
159
|
+
const operation = object(entry, "operation");
|
|
160
|
+
return {
|
|
161
|
+
id: string(operation.id, "operation.id"),
|
|
162
|
+
file: string(operation.file, "operation.file"),
|
|
163
|
+
name: string(operation.name, "operation.name"),
|
|
164
|
+
...sourcePosition(operation.position),
|
|
165
|
+
...operationTokens(operation)
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
const invocations = array(value.invocations, "invocations").map((entry) => {
|
|
169
|
+
const invocation = object(entry, "invocation");
|
|
170
|
+
if (typeof invocation.unresolved !== "boolean")
|
|
171
|
+
throw new Error("invocation.unresolved must be a boolean");
|
|
172
|
+
if (!Number.isInteger(invocation.line) || Number(invocation.line) < 1)
|
|
173
|
+
throw new Error("invocation.line must be a positive integer");
|
|
174
|
+
const binding = invocation.binding === undefined ? undefined : object(invocation.binding, "invocation.binding");
|
|
175
|
+
if (binding && (!Number.isInteger(binding.line) || Number(binding.line) < 1))
|
|
176
|
+
throw new Error("binding.line must be a positive integer");
|
|
177
|
+
return {
|
|
178
|
+
source: string(invocation.source, "invocation.source"),
|
|
179
|
+
targets: array(invocation.targets, "invocation.targets").map((target) => string(target, "invocation.target")),
|
|
180
|
+
unresolved: invocation.unresolved,
|
|
181
|
+
line: Number(invocation.line),
|
|
182
|
+
...sourcePosition(invocation.position),
|
|
183
|
+
...invocation.member === undefined ? {} : { member: string(invocation.member, "invocation.member") },
|
|
184
|
+
...binding === undefined ? {} : { binding: { file: string(binding.file, "binding.file"), line: Number(binding.line), ...sourcePosition(binding.position) } }
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
return { operations, invocations };
|
|
188
|
+
}
|
|
189
|
+
function operationEvidence(input, files) {
|
|
190
|
+
if (input.operations === undefined && input.invocations !== undefined)
|
|
191
|
+
throw new Error("invocations require operation declarations");
|
|
192
|
+
if (input.operations === undefined)
|
|
193
|
+
return {};
|
|
194
|
+
const operations = uniqueBy(input.operations, (operation) => operation.id, "operation id");
|
|
195
|
+
const ids = new Set(operations.map((operation) => operation.id));
|
|
196
|
+
for (const operation of operations) {
|
|
197
|
+
if (!files.has(operation.file))
|
|
198
|
+
throw new Error(`operation references unknown file: ${operation.file}`);
|
|
199
|
+
sourcePosition(operation.position);
|
|
200
|
+
validateOperationTokens(operation);
|
|
201
|
+
}
|
|
202
|
+
const invocations = input.invocations ?? [];
|
|
203
|
+
for (const invocation of invocations)
|
|
204
|
+
validateInvocation(invocation, ids, files);
|
|
205
|
+
return { operations, invocations };
|
|
206
|
+
}
|
|
207
|
+
function operationTokens(operation) {
|
|
208
|
+
if (operation.tokens === undefined) {
|
|
209
|
+
if (operation.startLine !== undefined || operation.endLine !== undefined) {
|
|
210
|
+
throw new Error("operation range requires tokens");
|
|
211
|
+
}
|
|
212
|
+
return {};
|
|
213
|
+
}
|
|
214
|
+
if (operation.startLine === undefined || operation.endLine === undefined) {
|
|
215
|
+
throw new Error("operation tokens require a source range");
|
|
216
|
+
}
|
|
217
|
+
if (!Number.isInteger(operation.startLine) || Number(operation.startLine) < 1) {
|
|
218
|
+
throw new Error("operation.startLine must be a positive integer");
|
|
219
|
+
}
|
|
220
|
+
if (!Number.isInteger(operation.endLine) || Number(operation.endLine) < 1) {
|
|
221
|
+
throw new Error("operation.endLine must be a positive integer");
|
|
222
|
+
}
|
|
223
|
+
const startLine = Number(operation.startLine);
|
|
224
|
+
const endLine = Number(operation.endLine);
|
|
225
|
+
if (endLine < startLine)
|
|
226
|
+
throw new Error("operation.endLine must be at or after startLine");
|
|
227
|
+
return {
|
|
228
|
+
startLine,
|
|
229
|
+
endLine,
|
|
230
|
+
tokens: array(operation.tokens, "operation.tokens").map((token) => string(token, "operation.token"))
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function validateOperationTokens(operation) {
|
|
234
|
+
operationTokens({
|
|
235
|
+
tokens: operation.tokens,
|
|
236
|
+
startLine: operation.startLine,
|
|
237
|
+
endLine: operation.endLine
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
function validateInvocation(invocation, ids, files) {
|
|
241
|
+
sourcePosition(invocation.position);
|
|
242
|
+
sourcePosition(invocation.binding?.position);
|
|
243
|
+
if (!invocation.targets.length && !invocation.unresolved)
|
|
244
|
+
throw new Error("an empty target set must be unresolved");
|
|
245
|
+
if (!ids.has(invocation.source) || invocation.targets.some((target) => !ids.has(target))) {
|
|
246
|
+
throw new Error("invocation references unknown operation");
|
|
247
|
+
}
|
|
248
|
+
if (invocation.binding && !files.has(invocation.binding.file))
|
|
249
|
+
throw new Error("invocation binding references unknown file");
|
|
250
|
+
}
|
|
251
|
+
function sourcePosition(position) {
|
|
252
|
+
if (position === undefined)
|
|
253
|
+
return {};
|
|
254
|
+
if (!Number.isInteger(position) || Number(position) < 0)
|
|
255
|
+
throw new Error("source position must be a non-negative integer");
|
|
256
|
+
return { position: Number(position) };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// plugins/scanners/rust/src/project.ts
|
|
260
|
+
import { execFile } from "node:child_process";
|
|
261
|
+
import { access } from "node:fs/promises";
|
|
262
|
+
import path from "node:path";
|
|
263
|
+
import { promisify } from "node:util";
|
|
264
|
+
var execute = promisify(execFile);
|
|
265
|
+
async function exists(file) {
|
|
266
|
+
try {
|
|
267
|
+
await access(file);
|
|
268
|
+
return true;
|
|
269
|
+
} catch {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function manifestAt(root, settings) {
|
|
274
|
+
if (settings.manifest === undefined)
|
|
275
|
+
return path.join(root, "Cargo.toml");
|
|
276
|
+
if (typeof settings.manifest !== "string") {
|
|
277
|
+
throw new Error("RUST_PROJECT_SELECTION: Set settings.manifest on the rust entry in scanners.json to a Cargo.toml path.");
|
|
278
|
+
}
|
|
279
|
+
return path.resolve(root, settings.manifest);
|
|
280
|
+
}
|
|
281
|
+
async function readRustProject(root, settings, options) {
|
|
282
|
+
const manifest = manifestAt(root, settings);
|
|
283
|
+
if (!await exists(manifest)) {
|
|
284
|
+
throw new Error("RUST_PROJECT_MISSING: Select an existing Cargo.toml with settings.manifest on the rust entry in scanners.json.");
|
|
285
|
+
}
|
|
286
|
+
let metadata;
|
|
287
|
+
try {
|
|
288
|
+
const result = await execute(options.cargo ?? "cargo", ["metadata", "--format-version", "1", "--offline", "--locked", "--manifest-path", manifest], { cwd: root, maxBuffer: 64 * 1024 * 1024 });
|
|
289
|
+
metadata = JSON.parse(result.stdout);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
throw new Error(`RUST_CARGO_NOT_READY: Install the project's Rust toolchain, then run cargo fetch --manifest-path "${manifest}" to prepare its lockfile and dependencies. ${error}`);
|
|
292
|
+
}
|
|
293
|
+
const packageAtManifest = metadata.packages.find((pkg) => pkg.manifest_path === manifest);
|
|
294
|
+
const packages = packageAtManifest ? [packageAtManifest] : metadata.packages.filter((pkg) => metadata.workspace_members.includes(pkg.id));
|
|
295
|
+
const targets = packages.flatMap((pkg) => pkg.targets).filter((target) => target.kind.some((kind) => kind === "lib" || kind === "bin")).map((target) => target.src_path).sort();
|
|
296
|
+
if (!targets.length)
|
|
297
|
+
throw new Error("RUST_TARGET_MISSING: The selected Cargo project needs a library or binary target.");
|
|
298
|
+
return { root, manifest, name: packageAtManifest?.name ?? path.basename(path.dirname(manifest)), targets };
|
|
299
|
+
}
|
|
300
|
+
async function checkRustToolchain(root, options) {
|
|
301
|
+
try {
|
|
302
|
+
await execute(options.cargo ?? "cargo", ["--version"], { cwd: root });
|
|
303
|
+
const result = await execute(options.rustc ?? "rustc", ["--print", "sysroot"], { cwd: root });
|
|
304
|
+
const library = path.join(result.stdout.trim(), "lib/rustlib/src/rust/library");
|
|
305
|
+
if (!await exists(library))
|
|
306
|
+
throw new Error("Rust standard library sources are missing.");
|
|
307
|
+
} catch (error) {
|
|
308
|
+
throw new Error(`RUST_TOOLCHAIN_MISSING: Install the project's Rust toolchain with Cargo and rustc, and add its standard library sources with rustup component add rust-src. ${error}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// plugins/scanners/rust/src/index.ts
|
|
313
|
+
var executable = fileURLToPath(new URL(`../dist/bin/${process.platform}-${process.arch}/groma-rust-scanner${process.platform === "win32" ? ".exe" : ""}`, import.meta.url));
|
|
314
|
+
async function checkRustReadiness(repositoryRoot, settings = {}, options = {}) {
|
|
315
|
+
const root = path2.resolve(repositoryRoot);
|
|
316
|
+
const worker = options.worker ?? executable;
|
|
317
|
+
if (!await exists(worker)) {
|
|
318
|
+
throw new Error("RUST_WORKER_MISSING: Install the packaged Rust scanner, or build it with bun plugins/scanners/rust/build.ts.");
|
|
319
|
+
}
|
|
320
|
+
await checkRustToolchain(root, options);
|
|
321
|
+
return { input: await readRustProject(root, settings, options), worker };
|
|
322
|
+
}
|
|
323
|
+
async function scanRustSource(repositoryRoot, settings = {}, options = {}) {
|
|
324
|
+
const { input, worker } = await checkRustReadiness(repositoryRoot, settings, options);
|
|
325
|
+
try {
|
|
326
|
+
const pending = execute(worker, [], {
|
|
327
|
+
cwd: input.root,
|
|
328
|
+
encoding: "utf8",
|
|
329
|
+
timeout: 120000,
|
|
330
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
331
|
+
killSignal: "SIGKILL",
|
|
332
|
+
windowsHide: true
|
|
333
|
+
});
|
|
334
|
+
pending.child.stdin?.end(JSON.stringify(input));
|
|
335
|
+
const { stdout } = await pending;
|
|
336
|
+
return parseScanObservation(stdout);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
throw new Error(`RUST_ANALYSIS_FAILED: No observation was produced. Check the selected Cargo project and the engine diagnostic. ${error}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
var scanner = {
|
|
342
|
+
id: "rust",
|
|
343
|
+
watch: {
|
|
344
|
+
include: [
|
|
345
|
+
"**/*.rs",
|
|
346
|
+
"**/Cargo.toml",
|
|
347
|
+
"**/Cargo.lock",
|
|
348
|
+
"**/rust-toolchain",
|
|
349
|
+
"**/rust-toolchain.toml",
|
|
350
|
+
"**/.cargo/**"
|
|
351
|
+
],
|
|
352
|
+
exclude: ["**/target/**", "**/node_modules/**", "**/.git/**", "**/vendor/**", "**/dist/**"]
|
|
353
|
+
},
|
|
354
|
+
checkReadiness: async (root, settings) => {
|
|
355
|
+
await checkRustReadiness(root, settings);
|
|
356
|
+
},
|
|
357
|
+
scan: scanRustSource
|
|
358
|
+
};
|
|
359
|
+
var src_default = scanner;
|
|
360
|
+
export {
|
|
361
|
+
checkRustReadiness,
|
|
362
|
+
src_default as default,
|
|
363
|
+
scanRustSource
|
|
364
|
+
};
|