Skip to content

Commit

Permalink
feat(): add checkForThrowingPlainErrors; add support for ignore paths…
Browse files Browse the repository at this point in the history
…; fix bug where path was never forwarded to final check
  • Loading branch information
mikaelvesavuori committed Jul 8, 2023
1 parent 1564692 commit 4688474
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 24 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,11 @@ Checks if there are any tests in the provided location. This will match anything
Checks:

- Do:
- Check for throwing native errors
- Ensure methods/functions are below a certain threshold in terms of lines of code
- Documentation coverage
- Ensure imports follow acceptable conventions
- No cyclic methods/dependencies
- Ensure methods/functions are below a certain threshold in terms of lines of code
- Check for throwing native errors
- Do later:
- Service metadata: Do you link to observability resources (logs/metrics/traces/dashboards etc.)?
- Support for external config
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "standardlint",
"description": "Extensible standards linter and auditor.",
"version": "1.0.4",
"version": "1.0.5",
"author": "Mikael Vesavuori",
"license": "MIT",
"keywords": [
Expand Down
12 changes: 8 additions & 4 deletions src/checks/checkForConsoleUsage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,29 @@ import { calculatePass } from '../application/calculatePass';
import { logDefaultPathMessage } from '../utils/logDefaultPathMessage';
import { getAllFiles } from '../utils/getAllFiles';
import { readFile } from '../utils/readFile';
import { filterFiles } from '../utils/filterFiles';

/**
* @description Checks if there is an API schema.
*/
export function checkForConsoleUsage(
severity: Severity,
basePath: string,
customPath?: string
customPath?: string,
ignorePaths?: string[]
): CheckResult {
const path = customPath || 'src';
const name = 'Console usage';
const message = 'Check for console usage';

if (!customPath) logDefaultPathMessage(name, path);

const tests = getAllFiles(`${basePath}/${path}`, []);
const files = getAllFiles(`${basePath}/${path}`, []);
const filteredFiles = ignorePaths ? filterFiles(files, ignorePaths) : files;

const regex = /console.(.*)/gi;
const includesConsole = tests.map((test: string) => regex.test(readFile(test)));
const result = !includesConsole.some(() => true); // We don't want any occurrences
const includesConsole = filteredFiles.map((test: string) => regex.test(readFile(test)));
const result = !includesConsole.includes(true); // We don't want any occurrences

return {
name,
Expand Down
8 changes: 6 additions & 2 deletions src/checks/checkForPresenceTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import { calculatePass } from '../application/calculatePass';

import { logDefaultPathMessage } from '../utils/logDefaultPathMessage';
import { getAllFiles } from '../utils/getAllFiles';
import { filterFiles } from '../utils/filterFiles';

/**
* @description Checks if there are tests.
*/
export function checkForPresenceTests(
severity: Severity,
basePath: string,
customPath?: string
customPath?: string,
ignorePaths?: string[]
): CheckResult {
const path = customPath || 'tests';
const name = 'Tests';
Expand All @@ -20,7 +22,9 @@ export function checkForPresenceTests(
if (!customPath) logDefaultPathMessage(name, path);

const files = getAllFiles(`${basePath}/${path}`, []);
const tests = files.filter(
const filteredFiles = ignorePaths ? filterFiles(files, ignorePaths) : files;

const tests = filteredFiles.filter(
(file: string) =>
file.endsWith('test.ts') ||
file.endsWith('spec.ts') ||
Expand Down
39 changes: 39 additions & 0 deletions src/checks/checkForThrowingPlainErrors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { CheckResult, Severity } from '../interface/Check';

import { calculatePass } from '../application/calculatePass';

import { logDefaultPathMessage } from '../utils/logDefaultPathMessage';
import { getAllFiles } from '../utils/getAllFiles';
import { readFile } from '../utils/readFile';
import { filterFiles } from '../utils/filterFiles';

/**
* @description Checks if plain (non-custom) errors are thrown.
*/
export function checkForThrowingPlainErrors(
severity: Severity,
basePath: string,
customPath?: string,
ignorePaths?: string[]
): CheckResult {
const path = customPath || 'src';
const name = 'Error handling';
const message = 'Check for presence of plain (non-custom) errors';

if (!customPath) logDefaultPathMessage(name, path);

const files = getAllFiles(`${basePath}/${path}`, []);
const filteredFiles = ignorePaths ? filterFiles(files, ignorePaths) : files;

const regex = /(throw Error|throw new Error)(.*)/gi;
const includesError = filteredFiles.map((test: string) => regex.test(readFile(test)));

const result = !includesError.includes(true);

return {
name,
status: calculatePass(result, severity),
message,
path
};
}
47 changes: 36 additions & 11 deletions src/domain/StandardLint.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CheckResult, Check, Severity, CheckInput } from '../interface/Check';
import { CheckResult, Check, Severity, CheckInput, IgnorePath } from '../interface/Check';
import { Configuration, ConfigurationInput, Result } from '../interface/StandardLint';

import { getStatusCount } from '../application/getStatusCount';
Expand All @@ -22,6 +22,7 @@ import { checkForPresenceServiceMetadata } from '../checks/checkForPresenceServi
import { checkForPresenceTemplateIssues } from '../checks/checkForPresenceTemplateIssues';
import { checkForPresenceTemplatePullRequests } from '../checks/checkForPresenceTemplatePullRequests';
import { checkForPresenceTests } from '../checks/checkForPresenceTests';
import { checkForThrowingPlainErrors } from '../checks/checkForThrowingPlainErrors';

import { exists } from '../utils/exists';

Expand All @@ -41,6 +42,7 @@ export function createNewStandardLint(config?: ConfigurationInput) {
class StandardLint {
private readonly defaultBasePathFallback = '.';
private readonly defaultSeverityFallback = 'error';
private readonly defaultIgnorePathsFallback = [];
readonly config: Configuration;

constructor(config?: ConfigurationInput) {
Expand All @@ -60,8 +62,12 @@ class StandardLint {
? this.getValidatedSeverityLevel(configInput.defaultSeverity)
: this.defaultSeverityFallback;

const ignorePaths = configInput?.ignorePaths
? this.getSanitizedPaths(configInput.ignorePaths)
: this.defaultIgnorePathsFallback;

const checkList = Array.isArray(configInput?.checks) ? configInput?.checks : [];
const checks = this.getValidatedChecks(checkList as CheckInput[], defaultSeverity);
const checks = this.getValidatedChecks(checkList as CheckInput[], defaultSeverity, ignorePaths);

return {
basePath,
Expand All @@ -80,13 +86,25 @@ class StandardLint {
return this.defaultSeverityFallback;
}

/**
* @description Sanitizes provided paths or return empty array if none is provided.
*/
private getSanitizedPaths(ignorePaths: IgnorePath[]) {
if (ignorePaths.length === 0) return [];
return ignorePaths.filter((path: string) => typeof path === 'string');
}

/**
* @description Validates and sanitizes a requested list of checks.
*
* Provide `defaultSeverity` as it's not yet available in the class `config` object
* when running the validation.
*/
private getValidatedChecks(checks: (string | CheckInput)[], defaultSeverity: Severity): Check[] {
private getValidatedChecks(
checks: (string | CheckInput)[],
defaultSeverity: Severity,
ignorePaths: IgnorePath[]
): Check[] {
const validCheckNames = [
'all',
'checkForConflictingLockfiles',
Expand All @@ -107,7 +125,8 @@ class StandardLint {
'checkForPresenceServiceMetadata',
'checkForPresenceTemplateIssues',
'checkForPresenceTemplatePullRequests',
'checkForPresenceTests'
'checkForPresenceTests',
'checkForThrowingPlainErrors'
];

const isValidCheckName = (name: string) => validCheckNames.includes(name);
Expand All @@ -122,19 +141,21 @@ class StandardLint {
if (typeof check === 'string' && isValidCheckName(check))
return <Check>{
name: check,
severity: defaultSeverity
severity: defaultSeverity,
ignorePaths
};

if (typeof check === 'object' && isValidCheckName(check.name))
return <Check>{
name: check.name,
severity: this.getValidatedSeverityLevel(check.severity || defaultSeverity)
path: check.path || '',
severity: this.getValidatedSeverityLevel(check.severity || defaultSeverity),
ignorePaths
};

// No match, remove in filter step
return <Check>{
name: '',
severity: defaultSeverity
name: ''
};
})
.filter((check: CheckInput) => check.name);
Expand Down Expand Up @@ -166,12 +187,13 @@ class StandardLint {
* @description Run test on an individual Check.
*/
private test(check: Check): CheckResult {
const { name, severity, path } = check;
const { name, severity, path, ignorePaths } = check;

const checksList: any = {
checkForConflictingLockfiles: () =>
checkForConflictingLockfiles(severity, this.config.basePath),
checkForConsoleUsage: () => checkForConsoleUsage(severity, this.config.basePath, path),
checkForConsoleUsage: () =>
checkForConsoleUsage(severity, this.config.basePath, path, ignorePaths),
checkForDefinedRelations: () =>
checkForDefinedRelations(severity, this.config.basePath, path),
checkForDefinedServiceLevelObjectives: () =>
Expand All @@ -198,7 +220,10 @@ class StandardLint {
checkForPresenceTemplateIssues(severity, this.config.basePath, path),
checkForPresenceTemplatePullRequests: () =>
checkForPresenceTemplatePullRequests(severity, this.config.basePath, path),
checkForPresenceTests: () => checkForPresenceTests(severity, this.config.basePath, path)
checkForPresenceTests: () =>
checkForPresenceTests(severity, this.config.basePath, path, ignorePaths),
checkForThrowingPlainErrors: () =>
checkForThrowingPlainErrors(severity, this.config.basePath, path, ignorePaths)
};

const result = checksList[name]();
Expand Down
7 changes: 7 additions & 0 deletions src/interface/Check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type CheckInput = {
name: string;
severity?: Severity;
path?: string;
ignorePaths?: IgnorePath[];
};

/**
Expand All @@ -14,6 +15,7 @@ export type Check = {
name: string;
severity: Severity;
path: string;
ignorePaths: IgnorePath[];
};

/**
Expand All @@ -35,3 +37,8 @@ export type Status = 'pass' | 'warn' | 'fail';
* @description Represents how severe a non-passing state is.
*/
export type Severity = 'error' | 'warn';

/**
* @description A path that will be ignored during certain checks.
*/
export type IgnorePath = string;
7 changes: 6 additions & 1 deletion src/interface/StandardLint.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Check, CheckInput, CheckResult, Severity } from './Check';
import { Check, CheckInput, CheckResult, IgnorePath, Severity } from './Check';

/**
* @description The `StandardLint` configuration.
Expand All @@ -17,6 +17,10 @@ export type Configuration = {
* default fallback level.
*/
defaultSeverity: Severity;
/**
* @description For some checks, the user can choose to ignore files on these paths.
*/
ignorePaths: IgnorePath[];
};

/**
Expand All @@ -26,6 +30,7 @@ export type ConfigurationInput = {
basePath?: string;
checks?: (string | CheckInput)[];
defaultSeverity?: Severity;
ignorePaths?: IgnorePath[];
};

/**
Expand Down
11 changes: 11 additions & 0 deletions src/utils/filterFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* @description Filters out file paths based on provided ignore paths.
*/
export function filterFiles(files: string[], ignorePaths: string[]) {
return files.filter((file: string) =>
ignorePaths.every((ignorePath: string) => {
const localFilePath = file.replace(process.cwd(), ''); // This is so we don't catch on other file paths in the system
return !localFilePath.includes(ignorePath);
})
);
}
15 changes: 14 additions & 1 deletion tests/checks/checkForConsoleUsage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,21 @@ test('It should pass when not finding any console usage when using check-only pa
t.deepEqual(result, expected);
});

test('It should pass and accept ignore paths', (t) => {
const expected = 'pass';

const standardlint = createNewStandardLint({
basePath: 'testdata',
ignorePaths: ['/src/'],
checks: [{ name: 'checkForConsoleUsage' }]
});
const result = standardlint.check().results?.[0]?.status;

t.deepEqual(result, expected);
});

/**
* NEGATIVE CASES
* NEGATIVE TESTS
*/

test('It should fail when finding console.log()', (t) => {
Expand Down
35 changes: 35 additions & 0 deletions tests/checks/checkForThrowingPlainErrors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import test from 'ava';

import { createNewStandardLint } from '../../src/domain/StandardLint';

test('It should pass when not finding any plain errors being thrown', (t) => {
const expected = 'pass';

const standardlint = createNewStandardLint({
basePath: '',
checks: [
{
name: 'checkForThrowingPlainErrors',
path: 'tests'
}
]
});
const result = standardlint.check().results?.[0]?.status;

t.deepEqual(result, expected);
});

/**
* NEGATIVE TESTS
*/

test('It should fail if it finds a plain error being thrown', (t) => {
const expected = 'fail';

const standardlint = createNewStandardLint({
checks: ['checkForThrowingPlainErrors']
});
const result = standardlint.check().results?.[0]?.status;

t.deepEqual(result, expected);
});
Loading

0 comments on commit 4688474

Please sign in to comment.