Skip to content

Commit

Permalink
feat(eslint-plugin): [object-curly-spacing] support MappedType (#3176)
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthiasKunnen committed Mar 21, 2021
1 parent e3a3ea0 commit 0557a43
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 12 deletions.
39 changes: 29 additions & 10 deletions packages/eslint-plugin/src/rules/object-curly-spacing.ts
Expand Up @@ -63,7 +63,7 @@ export default createRule<Options, MessageIds>({
* @param token The token to use for the report.
*/
function reportNoBeginningSpace(
node: TSESTree.TSTypeLiteral,
node: TSESTree.TSMappedType | TSESTree.TSTypeLiteral,
token: TSESTree.Token,
): void {
const nextToken = context
Expand All @@ -89,7 +89,7 @@ export default createRule<Options, MessageIds>({
* @param token The token to use for the report.
*/
function reportNoEndingSpace(
node: TSESTree.TSTypeLiteral,
node: TSESTree.TSMappedType | TSESTree.TSTypeLiteral,
token: TSESTree.Token,
): void {
const previousToken = context
Expand All @@ -115,7 +115,7 @@ export default createRule<Options, MessageIds>({
* @param token The token to use for the report.
*/
function reportRequiredBeginningSpace(
node: TSESTree.TSTypeLiteral,
node: TSESTree.TSMappedType | TSESTree.TSTypeLiteral,
token: TSESTree.Token,
): void {
context.report({
Expand All @@ -137,7 +137,7 @@ export default createRule<Options, MessageIds>({
* @param token The token to use for the report.
*/
function reportRequiredEndingSpace(
node: TSESTree.TSTypeLiteral,
node: TSESTree.TSMappedType | TSESTree.TSTypeLiteral,
token: TSESTree.Token,
): void {
context.report({
Expand All @@ -162,7 +162,7 @@ export default createRule<Options, MessageIds>({
* @param last The last token to check (should be closing brace)
*/
function validateBraceSpacing(
node: TSESTree.TSTypeLiteral,
node: TSESTree.TSMappedType | TSESTree.TSTypeLiteral,
first: TSESTree.Token,
second: TSESTree.Token | TSESTree.Comment,
penultimate: TSESTree.Token | TSESTree.Comment,
Expand All @@ -175,7 +175,10 @@ export default createRule<Options, MessageIds>({

const openingCurlyBraceMustBeSpaced =
options.arraysInObjectsException &&
secondType === AST_NODE_TYPES.TSIndexSignature
[
AST_NODE_TYPES.TSMappedType,
AST_NODE_TYPES.TSIndexSignature,
].includes(secondType)
? !options.spaced
: options.spaced;

Expand All @@ -197,15 +200,19 @@ export default createRule<Options, MessageIds>({
isClosingBracketToken(penultimate)) ||
(options.objectsInObjectsException &&
isClosingBraceToken(penultimate));
const penultimateType =
shouldCheckPenultimate &&
sourceCode.getNodeByRangeIndex(penultimate.range[0])!.type;
const penultimateType = shouldCheckPenultimate
? sourceCode.getNodeByRangeIndex(penultimate.range[0])!.type
: undefined;

const closingCurlyBraceMustBeSpaced =
(options.arraysInObjectsException &&
penultimateType === AST_NODE_TYPES.TSTupleType) ||
(options.objectsInObjectsException &&
penultimateType === AST_NODE_TYPES.TSTypeLiteral)
penultimateType !== undefined &&
[
AST_NODE_TYPES.TSMappedType,
AST_NODE_TYPES.TSTypeLiteral,
].includes(penultimateType))
? !options.spaced
: options.spaced;

Expand Down Expand Up @@ -246,6 +253,18 @@ export default createRule<Options, MessageIds>({
const rules = baseRule.create(context);
return {
...rules,
TSMappedType(node: TSESTree.TSMappedType): void {
const first = sourceCode.getFirstToken(node)!;
const last = sourceCode.getLastToken(node)!;
const second = sourceCode.getTokenAfter(first, {
includeComments: true,
})!;
const penultimate = sourceCode.getTokenBefore(last, {
includeComments: true,
})!;

validateBraceSpacing(node, first, second, penultimate, last);
},
TSTypeLiteral(node: TSESTree.TSTypeLiteral): void {
if (node.members.length === 0) {
return;
Expand Down
187 changes: 185 additions & 2 deletions packages/eslint-plugin/tests/rules/object-curly-spacing.test.ts
Expand Up @@ -570,6 +570,71 @@ ruleTester.run('object-curly-spacing', rule, {
code: 'const x:{[key: string]: [number]}',
},

// default - mapped types
{
code: "const x:{[k in 'union']: number}",
},
{
code: "const x:{ // line-comment\n[k in 'union']: number\n}",
},
{
code: "const x:{// line-comment\n[k in 'union']: number\n}",
},
{
code:
"const x:{/* inline-comment */[k in 'union']: number/* inline-comment */}",
},
{
code: "const x:{\n[k in 'union']: number\n}",
},
{
code: "const x:{[k in 'union']: {[k in 'union']: number}}",
},
{
code: "const x:{[k in 'union']: [number]}",
},
{
code: "const x:{[k in 'union']: value}",
},

// never - mapped types
{
code: "const x:{[k in 'union']: {[k in 'union']: number} }",
options: ['never', { objectsInObjects: true }],
},
{
code: "const x:{[k in 'union']: {[k in 'union']: number}}",
options: ['never', { objectsInObjects: false }],
},
{
code: "const x:{[k in 'union']: () => {[k in 'union']: number} }",
options: ['never', { objectsInObjects: true }],
},
{
code: "const x:{[k in 'union']: () => {[k in 'union']: number}}",
options: ['never', { objectsInObjects: false }],
},
{
code: "const x:{[k in 'union']: [ number ]}",
options: ['never', { arraysInObjects: false }],
},
{
code: "const x:{ [k in 'union']: value}",
options: ['never', { arraysInObjects: true }],
},
{
code: "const x:{[k in 'union']: value}",
options: ['never', { arraysInObjects: false }],
},
{
code: "const x:{ [k in 'union']: [number] }",
options: ['never', { arraysInObjects: true }],
},
{
code: "const x:{[k in 'union']: [number]}",
options: ['never', { arraysInObjects: false }],
},

// never - object literal types
{
code: 'const x:{f: {g: number} }',
Expand Down Expand Up @@ -612,6 +677,69 @@ ruleTester.run('object-curly-spacing', rule, {
options: ['never', { arraysInObjects: false }],
},

// always - mapped types
{
code: "const x:{ [k in 'union']: number }",
options: ['always'],
},
{
code: "const x:{ // line-comment\n[k in 'union']: number\n}",
options: ['always'],
},
{
code:
"const x:{ /* inline-comment */ [k in 'union']: number /* inline-comment */ }",
options: ['always'],
},
{
code: "const x:{\n[k in 'union']: number\n}",
options: ['always'],
},
{
code: "const x:{ [k in 'union']: [number] }",
options: ['always'],
},

// always - mapped types - objectsInObjects
{
code: "const x:{ [k in 'union']: { [k in 'union']: number } }",
options: ['always', { objectsInObjects: true }],
},
{
code: "const x:{ [k in 'union']: { [k in 'union']: number }}",
options: ['always', { objectsInObjects: false }],
},
{
code: "const x:{ [k in 'union']: () => { [k in 'union']: number } }",
options: ['always', { objectsInObjects: true }],
},
{
code: "const x:{ [k in 'union']: () => { [k in 'union']: number }}",
options: ['always', { objectsInObjects: false }],
},

// always - mapped types - arraysInObjects
{
code: "type x = { [k in 'union']: number }",
options: ['always'],
},
{
code: "const x:{ [k in 'union']: [number] }",
options: ['always', { arraysInObjects: true }],
},
{
code: "const x:{ [k in 'union']: value }",
options: ['always', { arraysInObjects: true }],
},
{
code: "const x:{[k in 'union']: value }",
options: ['always', { arraysInObjects: false }],
},
{
code: "const x:{[k in 'union']: [number]}",
options: ['always', { arraysInObjects: false }],
},

// always - object literal types
{
code: 'const x:{}',
Expand Down Expand Up @@ -642,7 +770,7 @@ ruleTester.run('object-curly-spacing', rule, {
options: ['always'],
},

// always - objectsInObjects
// always - literal types - objectsInObjects
{
code: 'const x:{ f: { g: number } }',
options: ['always', { objectsInObjects: true }],
Expand All @@ -660,7 +788,7 @@ ruleTester.run('object-curly-spacing', rule, {
options: ['always', { objectsInObjects: false }],
},

// always - arraysInObjects
// always - literal types - arraysInObjects
{
code: 'const x:{ f: [number] }',
options: ['always', { arraysInObjects: true }],
Expand Down Expand Up @@ -1912,6 +2040,7 @@ ruleTester.run('object-curly-spacing', rule, {
},

// object literal types
// never - literal types
{
code: 'type x = { f: number }',
output: 'type x = {f: number}',
Expand All @@ -1930,6 +2059,7 @@ ruleTester.run('object-curly-spacing', rule, {
output: 'type x = {f: number}',
errors: [{ messageId: 'unexpectedSpaceBefore' }],
},
// always - literal types
{
code: 'type x = {f: number}',
output: 'type x = { f: number }',
Expand All @@ -1951,5 +2081,58 @@ ruleTester.run('object-curly-spacing', rule, {
options: ['always'],
errors: [{ messageId: 'requireSpaceBefore' }],
},

// never - mapped types
{
code: "type x = { [k in 'union']: number }",
output: "type x = {[k in 'union']: number}",
errors: [
{ messageId: 'unexpectedSpaceAfter' },
{ messageId: 'unexpectedSpaceBefore' },
],
},
{
code: "type x = { [k in 'union']: number}",
output: "type x = {[k in 'union']: number}",
errors: [{ messageId: 'unexpectedSpaceAfter' }],
},
{
code: "type x = {[k in 'union']: number }",
output: "type x = {[k in 'union']: number}",
errors: [{ messageId: 'unexpectedSpaceBefore' }],
},
// always - mapped types
{
code: "type x = {[k in 'union']: number}",
output: "type x = { [k in 'union']: number }",
options: ['always'],
errors: [
{ messageId: 'requireSpaceAfter' },
{ messageId: 'requireSpaceBefore' },
],
},
{
code: "type x = {[k in 'union']: number }",
output: "type x = { [k in 'union']: number }",
options: ['always'],
errors: [{ messageId: 'requireSpaceAfter' }],
},
{
code: "type x = { [k in 'union']: number}",
output: "type x = { [k in 'union']: number }",
options: ['always'],
errors: [{ messageId: 'requireSpaceBefore' }],
},
// Mapped and literal types mix
{
code: "type x = { [k in 'union']: { [k: string]: number } }",
output: "type x = {[k in 'union']: {[k: string]: number}}",
errors: [
{ messageId: 'unexpectedSpaceAfter' },
{ messageId: 'unexpectedSpaceAfter' },
{ messageId: 'unexpectedSpaceBefore' },
{ messageId: 'unexpectedSpaceBefore' },
],
},
],
});

0 comments on commit 0557a43

Please sign in to comment.