diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..fc4585a --- /dev/null +++ b/.eslintrc @@ -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 + } +} diff --git a/README.md b/README.md index 2f93430..e055a84 100644 --- a/README.md +++ b/README.md @@ -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] diff --git a/package.json b/package.json index 34d13d9..5c949c5 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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" @@ -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", diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..0c481a7 --- /dev/null +++ b/src/constants.ts @@ -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; diff --git a/src/main.ts b/src/main.ts index b87015e..c7995a5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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 }); @@ -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 { + if (fs.existsSync(configPath)) { + return await processConfig(configPath); + } + + const config: Partial = yargs(hideBin(process.argv)) .options({ include: { type: 'string', @@ -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; } diff --git a/src/processor.ts b/src/processor.ts index 32dbdb0..6530ca3 100644 --- a/src/processor.ts +++ b/src/processor.ts @@ -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} - The list of resulting warnings + */ async processSources(sourceUnits: SourceUnit[]): Promise { const warnings: IWarning[] = []; @@ -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, @@ -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; diff --git a/src/types.ts b/src/types.ts index d4d4e2a..9556222 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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; +export type Config = Static; +export type Functions = Static; +export type Tags = Static; export interface NatspecDefinition { name?: string; @@ -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 }>; + }; +}; diff --git a/src/utils.ts b/src/utils.ts index 25e4a40..9aeab5e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,12 +1,26 @@ import fs from 'fs/promises'; import path from 'path'; -import { Natspec, NatspecDefinition, NodeToProcess } from './types'; +import Ajv from 'ajv'; +import { Natspec, NatspecDefinition, NodeToProcess, Config, configSchema, Functions, KeysForSupportedTags } from './types'; import { ASTKind, ASTReader, SourceUnit, compileSol, FunctionDefinition } from 'solc-typed-ast'; +import { defaultFunctions, defaultTags } from './constants'; +import { exec } from 'child_process'; +/** + * Returns the absolute paths of the Solidity files + * @param {string[]} files - The list of files paths + * @returns {Promise} - The list of absolute paths + */ export async function getSolidityFilesAbsolutePaths(files: string[]): Promise { return files.filter((file) => file.endsWith('.sol')).map((file) => path.resolve(file)); } +/** + * Returns the list of source units of the compiled Solidity files + * @param {string} rootPath - The root path of the project + * @param {string[]} includedPaths - The list of included paths + * @returns {SourceUnit[]} - The list of source units extracted from the compiled files + */ export async function getProjectCompiledSources(rootPath: string, includedPaths: string[]): Promise { // Fetch Solidity files from the specified directory const solidityFiles: string[] = await getSolidityFilesAbsolutePaths(includedPaths); @@ -26,6 +40,12 @@ export async function getProjectCompiledSources(rootPath: string, includedPaths: ); } +/** + * Checks if the file path is in the specified directory + * @param {string} directory - The directory path + * @param {string} filePath - The file path + * @returns {boolean} - True if the file is in the directory + */ export function isFileInDirectory(directory: string, filePath: string): boolean { // Convert both paths to absolute and normalize them const absoluteDirectoryPath = path.resolve(directory) + path.sep; @@ -35,6 +55,11 @@ export function isFileInDirectory(directory: string, filePath: string): boolean return absoluteFilePath.startsWith(absoluteDirectoryPath); } +/** + * Returns the remappings from the remappings.txt file or foundry.toml + * @param {string} rootPath - The root path of the project + * @returns {Promise} - The list of remappings + */ export async function getRemappings(rootPath: string): Promise { // First try the remappings.txt file try { @@ -44,11 +69,21 @@ export async function getRemappings(rootPath: string): Promise { try { return await exports.getRemappingsFromConfig(path.join(rootPath, 'foundry.toml')); } catch { - return []; + // If neither file exists, try to generate the remappings using forge + try { + return await exports.getRemappingsFromForge(); + } catch { + return []; + } } } } +/** + * Returns the remappings from the remappings.txt file + * @param {string} remappingsPath - The path of the remappings file + * @returns {Promise} - The list of remappings + */ export async function getRemappingsFromFile(remappingsPath: string): Promise { const remappingsContent = await fs.readFile(remappingsPath, 'utf8'); @@ -59,6 +94,11 @@ export async function getRemappingsFromFile(remappingsPath: string): Promise sanitizeRemapping(line)); } +/** + * Returns the remappings from the foundry.toml file + * @param {string} foundryConfigPath - The path of the foundry.toml file + * @returns {Promise} - The list of remappings + */ export async function getRemappingsFromConfig(foundryConfigPath: string): Promise { const foundryConfigContent = await fs.readFile(foundryConfigPath, 'utf8'); const regex = /remappings[\s|\n]*\=[\s\n]*\[(?[^\]]+)]/; @@ -71,12 +111,31 @@ export async function getRemappingsFromConfig(foundryConfigPath: string): Promis .filter((line) => line.length) .map((line) => sanitizeRemapping(line)); } else { - return []; + throw new Error('No remappings found in foundry.toml'); } } +/** + * Returns the remappings generated by forge + * @returns {Promise} - The list of remappings + */ +export async function getRemappingsFromForge(): Promise { + const remappingsContent = await new Promise((resolve, reject) => + exec('forge remappings', { encoding: 'utf8' }, (error, stdout) => (error ? reject(error) : resolve(stdout))) + ); + return remappingsContent + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.length) + .map((line) => sanitizeRemapping(line)); +} + +/** + * Makes sure both sides of a remapping either have or don't have a trailing slash + * @param {string} line - A line from the remappings array + * @returns {string} - The sanitized line + */ export function sanitizeRemapping(line: string): string { - // Make sure the key and the value both either have or don't have a trailing slash const [key, value] = line.split('='); const slashNeeded = key.endsWith('/'); @@ -87,6 +146,11 @@ export function sanitizeRemapping(line: string): string { } } +/** + * Parses the natspec of the node + * @param {NodeToProcess} node - The node to process + * @returns {Natspec} - The parsed natspec + */ export function parseNodeNatspec(node: NodeToProcess): Natspec { if (!node.documentation) { return { tags: [], params: [], returns: [] }; @@ -133,19 +197,92 @@ export function parseNodeNatspec(node: NodeToProcess): Natspec { return result; } +/** + * Returns the line number from the source code + * @param {string} fileContent - The content of the file + * @param {string} src - The node src location (e.g. "10:1:0") + * @returns {number} - The line number of the node + */ export function getLineNumberFromSrc(fileContent: string, src: string): number { const [start] = src.split(':').map(Number); const lines = fileContent.substring(0, start).split('\n'); return lines.length; // Line number } +/** + * Checks if the node matches the function kind + * @param {NodeToProcess} node - The node to process + * @param {string} kind - The function kind + * @returns {boolean} - True if the node matches the function kind + */ export function matchesFunctionKind(node: NodeToProcess, kind: string): boolean { return node instanceof FunctionDefinition && node.kind === kind; } +/** + * Returns the frequency of the elements in the array + * @param {any[]} array - The array of elements + * @returns {Record} - The frequency of the elements + */ export function getElementFrequency(array: any[]) { return array.reduce((acc, curr) => { acc[curr] = (acc[curr] || 0) + 1; return acc; }, {}); } + +/** + * Processes a config file based on the given file path + * @param {string} filePath - The path to the config file + * @returns {Config} - The config + */ +export async function processConfig(filePath: string): Promise { + const file = await fs.readFile(filePath, 'utf8'); + const detectedConfig = JSON.parse(file); + + if (!detectedConfig.functions) { + detectedConfig.functions = defaultFunctions; + } else { + for (const key of Object.keys(defaultFunctions)) { + if (!detectedConfig.functions[key]) { + detectedConfig.functions[key] = defaultFunctions[key as keyof Functions]; + } + } + } + + // TODO: Deprecation logic will be defined here + // Set defaults if needed + const config: Config = { + include: detectedConfig.include, + exclude: detectedConfig.exclude ?? '', + root: detectedConfig.root ?? './', + functions: detectedConfig.functions, + events: detectedConfig.events ?? defaultTags, + errors: detectedConfig.errors ?? defaultTags, + modifiers: detectedConfig.modifiers ?? defaultTags, + structs: detectedConfig.structs ?? defaultTags, + inheritdoc: detectedConfig.inheritdoc ?? true, + constructorNatspec: detectedConfig.constructorNatspec ?? false, + }; + + // Validate the received config matches our expected type + const ajv = new Ajv(); + const validate = ajv.compile(configSchema); + const valid = validate(config); + + if (!valid) { + throw new Error(`Invalid config: ${ajv.errorsText(validate.errors)}`); + } + + return config; +} + +/** + * Returns if the key being used is for a supported tag + * @dev A "supported tag" is a generalized term for events, errors, modifiers, and structs as they all share the same tag schema + * @param {string} key The key to check + * @returns {boolean} True if the key is for a supported tag + */ +export function isKeyForSupportedTags(key: string): key is KeysForSupportedTags { + return ['events', 'errors', 'modifiers', 'structs'].includes(key); +} diff --git a/src/validator.ts b/src/validator.ts index d51e3ed..abe6b46 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -1,5 +1,15 @@ -import { Config, Natspec, NodeToProcess } from './types'; -import { matchesFunctionKind, getElementFrequency } from './utils'; +import { + Config, + FunctionConfig, + Functions, + HasVParameters, + Natspec, + NatspecDefinition, + NodeToProcess, + KeysForSupportedTags, + Tags, +} from './types'; +import { matchesFunctionKind, getElementFrequency, isKeyForSupportedTags } from './utils'; import { EnumDefinition, ErrorDefinition, @@ -11,13 +21,25 @@ import { ContractDefinition, } from 'solc-typed-ast'; +/** + * Validator class that validates the natspec of the nodes + */ export class Validator { config: Config; + /** + * @param {Config} config - The configuration object + */ constructor(config: Config) { this.config = config; } + /** + * Validates the natspec of the node + * @param {NodeToProcess} node - The node to validate (Enum, Function etc.) + * @param {Natspec} natspec - Parsed natspec of the node + * @returns {string[]} - The list of alerts + */ validate(node: NodeToProcess, natspec: Natspec): string[] { // Ignore fallback and receive if (matchesFunctionKind(node, 'receive') || matchesFunctionKind(node, 'fallback')) { @@ -28,34 +50,96 @@ export class Validator { if (natspec.inheritdoc) return []; // Inheritdoc is enforced but not present, returning an error - if (this.config.enforceInheritdoc && this.requiresInheritdoc(node)) return [`@inheritdoc is missing`]; + if (this.config.inheritdoc && this.requiresInheritdoc(node)) return [`@inheritdoc is missing`]; const natspecParams = natspec.params.map((p) => p.name); // Validate natspec for the constructor only if configured if (matchesFunctionKind(node, 'constructor')) { - return this.config.constructorNatspec ? this.validateParameters(node as FunctionDefinition, natspecParams) : []; + return this.config.functions?.constructor ? this.validateParameters(node as FunctionDefinition, natspecParams) : []; } // Inheritdoc is not enforced nor present, and there is no other documentation, returning error - if (!natspec.tags.length) return [`Natspec is missing`]; + if (!natspec.tags.length) { + let needsWarning = false; + // If node is a function, check the user defined config + if (node instanceof FunctionDefinition) { + Object.keys(this.config.functions).forEach((key) => { + Object.keys(this.config.functions[key as keyof Functions].tags).forEach((tag) => { + if (this.config.functions[key as keyof Functions][tag as keyof FunctionConfig]) { + needsWarning = true; + } + }); + }); + } else { + // The other config rules use the same datatype so we can check them here + Object.keys(this.config).forEach((key) => { + if (isKeyForSupportedTags(key)) { + const tagsConfig = this.config[key]?.tags; + if (tagsConfig) { + Object.values(tagsConfig).forEach((value) => { + if (value) { + needsWarning = true; + } + }); + } + } + }); + } + + if (needsWarning) return [`Natspec is missing`]; + } // Validate the completeness of the documentation let alerts: string[] = []; + let isDevTagForced: boolean; + let isNoticeTagForced: boolean; if (node instanceof EnumDefinition) { // TODO: Process enums } else if (node instanceof ErrorDefinition) { - alerts = [...alerts, ...this.validateParameters(node, natspecParams)]; + isDevTagForced = this.config.errors.tags.dev; + isNoticeTagForced = this.config.errors.tags.notice; + + alerts = [ + ...alerts, + ...this.validateParameters(node, natspecParams, 'errors'), + ...this.validateTags(isDevTagForced, isNoticeTagForced, natspec.tags), + ]; } else if (node instanceof EventDefinition) { - alerts = [...alerts, ...this.validateParameters(node, natspecParams)]; + isDevTagForced = this.config.events.tags.dev; + isNoticeTagForced = this.config.events.tags.notice; + + alerts = [ + ...alerts, + ...this.validateParameters(node, natspecParams, 'events'), + ...this.validateTags(isDevTagForced, isNoticeTagForced, natspec.tags), + ]; } else if (node instanceof FunctionDefinition) { const natspecReturns = natspec.returns.map((p) => p.name); - alerts = [...alerts, ...this.validateParameters(node, natspecParams), ...this.validateReturnParameters(node, natspecReturns)]; + isDevTagForced = this.config.functions[node.visibility as keyof Functions]?.tags.dev; + isNoticeTagForced = this.config.functions[node.visibility as keyof Functions]?.tags.notice; + + alerts = [ + ...alerts, + ...this.validateParameters(node, natspecParams), + ...this.validateReturnParameters(node, natspecReturns), + ...this.validateTags(isDevTagForced, isNoticeTagForced, natspec.tags), + ]; } else if (node instanceof ModifierDefinition) { - alerts = [...alerts, ...this.validateParameters(node, natspecParams)]; + isDevTagForced = this.config.modifiers.tags.dev; + isNoticeTagForced = this.config.modifiers.tags.notice; + + alerts = [ + ...alerts, + ...this.validateParameters(node, natspecParams, 'modifiers'), + ...this.validateTags(isDevTagForced, isNoticeTagForced, natspec.tags), + ]; } else if (node instanceof StructDefinition) { - alerts = [...alerts, ...this.validateMembers(node, natspecParams)]; + isDevTagForced = this.config.structs.tags.dev; + isNoticeTagForced = this.config.structs.tags.notice; + + alerts = [...alerts, ...this.validateMembers(node, natspecParams), ...this.validateTags(isDevTagForced, isNoticeTagForced, natspec.tags)]; } else if (node instanceof VariableDeclaration) { // Only the presence of a notice is validated } @@ -63,12 +147,32 @@ export class Validator { return alerts; } - // All defined parameters should have natspec - private validateParameters(node: ErrorDefinition | FunctionDefinition | ModifierDefinition, natspecParams: (string | undefined)[]): string[] { + /** + * Validates the natspec for parameters. + * All defined parameters should have natspec. + * @param {ErrorDefinition | FunctionDefinition | ModifierDefinition} node - The node to validate + * @param {string[]} natspecParams - The list of parameters from the natspec + * @returns {string[]} - The list of alerts + */ + private validateParameters( + node: T, + natspecParams: (string | undefined)[], + key: KeysForSupportedTags | undefined = undefined + ): string[] { let definedParameters = node.vParameters.vParameters.map((p) => p.name); let alerts: string[] = []; const counter = getElementFrequency(natspecParams); + if (node instanceof FunctionDefinition) { + if (!this.config.functions[node.visibility as keyof Functions]?.tags.param) { + return []; + } + } else if (key !== undefined) { + if (!this.config[key]?.tags.param) { + return []; + } + } + for (let paramName of definedParameters) { if (!natspecParams.includes(paramName)) { alerts.push(`@param ${paramName} is missing`); @@ -79,8 +183,18 @@ export class Validator { return alerts; } - // All members of a struct should have natspec + /** + * Validates the natspec for members of a struct. + * All members of a struct should have natspec. + * @param {StructDefinition} node - The struct node + * @param {string[]} natspecParams - The list of parameters from the natspec + * @returns {string[]} - The list of alerts + */ private validateMembers(node: StructDefinition, natspecParams: (string | undefined)[]): string[] { + if (!this.config.structs.tags.param) { + return []; + } + let members = node.vMembers.map((p) => p.name); let alerts: string[] = []; const counter = getElementFrequency(natspecParams); @@ -96,8 +210,19 @@ export class Validator { return alerts; } - // All returned parameters should have natspec + /** + * Validates the natspec for return parameters. + * All returned parameters should have natspec + * @param {FunctionDefinition} node - The function node + * @param {(string | undefined)[]} natspecReturns - The list of `return` tags from the natspec + * @returns {string[]} - The list of alerts + */ private validateReturnParameters(node: FunctionDefinition, natspecReturns: (string | undefined)[]): string[] { + // If return tags are not enforced, return no warnings + if (!this.config.functions[node.visibility as keyof Functions]?.tags.return) { + return []; + } + let alerts: string[] = []; let functionReturns = node.vReturnParameters.vParameters.map((p) => p.name); @@ -115,6 +240,51 @@ export class Validator { return alerts; } + private validateTags(isDevTagForced: boolean, isNoticeTagForced: boolean, natspecTags: NatspecDefinition[]): string[] { + // If both are disabled no warnings should emit so we dont need to check anything + if (!isDevTagForced && !isNoticeTagForced) { + return []; + } + + let alerts: string[] = []; + + let devCounter = 0; + let noticeCounter = 0; + + for (const tag of natspecTags) { + if (tag.name === 'dev') { + devCounter++; + } else if (tag.name === 'notice') { + noticeCounter++; + } + } + + // Needs a dev tag + // More then one dev tag is ok + if (isDevTagForced && devCounter === 0) { + alerts.push(`@dev is missing`); + } + + if (isNoticeTagForced) { + // Needs one notice tag + if (noticeCounter === 0) { + alerts.push(`@notice is missing`); + } + + // Cant have more then one notice tag + if (noticeCounter > 1) { + alerts.push(`@notice is duplicated`); + } + } + + return alerts; + } + + /** + * Checks if the node requires inheritdoc + * @param {NodeToProcess} node - The node to process + * @returns {boolean} - True if the node requires inheritdoc + */ private requiresInheritdoc(node: NodeToProcess): boolean { let _requiresInheritdoc: boolean = false; diff --git a/test/contracts/BasicSample.sol b/test/contracts/BasicSample.sol index 599a4e2..2085755 100644 --- a/test/contracts/BasicSample.sol +++ b/test/contracts/BasicSample.sol @@ -49,6 +49,13 @@ contract BasicSample is AbstractBasic { return true; } + /** + * @notice External function that returns a bool + */ + function externalNoReturn() external pure returns (bool) { + return true; + } + /** * @notice Private test function * @param _magicNumber A parameter description diff --git a/test/parser.test.ts b/test/parser.test.ts index 7000b28..22ca00a 100644 --- a/test/parser.test.ts +++ b/test/parser.test.ts @@ -375,13 +375,6 @@ describe('Parser', () => { expect(result).toEqual(mockNatspec({})); }); - it('should parse block natspec with invalid formatting', async () => { - const node = findNode(contract.vFunctions, '_viewBlockLinterFail'); - const result = parseNodeNatspec(node); - - expect(result).toEqual(mockNatspec({})); - }); - it('should parse natspec with invalid formatting', async () => { const node = findNode(contract.vFunctions, '_viewLinterFail'); const result = parseNodeNatspec(node); diff --git a/test/utils.test.ts b/test/utils.test.ts index 270263f..a439880 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,8 +1,17 @@ +import child_process from 'child_process'; import fs from 'fs/promises'; +import fstest from 'fs'; import path from 'path'; import * as utils from '../src/utils'; import { mockFoundryConfig, mockFunctionDefinition } from './utils/mocks'; import { FunctionKind } from 'solc-typed-ast'; +import { defaultFunctions } from '../src/constants'; + +jest.mock('child_process', () => ({ + exec: jest.fn().mockImplementation(() => { + throw new Error(); + }), +})); describe('Utils', () => { describe('getSolidityFilesAbsolutePaths', () => { @@ -59,6 +68,20 @@ describe('Utils', () => { expect(output).toEqual(['config']); }); + it('should return forge auto remappings if fails', async () => { + const spyGetRemappingsFromFile = jest.spyOn(utils, 'getRemappingsFromFile'); + spyGetRemappingsFromFile.mockRejectedValueOnce(new Error()); + + const spyGetRemappingsFromConfig = jest.spyOn(utils, 'getRemappingsFromConfig'); + spyGetRemappingsFromConfig.mockRejectedValueOnce(new Error()); + + const spy = jest.spyOn(utils, 'getRemappingsFromForge'); + spy.mockResolvedValueOnce(['forge']); + + const output = await utils.getRemappings(''); + expect(output).toEqual(['forge']); + }); + it('should return empty array if all fails', async () => { const output = await utils.getRemappings('wrong/path'); expect(output).toEqual([]); @@ -78,11 +101,6 @@ describe('Utils', () => { it('should return correct remappings from config', async () => { const remappings = new Map(); - remappings.set( - [], // Expected value - [''] // Remappings strings that when parsed should return the expected value - ); - remappings.set( ['ds-test/=lib/ds-test/src/'], // Expected value [`remappings = [ 'ds-test/=lib/ds-test/src/' ]`] // Remappings strings that when parsed should return the expected value @@ -137,6 +155,23 @@ describe('Utils', () => { } } }); + + it('should revert with no remappings', async () => { + fs.readFile = jest.fn().mockResolvedValueOnce(mockFoundryConfig('')); + await expect(utils.getRemappingsFromConfig('')).rejects.toThrow(); + }); + }); + + describe('getRemappingsFromForge', () => { + it('should return correct remappings from file', async () => { + const mockRemappingsList = ['test/contracts/=contracts/', 'contract/contracts/=contracts/']; + jest.spyOn(child_process, 'exec').mockImplementationOnce((_, __, callback) => { + callback?.(null, mockRemappingsList.join('\n'), ''); + return {} as child_process.ChildProcess; + }); + const remappings = await utils.getRemappingsFromForge(); + expect(remappings).toEqual(mockRemappingsList); + }); }); describe('sanitizeRemapping', () => { @@ -186,4 +221,220 @@ describe('Utils', () => { }); }); }); + + describe('processConfig', () => { + it('should use a valid config', async () => { + fs.readFile = jest.fn().mockResolvedValueOnce( + JSON.stringify({ + include: './contracts/**/*.sol', + }) + ); + const config = await utils.processConfig(path.join(__dirname, './valid.config.json')); + + // The default settings should be applied + expect(config).toEqual({ + root: './', + include: './contracts/**/*.sol', + exclude: '', + inheritdoc: true, + functions: defaultFunctions, + modifiers: { + tags: { + dev: false, + notice: true, + param: true, + }, + }, + events: { + tags: { + dev: false, + notice: true, + param: true, + }, + }, + errors: { + tags: { + dev: false, + notice: true, + param: true, + }, + }, + structs: { + tags: { + dev: false, + notice: true, + param: true, + }, + }, + constructorNatspec: false, + }); + }); + + it('should revert with an invalid config', async () => { + fs.readFile = jest.fn().mockResolvedValueOnce( + JSON.stringify({ + include: './contracts/**/*.sol', + exclude: 123, + }) + ); + await expect(utils.processConfig(path.join(__dirname, './invalid.config.json'))).rejects.toThrow(); + }); + + it('should overwrite defaults if values are set', async () => { + fs.readFile = jest.fn().mockResolvedValueOnce( + JSON.stringify({ + include: './contracts/**/*.sol', + exclude: './contracts/ignored.sol', + root: './contracts', + inheritdoc: false, + }) + ); + const config = await utils.processConfig(path.join(__dirname, './valid.config.json')); + + expect(config).toEqual({ + root: './contracts', + include: './contracts/**/*.sol', + exclude: './contracts/ignored.sol', + inheritdoc: false, + functions: defaultFunctions, + modifiers: { + tags: { + dev: false, + notice: true, + param: true, + }, + }, + events: { + tags: { + dev: false, + notice: true, + param: true, + }, + }, + errors: { + tags: { + dev: false, + notice: true, + param: true, + }, + }, + structs: { + tags: { + dev: false, + notice: true, + param: true, + }, + }, + constructorNatspec: false, + }); + }); + + it('should set custom parameters for functions', async () => { + fs.readFile = jest.fn().mockResolvedValueOnce( + JSON.stringify({ + include: './contracts/**/*.sol', + functions: { + internal: { + tags: { + dev: true, + notice: true, + return: true, + param: true, + }, + }, + }, + constructorNatspec: true, + }) + ); + const config = await utils.processConfig(path.join(__dirname, './valid.config.json')); + + expect(config).toEqual({ + root: './', + include: './contracts/**/*.sol', + exclude: '', + inheritdoc: true, + functions: { + internal: { + tags: { + dev: true, + 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, + }, + }, + }, + modifiers: { + tags: { + dev: false, + notice: true, + param: true, + }, + }, + events: { + tags: { + dev: false, + notice: true, + param: true, + }, + }, + errors: { + tags: { + dev: false, + notice: true, + param: true, + }, + }, + structs: { + tags: { + dev: false, + notice: true, + param: true, + }, + }, + constructorNatspec: true, + }); + }); + + it('should revert if a function block is incomplete', async () => { + fs.readFile = jest.fn().mockResolvedValueOnce( + JSON.stringify({ + include: './contracts/**/*.sol', + functions: { + internal: { + tags: { + dev: true, + notice: true, + }, + }, + }, + }) + ); + + await expect(utils.processConfig(path.join(__dirname, './invalid.config.json'))).rejects.toThrow(); + }); + }); }); diff --git a/test/utils/helpers.ts b/test/utils/helpers.ts index 67edddb..fd55cc1 100644 --- a/test/utils/helpers.ts +++ b/test/utils/helpers.ts @@ -1,5 +1,5 @@ import { ASTKind, ASTReader, SourceUnit, compileSol } from 'solc-typed-ast'; -import { NodeToProcess } from '../../src/types'; +import { Functions, NodeToProcess } from '../../src/types'; export async function getFileCompiledSource(filePath: string): Promise { const compiledFile = await compileSol(filePath, 'auto'); @@ -14,3 +14,25 @@ export function expectWarning(warnArray: string[], expectedWarn: string, numberO export function findNode(nodes: readonly NodeToProcess[], name: string): any { return nodes.find((x) => x.name === name); } + +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 } }, +}; + +export const defaultTags = { tags: { dev: false, notice: true, param: true } }; + +export const defaultConfig = { + include: '', + exclude: '', + root: './', + functions: defaultFunctions, + events: defaultTags, + errors: defaultTags, + modifiers: defaultTags, + structs: defaultTags, + inheritdoc: false, + constructorNatspec: false, +}; diff --git a/test/validator.test.ts b/test/validator.test.ts index 772dbaa..ff26703 100644 --- a/test/validator.test.ts +++ b/test/validator.test.ts @@ -1,3 +1,4 @@ +import { defaultConfig, defaultFunctions, defaultTags } from './utils/helpers'; import { Validator } from '../src/validator'; import { getFileCompiledSource, expectWarning, findNode } from './utils/helpers'; import { mockConfig, mockNatspec } from './utils/mocks'; @@ -5,7 +6,7 @@ import { ContractDefinition } from 'solc-typed-ast'; describe('Validator', () => { let contract: ContractDefinition; - let validator: Validator = new Validator(mockConfig({})); + let validator: Validator = new Validator(mockConfig(defaultConfig)); beforeAll(async () => { const compileResult = await getFileCompiledSource('test/contracts/BasicSample.sol'); @@ -364,7 +365,7 @@ describe('Validator', () => { describe('with enforced constructor natspec', () => { beforeAll(async () => { - validator = new Validator(mockConfig({ constructorNatspec: true })); + validator = new Validator(mockConfig({ constructorNatspec: true, functions: defaultFunctions })); }); it('should reveal missing constructor natspec', () => { @@ -376,7 +377,7 @@ describe('Validator', () => { describe('with disabled constructor natspec', () => { beforeAll(async () => { - validator = new Validator(mockConfig({ constructorNatspec: false })); + validator = new Validator(mockConfig({ constructorNatspec: true })); }); it('should ignore missing constructor natspec', () => { @@ -388,7 +389,7 @@ describe('Validator', () => { describe('with enforced inheritdoc', () => { beforeAll(async () => { - validator = new Validator(mockConfig({ enforceInheritdoc: true })); + validator = new Validator(mockConfig({ inheritdoc: true })); }); it('should return no warnings if inheritdoc is in place', () => { @@ -419,4 +420,495 @@ describe('Validator', () => { expect(result).toContainEqual(`@inheritdoc is missing`); }); }); + + describe('config rules', () => { + it('should have no warnings if return is disabled for a function', () => { + const mockFunctions = defaultFunctions; + mockFunctions.external.tags.return = false; + const noReturnValidator = new Validator(mockConfig({ functions: mockFunctions })); + const node = findNode(contract.vFunctions, 'externalNoReturn'); + const natspec = mockNatspec({ + tags: [ + { + name: 'notice', + content: 'External function that returns a bool', + }, + ], + params: [], + returns: [], + }); + + const result = noReturnValidator.validate(node, natspec); + expect(result).toEqual([]); + }); + + it('should have no warnings if param is disabled', () => { + const mockFunctions = defaultFunctions; + mockFunctions.external.tags.param = false; + const noParamValidator = new Validator(mockConfig({ functions: mockFunctions })); + const node = findNode(contract.vFunctions, 'externalSimple'); + const natspec = mockNatspec({ + tags: [ + { + name: 'notice', + content: 'External function that returns a bool', + }, + ], + params: [], + returns: [ + { + name: '_isMagic', + content: 'Some return data', + }, + ], + }); + + const result = noParamValidator.validate(node, natspec); + expect(result).toEqual([]); + }); + + it('should have no warnings if notice is disabled', () => { + const mockFunctions = defaultFunctions; + mockFunctions.external.tags.notice = false; + const noNoticeValidator = new Validator(mockConfig({ functions: mockFunctions })); + const node = findNode(contract.vFunctions, 'externalSimple'); + const natspec = mockNatspec({ + tags: [ + { + name: 'dev', + content: 'A dev comment', + }, + ], + params: [], + returns: [ + { + name: '_isMagic', + content: 'Some return data', + }, + ], + }); + + const result = noNoticeValidator.validate(node, natspec); + expect(result).toEqual([]); + }); + + it('should have a warning if notice is forced for a function', () => { + const mockFunctions = defaultFunctions; + mockFunctions.external.tags.notice = true; + const noticeValidator = new Validator(mockConfig({ functions: mockFunctions })); + const node = findNode(contract.vFunctions, 'externalSimple'); + const natspec = mockNatspec({ + tags: [ + { + name: 'dev', + content: 'A dev comment', + }, + ], + params: [], + returns: [ + { + name: '_isMagic', + content: 'Some return data', + }, + ], + }); + + const result = noticeValidator.validate(node, natspec); + expectWarning(result, `@notice is missing`, 1); + }); + + it('should have a warning if notice is forced for a modifier', () => { + const mockModifier = defaultTags; + mockModifier.tags.notice = true; + const noticeValidator = new Validator(mockConfig({ modifiers: mockModifier })); + const node = findNode(contract.vModifiers, 'transferFee'); + const natspec = mockNatspec({ + tags: [ + { + name: 'dev', + content: 'Modifier notice', + }, + ], + params: [], + }); + + const result = noticeValidator.validate(node, natspec); + expectWarning(result, `@notice is missing`, 1); + }); + + it('should have a warning if notice is forced for a struct', () => { + const mockStruct = defaultTags; + mockStruct.tags.notice = true; + const noticeValidator = new Validator(mockConfig({ structs: mockStruct })); + const node = findNode(contract.vStructs, 'TestStruct'); + const natspec = mockNatspec({ + tags: [ + { + name: 'dev', + content: 'Modifier notice', + }, + ], + params: [], + }); + + const result = noticeValidator.validate(node, natspec); + expectWarning(result, `@notice is missing`, 1); + }); + + it('should have a warning if notice is forced for an error', () => { + const mockError = defaultTags; + mockError.tags.notice = true; + const noticeValidator = new Validator(mockConfig({ errors: mockError })); + const node = findNode(contract.vErrors, 'BasicSample_SomeError'); + const natspec = mockNatspec({ + tags: [ + { + name: 'dev', + content: 'Modifier notice', + }, + ], + params: [], + }); + + const result = noticeValidator.validate(node, natspec); + expectWarning(result, `@notice is missing`, 1); + }); + + it('should have a warning if notice is forced for an event', () => { + const mockEvent = defaultTags; + mockEvent.tags.notice = true; + const noticeValidator = new Validator(mockConfig({ events: mockEvent })); + const node = findNode(contract.vEvents, 'BasicSample_BasicEvent'); + const natspec = mockNatspec({ + tags: [ + { + name: 'dev', + content: 'Modifier notice', + }, + ], + params: [], + }); + + const result = noticeValidator.validate(node, natspec); + expectWarning(result, `@notice is missing`, 1); + }); + + it('should have a warning if dev is forced for a function', () => { + const mockFunctions = defaultFunctions; + mockFunctions.external.tags.dev = true; + const devValidator = new Validator(mockConfig({ functions: mockFunctions })); + const node = findNode(contract.vFunctions, 'externalSimple'); + const natspec = mockNatspec({ + tags: [ + { + name: 'notice', + content: 'External function that returns a bool', + }, + ], + params: [], + returns: [ + { + name: '_isMagic', + content: 'Some return data', + }, + ], + }); + + const result = devValidator.validate(node, natspec); + expectWarning(result, `@dev is missing`, 1); + }); + + it('should have a warning if dev is forced for a modifier', () => { + const mockModifier = defaultTags; + mockModifier.tags.dev = true; + const devValidator = new Validator(mockConfig({ modifiers: mockModifier })); + const node = findNode(contract.vModifiers, 'transferFee'); + const natspec = mockNatspec({ + tags: [ + { + name: 'notice', + content: 'Modifier notice', + }, + ], + params: [], + }); + + const result = devValidator.validate(node, natspec); + expectWarning(result, `@dev is missing`, 1); + }); + + it('should have a warning if dev is forced for a struct', () => { + const mockStruct = defaultTags; + mockStruct.tags.dev = true; + const devValidator = new Validator(mockConfig({ structs: mockStruct })); + const node = findNode(contract.vStructs, 'TestStruct'); + const natspec = mockNatspec({ + tags: [ + { + name: 'notice', + content: 'Modifier notice', + }, + ], + params: [], + }); + + const result = devValidator.validate(node, natspec); + expectWarning(result, `@dev is missing`, 1); + }); + + it('should have a warning if dev is forced for an error', () => { + const mockError = defaultTags; + mockError.tags.dev = true; + const devValidator = new Validator(mockConfig({ errors: mockError })); + const node = findNode(contract.vErrors, 'BasicSample_SomeError'); + const natspec = mockNatspec({ + tags: [ + { + name: 'notice', + content: 'Modifier notice', + }, + ], + params: [], + }); + + const result = devValidator.validate(node, natspec); + expectWarning(result, `@dev is missing`, 1); + }); + + it('should have a warning if dev is forced for an event', () => { + const mockEvent = defaultTags; + mockEvent.tags.dev = true; + const devValidator = new Validator(mockConfig({ events: mockEvent })); + const node = findNode(contract.vEvents, 'BasicSample_BasicEvent'); + const natspec = mockNatspec({ + tags: [ + { + name: 'notice', + content: 'Modifier notice', + }, + ], + params: [], + }); + + const result = devValidator.validate(node, natspec); + expectWarning(result, `@dev is missing`, 1); + }); + + it('should have a warning if notice is duplicated for a function', () => { + const mockFunctions = defaultFunctions; + mockFunctions.external.tags.notice = true; + const noticeValidator = new Validator(mockConfig({ functions: mockFunctions })); + const node = findNode(contract.vFunctions, 'externalSimple'); + const natspec = mockNatspec({ + tags: [ + { + name: 'notice', + content: 'External function that returns a bool', + }, + { + name: 'notice', + content: 'External function that returns a bool', + }, + ], + params: [], + returns: [ + { + name: '_isMagic', + content: 'Some return data', + }, + ], + }); + + const result = noticeValidator.validate(node, natspec); + expectWarning(result, `@notice is duplicated`, 1); + }); + + it('should have a warning if noticve is duplicated for a modifier', () => { + const mockModifier = defaultTags; + mockModifier.tags.notice = true; + const noticeValidator = new Validator(mockConfig({ modifiers: mockModifier })); + const node = findNode(contract.vModifiers, 'transferFee'); + const natspec = mockNatspec({ + tags: [ + { + name: 'notice', + content: 'Modifier notice', + }, + { + name: 'notice', + content: 'Modifier notice', + }, + ], + params: [], + }); + + const result = noticeValidator.validate(node, natspec); + expectWarning(result, `@notice is duplicated`, 1); + }); + + it('should have a warning if notice is duplicated for a struct', () => { + const mockStruct = defaultTags; + mockStruct.tags.notice = true; + const noticeValidator = new Validator(mockConfig({ structs: mockStruct })); + const node = findNode(contract.vStructs, 'TestStruct'); + const natspec = mockNatspec({ + tags: [ + { + name: 'notice', + content: 'Modifier notice', + }, + { + name: 'notice', + content: 'Modifier notice', + }, + ], + params: [], + }); + + const result = noticeValidator.validate(node, natspec); + expectWarning(result, `@notice is duplicated`, 1); + }); + + it('should have a warning if notice is duplicated for an error', () => { + const mockError = defaultTags; + mockError.tags.notice = true; + const noticeValidator = new Validator(mockConfig({ errors: mockError })); + const node = findNode(contract.vErrors, 'BasicSample_SomeError'); + const natspec = mockNatspec({ + tags: [ + { + name: 'notice', + content: 'Modifier notice', + }, + { + name: 'notice', + content: 'Modifier notice', + }, + ], + params: [], + }); + + const result = noticeValidator.validate(node, natspec); + expectWarning(result, `@notice is duplicated`, 1); + }); + + it('should have a warning if notice is duplicated for an event', () => { + const mockEvent = defaultTags; + mockEvent.tags.notice = true; + const noticeValidator = new Validator(mockConfig({ events: mockEvent })); + const node = findNode(contract.vEvents, 'BasicSample_BasicEvent'); + const natspec = mockNatspec({ + tags: [ + { + name: 'notice', + content: 'Modifier notice', + }, + { + name: 'notice', + content: 'Modifier notice', + }, + ], + params: [], + }); + + const result = noticeValidator.validate(node, natspec); + expectWarning(result, `@notice is duplicated`, 1); + }); + + it('should have no warnings if everything is disabled for a function', () => { + const mockFunctions = defaultFunctions; + mockFunctions.external.tags.dev = false; + mockFunctions.external.tags.notice = false; + mockFunctions.external.tags.param = false; + mockFunctions.external.tags.return = false; + const noNoticeValidator = new Validator(mockConfig({ functions: mockFunctions })); + const node = findNode(contract.vFunctions, 'externalSimple'); + const natspec = mockNatspec({ + tags: [], + params: [], + returns: [], + }); + + const result = noNoticeValidator.validate(node, natspec); + expect(result).toEqual([]); + }); + + it('should have no warnings if everything is disabled for a modifier', () => { + const mockModifier = defaultTags; + mockModifier.tags.dev = false; + mockModifier.tags.notice = false; + mockModifier.tags.param = false; + const noNoticeValidator = new Validator(mockConfig({ modifiers: mockModifier })); + const node = findNode(contract.vModifiers, 'transferFee'); + const natspec = mockNatspec({ + tags: [], + params: [], + }); + + const result = noNoticeValidator.validate(node, natspec); + expect(result).toEqual([]); + }); + + it('should have no warnings if everything is disabled for a struct', () => { + const mockStruct = defaultTags; + mockStruct.tags.dev = false; + mockStruct.tags.notice = false; + mockStruct.tags.param = false; + const noNoticeValidator = new Validator(mockConfig({ structs: mockStruct })); + const node = findNode(contract.vStructs, 'TestStruct'); + const natspec = mockNatspec({ + tags: [], + params: [], + }); + + const result = noNoticeValidator.validate(node, natspec); + expect(result).toEqual([]); + }); + + it('should have no warnings if everything is disabled for an error', () => { + const mockError = defaultTags; + mockError.tags.dev = false; + mockError.tags.notice = false; + mockError.tags.param = false; + const noNoticeValidator = new Validator(mockConfig({ errors: mockError })); + const node = findNode(contract.vErrors, 'BasicSample_SomeError'); + const natspec = mockNatspec({ + tags: [], + params: [], + }); + + const result = noNoticeValidator.validate(node, natspec); + expect(result).toEqual([]); + }); + + it('should have no warnings if everything is disabled for an event', () => { + const mockEvent = defaultTags; + mockEvent.tags.dev = false; + mockEvent.tags.notice = false; + mockEvent.tags.param = false; + const noNoticeValidator = new Validator(mockConfig({ events: mockEvent })); + const node = findNode(contract.vEvents, 'BasicSample_BasicEvent'); + const natspec = mockNatspec({ + tags: [], + params: [], + }); + + const result = noNoticeValidator.validate(node, natspec); + expect(result).toEqual([]); + }); + + it('should have a warning if tags are empty and notice is forced for a modifier', () => { + const mockModifier = defaultTags; + mockModifier.tags.notice = true; + const noticeValidator = new Validator(mockConfig({ modifiers: mockModifier })); + const node = findNode(contract.vModifiers, 'transferFee'); + const natspec = mockNatspec({ + tags: [], + params: [], + }); + + const result = noticeValidator.validate(node, natspec); + expectWarning(result, `Natspec is missing`, 1); + }); + }); }); diff --git a/yarn.lock b/yarn.lock index 6bf0f70..aaf98b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -465,11 +470,71 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@es-joy/jsdoccomment@~0.42.0": + version "0.42.0" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.42.0.tgz#59e878708336aaee88c2b34c894f73dbf77ae2b0" + integrity sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw== + dependencies: + comment-parser "1.4.1" + esquery "^1.5.0" + jsdoc-type-pratt-parser "~4.0.0" + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.56.0": + version "8.56.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" + integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== + "@faker-js/faker@8.3.1": version "8.3.1" resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.3.1.tgz#7753df0cb88d7649becf984a96dd1bd0a26f43e3" integrity sha512-FdgpFxY6V6rLZE9mmIBb9hM0xpfvQOSNOLnzolzKwsE1DH+gC7lEKV1p1IbR0lAYyvYd5a4u3qWJzowUkw1bIw== +"@humanwhocodes/config-array@^0.11.13": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" + integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -748,7 +813,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -778,6 +843,11 @@ "@noble/hashes" "~1.3.0" "@scure/base" "~1.1.0" +"@sinclair/typebox@0.32.14": + version "0.32.14" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.32.14.tgz#ef0a4ed981515fd430cadfb65cb6c2719a0b5539" + integrity sha512-EC77Mw8huT2z9YlYbWfpIQgN6shZE1tH4NP4/Trig8UBel9FZNMZRJ42ubJI8PLor2uIU+waLml1dce5ReCOPg== + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -930,6 +1000,56 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/parser@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.2.0.tgz#d37c30b0f459c6f39455335d8f4f085919a1c644" + integrity sha512-igVYOqtiK/UsvKAmmloQAruAdUHihsOCvplJpplPZ+3h4aDkC/UKZZNKgB6h93ayuYLuEymU3h8nF1xMRbh37g== + dependencies: + "@typescript-eslint/scope-manager" "6.2.0" + "@typescript-eslint/types" "6.2.0" + "@typescript-eslint/typescript-estree" "6.2.0" + "@typescript-eslint/visitor-keys" "6.2.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.2.0.tgz#412a710d8fa20bc045533b3b19f423810b24f87a" + integrity sha512-1ZMNVgm5nnHURU8ZSJ3snsHzpFeNK84rdZjluEVBGNu7jDymfqceB3kdIZ6A4xCfEFFhRIB6rF8q/JIqJd2R0Q== + dependencies: + "@typescript-eslint/types" "6.2.0" + "@typescript-eslint/visitor-keys" "6.2.0" + +"@typescript-eslint/types@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.2.0.tgz#b341a4e6d5f609267306b07afc6f62bcf92b1495" + integrity sha512-1nRRaDlp/XYJQLvkQJG5F3uBTno5SHPT7XVcJ5n1/k2WfNI28nJsvLakxwZRNY5spuatEKO7d5nZWsQpkqXwBA== + +"@typescript-eslint/typescript-estree@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.0.tgz#4969944b831b481996aa4fbd73c7164ca683b8ef" + integrity sha512-Mts6+3HQMSM+LZCglsc2yMIny37IhUgp1Qe8yJUYVyO6rHP7/vN0vajKu3JvHCBIy8TSiKddJ/Zwu80jhnGj1w== + dependencies: + "@typescript-eslint/types" "6.2.0" + "@typescript-eslint/visitor-keys" "6.2.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/visitor-keys@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.0.tgz#71943f42fdaa2ec86dc3222091f41761a49ae71a" + integrity sha512-QbaYUQVKKo9bgCzpjz45llCfwakyoxHetIy8CAvYCtd16Zu1KrpzNHofwF8kGkpPOxZB2o6kz+0nqH8ZkIzuoQ== + dependencies: + "@typescript-eslint/types" "6.2.0" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + JSONStream@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -943,12 +1063,17 @@ abitype@0.7.1: resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.7.1.tgz#16db20abe67de80f6183cf75f3de1ff86453b745" integrity sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ== +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + acorn-walk@^8.1.1: version "8.3.1" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.1.tgz#2f10f5b69329d90ae18c58bf1fa8fccd8b959a43" integrity sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw== -acorn@^8.4.1: +acorn@^8.4.1, acorn@^8.9.0: version "8.11.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== @@ -961,7 +1086,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^8.11.0: +ajv@8.12.0, ajv@^8.11.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== @@ -1020,6 +1145,11 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" +are-docs-informative@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz#387f0e93f5d45280373d387a59d34c96db321963" + integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig== + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -1042,6 +1172,11 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -1180,6 +1315,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + call-bind@^1.0.2, call-bind@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" @@ -1358,6 +1498,11 @@ commander@^8.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +comment-parser@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.1.tgz#bdafead37961ac079be11eb7ec65c4d021eaf9cc" + integrity sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg== + compare-func@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" @@ -1435,7 +1580,7 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.3: +cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1449,7 +1594,7 @@ dargs@^7.0.0: resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -1479,6 +1624,11 @@ dedent@^1.0.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" @@ -1535,6 +1685,13 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + dot-prop@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -1589,11 +1746,121 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-plugin-jsdoc@48.1.0: + version "48.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.1.0.tgz#565363770b433485bfc70dc862b50b7f780529ec" + integrity sha512-g9S8ukmTd1DVcV/xeBYPPXOZ6rc8WJ4yi0+MVxJ1jBOrz5kmxV9gJJQ64ltCqIWFnBChLIhLVx3tbTSarqVyFA== + dependencies: + "@es-joy/jsdoccomment" "~0.42.0" + are-docs-informative "^0.0.2" + comment-parser "1.4.1" + debug "^4.3.4" + escape-string-regexp "^4.0.0" + esquery "^1.5.0" + is-builtin-module "^3.2.1" + semver "^7.6.0" + spdx-expression-parse "^4.0.0" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@8.56.0: + version "8.56.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15" + integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.56.0" + "@humanwhocodes/config-array" "^0.11.13" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.4.2, esquery@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + ethereum-cryptography@^2.0.0: version "2.1.2" resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz#18fa7108622e56481157a5cb7c01c0c6a672eb67" @@ -1657,12 +1924,12 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@3.3.2, fast-glob@^3.3.0: +fast-glob@3.3.2, fast-glob@^3.2.9, fast-glob@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -1673,11 +1940,16 @@ fast-glob@3.3.2, fast-glob@^3.3.0: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + fastq@^1.6.0: version "1.16.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.16.0.tgz#83b9a9375692db77a822df081edb6a9cf6839320" @@ -1692,6 +1964,13 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -1725,6 +2004,20 @@ findup-sync@^5.0.0: micromatch "^4.0.4" resolve-dir "^1.0.1" +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.2.9" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" + integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== + follow-redirects@^1.12.1, follow-redirects@^1.15.4: version "1.15.4" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" @@ -1823,6 +2116,13 @@ glob-parent@^5.1.2: dependencies: is-glob "^4.0.1" +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -1867,6 +2167,25 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + globby@^13.1.2: version "13.2.2" resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" @@ -1890,6 +2209,11 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -1975,12 +2299,17 @@ husky@8.0.3: resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== +ignore@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + ignore@^5.2.4: version "5.3.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== -import-fresh@^3.0.0, import-fresh@^3.3.0: +import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -2037,6 +2366,13 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.3: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -2076,7 +2412,7 @@ is-generator-function@^1.0.7: dependencies: has-tostringtag "^1.0.0" -is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -2093,6 +2429,11 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -2573,6 +2914,11 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsdoc-type-pratt-parser@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz#136f0571a99c184d84ec84662c45c29ceff71114" + integrity sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ== + jsel@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/jsel/-/jsel-1.1.6.tgz#9257fee6c6e8ad8e75d5706503fe84f376035896" @@ -2583,16 +2929,31 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + json-schema-traverse@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" @@ -2612,6 +2973,13 @@ jsonparse@^1.2.0: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -2627,6 +2995,14 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + lilconfig@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" @@ -2864,7 +3240,7 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@^3.0.4, minimatch@^3.1.1: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -2970,6 +3346,18 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -3099,6 +3487,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + prettier@2.8.8: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" @@ -3266,6 +3659,13 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -3457,6 +3857,14 @@ spdx-expression-parse@^3.0.0: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" +spdx-expression-parse@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz#a23af9f3132115465dac215c099303e4ceac5794" + integrity sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + spdx-license-ids@^3.0.0: version "3.0.16" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz#a14f64e0954f6e25cc6587bd4f392522db0d998f" @@ -3605,6 +4013,11 @@ text-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + through2@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" @@ -3646,6 +4059,11 @@ trim-newlines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== +ts-api-utils@^1.0.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.2.1.tgz#f716c7e027494629485b21c0df6180f4d08f5e8b" + integrity sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA== + ts-jest@29.1.1: version "29.1.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" @@ -3684,6 +4102,13 @@ tslib@^2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -3694,6 +4119,11 @@ type-fest@^0.18.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"