Implement semicolon insertion

This commit is contained in:
Andrew Branch
2019-09-05 14:58:41 -07:00
parent 7cca9f47bc
commit 461e6ad8ea
6 changed files with 112 additions and 13 deletions
+13 -2
View File
@@ -2158,8 +2158,19 @@ namespace ts {
return (arg: T) => f(arg) && g(arg);
}
export function or<T>(f: (arg: T) => boolean, g: (arg: T) => boolean): (arg: T) => boolean {
return arg => f(arg) || g(arg);
export function or<T extends unknown>(...fs: ((arg: T) => boolean)[]): (arg: T) => boolean {
return arg => {
for (const f of fs) {
if (f(arg)) {
return true;
}
}
return false;
};
}
export function not<T extends unknown[]>(fn: (...args: T) => boolean): (...args: T) => boolean {
return (...args) => !fn(...args);
}
export function assertType<T>(_: T): void { } // tslint:disable-line no-empty
+3
View File
@@ -1186,6 +1186,9 @@ namespace ts.formatting {
recordReplace(previousRange.end, currentRange.pos - previousRange.end, " ");
return onLaterLine ? LineAction.LineRemoved : LineAction.None;
}
break;
case RuleAction.TrailingSemicolon:
recordReplace(previousRange.end, 0, ";");
}
return LineAction.None;
}
+7 -6
View File
@@ -12,14 +12,15 @@ namespace ts.formatting {
export const anyContext: ReadonlyArray<ContextPredicate> = emptyArray;
export const enum RuleAction {
Ignore = 1 << 0,
Space = 1 << 1,
NewLine = 1 << 2,
DeleteTrivia = 1 << 3,
DeleteToken = 1 << 4,
Ignore = 1 << 0,
Space = 1 << 1,
NewLine = 1 << 2,
DeleteTrivia = 1 << 3,
DeleteToken = 1 << 4,
TrailingSemicolon = 1 << 5,
TriviaAction = Space | NewLine | DeleteTrivia,
TokenAction = DeleteToken
TokenAction = DeleteToken | TrailingSemicolon
}
export const enum RuleFlags {
+13 -1
View File
@@ -310,7 +310,8 @@ namespace ts.formatting {
rule("SpaceBeforeTypeAnnotation", anyToken, SyntaxKind.ColonToken, [isOptionEnabled("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.Space),
rule("NoSpaceBeforeTypeAnnotation", anyToken, SyntaxKind.ColonToken, [isOptionDisabledOrUndefined("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.DeleteTrivia),
rule("NoDiscretionarySemicolon", SyntaxKind.SemicolonToken, anyToken, [isSemicolonDeletionContext], RuleAction.DeleteToken),
rule("NoOptionalSemicolon", SyntaxKind.SemicolonToken, anyToken, [isOptionDisabled("insertTrailingSemicolon"), isSemicolonDeletionContext], RuleAction.DeleteToken),
rule("OptionalSemicolon", anyToken, anyToken, [isOptionEnabled("insertTrailingSemicolon"), isSemicolonInsertionContext], RuleAction.TrailingSemicolon),
];
function isSemicolonDeletionContext(context: FormattingContext): boolean {
@@ -329,6 +330,17 @@ namespace ts.formatting {
&& context.nextTokenSpan.kind !== SyntaxKind.DotToken;
}
function isSemicolonInsertionContext(context: FormattingContext): boolean {
const contextAncestor = findAncestor(context.currentTokenParent, ancestor => {
if (ancestor.end !== context.currentTokenSpan.end) {
return "quit";
}
return syntaxMayBeASICandidate(ancestor.kind);
});
return !!contextAncestor && isASICandidate(contextAncestor);
}
// These rules are lower in priority than user-configurable. Rules earlier in this list have priority over rules later in the list.
const lowPriorityCommonRules = [
// Space after keyword but not before ; or : or ?
+2
View File
@@ -734,6 +734,7 @@ namespace ts {
readonly placeOpenBraceOnNewLineForControlBlocks?: boolean;
readonly insertSpaceBeforeTypeAnnotation?: boolean;
readonly indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean;
readonly insertTrailingSemicolon?: boolean;
}
export function getDefaultFormatCodeSettings(newLineCharacter?: string): FormatCodeSettings {
@@ -757,6 +758,7 @@ namespace ts {
insertSpaceBeforeFunctionParenthesis: false,
placeOpenBraceOnNewLineForFunctions: false,
placeOpenBraceOnNewLineForControlBlocks: false,
insertTrailingSemicolon: true,
};
}
+74 -4
View File
@@ -743,7 +743,7 @@ namespace ts {
return findPrecedingToken(position, file);
}
export function findNextToken(previousToken: Node, parent: Node, sourceFile: SourceFile): Node | undefined {
export function findNextToken(previousToken: Node, parent: Node, sourceFile: SourceFileLike): Node | undefined {
return find(parent);
function find(n: Node): Node | undefined {
@@ -1981,7 +1981,27 @@ namespace ts {
return typeIsAccessible ? res : undefined;
}
export function syntaxUsuallyHasTrailingSemicolon(kind: SyntaxKind) {
export function syntaxRequiresTrailingCommaOrSemicolonOrASI(kind: SyntaxKind) {
return kind === SyntaxKind.CallSignature
|| kind === SyntaxKind.ConstructSignature
|| kind === SyntaxKind.IndexSignature
|| kind === SyntaxKind.PropertySignature
|| kind === SyntaxKind.MethodSignature;
}
export function syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(kind: SyntaxKind) {
return kind === SyntaxKind.FunctionDeclaration
|| kind === SyntaxKind.Constructor
|| kind === SyntaxKind.MethodDeclaration
|| kind === SyntaxKind.GetAccessor
|| kind === SyntaxKind.SetAccessor;
}
export function syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(kind: SyntaxKind) {
return kind === SyntaxKind.ModuleDeclaration;
}
export function syntaxRequiresTrailingSemicolonOrASI(kind: SyntaxKind) {
return kind === SyntaxKind.VariableStatement
|| kind === SyntaxKind.ExpressionStatement
|| kind === SyntaxKind.DoStatement
@@ -1994,7 +2014,57 @@ namespace ts {
|| kind === SyntaxKind.TypeAliasDeclaration
|| kind === SyntaxKind.ImportDeclaration
|| kind === SyntaxKind.ImportEqualsDeclaration
|| kind === SyntaxKind.ExportDeclaration;
|| kind === SyntaxKind.ExportDeclaration
|| kind === SyntaxKind.NamespaceExportDeclaration
|| kind === SyntaxKind.ExportAssignment;
}
export const syntaxMayBeASICandidate = or(
syntaxRequiresTrailingCommaOrSemicolonOrASI,
syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI,
syntaxRequiresTrailingModuleBlockOrSemicolonOrASI,
syntaxRequiresTrailingSemicolonOrASI);
export function isASICandidate(node: Node): boolean {
const lastToken = node.getLastToken();
if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) {
return false;
}
if (syntaxRequiresTrailingCommaOrSemicolonOrASI(node.kind)) {
if (lastToken && lastToken.kind === SyntaxKind.CommaToken) {
return false;
}
}
if (syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(node.kind)) {
const children = node.parent.getChildren();
const nextChild = children[children.indexOf(node) + 1];
if (nextChild && isModuleBlock(nextChild)) {
return false;
}
}
else if (syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(node.kind)) {
const children = node.parent.getChildren();
const nextChild = children[children.indexOf(node) + 1];
if (nextChild && isFunctionBlock(nextChild)) {
return false;
}
}
else if (!syntaxRequiresTrailingSemicolonOrASI(node.kind)) {
return false;
}
const sourceFile = node.getSourceFile();
const nextToken = findNextToken(node, sourceFile, sourceFile);
if (!nextToken) {
return false;
}
if (nextToken.kind === SyntaxKind.CloseBraceToken) {
return true;
}
return !positionsAreOnSameLine(node.getEnd(), nextToken.getStart(sourceFile), sourceFile);
}
export function probablyUsesSemicolons(sourceFile: SourceFile): boolean {
@@ -2002,7 +2072,7 @@ namespace ts {
let withoutSemicolon = 0;
const nStatementsToObserve = 5;
forEachChild(sourceFile, function visit(node): boolean | undefined {
if (syntaxUsuallyHasTrailingSemicolon(node.kind)) {
if (syntaxRequiresTrailingSemicolonOrASI(node.kind)) {
const lastToken = node.getLastToken(sourceFile);
if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) {
withSemicolon++;