diff --git a/.changeset/tame-walls-rush.md b/.changeset/tame-walls-rush.md new file mode 100644 index 00000000..c4a62ac4 --- /dev/null +++ b/.changeset/tame-walls-rush.md @@ -0,0 +1,6 @@ +--- +"@nodesecure/scanner": minor +"@nodesecure/tarball": minor +--- + +feat: add path and file number for tarball stats diff --git a/workspaces/scanner/docs/logger.md b/workspaces/scanner/docs/logger.md index 5cd2a8dd..57a013f4 100644 --- a/workspaces/scanner/docs/logger.md +++ b/workspaces/scanner/docs/logger.md @@ -88,6 +88,11 @@ logger.on("stat", (stat: ApiStats) => { console.log(`API call: ${stat.name}`); console.log(`Duration: ${stat.executionTime}ms`); console.log(`Start at: ${stat.startedAt}`); + // tarball specific stats + if(stat.tarball){ + console.log(`path: ${stat.tarball.path}`); + console.log(`files count: ${stat.tarball.filesCount}`); + } }); ### depWalkerFinished diff --git a/workspaces/scanner/src/class/StatsCollector.class.ts b/workspaces/scanner/src/class/StatsCollector.class.ts index 6206efc8..2dad43f3 100644 --- a/workspaces/scanner/src/class/StatsCollector.class.ts +++ b/workspaces/scanner/src/class/StatsCollector.class.ts @@ -18,6 +18,11 @@ export type Options = { isVerbose: boolean; }; +export type OnSuccess any> = ( + res: Awaited>, + stat: ApiStats +) => void; + export class StatsCollector { #logger: EventEmitter; #dateProvider: DateProvider; @@ -34,14 +39,26 @@ export class StatsCollector { this.#isVerbose = options.isVerbose; } - track any>(name: string, phase: string, fn: T): ReturnType { + track any>(options: { + name: string; + phase: string; + fn: T; + onSuccess?: OnSuccess; + }): ReturnType { + const { name, phase, fn, onSuccess } = options; const startedAt = this.#dateProvider.now(); try { const result = fn(); if (result instanceof Promise) { return result .then((res: ReturnType) => { - this.#addApiStatVerbose(name, startedAt, this.#calcExecutionTime(startedAt)); + this.#addApiStatVerbose({ + name, + startedAt, + executionTime: this.#calcExecutionTime(startedAt), + result: res, + onSuccess + }); return res; }) @@ -59,7 +76,13 @@ export class StatsCollector { }) as ReturnType; } - this.#addApiStatVerbose(name, startedAt, this.#calcExecutionTime(startedAt)); + this.#addApiStatVerbose({ + name, + startedAt, + executionTime: this.#calcExecutionTime(startedAt), + result, + onSuccess + }); return result; } @@ -81,12 +104,21 @@ export class StatsCollector { return this.#dateProvider.now() - startedAt; } - #addApiStatVerbose(name: string, startedAt: number, executionTime: number) { + #addApiStatVerbose any>({ name, startedAt, executionTime, result, onSuccess }: { + name: string; + startedAt: number; + executionTime: number; + onSuccess?: OnSuccess; + result: ReturnType; + }) { const stat = { name, startedAt, executionTime }; + if (onSuccess) { + onSuccess(result as Awaited>, stat); + } this.#apiCalls.push(stat); if (this.#isVerbose) { this.#logger.emit("stat", stat); diff --git a/workspaces/scanner/src/class/TarballScanner.class.ts b/workspaces/scanner/src/class/TarballScanner.class.ts index e10835e0..a0b49650 100644 --- a/workspaces/scanner/src/class/TarballScanner.class.ts +++ b/workspaces/scanner/src/class/TarballScanner.class.ts @@ -107,17 +107,23 @@ export class TarballScanner { const mama = await this.#extract(spec, registry); - const result = await this.#statsCollector.track( - `tarball.scanDirOrArchive ${spec}`, - "tarball-scan", - () => this.#workerPool!.scan({ + const result = await this.#statsCollector.track({ + name: `tarball.scanDirOrArchive ${spec}`, + phase: "tarball-scan", + fn: () => this.#workerPool!.scan({ location: mama.location!, astAnalyserOptions: { optionalWarnings: hasLocation }, collectableTypes: this.#collectableTypes - }) - ); + }), + onSuccess: (result, stat) => { + stat.tarball = { + path: result.path, + filesCount: result.composition.files.length + }; + } + }); this.#applyResult(ref, result); this.#mergeCollectables(result.collectables); @@ -149,16 +155,23 @@ export class TarballScanner { }) ); - await this.#statsCollector.track( - `tarball.scanDirOrArchive ${spec}`, - "tarball-scan", - () => scanDirOrArchive(mama, ref, { + await this.#statsCollector.track({ + name: `tarball.scanDirOrArchive ${spec}`, + phase: "tarball-scan", + fn: () => scanDirOrArchive(mama, ref, { astAnalyserOptions: { optionalWarnings: hasLocation, collectables: this.#collectables } - }) - ); + }), + onSuccess: (_, stat) => { + stat.tarball = { + path: ref.path, + filesCount: ref.composition.files.length + }; + delete ref.path; + } + }); } async #extract( diff --git a/workspaces/scanner/src/depWalker.ts b/workspaces/scanner/src/depWalker.ts index 366aa9a3..c151cad7 100644 --- a/workspaces/scanner/src/depWalker.ts +++ b/workspaces/scanner/src/depWalker.ts @@ -155,14 +155,14 @@ export async function depWalker( const pacoteProvider: PacoteProvider = { async extract(spec, dest, opts): Promise { - await statsCollector.track( - `pacote.extract ${spec}`, - "tarball-scan", - () => pacote.extract(spec, dest, { + await statsCollector.track({ + name: `pacote.extract ${spec}`, + phase: "tarball-scan", + fn: () => pacote.extract(spec, dest, { ...opts, ...pacoteScopedConfig }) - ); + }); } }; @@ -191,32 +191,37 @@ export async function depWalker( registry, providers: { pacote: { - manifest: (spec, opts) => statsCollector.track(`pacote.manifest ${spec}`, "tree-walk", () => pacote.manifest(spec, - { ...opts, ...pacoteScopedConfig })), - packument: (spec, opts) => statsCollector.track(`pacote.packument ${spec}`, - "tree-walk", - () => pacote.packument(spec, { ...opts, ...pacoteScopedConfig })) + manifest: (spec, opts) => statsCollector.track({ + name: `pacote.manifest ${spec}`, + phase: "tree-walk", fn: () => pacote.manifest(spec, + { ...opts, ...pacoteScopedConfig }) + }), + packument: (spec, opts) => statsCollector.track({ + name: `pacote.packument ${spec}`, + phase: "tree-walk", + fn: () => pacote.packument(spec, { ...opts, ...pacoteScopedConfig }) + }) } } }); const npmApiClient: NpmApiClient = { - packument: (name, opts) => statsCollector.track( - `npmRegistrySDK.packument ${name}`, - "metadata-fetch", - () => npmRegistrySDK.packument(name, opts) - ), - - packumentVersion: (name, version, opts) => statsCollector.track( - `npmRegistrySDK.packumentVersion ${name}@${version}`, - "metadata-fetch", - () => npmRegistrySDK.packumentVersion(name, version, opts) - ), - - org: (namespace) => statsCollector.track( - `npmRegistrySDK.org ${namespace}`, - "metadata-fetch", - () => npmRegistrySDK.org(namespace) - ) + packument: (name, opts) => statsCollector.track({ + name: `npmRegistrySDK.packument ${name}`, + phase: "metadata-fetch", + fn: () => npmRegistrySDK.packument(name, opts) + }), + + packumentVersion: (name, version, opts) => statsCollector.track({ + name: `npmRegistrySDK.packumentVersion ${name}@${version}`, + phase: "metadata-fetch", + fn: () => npmRegistrySDK.packumentVersion(name, version, opts) + }), + + org: (namespace) => statsCollector.track({ + name: `npmRegistrySDK.org ${namespace}`, + phase: "metadata-fetch", + fn: () => npmRegistrySDK.org(namespace) + }) }; logger diff --git a/workspaces/scanner/src/types.ts b/workspaces/scanner/src/types.ts index da98425b..1529c3fb 100644 --- a/workspaces/scanner/src/types.ts +++ b/workspaces/scanner/src/types.ts @@ -6,6 +6,7 @@ import type { PackageModuleType } from "@nodesecure/mama"; import type { SpdxFileLicenseConformance } from "@nodesecure/conformance"; import type { IlluminatedContact } from "@nodesecure/contact"; import type { Contact, Dist } from "@nodesecure/npm-types"; +import type { Path } from "@nodesecure/tarball"; export type Maintainer = Contact & { /** @@ -198,6 +199,13 @@ export type ApiStats = { * Name of the api call */ name: string; + /** + * Tarball specific stats + */ + tarball?: { + path: Path; + filesCount: number; + }; }; export type Error = { diff --git a/workspaces/scanner/test/StatsCollector.spec.ts b/workspaces/scanner/test/StatsCollector.spec.ts index 91a70652..b3a5b59c 100644 --- a/workspaces/scanner/test/StatsCollector.spec.ts +++ b/workspaces/scanner/test/StatsCollector.spec.ts @@ -9,6 +9,7 @@ import * as npmRegistrySDK from "@nodesecure/npm-registry-sdk"; // Import Internal Dependencies import type { DateProvider } from "../src/class/DateProvider.class.ts"; import { type LoggerEventsMap } from "../src/class/logger.class.ts"; +import type { ApiStats } from "../src/types.ts"; import { StatsCollector } from "../src/class/StatsCollector.class.ts"; class FakeLogger extends EventEmitter { @@ -57,9 +58,11 @@ describe("StatsCollectors", () => { isVerbose: false }); assert.throws(() => { - statsCollector.track("api/test/1", "phase-1", () => { - dateProvider.setNow(1658512001000); - throw new Error("oh no!"); + statsCollector.track({ + name: "api/test/1", phase: "phase-1", fn: () => { + dateProvider.setNow(1658512001000); + throw new Error("oh no!"); + } }); }); @@ -76,28 +79,33 @@ describe("StatsCollectors", () => { assert.strictEqual(fakeLogger.stats.length, 0); }); - it("should be able to track the start and execution time of external api call", async() => { + it("should be able to track the start and execution time of external api call", async(t) => { let hasFnOneBeenCalled = false; let hasFnTwoBeenCalled = false; const dateProvider = new FakeDateProvider(); dateProvider.setNow(1658512000000); const statsCollector = new StatsCollector({ logger: fakeLogger, dateProvider }, { isVerbose: false }); + const onSuccessMock = t.mock.fn<(res: number, stat: ApiStats) => void>(); dateProvider.setNow(1658512001001); - const promise = statsCollector.track("api/test/1", "phase-1", () => { - hasFnOneBeenCalled = true; + const promise = statsCollector.track({ + name: "api/test/1", phase: "phase-1", fn: () => { + hasFnOneBeenCalled = true; - return Promise.resolve(1); + return Promise.resolve(1); + }, onSuccess: onSuccessMock as any }); dateProvider.setNow(1658512002000); const promiseResult = await promise; dateProvider.setNow(1658512003000); - const fnResult = statsCollector.track("api/test/2", "phase-2", () => { - hasFnTwoBeenCalled = true; - dateProvider.setNow(1658512004000); + const fnResult = statsCollector.track({ + name: "api/test/2", phase: "phase-2", fn: () => { + hasFnTwoBeenCalled = true; + dateProvider.setNow(1658512004000); - return null; + return null; + }, onSuccess: onSuccessMock as any }); dateProvider.setNow(1658512005000); const { apiCalls, apiCallsCount } = statsCollector.getStats(); @@ -118,6 +126,17 @@ describe("StatsCollectors", () => { executionTime: 1000 } ]); + assert.strictEqual(onSuccessMock.mock.callCount(), 2); + assert.deepEqual(onSuccessMock.mock.calls[0].arguments, [1, { + name: "api/test/1", + startedAt: 1658512001001, + executionTime: 999 + }]); + assert.deepEqual(onSuccessMock.mock.calls[1].arguments, [null, { + name: "api/test/2", + startedAt: 1658512003000, + executionTime: 1000 + }]); assert.strictEqual(fakeLogger.stats.length, 0); }); }); @@ -137,9 +156,11 @@ describe("StatsCollectors", () => { dateProvider.setNow(1658512000000); const statsCollector = new StatsCollector({ logger: fakeLogger, dateProvider }, { isVerbose: false }); await assert.rejects(async() => { - await statsCollector.track("api/test/1", "phase-async", async() => { - dateProvider.setNow(1658512001000); - throw new Error("async oh no!"); + await statsCollector.track({ + name: "api/test/1", phase: "phase-async", fn: async() => { + dateProvider.setNow(1658512001000); + throw new Error("async oh no!"); + } }); }); const { errors, errorCount } = statsCollector.getStats(); @@ -157,9 +178,11 @@ describe("StatsCollectors", () => { dateProvider.setNow(1658512000000); const statsCollector = new StatsCollector({ logger: fakeLogger, dateProvider }, { isVerbose: true }); assert.throws(() => { - statsCollector.track("api/test/1", "phase-1", () => { - dateProvider.setNow(1658512001000); - throw new Error("oh no!"); + statsCollector.track({ + name: "api/test/1", phase: "phase-1", fn: () => { + dateProvider.setNow(1658512001000); + throw new Error("oh no!"); + } }); }); const { errors, errorCount } = statsCollector.getStats(); @@ -186,10 +209,11 @@ describe("StatsCollectors", () => { dateProvider.setNow(1658512000000); const statsCollector = new StatsCollector({ logger: fakeLogger, dateProvider }, { isVerbose: true }); assert.throws(() => { - statsCollector.track("api/test/1", "phase-1", () => { - dateProvider.setNow(1658512001000); - // eslint-disable-next-line no-throw-literal - throw null; + statsCollector.track({ + name: "api/test/1", phase: "phase-1", fn: () => { + dateProvider.setNow(1658512001000); + throw new Error("oh no!"); + } }); }); const { errors, errorCount } = statsCollector.getStats(); @@ -213,10 +237,12 @@ describe("StatsCollectors", () => { const dateProvider = new FakeDateProvider(); dateProvider.setNow(1658512000000); const statsCollector = new StatsCollector({ logger: fakeLogger, dateProvider }, { isVerbose: true }); - await statsCollector.track("api/test/1", "phase-1", async() => { - dateProvider.setNow(1658512001000); + await statsCollector.track({ + name: "api/test/1", phase: "phase-1", fn: async() => { + dateProvider.setNow(1658512001000); - return Promise.resolve(42); + return Promise.resolve(42); + } }); const { errors, errorCount } = statsCollector.getStats(); assert.strictEqual(errorCount, 0); @@ -230,9 +256,11 @@ describe("StatsCollectors", () => { dateProvider.setNow(1658512000000); const statsCollector = new StatsCollector({ logger: fakeLogger, dateProvider }, { isVerbose: true }); await assert.rejects(async() => { - await statsCollector.track("api/test/1", "phase-async", async() => { - dateProvider.setNow(1658512001000); - throw new Error("async oh no!"); + await statsCollector.track({ + name: "api/test/1", phase: "phase-async", fn: async() => { + dateProvider.setNow(1658512001000); + throw new Error("async oh no!"); + } }); }); const { errors, errorCount } = statsCollector.getStats(); @@ -259,10 +287,12 @@ describe("StatsCollectors", () => { dateProvider.setNow(1658512000000); const statsCollector = new StatsCollector({ logger: fakeLogger, dateProvider }, { isVerbose: true }); await assert.rejects(async() => { - await statsCollector.track("api/test/1", "phase-1", async() => { - dateProvider.setNow(1658512001000); - // eslint-disable-next-line no-throw-literal - throw "string error"; + await statsCollector.track({ + name: "api/test/1", phase: "phase-1", fn: async() => { + dateProvider.setNow(1658512001000); + // eslint-disable-next-line no-throw-literal + throw "string error"; + } }); }); const { errors, errorCount } = statsCollector.getStats(); @@ -287,10 +317,12 @@ describe("StatsCollectors", () => { dateProvider.setNow(1658512000000); const statsCollector = new StatsCollector({ logger: fakeLogger, dateProvider }, { isVerbose: true }); await assert.rejects(async() => { - await statsCollector.track("api/test/1", "phase-1", async() => { - dateProvider.setNow(1658512001000); + await statsCollector.track({ + name: "api/test/1", phase: "phase-1", fn: async() => { + dateProvider.setNow(1658512001000); - return npmRegistrySDK.packument("does-not-exist"); + return npmRegistrySDK.packument("does-not-exist"); + } }); }); const { errors, errorCount } = statsCollector.getStats(); @@ -319,10 +351,12 @@ describe("StatsCollectors", () => { const dateProvider = new FakeDateProvider(); dateProvider.setNow(1658512000000); const statsCollector = new StatsCollector({ logger: fakeLogger, dateProvider }, { isVerbose: true }); - statsCollector.track("api/test/1", "phase-1", () => { - dateProvider.setNow(1658512001000); + statsCollector.track({ + name: "api/test/1", phase: "phase-1", fn: () => { + dateProvider.setNow(1658512001000); - return 42; + return 42; + } }); assert.strictEqual(fakeLogger.stats.length, 1); assert.deepStrictEqual(fakeLogger.stats[0], { @@ -336,10 +370,12 @@ describe("StatsCollectors", () => { const dateProvider = new FakeDateProvider(); dateProvider.setNow(1658512000000); const statsCollector = new StatsCollector({ logger: fakeLogger, dateProvider }, { isVerbose: true }); - await statsCollector.track("api/test/1", "phase-1", async() => { - dateProvider.setNow(1658512001000); + await statsCollector.track({ + name: "api/test/1", phase: "phase-1", fn: async() => { + dateProvider.setNow(1658512001000); - return Promise.resolve(42); + return Promise.resolve(42); + } }); assert.strictEqual(fakeLogger.stats.length, 1); assert.deepStrictEqual(fakeLogger.stats[0], { @@ -354,9 +390,11 @@ describe("StatsCollectors", () => { dateProvider.setNow(1658512000000); const statsCollector = new StatsCollector({ logger: fakeLogger, dateProvider }, { isVerbose: true }); assert.throws(() => { - statsCollector.track("api/test/1", "phase-1", () => { - dateProvider.setNow(1658512001000); - throw new Error("sync error!"); + statsCollector.track({ + name: "api/test/1", phase: "phase-1", fn: () => { + dateProvider.setNow(1658512001000); + throw new Error("sync error!"); + } }); }); assert.strictEqual(fakeLogger.stats.length, 0); @@ -367,9 +405,12 @@ describe("StatsCollectors", () => { dateProvider.setNow(1658512000000); const statsCollector = new StatsCollector({ logger: fakeLogger, dateProvider }, { isVerbose: true }); await assert.rejects(async() => { - await statsCollector.track("api/test/1", "phase-1", async() => { - dateProvider.setNow(1658512001000); - throw new Error("async error!"); + await statsCollector.track({ + name: "api/test/1", phase: "phase-1", fn: async() => { + dateProvider.setNow(1658512001000); + + return npmRegistrySDK.packument("does-not-exist"); + } }); }); assert.strictEqual(fakeLogger.stats.length, 0); diff --git a/workspaces/scanner/test/depWalker.spec.ts b/workspaces/scanner/test/depWalker.spec.ts index e236cca7..c063c24c 100644 --- a/workspaces/scanner/test/depWalker.spec.ts +++ b/workspaces/scanner/test/depWalker.spec.ts @@ -185,8 +185,28 @@ describe("depWalker", { concurrency: 2 }, () => { assert.strictEqual(metadata.errorCount, 2); assert.strictEqual(metadata.errors.length, 2); assert.strictEqual(statsCount(), 40); + assert.deepEqual(metadata.apiCalls.flatMap(({ name, tarball }) => (name.startsWith("tarball.scanDirOrArchive") ? + [tarball] : [])).sort(byFilesCount), + [{ path: "All", filesCount: 37 }, + { path: "EntryFileAnalyser", filesCount: 5 }, + { path: "EntryFileAnalyser", filesCount: 11 }, + { path: "EntryFileAnalyser", filesCount: 5 }, + { path: "NONE", filesCount: 3 }, + { path: "EntryFileAnalyser", filesCount: 6 }, + { path: "EntryFileAnalyser", filesCount: 17 }, + { path: "EntryFileAnalyser", filesCount: 47 }, + { path: "EntryFileAnalyser", filesCount: 67 }, + { path: "EntryFileAnalyser", filesCount: 210 }].sort(byFilesCount)); }); + function byFilesCount(a: T, b: T) { + if (a === undefined || b === undefined) { + return 0; + } + + return a.filesCount - b.filesCount; + } + describe("typo-squatting", () => { it("should emit a global warning when a location is provided", { skip }, async(t) => { Vulnera.setStrategy(Vulnera.strategies.GITHUB_ADVISORY); diff --git a/workspaces/tarball/src/class/SourceCodeScanner.class.ts b/workspaces/tarball/src/class/SourceCodeScanner.class.ts index 781c2002..cd6e9383 100644 --- a/workspaces/tarball/src/class/SourceCodeScanner.class.ts +++ b/workspaces/tarball/src/class/SourceCodeScanner.class.ts @@ -14,7 +14,10 @@ import { type LocatedManifestManager } from "@nodesecure/mama"; +export type Path = "NONE" | "EntryFileAnalyser" | "All" | "Both"; + export interface SourceCodeAggregator { + path: Path; readonly consumed: boolean; push(report: ReportOnFile & { file: string; }): void; @@ -33,6 +36,7 @@ export interface SourceCodeEntries { export class SourceCodeReport implements SourceCodeAggregator { #isConsumed = false; + path: Path = "NONE"; warnings: Warning[] = []; dependencies: Record< @@ -118,6 +122,8 @@ export class SourceCodeScanner< report: T, entries: SourceCodeEntries ): Promise { + report.path = "EntryFileAnalyser"; + const { location } = this.manifest; const efa = new EntryFilesAnalyser({ @@ -155,6 +161,7 @@ export class SourceCodeScanner< if (sourceFiles.length === 0) { return report; } + report.path = report.path === "EntryFileAnalyser" ? "Both" : "All"; const { location, diff --git a/workspaces/tarball/src/tarball.ts b/workspaces/tarball/src/tarball.ts index a205d5f3..b837edbf 100644 --- a/workspaces/tarball/src/tarball.ts +++ b/workspaces/tarball/src/tarball.ts @@ -31,6 +31,7 @@ import type { ScanResultPayload, DependencyRef } from "./types.ts"; +import type { Path } from "./class/SourceCodeScanner.class.ts"; export interface ScanOptions { astAnalyserOptions?: AstAnalyserOptions; @@ -102,7 +103,8 @@ export async function scanPackageCore( required_nodejs: dependencies.nodeJs, required_thirdparty: dependencies.thirdparty, required_subpath: dependencies.subpathImports - } + }, + path: code.path }; } @@ -130,6 +132,7 @@ export async function scanDirOrArchive( ref.composition.required_nodejs = result.composition.required_nodejs; ref.composition.required_thirdparty = result.composition.required_thirdparty; ref.composition.required_subpath = result.composition.required_subpath; + ref.path = result.path; const flags = result.flags.filter((flag) => flag !== "hasWarnings" || !ref.flags.includes("hasWarnings")); ref.flags.push(...flags); @@ -154,6 +157,7 @@ export interface ScannedPackageResult { dependencies: Record>; warnings: Warning[]; }; + path: Path; } export async function scanPackage( @@ -195,7 +199,8 @@ export async function scanPackage( ast: { dependencies: dependencySet.dependencies, warnings - } + }, + path: code.path }; } diff --git a/workspaces/tarball/src/types.ts b/workspaces/tarball/src/types.ts index d003bb66..ee5a2a09 100644 --- a/workspaces/tarball/src/types.ts +++ b/workspaces/tarball/src/types.ts @@ -7,6 +7,11 @@ import type { PackageModuleType } from "@nodesecure/mama"; +// Import Internal Dependencies +import type { Path } from "./class/SourceCodeScanner.class.ts"; + +export type { Path } from "./class/SourceCodeScanner.class.ts"; + export interface Composition { extensions: string[]; files: string[]; @@ -38,6 +43,7 @@ export interface ScanResultPayload { * Only present when `collectableTypes` was specified in the WorkerTask. */ collectables?: CollectableSetData[]; + path: Path; } export interface DependencyRef { @@ -59,4 +65,5 @@ export interface DependencyRef { gitUrl: string | null; alias: Record; composition: Composition; + path?: Path; } diff --git a/workspaces/tarball/test/SourceCodeScanner.spec.ts b/workspaces/tarball/test/SourceCodeScanner.spec.ts index c589d2a3..1279a996 100644 --- a/workspaces/tarball/test/SourceCodeScanner.spec.ts +++ b/workspaces/tarball/test/SourceCodeScanner.spec.ts @@ -30,6 +30,7 @@ describe("SourceCodeScanner", () => { const report = await scanner.iterate({ manifest: [], javascript: [] }); assert.strictEqual(report.consumed, false); + assert.strictEqual(report.path, "NONE"); }); test("iterate() should return empty report if we provide a manifest that doesn't exist and zero JavaScript files", async() => { @@ -71,7 +72,45 @@ describe("SourceCodeScanner", () => { path.join("src", "index.js"), path.join("src", "foo.js") ].sort() + + ); + + assert.strictEqual(aggregator.path, "EntryFileAnalyser"); + }); + + test("should have a path of Both when we have entries + js files", async() => { + const mama = loadFixtureManifest("entryfiles"); + const aggregator = createAggregator(false); + + const scanner = new SourceCodeScanner(mama, { + reportInitiator: () => aggregator + }); + await scanner.iterate({ + manifest: [ + "src/index.js" + ], + javascript: [ + "src/alone.js" + ] + }); + + const { reports } = aggregator; + + const files = reports + .map((report) => path.normalize(report.file)) + .sort(); + + assert.deepEqual( + files, + [ + path.join("src", "index.js"), + path.join("src", "foo.js"), + path.join("src", "alone.js") + ].sort() + ); + + assert.strictEqual(aggregator.path, "Both"); }); test("iterate() should trace and report only provided JavaScript files", async() => { @@ -89,7 +128,7 @@ describe("SourceCodeScanner", () => { ] }); - const { reports } = aggregator; + const { reports, path: aggregatorPath } = aggregator; const files = reports .map((report) => path.normalize(report.file)) @@ -102,6 +141,8 @@ describe("SourceCodeScanner", () => { path.join("src", "deps.js") ].sort() ); + + assert.strictEqual(aggregatorPath, "All"); }); test("iterate() should report optional warning for synchronous I/O", async() => { @@ -232,7 +273,7 @@ function createAggregator( ): CustomAggregator { return { reports: [], - + path: "NONE", consumed, push(report) { this.reports.push(report); diff --git a/workspaces/tarball/test/fixtures/scanPackage/entryfiles/src/alone.js b/workspaces/tarball/test/fixtures/scanPackage/entryfiles/src/alone.js new file mode 100644 index 00000000..ba2ce3e8 --- /dev/null +++ b/workspaces/tarball/test/fixtures/scanPackage/entryfiles/src/alone.js @@ -0,0 +1 @@ +console.log("this file is not referenced in index.js on purpose for a test case"); diff --git a/workspaces/tarball/test/tarball/scanPackage.spec.ts b/workspaces/tarball/test/tarball/scanPackage.spec.ts index 193ece42..c7b8a396 100644 --- a/workspaces/tarball/test/tarball/scanPackage.spec.ts +++ b/workspaces/tarball/test/tarball/scanPackage.spec.ts @@ -75,6 +75,7 @@ test("scanPackage (caseone)", async() => { "kleur" ]); assert.ok(result.ast.dependencies["index.js"].fs.inTry); + assert.strictEqual(result.path, "All"); }); test("scanPackage should detect empty packages (only package.json)", async() => {