diff --git a/src/FileHandler.ts b/src/FileHandler.ts index 9f05e52..099306f 100644 --- a/src/FileHandler.ts +++ b/src/FileHandler.ts @@ -4,6 +4,8 @@ interface FileHandler { getModule( filename: string | null | undefined ): LicenseIdentifiedModule | null; + isBuildRoot(filename: string): boolean; + isInModuleDirectory(filename: string): boolean; } export { FileHandler }; diff --git a/src/LicenseIdentifiedModule.ts b/src/LicenseIdentifiedModule.ts index 8e5966d..be396b2 100644 --- a/src/LicenseIdentifiedModule.ts +++ b/src/LicenseIdentifiedModule.ts @@ -2,9 +2,9 @@ import { Module } from './Module'; import { PackageJson } from './PackageJson'; interface LicenseIdentifiedModule extends Module { - packageJson: PackageJson; - licenseId: string | null; - licenseText: string | null; + packageJson?: PackageJson; + licenseId?: string | null; + licenseText?: string | null; } export { LicenseIdentifiedModule }; diff --git a/src/PluginChunkReadHandler.ts b/src/PluginChunkReadHandler.ts index 436b930..25f7799 100644 --- a/src/PluginChunkReadHandler.ts +++ b/src/PluginChunkReadHandler.ts @@ -1,7 +1,8 @@ import { WebpackChunkHandler } from './WebpackChunkHandler'; import { WebpackChunk } from './WebpackChunk'; +import { WebpackChunkModule } from './WebpackChunkModule'; import { WebpackChunkModuleIterator } from './WebpackChunkModuleIterator'; -import { WebpackModuleFileIterator } from './WebpackModuleFileIterator'; +import { WebpackInnerModuleIterator } from './WebpackInnerModuleIterator'; import { FileHandler } from './FileHandler'; import { LicenseTypeIdentifier } from './LicenseTypeIdentifier'; import { FileSystem } from './FileSystem'; @@ -9,7 +10,6 @@ import { PackageJson } from './PackageJson'; import { LicenseTextReader } from './LicenseTextReader'; import { ModuleCache } from './ModuleCache'; import { LicensePolicy } from './LicensePolicy'; -import { Module } from './Module'; import { LicenseIdentifiedModule } from './LicenseIdentifiedModule'; import { WebpackCompilation } from './WebpackCompilation'; import { Logger } from './Logger'; @@ -17,7 +17,7 @@ import { WebpackStats } from './WebpackStats'; class PluginChunkReadHandler implements WebpackChunkHandler { private moduleIterator = new WebpackChunkModuleIterator(); - private fileIterator = new WebpackModuleFileIterator(require.resolve); + private innerModuleIterator = new WebpackInnerModuleIterator(require.resolve); constructor( private logger: Logger, @@ -34,15 +34,51 @@ class PluginChunkReadHandler implements WebpackChunkHandler { moduleCache: ModuleCache, stats: WebpackStats | undefined ) { - this.moduleIterator.iterateModules(compilation, chunk, stats, module => { - this.fileIterator.iterateFiles( - module, - (filename: string | null | undefined) => { - const module = this.fileHandler.getModule(filename); - this.processModule(compilation, chunk, moduleCache, module); - } - ); - }); + this.moduleIterator.iterateModules( + compilation, + chunk, + stats, + chunkModule => { + this.innerModuleIterator.iterateModules( + chunkModule, + (module: WebpackChunkModule) => { + const identifiedModule = + this.extractIdentifiedModule(module) || + this.fileHandler.getModule(module.resource); + if (identifiedModule) { + this.processModule( + compilation, + chunk, + moduleCache, + identifiedModule + ); + } + } + ); + } + ); + } + + private extractIdentifiedModule( + module: WebpackChunkModule + ): LicenseIdentifiedModule | undefined { + const resolved = module.resourceResolveData; + if (!resolved) return undefined; + const { + descriptionFileRoot: directory, + descriptionFileData: packageJson + } = resolved; + if ( + this.fileHandler.isInModuleDirectory(directory) && + !this.fileHandler.isBuildRoot(directory) + ) { + return { + directory, + packageJson, + name: packageJson.name + }; + } + return undefined; } private getPackageJson(directory: string): PackageJson { @@ -54,20 +90,17 @@ class PluginChunkReadHandler implements WebpackChunkHandler { compilation: WebpackCompilation, chunk: WebpackChunk, moduleCache: ModuleCache, - module: Module | LicenseIdentifiedModule | null + module: LicenseIdentifiedModule ) { - if (module && !moduleCache.alreadySeenForChunk(chunk.name, module.name)) { + if (!moduleCache.alreadySeenForChunk(chunk.name, module.name)) { const alreadyIncludedModule = moduleCache.getModule(module.name); if (alreadyIncludedModule !== null) { moduleCache.registerModule(chunk.name, alreadyIncludedModule); } else { // module not yet in cache - const packageJson: PackageJson = - (module).packageJson ?? - this.getPackageJson(module.directory); - const licenseType: - | string - | null = this.licenseTypeIdentifier.findLicenseIdentifier( + const packageJson = + module.packageJson ?? this.getPackageJson(module.directory); + const licenseType = this.licenseTypeIdentifier.findLicenseIdentifier( compilation, module.name, packageJson diff --git a/src/PluginFileHandler.ts b/src/PluginFileHandler.ts index 2de7ec8..789923b 100644 --- a/src/PluginFileHandler.ts +++ b/src/PluginFileHandler.ts @@ -20,28 +20,30 @@ class PluginFileHandler implements FileHandler { : this.cache[filename]; } + isInModuleDirectory(filename: string) { + if (this.modulesDirectories === null) return true; + if (filename === null || filename === undefined) return false; + let foundInModuleDirectory = false; + const resolvedPath = this.fileSystem.resolvePath(filename); + for (const modulesDirectory of this.modulesDirectories) { + if ( + resolvedPath.startsWith(this.fileSystem.resolvePath(modulesDirectory)) + ) { + foundInModuleDirectory = true; + } + } + return foundInModuleDirectory; + } + + isBuildRoot(filename: string) { + return this.buildRoot === filename; + } + private getModuleInternal( filename: string ): Partial | null { - if (filename === null || filename === undefined) { - return null; - } - - if (this.modulesDirectories !== null) { - let foundInModuleDirectory = false; - for (const modulesDirectory of this.modulesDirectories) { - if ( - this.fileSystem - .resolvePath(filename) - .startsWith(this.fileSystem.resolvePath(modulesDirectory)) - ) { - foundInModuleDirectory = true; - } - } - if (!foundInModuleDirectory) { - return null; - } - } + if (filename === null || filename === undefined) return null; + if (!this.isInModuleDirectory(filename)) return null; const module = this.findModuleDir(filename); @@ -78,7 +80,7 @@ class PluginFileHandler implements FileHandler { } } - if (this.buildRoot === dirOfModule) { + if (this.isBuildRoot(dirOfModule)) { return null; } diff --git a/src/WebpackChunkHandler.ts b/src/WebpackChunkHandler.ts index 830b858..e9597e9 100644 --- a/src/WebpackChunkHandler.ts +++ b/src/WebpackChunkHandler.ts @@ -1,6 +1,6 @@ import { WebpackChunk } from './WebpackChunk'; import { ModuleCache } from './ModuleCache'; -import { Module } from './Module'; +import { LicenseIdentifiedModule } from './LicenseIdentifiedModule'; import { WebpackCompilation } from './WebpackCompilation'; import { WebpackStats } from './WebpackStats'; @@ -15,7 +15,7 @@ interface WebpackChunkHandler { compilation: WebpackCompilation, chunk: WebpackChunk, moduleCache: ModuleCache, - module: Module + module: LicenseIdentifiedModule ): void; } diff --git a/src/WebpackChunkModule.ts b/src/WebpackChunkModule.ts index fd86b50..63bee64 100644 --- a/src/WebpackChunkModule.ts +++ b/src/WebpackChunkModule.ts @@ -1,5 +1,7 @@ +import { PackageJson } from './PackageJson'; + export interface WebpackChunkModule { - resource: string; + resource?: string; rootModule?: { resource?: string; }; @@ -8,4 +10,8 @@ export interface WebpackChunkModule { }; fileDependencies?: string[]; dependencies?: WebpackChunkModule[]; + resourceResolveData?: { + descriptionFileRoot: string; + descriptionFileData: PackageJson; + }; } diff --git a/src/WebpackFileSystem.ts b/src/WebpackFileSystem.ts index 07f5708..c916095 100644 --- a/src/WebpackFileSystem.ts +++ b/src/WebpackFileSystem.ts @@ -18,8 +18,8 @@ class WebpackFileSystem implements FileSystem { pathExists(filename: string): boolean { try { - this.fs.statSync(filename); - return true; + const stat = this.fs.statSync(filename, { throwIfNoEntry: false }); + return !!stat; } catch (e) { return false; } @@ -44,7 +44,7 @@ class WebpackFileSystem implements FileSystem { isDirectory(dir: string): boolean { let isDir = false; try { - isDir = this.fs.statSync(dir).isDirectory(); + isDir = this.fs.statSync(dir, { throwIfNoEntry: false }).isDirectory(); } catch (e) {} return isDir; } diff --git a/src/WebpackModuleFileIterator.ts b/src/WebpackInnerModuleIterator.ts similarity index 74% rename from src/WebpackModuleFileIterator.ts rename to src/WebpackInnerModuleIterator.ts index aa3a565..198f3e2 100644 --- a/src/WebpackModuleFileIterator.ts +++ b/src/WebpackInnerModuleIterator.ts @@ -1,35 +1,37 @@ import { WebpackChunkModule } from './WebpackChunkModule'; -class WebpackModuleFileIterator { +class WebpackInnerModuleIterator { constructor(private requireResolve: RequireResolve) {} - iterateFiles( + iterateModules( chunkModule: WebpackChunkModule, - callback: (filename: string | null | undefined) => void + callback: (module: WebpackChunkModule) => void ) { const internalCallback = this.internalCallback.bind(this, callback); internalCallback( - chunkModule.resource || - (chunkModule.rootModule && chunkModule.rootModule.resource) + chunkModule.resource ? chunkModule : chunkModule.rootModule ); if (Array.isArray(chunkModule.fileDependencies)) { const fileDependencies: string[] = chunkModule.fileDependencies; - fileDependencies.forEach(internalCallback); + fileDependencies.forEach(fileDependency => + internalCallback({ resource: fileDependency }) + ); } if (Array.isArray(chunkModule.dependencies)) { chunkModule.dependencies.forEach(module => - internalCallback(module.originModule && module.originModule.resource) + internalCallback(module.originModule) ); } } private internalCallback( - callback: (filename: string | null | undefined) => void, - filename: string | null | undefined + callback: (module: WebpackChunkModule) => void, + module: WebpackChunkModule | undefined ): void { - const actualFileName = this.getActualFilename(filename); + if (!module) return; + const actualFileName = this.getActualFilename(module.resource); if (actualFileName) { - callback(actualFileName); + callback({ ...module, resource: actualFileName }); } } @@ -69,4 +71,4 @@ class WebpackModuleFileIterator { } } -export { WebpackModuleFileIterator }; +export { WebpackInnerModuleIterator }; diff --git a/src/__tests__/WebpackModuleFileIterator.test.ts b/src/__tests__/WebpackInnerModuleIterator.test.ts similarity index 93% rename from src/__tests__/WebpackModuleFileIterator.test.ts rename to src/__tests__/WebpackInnerModuleIterator.test.ts index 35dda1a..7ef682a 100644 --- a/src/__tests__/WebpackModuleFileIterator.test.ts +++ b/src/__tests__/WebpackInnerModuleIterator.test.ts @@ -1,12 +1,12 @@ -import { WebpackModuleFileIterator } from '../WebpackModuleFileIterator'; +import { WebpackInnerModuleIterator } from '../WebpackInnerModuleIterator'; import { fakeRequireResolve, FAKE_REQUIRE_RESOLVE_OUTPUT } from './FakeRequireResolve'; -const iterator = new WebpackModuleFileIterator(fakeRequireResolve); +const iterator = new WebpackInnerModuleIterator(fakeRequireResolve); -describe('WebpackModuleFileIterator', () => { +describe('WebpackInnerModuleIterator', () => { it('returns null for falsy filename', () => { expect(iterator.getActualFilename('')).toBeNull(); expect(iterator.getActualFilename(null)).toBeNull();