Skip to content

Commit

Permalink
feat: migrate ts rules
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Sep 18, 2023
1 parent 8cc6daf commit ca938b3
Show file tree
Hide file tree
Showing 62 changed files with 34,747 additions and 20 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@types/eslint": "^8.44.2",
"@types/fs-extra": "^11.0.2",
"@types/node": "^20.6.2",
"@types/semver": "^7.5.2",
"bumpp": "^9.2.0",
"eslint": "^8.49.0",
"esno": "^0.17.0",
Expand Down
20 changes: 19 additions & 1 deletion packages/eslint-plugin-ts/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,22 @@

| Rule ID | Description | Fixable | Recommended |
| --- | --- | --- | --- |
| [`@stylistic/ts/semi`](./rules/semi) | Require or disallow semicolons instead of ASI || |
| [`@stylistic/ts/block-spacing`](./rules/block-spacing) | Disallow or enforce spaces inside of blocks after opening block and before closing block || |
| [`@stylistic/ts/brace-style`](./rules/brace-style) | Enforce consistent brace style for blocks || |
| [`@stylistic/ts/comma-dangle`](./rules/comma-dangle) | Require or disallow trailing commas || |
| [`@stylistic/ts/comma-spacing`](./rules/comma-spacing) | Enforce consistent spacing before and after commas || |
| [`@stylistic/ts/func-call-spacing`](./rules/func-call-spacing) | Require or disallow spacing between function identifiers and their invocations || |
| [`@stylistic/ts/indent`](./rules/indent) | Enforce consistent indentation || |
| [`@stylistic/ts/key-spacing`](./rules/key-spacing) | Enforce consistent spacing between property names and type annotations in types and interfaces || |
| [`@stylistic/ts/keyword-spacing`](./rules/keyword-spacing) | Enforce consistent spacing before and after keywords || |
| [`@stylistic/ts/lines-around-comment`](./rules/lines-around-comment) | Require empty lines around comments || |
| [`@stylistic/ts/lines-between-class-members`](./rules/lines-between-class-members) | Require or disallow an empty line between class members || |
| [`@stylistic/ts/member-delimiter-style`](./rules/member-delimiter-style) | Require a specific member delimiter style for interfaces and type literals || |
| [`@stylistic/ts/no-extra-parens`](./rules/no-extra-parens) | Disallow unnecessary parentheses || |
| [`@stylistic/ts/padding-line-between-statements`](./rules/padding-line-between-statements) | Require or disallow padding lines between statements || |
| [`@stylistic/ts/quotes`](./rules/quotes) | Enforce the consistent use of either backticks, double, or single quotes || |
| [`@stylistic/ts/semi`](./rules/semi) | Require or disallow semicolons instead of ASI || |
| [`@stylistic/ts/space-before-blocks`](./rules/space-before-blocks) | Enforce consistent spacing before blocks || |
| [`@stylistic/ts/space-before-function-paren`](./rules/space-before-function-paren) | Enforce consistent spacing before function parenthesis || |
| [`@stylistic/ts/space-infix-ops`](./rules/space-infix-ops) | Require spacing around infix operators || |
| [`@stylistic/ts/type-annotation-spacing`](./rules/type-annotation-spacing) | Require consistent spacing around type annotations || |
10 changes: 10 additions & 0 deletions packages/eslint-plugin-ts/rules/block-spacing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
description: 'Disallow or enforce spaces inside of blocks after opening block and before closing block.'
---

> 🛑 This file is source code, not the primary documentation location! 🛑
>
> See **https://typescript-eslint.io/rules/block-spacing** for documentation.
This rule extends the base [`eslint/block-spacing`](https://eslint.org/docs/rules/block-spacing) rule.
This version adds support for TypeScript related blocks (interfaces, object type literals and enums).
148 changes: 148 additions & 0 deletions packages/eslint-plugin-ts/rules/block-spacing/block-spacing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import type {
InvalidTestCase,
ValidTestCase,
} from '@typescript-eslint/rule-tester'
import { RuleTester } from '@typescript-eslint/rule-tester'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'

import rule from './block-spacing'

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

type InvalidBlockSpacingTestCase = InvalidTestCase<
'extra' | 'missing',
['always' | 'never']
>

const options = ['always', 'never'] as const
const typeDeclarations = [
{
nodeType: AST_NODE_TYPES.TSInterfaceBody,
stringPrefix: 'interface Foo ',
},
{
nodeType: AST_NODE_TYPES.TSTypeLiteral,
stringPrefix: 'type Foo = ',
},
{
nodeType: AST_NODE_TYPES.TSEnumDeclaration,
stringPrefix: 'enum Foo ',
},
{
nodeType: AST_NODE_TYPES.TSEnumDeclaration,
stringPrefix: 'const enum Foo ',
},
]
const emptyBlocks = ['{}', '{ }']
const singlePropertyBlocks = ['{bar: true}', '{ bar: true }']
const blockComment = '/* comment */'

ruleTester.run('block-spacing', rule, {
valid: [
// Empty blocks don't apply
...options.flatMap(option =>
typeDeclarations.flatMap(typeDec =>
emptyBlocks.map<ValidTestCase<['always' | 'never']>>(blockType => ({
code: typeDec.stringPrefix + blockType,
options: [option],
})),
),
),
...typeDeclarations.flatMap<ValidTestCase<['always' | 'never']>>(
(typeDec) => {
const property
= typeDec.nodeType === AST_NODE_TYPES.TSEnumDeclaration
? 'bar = 1'
: 'bar: true;'
return [
{
code: `${typeDec.stringPrefix}{ /* comment */ ${property} /* comment */ } // always`,
options: ['always'],
},
{
code: `${typeDec.stringPrefix}{/* comment */ ${property} /* comment */} // never`,
options: ['never'],
},
{
code: `${typeDec.stringPrefix}{ //comment\n ${property}}`,
options: ['never'],
},
]
},
),
],
invalid: [
...options.flatMap(option =>
typeDeclarations.flatMap((typeDec) => {
return singlePropertyBlocks.flatMap<InvalidBlockSpacingTestCase>(
(blockType, blockIndex) => {
// These are actually valid, so filter them out
if (
(option === 'always' && blockType.startsWith('{ '))
|| (option === 'never' && blockType.startsWith('{bar'))
)
return []

const reverseBlockType = singlePropertyBlocks[1 - blockIndex]
let code = `${typeDec.stringPrefix}${blockType}; /* ${option} */`
let output = `${typeDec.stringPrefix}${reverseBlockType}; /* ${option} */`
if (typeDec.nodeType === AST_NODE_TYPES.TSEnumDeclaration) {
output = output.replace(':', '=')
code = code.replace(':', '=')
}

return {
code,
options: [option],
output,
errors: [
{
type: typeDec.nodeType,
messageId: option === 'always' ? 'missing' : 'extra',
data: { location: 'after', token: '{' },
},
{
type: typeDec.nodeType,
messageId: option === 'always' ? 'missing' : 'extra',
data: { location: 'before', token: '}' },
},
],
}
},
)
}),
),
// With block comments
...options.flatMap(option =>
typeDeclarations.flatMap<InvalidBlockSpacingTestCase>((typeDec) => {
const property
= typeDec.nodeType === AST_NODE_TYPES.TSEnumDeclaration
? 'bar = 1'
: 'bar: true;'
const alwaysSpace = option === 'always' ? '' : ' '
const neverSpace = option === 'always' ? ' ' : ''
return [
{
code: `${typeDec.stringPrefix}{${alwaysSpace}${blockComment}${property}${blockComment}${alwaysSpace}} /* ${option} */`,
output: `${typeDec.stringPrefix}{${neverSpace}${blockComment}${property}${blockComment}${neverSpace}} /* ${option} */`,
options: [option],
errors: [
{
type: typeDec.nodeType,
messageId: option === 'always' ? 'missing' : 'extra',
data: { location: 'after', token: '{' },
},
{
type: typeDec.nodeType,
messageId: option === 'always' ? 'missing' : 'extra',
data: { location: 'before', token: '}' },
},
],
},
]
}),
),
],
})
158 changes: 158 additions & 0 deletions packages/eslint-plugin-ts/rules/block-spacing/block-spacing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import type { TSESTree } from '@typescript-eslint/utils'
import { AST_TOKEN_TYPES } from '@typescript-eslint/utils'

import * as util from '../../util'
import { getESLintCoreRule } from '../../util/getESLintCoreRule'

const baseRule = getESLintCoreRule('block-spacing')

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

export default util.createRule<Options, MessageIds>({
name: 'block-spacing',
meta: {
type: 'layout',
docs: {
description:
'Disallow or enforce spaces inside of blocks after opening block and before closing block',
extendsBaseRule: true,
},
fixable: 'whitespace',
hasSuggestions: baseRule.meta.hasSuggestions,
schema: baseRule.meta.schema,
messages: baseRule.meta.messages,
},
defaultOptions: ['always'],

create(context, [whenToApplyOption]) {
const sourceCode = context.getSourceCode()
const baseRules = baseRule.create(context)
const always = whenToApplyOption !== 'never'
const messageId = always ? 'missing' : 'extra'
/**
* Gets the open brace token from a given node.
* @returns The token of the open brace.
*/
function getOpenBrace(
node: TSESTree.TSEnumDeclaration,
): TSESTree.PunctuatorToken {
// guaranteed for enums
// This is the only change made here from the base rule
return sourceCode.getFirstToken(node, {
filter: token =>
token.type === AST_TOKEN_TYPES.Punctuator && token.value === '{',
}) as TSESTree.PunctuatorToken
}

/**
* Checks whether or not:
* - given tokens are on same line.
* - there is/isn't a space between given tokens.
* @param left A token to check.
* @param right The token which is next to `left`.
* @returns
* When the option is `"always"`, `true` if there are one or more spaces between given tokens.
* When the option is `"never"`, `true` if there are not any spaces between given tokens.
* If given tokens are not on same line, it's always `true`.
*/
function isValid(left: TSESTree.Token, right: TSESTree.Token): boolean {
return (
!util.isTokenOnSameLine(left, right)
|| sourceCode.isSpaceBetween!(left, right) === always
)
}

/**
* Checks and reports invalid spacing style inside braces.
*/
function checkSpacingInsideBraces(node: TSESTree.TSEnumDeclaration): void {
// Gets braces and the first/last token of content.
const openBrace = getOpenBrace(node)
const closeBrace = sourceCode.getLastToken(node)!
const firstToken = sourceCode.getTokenAfter(openBrace, {
includeComments: true,
})!
const lastToken = sourceCode.getTokenBefore(closeBrace, {
includeComments: true,
})!

// Skip if the node is invalid or empty.
if (
openBrace.type !== AST_TOKEN_TYPES.Punctuator
|| openBrace.value !== '{'
|| closeBrace.type !== AST_TOKEN_TYPES.Punctuator
|| closeBrace.value !== '}'
|| firstToken === closeBrace
)
return

// Skip line comments for option never
if (!always && firstToken.type === AST_TOKEN_TYPES.Line)
return

if (!isValid(openBrace, firstToken)) {
let loc = openBrace.loc

if (messageId === 'extra') {
loc = {
start: openBrace.loc.end,
end: firstToken.loc.start,
}
}

context.report({
node,
loc,
messageId,
data: {
location: 'after',
token: openBrace.value,
},
fix(fixer) {
if (always)
return fixer.insertTextBefore(firstToken, ' ')

return fixer.removeRange([openBrace.range[1], firstToken.range[0]])
},
})
}
if (!isValid(lastToken, closeBrace)) {
let loc = closeBrace.loc

if (messageId === 'extra') {
loc = {
start: lastToken.loc.end,
end: closeBrace.loc.start,
}
}
context.report({
node,
loc,
messageId,
data: {
location: 'before',
token: closeBrace.value,
},
fix(fixer) {
if (always)
return fixer.insertTextAfter(lastToken, ' ')

return fixer.removeRange([lastToken.range[1], closeBrace.range[0]])
},
})
}
}
return {
...baseRules,

// This code worked "out of the box" for interface and type literal
// Enums were very close to match as well, the only reason they are not is that was that enums don't have a body node in the parser
// So the opening brace punctuator starts in the middle of the node - `getFirstToken` in
// the base rule did not filter for the first opening brace punctuator
TSInterfaceBody: baseRules.BlockStatement as never,
TSTypeLiteral: baseRules.BlockStatement as never,
TSEnumDeclaration: checkSpacingInsideBraces,
}
},
})
10 changes: 10 additions & 0 deletions packages/eslint-plugin-ts/rules/brace-style/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
description: 'Enforce consistent brace style for blocks.'
---

> 🛑 This file is source code, not the primary documentation location! 🛑
>
> See **https://typescript-eslint.io/rules/brace-style** for documentation.
This rule extends the base [`eslint/brace-style`](https://eslint.org/docs/rules/brace-style) rule.
It adds support for `enum`, `interface`, `namespace` and `module` declarations.
Loading

0 comments on commit ca938b3

Please sign in to comment.