diff --git a/lib/antlr/visitors/exportVisitor.ts b/lib/antlr/visitors/exportVisitor.ts index 41dc8a5..7c90344 100644 --- a/lib/antlr/visitors/exportVisitor.ts +++ b/lib/antlr/visitors/exportVisitor.ts @@ -1,5 +1,6 @@ -import { CharStreams, CommonTokenStream } from 'antlr4ts'; +import { CharStreams, CommonTokenStream, Token } from 'antlr4ts'; import { ParseTreeWalker } from 'antlr4ts/tree/ParseTreeWalker'; +import { ExportType } from '../../types'; import { SolidityLexer } from '../generated/SolidityLexer'; import { SolidityListener } from '../generated/SolidityListener'; import { @@ -10,11 +11,14 @@ import { SourceUnitContext, StructDefinitionContext, } from '../generated/SolidityParser'; -import { ExportType, ExportVisitResult, VisitCallback } from './types'; +import { ExportVisitResult, VisitCallback } from './types'; + +const HIDDEN_CHANNEL = 1; export class SolidityExportVisitor { #inputContent: string; #antlrTree: SourceUnitContext; + #comments: Token[] = []; constructor(inputContent: string) { this.#inputContent = inputContent; @@ -23,11 +27,74 @@ export class SolidityExportVisitor { const tokens = new CommonTokenStream(lexer); const parser = new SolidityParser(tokens); this.#antlrTree = parser.sourceUnit(); + + this.#comments = tokens + .getRange(0, tokens.size) + .filter((t) => t.channel === HIDDEN_CHANNEL); } visit(onVisit: VisitCallback) { - const listener: SolidityListener = new ExportVisitor(onVisit); + const listener: SolidityListener = new ExportVisitor((visitResult) => { + this.onVisit(visitResult, onVisit); + }); ParseTreeWalker.DEFAULT.walk(listener, this.#antlrTree); + this.flushComments(onVisit); + } + + private onVisit( + visitResult: ExportVisitResult, + onVisit: VisitCallback, + ) { + if (!this.#comments.length) { + return onVisit(visitResult); + } + this.emitCommentsBefore(visitResult, onVisit); + onVisit(visitResult); + } + + private emitCommentsBefore( + visitResult: ExportVisitResult, + onVisit: VisitCallback, + ) { + while ( + this.#comments.length && + this.#comments[0].startIndex < visitResult.start + ) { + const comment = this.#comments.shift(); + if (!comment) { + continue; + } + onVisit(this.buildComment(comment)); + } + while ( + this.#comments.length && + this.#comments[0].stopIndex < visitResult.end + ) { + this.#comments.shift(); + } + } + + private flushComments(onVisit: VisitCallback) { + if (!this.#comments.length) { + return; + } + + this.#comments.forEach((comment) => onVisit(this.buildComment(comment))); + } + + private buildComment(comment: Token): ExportVisitResult { + return { + abstract: false, + body: { + start: comment.startIndex, + end: comment.stopIndex, + }, + start: comment.startIndex, + end: comment.stopIndex, + is: null, + name: `Comment#${comment.startIndex}`, + type: ExportType.comment, + }; } } @@ -109,7 +176,7 @@ class ExportVisitor implements SolidityListener { start, end, abstract: false, - type: 'struct', + type: ExportType.struct, body: { start: bodyStart + 1, end, @@ -145,7 +212,7 @@ class ExportVisitor implements SolidityListener { start, end, abstract: false, - type: 'enum', + type: ExportType.enum, body: { start: bodyStart + 1, end, diff --git a/lib/antlr/visitors/types.ts b/lib/antlr/visitors/types.ts index 19beabf..319c37e 100644 --- a/lib/antlr/visitors/types.ts +++ b/lib/antlr/visitors/types.ts @@ -1,3 +1,5 @@ +import { ExportType } from '../../types'; + export interface RangeVisitResult { start: number; end: number; @@ -6,21 +8,19 @@ export interface RangeVisitResult { export interface ImportVisitResult extends RangeVisitResult { filename: string; globalRename: string | null; - namedImports: ImportVisitNamedImport[] | null + namedImports: ImportVisitNamedImport[] | null; } export interface ImportVisitNamedImport { name: string; - as: string | null + as: string | null; } -export type ExportType = 'contract' | 'library' | 'interface' | 'struct' | 'enum'; - export interface ExportVisitResult extends RangeVisitResult { abstract: boolean; type: ExportType; name: string; - body: RangeVisitResult, + body: RangeVisitResult; is: RangeVisitResult | null; } diff --git a/lib/exportsAnalyzer.ts b/lib/exportsAnalyzer.ts index 0e87b31..e2f2b3b 100644 --- a/lib/exportsAnalyzer.ts +++ b/lib/exportsAnalyzer.ts @@ -1,11 +1,11 @@ import Debug from 'debug'; -import parser from 'solidity-parser-antlr'; import { SolidityExportVisitor } from './antlr/visitors/exportVisitor'; +import { ExportType } from './types'; const error = Debug('sol-merger:error'); export interface ExportsAnalyzerResult { - type: 'contract' | 'library' | 'interface' | 'struct' | 'enum'; + type: ExportType; name: string; is: string; body: string; @@ -33,7 +33,9 @@ export class ExportsAnalyzer { type: e.type, name: e.name, body: this.contents.substring(e.body.start, e.body.end + 1).trim(), - is: e.is ? this.contents.substring(e.is.start, e.is.end + 1).trimLeft() : '', + is: e.is + ? this.contents.substring(e.is.start, e.is.end + 1).trimLeft() + : '', }); }); return results; diff --git a/lib/fileAnalyzer.ts b/lib/fileAnalyzer.ts index 9a486e9..8389095 100644 --- a/lib/fileAnalyzer.ts +++ b/lib/fileAnalyzer.ts @@ -2,7 +2,8 @@ import fs from 'fs-extra'; import stripComments from 'strip-json-comments'; import { ExportsAnalyzer, ExportsAnalyzerResult } from './exportsAnalyzer'; import { RegistredImport } from './importRegistry'; -import { ImportsAnalyzerResult, ImportsAnalyzer } from './importsAnalyzer'; +import { ImportsAnalyzer, ImportsAnalyzerResult } from './importsAnalyzer'; +import { ExportType } from './types'; export class FileAnalyzer { filename: string; @@ -16,6 +17,10 @@ export class FileAnalyzer { newName: string | null, globalRenames: RegistredImport[], ): string { + if (e.type === ExportType.comment) { + return e.body; + } + let is = e.is; if (is) { globalRenames.forEach((i) => { diff --git a/lib/importsAnalyzer.ts b/lib/importsAnalyzer.ts index d1f44c2..86a7cd1 100644 --- a/lib/importsAnalyzer.ts +++ b/lib/importsAnalyzer.ts @@ -1,4 +1,3 @@ -import parser from 'solidity-parser-antlr'; import { Utils } from './utils'; import { SolidityImportVisitor } from './antlr/visitors/importVisitor'; import { ImportVisitResult } from './antlr/visitors/types'; @@ -38,7 +37,6 @@ export class ImportsAnalyzer { */ analyzeImports(): ImportsAnalyzerResult[] { const imports: ImportsAnalyzerResult[] = []; - const ast = Utils.getAstNode(this.contents); const importDirectives: ImportVisitResult[] = []; const visitor = new SolidityImportVisitor(this.contents); @@ -46,10 +44,6 @@ export class ImportsAnalyzer { importDirectives.push(i); }); - if (!ast) { - return []; - } - for (const importDirective of importDirectives) { const analyzedImport = this.analyzeImport(importDirective); imports.push(analyzedImport); @@ -65,7 +59,9 @@ export class ImportsAnalyzer { * 3. Extract filename from import * */ - private analyzeImport(importVisitResult: ImportVisitResult): ImportsAnalyzerResult { + private analyzeImport( + importVisitResult: ImportVisitResult, + ): ImportsAnalyzerResult { return { file: importVisitResult.filename, globalRenameImport: importVisitResult.globalRename, diff --git a/lib/merger.ts b/lib/merger.ts index 8c1a9bd..ce268db 100644 --- a/lib/merger.ts +++ b/lib/merger.ts @@ -5,6 +5,7 @@ import { ExportsAnalyzerResult } from './exportsAnalyzer'; import { FileAnalyzer, FileAnalyzerResult } from './fileAnalyzer'; import { ImportsRegistry } from './importRegistry'; import { ImportsAnalyzer, ImportsAnalyzerResult } from './importsAnalyzer'; +import { ExportType } from './types'; import { Utils } from './utils'; const error = Debug('sol-merger:error'); @@ -64,7 +65,11 @@ export class Merger { await this.init(file); } if (this.importRegistry.isImportProcessed(parentImport?.importStatement)) { - debug(' %s Import statement already processed: %s', '⚠', parentImport?.importStatement); + debug( + ' %s Import statement already processed: %s', + '⚠', + parentImport?.importStatement, + ); return ''; } if (parentImport) { @@ -150,6 +155,12 @@ export class Merger { ); const shouldBeImported = (exportName: string) => { + if ( + e.type === ExportType.comment && + (isAllImport || isRenameGlobalImport) + ) { + return true; + } return ( isAllImport || isRenameGlobalImport || diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 0000000..2d8872f --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,8 @@ +export enum ExportType { + contract = 'contract', + library = 'library', + interface = 'interface', + struct = 'struct', + enum = 'enum', + comment = 'comment' +} diff --git a/lib/utils.ts b/lib/utils.ts index 49a7351..75f363e 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,15 +1,5 @@ -import parser, { ASTNode } from 'solidity-parser-antlr'; - export class Utils { static isRelative(file: string) { return file.startsWith('.'); } - - static getAstNode(contents: string): ASTNode | null { - try { - return parser.parse(contents, { loc: true, range: true }); - } catch { - return null; - } - } } diff --git a/package-lock.json b/package-lock.json index dc89df1..a65c0fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1040,11 +1040,6 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "solidity-parser-antlr": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/solidity-parser-antlr/-/solidity-parser-antlr-0.4.11.tgz", - "integrity": "sha512-4jtxasNGmyC0midtjH/lTFPZYvTTUMy6agYcF+HoMnzW8+cqo3piFrINb4ZCzpPW+7tTVFCGa5ubP34zOzeuMg==" - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index 4d8a331..1bf0181 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "debug": "^4.1.1", "fs-extra": "^8.0.1", "glob": "^7.1.2", - "solidity-parser-antlr": "^0.4.11", "strip-json-comments": "^3.0.1" }, "pre-commit": [ diff --git a/test/compiled/ContactWithKeywordsInsideString.sol b/test/compiled/ContactWithKeywordsInsideString.sol index ff757fb..594929e 100644 --- a/test/compiled/ContactWithKeywordsInsideString.sol +++ b/test/compiled/ContactWithKeywordsInsideString.sol @@ -20,6 +20,8 @@ contract ConflictingInheritance { } +// This contract will not be detected + contract B { uint private b; diff --git a/test/compiled/GlobalComments.sol b/test/compiled/GlobalComments.sol new file mode 100644 index 0000000..bac3800 --- /dev/null +++ b/test/compiled/GlobalComments.sol @@ -0,0 +1,21 @@ +pragma solidity 0.6.0; + + +/* + * Multiline Comment Before + */ + +// This is not included Before + +contract MyContract { + // This is included + function myFunction() { + // This is included + } +} + +// This is not included After + +/* + * Multiline Comment After + */ \ No newline at end of file diff --git a/test/contracts/Enum.sol b/test/contracts/Enum.sol index d23b831..81ea1a2 100644 --- a/test/contracts/Enum.sol +++ b/test/contracts/Enum.sol @@ -1,6 +1,6 @@ pragma solidity ^0.4.0; -enum State1 { Created1, Locked1, Inactive1 } // Enum +enum State1 { Created1, Locked1, Inactive1 } contract Purchase { enum State { Created, Locked, Inactive } // Enum diff --git a/test/contracts/GlobalComments.sol b/test/contracts/GlobalComments.sol new file mode 100644 index 0000000..d5d543a --- /dev/null +++ b/test/contracts/GlobalComments.sol @@ -0,0 +1,16 @@ +pragma solidity 0.6.0; + +/* + * Multiline Comment Before + */ +// This is not included Before +contract MyContract { + // This is included + function myFunction() { + // This is included + } +} +// This is not included After +/* + * Multiline Comment After + */ \ No newline at end of file diff --git a/test/exportsAnalyzer.spec.ts b/test/exportsAnalyzer.spec.ts index 63143fd..37dc324 100644 --- a/test/exportsAnalyzer.spec.ts +++ b/test/exportsAnalyzer.spec.ts @@ -68,7 +68,7 @@ describe('ExportsAnalyzer', () => { ]); }); - it('should return empty array if there are no exports', () => { + it('should return comments array', () => { const exportsAnalyzer = new ExportsAnalyzer(` // Some contracts without exports @@ -76,6 +76,28 @@ describe('ExportsAnalyzer', () => { `); const exports = exportsAnalyzer.analyzeExports(); + assert.deepEqual(exports, [ + { + body: '// Some contracts without exports', + is: '', + name: 'Comment#9', + type: 'comment', + }, + { + body: '// Some contract text that is not required here', + is: '', + name: 'Comment#52', + type: 'comment', + }, + ]); + }); + + it('should return empty array if there are no exports', () => { + const exportsAnalyzer = new ExportsAnalyzer(` + + `); + const exports = exportsAnalyzer.analyzeExports(); + assert.deepEqual(exports, []); }); }); diff --git a/test/index.spec.ts b/test/index.spec.ts index d617715..d695c24 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -83,6 +83,10 @@ describe('Solidity Merger', () => { await testFile('ImportStruct'); }); + it('should compile while when importing the struct', async () => { + await testFile('GlobalComments'); + }); + it('should compile file without imports and exports (empty content)', async () => { const merger = new Merger(); const file = path.join(__dirname, `/contracts/EmptyFile.sol`);