diff --git a/lib/merger.ts b/lib/merger.ts index 90d4bbd..fc02166 100644 --- a/lib/merger.ts +++ b/lib/merger.ts @@ -5,8 +5,9 @@ import { ExportsAnalyzerResult } from './exportsAnalyzer'; import { FileAnalyzer, FileAnalyzerResult } from './fileAnalyzer'; import { ImportsRegistry } from './importRegistry'; import { ImportsAnalyzer, ImportsAnalyzerResult } from './importsAnalyzer'; -import { ExportType } from './types'; +import { ExportType, ExportPluginCtor } from './types'; import { Utils } from './utils'; +import { ExportPluginsRegistry } from './pluginsRegistry'; const error = Debug('sol-merger:error'); const debug = Debug('sol-merger:debug'); @@ -17,6 +18,7 @@ export class Merger { removeComments: boolean; private importRegistry: ImportsRegistry; + #pluginsRegistry: ExportPluginsRegistry; nodeModulesRoot = ''; constructor(private options: SolMergerOptions = {}) { @@ -27,6 +29,9 @@ export class Merger { } this.importRegistry = new ImportsRegistry(); + + const exportPlugins = options.exportPlugins || []; + this.#pluginsRegistry = new ExportPluginsRegistry(exportPlugins); } getPragmaRegex() { @@ -195,9 +200,13 @@ export class Merger { rename = `${parentImport.globalRenameImport}$${e.name}`; } const globalRenames = this.importRegistry.getGlobalImports(); + const newExport = this.#pluginsRegistry.processExport(e); + if (!newExport) { + return []; + } const body = FileAnalyzer.buildExportBody( analyzedFile, - e, + newExport, rename, globalRenames, ); @@ -239,4 +248,5 @@ export interface SolMergerOptions { delimeter?: string; removeComments?: boolean; commentsDelimeter?: string; + exportPlugins?: ExportPluginCtor[]; } diff --git a/lib/plugins.ts b/lib/plugins.ts new file mode 100644 index 0000000..41e9856 --- /dev/null +++ b/lib/plugins.ts @@ -0,0 +1 @@ +export { ExportPlugin as SPDXLicenseRemovePlugin } from './plugins/SPDXLicenseRemovePlugin'; diff --git a/lib/plugins/SPDXLicenseRemovePlugin.ts b/lib/plugins/SPDXLicenseRemovePlugin.ts new file mode 100644 index 0000000..45338be --- /dev/null +++ b/lib/plugins/SPDXLicenseRemovePlugin.ts @@ -0,0 +1,18 @@ +import { ExportPluginProcessor, ExportPluginCtor, ExportType } from '../types'; +import { ExportsAnalyzerResult } from '../exportsAnalyzer'; + +class SPDXLicenseRemovePlugin implements ExportPluginProcessor { + name = 'SPDXLicenseRemovePlugin'; + processExport(e: Readonly): ExportsAnalyzerResult { + const SPDX_COMMENT_REGEX = /SPDX-License-Identifier: .+/g; + if (e.type !== ExportType.comment) { + return e; + } + return { + ...e, + body: e.body.replace(SPDX_COMMENT_REGEX, ''), + }; + } +} + +export const ExportPlugin: ExportPluginCtor = SPDXLicenseRemovePlugin; diff --git a/lib/pluginsRegistry.ts b/lib/pluginsRegistry.ts new file mode 100644 index 0000000..3178bba --- /dev/null +++ b/lib/pluginsRegistry.ts @@ -0,0 +1,31 @@ +import { ExportsAnalyzerResult } from './exportsAnalyzer'; +import { ExportPluginCtor, ExportPluginProcessor } from './types'; + +export class ExportPluginsRegistry { + #plugins: ExportPluginProcessor[]; + constructor(plugins: ExportPluginCtor[]) { + this.#plugins = plugins.map((PluginCtor) => new PluginCtor()); + } + + processExport( + e: Readonly, + ): ExportsAnalyzerResult | null { + if (!this.#plugins.length) { + return e; + } + + return this.#plugins.reduce( + (latestExport: ExportsAnalyzerResult | null, plugin) => { + if (!latestExport) { + return null; + } + return plugin.processExport(latestExport); + }, + e, + ); + } + + registerPlugin(plugin: ExportPluginProcessor) { + this.#plugins.push(plugin); + } +} diff --git a/lib/types.ts b/lib/types.ts index 2d8872f..65f16ad 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,8 +1,19 @@ +import { ExportsAnalyzerResult } from './exportsAnalyzer'; + export enum ExportType { contract = 'contract', library = 'library', interface = 'interface', struct = 'struct', enum = 'enum', - comment = 'comment' + comment = 'comment', } + +export interface ExportPluginProcessor { + name: string; + processExport(e: Readonly): ExportsAnalyzerResult | null; +} + +export type ExportPluginCtor< + T extends ExportPluginProcessor = ExportPluginProcessor +> = new () => T; diff --git a/test/compiled/LocalImportsWithSPDX.sol b/test/compiled/LocalImportsWithSPDX.sol new file mode 100644 index 0000000..043c70d --- /dev/null +++ b/test/compiled/LocalImportsWithSPDX.sol @@ -0,0 +1,35 @@ +pragma solidity ^0.4.11; +pragma experimental ABIEncoderV2; + + +/* + +*/ +contract Ownable { + address public owner; + + function Ownable() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + function transferOwnership(address newOwner) onlyOwner { + if (newOwner != address(0)) { + owner = newOwner; + } + } + +} + +/* + +*/ +contract MyOwned is Ownable { + string public constant name = "My Owned"; + + function MyOwned() {} +} diff --git a/test/contracts/LocalImportsWithSPDX.sol b/test/contracts/LocalImportsWithSPDX.sol new file mode 100644 index 0000000..fa5aa42 --- /dev/null +++ b/test/contracts/LocalImportsWithSPDX.sol @@ -0,0 +1,13 @@ +pragma solidity ^0.4.11; +pragma experimental ABIEncoderV2; + +import "./imports/ownableWithSPDX.sol"; + +/* +SPDX-License-Identifier: MIT +*/ +contract MyOwned is Ownable { + string public constant name = "My Owned"; + + function MyOwned() {} +} diff --git a/test/contracts/imports/ownableWithSPDX.sol b/test/contracts/imports/ownableWithSPDX.sol new file mode 100644 index 0000000..515621d --- /dev/null +++ b/test/contracts/imports/ownableWithSPDX.sol @@ -0,0 +1,25 @@ +pragma solidity ^0.4.11; +pragma experimental ABIEncoderV2; + +/* +SPDX-License-Identifier: Apache-2.0 AND (MIT OR GPL-2.0-only) +*/ +contract Ownable { + address public owner; + + function Ownable() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + function transferOwnership(address newOwner) onlyOwner { + if (newOwner != address(0)) { + owner = newOwner; + } + } + +} diff --git a/test/index.spec.ts b/test/index.spec.ts index 4e552c0..2abf0f9 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -1,10 +1,11 @@ import { assert } from 'chai'; import path from 'path'; import { merge, Merger } from '../lib/index'; +import { SPDXLicenseRemovePlugin } from '../lib/plugins'; import { assertWithFile, testFile } from './utils'; describe('Solidity Merger', () => { - it('should set default options', function() { + it('should set default options', function () { const merger = new Merger(); assert.equal( merger.delimeter, @@ -91,6 +92,12 @@ describe('Solidity Merger', () => { await testFile('AbstractContract'); }); + it('should compile use plugins correctly', async () => { + await testFile('LocalImportsWithSPDX', { + exportPlugins: [SPDXLicenseRemovePlugin], + }); + }); + it('should compile file without imports and exports (empty content)', async () => { const merger = new Merger(); const file = path.join(__dirname, `/contracts/EmptyFile.sol`); diff --git a/test/plugins/SPDXLicenseRemovePlugin.spec.ts b/test/plugins/SPDXLicenseRemovePlugin.spec.ts new file mode 100644 index 0000000..935b99f --- /dev/null +++ b/test/plugins/SPDXLicenseRemovePlugin.spec.ts @@ -0,0 +1,41 @@ +import { assert } from 'chai'; +import { ExportPlugin } from '../../lib/plugins/SPDXLicenseRemovePlugin'; +import { ExportsAnalyzerResult } from '../../lib/exportsAnalyzer'; +import { ExportType } from '../../lib/types'; + +describe('SPDXLicenseRemovePlugin', () => { + it('should remove SPDX License from comments', () => { + const plugin = new ExportPlugin(); + + const comment: ExportsAnalyzerResult = { + type: ExportType.comment, + abstact: false, + body: `// SPDX-License-Identifier: MIT `, + is: '', + name: 'Test', + }; + + const newExport = plugin.processExport(comment); + assert.equal(newExport.body, '// '); + }); + + it('should remove multiple SPDX License comments', () => { + const plugin = new ExportPlugin(); + + const comment: ExportsAnalyzerResult = { + type: ExportType.comment, + abstact: false, + body: `/* + SPDX-License-Identifier: MIT + SPDX-License-Identifier: EPL-1.0+ + SPDX-License-Identifier: GPL-2.0-only + SPDX-License-Identifier: Apache-2.0 AND (MIT OR GPL-2.0-only) + */`, + is: '', + name: 'Test', + }; + + const newExport = plugin.processExport(comment); + assert.match(newExport.body, /\/\*[ \r\n]+\*\//); + }); +});