Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export const rules = {
'invalid-cfn-imports': require('./rules/invalid-cfn-imports'),
'no-literal-partition': require('./rules/no-literal-partition'),
'no-invalid-path': require('./rules/no-invalid-path'),
'no-throw-default-error': require('./rules/no-throw-default-error'),
'promiseall-no-unbounded-parallelism': require('./rules/promiseall-no-unbounded-parallelism'),
};
52 changes: 52 additions & 0 deletions src/rules/no-throw-default-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Rule } from 'eslint';
import type { NewExpression, ThrowStatement } from 'estree';

export const meta = {
hasSuggestions: true,
};

export function create(context: Rule.RuleContext): Rule.NodeListener {
return {
ThrowStatement(node: ThrowStatement) {
if (node.argument.type !== 'NewExpression') {
return;
}

const newExpr = node.argument as NewExpression;
if (
newExpr.callee &&
newExpr.callee.type === 'Identifier' &&
newExpr.callee.name === 'Error'
) {
context.report({
node: newExpr,
message: 'Expected a non-default error object to be thrown.',
suggest: [
{
desc: 'Replace with `ValidationError`',
fix: (fixer: Rule.RuleFixer) => {
// no existing args
if (newExpr.arguments.length === 0) {
return fixer.replaceText(newExpr, "new ValidationError('<insert error message>', this)");
}

const fixes = [
fixer.replaceText(newExpr.callee, 'ValidationError'),
];

const last = newExpr.arguments.at(-1)?.range;
if (last) {
fixes.push(
fixer.insertTextAfterRange(last, ', this'),
);
}

return fixes;
},
},
],
});
}
},
};
}
40 changes: 37 additions & 3 deletions test/rules/fixtures.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ const fixturesRoot = path.join(__dirname, 'fixtures');
fs.readdirSync(fixturesRoot).filter(f => f !== 'lib').filter(f => fs.lstatSync(path.join(fixturesRoot, f)).isDirectory()).forEach(d => {
describe(d, () => {
const fixturesDir = path.join(fixturesRoot, d);
const fixtureFiles = fs.readdirSync(fixturesDir).filter(f => f.endsWith('.ts') && !f.endsWith('.expected.ts') && f !== 'index.ts');
const fixtureFiles = fs.readdirSync(fixturesDir).filter(f =>
f.endsWith('.ts')
&& !f.endsWith('.expected.ts')
&& !f.endsWith('.suggested.ts')
&& f !== 'index.ts',
);

if (!fixtureFiles.length) {
return;
Expand Down Expand Up @@ -43,11 +48,13 @@ fs.readdirSync(fixturesRoot).filter(f => f !== 'lib').filter(f => fs.lstatSync(p
it(f, async () => {
const originalFilePath = path.join(fixturesDir, f);
const expectedFixedFilePath = path.join(fixturesDir, `${path.basename(f, '.ts')}.expected.ts`);
const suggestedFixFilePath = path.join(fixturesDir, `${path.basename(f, '.ts')}.suggested.ts`);
const expectedErrorFilepath = path.join(fixturesDir, `${path.basename(f, '.ts')}.error.txt`);
const fix = fs.existsSync(expectedFixedFilePath);
const suggestion = fs.existsSync(suggestedFixFilePath);
const checkErrors = fs.existsSync(expectedErrorFilepath);
if (fix && checkErrors) {
fail(`Expected only a fixed file or an expected error message file. Both ${expectedFixedFilePath} and ${expectedErrorFilepath} are present.`);
if (fix && checkErrors && suggestion) {
fail(`Expected only a fixed file or an expected error message file. Multiple of ${expectedFixedFilePath}, ${suggestedFixFilePath} and ${expectedErrorFilepath} are present.`);
} else if (fix) {
const actualFile = await lintAndFix(originalFilePath, outputDir);
const actual = await fs.readFile(actualFile, { encoding: 'utf8' });
Expand All @@ -56,6 +63,14 @@ fs.readdirSync(fixturesRoot).filter(f => f !== 'lib').filter(f => fs.lstatSync(p
fail(`Linted file did not match expectations.\n--------- Expected ----------\n${expected}\n---------- Actual ----------\n${actual}`);
}
return;
} else if (suggestion) {
const actualFile = await lintAndApplySuggestion(originalFilePath, outputDir);
const actual = await fs.readFile(actualFile, { encoding: 'utf8' });
const expected = await fs.readFile(suggestedFixFilePath, { encoding: 'utf8' });
if (actual !== expected) {
fail(`Linted file did not match expectations.\n--------- Expected ----------\n${expected}\n---------- Actual ----------\n${actual}`);
}
return;
} else if (checkErrors) {
const actualErrorMessages = await lint(originalFilePath);
const expectedErrorMessages = (await fs.readFile(expectedErrorFilepath, { encoding: 'utf8' })).split('\n');
Expand Down Expand Up @@ -92,6 +107,25 @@ async function lintAndFix(file: string, outputDir: string) {
return newPath;
}

async function lintAndApplySuggestion(file: string, outputDir: string) {
const newPath = path.join(outputDir, path.basename(file));
let result = await linter.lintFiles(file);
const hasSuggestions = result.find(r => r.messages.find(m => m.suggestions?.length)) !== undefined;
if (hasSuggestions) {
for (const r of result) {
const suggestion = r.messages.find(m => m.suggestions?.length)?.suggestions?.[0];
if (suggestion && r.source) {
const output = r.source.slice(0, suggestion.fix.range[0]) + suggestion.fix.text + r.source.slice(suggestion.fix.range[1]);
await fs.writeFile(newPath, output, { encoding: 'utf8' });
}
}
} else {
// If there are no fixes, copy the input file as output
await fs.copyFile(file, newPath);
}
return newPath;
}

async function lint(file: string) {
const result = await linter.lintFiles(file);
// If you only lint one file, then result.length will always be one.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
throw new ValidationError('<insert error message>', this);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
throw new Error();
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
throw new ValidationError('My error', this);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
throw new Error('My error');
8 changes: 8 additions & 0 deletions test/rules/fixtures/no-throw-default-error/eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
plugins: ['local'],
rules: {
'local/no-throw-default-error': [ 'error' ],
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Expected a non-default error object to be thrown.
1 change: 1 addition & 0 deletions test/rules/fixtures/no-throw-default-error/throw-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
throw new Error('Some error');