@groma/scanner-go 0.1.3 → 0.2.1

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.
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,23 +1,30 @@
1
1
  {
2
2
  "name": "@groma/scanner-go",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Go project evidence through Go language tooling.",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "license": "MIT",
8
8
  "os": [
9
+ "win32",
9
10
  "linux",
10
- "darwin",
11
- "win32"
11
+ "darwin"
12
12
  ],
13
13
  "cpu": [
14
- "arm64",
15
- "x64"
14
+ "x64",
15
+ "arm64"
16
16
  ],
17
17
  "groma": {
18
18
  "scanner": {
19
19
  "id": "go",
20
20
  "entry": "./src/index.js",
21
+ "include": [
22
+ "**/*.go",
23
+ "**/go.mod"
24
+ ],
25
+ "exclude": [
26
+ "vendor/"
27
+ ],
21
28
  "discovery": {
22
29
  "technologies": [
23
30
  "go"
@@ -35,14 +42,14 @@
35
42
  }
36
43
  ],
37
44
  "compatibility": {
38
- "groma": ">=0.3.0"
45
+ "groma": ">=0.5.0"
39
46
  }
40
47
  }
41
48
  }
42
49
  },
43
50
  "repository": {
44
51
  "type": "git",
45
- "url": "https://github.com/MrLesk/Groma.md.git"
52
+ "url": "https://github.com/MrLesk/groma.md.git"
46
53
  },
47
54
  "publishConfig": {
48
55
  "access": "public"
package/src/index.js CHANGED
@@ -1,42 +1,12 @@
1
1
  // @bun
2
2
  // plugins/scanners/go/src/index.ts
3
- import path5 from "path";
4
-
5
- // plugins/scanners/projects.ts
6
- import { execFile } from "child_process";
7
- import { existsSync } from "fs";
8
- import path from "path";
9
- import { promisify } from "util";
10
- var execute = promisify(execFile);
11
- var excluded = new Set([
12
- ".git",
13
- "node_modules",
14
- "vendor",
15
- "target",
16
- "dist",
17
- "build",
18
- "bin",
19
- "obj",
20
- ".gradle",
21
- ".angular",
22
- "coverage",
23
- "generated",
24
- "groma",
25
- ".groma"
26
- ]);
27
- async function repositoryFiles(root, matches) {
28
- const { stdout } = await execute("git", ["-C", root, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { maxBuffer: 64 * 1024 * 1024 });
29
- return [...new Set(stdout.split("\x00").filter((file) => file && matches(file) && existsSync(path.join(root, file))))].sort();
30
- }
31
- function projectFiles(root, matches) {
32
- return repositoryFiles(root, (file) => matches(file) && !file.split("/").some((part) => excluded.has(part)));
33
- }
3
+ import path4 from "path";
34
4
 
35
5
  // plugins/scanners/project-scanner.ts
36
- import path3 from "path";
6
+ import path2 from "path";
37
7
 
38
8
  // plugins/scanners/observations.ts
39
- import path2 from "path";
9
+ import path from "path";
40
10
 
41
11
  // packages/scanner/src/values.ts
42
12
  function object(value, label) {
@@ -154,13 +124,13 @@ function compare(...values) {
154
124
  }
155
125
  function uniqueBy(values, key, label) {
156
126
  const seen = new Set;
157
- for (const value of values) {
158
- const id = key(value);
127
+ const keyed = values.map((value) => ({ id: key(value), value }));
128
+ for (const { id } of keyed) {
159
129
  if (seen.has(id))
160
130
  throw new Error(`duplicate ${label}: ${id}`);
161
131
  seen.add(id);
162
132
  }
163
- return [...values].sort((left, right) => key(left).localeCompare(key(right)));
133
+ return keyed.sort((left, right) => left.id.localeCompare(right.id)).map((entry) => entry.value);
164
134
  }
165
135
  function createScanObservation(input) {
166
136
  const roots = validateRoots(input.roots);
@@ -171,9 +141,7 @@ function createScanObservation(input) {
171
141
  symbols: [...new Map(file.symbols.map((symbol) => [
172
142
  compare(symbol.id, symbol.kind),
173
143
  symbol
174
- ])).values()].sort((left, right) => {
175
- return compare(left.id, left.kind).localeCompare(compare(right.id, right.kind));
176
- })
144
+ ]))].sort(([left], [right]) => left.localeCompare(right)).map(([, symbol]) => symbol)
177
145
  })), (file) => file.file, "file path");
178
146
  const filePaths = new Set(files.map((file) => file.file));
179
147
  for (const file of files) {
@@ -184,24 +152,46 @@ function createScanObservation(input) {
184
152
  throw new Error(`file references unknown root: ${root}`);
185
153
  }
186
154
  }
187
- for (const diagnostic of input.diagnostics)
188
- diagnosticLocation(diagnostic);
189
155
  return {
190
156
  schemaVersion: 1,
191
157
  scanner: input.scanner,
192
158
  roots,
193
159
  files,
194
160
  ...sourceUnits(input.sourceUnits, filePaths),
161
+ ...entryPoints(input.entryPoints, filePaths),
195
162
  ...operationEvidence(input, filePaths),
196
163
  ...httpEvidence(input.httpEndpoints, input.httpRequests, input.operations && new Set(input.operations.map((operation) => operation.id))),
197
- diagnostics: [...new Map(input.diagnostics.map((diagnostic) => [
198
- diagnosticKey(diagnostic),
199
- diagnostic
200
- ])).values()].sort((left, right) => {
201
- return diagnosticKey(left).localeCompare(diagnosticKey(right));
202
- })
164
+ diagnostics: normalizeScanDiagnostics(input.diagnostics)
203
165
  };
204
166
  }
167
+ function normalizeScanDiagnostics(diagnostics) {
168
+ for (const diagnostic of diagnostics)
169
+ diagnosticLocation(diagnostic);
170
+ return [...new Map(diagnostics.map((diagnostic) => [diagnosticKey(diagnostic), diagnostic]))].sort(([left], [right]) => left.localeCompare(right)).map(([, diagnostic]) => diagnostic);
171
+ }
172
+ function entryPoints(input, paths) {
173
+ if (input === undefined)
174
+ return {};
175
+ const entries = array(input, "entryPoints").map((value) => {
176
+ const entry = object(value, "entry point");
177
+ const files = [...new Set(array(entry.files, "entry point.files").map((file) => string(file, "entry point.file")))].sort();
178
+ for (const file of files) {
179
+ if (!paths.has(file))
180
+ throw new Error(`entry point references unknown file: ${file}`);
181
+ }
182
+ const file = string(entry.file, "entry point.file");
183
+ if (!files.includes(file))
184
+ throw new Error(`entry point must include its own source file: ${file}`);
185
+ return {
186
+ file,
187
+ declaration: string(entry.declaration, "entry point.declaration"),
188
+ name: string(entry.name, "entry point.name"),
189
+ files
190
+ };
191
+ });
192
+ const key = (entry) => compare(entry.file, entry.declaration, entry.name, ...entry.files);
193
+ return { entryPoints: [...new Map(entries.map((entry) => [key(entry), entry])).values()].sort((a, b) => key(a).localeCompare(key(b))) };
194
+ }
205
195
  function sourceUnits(input, paths) {
206
196
  if (input === undefined)
207
197
  return {};
@@ -264,6 +254,7 @@ function parseScanObservation(source) {
264
254
  ...parseOperations(value),
265
255
  httpEndpoints: value.httpEndpoints,
266
256
  httpRequests: value.httpRequests,
257
+ entryPoints: value.entryPoints,
267
258
  ...sourceUnits(value.sourceUnits, new Set(array(value.files, "files").map((entry) => string(object(entry, "file").file, "file.file")))),
268
259
  roots: array(value.roots, "roots").map((entry) => {
269
260
  const root = object(entry, "root");
@@ -376,10 +367,14 @@ function operationTokens(operation) {
376
367
  const endLine = Number(operation.endLine);
377
368
  if (endLine < startLine)
378
369
  throw new Error("operation.endLine must be at or after startLine");
370
+ const tokens = array(operation.tokens, "operation.tokens");
371
+ tokens.forEach((token) => {
372
+ string(token, "operation.token");
373
+ });
379
374
  return {
380
375
  startLine,
381
376
  endLine,
382
- tokens: array(operation.tokens, "operation.tokens").map((token) => string(token, "operation.token"))
377
+ tokens
383
378
  };
384
379
  }
385
380
  function validateOperationTokens(operation) {
@@ -410,7 +405,7 @@ function sourcePosition(position) {
410
405
 
411
406
  // plugins/scanners/observations.ts
412
407
  function relocateObservation(observation, directory) {
413
- const file = (value) => path2.posix.normalize(path2.posix.join(directory, value));
408
+ const file = (value) => path.posix.normalize(path.posix.join(directory, value));
414
409
  return {
415
410
  ...observation,
416
411
  roots: observation.roots.map((root) => ({ ...root, ...root.file ? { file: file(root.file) } : {} })),
@@ -418,11 +413,20 @@ function relocateObservation(observation, directory) {
418
413
  ...observation.sourceUnits === undefined ? {} : {
419
414
  sourceUnits: observation.sourceUnits.map((unit) => ({ primary: file(unit.primary), files: unit.files.map(file) }))
420
415
  },
416
+ ...observation.entryPoints === undefined ? {} : {
417
+ entryPoints: observation.entryPoints.map((entry) => ({
418
+ ...entry,
419
+ file: file(entry.file),
420
+ declaration: file(entry.declaration),
421
+ files: entry.files.map(file)
422
+ }))
423
+ },
421
424
  operations: observation.operations?.map((operation) => ({ ...operation, file: file(operation.file) })),
422
425
  invocations: observation.invocations?.map((call) => ({
423
426
  ...call,
424
427
  ...call.binding ? { binding: { ...call.binding, file: file(call.binding.file) } } : {}
425
428
  })),
429
+ httpEndpoints: observation.httpEndpoints?.map((endpoint) => endpoint.order === undefined ? endpoint : { ...endpoint, order: { ...endpoint.order, application: file(endpoint.order.application) } }),
426
430
  diagnostics: observation.diagnostics.map((item) => ({ ...item, ...item.file ? { file: file(item.file) } : {} }))
427
431
  };
428
432
  }
@@ -435,6 +439,7 @@ function combineObservations(parts) {
435
439
  const invocations = [];
436
440
  const diagnostics = [];
437
441
  const sourceUnits = [];
442
+ const entryPoints = [];
438
443
  const httpEndpoints = [];
439
444
  const httpRequests = [];
440
445
  for (const { key, observation } of parts) {
@@ -452,6 +457,7 @@ function combineObservations(parts) {
452
457
  httpRequests.push(...(observation.httpRequests ?? []).map((request) => ({ ...request, operation: id(request.operation) })));
453
458
  diagnostics.push(...observation.diagnostics);
454
459
  sourceUnits.push(...observation.sourceUnits ?? []);
460
+ entryPoints.push(...observation.entryPoints ?? []);
455
461
  }
456
462
  return createScanObservation({
457
463
  scanner: parts[0].observation.scanner,
@@ -461,29 +467,36 @@ function combineObservations(parts) {
461
467
  invocations,
462
468
  diagnostics,
463
469
  ...parts.some((part) => part.observation.sourceUnits !== undefined) ? { sourceUnits } : {},
470
+ ...parts.some((part) => part.observation.entryPoints !== undefined) ? { entryPoints } : {},
464
471
  ...parts.some((part) => part.observation.httpEndpoints !== undefined) ? { httpEndpoints } : {},
465
472
  ...parts.some((part) => part.observation.httpRequests !== undefined) ? { httpRequests } : {}
466
473
  });
467
474
  }
468
475
 
469
476
  // plugins/scanners/project-scanner.ts
477
+ function within(root, project, files) {
478
+ const key = path2.relative(root, project).split(path2.sep).join("/");
479
+ if (key === "")
480
+ return { key, files: [...files] };
481
+ return { key, files: files.flatMap((file) => file.startsWith(`${key}/`) ? [file.slice(key.length + 1)] : []) };
482
+ }
470
483
  function projectScanner(scanner, select) {
471
484
  return {
472
485
  ...scanner,
473
- checkReadiness: async (root, settings = {}) => {
474
- const projects = await select(root, settings);
486
+ checkReadiness: async (root, settings, files) => {
487
+ const projects = await select(root, settings, files);
475
488
  if (!projects.length)
476
489
  throw new Error(`${scanner.id}: No supported project declaration was found.`);
477
490
  for (const project of projects)
478
- await scanner.checkReadiness?.(project, settings);
491
+ await scanner.checkReadiness?.(project, settings, within(root, project, files).files);
479
492
  },
480
- scan: async (root, settings = {}) => {
493
+ scan: async (root, settings, files) => {
481
494
  const parts = [];
482
- for (const project of await select(root, settings)) {
483
- const observation = await scanner.scan(project, settings);
484
- const key = path3.relative(root, project).split(path3.sep).join("/");
495
+ for (const project of await select(root, settings, files)) {
496
+ const local = within(root, project, files);
497
+ const observation = await scanner.scan(project, settings, local.files);
485
498
  if (observation)
486
- parts.push({ key, observation: relocateObservation(observation, key) });
499
+ parts.push({ key: local.key, observation: relocateObservation(observation, local.key) });
487
500
  }
488
501
  return combineObservations(parts);
489
502
  }
@@ -491,14 +504,42 @@ function projectScanner(scanner, select) {
491
504
  }
492
505
 
493
506
  // plugins/scanners/go/src/adapter.ts
494
- import { execFile as execFile2 } from "child_process";
507
+ import { execFile } from "child_process";
495
508
  import { access, realpath } from "fs/promises";
496
- import path4 from "path";
497
509
  import { fileURLToPath } from "url";
510
+
511
+ // plugins/scanners/go/src/sources.ts
512
+ import path3 from "path";
513
+ function ignoredByGo(file) {
514
+ return file.endsWith("_test.go") || file.split("/").some((part) => part === "testdata" || part.startsWith(".") || part.startsWith("_"));
515
+ }
516
+ function goFiles(files) {
517
+ const read = files.filter((file) => !ignoredByGo(file));
518
+ return {
519
+ modules: read.filter((file) => path3.posix.basename(file) === "go.mod").map((file) => path3.posix.dirname(file)),
520
+ sources: read.filter((file) => file.endsWith(".go"))
521
+ };
522
+ }
523
+ function moduleOf(file, modules) {
524
+ return modules.filter((module) => module === "." || file.startsWith(`${module}/`)).sort((left, right) => right.length - left.length)[0];
525
+ }
526
+ function goModules(files) {
527
+ return goFiles(files).modules;
528
+ }
529
+ function goSources(candidates) {
530
+ const { modules, sources } = goFiles(candidates);
531
+ return sources.filter((file) => moduleOf(file, modules) !== undefined);
532
+ }
533
+ function moduleSources(files) {
534
+ const { modules, sources } = goFiles(files);
535
+ return sources.filter((file) => moduleOf(file, modules) === ".");
536
+ }
537
+
538
+ // plugins/scanners/go/src/adapter.ts
498
539
  var packagedWorker = fileURLToPath(new URL(`../dist/${process.platform}-${process.arch}/worker${process.platform === "win32" ? ".exe" : ""}`, import.meta.url));
499
540
  function run(command, args, root, { env = process.env, input } = {}) {
500
541
  return new Promise((resolve, reject) => {
501
- const child = execFile2(command, args, {
542
+ const child = execFile(command, args, {
502
543
  cwd: root,
503
544
  env,
504
545
  encoding: "utf8",
@@ -524,41 +565,35 @@ async function workerPath(options) {
524
565
  }
525
566
  return worker;
526
567
  }
527
- async function checkGoReadiness(repositoryRoot, options = {}) {
528
- const root = await realpath(repositoryRoot);
568
+ async function checkGoReadiness(moduleRoot, options = {}) {
569
+ const root = await realpath(moduleRoot);
529
570
  const worker = await workerPath(options);
530
- await access(path4.join(root, "go.mod"));
531
571
  return { root, worker };
532
572
  }
533
573
  async function readGoCodeStructure(repositoryRoot, references, options = {}) {
534
574
  const worker = await workerPath(options);
535
575
  return JSON.parse(await run(worker, ["outline", repositoryRoot], repositoryRoot, { input: JSON.stringify(references) }));
536
576
  }
537
- async function scanGoSource(repositoryRoot, options = {}) {
538
- const { root, worker } = await checkGoReadiness(repositoryRoot, options);
577
+ async function scanGoSource(moduleRoot, options, files) {
578
+ const { root, worker } = await checkGoReadiness(moduleRoot, options);
539
579
  try {
540
- return parseScanObservation(await run(worker, [root], root));
580
+ return parseScanObservation(await run(worker, [root], root, { input: JSON.stringify(moduleSources(files)) }));
541
581
  } catch (error) {
542
582
  throw new Error(`GO_SOURCE_INVALID: No observation was produced. Check Go source syntax and module declarations. ${error}`);
543
583
  }
544
584
  }
545
585
 
546
586
  // plugins/scanners/go/src/index.ts
547
- async function goSources(root) {
548
- const modules = (await projectFiles(root, (file) => path5.posix.basename(file) === "go.mod")).map((file) => path5.posix.dirname(file));
549
- return projectFiles(root, (file) => file.endsWith(".go") && !file.endsWith("_test.go") && !file.split("/").some((part) => part === "testdata" || part.startsWith(".")) && modules.some((module) => module === "." || file.startsWith(`${module}/`)));
550
- }
551
587
  var scanner = {
552
588
  id: "go",
553
- watch: { include: ["**/*.go", "**/go.mod", "**/go.sum", "**/go.work"], exclude: [] },
554
- listSourceFiles: goSources,
589
+ listSourceFiles: async (_root, _settings, candidates) => goSources(candidates),
555
590
  checkReadiness: async (root) => {
556
591
  await checkGoReadiness(root);
557
592
  },
558
593
  readCodeStructure: readGoCodeStructure,
559
594
  scan: scanGoSource
560
595
  };
561
- var src_default = projectScanner(scanner, async (root) => (await projectFiles(root, (file) => path5.posix.basename(file) === "go.mod")).map((file) => path5.dirname(path5.join(root, file))));
596
+ var src_default = projectScanner(scanner, async (root, _settings, files) => goModules(files).map((module) => path4.join(root, module)));
562
597
  export {
563
598
  src_default as default
564
599
  };