Detect semicolons in file before writing quick fixes
This commit is contained in:
parent
7815778fb0
commit
b37875ff7a
2 changed files with 177 additions and 104 deletions
|
@ -834,9 +834,10 @@ namespace ts.textChanges {
|
||||||
|
|
||||||
/** Note: output node may be mutated input node. */
|
/** Note: output node may be mutated input node. */
|
||||||
export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } {
|
export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } {
|
||||||
const writer = new Writer(newLineCharacter);
|
const omitTrailingSemicolon = !!sourceFile && !probablyUsesSemicolons(sourceFile);
|
||||||
|
const writer = createWriter(newLineCharacter, omitTrailingSemicolon);
|
||||||
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
|
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
|
||||||
createPrinter({ newLine, neverAsciiEscape: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
|
createPrinter({ newLine, neverAsciiEscape: true, omitTrailingSemicolon }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
|
||||||
return { text: writer.getText(), node: assignPositionsToNode(node) };
|
return { text: writer.getText(), node: assignPositionsToNode(node) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -874,143 +875,168 @@ namespace ts.textChanges {
|
||||||
return nodeArray;
|
return nodeArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Writer implements EmitTextWriter, PrintHandlers {
|
interface TextChangesWriter extends EmitTextWriter, PrintHandlers {}
|
||||||
private lastNonTriviaPosition = 0;
|
|
||||||
private readonly writer: EmitTextWriter;
|
|
||||||
|
|
||||||
public readonly onEmitNode: PrintHandlers["onEmitNode"];
|
function createWriter(newLine: string, omitTrailingSemicolon?: boolean): TextChangesWriter {
|
||||||
public readonly onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"];
|
let lastNonTriviaPosition = 0;
|
||||||
public readonly onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"];
|
|
||||||
public readonly onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"];
|
|
||||||
public readonly onAfterEmitToken: PrintHandlers["onAfterEmitToken"];
|
|
||||||
|
|
||||||
constructor(newLine: string) {
|
|
||||||
this.writer = createTextWriter(newLine);
|
|
||||||
this.onEmitNode = (hint, node, printCallback) => {
|
|
||||||
if (node) {
|
|
||||||
setPos(node, this.lastNonTriviaPosition);
|
|
||||||
}
|
|
||||||
printCallback(hint, node);
|
|
||||||
if (node) {
|
|
||||||
setEnd(node, this.lastNonTriviaPosition);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.onBeforeEmitNodeArray = nodes => {
|
|
||||||
if (nodes) {
|
|
||||||
setPos(nodes, this.lastNonTriviaPosition);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.onAfterEmitNodeArray = nodes => {
|
|
||||||
if (nodes) {
|
|
||||||
setEnd(nodes, this.lastNonTriviaPosition);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.onBeforeEmitToken = node => {
|
|
||||||
if (node) {
|
|
||||||
setPos(node, this.lastNonTriviaPosition);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.onAfterEmitToken = node => {
|
|
||||||
if (node) {
|
|
||||||
setEnd(node, this.lastNonTriviaPosition);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private setLastNonTriviaPosition(s: string, force: boolean) {
|
const writer = omitTrailingSemicolon ? getTrailingSemicolonOmittingWriter(createTextWriter(newLine)) : createTextWriter(newLine);
|
||||||
|
const onEmitNode: PrintHandlers["onEmitNode"] = (hint, node, printCallback) => {
|
||||||
|
if (node) {
|
||||||
|
setPos(node, lastNonTriviaPosition);
|
||||||
|
}
|
||||||
|
printCallback(hint, node);
|
||||||
|
if (node) {
|
||||||
|
setEnd(node, lastNonTriviaPosition);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"] = nodes => {
|
||||||
|
if (nodes) {
|
||||||
|
setPos(nodes, lastNonTriviaPosition);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"] = nodes => {
|
||||||
|
if (nodes) {
|
||||||
|
setEnd(nodes, lastNonTriviaPosition);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"] = node => {
|
||||||
|
if (node) {
|
||||||
|
setPos(node, lastNonTriviaPosition);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onAfterEmitToken: PrintHandlers["onAfterEmitToken"] = node => {
|
||||||
|
if (node) {
|
||||||
|
setEnd(node, lastNonTriviaPosition);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function setLastNonTriviaPosition(s: string, force: boolean) {
|
||||||
if (force || !isTrivia(s)) {
|
if (force || !isTrivia(s)) {
|
||||||
this.lastNonTriviaPosition = this.writer.getTextPos();
|
lastNonTriviaPosition = writer.getTextPos();
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while (isWhiteSpaceLike(s.charCodeAt(s.length - i - 1))) {
|
while (isWhiteSpaceLike(s.charCodeAt(s.length - i - 1))) {
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
// trim trailing whitespaces
|
// trim trailing whitespaces
|
||||||
this.lastNonTriviaPosition -= i;
|
lastNonTriviaPosition -= i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
write(s: string): void {
|
function write(s: string): void {
|
||||||
this.writer.write(s);
|
writer.write(s);
|
||||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
setLastNonTriviaPosition(s, /*force*/ false);
|
||||||
}
|
}
|
||||||
writeComment(s: string): void {
|
function writeComment(s: string): void {
|
||||||
this.writer.writeComment(s);
|
writer.writeComment(s);
|
||||||
}
|
}
|
||||||
writeKeyword(s: string): void {
|
function writeKeyword(s: string): void {
|
||||||
this.writer.writeKeyword(s);
|
writer.writeKeyword(s);
|
||||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
setLastNonTriviaPosition(s, /*force*/ false);
|
||||||
}
|
}
|
||||||
writeOperator(s: string): void {
|
function writeOperator(s: string): void {
|
||||||
this.writer.writeOperator(s);
|
writer.writeOperator(s);
|
||||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
setLastNonTriviaPosition(s, /*force*/ false);
|
||||||
}
|
}
|
||||||
writePunctuation(s: string): void {
|
function writePunctuation(s: string): void {
|
||||||
this.writer.writePunctuation(s);
|
writer.writePunctuation(s);
|
||||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
setLastNonTriviaPosition(s, /*force*/ false);
|
||||||
}
|
}
|
||||||
writeTrailingSemicolon(s: string): void {
|
function writeTrailingSemicolon(s: string): void {
|
||||||
this.writer.writeTrailingSemicolon(s);
|
writer.writeTrailingSemicolon(s);
|
||||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
setLastNonTriviaPosition(s, /*force*/ false);
|
||||||
}
|
}
|
||||||
writeParameter(s: string): void {
|
function writeParameter(s: string): void {
|
||||||
this.writer.writeParameter(s);
|
writer.writeParameter(s);
|
||||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
setLastNonTriviaPosition(s, /*force*/ false);
|
||||||
}
|
}
|
||||||
writeProperty(s: string): void {
|
function writeProperty(s: string): void {
|
||||||
this.writer.writeProperty(s);
|
writer.writeProperty(s);
|
||||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
setLastNonTriviaPosition(s, /*force*/ false);
|
||||||
}
|
}
|
||||||
writeSpace(s: string): void {
|
function writeSpace(s: string): void {
|
||||||
this.writer.writeSpace(s);
|
writer.writeSpace(s);
|
||||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
setLastNonTriviaPosition(s, /*force*/ false);
|
||||||
}
|
}
|
||||||
writeStringLiteral(s: string): void {
|
function writeStringLiteral(s: string): void {
|
||||||
this.writer.writeStringLiteral(s);
|
writer.writeStringLiteral(s);
|
||||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
setLastNonTriviaPosition(s, /*force*/ false);
|
||||||
}
|
}
|
||||||
writeSymbol(s: string, sym: Symbol): void {
|
function writeSymbol(s: string, sym: Symbol): void {
|
||||||
this.writer.writeSymbol(s, sym);
|
writer.writeSymbol(s, sym);
|
||||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
setLastNonTriviaPosition(s, /*force*/ false);
|
||||||
}
|
}
|
||||||
writeLine(): void {
|
function writeLine(): void {
|
||||||
this.writer.writeLine();
|
writer.writeLine();
|
||||||
}
|
}
|
||||||
increaseIndent(): void {
|
function increaseIndent(): void {
|
||||||
this.writer.increaseIndent();
|
writer.increaseIndent();
|
||||||
}
|
}
|
||||||
decreaseIndent(): void {
|
function decreaseIndent(): void {
|
||||||
this.writer.decreaseIndent();
|
writer.decreaseIndent();
|
||||||
}
|
}
|
||||||
getText(): string {
|
function getText(): string {
|
||||||
return this.writer.getText();
|
return writer.getText();
|
||||||
}
|
}
|
||||||
rawWrite(s: string): void {
|
function rawWrite(s: string): void {
|
||||||
this.writer.rawWrite(s);
|
writer.rawWrite(s);
|
||||||
this.setLastNonTriviaPosition(s, /*force*/ false);
|
setLastNonTriviaPosition(s, /*force*/ false);
|
||||||
}
|
}
|
||||||
writeLiteral(s: string): void {
|
function writeLiteral(s: string): void {
|
||||||
this.writer.writeLiteral(s);
|
writer.writeLiteral(s);
|
||||||
this.setLastNonTriviaPosition(s, /*force*/ true);
|
setLastNonTriviaPosition(s, /*force*/ true);
|
||||||
}
|
}
|
||||||
getTextPos(): number {
|
function getTextPos(): number {
|
||||||
return this.writer.getTextPos();
|
return writer.getTextPos();
|
||||||
}
|
}
|
||||||
getLine(): number {
|
function getLine(): number {
|
||||||
return this.writer.getLine();
|
return writer.getLine();
|
||||||
}
|
}
|
||||||
getColumn(): number {
|
function getColumn(): number {
|
||||||
return this.writer.getColumn();
|
return writer.getColumn();
|
||||||
}
|
}
|
||||||
getIndent(): number {
|
function getIndent(): number {
|
||||||
return this.writer.getIndent();
|
return writer.getIndent();
|
||||||
}
|
}
|
||||||
isAtStartOfLine(): boolean {
|
function isAtStartOfLine(): boolean {
|
||||||
return this.writer.isAtStartOfLine();
|
return writer.isAtStartOfLine();
|
||||||
}
|
}
|
||||||
clear(): void {
|
function clear(): void {
|
||||||
this.writer.clear();
|
writer.clear();
|
||||||
this.lastNonTriviaPosition = 0;
|
lastNonTriviaPosition = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
onEmitNode,
|
||||||
|
onBeforeEmitNodeArray,
|
||||||
|
onAfterEmitNodeArray,
|
||||||
|
onBeforeEmitToken,
|
||||||
|
onAfterEmitToken,
|
||||||
|
write,
|
||||||
|
writeComment,
|
||||||
|
writeKeyword,
|
||||||
|
writeOperator,
|
||||||
|
writePunctuation,
|
||||||
|
writeTrailingSemicolon,
|
||||||
|
writeParameter,
|
||||||
|
writeProperty,
|
||||||
|
writeSpace,
|
||||||
|
writeStringLiteral,
|
||||||
|
writeSymbol,
|
||||||
|
writeLine,
|
||||||
|
increaseIndent,
|
||||||
|
decreaseIndent,
|
||||||
|
getText,
|
||||||
|
rawWrite,
|
||||||
|
writeLiteral,
|
||||||
|
getTextPos,
|
||||||
|
getLine,
|
||||||
|
getColumn,
|
||||||
|
getIndent,
|
||||||
|
isAtStartOfLine,
|
||||||
|
clear
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInsertionPositionAtSourceFileTop(sourceFile: SourceFile): number {
|
function getInsertionPositionAtSourceFileTop(sourceFile: SourceFile): number {
|
||||||
|
|
|
@ -1991,4 +1991,51 @@ namespace ts {
|
||||||
});
|
});
|
||||||
return typeIsAccessible ? res : undefined;
|
return typeIsAccessible ? res : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function syntaxUsuallyHasTrailingSemicolon(kind: SyntaxKind) {
|
||||||
|
return kind === SyntaxKind.VariableStatement
|
||||||
|
|| kind === SyntaxKind.ExpressionStatement
|
||||||
|
|| kind === SyntaxKind.DoStatement
|
||||||
|
|| kind === SyntaxKind.ContinueStatement
|
||||||
|
|| kind === SyntaxKind.BreakStatement
|
||||||
|
|| kind === SyntaxKind.ReturnStatement
|
||||||
|
|| kind === SyntaxKind.ThrowStatement
|
||||||
|
|| kind === SyntaxKind.DebuggerStatement
|
||||||
|
|| kind === SyntaxKind.PropertyDeclaration
|
||||||
|
|| kind === SyntaxKind.TypeAliasDeclaration
|
||||||
|
|| kind === SyntaxKind.ImportDeclaration
|
||||||
|
|| kind === SyntaxKind.ImportEqualsDeclaration
|
||||||
|
|| kind === SyntaxKind.ExportDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function probablyUsesSemicolons(sourceFile: SourceFile): boolean {
|
||||||
|
let withSemicolon = 0;
|
||||||
|
let withoutSemicolon = 0;
|
||||||
|
const nStatementsToObserve = 5;
|
||||||
|
forEachChild(sourceFile, function visit(node): boolean | undefined {
|
||||||
|
if (syntaxUsuallyHasTrailingSemicolon(node.kind)) {
|
||||||
|
const lastToken = node.getLastToken(sourceFile);
|
||||||
|
if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) {
|
||||||
|
withSemicolon++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
withoutSemicolon++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (withSemicolon + withoutSemicolon >= nStatementsToObserve) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return forEachChild(node, visit);
|
||||||
|
});
|
||||||
|
|
||||||
|
// One statement missing a semicolon isn’t sufficient evidence to say the user
|
||||||
|
// doesn’t want semicolons, because they may not even be done writing that statement.
|
||||||
|
if (withSemicolon === 0 && withoutSemicolon <= 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If even 2/5 places have a semicolon, the user probably wants semicolons
|
||||||
|
return withSemicolon / withoutSemicolon > 1 / nStatementsToObserve;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue