From f435429e609d38c6061258e7a4472480e52421b9 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Thu, 11 Jan 2024 16:45:22 +0400 Subject: [PATCH] style: fix the linter issues --- .github/workflows/canary.yml | 6 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 6 +- .github/workflows/test.yml | 32 +- README.md | 6 + commitlint.config.js | 2 +- jest.config.js | 2 +- sample-data/BasicSample.sol | 130 +++--- sample-data/InterfacedSample.sol | 20 +- sample-data/LibrarySample.sol | 8 +- sample-data/ignored-data/IgnoredContract.sol | 8 +- sample-data/tests/BasicTest.t.sol | 6 +- src/main.ts | 84 ++-- src/parser.ts | 84 ++-- src/processor.ts | 93 ++-- src/types/natspec.t.ts | 12 +- src/types/solc-typed-ast.t.ts | 33 +- src/utils.ts | 102 ++--- src/validator.ts | 160 ++++--- test/parser.test.ts | 298 ++++++------ test/processor.test.ts | 36 +- test/validator.test.ts | 457 +++++++++---------- tsconfig.json | 15 +- 23 files changed, 830 insertions(+), 772 deletions(-) diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 29edc98..7a11d7c 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -14,8 +14,8 @@ jobs: - name: Install Node uses: actions/setup-node@v4 with: - registry-url: 'https://registry.npmjs.org' - cache: 'yarn' + registry-url: "https://registry.npmjs.org" + cache: "yarn" - name: Install Dependencies run: yarn --frozen-lockfile @@ -29,4 +29,4 @@ jobs: - name: Publish run: npm publish --access public --tag canary env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 86f1bec..d90dab9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 18.x - cache: 'yarn' + cache: "yarn" - name: Install dependencies run: yarn --frozen-lockfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a865eed..278e66b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,8 +16,8 @@ jobs: - name: Install Node uses: actions/setup-node@v4 with: - registry-url: 'https://registry.npmjs.org' - cache: 'yarn' + registry-url: "https://registry.npmjs.org" + cache: "yarn" - name: Install Dependencies run: yarn --frozen-lockfile @@ -28,4 +28,4 @@ jobs: - name: Publish run: yarn publish --access public --tag latest env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b14ff41..06a3e61 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,24 +16,24 @@ jobs: node-version: [16.x, 18.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} - - name: Install Solc AST binary - run: yarn global add solc-typed-ast + - name: Install Solc AST binary + run: yarn global add solc-typed-ast - - name: Install dependencies - run: yarn install + - name: Install dependencies + run: yarn install - - name: Pre-download compilers from historical builds archive - run: | - sol-ast-compile --download-compilers native wasm - find $SOL_AST_COMPILER_CACHE -name 'list.json' -delete - find $SOL_AST_COMPILER_CACHE -name '*v0.5.17*' -delete + - name: Pre-download compilers from historical builds archive + run: | + sol-ast-compile --download-compilers native wasm + find $SOL_AST_COMPILER_CACHE -name 'list.json' -delete + find $SOL_AST_COMPILER_CACHE -name '*v0.5.17*' -delete - - name: Run tests - run: yarn test + - name: Run tests + run: yarn test diff --git a/README.md b/README.md index 41e39cf..bc0dda2 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Some description will be written here. ## Usage As simple as it gets, run: + ```bash npx @defi-wonderland/natspec-smells --contracts ./solidity ``` @@ -14,24 +15,29 @@ npx @defi-wonderland/natspec-smells --contracts ./solidity ## Options ### `--contracts` (Required) + Relative path to your solidity files. ### `--root` + Root directory to be used. Default: `./` ### `--enforceInheritdoc` + Whether `@inheritdoc` is used or not. Default: `true` ### `--constructorNatspec` + Whether to enforce natspec for constructors. Default: `false` ### `--ignore` + Glob pattern of files and directories to exclude from processing. Default: `[]` diff --git a/commitlint.config.js b/commitlint.config.js index 4fedde6..422b194 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1 +1 @@ -module.exports = { extends: ['@commitlint/config-conventional'] } +module.exports = { extends: ['@commitlint/config-conventional'] }; diff --git a/jest.config.js b/jest.config.js index b413e10..3745fc2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,4 +2,4 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', -}; \ No newline at end of file +}; diff --git a/sample-data/BasicSample.sol b/sample-data/BasicSample.sol index 09cc57d..239fbac 100644 --- a/sample-data/BasicSample.sol +++ b/sample-data/BasicSample.sol @@ -2,76 +2,80 @@ pragma solidity =0.8.19; contract BasicSample { - /** - * @notice Some notice of the struct - */ - struct TestStruct { - address someAddress; - uint256 someNumber; - } + /** + * @notice Some notice of the struct + */ + struct TestStruct { + address someAddress; + uint256 someNumber; + } - /** - * @notice Some error missing parameter natspec - */ - error BasicSample_SomeError(uint256 _param1); + /** + * @notice Some error missing parameter natspec + */ + error BasicSample_SomeError(uint256 _param1); - /** - * @notice An event missing parameter natspec - */ - event BasicSample_BasicEvent(uint256 _param1); + /** + * @notice An event missing parameter natspec + */ + event BasicSample_BasicEvent(uint256 _param1); - /** - * @notice Empty string for revert checks - * @dev result of doing keccak256(bytes('')) - */ - bytes32 internal constant _EMPTY_STRING = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + /** + * @notice Empty string for revert checks + * @dev result of doing keccak256(bytes('')) + */ + bytes32 internal constant _EMPTY_STRING = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; - /** - * @notice External function that returns a bool - * @dev A dev comment - * @param _magicNumber A parameter description - * @param _name Another parameter description - * @return _isMagic Some return data - */ - function externalSimple(uint256 _magicNumber, string memory _name) external pure returns(bool _isMagic) { - return true; - } + /** + * @notice External function that returns a bool + * @dev A dev comment + * @param _magicNumber A parameter description + * @param _name Another parameter description + * @return _isMagic Some return data + */ + function externalSimple(uint256 _magicNumber, string memory _name) external pure returns (bool _isMagic) { + return true; + } - /** - * @notice Private test function - * @param _magicNumber A parameter description - */ - function privateSimple(uint256 _magicNumber) private pure {} + /** + * @notice Private test function + * @param _magicNumber A parameter description + */ + function privateSimple(uint256 _magicNumber) private pure {} - /** - * @notice Private test function - * with multiple - * lines - */ - function multiline() external pure {} + /** + * @notice Private test function + * with multiple + * lines + */ + function multiline() external pure {} - /** - * @notice Private test function - * @notice Another notice - */ - function multitag() external pure {} + /** + * @notice Private test function + * @notice Another notice + */ + function multitag() external pure {} - /** - * @notice External function that returns a bool - * @dev A dev comment - * @param _magicNumber A parameter description - * @param _name Another parameter description - * @return _isMagic Some return data - * @return Test test - */ - function externalSimpleMultipleReturn(uint256 _magicNumber, string memory _name) external pure returns(bool _isMagic, uint256) { - return (true, 111); - } + /** + * @notice External function that returns a bool + * @dev A dev comment + * @param _magicNumber A parameter description + * @param _name Another parameter description + * @return _isMagic Some return data + * @return Test test + */ + function externalSimpleMultipleReturn(uint256 _magicNumber, string memory _name) + external + pure + returns (bool _isMagic, uint256) + { + return (true, 111); + } - /** - * @notice Modifier notice - */ - modifier transferFee(uint256 _receiver) { - _; - } + /** + * @notice Modifier notice + */ + modifier transferFee(uint256 _receiver) { + _; + } } diff --git a/sample-data/InterfacedSample.sol b/sample-data/InterfacedSample.sol index ead2a2e..990e7f5 100644 --- a/sample-data/InterfacedSample.sol +++ b/sample-data/InterfacedSample.sol @@ -2,17 +2,17 @@ pragma solidity =0.8.19; interface IInterfacedSample { - /** - * @notice Greets the caller - * - * @return _greeting The greeting - * @return _balance Current token balance of the caller - */ - function greet() external view returns (string memory _greeting, uint256 _balance); + /** + * @notice Greets the caller + * + * @return _greeting The greeting + * @return _balance Current token balance of the caller + */ + function greet() external view returns (string memory _greeting, uint256 _balance); } contract InterfacedSample is IInterfacedSample { - /// @inheritdoc IInterfacedSample - /// @dev some dev thingy - function greet() external view returns (string memory _greeting, uint256 _balance) {} + /// @inheritdoc IInterfacedSample + /// @dev some dev thingy + function greet() external view returns (string memory _greeting, uint256 _balance) {} } diff --git a/sample-data/LibrarySample.sol b/sample-data/LibrarySample.sol index 8effeda..c49644d 100644 --- a/sample-data/LibrarySample.sol +++ b/sample-data/LibrarySample.sol @@ -2,11 +2,11 @@ pragma solidity =0.8.19; library StringUtils { - function nothing(string memory input) public pure returns (string memory) { - return input; - } + function nothing(string memory input) public pure returns (string memory) { + return input; + } } contract BasicSample { - using StringUtils for string; + using StringUtils for string; } diff --git a/sample-data/ignored-data/IgnoredContract.sol b/sample-data/ignored-data/IgnoredContract.sol index 95fad93..8661dbd 100644 --- a/sample-data/ignored-data/IgnoredContract.sol +++ b/sample-data/ignored-data/IgnoredContract.sol @@ -2,8 +2,8 @@ pragma solidity =0.8.19; contract IgnoredContract { - struct AnotherStruct { - address someAddress; - uint256 someNumber; - } + struct AnotherStruct { + address someAddress; + uint256 someNumber; + } } diff --git a/sample-data/tests/BasicTest.t.sol b/sample-data/tests/BasicTest.t.sol index c6f9d1e..6a24e94 100644 --- a/sample-data/tests/BasicTest.t.sol +++ b/sample-data/tests/BasicTest.t.sol @@ -2,7 +2,7 @@ pragma solidity =0.8.19; contract BasicTest { - function testFunction() pure external returns(bool) { - return true; - } + function testFunction() external pure returns (bool) { + return true; + } } diff --git a/src/main.ts b/src/main.ts index cccb79a..ec4bba6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,52 +6,52 @@ import { getProjectCompiledSources, Config } from './utils'; import { processSources } from './processor'; (async () => { - const config: Config = getArguments(); - const ignoredPaths = config.ignore.map(path => globSync(path, { cwd: config.root })).flat(); - const sourceUnits = await getProjectCompiledSources(config.root, config.contracts, ignoredPaths); - if (!sourceUnits.length) return console.error('No solidity files found in the specified directory'); + const config: Config = getArguments(); + const ignoredPaths = config.ignore.map((path) => globSync(path, { cwd: config.root })).flat(); + const sourceUnits = await getProjectCompiledSources(config.root, config.contracts, ignoredPaths); + if (!sourceUnits.length) return console.error('No solidity files found in the specified directory'); - const warnings = await processSources(sourceUnits, config); + const warnings = await processSources(sourceUnits, config); - warnings.forEach(({ location, messages }) => { - console.warn(location); - messages.forEach(message => { - console.warn(` ${message}`); - }); - console.warn(); + warnings.forEach(({ location, messages }) => { + console.warn(location); + messages.forEach((message) => { + console.warn(` ${message}`); }); + console.warn(); + }); })().catch(console.error); function getArguments(): Config { - return yargs(hideBin(process.argv)) - .strict() - .options({ - root: { - type: 'string', - description: 'The target root directory', - default: './', - }, - contracts: { - type: 'string', - description: 'The directory containing your Solidity contracts', - required: true, - }, - enforceInheritdoc: { - type: 'boolean', - description: 'If set to true, all external and public functions must have @inheritdoc', - default: true, - }, - constructorNatspec: { - type: 'boolean', - description: 'True if constructor natspec is mandatory', - default: false, - }, - ignore: { - describe: 'Glob pattern of files and directories to exclude from processing', - default: [], - type: 'array', - string: true, - }, - }) - .parseSync(); + return yargs(hideBin(process.argv)) + .strict() + .options({ + root: { + type: 'string', + description: 'The target root directory', + default: './', + }, + contracts: { + type: 'string', + description: 'The directory containing your Solidity contracts', + required: true, + }, + enforceInheritdoc: { + type: 'boolean', + description: 'If set to true, all external and public functions must have @inheritdoc', + default: true, + }, + constructorNatspec: { + type: 'boolean', + description: 'True if constructor natspec is mandatory', + default: false, + }, + ignore: { + describe: 'Glob pattern of files and directories to exclude from processing', + default: [], + type: 'array', + string: true, + }, + }) + .parseSync(); } diff --git a/src/parser.ts b/src/parser.ts index 3eb75c6..891ecd4 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,48 +1,48 @@ -import { Natspec, NatspecDefinition } from "./types/natspec.t"; -import { NodeToProcess } from "./types/solc-typed-ast.t"; +import { Natspec, NatspecDefinition } from './types/natspec.t'; +import { NodeToProcess } from './types/solc-typed-ast.t'; export function parseNodeNatspec(node: NodeToProcess): Natspec { - if (!node.documentation) { - return { tags: [], params: [], returns: [] }; - } + if (!node.documentation) { + return { tags: [], params: [], returns: [] }; + } + + const docText: string = typeof node.documentation === 'string' ? node.documentation : node.documentation.text; + + let currentTag: NatspecDefinition | null = null; + const result: Natspec = { + tags: [], + params: [], + returns: [], + }; - const docText: string = typeof node.documentation === "string" ? node.documentation : node.documentation.text; - - let currentTag: NatspecDefinition | null = null; - const result: Natspec = { - tags: [], - params: [], - returns: [] - }; + docText.split('\n').forEach((line) => { + const tagTypeMatch = line.match(/^\s*@(\w+)/); + if (tagTypeMatch) { + const tagName = tagTypeMatch[1]; - 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; + 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; -}; + return result; +} diff --git a/src/processor.ts b/src/processor.ts index c71b8ce..b937d13 100644 --- a/src/processor.ts +++ b/src/processor.ts @@ -1,64 +1,63 @@ -import { parseNodeNatspec } from "./parser"; +import { parseNodeNatspec } from './parser'; import { Config } from './utils'; -import { validate } from "./validator"; +import { validate } from './validator'; import { SourceUnit, FunctionDefinition } from 'solc-typed-ast'; import fs from 'fs'; interface IWarning { - location: string; - messages: string[]; + location: string; + messages: string[]; } export async function processSources(sourceUnits: SourceUnit[], config: Config): Promise { - let warnings: IWarning[] = []; - - sourceUnits.forEach(sourceUnit => { - sourceUnit.vContracts.forEach(contract => { - [ - ...contract.vEnums, - ...contract.vErrors, - ...contract.vEvents, - ...contract.vFunctions, - ...contract.vModifiers, - ...contract.vStateVariables, - ...contract.vStructs - ] - .forEach(node => { - if (!node) return; - - const nodeNatspec = parseNodeNatspec(node); - const validationMessages = validate(node, nodeNatspec, config); - - // the constructor function definition does not have a name, but it has kind: 'constructor' - const nodeName = node instanceof FunctionDefinition ? node.name || node.kind : node.name; - const sourceCode = fs.readFileSync(sourceUnit.absolutePath, "utf8"); - const line = lineNumber(nodeName as string, sourceCode); - - if (validationMessages.length) { - warnings.push({ - location: `${sourceUnit.absolutePath}:${line}\n${contract.name}:${nodeName}`, - messages: validationMessages, - }); - } - }); - }); + let warnings: IWarning[] = []; + + sourceUnits.forEach((sourceUnit) => { + sourceUnit.vContracts.forEach((contract) => { + [ + ...contract.vEnums, + ...contract.vErrors, + ...contract.vEvents, + ...contract.vFunctions, + ...contract.vModifiers, + ...contract.vStateVariables, + ...contract.vStructs, + ].forEach((node) => { + if (!node) return; + + const nodeNatspec = parseNodeNatspec(node); + const validationMessages = validate(node, nodeNatspec, config); + + // the constructor function definition does not have a name, but it has kind: 'constructor' + const nodeName = node instanceof FunctionDefinition ? node.name || node.kind : node.name; + const sourceCode = fs.readFileSync(sourceUnit.absolutePath, 'utf8'); + const line = lineNumber(nodeName as string, sourceCode); + + if (validationMessages.length) { + warnings.push({ + location: `${sourceUnit.absolutePath}:${line}\n${contract.name}:${nodeName}`, + messages: validationMessages, + }); + } + }); }); + }); - return warnings; + return warnings; } function lineNumberByIndex(index: number, string: string): Number { - let line = 0 - let match; - let re = /(^)[\S\s]/gm; - - while (match = re.exec(string)) { - if(match.index > index) break; - line++; - } - return line; + let line = 0; + let match; + let re = /(^)[\S\s]/gm; + + while ((match = re.exec(string))) { + if (match.index > index) break; + line++; + } + return line; } function lineNumber(needle: string, haystack: string): Number { - return lineNumberByIndex(haystack.indexOf(needle), haystack); + return lineNumberByIndex(haystack.indexOf(needle), haystack); } diff --git a/src/types/natspec.t.ts b/src/types/natspec.t.ts index 7e9defc..ccd7586 100644 --- a/src/types/natspec.t.ts +++ b/src/types/natspec.t.ts @@ -1,11 +1,11 @@ export interface NatspecDefinition { - name?: string; - content: string; + name?: string; + content: string; } export interface Natspec { - inheritdoc?: NatspecDefinition; - tags: NatspecDefinition[]; - params: NatspecDefinition[]; - returns: NatspecDefinition[]; + inheritdoc?: NatspecDefinition; + tags: NatspecDefinition[]; + params: NatspecDefinition[]; + returns: NatspecDefinition[]; } diff --git a/src/types/solc-typed-ast.t.ts b/src/types/solc-typed-ast.t.ts index 94dc34e..c293c55 100644 --- a/src/types/solc-typed-ast.t.ts +++ b/src/types/solc-typed-ast.t.ts @@ -1,16 +1,31 @@ -import { EnumDefinition, ErrorDefinition, EventDefinition, FunctionDefinition, ModifierDefinition, StructDefinition, VariableDeclaration } from "solc-typed-ast"; +import { + EnumDefinition, + ErrorDefinition, + EventDefinition, + FunctionDefinition, + ModifierDefinition, + StructDefinition, + VariableDeclaration, +} from 'solc-typed-ast'; export interface ASTNodeRawDocumentation { - id: number, - nodeType: string, - src: string, - text: string + id: number; + nodeType: string; + src: string; + text: string; } export interface ASTNodeRaw { - name: string, - kind: string, - documentation?: ASTNodeRawDocumentation + name: string; + kind: string; + documentation?: ASTNodeRawDocumentation; } -export type NodeToProcess = FunctionDefinition | EnumDefinition | ErrorDefinition | EventDefinition | ModifierDefinition | VariableDeclaration | StructDefinition; +export type NodeToProcess = + | FunctionDefinition + | EnumDefinition + | ErrorDefinition + | EventDefinition + | ModifierDefinition + | VariableDeclaration + | StructDefinition; diff --git a/src/utils.ts b/src/utils.ts index c88404c..4c0e870 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,75 +3,75 @@ import path from 'path'; import { ASTKind, ASTReader, SourceUnit, compileSol } from 'solc-typed-ast'; export interface Config { - root: string; - contracts: string, - enforceInheritdoc: boolean, - constructorNatspec: boolean, - ignore: string[], + root: string; + contracts: string; + enforceInheritdoc: boolean; + constructorNatspec: boolean; + ignore: string[]; } export async function getSolidityFiles(dir: string): Promise { - let files = await fs.readdir(dir, { withFileTypes: true }); - let solidityFiles: string[] = []; + let files = await fs.readdir(dir, { withFileTypes: true }); + let solidityFiles: string[] = []; - for (const file of files) { - const res = path.resolve(dir, file.name); - if (file.isDirectory()) { - solidityFiles = solidityFiles.concat(await getSolidityFiles(res)); - } else if (file.isFile() && file.name.endsWith('.sol')) { - solidityFiles.push(res); - } + for (const file of files) { + const res = path.resolve(dir, file.name); + if (file.isDirectory()) { + solidityFiles = solidityFiles.concat(await getSolidityFiles(res)); + } else if (file.isFile() && file.name.endsWith('.sol')) { + solidityFiles.push(res); } + } - return solidityFiles; + return solidityFiles; } export async function getRemappings(rootPath: string): Promise { - try { + try { + const filePath = path.join(rootPath, 'remappings.txt'); + const fileContent = await fs.readFile(filePath, 'utf8'); - const filePath = path.join(rootPath, 'remappings.txt'); - const fileContent = await fs.readFile(filePath, 'utf8'); - - return fileContent - .split('\n') - .map(line => line.trim()) - .filter(line => line.length) - .map(line => line.slice(-1) === '/' ? line : line + '/'); - } catch (e) { - return []; - } + return fileContent + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.length) + .map((line) => (line.slice(-1) === '/' ? line : line + '/')); + } catch (e) { + return []; + } } export async function getProjectCompiledSources(rootPath: string, contractsPath: string, ignoredPaths: string[]): Promise { - // Fetch Solidity files from the specified directory - const solidityFiles: string[] = await getSolidityFiles(contractsPath); - const remappings: string[] = await getRemappings(rootPath); + // Fetch Solidity files from the specified directory + const solidityFiles: string[] = await getSolidityFiles(contractsPath); + const remappings: string[] = await getRemappings(rootPath); - const compiledFiles = await compileSol(solidityFiles, 'auto', { - basePath: rootPath, - remapping: remappings, - includePath: [rootPath], - }); + const compiledFiles = await compileSol(solidityFiles, 'auto', { + basePath: rootPath, + remapping: remappings, + includePath: [rootPath], + }); - return new ASTReader() - .read(compiledFiles.data, ASTKind.Any, compiledFiles.files) - // avoid processing files that are not in the specified directory, e.g. node modules or other imported files - .filter(sourceUnit => isFileInDirectory(contractsPath, sourceUnit.absolutePath)) - // avoid processing files from ignored directories - .filter(sourceUnit => !ignoredPaths.some(ignoredPath => ignoredPath === sourceUnit.absolutePath)); + return ( + new ASTReader() + .read(compiledFiles.data, ASTKind.Any, compiledFiles.files) + // avoid processing files that are not in the specified directory, e.g. node modules or other imported files + .filter((sourceUnit) => isFileInDirectory(contractsPath, sourceUnit.absolutePath)) + // avoid processing files from ignored directories + .filter((sourceUnit) => !ignoredPaths.some((ignoredPath) => ignoredPath === sourceUnit.absolutePath)) + ); } export async function getFileCompiledSource(filePath: string): Promise { - const compiledFile = await compileSol(filePath, 'auto'); - return new ASTReader() - .read(compiledFile.data, ASTKind.Any, compiledFile.files)[0] -}; + const compiledFile = await compileSol(filePath, 'auto'); + return new ASTReader().read(compiledFile.data, ASTKind.Any, compiledFile.files)[0]; +} export function isFileInDirectory(directory: string, filePath: string): boolean { - // Convert both paths to absolute and normalize them - const absoluteDirectoryPath = path.resolve(directory) + path.sep; - const absoluteFilePath = path.resolve(filePath); + // Convert both paths to absolute and normalize them + const absoluteDirectoryPath = path.resolve(directory) + path.sep; + const absoluteFilePath = path.resolve(filePath); - // Check if the file path starts with the directory path - return absoluteFilePath.startsWith(absoluteDirectoryPath); -} \ No newline at end of file + // Check if the file path starts with the directory path + return absoluteFilePath.startsWith(absoluteDirectoryPath); +} diff --git a/src/validator.ts b/src/validator.ts index 8dbd632..e056f83 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -1,108 +1,116 @@ import { Natspec } from '../src/types/natspec.t'; import { Config } from './utils'; import { NodeToProcess } from './types/solc-typed-ast.t'; -import { EnumDefinition, ErrorDefinition, EventDefinition, FunctionDefinition, ModifierDefinition, StructDefinition, VariableDeclaration } from "solc-typed-ast"; +import { + EnumDefinition, + ErrorDefinition, + EventDefinition, + FunctionDefinition, + ModifierDefinition, + StructDefinition, + VariableDeclaration, +} from 'solc-typed-ast'; export function validate(node: NodeToProcess, natspec: Natspec, config: Config): string[] { - // There is inheritdoc, no other validation is needed - if(natspec.inheritdoc) return []; - - // Inheritdoc is enforced but not present, returning an error - if(config.enforceInheritdoc && requiresInheritdoc(node)) return [`@inheritdoc is missing`]; - - // Validate natspec for the constructor only if configured - if(node instanceof FunctionDefinition && node.kind === 'constructor') { - return config.constructorNatspec ? validateParameters(node, natspec) : []; - } - - // Inheritdoc is not enforced nor present, and there is no other documentation, returning error - if(!natspec.tags.length) return [`Natspec is missing`]; - - // Validate the completeness of the documentation - let alerts: string[] = []; - if(node instanceof EnumDefinition) { - // TODO: Process enums - } else if(node instanceof ErrorDefinition) { - alerts = [...alerts, ...validateParameters(node, natspec)]; - } else if(node instanceof EventDefinition) { - alerts = [...alerts, ...validateParameters(node, natspec)]; - } else if(node instanceof FunctionDefinition) { - alerts = [...alerts, ...validateParameters(node, natspec), ...validateReturnParameters(node, natspec)]; - } else if(node instanceof ModifierDefinition) { - alerts = [...alerts, ...validateParameters(node, natspec)]; - } else if(node instanceof StructDefinition) { - alerts = [...alerts, ...validateMembers(node, natspec)]; - } else if(node instanceof VariableDeclaration) { - // Only the presence of a notice is validated - } - - return alerts; + // There is inheritdoc, no other validation is needed + if (natspec.inheritdoc) return []; + + // Inheritdoc is enforced but not present, returning an error + if (config.enforceInheritdoc && requiresInheritdoc(node)) return [`@inheritdoc is missing`]; + + // Validate natspec for the constructor only if configured + if (node instanceof FunctionDefinition && node.kind === 'constructor') { + return config.constructorNatspec ? validateParameters(node, natspec) : []; + } + + // Inheritdoc is not enforced nor present, and there is no other documentation, returning error + if (!natspec.tags.length) return [`Natspec is missing`]; + + // Validate the completeness of the documentation + let alerts: string[] = []; + if (node instanceof EnumDefinition) { + // TODO: Process enums + } else if (node instanceof ErrorDefinition) { + alerts = [...alerts, ...validateParameters(node, natspec)]; + } else if (node instanceof EventDefinition) { + alerts = [...alerts, ...validateParameters(node, natspec)]; + } else if (node instanceof FunctionDefinition) { + alerts = [...alerts, ...validateParameters(node, natspec), ...validateReturnParameters(node, natspec)]; + } else if (node instanceof ModifierDefinition) { + alerts = [...alerts, ...validateParameters(node, natspec)]; + } else if (node instanceof StructDefinition) { + alerts = [...alerts, ...validateMembers(node, natspec)]; + } else if (node instanceof VariableDeclaration) { + // Only the presence of a notice is validated + } + + return alerts; } function validateParameters(node: ErrorDefinition | FunctionDefinition | ModifierDefinition, natspec: Natspec): string[] { - // Make sure all defined parameters have natspec - let alerts: string[] = []; + // Make sure all defined parameters have natspec + let alerts: string[] = []; - let definedParameters = node.vParameters.vParameters.map(p => p.name); - let natspecParameters = natspec.params.map(p => p.name); + let definedParameters = node.vParameters.vParameters.map((p) => p.name); + let natspecParameters = natspec.params.map((p) => p.name); - for(let paramName of definedParameters) { - if(!natspecParameters.includes(paramName)) { - alerts.push(`@param ${paramName} is missing`); - } + for (let paramName of definedParameters) { + if (!natspecParameters.includes(paramName)) { + alerts.push(`@param ${paramName} is missing`); } + } - return alerts; + return alerts; } function validateReturnParameters(node: FunctionDefinition, natspec: Natspec): string[] { - let alerts: string[] = []; - let functionReturns = node.vReturnParameters.vParameters.map(p => p.name); - let natspecReturns = natspec.returns.map(p => p.name); - - // Make sure all defined returns have natspec - for(let paramName of functionReturns) { - if(!natspecReturns.includes(paramName)) { - let message = paramName === '' ? '@return missing for unnamed return' : `@return ${paramName} is missing`; - alerts.push(message); - } + let alerts: string[] = []; + let functionReturns = node.vReturnParameters.vParameters.map((p) => p.name); + let natspecReturns = natspec.returns.map((p) => p.name); + + // Make sure all defined returns have natspec + for (let paramName of functionReturns) { + if (!natspecReturns.includes(paramName)) { + let message = paramName === '' ? '@return missing for unnamed return' : `@return ${paramName} is missing`; + alerts.push(message); } + } - // Make sure there is no natspec defined for non-existing returns - for(let paramName of natspecReturns) { - if(paramName && !functionReturns.includes(paramName)) { - alerts.push(`Missing named return for: @return ${paramName}`); - } + // Make sure there is no natspec defined for non-existing returns + for (let paramName of natspecReturns) { + if (paramName && !functionReturns.includes(paramName)) { + alerts.push(`Missing named return for: @return ${paramName}`); } + } - return alerts; + return alerts; } function validateMembers(node: StructDefinition, natspec: Natspec): string[] { - let alerts: string[] = []; - let members = node.vMembers.map(p => p.name); - let natspecMembers = natspec.params.map(p => p.name); - - for(let memberName of members) { - if(!natspecMembers.includes(memberName)) { - alerts.push(`@param ${memberName} is missing`); - } + let alerts: string[] = []; + let members = node.vMembers.map((p) => p.name); + let natspecMembers = natspec.params.map((p) => p.name); + + for (let memberName of members) { + if (!natspecMembers.includes(memberName)) { + alerts.push(`@param ${memberName} is missing`); } + } - return alerts; + return alerts; } function requiresInheritdoc(node: NodeToProcess): boolean { - let _requiresInheritdoc: boolean = false; + let _requiresInheritdoc: boolean = false; - // External or public function - _requiresInheritdoc ||= node instanceof FunctionDefinition && (node.visibility === 'external' || node.visibility === 'public'); + // External or public function + _requiresInheritdoc ||= node instanceof FunctionDefinition && (node.visibility === 'external' || node.visibility === 'public'); - // Internal virtual function - _requiresInheritdoc ||= node instanceof FunctionDefinition && node.visibility === 'internal' && node.virtual; + // Internal virtual function + _requiresInheritdoc ||= node instanceof FunctionDefinition && node.visibility === 'internal' && node.virtual; - // Public variable - _requiresInheritdoc ||= node instanceof VariableDeclaration && node.visibility === 'public'; + // Public variable + _requiresInheritdoc ||= node instanceof VariableDeclaration && node.visibility === 'public'; - return _requiresInheritdoc; + return _requiresInheritdoc; } diff --git a/test/parser.test.ts b/test/parser.test.ts index 1210cc6..fd9c681 100644 --- a/test/parser.test.ts +++ b/test/parser.test.ts @@ -3,145 +3,167 @@ import { parseNodeNatspec } from '../src/parser'; import { getFileCompiledSource } from '../src/utils'; describe('parseNodeNatspec', () => { + describe('BasicSample.sol', () => { + let contract: ContractDefinition; - describe('BasicSample.sol', () => { - let contract: ContractDefinition; - - beforeAll(async () => { - const compileResult = await getFileCompiledSource('sample-data/BasicSample.sol'); - contract = compileResult.vContracts[0]; - }); - - it('should parse struct', async () => { - const structNode = contract.vStructs.find(({ name }) => name === 'TestStruct')!; - const result = parseNodeNatspec(structNode); - - expect(result).toEqual({ - tags: [{ - name: 'notice', - content: 'Some notice of the struct', - }], - params: [], - returns: [], - }); - }); - - it('should parse constant', async () => { - const emptyStringNode = contract.vStateVariables.find(({ name }) => name === '_EMPTY_STRING')!; - const result = parseNodeNatspec(emptyStringNode); - - expect(result).toEqual({ - tags: [{ - name: 'notice', - content: 'Empty string for revert checks', - }, { - name: 'dev', - content: `result of doing keccak256(bytes(''))`, - }], - params: [], - returns: [], - }); - }); - - it('should parse a fully natspeced external function', async () => { - const functionNode = contract.vFunctions.find(({ name }) => name === 'externalSimple')!; - const result = parseNodeNatspec(functionNode); - - expect(result).toEqual({ - tags: [{ - name: 'notice', - content: 'External function that returns a bool', - }, { - name: 'dev', - content: 'A dev comment', - }], - params: [{ - name: '_magicNumber', - content: 'A parameter description', - }, { - name: '_name', - content: 'Another parameter description', - }], - returns: [{ - name: '_isMagic', - content: 'Some return data', - }], - }); - }); - - it('should parse a fully natspeced private function', async () => { - const functionNode = contract.vFunctions.find(({ name }) => name === 'privateSimple')!; - const result = parseNodeNatspec(functionNode); - - expect(result).toEqual({ - tags: [{ - name: 'notice', - content: 'Private test function', - }], - params: [{ - name: '_magicNumber', - content: 'A parameter description', - }], - returns: [], - }); - }); - - it('should parse multiline descriptions', async () => { - const functionNode = contract.vFunctions.find(({ name }) => name === 'multiline')!; - const result = parseNodeNatspec(functionNode); - - expect(result).toEqual({ - tags: [{ - name: 'notice', - content: 'Private test function\n with multiple\n lines', - }], - params: [], - returns: [], - }); - }); - - it('should parse multiple of the same tag', async () => { - const functionNode = contract.vFunctions.find(({ name }) => name === 'multitag')!; - const result = parseNodeNatspec(functionNode); - - expect(result).toEqual({ - tags: [{ - name: 'notice', - content: 'Private test function', - }, { - name: 'notice', - content: 'Another notice', - }], - params: [], - returns: [], - }); - }); + beforeAll(async () => { + const compileResult = await getFileCompiledSource('sample-data/BasicSample.sol'); + contract = compileResult.vContracts[0]; }); - describe('InterfacedSample.sol', () => { - let contract: ContractDefinition; - - beforeAll(async () => { - const compileResult = await getFileCompiledSource('sample-data/InterfacedSample.sol'); - contract = compileResult.vContracts[1]; - }); - - - it('should parse the inheritdoc tag', async () => { - const functionNode = contract.vFunctions.find(({ name }) => name === 'greet')!; - const result = parseNodeNatspec(functionNode); - - expect(result).toEqual({ - inheritdoc: { - content: 'IInterfacedSample', - }, - tags: [{ - name: 'dev', - content: 'some dev thingy', - }], - params: [], - returns: [], - }); - }); + it('should parse struct', async () => { + const structNode = contract.vStructs.find(({ name }) => name === 'TestStruct')!; + const result = parseNodeNatspec(structNode); + + expect(result).toEqual({ + tags: [ + { + name: 'notice', + content: 'Some notice of the struct', + }, + ], + params: [], + returns: [], + }); }); -}); \ No newline at end of file + + it('should parse constant', async () => { + const emptyStringNode = contract.vStateVariables.find(({ name }) => name === '_EMPTY_STRING')!; + const result = parseNodeNatspec(emptyStringNode); + + expect(result).toEqual({ + tags: [ + { + name: 'notice', + content: 'Empty string for revert checks', + }, + { + name: 'dev', + content: `result of doing keccak256(bytes(''))`, + }, + ], + params: [], + returns: [], + }); + }); + + it('should parse a fully natspeced external function', async () => { + const functionNode = contract.vFunctions.find(({ name }) => name === 'externalSimple')!; + const result = parseNodeNatspec(functionNode); + + expect(result).toEqual({ + tags: [ + { + name: 'notice', + content: 'External function that returns a bool', + }, + { + name: 'dev', + content: 'A dev comment', + }, + ], + params: [ + { + name: '_magicNumber', + content: 'A parameter description', + }, + { + name: '_name', + content: 'Another parameter description', + }, + ], + returns: [ + { + name: '_isMagic', + content: 'Some return data', + }, + ], + }); + }); + + it('should parse a fully natspeced private function', async () => { + const functionNode = contract.vFunctions.find(({ name }) => name === 'privateSimple')!; + const result = parseNodeNatspec(functionNode); + + expect(result).toEqual({ + tags: [ + { + name: 'notice', + content: 'Private test function', + }, + ], + params: [ + { + name: '_magicNumber', + content: 'A parameter description', + }, + ], + returns: [], + }); + }); + + it('should parse multiline descriptions', async () => { + const functionNode = contract.vFunctions.find(({ name }) => name === 'multiline')!; + const result = parseNodeNatspec(functionNode); + + expect(result).toEqual({ + tags: [ + { + name: 'notice', + content: 'Private test function\n with multiple\n lines', + }, + ], + params: [], + returns: [], + }); + }); + + it('should parse multiple of the same tag', async () => { + const functionNode = contract.vFunctions.find(({ name }) => name === 'multitag')!; + const result = parseNodeNatspec(functionNode); + + expect(result).toEqual({ + tags: [ + { + name: 'notice', + content: 'Private test function', + }, + { + name: 'notice', + content: 'Another notice', + }, + ], + params: [], + returns: [], + }); + }); + }); + + describe('InterfacedSample.sol', () => { + let contract: ContractDefinition; + + beforeAll(async () => { + const compileResult = await getFileCompiledSource('sample-data/InterfacedSample.sol'); + contract = compileResult.vContracts[1]; + }); + + it('should parse the inheritdoc tag', async () => { + const functionNode = contract.vFunctions.find(({ name }) => name === 'greet')!; + const result = parseNodeNatspec(functionNode); + + expect(result).toEqual({ + inheritdoc: { + content: 'IInterfacedSample', + }, + tags: [ + { + name: 'dev', + content: 'some dev thingy', + }, + ], + params: [], + returns: [], + }); + }); + }); +}); diff --git a/test/processor.test.ts b/test/processor.test.ts index 1a30dd9..73f001a 100644 --- a/test/processor.test.ts +++ b/test/processor.test.ts @@ -3,24 +3,24 @@ import { getFileCompiledSource } from '../src/utils'; import { Config } from '../src/utils'; describe('processSources', () => { - const config: Config = { - root: '.', - contracts: './sample-data', - enforceInheritdoc: false, - constructorNatspec: false, - ignore: [] - }; + const config: Config = { + root: '.', + contracts: './sample-data', + enforceInheritdoc: false, + constructorNatspec: false, + ignore: [], + }; - describe('LibrarySample.sol', () => { - it('should return warnings only for the library method empty natspec', async () => { - const source = await getFileCompiledSource('sample-data/LibrarySample.sol'); - const warnings = await processSources([source], config); - expect(warnings).toEqual([ - { - location: "sample-data/LibrarySample.sol:5\nStringUtils:nothing", - messages: ['Natspec is missing'] - }, - ]); - }); + describe('LibrarySample.sol', () => { + it('should return warnings only for the library method empty natspec', async () => { + const source = await getFileCompiledSource('sample-data/LibrarySample.sol'); + const warnings = await processSources([source], config); + expect(warnings).toEqual([ + { + location: 'sample-data/LibrarySample.sol:5\nStringUtils:nothing', + messages: ['Natspec is missing'], + }, + ]); }); + }); }); diff --git a/test/validator.test.ts b/test/validator.test.ts index 98b88f3..82d9f02 100644 --- a/test/validator.test.ts +++ b/test/validator.test.ts @@ -1,242 +1,241 @@ import { validate } from '../src/validator'; import { Config } from '../src/utils'; import { getFileCompiledSource } from '../src/utils'; -import { NodeToProcess } from "../src/types/solc-typed-ast.t"; +import { NodeToProcess } from '../src/types/solc-typed-ast.t'; import { ContractDefinition } from 'solc-typed-ast'; describe('validator function', () => { - let contract: ContractDefinition; - let node: NodeToProcess; - - const config: Config = { - root: '.', - contracts: './sample-data', - enforceInheritdoc: false, - constructorNatspec: false, - ignore: [] + let contract: ContractDefinition; + let node: NodeToProcess; + + const config: Config = { + root: '.', + contracts: './sample-data', + enforceInheritdoc: false, + constructorNatspec: false, + ignore: [], + }; + + beforeAll(async () => { + const compileResult = await getFileCompiledSource('sample-data/BasicSample.sol'); + contract = compileResult.vContracts[0]; + node = contract.vFunctions.find(({ name }) => name === 'externalSimple')!; + }); + + let natspec = { + tags: [ + { + name: 'notice', + content: 'External function that returns a bool', + }, + { + name: 'dev', + content: 'A dev comment', + }, + ], + params: [ + { + name: '_magicNumber', + content: 'A parameter description', + }, + { + name: '_name', + content: 'Another parameter description', + }, + ], + returns: [ + { + name: '_isMagic', + content: 'Some return data', + }, + { + name: undefined, + content: 'Test test', + }, + ], + }; + + it('should validate proper natspec', () => { + const result = validate(node, natspec, config); + expect(result).toEqual([]); + }); + + it('should reveal missing natspec for parameters', () => { + const paramName = '_magicNumber'; + let natspec = { + tags: [ + { + name: 'notice', + content: 'External function that returns a bool', + }, + ], + params: [ + { + name: '_name', + content: 'Another parameter description', + }, + ], + returns: [ + { + name: '_isMagic', + content: 'Some return data', + }, + ], + }; + + const result = validate(node, natspec, config); + expect(result).toContainEqual(`@param ${paramName} is missing`); + }); + + it('should reveal missing natspec for returned values', () => { + const paramName = '_isMagic'; + let natspec = { + tags: [ + { + name: 'notice', + content: 'External function that returns a bool', + }, + { + name: 'dev', + content: 'A dev comment', + }, + ], + params: [ + { + name: '_magicNumber', + content: 'A parameter description', + }, + { + name: '_name', + content: 'Another parameter description', + }, + ], + returns: [], }; - beforeAll(async () => { - const file = 'sample-data/BasicSample.sol'; - const compileResult = await getFileCompiledSource('sample-data/BasicSample.sol'); - contract = compileResult.vContracts[0]; - node = contract.vFunctions.find(({ name }) => name === 'externalSimple')!; - }); + const result = validate(node, natspec, config); + expect(result).toContainEqual(`@return ${paramName} is missing`); + }); + it('should reveal missing natspec for unnamed returned values', () => { + node = contract.vFunctions.find(({ name }) => name === 'externalSimpleMultipleReturn')!; let natspec = { - tags: [ - { - name: 'notice', - content: 'External function that returns a bool' - }, - { - name: 'dev', - content: 'A dev comment' - } - ], - params: [ - { - name: '_magicNumber', - content: 'A parameter description' - }, - { - name: '_name', - content: 'Another parameter description' - } - ], - returns: [ - { - name: '_isMagic', - content: 'Some return data' - }, - { - name: undefined, - content: 'Test test' - } - ] + tags: [ + { + name: 'notice', + content: 'External function that returns a bool', + }, + { + name: 'dev', + content: 'A dev comment', + }, + ], + params: [ + { + name: '_magicNumber', + content: 'A parameter description', + }, + { + name: '_name', + content: 'Another parameter description', + }, + ], + returns: [ + { + name: '_isMagic', + content: 'Some return data', + }, + ], }; - it('should validate proper natspec', () => { - const result = validate(node, natspec, config); - expect(result).toEqual([]); - }); - - it('should reveal missing natspec for parameters', () => { - const paramName = '_magicNumber'; - let natspec = { - tags: [ - { - name: 'notice', - content: 'External function that returns a bool' - } - ], - params: [ - { - name: '_name', - content: 'Another parameter description' - } - ], - returns: [ - { - name: '_isMagic', - content: 'Some return data' - } - ] - }; - - const result = validate(node, natspec, config); - expect(result).toContainEqual(`@param ${paramName} is missing`); - }); - - it('should reveal missing natspec for returned values', () => { - const paramName = '_isMagic'; - let natspec = { - tags: [ - { - name: 'notice', - content: 'External function that returns a bool' - }, - { - name: 'dev', - content: 'A dev comment' - } - ], - params: [ - { - name: '_magicNumber', - content: 'A parameter description' - }, - { - name: '_name', - content: 'Another parameter description' - } - ], - returns: [] - }; - - const result = validate(node, natspec, config); - expect(result).toContainEqual(`@return ${paramName} is missing`); - }); - - it('should reveal missing natspec for unnamed returned values', () => { - node = contract.vFunctions.find(({ name }) => name === 'externalSimpleMultipleReturn')!; - let natspec = { - tags: [ - { - name: 'notice', - content: 'External function that returns a bool' - }, - { - name: 'dev', - content: 'A dev comment' - } - ], - params: [ - { - name: '_magicNumber', - content: 'A parameter description' - }, - { - name: '_name', - content: 'Another parameter description' - } - ], - returns: [ - { - name: '_isMagic', - content: 'Some return data' - } - ] - }; - - const result = validate(node, natspec, config); - expect(result).toContainEqual(`@return missing for unnamed return`); - }); - - // TODO: Check overridden functions, virtual, etc? - // it('should reveal missing natspec for an external function'); - // it('should reveal missing natspec for a public function'); - // it('should reveal missing natspec for a private function'); - // it('should reveal missing natspec for an internal function'); - - it('should reveal missing natspec for a variable', () => { - node = contract.vStateVariables.find(({ name }) => name === '_EMPTY_STRING')!; - natspec = { - tags: [], - params: [], - returns: [] - }; - const result = validate(node, natspec, config); - expect(result).toContainEqual(`Natspec is missing`); - }); - - it('should reveal missing natspec for an error', () => { - node = contract.vErrors.find(({ name }) => name === 'BasicSample_SomeError')!; - const paramName = '_param1'; - natspec = { - tags: [ - { - name: 'notice', - content: 'Some error missing parameter natspec' - } - ], - params: [], - returns: [] - }; - const result = validate(node, natspec, config); - expect(result).toContainEqual(`@param ${paramName} is missing`); - }); - - it('should reveal missing natspec for an event', () => { - node = contract.vEvents.find(({ name }) => name === 'BasicSample_BasicEvent')!; - const paramName = '_param1'; - natspec = { - tags: [ - { - name: 'notice', - content: 'An event missing parameter natspec' - } - ], - params: [], - returns: [] - }; - const result = validate(node, natspec, config); - expect(result).toContainEqual(`@param ${paramName} is missing`); - }); - - it('should reveal missing natspec for an modifier', () => { - node = contract.vModifiers.find(({ name }) => name === 'transferFee')!; - const paramName = '_receiver'; - natspec = { - tags: [ - { - name: 'notice', - content: 'Modifier notice' - } - ], - params: [], - returns: [] - }; - const result = validate(node, natspec, config); - expect(result).toContainEqual(`@param ${paramName} is missing`); - }); - - it('should reveal missing natspec for a struct', () => { - node = contract.vStructs.find(({ name }) => name === 'TestStruct')!; - const paramName1 = 'someAddress'; - const paramName2 = 'someNumber'; - natspec = { - tags: [ - { - name: 'notice', - content: 'Modifier notice' - } - ], - params: [], - returns: [] - }; - const result = validate(node, natspec, config); - expect(result).toContainEqual(`@param ${paramName1} is missing`); - expect(result).toContainEqual(`@param ${paramName2} is missing`); - }); + const result = validate(node, natspec, config); + expect(result).toContainEqual(`@return missing for unnamed return`); + }); + + // TODO: Check overridden functions, virtual, etc? + // it('should reveal missing natspec for an external function'); + // it('should reveal missing natspec for a public function'); + // it('should reveal missing natspec for a private function'); + // it('should reveal missing natspec for an internal function'); + + it('should reveal missing natspec for a variable', () => { + node = contract.vStateVariables.find(({ name }) => name === '_EMPTY_STRING')!; + natspec = { + tags: [], + params: [], + returns: [], + }; + const result = validate(node, natspec, config); + expect(result).toContainEqual(`Natspec is missing`); + }); + + it('should reveal missing natspec for an error', () => { + node = contract.vErrors.find(({ name }) => name === 'BasicSample_SomeError')!; + const paramName = '_param1'; + natspec = { + tags: [ + { + name: 'notice', + content: 'Some error missing parameter natspec', + }, + ], + params: [], + returns: [], + }; + const result = validate(node, natspec, config); + expect(result).toContainEqual(`@param ${paramName} is missing`); + }); + + it('should reveal missing natspec for an event', () => { + node = contract.vEvents.find(({ name }) => name === 'BasicSample_BasicEvent')!; + const paramName = '_param1'; + natspec = { + tags: [ + { + name: 'notice', + content: 'An event missing parameter natspec', + }, + ], + params: [], + returns: [], + }; + const result = validate(node, natspec, config); + expect(result).toContainEqual(`@param ${paramName} is missing`); + }); + + it('should reveal missing natspec for an modifier', () => { + node = contract.vModifiers.find(({ name }) => name === 'transferFee')!; + const paramName = '_receiver'; + natspec = { + tags: [ + { + name: 'notice', + content: 'Modifier notice', + }, + ], + params: [], + returns: [], + }; + const result = validate(node, natspec, config); + expect(result).toContainEqual(`@param ${paramName} is missing`); + }); + + it('should reveal missing natspec for a struct', () => { + node = contract.vStructs.find(({ name }) => name === 'TestStruct')!; + const paramName1 = 'someAddress'; + const paramName2 = 'someNumber'; + natspec = { + tags: [ + { + name: 'notice', + content: 'Modifier notice', + }, + ], + params: [], + returns: [], + }; + const result = validate(node, natspec, config); + expect(result).toContainEqual(`@param ${paramName1} is missing`); + expect(result).toContainEqual(`@param ${paramName2} is missing`); + }); }); diff --git a/tsconfig.json b/tsconfig.json index 5148e2a..bbc5894 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,18 @@ { + "compileOnSave": true, "compilerOptions": { - "target": "es6", - "module": "commonjs", "declaration": true, - "outDir": "./lib", + "declarationMap": true, "strict": true, + "outDir": "lib", "esModuleInterop": true, + "module": "commonjs", + "noImplicitReturns": true, + "noUnusedLocals": true, + "sourceMap": true, "resolveJsonModule": true, + "target": "es2020" }, - "include": ["src"], - "exclude": ["node_modules", "**/__tests__/*"] + "exclude": ["node_modules", "**/__tests__/*"], + "include": ["src"] }