Skip to content

feat(eslint-plugin): Add semi [extension] #461

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 24, 2019
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 packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
| [`@typescript-eslint/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules. (`no-internal-module` from TSLint) | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async. (`promise-function-async` from TSLint) | | | :thought_balloon: |
| [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string. (`restrict-plus-operands` from TSLint) | | | :thought_balloon: |
| [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | |
| [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations (`typedef-whitespace` from TSLint) | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope. (`no-unbound-method` from TSLint) | :heavy_check_mark: | | :thought_balloon: |
| [`@typescript-eslint/unified-signatures`](./docs/rules/unified-signatures.md) | Warns for any two overloads that could be unified into one. (`unified-signatures` from TSLint) | | | |
Expand Down
25 changes: 25 additions & 0 deletions packages/eslint-plugin/docs/semi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# require or disallow semicolons instead of ASI (semi)

This rule enforces consistent use of semicolons.

## Rule Details

This rule extends the base [eslint/semi](https://eslint.org/docs/rules/semi) rule.
It supports all options and features of the base rule.
This version adds support for numerous typescript features.

## How to use

```cjson
{
// note you must disable the base rule as it can report incorrect errors
"semi": "off",
"@typescript-eslint/semi": ["error"]
}
```

## Options

See [eslint/semi options](https://eslint.org/docs/rules/semi#options).

<sup>Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/semi.md)</sup>
66 changes: 66 additions & 0 deletions packages/eslint-plugin/src/rules/semi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree';
import baseRule from 'eslint/lib/rules/semi';
import { RuleListener, RuleFunction } from 'ts-eslint';
import * as util from '../util';

export type Options = util.InferOptionsTypeFromRule<typeof baseRule>;
export type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;

export default util.createRule<Options, MessageIds>({
name: 'semi',
meta: {
type: 'layout',
docs: {
description: 'Require or disallow semicolons instead of ASI',
category: 'Stylistic Issues',
recommended: false,
},
fixable: 'code',
schema: baseRule.meta.schema,
messages: baseRule.meta.messages,
},
defaultOptions: [
'always',
{
omitLastInOneLineBlock: false,
beforeStatementContinuationChars: 'any',
},
],
create(context) {
const rules = baseRule.create(context);
const checkForSemicolon = rules.ExpressionStatement as RuleFunction<
TSESTree.Node
>;

/*
The following nodes are handled by the member-delimiter-style rule
AST_NODE_TYPES.TSCallSignatureDeclaration,
AST_NODE_TYPES.TSConstructSignatureDeclaration,
AST_NODE_TYPES.TSIndexSignature,
AST_NODE_TYPES.TSMethodSignature,
AST_NODE_TYPES.TSPropertySignature,
*/
const nodesToCheck = [
AST_NODE_TYPES.ClassProperty,
AST_NODE_TYPES.TSAbstractClassProperty,
AST_NODE_TYPES.TSAbstractMethodDefinition,
AST_NODE_TYPES.TSDeclareFunction,
AST_NODE_TYPES.TSExportAssignment,
AST_NODE_TYPES.TSImportEqualsDeclaration,
AST_NODE_TYPES.TSTypeAliasDeclaration,
].reduce<RuleListener>((acc, node) => {
acc[node] = checkForSemicolon;
return acc;
}, {});

return {
...rules,
...nodesToCheck,
ExportDefaultDeclaration(node) {
if (node.declaration.type !== AST_NODE_TYPES.TSInterfaceDeclaration) {
rules.ExportDefaultDeclaration(node);
}
},
};
},
});
119 changes: 119 additions & 0 deletions packages/eslint-plugin/tests/rules/semi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import rule, { MessageIds, Options } from '../../src/rules/semi';
import { InvalidTestCase, RuleTester, ValidTestCase } from '../RuleTester';

const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
});

ruleTester.run('semi', rule, {
valid: [
`
class Class {
prop: string;
}
`,
`
abstract class AbsClass {
abstract prop: string;
abstract meth(): string;
}
`,
`declare function declareFn(): string;`,
`export default interface Foo {}`,
'export = Foo;',
'import f = require("f");',
'type Foo = {};',
].reduce<ValidTestCase<Options>[]>((acc, code) => {
acc.push({
code,
options: ['always'],
});
acc.push({
code: code.replace(/;/g, ''),
options: ['never'],
});

return acc;
}, []),
invalid: [
{
code: `
class Class {
prop: string;
}
`,
errors: [
{
line: 3,
},
],
},
{
code: `
abstract class AbsClass {
abstract prop: string;
abstract meth(): string;
}
`,
errors: [
{
line: 3,
},
{
line: 4,
},
],
},
{
code: `declare function declareFn(): string;`,
errors: [
{
line: 1,
},
],
},
{
code: 'export = Foo;',
errors: [
{
line: 1,
},
],
},
{
code: 'import f = require("f");',
errors: [
{
line: 1,
},
],
},
{
code: 'type Foo = {};',
errors: [
{
line: 1,
},
],
},
].reduce<InvalidTestCase<MessageIds, Options>[]>((acc, test) => {
acc.push({
code: test.code.replace(/;/g, ''),
options: ['always'],
errors: test.errors.map(e => ({
...e,
message: 'Missing semicolon.',
})) as any,
});
acc.push({
code: test.code,
options: ['never'],
errors: test.errors.map(e => ({
...e,
message: 'Extra semicolon.',
})) as any,
});

return acc;
}, []),
});
31 changes: 31 additions & 0 deletions packages/eslint-plugin/typings/eslint-rules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,34 @@ declare module 'eslint/lib/rules/no-extra-parens' {
>;
export = rule;
}

declare module 'eslint/lib/rules/semi' {
import { TSESTree } from '@typescript-eslint/typescript-estree';
import RuleModule from 'ts-eslint';

const rule: RuleModule<
never,
[
'always' | 'never',
{
beforeStatementContinuationChars?: 'always' | 'any' | 'never';
omitLastInOneLineBlock?: boolean;
}?
],
{
VariableDeclaration(node: TSESTree.VariableDeclaration): void;
ExpressionStatement(node: TSESTree.ExpressionStatement): void;
ReturnStatement(node: TSESTree.ReturnStatement): void;
ThrowStatement(node: TSESTree.ThrowStatement): void;
DoWhileStatement(node: TSESTree.DoWhileStatement): void;
DebuggerStatement(node: TSESTree.DebuggerStatement): void;
BreakStatement(node: TSESTree.BreakStatement): void;
ContinueStatement(node: TSESTree.ContinueStatement): void;
ImportDeclaration(node: TSESTree.ImportDeclaration): void;
ExportAllDeclaration(node: TSESTree.ExportAllDeclaration): void;
ExportNamedDeclaration(node: TSESTree.ExportNamedDeclaration): void;
ExportDefaultDeclaration(node: TSESTree.ExportDefaultDeclaration): void;
}
>;
export = rule;
}
1 change: 1 addition & 0 deletions packages/eslint-plugin/typings/ts-eslint.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ declare module 'ts-eslint' {
ClassBody?: RuleFunction<TSESTree.ClassBody>;
ClassDeclaration?: RuleFunction<TSESTree.ClassDeclaration>;
ClassExpression?: RuleFunction<TSESTree.ClassExpression>;
ClassProperty?: RuleFunction<TSESTree.ClassProperty>;
Comment?: RuleFunction<TSESTree.Comment>;
ConditionalExpression?: RuleFunction<TSESTree.ConditionalExpression>;
ContinueStatement?: RuleFunction<TSESTree.ContinueStatement>;
Expand Down