diff --git a/src/parser.ts b/src/parser.ts deleted file mode 100644 index c5aaa13..0000000 --- a/src/parser.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Natspec, NatspecDefinition } from './types/natspec.t'; -import { NodeToProcess } from './types/solc-typed-ast.t'; - -export class Parser { - parseNodeNatspec(node: NodeToProcess): Natspec { - if (!node.documentation) { - return { tags: [], params: [], returns: [] }; - } - - let currentTag: NatspecDefinition | null = null; - const result: Natspec = { - tags: [], - params: [], - returns: [], - }; - - const docText: string = typeof node.documentation === 'string' ? node.documentation : node.documentation.text; - - docText.split('\n').forEach((line) => { - const tagTypeMatch = line.match(/^\s*@(\w+)/); - if (tagTypeMatch) { - const tagName = tagTypeMatch[1]; - - if (tagName === 'inheritdoc') { - const tagMatch = line.match(/^\s*@(\w+) (.*)$/); - if (tagMatch) { - currentTag = null; - result.inheritdoc = { content: tagMatch[2] }; - } - } else if (tagName === 'param' || tagName === 'return') { - const tagMatch = line.match(/^\s*@(\w+) *(\w+) (.*)$/); - if (tagMatch) { - currentTag = { name: tagMatch[2], content: tagMatch[3].trim() }; - result[tagName === 'param' ? 'params' : 'returns'].push(currentTag); - } - } else { - const tagMatch = line.match(/^\s*@(\w+) *(.*)$/); - if (tagMatch) { - currentTag = { name: tagName, content: tagMatch[2] }; - result.tags.push(currentTag); - } - } - } else if (currentTag) { - currentTag.content += '\n' + line; - } - }); - - return result; - } -} diff --git a/src/processor.ts b/src/processor.ts index 60847fe..0768565 100644 --- a/src/processor.ts +++ b/src/processor.ts @@ -1,9 +1,9 @@ import fs from 'fs'; -import { Parser } from './parser'; import { Config } from './types/config.t'; import { Validator } from './validator'; import { SourceUnit, FunctionDefinition, ContractDefinition } from 'solc-typed-ast'; import { NodeToProcess } from './types/solc-typed-ast.t'; +import { parseNodeNatspec } from './utils'; interface IWarning { location: string; @@ -13,12 +13,10 @@ interface IWarning { export class Processor { config: Config; validator: Validator; - parser: Parser; constructor(config: Config) { this.config = config; this.validator = new Validator(config); - this.parser = new Parser(); } processSources(sourceUnits: SourceUnit[]): IWarning[] { @@ -42,7 +40,7 @@ export class Processor { } validateNatspec(node: NodeToProcess): string[] { - const nodeNatspec = this.parser.parseNodeNatspec(node); + const nodeNatspec = parseNodeNatspec(node); return this.validator.validate(node, nodeNatspec); } diff --git a/src/utils.ts b/src/utils.ts index 5f9c9bd..959d488 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,8 @@ import fs from 'fs/promises'; import path from 'path'; import { ASTKind, ASTReader, SourceUnit, compileSol } from 'solc-typed-ast'; +import { Natspec, NatspecDefinition } from './types/natspec.t'; +import { NodeToProcess } from './types/solc-typed-ast.t'; export async function getProjectCompiledSources(rootPath: string, contractsPath: string, ignoredPaths: string[]): Promise { // Fetch Solidity files from the specified directory @@ -62,3 +64,49 @@ export async function getRemappings(rootPath: string): Promise { return []; } } + +export function parseNodeNatspec(node: NodeToProcess): Natspec { + if (!node.documentation) { + return { tags: [], params: [], returns: [] }; + } + + let currentTag: NatspecDefinition | null = null; + const result: Natspec = { + tags: [], + params: [], + returns: [], + }; + + const docText: string = typeof node.documentation === 'string' ? node.documentation : node.documentation.text; + + docText.split('\n').forEach((line) => { + const tagTypeMatch = line.match(/^\s*@(\w+)/); + if (tagTypeMatch) { + const tagName = tagTypeMatch[1]; + + if (tagName === 'inheritdoc') { + const tagMatch = line.match(/^\s*@(\w+) (.*)$/); + if (tagMatch) { + currentTag = null; + result.inheritdoc = { content: tagMatch[2] }; + } + } else if (tagName === 'param' || tagName === 'return') { + const tagMatch = line.match(/^\s*@(\w+) *(\w+) (.*)$/); + if (tagMatch) { + currentTag = { name: tagMatch[2], content: tagMatch[3].trim() }; + result[tagName === 'param' ? 'params' : 'returns'].push(currentTag); + } + } else { + const tagMatch = line.match(/^\s*@(\w+) *(.*)$/); + if (tagMatch) { + currentTag = { name: tagName, content: tagMatch[2] }; + result.tags.push(currentTag); + } + } + } else if (currentTag) { + currentTag.content += '\n' + line; + } + }); + + return result; +} diff --git a/test/parser.test.ts b/test/parser.test.ts index 3ccc4aa..5cdbb8d 100644 --- a/test/parser.test.ts +++ b/test/parser.test.ts @@ -1,10 +1,9 @@ import { ContractDefinition } from 'solc-typed-ast'; -import { Parser } from '../src/parser'; import { getFileCompiledSource } from './utils'; +import { parseNodeNatspec } from '../src/utils'; import { mockNatspec } from './mocks'; describe('Parser', () => { - const parser: Parser = new Parser(); let contract: ContractDefinition; describe('Contract', () => { @@ -15,7 +14,7 @@ describe('Parser', () => { it('should parse the inheritdoc tag', async () => { const node = contract.vFunctions.find(({ name }) => name === 'viewFunctionNoParams')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -32,7 +31,7 @@ describe('Parser', () => { it('should parse constant', async () => { const node = contract.vStateVariables.find(({ name }) => name === 'SOME_CONSTANT')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -45,7 +44,7 @@ describe('Parser', () => { it('should parse variable', async () => { const node = contract.vStateVariables.find(({ name }) => name === 'someVariable')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -58,7 +57,7 @@ describe('Parser', () => { it('should parse modifier', async () => { const node = contract.vModifiers.find(({ name }) => name === 'someModifier')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -80,7 +79,7 @@ describe('Parser', () => { it('should parse external function', async () => { const node = contract.vFunctions.find(({ name }) => name === 'viewFunctionNoParams')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -99,7 +98,7 @@ describe('Parser', () => { it('should parse private function', async () => { const node = contract.vFunctions.find(({ name }) => name === '_viewPrivate')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -131,7 +130,7 @@ describe('Parser', () => { it('should parse multiline descriptions', async () => { const node = contract.vFunctions.find(({ name }) => name === '_viewMultiline')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -147,7 +146,7 @@ describe('Parser', () => { it('should parse multiple of the same tag', async () => { const node = contract.vFunctions.find(({ name }) => name === '_viewDuplicateTag')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -174,7 +173,7 @@ describe('Parser', () => { it('should parse error', async () => { const node = contract.vErrors.find(({ name }) => name === 'SimpleError')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -190,7 +189,7 @@ describe('Parser', () => { it('should parse event', async () => { const node = contract.vEvents.find(({ name }) => name === 'SimpleEvent')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -206,7 +205,7 @@ describe('Parser', () => { it('should parse struct', async () => { const node = contract.vStructs.find(({ name }) => name === 'SimplestStruct')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -235,7 +234,7 @@ describe('Parser', () => { // TODO: Parse natspec for enums // it('should parse enum', async () => { // const node = contract.vEnums.find(({ name }) => name === 'SimpleEnum')!; - // const result = parser.parseNodeNatspec(node); + // const result = parseNodeNatspec(node); // expect(result).toEqual(mockNatspec({ // tags: [], @@ -244,7 +243,7 @@ describe('Parser', () => { it('should parse external function without parameters', async () => { const node = contract.vFunctions.find(({ name }) => name === 'viewFunctionNoParams')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -270,7 +269,7 @@ describe('Parser', () => { it('should parse external function with parameters', async () => { const node = contract.vFunctions.find(({ name }) => name === 'viewFunctionWithParams')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -309,7 +308,7 @@ describe('Parser', () => { it('should parse struct', async () => { const node = contract.vStructs.find(({ name }) => name === 'SimpleStruct')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -320,7 +319,7 @@ describe('Parser', () => { it('should parse inheritdoc + natspec', async () => { const node = contract.vStateVariables.find(({ name }) => name === 'someVariable')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -339,21 +338,21 @@ describe('Parser', () => { it('should not parse the inheritdoc tag with just 2 slashes', async () => { const node = contract.vStateVariables.find(({ name }) => name === 'SOME_CONSTANT')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual(mockNatspec({})); }); it('should not parse regular comments as natspec', async () => { const node = contract.vFunctions.find(({ name }) => name === 'viewFunctionWithParams')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual(mockNatspec({})); }); it('should parse natspec with multiple spaces', async () => { const node = contract.vFunctions.find(({ name }) => name === '_viewPrivate')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -381,14 +380,14 @@ describe('Parser', () => { it('should not parse natspec with invalid number of slashes', async () => { const node = contract.vFunctions.find(({ name }) => name === '_viewInternal')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual(mockNatspec({})); }); it('should parse block natspec with invalid formatting', async () => { const node = contract.vFunctions.find(({ name }) => name === '_viewBlockLinterFail')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({ @@ -404,7 +403,7 @@ describe('Parser', () => { it('should parse block natspec with invalid formatting', async () => { const node = contract.vFunctions.find(({ name }) => name === '_viewLinterFail')!; - const result = parser.parseNodeNatspec(node); + const result = parseNodeNatspec(node); expect(result).toEqual( mockNatspec({