Skip to content

Commit

Permalink
feat(FileAnalyzer): use external library to parse contracts.
Browse files Browse the repository at this point in the history
Relates #24
  • Loading branch information
RyuuGan committed Jan 4, 2020
1 parent 685bdc3 commit cbee328
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 124 deletions.
96 changes: 25 additions & 71 deletions lib/fileAnalyzer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import Debug from 'debug';
import fs from 'fs-extra';
import parser from 'solidity-parser-antlr';
import stripComments from 'strip-json-comments';
import { RegistredImport } from './merger';

const error = Debug('sol-merger:error');

export class FileAnalyzer {
filename: string;
/**
Expand Down Expand Up @@ -129,80 +133,30 @@ export class FileAnalyzer {
*
*/
analyzeExports(contents: string): FileAnalyzerExportsResult[] {
const exportRegex = /(contract|library|interface)\s+([a-zA-Z_$][a-zA-Z_$0-9]*)\s*([\s\S]*?)\{/;
const isRegex = /^is\s*[a-zA-Z_$][a-zA-Z_$0-9]*(.[a-zA-Z_$][a-zA-Z_$0-9]*)?(\([\s\S]*?\))?(,\s*?[a-zA-Z_$][a-zA-Z_$0-9]*(.[a-zA-Z_$][a-zA-Z_$0-9]*)?(\([\s\S]*?\))?)*\s*$/;
const results = [];
let group: RegExpExecArray;
let restContents = contents;
while ((group = exportRegex.exec(restContents))) {
const [, type, name, is] = group;
// Checking that `is` clause is correct
if (is.trim() && !isRegex.test(is.trim())) {
continue;
}
const bodyStart = group.index + group[0].length - 1;
const body = this.findBodyEnd(
restContents,
bodyStart,
);
results.push({
type,
name,
is,
body,
try {
const ast = parser.parse(contents, { loc: true, range: true });
const results: FileAnalyzerExportsResult[] = [];
const exportRegex = /(contract|library|interface)\s+([a-zA-Z_$][a-zA-Z_$0-9]*)\s*([\s\S]*?)\{/;
parser.visit(ast, {
ContractDefinition: (node) => {
const contract = contents.substring(node.range[0], node.range[1] + 1);
const group = exportRegex.exec(contract);
const [match, _, __, is] = group;
results.push({
is: is,
name: node.name,
type: node.kind as any,
body: contract.substring(match.length - 1),
});
},
});

restContents = restContents.substring(0, group.index) + restContents.substring(bodyStart + body.length);
}
return results;
}

/**
* @param contents file contents
* @param start start of the body, start must be pointing to "{"
* @returns body of the export
*/
findBodyEnd(contents: string, start: number): string {
let deep = 1;
let idx = start + 1;
let inString = false;
let isSingleQuotedString = false;
while (deep !== 0 && idx < contents.length) {
if (contents[idx] === '}' && !inString) {
deep -= 1;
return results;
} catch (e) {
if (e instanceof (parser as any).ParserError) {
error(e.errors);
}
if (contents[idx] === '{' && !inString) {
deep += 1;
}

if (contents[idx] === '"') {
if (
(inString && contents[idx - 1] !== '\\' && !isSingleQuotedString) ||
!inString
) {
isSingleQuotedString = false;
inString = !inString;
}
}

if (contents[idx] === "'") {
if (
(inString && contents[idx - 1] !== '\\' && isSingleQuotedString) ||
!inString
) {
isSingleQuotedString = true;
inString = !inString;
}
}

idx += 1;
}
if (deep !== 0) {
throw new Error(
'Export is not correct. Has more opening brackets then closing.',
);
return [];
}
return contents.substring(start, idx);
}
}

Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"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": [
Expand Down
65 changes: 12 additions & 53 deletions test/fileAnalyzer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('FileAnalyzer', () => {
assert.throws(
() => fileAnalyzer.analyzeImport('import "filename.sol;'),
Error,
'Unknown import statement'
'Unknown import statement',
);
});

Expand All @@ -24,7 +24,7 @@ describe('FileAnalyzer', () => {

it('should analyze global rename', () => {
let result = fileAnalyzer.analyzeImport(
'import "filename.sol" as myName;'
'import "filename.sol" as myName;',
);
assert.deepEqual(
result,
Expand All @@ -33,11 +33,11 @@ describe('FileAnalyzer', () => {
globalRenameImport: 'myName',
namedImports: null,
},
'solidity as syntax'
'solidity as syntax',
);

result = fileAnalyzer.analyzeImport(
'import * as myName from "filename.sol";'
'import * as myName from "filename.sol";',
);
assert.deepEqual(
result,
Expand All @@ -46,13 +46,13 @@ describe('FileAnalyzer', () => {
globalRenameImport: 'myName',
namedImports: null,
},
'* as syntax'
'* as syntax',
);
});

it('should analyze named imports', () => {
const result = fileAnalyzer.analyzeImport(
'import { A, B as Bingo, C as C1, D } from "filename.sol";'
'import { A, B as Bingo, C as C1, D } from "filename.sol";',
);
assert.deepEqual(result, {
file: 'filename.sol',
Expand Down Expand Up @@ -144,47 +144,6 @@ describe('FileAnalyzer', () => {
});
});

describe('findBodyEnd', () => {
const fileAnalyzer = new FileAnalyzer('./contracts/LocalImports.sol');
it('should find body end', () => {
const contents = '{{{}{}}{}} {{{}{{}}}}';
let body = fileAnalyzer.findBodyEnd(contents, 0);
assert.equal(body, '{{{}{}}{}}');

body = fileAnalyzer.findBodyEnd(contents, 2);
assert.equal(body, '{}');

body = fileAnalyzer.findBodyEnd(contents, 11);
assert.equal(body, '{{{}{{}}}}');
});

it('should not count brackets in string literals', () => {
let body = fileAnalyzer.findBodyEnd('{ "}" }', 0);
assert.equal(body, '{ "}" }', 'closing bracket double quoted');

body = fileAnalyzer.findBodyEnd('{ "{" }', 0);
assert.equal(body, '{ "{" }', 'opening bracket double quoted');

body = fileAnalyzer.findBodyEnd("{ '}' }", 0);
assert.equal(body, "{ '}' }", 'closing bracket single quoted');

body = fileAnalyzer.findBodyEnd("{ '{' }", 0);
assert.equal(body, "{ '{' }", 'opening bracket single quoted');

body = fileAnalyzer.findBodyEnd(`{ '"{"' }`, 0);
assert.equal(
body,
`{ '"{"' }`,
'opening bracket single quoted with double quote inside'
);
});

it('should correctly process escaped literals', () => {
const body = fileAnalyzer.findBodyEnd('{ "\\"{\\"" }', 0);
assert.equal(body, '{ "\\"{\\"" }');
});
});

describe('analyzeExports', () => {
const fileAnalyzer = new FileAnalyzer('./contracts/LocalImports.sol');

Expand All @@ -193,15 +152,15 @@ describe('FileAnalyzer', () => {
contract A { }
contract B is A {
some body here...
// some body here...
}
library L {
l...
// l...
}
interface B {
i...
// i...
}
`);

Expand All @@ -211,19 +170,19 @@ describe('FileAnalyzer', () => {
type: 'contract',
name: 'B',
is: 'is A ',
body: '{\n some body here...\n }',
body: '{\n // some body here...\n }',
},
{
type: 'library',
name: 'L',
is: '',
body: '{\n l...\n }',
body: '{\n // l...\n }',
},
{
type: 'interface',
name: 'B',
is: '',
body: '{\n i...\n }',
body: '{\n // i...\n }',
},
]);
});
Expand Down

0 comments on commit cbee328

Please sign in to comment.