-
-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
62 changed files
with
34,747 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
148
packages/eslint-plugin-ts/rules/block-spacing/block-spacing.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
158
packages/eslint-plugin-ts/rules/block-spacing/block-spacing.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Oops, something went wrong.