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
14 changes: 14 additions & 0 deletions src/rules/block-scope-case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export default createRule({
},
defaultOptions: [],
create: (context) => {
const { sourceCode } = context;

return {
SwitchCase: (node) => {
if (node.consequent.length > 0 && node.consequent[0].type !== 'BlockStatement') {
Expand All @@ -29,6 +31,18 @@ export default createRule({
line: node.loc.start.line,
},
messageId: 'default',
fix: (fixer) => {
const colonToken = sourceCode.getTokenBefore(node.consequent[0]);

if (!colonToken) {
return null;
}

const lastStatement = node.consequent[node.consequent.length - 1];
const indent = ' '.repeat(node.loc.start.column);

return [fixer.insertTextAfter(colonToken, ' {'), fixer.insertTextAfter(lastStatement, `\n${indent}}`)];
},
});
}
},
Expand Down
17 changes: 14 additions & 3 deletions src/rules/catch-error-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,22 @@ export default createRule<Options, MessageIds>({
fixedName: expectedName,
},
fix: (fixer) => {
if (node.param) {
return fixer.replaceText(node.param, 'error');
if (!node.param) {
return null;
}

return null;
// renaming would conflict with an existing usage of the expected name
if (new RegExp(`\\b${expectedName}\\b`).test(context.sourceCode.getText(node.body))) {
return null;
}

const variable = context.sourceCode
.getDeclaredVariables(node)
.find(({ name }) => name === originalName);

const references = variable?.references.map(({ identifier }) => identifier) ?? [];

return [node.param, ...references].map((identifier) => fixer.replaceText(identifier, expectedName));
},
});
}
Expand Down
140 changes: 129 additions & 11 deletions src/rules/comment-syntax.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { TSESLint } from '@typescript-eslint/utils';
import { createRule } from '../util';
import { oneLine } from '../util/messages';

Expand Down Expand Up @@ -134,13 +135,20 @@ export default createRule<Options, MessageIds>({
const comments = sourceCode.getAllComments();

for (const comment of comments) {
// start of the comment value, right after the `//` or `/*`
const valueStart = comment.range[0] + 2;

if (comment.type === 'Line') {
const firstChar = comment.value?.charCodeAt(0);
const secondChar = comment.value[1];
const lastChar = comment.value[comment.value.length - 1];

if (firstChar !== SPACE_CHARCODE && firstChar !== SLASH_CHARCODE && !isRegion(comment.value)) {
context.report({ node: comment, messageId: 'shouldStartWithSpace' });
context.report({
node: comment,
messageId: 'shouldStartWithSpace',
fix: (fixer) => fixer.insertTextBeforeRange([valueStart, valueStart], ' '),
});

// if this one fails, the others are interpreted incorrectly
continue;
Expand All @@ -151,11 +159,19 @@ export default createRule<Options, MessageIds>({
isCapital(secondChar) &&
!isCapitalizedOrAllowed(comment.value.slice(1), ignoredWords)
) {
context.report({ node: comment, messageId: 'lineCommentCapital' });
context.report({
node: comment,
messageId: 'lineCommentCapital',
fix: (fixer) => fixer.replaceTextRange([valueStart + 1, valueStart + 2], secondChar.toLowerCase()),
});
}

if (lastChar === '.' && !comment.value.endsWith('etc.') && !comment.value.endsWith('...')) {
context.report({ node: comment, messageId: 'lineCommentEnding' });
context.report({
node: comment,
messageId: 'lineCommentEnding',
fix: (fixer) => fixer.removeRange([comment.range[1] - 1, comment.range[1]]),
});
}

continue;
Expand All @@ -169,9 +185,37 @@ export default createRule<Options, MessageIds>({
continue;
}

const valueLines = lines;
const indent = ' '.repeat(comment.loc.start.column + 1);

const lineStartOffsets: number[] = [];

let currentOffset = 0;

for (const valueLine of valueLines) {
lineStartOffsets.push(currentOffset);
currentOffset += valueLine.length + 1;
}

// absolute source range of the line at `index` in the sliced `lines` array
const lineRange = (index: number): [number, number] => {
const start = valueStart + lineStartOffsets[index + 1];

return [start, start + valueLines[index + 1].length];
};

// verify the first char is a '*'
if (lines[0] !== STAR) {
context.report({ node: comment, messageId: 'shouldStartWithBlock' });
context.report({
node: comment,
messageId: 'shouldStartWithBlock',
fix: (fixer) => {
const content = valueLines[0].replace(/^\*+\s*/, '').trim();
const range: [number, number] = [valueStart, valueStart + valueLines[0].length];

return fixer.replaceTextRange(range, content ? `*\n${indent}* ${content}` : '*');
},
});
continue;
}

Expand Down Expand Up @@ -199,12 +243,29 @@ export default createRule<Options, MessageIds>({

if (isLastLine) {
if (!BLOCK_COMMENT_END.test(line)) {
context.report({ node: comment, messageId: 'shouldEndWithBlock' });
context.report({
node: comment,
messageId: 'shouldEndWithBlock',
fix: (fixer) => {
const [start] = lineRange(i);
const contentEnd = start + line.replace(/\s+$/, '').length;

return fixer.replaceTextRange([contentEnd, comment.range[1] - 2], `\n${indent}`);
},
});
break;
}

if (EMPTY_BLOCK_COMMENT_LINE.test(prevLine)) {
context.report({ node: comment, messageId: 'noSpaceBeforeEnd' });
context.report({
node: comment,
messageId: 'noSpaceBeforeEnd',
fix: (fixer) => {
const [prevStart, prevEnd] = lineRange(i - 1);

return fixer.removeRange([prevStart - 1, prevEnd]);
},
});
break;
}

Expand All @@ -226,7 +287,16 @@ export default createRule<Options, MessageIds>({

if (isCurrentLineJSDoc && !insideCodeBlock) {
if (prevLine && !EMPTY_BLOCK_COMMENT_LINE.test(prevLine) && !JS_DOC_REGEX.test(prevLine)) {
context.report({ node: comment, messageId: 'spaceBeforeJSDoc', data: { ...lineData } });
context.report({
node: comment,
messageId: 'spaceBeforeJSDoc',
data: { ...lineData },
fix: (fixer) => {
const [start] = lineRange(i);

return fixer.insertTextBeforeRange([start, start], `${indent}*\n`);
},
});
break;
}

Expand All @@ -238,7 +308,23 @@ export default createRule<Options, MessageIds>({
}

if (!isCurrentLineEmpty && !BLOCK_COMMENT_LINE_START.test(line)) {
context.report({ node: comment, messageId: 'invalidBlockCommentLine', data: { ...lineData } });
context.report({
node: comment,
messageId: 'invalidBlockCommentLine',
data: { ...lineData },
fix: (fixer) => {
const [start] = lineRange(i);
const starMatch = line.match(/^\s*\*/);

if (starMatch) {
return fixer.insertTextAfterRange([start, start + starMatch[0].length], ' ');
}

const leadingWhitespace = line.match(/^\s*/)?.[0] ?? '';

return fixer.insertTextAfterRange([start, start + leadingWhitespace.length], '* ');
},
});
break;
}

Expand All @@ -249,7 +335,14 @@ export default createRule<Options, MessageIds>({
prevListItemIndentation = line.match(LIST_ITEM_INDENTATION)?.[1].length;

if (spaces !== 1) {
context.report({ node: comment, messageId: 'invalidListItem', data: { ...lineData } });
context.report({
node: comment,
messageId: 'invalidListItem',
data: { ...lineData },
fix: (fixer) => {
return fixer.replaceTextRange(lineRange(i), line.replace(/^(\s*\*\s*(?:-|\d\.))\s*/, '$1 '));
},
});
break;
}

Expand All @@ -276,7 +369,20 @@ export default createRule<Options, MessageIds>({
const firstChar = text[0];

if (isLetter(firstChar) && !(isCapital(firstChar) || isCapitalizedOrAllowed(text, ignoredWords))) {
context.report({ node: comment, messageId: 'paragraphCapitalized', data: { ...lineData } });
context.report({
node: comment,
messageId: 'paragraphCapitalized',
data: { ...lineData },
fix: (fixer) => {
const [start] = lineRange(i);
const prefixLength = line.length - text.length;

return fixer.replaceTextRange(
[start + prefixLength, start + prefixLength + 1],
firstChar.toUpperCase(),
);
},
});
break;
}
}
Expand All @@ -287,8 +393,19 @@ export default createRule<Options, MessageIds>({
if (isNextLineEmpty || isLastContentLine || isNextLineCodeBlock) {
const lastChar = line[line.length - 1];

const appendDot = (fixer: TSESLint.RuleFixer) => {
const [start] = lineRange(i);

return fixer.insertTextAfterRange([start, start + line.replace(/\s+$/, '').length], '.');
};

if (isLastContentLine && !allowedParagraphEndings.some((ending) => ending === lastChar)) {
context.report({ node: comment, messageId: 'shouldEndWithDot', data: { ...lineData } });
context.report({
node: comment,
messageId: 'shouldEndWithDot',
data: { ...lineData },
fix: appendDot,
});
break;
}

Expand All @@ -297,6 +414,7 @@ export default createRule<Options, MessageIds>({
node: comment,
messageId: 'invalidParagraphEnding',
data: { ...lineData, allowedParagraphEndings: `[${allowedParagraphEndings.join(' ')}]` },
fix: appendDot,
});

break;
Expand Down
4 changes: 4 additions & 0 deletions src/rules/lines-around-comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,10 @@ export default createRule<Options, MessageIds>({
node: token,
messageId,
fix(fixer) {
if (messageId === 'after') {
return fixer.insertTextAfterRange(token.range, '\n');
}

return fixer.insertTextBeforeRange(range, '\n');
},
});
Expand Down
36 changes: 36 additions & 0 deletions test/rules/block-scope-case.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,42 @@ ruleTester({
break;
}
`,
output: stripIndent`
const foo = 1;

switch (foo) {
case 1: {
break;
}
}
`,
errors: [
{
messageId,
},
],
},
{
// default case with multiple statements
code: stripIndent`
const foo = 1;

switch (foo) {
default:
console.log(foo);
break;
}
`,
output: stripIndent`
const foo = 1;

switch (foo) {
default: {
console.log(foo);
break;
}
}
`,
errors: [
{
messageId,
Expand Down
53 changes: 53 additions & 0 deletions test/rules/catch-error-name.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,58 @@ ruleTester({
} catch (error) {}
`,
},
{
// references to the parameter are renamed as well
code: stripIndent`
try {} catch (err) {
console.log(err.message, err);
}
`,
errors: [
{
messageId: 'default',
},
],
output: stripIndent`
try {} catch (error) {
console.log(error.message, error);
}
`,
},
{
// the configured name is used, not a hardcoded one
code: stripIndent`
try {} catch (error) {}
`,
options: [
{
name: 'exception',
ignore: [],
},
],
errors: [
{
messageId: 'default',
},
],
output: stripIndent`
try {} catch (exception) {}
`,
},
{
// no fix when the expected name is already used inside the catch block
code: stripIndent`
try {} catch (err) {
const error = 1;
console.log(err, error);
}
`,
errors: [
{
messageId: 'default',
},
],
output: null,
},
],
});
Loading
Loading