Skip to content

Commit 5625cb0

Browse files
committed
Omit last element semicolon from single-line object-like types
1 parent 4675779 commit 5625cb0

76 files changed

Lines changed: 144 additions & 99 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/server/protocol.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,6 +2960,8 @@ namespace ts.server.protocol {
29602960
Ignore = "ignore",
29612961
Insert = "insert",
29622962
Remove = "remove",
2963+
/*@internal*/
2964+
RemoveUnconventional = "remove-unconventional"
29632965
}
29642966

29652967
export interface EditorSettings {

src/services/formatting/rules.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ namespace ts.formatting {
314314
rule("SpaceBeforeTypeAnnotation", anyToken, SyntaxKind.ColonToken, [isOptionEnabled("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.Space),
315315
rule("NoSpaceBeforeTypeAnnotation", anyToken, SyntaxKind.ColonToken, [isOptionDisabledOrUndefined("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.DeleteTrivia),
316316
rule("NoOptionalSemicolon", SyntaxKind.SemicolonToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Remove), isSemicolonDeletionContext], RuleAction.DeleteToken),
317+
rule("NoUnconventionalSemicolon", SyntaxKind.SemicolonToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.RemoveUnconventional), isUnconventionalSemicolonDeletionContext], RuleAction.DeleteToken),
317318
rule("OptionalSemicolon", anyToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Insert), isSemicolonInsertionContext], RuleAction.TrailingSemicolon),
318319
];
319320

@@ -790,6 +791,14 @@ namespace ts.formatting {
790791
return context.contextNode.kind === SyntaxKind.NonNullExpression;
791792
}
792793

794+
function isUnconventionalSemicolonDeletionContext(context: FormattingContext): boolean {
795+
return nodeAllowsUnconventionalTrailingSemicolon(
796+
context.currentTokenParent,
797+
context.contextNode,
798+
context.nextTokenSpan.kind,
799+
context.sourceFile);
800+
}
801+
793802
function isSemicolonDeletionContext(context: FormattingContext): boolean {
794803
let nextTokenKind = context.nextTokenSpan.kind;
795804
let nextTokenStart = context.nextTokenSpan.pos;
@@ -859,7 +868,7 @@ namespace ts.formatting {
859868
if (ancestor.end !== context.currentTokenSpan.end) {
860869
return "quit";
861870
}
862-
return syntaxMayBeASICandidate(ancestor.kind);
871+
return nodeMayBeASICandidate(ancestor);
863872
});
864873

865874
return !!contextAncestor && isASICandidate(contextAncestor, context.sourceFile);

src/services/textChanges.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -836,12 +836,12 @@ namespace ts.textChanges {
836836
return {
837837
...context.options,
838838
// If the user has no semicolon preference defined and the file doesn’t use semicolons,
839-
// make the formatter remove them. Otherwise, ignore semicolons in the formatter because
839+
// make the formatter remove them. Otherwise, only remove unconventional semicolons because
840840
// the writer will insert them by default.
841841
semicolons: context.options.semicolons === SemicolonPreference.Remove ||
842842
(!context.options.semicolons || context.options.semicolons === SemicolonPreference.Ignore) && !probablyUsesSemicolons(sourceFile)
843843
? SemicolonPreference.Remove
844-
: SemicolonPreference.Ignore,
844+
: SemicolonPreference.RemoveUnconventional,
845845
};
846846
}
847847

src/services/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,8 @@ namespace ts {
684684
Ignore = "ignore",
685685
Insert = "insert",
686686
Remove = "remove",
687+
/*@internal*/
688+
RemoveUnconventional = "remove-unconventional"
687689
}
688690

689691
/* @deprecated - consider using EditorSettings instead */

src/services/utilities.ts

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2027,13 +2027,37 @@ namespace ts {
20272027
|| kind === SyntaxKind.ExportAssignment;
20282028
}
20292029

2030-
export const syntaxMayBeASICandidate = or(
2031-
syntaxRequiresTrailingCommaOrSemicolonOrASI,
2032-
syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI,
2033-
syntaxRequiresTrailingModuleBlockOrSemicolonOrASI,
2034-
syntaxRequiresTrailingSemicolonOrASI);
2030+
function isMappedTypeNodeType(node: Node) {
2031+
return node.parent && isMappedTypeNode(node.parent) && node.parent.type === node;
2032+
}
2033+
2034+
export function nodeMayBeASICandidate(node: Node) {
2035+
if (syntaxRequiresTrailingCommaOrSemicolonOrASI(node.kind) ||
2036+
syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(node.kind) ||
2037+
syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(node.kind) ||
2038+
syntaxRequiresTrailingSemicolonOrASI(node.kind)) {
2039+
return true;
2040+
}
2041+
return isMappedTypeNodeType(node);
2042+
}
20352043

2036-
export function isASICandidate(node: Node, sourceFile: SourceFileLike): boolean {
2044+
export function nodeAllowsUnconventionalTrailingSemicolon(node: Node, contextNode: Node, nextTokenKind: SyntaxKind | undefined, sourceFile: SourceFileLike) {
2045+
if (isMappedTypeNodeType(node)) {
2046+
return rangeIsOnSingleLine(contextNode, sourceFile as SourceFile);
2047+
}
2048+
if (syntaxRequiresTrailingCommaOrSemicolonOrASI(node.kind)) {
2049+
return nextTokenKind === SyntaxKind.CloseBraceToken && rangeIsOnSingleLine(contextNode, sourceFile as SourceFile);
2050+
}
2051+
return false;
2052+
}
2053+
2054+
/**
2055+
* @param strict Return true for positions that allow semicolons but conventionally
2056+
* drop them, even in code that largely contains semicolons. Examples include the last
2057+
* declaration inside the curly braces of single-line object type literals and mapped types,
2058+
* e.g. `type X = { x: string; }` and `type X<T> = { [K in keyof T]: T[K]; }`.
2059+
*/
2060+
export function isASICandidate(node: Node, sourceFile: SourceFileLike, strict?: boolean): boolean {
20372061
const lastToken = node.getLastToken(sourceFile);
20382062
if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) {
20392063
return false;
@@ -2060,20 +2084,28 @@ namespace ts {
20602084
return false;
20612085
}
20622086

2087+
let nextToken = getNextToken();
2088+
const contextNode = findAncestor(node, or(isObjectTypeDeclaration, isMappedTypeNode));
2089+
if (contextNode && nodeAllowsUnconventionalTrailingSemicolon(node, contextNode, nextToken && nextToken.kind, sourceFile)) {
2090+
return !!strict;
2091+
}
2092+
20632093
// See comment in parser’s `parseDoStatement`
20642094
if (node.kind === SyntaxKind.DoStatement) {
20652095
return true;
20662096
}
20672097

2068-
const topNode = findAncestor(node, ancestor => !ancestor.parent)!;
2069-
const nextToken = findNextToken(node, topNode, sourceFile);
20702098
if (!nextToken || nextToken.kind === SyntaxKind.CloseBraceToken) {
20712099
return true;
20722100
}
20732101

2074-
const startLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line;
2075-
const endLine = sourceFile.getLineAndCharacterOfPosition(nextToken.getStart(sourceFile)).line;
2076-
return startLine !== endLine;
2102+
return !positionsAreOnSameLine(node.getEnd(),
2103+
nextToken.getStart(sourceFile),
2104+
sourceFile as SourceFile);
2105+
2106+
function getNextToken(): Node | undefined {
2107+
return nextToken || (nextToken = findNextToken(node, findAncestor(node, ancestor => !ancestor.parent)!, sourceFile));
2108+
}
20772109
}
20782110

20792111
export function probablyUsesSemicolons(sourceFile: SourceFile): boolean {

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5297,7 +5297,7 @@ declare namespace ts {
52975297
enum SemicolonPreference {
52985298
Ignore = "ignore",
52995299
Insert = "insert",
5300-
Remove = "remove"
5300+
Remove = "remove",
53015301
}
53025302
interface EditorOptions {
53035303
BaseIndentSize?: number;
@@ -8202,7 +8202,7 @@ declare namespace ts.server.protocol {
82028202
enum SemicolonPreference {
82038203
Ignore = "ignore",
82048204
Insert = "insert",
8205-
Remove = "remove"
8205+
Remove = "remove",
82068206
}
82078207
interface EditorSettings {
82088208
baseIndentSize?: number;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5297,7 +5297,7 @@ declare namespace ts {
52975297
enum SemicolonPreference {
52985298
Ignore = "ignore",
52995299
Insert = "insert",
5300-
Remove = "remove"
5300+
Remove = "remove",
53015301
}
53025302
interface EditorOptions {
53035303
BaseIndentSize?: number;

tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_catchBlockUniqueParamsBindingPattern.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ function /*[#|*/f/*|]*/() {
77
// ==ASYNC FUNCTION::Convert to async function==
88

99
async function f() {
10-
let result: { x: number; } | { x: string; };
10+
let result: { x: number } | { x: string };
1111
try {
1212
await Promise.resolve();
1313
result = ({ x: 3 });

tests/baselines/reference/extractFunction/extractFunction_VariableDeclaration_Multiple1.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ x; y;
55

66
// ==SCOPE::Extract to function in global scope==
77

8-
const { x, y }: { x: number; y: string; } = /*RENAME*/newFunction();
8+
const { x, y }: { x: number; y: string } = /*RENAME*/newFunction();
99
x; y;
1010

1111
function newFunction() {

tests/baselines/reference/extractFunction/extractFunction_VariableDeclaration_Multiple3.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ x; y; z;
66

77
// ==SCOPE::Extract to function in global scope==
88

9-
var { x, y, z }: { x: number; y: string; z: number; } = /*RENAME*/newFunction();
9+
var { x, y, z }: { x: number; y: string; z: number } = /*RENAME*/newFunction();
1010
x; y; z;
1111

1212
function newFunction() {

0 commit comments

Comments
 (0)