Skip to content

Commit

Permalink
feat: config file (#12)
Browse files Browse the repository at this point in the history
* feat: adding basic config implementation

* fix: renamed .t.ts do .d.ts

* refactor: renamed configs

* style: linter fixes

* refactor: leave only one config

* fix: config

* refactor: added default config and small refactor

---------

Co-authored-by: Gas One Cent <[email protected]>
  • Loading branch information
0xGorilla and gas1cent authored Jan 13, 2024
1 parent 10b5cca commit 2b981e3
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 88 deletions.
9 changes: 9 additions & 0 deletions example-natspec-smells.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* For full explanation of each supported config, make sure to check the Config type below
*/

/** @type {import('@defi-wonderland/natspec-smells').Config} */
module.exports = {
include: 'solidity',
exclude: ['tests', 'scripts'],
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"license": "MIT",
"author": "Wonderland",
"main": "lib/main.js",
"types": "lib/main.d.ts",
"types": "lib/types.d.ts",
"bin": "./lib/main.js",
"scripts": {
"build": "tsc",
Expand Down
1 change: 1 addition & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Config } from './types/config';
43 changes: 24 additions & 19 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { Config } from './types/config.t';
import { getProjectCompiledSources } from './utils';
import { globSync } from 'fast-glob';
import { getProjectCompiledSources } from './utils';
import { Processor } from './processor';
import { Config } from './types/config';
import { Validator } from './validator';

(async () => {
const config: Config = getArguments();
const ignoredPaths = config.ignore.map((path: string) => globSync(path, { cwd: config.root })).flat();
const sourceUnits = await getProjectCompiledSources(config.root, config.contracts, ignoredPaths);

const excludedPaths = config.exclude.map((path) => globSync(path, { cwd: config.root })).flat();
const sourceUnits = await getProjectCompiledSources(config.root, config.include, excludedPaths);
if (!sourceUnits.length) return console.error('No solidity files found in the specified directory');

const processor = new Processor(config);
const validator = new Validator(config);
const processor = new Processor(validator);
const warnings = processor.processSources(sourceUnits);

warnings.forEach(({ location, messages }) => {
Expand All @@ -28,32 +31,34 @@ function getArguments(): Config {
return yargs(hideBin(process.argv))
.strict()
.options({
root: {
include: {
type: 'string',
description: 'The target root directory',
default: './',
description: 'Glob pattern of files to process.',
required: true,
},
contracts: {
exclude: {
type: 'array',
description: 'Glob patterns of files to exclude.',
default: [],
string: true,
},
root: {
type: 'string',
description: 'The directory containing your Solidity contracts',
required: true,
description: 'Root directory of the project.',
default: './',
},
enforceInheritdoc: {
type: 'boolean',
description: 'If set to true, all external and public functions must have @inheritdoc',
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',
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,
},
})
.config()
.default('config', 'natspec-smells.config')
.parseSync();
}
23 changes: 4 additions & 19 deletions src/processor.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import fs from 'fs';
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';
import { NodeToProcess } from './types/solc-typed-ast.d';
import { getLineNumberFromSrc, parseNodeNatspec } from './utils';

interface IWarning {
location: string;
messages: string[];
}

export class Processor {
config: Config;
validator: Validator;

constructor(config: Config) {
this.config = config;
this.validator = new Validator(config);
}
constructor(private validator: Validator) {}

processSources(sourceUnits: SourceUnit[]): IWarning[] {
return sourceUnits.flatMap((sourceUnit) =>
Expand Down Expand Up @@ -60,14 +52,7 @@ export class Processor {
formatLocation(node: NodeToProcess, sourceUnit: SourceUnit, contract: ContractDefinition): string {
// 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 line = this.getLineNumberFromSrc(sourceUnit.absolutePath, node.src);
const line = getLineNumberFromSrc(sourceUnit.absolutePath, node.src);
return `${sourceUnit.absolutePath}:${line}\n${contract.name}:${nodeName}`;
}

private getLineNumberFromSrc(filePath: string, src: string) {
const [start] = src.split(':').map(Number);
const fileContent = fs.readFileSync(filePath, 'utf8');
const lines = fileContent.substring(0, start).split('\n');
return lines.length; // Line number
}
}
7 changes: 7 additions & 0 deletions src/types/config.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Config {
include: string; // Required: Glob pattern of files to process.
exclude: string[]; // Optional: Glob patterns of files to exclude.
root: string; // Optional: The target root directory.
enforceInheritdoc: boolean; // Optional: If set to true, all external and public functions must have @inheritdoc.
constructorNatspec: boolean; // Optional: True if constructor natspec is mandatory.
}
7 changes: 0 additions & 7 deletions src/types/config.t.ts

This file was deleted.

File renamed without changes.
File renamed without changes.
57 changes: 34 additions & 23 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
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';
import { Natspec, NatspecDefinition } from './types/natspec.d';
import { NodeToProcess } from './types/solc-typed-ast.d';

export async function getProjectCompiledSources(rootPath: string, contractsPath: string, ignoredPaths: string[]): Promise<SourceUnit[]> {
export async function getSolidityFiles(dir: string): Promise<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);
}
}

return solidityFiles;
}

export async function getProjectCompiledSources(rootPath: string, includedPath: string, excludedPaths: string[]): Promise<SourceUnit[]> {
// Fetch Solidity files from the specified directory
const solidityFiles: string[] = await getSolidityFiles(contractsPath);
const solidityFiles: string[] = await getSolidityFiles(includedPath);
const remappings: string[] = await getRemappings(rootPath);

const compiledFiles = await compileSol(solidityFiles, 'auto', {
Expand All @@ -19,12 +35,17 @@ export async function getProjectCompiledSources(rootPath: string, contractsPath:
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))
.filter((sourceUnit) => isFileInDirectory(includedPath, sourceUnit.absolutePath))
// avoid processing files from excluded directories
.filter((sourceUnit) => !excludedPaths.some((excludedPath) => excludedPath === sourceUnit.absolutePath))
);
}

export async function getFileCompiledSource(filePath: string): Promise<SourceUnit> {
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;
Expand All @@ -34,22 +55,6 @@ export function isFileInDirectory(directory: string, filePath: string): boolean
return absoluteFilePath.startsWith(absoluteDirectoryPath);
}

export async function getSolidityFiles(dir: string): Promise<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);
}
}

return solidityFiles;
}

export async function getRemappings(rootPath: string): Promise<string[]> {
try {
const filePath = path.join(rootPath, 'remappings.txt');
Expand Down Expand Up @@ -110,3 +115,9 @@ export function parseNodeNatspec(node: NodeToProcess): Natspec {

return result;
}

export async function getLineNumberFromSrc(fileContent: string, src: string) {
const [start] = src.split(':').map(Number);
const lines = fileContent.substring(0, start).split('\n');
return lines.length; // Line number
}
6 changes: 3 additions & 3 deletions src/validator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Natspec } from '../src/types/natspec.t';
import { Config } from './types/config.t';
import { NodeToProcess } from './types/solc-typed-ast.t';
import { Config } from './types/config';
import { Natspec } from './types/natspec';
import { NodeToProcess } from './types/solc-typed-ast';
import {
EnumDefinition,
ErrorDefinition,
Expand Down
4 changes: 2 additions & 2 deletions test/mocks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Natspec } from '../src/types/natspec.t';
import { Natspec } from '../src/types/natspec.d';
import { SourceUnit, ContractDefinition, FunctionDefinition } from 'solc-typed-ast';
import { NodeToProcess } from '../src/types/solc-typed-ast.t';
import { NodeToProcess } from '../src/types/solc-typed-ast.d';

export function mockNatspec(mockNatspec: Partial<Natspec>): Natspec {
const natspec: Natspec = {
Expand Down
26 changes: 16 additions & 10 deletions test/processor.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { Config } from '../src/types/config';
import { ContractDefinition, FunctionDefinition, UserDefinedType, UsingForDirective } from 'solc-typed-ast';
import { getFileCompiledSource } from './utils';
import { Processor } from '../src/processor';
import { Config } from '../src/types/config.t';
import { Validator } from '../src/validator';

describe('Processor', () => {
const config: Config = {
root: '.',
contracts: './sample-data',
enforceInheritdoc: false,
constructorNatspec: false,
ignore: [],
};

const processor: Processor = new Processor(config);
let processor: Processor;

beforeAll(() => {
const config: Config = {
root: '.',
include: './sample-data',
exclude: [],
enforceInheritdoc: false,
constructorNatspec: false,
};

const validator = new Validator(config);
processor = new Processor(validator);
});

// TODO: Fix these tests
// describe('formatLocation', () => {
Expand Down
9 changes: 5 additions & 4 deletions test/validator.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { Validator } from '../src/validator';
import { Config } from '../src/types/config.t';
import { getFileCompiledSource } from './utils';
import { NodeToProcess } from '../src/types/solc-typed-ast.t';
import { NodeToProcess } from '../src/types/solc-typed-ast.d';
import { ContractDefinition } from 'solc-typed-ast';
import { Config } from '../src/types/config';

describe('Validator', () => {
let contract: ContractDefinition;
let node: NodeToProcess;

const config: Config = {
root: '.',
contracts: './sample-data',
include: './sample-data',
exclude: [],
enforceInheritdoc: false,
constructorNatspec: false,
ignore: [],
};

const validator: Validator = new Validator(config);
Expand Down

0 comments on commit 2b981e3

Please sign in to comment.