Skip to content

Commit c08c2c8

Browse files
authored
fix: conflict error (#619)
* fix: conflict error * Use Set to avoid unnecessary duplicated filenames * Raise error instead of silently ignoring licenses
1 parent c9bda69 commit c08c2c8

File tree

2 files changed

+206
-10
lines changed

2 files changed

+206
-10
lines changed

src/WebpackLicensePlugin.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import WebpackFileSystem from './WebpackFileSystem'
1313

1414
const pluginName = 'WebpackLicensePlugin'
1515

16+
interface ObservedCompiler {
17+
name: string;
18+
isChild: boolean;
19+
}
20+
1621
/**
1722
* @todo "emit" vs "compilation" & "optimizeChunkAssets" hooks
1823
* @todo add banner to chunks? boolean option + banner formatter?
@@ -23,6 +28,10 @@ const pluginName = 'WebpackLicensePlugin'
2328
* @todo preferred license types on ambiguity (licenses array or spdx expression)
2429
*/
2530
export default class WebpackLicensePlugin implements IWebpackPlugin {
31+
private readonly filenames = new Set<string>()
32+
private createdFiles = false
33+
private observedCompilers: ObservedCompiler[] = []
34+
2635
constructor(private pluginOptions: Partial<IPluginOptions> = {}) {}
2736

2837
public apply(compiler: webpack.Compiler) {
@@ -31,14 +40,28 @@ export default class WebpackLicensePlugin implements IWebpackPlugin {
3140
'webpack-license-plugin',
3241
this.handleCompilation.bind(this, compiler)
3342
)
43+
compiler.hooks.watchRun.tapAsync(
44+
'webpack-license-plugin',
45+
this.handleWatchRun.bind(this)
46+
)
3447
} else if (typeof compiler.plugin !== 'undefined') {
3548
compiler.plugin(
3649
'compilation',
3750
this.handleCompilation.bind(this, compiler)
3851
)
52+
compiler.plugin(
53+
'watchRun',
54+
this.handleWatchRun.bind(this)
55+
)
3956
}
4057
}
4158

59+
public async handleWatchRun(_: unknown, callback: () => void) {
60+
this.createdFiles = false
61+
this.observedCompilers = []
62+
callback()
63+
}
64+
4265
public handleCompilation(
4366
compiler: webpack.Compiler,
4467
compilation: webpack.compilation.Compilation
@@ -62,14 +85,38 @@ export default class WebpackLicensePlugin implements IWebpackPlugin {
6285
chunks: webpack.compilation.Chunk[],
6386
callback: () => void
6487
) {
88+
this.observedCompilers.push({
89+
name: compilation.compiler.name,
90+
isChild: compilation.compiler.isChild()
91+
})
92+
93+
if (this.createdFiles) {
94+
const observedCompilersMessage = this.observedCompilers.map(({ name, isChild }) => `compiler: ${name}, isChild: ${isChild}`).join('\n');
95+
const errorMessage = `${pluginName}: Found licenses after license files were already created.\nIf you see this message, you ran into an edge case we thought would not happen. Please open an isssue at https://github.com/codepunkt/webpack-license-plugin/issues with details of your webpack configuration so we can invastigate it further.\n${observedCompilersMessage}`
96+
compilation.errors.push(errorMessage)
97+
callback()
98+
return
99+
}
100+
101+
if (!compilation.compiler.isChild()) {
102+
this.createdFiles = true
103+
}
104+
65105
const alertAggregator = new WebpackAlertAggregator(compilation)
66106
const optionsProvider = new OptionsProvider(alertAggregator)
67107

68108
const options = optionsProvider.getOptions(this.pluginOptions)
69109
alertAggregator.flushAlerts(pluginName)
70110

71111
const chunkIterator = new WebpackChunkIterator()
72-
const filenames = chunkIterator.iterateChunks(compilation, chunks)
112+
for (const filename of chunkIterator.iterateChunks(compilation, chunks)) {
113+
this.filenames.add(filename)
114+
}
115+
116+
if (compilation.compiler.isChild()) {
117+
callback()
118+
return
119+
}
73120

74121
const fileSystem = new WebpackFileSystem(compiler.inputFileSystem)
75122
const packageJsonReader = new PackageJsonReader(fileSystem)
@@ -88,11 +135,9 @@ export default class WebpackLicensePlugin implements IWebpackPlugin {
88135
)
89136
)
90137

91-
await licenseFileWriter.writeLicenseFiles(filenames, options)
138+
await licenseFileWriter.writeLicenseFiles([...this.filenames], options)
92139
alertAggregator.flushAlerts(pluginName)
93140

94-
if (callback) {
95-
callback()
96-
}
141+
callback()
97142
}
98143
}

test/unit/WebpackLicensePlugin.test.ts

Lines changed: 156 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,35 @@
1+
import type * as webpack from 'webpack'
2+
import LicenseFileWriter from '../../src/LicenseFileWriter'
3+
import WebpackChunkIterator from '../../src/WebpackChunkIterator'
14
import WebpackLicensePlugin from '../../src/WebpackLicensePlugin'
2-
import webpack = require('webpack')
5+
6+
jest.mock('../../src/LicenseFileWriter')
7+
jest.mock('../../src/WebpackChunkIterator')
38

49
const MockCompiler = jest.fn<webpack.Compiler, any[]>((i) => i)
510
const MockCompilation = jest.fn<webpack.compilation.Compilation, any[]>((i) => i)
611
const MockChunk = jest.fn<webpack.compilation.Chunk, any[]>((i) => i)
712

813
describe('WebpackLicensePlugin', () => {
14+
beforeEach(() => {
15+
(LicenseFileWriter as jest.Mock).mockReset();
16+
(LicenseFileWriter as jest.Mock).mockImplementation(() => ({
17+
writeLicenseFiles: jest.fn()
18+
}));
19+
(WebpackChunkIterator as jest.Mock).mockReset();
20+
(WebpackChunkIterator as jest.Mock).mockImplementation(() => ({
21+
iterateChunks: () => []
22+
}))
23+
})
24+
925
describe('apply', () => {
10-
test('taps into compilation hook if hooks are defined', () => {
26+
test('taps into compilation and watchRun hooks if hooks are defined', () => {
1127
const compiler = new MockCompiler({
12-
hooks: { compilation: { tap: jest.fn() } },
28+
hooks: {
29+
compilation: { tap: jest.fn() },
30+
watchRun: { tapAsync: jest.fn() }
31+
},
32+
1333
})
1434
const instance = new WebpackLicensePlugin({})
1535
instance.apply(compiler)
@@ -19,15 +39,21 @@ describe('WebpackLicensePlugin', () => {
1939
'webpack-license-plugin',
2040
expect.any(Function)
2141
)
42+
expect(compiler.hooks.watchRun.tapAsync).toHaveBeenCalledTimes(1)
43+
expect(compiler.hooks.watchRun.tapAsync).toHaveBeenCalledWith(
44+
'webpack-license-plugin',
45+
expect.any(Function)
46+
)
2247
})
2348

2449
test('plugs into compilation otherwise', () => {
2550
const compiler = new MockCompiler({ plugin: jest.fn() })
2651
const instance = new WebpackLicensePlugin()
2752
instance.apply(compiler)
2853

29-
expect(compiler.plugin).toHaveBeenCalledTimes(1)
54+
expect(compiler.plugin).toHaveBeenCalledTimes(2)
3055
expect(compiler.plugin).toHaveBeenCalledWith('compilation', expect.any(Function))
56+
expect(compiler.plugin).toHaveBeenCalledWith('watchRun', expect.any(Function))
3157
})
3258
})
3359

@@ -57,18 +83,143 @@ describe('WebpackLicensePlugin', () => {
5783
})
5884

5985
describe('handleChunkAssetOptimization', () => {
86+
const createMockCompilation = (name: string, isChild: boolean) => new MockCompilation({
87+
assets: [],
88+
errors: [],
89+
warnings: [],
90+
compiler: {
91+
name,
92+
isChild: () => isChild
93+
}
94+
})
95+
6096
test('calls plugin mechanism callback when done', async () => {
6197
const instance = new WebpackLicensePlugin()
6298
const callback = jest.fn()
6399

64100
await instance.handleChunkAssetOptimization(
65101
new MockCompiler({ inputFileSystem: 'a', options: { context: 'b' } }),
66-
new MockCompilation({ assets: [], errors: [], warnings: [] }),
102+
createMockCompilation('mockCompiler', false),
67103
[new MockChunk()],
68104
callback
69105
)
70106

71107
expect(callback).toHaveBeenCalledTimes(1)
72108
})
109+
110+
test('calls writeLicenseFiles with all filenames', async () => {
111+
(WebpackChunkIterator as jest.Mock).mockReset();
112+
(WebpackChunkIterator as jest.Mock)
113+
.mockImplementationOnce(() => ({
114+
iterateChunks: () => ['filename1', 'filename2']
115+
}))
116+
.mockImplementationOnce(() => ({
117+
iterateChunks: () => ['filename1', 'filename3', 'filename4']
118+
}))
119+
120+
const writeLicenseFiles = jest.fn();
121+
(LicenseFileWriter as jest.Mock).mockReset();
122+
(LicenseFileWriter as jest.Mock).mockImplementationOnce(() => ({ writeLicenseFiles }))
123+
124+
const instance = new WebpackLicensePlugin()
125+
126+
const callback1 = jest.fn()
127+
const mockCompilation1 = createMockCompilation('mockCompiler1', true)
128+
await instance.handleChunkAssetOptimization(
129+
new MockCompiler({ inputFileSystem: 'a', options: { context: 'b' } }),
130+
mockCompilation1,
131+
[new MockChunk()],
132+
callback1
133+
)
134+
135+
const callback2 = jest.fn()
136+
const mockCompilation2 = createMockCompilation('mockCompiler2', false)
137+
await instance.handleChunkAssetOptimization(
138+
new MockCompiler({ inputFileSystem: 'a', options: { context: 'b' } }),
139+
mockCompilation2,
140+
[new MockChunk()],
141+
callback2
142+
)
143+
144+
expect(callback1).toHaveBeenCalledTimes(1)
145+
expect(callback2).toHaveBeenCalledTimes(1)
146+
147+
expect(mockCompilation1.errors).toEqual([])
148+
expect(mockCompilation2.errors).toEqual([])
149+
150+
expect(writeLicenseFiles).toHaveBeenCalledWith(['filename1', 'filename2', 'filename3', 'filename4'], expect.anything())
151+
expect(writeLicenseFiles).toHaveBeenCalledTimes(1)
152+
})
153+
154+
test('pushes error if handleChunkAssetOptimization is called again after files were written', async () => {
155+
const instance = new WebpackLicensePlugin()
156+
157+
const callback1 = jest.fn()
158+
const mockCompilation1 = createMockCompilation('mockCompiler1', false)
159+
await instance.handleChunkAssetOptimization(
160+
new MockCompiler({ inputFileSystem: 'a', options: { context: 'b' } }),
161+
mockCompilation1,
162+
[new MockChunk()],
163+
callback1
164+
)
165+
166+
const callback2 = jest.fn()
167+
const mockCompilation2 = createMockCompilation('mockCompiler2', true)
168+
await instance.handleChunkAssetOptimization(
169+
new MockCompiler({ inputFileSystem: 'a', options: { context: 'b' } }),
170+
mockCompilation2,
171+
[new MockChunk()],
172+
callback2
173+
)
174+
175+
expect(callback1).toHaveBeenCalledTimes(1)
176+
expect(callback2).toHaveBeenCalledTimes(1)
177+
178+
expect(mockCompilation1.errors).toEqual([])
179+
expect(mockCompilation2.errors).toEqual(["WebpackLicensePlugin: Found licenses after license files were already created.\nIf you see this message, you ran into an edge case we thought would not happen. Please open an isssue at https://github.com/codepunkt/webpack-license-plugin/issues with details of your webpack configuration so we can invastigate it further.\ncompiler: mockCompiler1, isChild: false\ncompiler: mockCompiler2, isChild: true"])
180+
})
181+
182+
test('reset when handleWatchRun is called', async () => {
183+
const instance = new WebpackLicensePlugin()
184+
185+
const callback1 = jest.fn()
186+
const mockCompilation1 = createMockCompilation('mockCompiler1', false)
187+
await instance.handleChunkAssetOptimization(
188+
new MockCompiler({ inputFileSystem: 'a', options: { context: 'b' } }),
189+
mockCompilation1,
190+
[new MockChunk()],
191+
callback1
192+
)
193+
194+
const callbackWatchRun = jest.fn()
195+
await instance.handleWatchRun(undefined, callbackWatchRun)
196+
197+
const callback2 = jest.fn()
198+
const mockCompilation2 = createMockCompilation('mockCompiler2', false)
199+
await instance.handleChunkAssetOptimization(
200+
new MockCompiler({ inputFileSystem: 'a', options: { context: 'b' } }),
201+
mockCompilation2,
202+
[new MockChunk()],
203+
callback2
204+
)
205+
206+
const callback3 = jest.fn()
207+
const mockCompilation3 = createMockCompilation('mockCompiler3', true)
208+
await instance.handleChunkAssetOptimization(
209+
new MockCompiler({ inputFileSystem: 'a', options: { context: 'b' } }),
210+
mockCompilation3,
211+
[new MockChunk()],
212+
callback3
213+
)
214+
215+
expect(callback1).toHaveBeenCalledTimes(1)
216+
expect(callbackWatchRun).toHaveBeenCalledTimes(1)
217+
expect(callback2).toHaveBeenCalledTimes(1)
218+
expect(callback3).toHaveBeenCalledTimes(1)
219+
220+
expect(mockCompilation1.errors).toEqual([])
221+
expect(mockCompilation2.errors).toEqual([])
222+
expect(mockCompilation3.errors).toEqual(["WebpackLicensePlugin: Found licenses after license files were already created.\nIf you see this message, you ran into an edge case we thought would not happen. Please open an isssue at https://github.com/codepunkt/webpack-license-plugin/issues with details of your webpack configuration so we can invastigate it further.\ncompiler: mockCompiler2, isChild: false\ncompiler: mockCompiler3, isChild: true"])
223+
})
73224
})
74225
})

0 commit comments

Comments
 (0)