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

Print leading comment for readonly modifier of TS Mapped Types #11190

Closed
wants to merge 8 commits into from
Closed
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
23 changes: 23 additions & 0 deletions changelog_unreleased/typescript/11190.md
@@ -0,0 +1,23 @@
#### Print leading comment for readonly modifier of TS Mapped Types (#11190 by @sosukesuzuki)

<!-- prettier-ignore -->
```ts
// Input
type Type = {
// comment
readonly [key in Foo];
};

// Prettier stable
type Type = {
readonly // comment
[key in Foo];
};

// Prettier main
type Type = {
// comment
readonly [key in Foo];
};

```
73 changes: 73 additions & 0 deletions src/language-js/comments.js
Expand Up @@ -25,6 +25,7 @@ const {
isCallExpression,
isMemberExpression,
isObjectProperty,
danglingCommentMarkerForReadonlyMappedType,
} = require("./utils");
const { locStart, locEnd } = require("./loc");

Expand Down Expand Up @@ -65,6 +66,7 @@ function handleOwnLineComment(context) {
handleAssignmentPatternComments,
handleMethodNameComments,
handleLabeledStatementComments,
handleOwnLineMappedTypesComments,
].some((fn) => fn(context));
}

Expand Down Expand Up @@ -331,6 +333,26 @@ function handleMemberExpressionComments({
return false;
}

/**
* @param {CommentContext} context
* @return {boolean}
*/
function handleOwnLineMappedTypesComments({ enclosingNode, comment, text }) {
if (
enclosingNode &&
enclosingNode.type === "TSMappedType" &&
isCommentBeforeMappedTypeReadonly(enclosingNode, text, comment)
) {
addDanglingComment(
enclosingNode,
comment,
danglingCommentMarkerForReadonlyMappedType
);
return true;
}
return false;
}

function handleConditionalExpressionComments({
comment,
precedingNode,
Expand Down Expand Up @@ -970,6 +992,57 @@ function willPrintOwnComments(path /*, options */) {
);
}

/**
* Checks if a comment is before `readonly` for Mapped Types.
* type Foo = {
* // comment
* readonly [key in Foo]: Bar;
* };
* @param {any} enclosingNode
* @param {CommentContext["text"]} text
* @param {CommentContext["comment"]} comment
* @returns {boolean}
*/
function isCommentBeforeMappedTypeReadonly(enclosingNode, text, comment) {
if (!enclosingNode.readonly) {
return false;
}
const nextIndexOfComment = getNextNonSpaceNonCommentCharacterIndex(
text,
comment,
locEnd
);
if (!nextIndexOfComment) {
return false;
}
const isReadonlyString =
enclosingNode.readonly === "+" || enclosingNode.readonly === "-";
const readonlyStartIndex = isReadonlyString
? getNextNonSpaceNonCommentCharacterIndexWithStartIndex(
text,
nextIndexOfComment + 1
)
: nextIndexOfComment;
if (!readonlyStartIndex) {
return false;
}
const READONLY_KEYWORD = "readonly";
const readonlyEndIndex = readonlyStartIndex + READONLY_KEYWORD.length;
const maybeReadonlyString = text.slice(readonlyStartIndex, readonlyEndIndex);
if (maybeReadonlyString !== READONLY_KEYWORD) {
return false;
}
const afterReadonlyIndex =
getNextNonSpaceNonCommentCharacterIndexWithStartIndex(
text,
readonlyEndIndex
);
if (!afterReadonlyIndex) {
return false;
}
return text[afterReadonlyIndex] === "[";
}

module.exports = {
handleOwnLineComment,
handleEndOfLineComment,
Expand Down
16 changes: 15 additions & 1 deletion src/language-js/print/typescript.js
Expand Up @@ -20,6 +20,7 @@ const {
shouldPrintComma,
isCallExpression,
isMemberExpression,
danglingCommentMarkerForReadonlyMappedType,
} = require("../utils");
const { locStart, locEnd } = require("../loc");

Expand Down Expand Up @@ -297,13 +298,21 @@ function printTypescript(path, options, print) {
locStart(node),
locEnd(node)
);
const printedLeadingCommentsForReadonly = printDanglingComments(
path,
options,
/* sameIndent */ true,
({ marker }) => marker === danglingCommentMarkerForReadonlyMappedType
);
return group(
[
"{",
indent([
options.bracketSpacing ? line : softline,
node.readonly
? [
printedLeadingCommentsForReadonly,
printedLeadingCommentsForReadonly && hardline,
getTypeScriptMappedTypeModifier(node.readonly, "readonly"),
" ",
]
Expand All @@ -317,7 +326,12 @@ function printTypescript(path, options, print) {
print("typeAnnotation"),
ifBreak(semi),
]),
printDanglingComments(path, options, /* sameIndent */ true),
printDanglingComments(
path,
options,
/* sameIndent */ true,
({ marker }) => !marker
),
options.bracketSpacing ? line : softline,
"}",
],
Expand Down
4 changes: 4 additions & 0 deletions src/language-js/utils.js
Expand Up @@ -1305,6 +1305,9 @@ function isObjectProperty(node) {
);
}

const danglingCommentMarkerForReadonlyMappedType =
"danglingCommentMarkerForReadonlyMappedType";

module.exports = {
getFunctionParameters,
iterateFunctionParametersPath,
Expand Down Expand Up @@ -1368,4 +1371,5 @@ module.exports = {
hasComment,
getComments,
CommentCheckFlags,
danglingCommentMarkerForReadonlyMappedType,
};
@@ -1,5 +1,97 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`issue-11098.ts format 1`] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
| printWidth
=====================================input======================================
type Type = {
// comment
readonly [T in number];
};

type Type = {
// comment1
// comment2
readonly [T in number];
};

type Type = {
// comment
+readonly [T in number];
};

type Type = {
// comment
-readonly [T in number];
};

type Type = {
// comment
+ readonly [T in number];
};

type Type = {
// comment
+readonly [T in number];
};

type Type = {
// comment
readonly [T in number];
};

type Type = {
// comment
[T in number];
};

=====================================output=====================================
type Type = {
// comment
readonly [T in number];
};

type Type = {
// comment1
// comment2
readonly [T in number];
};

type Type = {
// comment
+readonly [T in number];
};

type Type = {
// comment
-readonly [T in number];
};

type Type = {
// comment
+readonly [T in number];
};

type Type = {
// comment
+readonly [T in number];
};

type Type = {
// comment
readonly [T in number];
};

type Type = {
// comment
[T in number];
};

================================================================================
`;

exports[`mapped-type.ts format 1`] = `
====================================options=====================================
parsers: ["typescript"]
Expand Down
40 changes: 40 additions & 0 deletions tests/format/typescript/mapped-type/issue-11098.ts
@@ -0,0 +1,40 @@
type Type = {
// comment
readonly [T in number];
};

type Type = {
// comment1
// comment2
readonly [T in number];
};

type Type = {
// comment
+readonly [T in number];
};

type Type = {
// comment
-readonly [T in number];
};

type Type = {
// comment
+ readonly [T in number];
};

type Type = {
// comment
+readonly [T in number];
};

type Type = {
// comment
readonly [T in number];
};

type Type = {
// comment
[T in number];
};