Skip to content

feat(eslint-plugin): extend eslint semi rule #411

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

Closed
1 change: 1 addition & 0 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,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 (semi). (`semi` from TSLint) | | :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
3 changes: 2 additions & 1 deletion packages/eslint-plugin/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
| [`prefer-while`] | 🛑 | N/A |
| [`quotemark`] | 🌟 | [`quotes`][quotes] |
| [`return-undefined`] | 🛑 | N/A |
| [`semicolon`] | 🌟 | [`semi`][semi] |
| [`semicolon`] | 🌓 | [`@typescript-eslint/semi`] |
| [`space-before-function-paren`] | 🌟 | [`space-before-function-paren`][space-after-function-paren] |
| [`space-within-parens`] | 🌟 | [`space-in-parens`][space-in-parens] |
| [`switch-final-break`] | 🛑 | N/A |
Expand Down Expand Up @@ -611,6 +611,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint-
[`@typescript-eslint/prefer-function-type`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-function-type.md
[`@typescript-eslint/no-for-in-array`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-for-in-array.md
[`@typescript-eslint/no-unnecessary-qualifier`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md
[`@typescript-eslint/semi`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/semi.md

<!-- eslint-plugin-import -->

Expand Down
74 changes: 74 additions & 0 deletions packages/eslint-plugin/docs/rules/semi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# 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 plus TS type assertions.

## 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).

### always

Examples of **incorrect** TS code for this rule with the default `"always"` option:

<!-- prettier-ignore -->
```ts
/*eslint @typescript-eslint/semi: "error"*/
type Foo = {}

class PanCamera extends FreeCamera {
public invertY: boolean = false
}
```

Examples of **correct** TS code for this rule with the default `"always"` option:

<!-- prettier-ignore -->
```ts
/*eslint @typescript-eslint/semi: "error"*/
type Foo = {};

class PanCamera extends FreeCamera {
public invertY: boolean = false;
}
```

### never

Examples of **incorrect** TS code for this rule with the `"never"` option:

<!-- prettier-ignore -->
```ts
/*eslint @typescript-eslint/semi: ["error", "never"]*/
type Foo = {};

class PanCamera extends FreeCamera {
public invertY: boolean = false;
}
```

Examples of **correct** TS code for this rule with the `"never"` option:

<!-- prettier-ignore -->
```ts
/*eslint @typescript-eslint/semi: ["error", "never"]*/
type Foo = {}

class PanCamera extends FreeCamera {
public invertY: boolean = false
}
```
64 changes: 64 additions & 0 deletions packages/eslint-plugin/src/rules/semi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree';
import baseRule from 'eslint/lib/rules/semi';
import * as util from '../util';

type Options = util.InferOptionsTypeFromRule<typeof baseRule>;
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',
tslintRuleName: 'semi',
category: 'Stylistic Issues',
recommended: 'error',
},
fixable: 'code',
schema: baseRule.meta.schema,
messages: baseRule.meta.messages,
},
defaultOptions: ['always', { omitLastInOneLineBlock: false }] as Options,
create(context, optionsWithDefaults) {
// because we extend the base rule, have to update opts on the context
// the context defines options as readonly though...
const contextWithDefaults: typeof context = Object.create(context, {
options: {
writable: false,
configurable: false,
value: optionsWithDefaults,
},
});

const rules = baseRule.create(contextWithDefaults);

return Object.assign({}, rules, {
TSTypeAliasDeclaration(node: TSESTree.TSTypeAliasDeclaration) {
return rules.VariableDeclaration({
...node,
type: AST_NODE_TYPES.VariableDeclaration,
kind: 'const' as 'const',
declarations: [],
});
},
ClassProperty(node: TSESTree.ClassProperty) {
return rules.VariableDeclaration({
...node,
type: AST_NODE_TYPES.VariableDeclaration,
kind: 'const' as 'const',
declarations: [],
});
},
ExportDefaultDeclaration(node: TSESTree.ExportDefaultDeclaration) {
if (
!/(?:Class|Function|TSInterface)Declaration/.test(
node.declaration.type,
)
) {
rules.ExportDefaultDeclaration(node);
}
},
});
},
});
Loading