diff --git a/changelog_unreleased/typescript/17498.md b/changelog_unreleased/typescript/17498.md new file mode 100644 index 000000000..77db2f5dd --- /dev/null +++ b/changelog_unreleased/typescript/17498.md @@ -0,0 +1,16 @@ +#### Improve new line detect in mapped type (#17498 by @fisker) + + +```jsx +// Input +type A = { readonly + [A in B]: T} + +// Prettier stable +type A = { + readonly [A in B]: T; +}; + +// Prettier main +type A = { readonly [A in B]: T }; +``` diff --git a/src/language-js/clean.js b/src/language-js/clean.js index aa209a344..5dd02f7ed 100644 --- a/src/language-js/clean.js +++ b/src/language-js/clean.js @@ -190,12 +190,6 @@ function clean(original, cloned) { cloned.expression.type = "ChainExpression"; } - // `@typescript-eslint/typescript-estree` v8 - if (original.type === "TSMappedType") { - delete cloned.key; - delete cloned.constraint; - } - // `@typescript-eslint/typescript-estree` v8 if (original.type === "TSEnumDeclaration") { delete cloned.body; diff --git a/src/language-js/comments/handle-comments.js b/src/language-js/comments/handle-comments.js index b9a89e671..84ca9f1b9 100644 --- a/src/language-js/comments/handle-comments.js +++ b/src/language-js/comments/handle-comments.js @@ -69,6 +69,7 @@ function handleOwnLineComment(context) { handleBreakAndContinueStatementComments, handleNestedConditionalExpressionComments, handleCommentsInDestructuringPattern, + handleTSMappedTypeComments, ].some((fn) => fn(context)); } @@ -95,6 +96,7 @@ function handleEndOfLineComment(context) { handleSwitchDefaultCaseComments, handleLastUnionElementInExpression, handleLastBinaryOperatorOperand, + handleTSMappedTypeComments, ].some((fn) => fn(context)); } @@ -113,7 +115,6 @@ function handleRemainingComment(context) { handleOnlyComments, handleCommentAfterArrowParams, handleFunctionNameComments, - handleTSMappedTypeComments, handleBreakAndContinueStatementComments, handleTSFunctionTrailingComments, ].some((fn) => fn(context)); @@ -906,8 +907,7 @@ function handleIgnoreComments({ comment, enclosingNode, followingNode }) { if ( isPrettierIgnoreComment(comment) && enclosingNode?.type === "TSMappedType" && - followingNode?.type === "TSTypeParameter" && - followingNode.constraint + followingNode === enclosingNode.key ) { enclosingNode.prettierIgnore = true; comment.unignore = true; @@ -915,27 +915,15 @@ function handleIgnoreComments({ comment, enclosingNode, followingNode }) { } } -function handleTSMappedTypeComments({ - comment, - precedingNode, - enclosingNode, - followingNode, -}) { +function handleTSMappedTypeComments({ comment, precedingNode, enclosingNode }) { if (enclosingNode?.type !== "TSMappedType") { - return false; + return; } - if (followingNode?.type === "TSTypeParameter" && followingNode.name) { - addLeadingComment(followingNode.name, comment); + if (!precedingNode) { + addDanglingComment(enclosingNode, comment); return true; } - - if (precedingNode?.type === "TSTypeParameter" && precedingNode.constraint) { - addTrailingComment(precedingNode.constraint, comment); - return true; - } - - return false; } function handleSwitchDefaultCaseComments({ diff --git a/src/language-js/parse/postprocess/index.js b/src/language-js/parse/postprocess/index.js index c9b464a3e..5d28da2f1 100644 --- a/src/language-js/parse/postprocess/index.js +++ b/src/language-js/parse/postprocess/index.js @@ -145,14 +145,7 @@ function postprocess(ast, options) { case "TSTypeParameter": // babel-ts - if (typeof node.name === "string") { - const start = locStart(node); - node.name = { - type: "Identifier", - name: node.name, - range: [start, start + node.name.length], - }; - } + fixBabelTSTypeParameter(node); break; // For hack-style pipeline @@ -167,6 +160,19 @@ function postprocess(ast, options) { return node.types[0]; } break; + + // Remove this when update `@babel/parser` to v8 + // https://github.com/typescript-eslint/typescript-eslint/pull/7065 + case "TSMappedType": + if (!node.constraint && !node.key) { + const { name: key, constraint } = fixBabelTSTypeParameter( + node.typeParameter, + ); + node.constraint = constraint; + node.key = key; + delete node.typeParameter; + } + break; } /* c8 ignore next 3 */ @@ -183,6 +189,19 @@ function postprocess(ast, options) { return ast; } +function fixBabelTSTypeParameter(node) { + if (node.type === "TSTypeParameter" && typeof node.name === "string") { + const start = locStart(node); + node.name = { + type: "Identifier", + name: node.name, + range: [start, start + node.name.length], + }; + } + + return node; +} + function isUnbalancedLogicalTree(node) { return ( node.type === "LogicalExpression" && diff --git a/src/language-js/print/mapped-type.js b/src/language-js/print/mapped-type.js index d37f43d07..1cfbe4b48 100644 --- a/src/language-js/print/mapped-type.js +++ b/src/language-js/print/mapped-type.js @@ -1,5 +1,6 @@ import { group, + hardline, ifBreak, indent, line, @@ -8,6 +9,8 @@ import { import { printDanglingComments } from "../../main/comments/print.js"; import hasNewlineInRange from "../../utils/has-newline-in-range.js"; import { locStart } from "../loc.js"; +import getTextWithoutComments from "../utils/get-text-without-comments.js"; +import { CommentCheckFlags, hasComment } from "../utils/index.js"; /** * @param {string | null} optional @@ -52,25 +55,44 @@ function printTypeScriptMappedTypeModifier(tokenNode, keyword) { return keyword; } -function printTypescriptMappedType(path, options, print) { +function printTypeScriptMappedType(path, options, print) { const { node } = path; // Break after `{` like `printObject` - const shouldBreak = - options.objectWrap === "preserve" && - hasNewlineInRange( - options.originalText, - locStart(node), - // Ideally, this should be the next token after `{`, but there is no node starts with it. - locStart(node.typeParameter), + let shouldBreak = false; + if (options.objectWrap === "preserve") { + const start = locStart(node); + const textAfter = getTextWithoutComments( + options, + start + 1, + locStart(node.key), ); + const nextTokenIndex = start + 1 + textAfter.search(/\S/u); + if (hasNewlineInRange(options.originalText, start, nextTokenIndex)) { + shouldBreak = true; + } + } return group( [ "{", indent([ options.bracketSpacing ? line : softline, + hasComment(node, CommentCheckFlags.Dangling) + ? group([printDanglingComments(path, options), hardline]) + : "", group([ - print("typeParameter"), + node.readonly + ? [ + printTypeScriptMappedTypeModifier(node.readonly, "readonly"), + " ", + ] + : "", + "[", + print("key"), + " in ", + print("constraint"), + node.nameType ? [" as ", print("nameType")] : "", + "]", node.optional ? printTypeScriptMappedTypeModifier(node.optional, "?") : "", @@ -79,7 +101,6 @@ function printTypescriptMappedType(path, options, print) { ]), options.semi ? ifBreak(";") : "", ]), - printDanglingComments(path, options), options.bracketSpacing ? line : softline, "}", ], @@ -87,8 +108,4 @@ function printTypescriptMappedType(path, options, print) { ); } -export { - printFlowMappedTypeProperty, - printTypescriptMappedType, - printTypeScriptMappedTypeModifier, -}; +export { printFlowMappedTypeProperty, printTypeScriptMappedType }; diff --git a/src/language-js/print/type-parameters.js b/src/language-js/print/type-parameters.js index 5368c07ed..083851c92 100644 --- a/src/language-js/print/type-parameters.js +++ b/src/language-js/print/type-parameters.js @@ -20,7 +20,6 @@ import { shouldPrintComma, } from "../utils/index.js"; import { isArrowFunctionVariableDeclarator } from "./assignment.js"; -import { printTypeScriptMappedTypeModifier } from "./mapped-type.js"; import { printTypeAnnotationProperty, shouldHugType, @@ -127,7 +126,7 @@ function printDanglingCommentsForInline(path, options) { } function printTypeParameter(path, options, print) { - const { node, parent } = path; + const { node } = path; /** * @type {Doc[]} @@ -136,27 +135,6 @@ function printTypeParameter(path, options, print) { const name = node.type === "TSTypeParameter" ? print("name") : node.name; - if (parent.type === "TSMappedType") { - if (parent.readonly) { - parts.push( - printTypeScriptMappedTypeModifier(parent.readonly, "readonly"), - " ", - ); - } - parts.push("[", name); - if (node.constraint) { - parts.push(" in ", print("constraint")); - } - if (parent.nameType) { - parts.push( - " as ", - path.callParent(() => print("nameType")), - ); - } - parts.push("]"); - return parts; - } - if (node.variance) { parts.push(print("variance")); } diff --git a/src/language-js/print/typescript.js b/src/language-js/print/typescript.js index 1d81ab98f..f849ff49c 100644 --- a/src/language-js/print/typescript.js +++ b/src/language-js/print/typescript.js @@ -25,7 +25,7 @@ import { shouldGroupFunctionParameters, } from "./function-parameters.js"; import { printInterface } from "./interface.js"; -import { printTypescriptMappedType } from "./mapped-type.js"; +import { printTypeScriptMappedType } from "./mapped-type.js"; import { printDeclareToken, printOptionalToken, @@ -230,7 +230,7 @@ function printTypescript(path, options, print) { return [node.operator, " ", print("typeAnnotation")]; case "TSMappedType": - return printTypescriptMappedType(path, options, print); + return printTypeScriptMappedType(path, options, print); case "TSMethodSignature": { const kind = node.kind && node.kind !== "method" ? `${node.kind} ` : ""; diff --git a/src/language-js/traverse/visitor-keys.evaluate.js b/src/language-js/traverse/visitor-keys.evaluate.js index 9e6ce9742..0c6907088 100644 --- a/src/language-js/traverse/visitor-keys.evaluate.js +++ b/src/language-js/traverse/visitor-keys.evaluate.js @@ -62,11 +62,8 @@ const excludeKeys = { ExportNamedDeclaration: ["assertions"], ImportDeclaration: ["assertions"], ImportExpression: ["attributes"], + TSMappedType: ["typeParameter"], - // `key` and `constraint` added in `@typescript-eslint/typescript-estree` v8 - // https://github.com/typescript-eslint/typescript-eslint/pull/7065 - // TODO: Use the new AST properties instead - TSMappedType: ["key", "constraint"], // `body` added in `@typescript-eslint/typescript-estree` v8 // https://github.com/typescript-eslint/typescript-eslint/pull/8920 // TODO: Use the new AST properties instead diff --git a/tests/format/html/multiparser/ts/__snapshots__/format.test.js.snap b/tests/format/html/multiparser/ts/__snapshots__/format.test.js.snap index dc8edd9c7..9ccd1d622 100644 --- a/tests/format/html/multiparser/ts/__snapshots__/format.test.js.snap +++ b/tests/format/html/multiparser/ts/__snapshots__/format.test.js.snap @@ -32,9 +32,7 @@ printWidth: 80