Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(remappings): ✨ fallback to forge auto-remappings #47

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"plugins": ["eslint-plugin-jsdoc"],
"extends": ["plugin:eslint-plugin-jsdoc/recommended"],
"ignorePatterns": ["dist", ".eslintrc.cjs"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module",
"project": "./tsconfig.json"
},
"env": {
"node": true,
"es6": true
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Want to quickly check if your natspec smells?
Just run:

```
npx @defi-wonderland/natspec-smells --include src --exclude "src/**/*.sol" "(test|scripts)/**/*.sol"
npx @defi-wonderland/natspec-smells --include "src/**/*.sol" --exclude "(test|scripts)/**/*.sol"
```

> [!NOTE]
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"scripts": {
"build": "tsc",
"lint:check": "prettier --check .",
"lint:docs": "eslint 'src/**/*.ts'",
"lint:fix": "sort-package-json && prettier --write .",
"prepack": "pinst --disable",
"postpack": "pinst --enable",
Expand All @@ -27,6 +28,8 @@
"**/*": "prettier --write --ignore-unknown"
},
"dependencies": {
"@sinclair/typebox": "0.32.14",
"ajv": "8.12.0",
"fast-glob": "3.3.2",
"solc-typed-ast": "18.1.2",
"yargs": "17.7.2"
Expand All @@ -37,6 +40,9 @@
"@faker-js/faker": "8.3.1",
"@types/jest": "29.5.11",
"@types/node": "20.10.7",
"@typescript-eslint/parser": "6.2.0",
"eslint": "8.56.0",
"eslint-plugin-jsdoc": "48.1.0",
"husky": "8.0.3",
"jest": "29.7.0",
"lint-staged": "13.2.2",
Expand Down
16 changes: 16 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Functions } from './types';

export const defaultFunctions: Functions = {
internal: { tags: { dev: false, notice: true, return: true, param: true } },
external: { tags: { dev: false, notice: true, return: true, param: true } },
public: { tags: { dev: false, notice: true, return: true, param: true } },
private: { tags: { dev: false, notice: true, return: true, param: true } },
} as const;

export const defaultTags = {
tags: {
dev: false,
notice: true,
param: true,
},
} as const;
42 changes: 33 additions & 9 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
#!/usr/bin/env node
import path from 'path';
import yargs from 'yargs';
import fs from 'fs';
import { hideBin } from 'yargs/helpers';
import { glob } from 'fast-glob';
import { getProjectCompiledSources } from './utils';
import { getProjectCompiledSources, processConfig } from './utils';
import { Processor } from './processor';
import { Config } from './types';
import { Validator } from './validator';
import { defaultFunctions, defaultTags } from './constants';

/**
* Main function that processes the sources and prints the warnings
*/
(async () => {
const config: Config = getArguments();
// Requires the config is in the root of the users directory
const configPath = path.join(process.cwd(), './natspec-smells.config.json');
const config: Config = await getConfig(configPath);

// TODO: Add configuration logic to the linter
const excludedPaths = config.exclude === '' ? [] : await glob(config.exclude, { cwd: config.root });
const includedPaths = await glob(config.include, { cwd: config.root, ignore: excludedPaths });

Expand All @@ -34,9 +43,18 @@ import { Validator } from './validator';
});
})().catch(console.error);

function getArguments(): Config {
return yargs(hideBin(process.argv))
.strict()
/**
* Gets the config from the CLI or the config file
* @dev Prioritizes the config file over the CLI
* @param {string} configPath - The expected config path
* @returns {Config} - The config
*/
async function getConfig(configPath: string): Promise<Config> {
if (fs.existsSync(configPath)) {
return await processConfig(configPath);
}

const config: Partial<Config> = yargs(hideBin(process.argv))
.options({
include: {
type: 'string',
Expand All @@ -53,18 +71,24 @@ function getArguments(): Config {
description: 'Root directory of the project.',
default: './',
},
enforceInheritdoc: {
inheritdoc: {
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.',
description: 'If set to true, all contracts must have a natspec for the constructor.',
default: false,
},
})
.config()
.default('config', 'natspec-smells.config')
.parseSync();

config.functions = defaultFunctions;
config.modifiers = defaultTags;
config.errors = defaultTags;
config.events = defaultTags;
config.structs = defaultTags;

return config as Config;
}
25 changes: 25 additions & 0 deletions src/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export interface IWarning {
export class Processor {
constructor(private validator: Validator) {}

/**
* Goes through all functions, modifiers, state variables, structs, enums, errors and events
* of the source files and validates their natspec
* @param {SourceUnit[]} sourceUnits - The list of source files
* @returns {Promise<IWarning[]>} - The list of resulting warnings
*/
async processSources(sourceUnits: SourceUnit[]): Promise<IWarning[]> {
const warnings: IWarning[] = [];

Expand Down Expand Up @@ -41,6 +47,12 @@ export class Processor {
return warnings;
}

/**
* Selects the nodes that are eligible for natspec validation:
* Enums, Errors, Events, Functions, Modifiers, State Variables, Structs
* @param {ContractDefinition} contract - The contract source
* @returns {NodeToProcess[]} - The list of nodes to process
*/
selectEligibleNodes(contract: ContractDefinition): NodeToProcess[] {
return [
...contract.vEnums,
Expand All @@ -53,11 +65,24 @@ export class Processor {
];
}

/**
* Validates the natspec of the node
* @param {NodeToProcess} node - The node to process
* @returns {string[]} - The list of warning messages
*/
validateNatspec(node: NodeToProcess): string[] {
const nodeNatspec = parseNodeNatspec(node);
return this.validator.validate(node, nodeNatspec);
}

/**
* Generates a warning location string
* @param {string} filePath - Path of the file with the warning
* @param {string} fileContent - The content of the file
* @param {string} contractName - The name of the contract
* @param {NodeToProcess} node - The node with the warning
* @returns {string} - The formatted location
*/
formatLocation(filePath: string, fileContent: string, contractName: string, node: NodeToProcess): 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;
Expand Down
66 changes: 59 additions & 7 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,55 @@ import {
StructDefinition,
VariableDeclaration,
} from 'solc-typed-ast';
import { Static, Type } from '@sinclair/typebox';

export interface Config {
include: string; // Required: Glob pattern of files to process.
exclude: string; // Optional: Glob pattern of files to exclude.
root: string; // Optional: Project root directory.
enforceInheritdoc: boolean; // Optional: True if all external and public functions should have @inheritdoc.
constructorNatspec: boolean; // Optional: True if the constructor should have natspec.
}
// NOTE: For params like `return` if its set to true we will only force it if the function does return something

export const tagSchema = Type.Object({
tags: Type.Object({
dev: Type.Boolean({ default: false }),
notice: Type.Boolean({ default: true }),
param: Type.Boolean({ default: true }),
}),
});

export const functionSchema = Type.Object({
tags: Type.Object({
dev: Type.Boolean({ default: false }),
notice: Type.Boolean({ default: true }),
param: Type.Boolean({ default: true }),
return: Type.Boolean({ default: true }),
}),
});

export const functionConfigSchema = Type.Object({
internal: functionSchema,

external: functionSchema,

public: functionSchema,

private: functionSchema,
});

export const configSchema = Type.Object({
include: Type.String(),
exclude: Type.String({ default: '' }),
root: Type.String({ default: './' }),
functions: functionConfigSchema,
events: tagSchema,
errors: tagSchema,
modifiers: tagSchema,
structs: tagSchema,
inheritdoc: Type.Boolean({ default: true }),
constructorNatspec: Type.Boolean({ default: false }),
});

export type KeysForSupportedTags = 'events' | 'errors' | 'modifiers' | 'structs';
export type FunctionConfig = Static<typeof functionSchema>;
export type Config = Static<typeof configSchema>;
export type Functions = Static<typeof functionConfigSchema>;
export type Tags = Static<typeof tagSchema>;

export interface NatspecDefinition {
name?: string;
Expand Down Expand Up @@ -49,3 +90,14 @@ export type NodeToProcess =
| ModifierDefinition
| VariableDeclaration
| StructDefinition;

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

export type HasVParameters = {
vParameters: {
vParameters: Array<{ name: string }>;
};
};
Loading