From bf0b6e41bd2984e183e41f988710a328eaeda4c3 Mon Sep 17 00:00:00 2001 From: Valery Aligorsky Date: Sun, 17 May 2020 14:49:43 +0200 Subject: [PATCH] feat(comments): add global comments as exports --- lib/antlr/visitors/exportVisitor.ts | 70 ++++++++++++++++++- lib/antlr/visitors/types.ts | 2 +- lib/exportsAnalyzer.ts | 2 +- lib/fileAnalyzer.ts | 4 ++ lib/merger.ts | 11 ++- .../ContactWithKeywordsInsideString.sol | 2 + test/compiled/GlobalComments.sol | 21 ++++++ test/contracts/Enum.sol | 2 +- test/contracts/GlobalComments.sol | 16 +++++ test/exportsAnalyzer.spec.ts | 24 ++++++- test/index.spec.ts | 4 ++ 11 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 test/compiled/GlobalComments.sol create mode 100644 test/contracts/GlobalComments.sol diff --git a/lib/antlr/visitors/exportVisitor.ts b/lib/antlr/visitors/exportVisitor.ts index 41dc8a5..41a1cbe 100644 --- a/lib/antlr/visitors/exportVisitor.ts +++ b/lib/antlr/visitors/exportVisitor.ts @@ -1,4 +1,4 @@ -import { CharStreams, CommonTokenStream } from 'antlr4ts'; +import { CharStreams, CommonTokenStream, Token } from 'antlr4ts'; import { ParseTreeWalker } from 'antlr4ts/tree/ParseTreeWalker'; import { SolidityLexer } from '../generated/SolidityLexer'; import { SolidityListener } from '../generated/SolidityListener'; @@ -12,9 +12,12 @@ import { } from '../generated/SolidityParser'; import { ExportType, 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 +26,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 + ) { + const skipped = 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: 'comment', + }; } } diff --git a/lib/antlr/visitors/types.ts b/lib/antlr/visitors/types.ts index 19beabf..6dcccb0 100644 --- a/lib/antlr/visitors/types.ts +++ b/lib/antlr/visitors/types.ts @@ -14,7 +14,7 @@ export interface ImportVisitNamedImport { as: string | null } -export type ExportType = 'contract' | 'library' | 'interface' | 'struct' | 'enum'; +export type ExportType = 'contract' | 'library' | 'interface' | 'struct' | 'enum' | 'comment'; export interface ExportVisitResult extends RangeVisitResult { abstract: boolean; diff --git a/lib/exportsAnalyzer.ts b/lib/exportsAnalyzer.ts index 0e87b31..c475550 100644 --- a/lib/exportsAnalyzer.ts +++ b/lib/exportsAnalyzer.ts @@ -5,7 +5,7 @@ import { SolidityExportVisitor } from './antlr/visitors/exportVisitor'; const error = Debug('sol-merger:error'); export interface ExportsAnalyzerResult { - type: 'contract' | 'library' | 'interface' | 'struct' | 'enum'; + type: 'contract' | 'library' | 'interface' | 'struct' | 'enum' | 'comment'; name: string; is: string; body: string; diff --git a/lib/fileAnalyzer.ts b/lib/fileAnalyzer.ts index 9a486e9..73a7e8b 100644 --- a/lib/fileAnalyzer.ts +++ b/lib/fileAnalyzer.ts @@ -16,6 +16,10 @@ export class FileAnalyzer { newName: string | null, globalRenames: RegistredImport[], ): string { + if (e.type === 'comment') { + return e.body; + } + let is = e.is; if (is) { globalRenames.forEach((i) => { diff --git a/lib/merger.ts b/lib/merger.ts index 8c1a9bd..8fdef1f 100644 --- a/lib/merger.ts +++ b/lib/merger.ts @@ -64,7 +64,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 +154,9 @@ export class Merger { ); const shouldBeImported = (exportName: string) => { + if (e.type === 'comment' && (isAllImport || isRenameGlobalImport)) { + return true; + } return ( isAllImport || isRenameGlobalImport || @@ -169,7 +176,7 @@ export class Merger { analyzedFile.filename, e.name, rename, - ); + ) if (isImported) { debug('%s Already imported: %s %s', '⚠', e.name, analyzedFile.filename); return []; 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`);