Skip to content
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

feat(eslint-plugin): [object-curly-spacing] support MappedType #3176

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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' },
],
},
],
});