From 50310188a14bfab608ae110d266be9b24469ed99 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 27 Mar 2018 08:35:57 -0700 Subject: [PATCH] textChanges: Add insertCommentBeforeLine method --- .../codefixes/disableJsDiagnostics.ts | 27 +++------------ src/services/textChanges.ts | 34 +++++++++++++++++-- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/services/codefixes/disableJsDiagnostics.ts b/src/services/codefixes/disableJsDiagnostics.ts index 57778d7352607..2d82a198ce9ee 100644 --- a/src/services/codefixes/disableJsDiagnostics.ts +++ b/src/services/codefixes/disableJsDiagnostics.ts @@ -27,7 +27,7 @@ namespace ts.codefix { fixId: undefined, }]; - if (isValidSuppressLocation(sourceFile, span.start)) { + if (textChanges.isValidLocationToAddComment(sourceFile, span.start)) { fixes.unshift({ description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message), changes: textChanges.ChangeTracker.with(context, t => makeChange(t, sourceFile, span.start)), @@ -41,37 +41,18 @@ namespace ts.codefix { getAllCodeActions: context => { const seenLines = createMap(); return codeFixAll(context, errorCodes, (changes, diag) => { - if (isValidSuppressLocation(diag.file!, diag.start!)) { + if (textChanges.isValidLocationToAddComment(diag.file!, diag.start!)) { makeChange(changes, diag.file!, diag.start!, seenLines); } }); }, }); - function isValidSuppressLocation(sourceFile: SourceFile, position: number) { - return !isInComment(sourceFile, position) && !isInString(sourceFile, position) && !isInTemplateString(sourceFile, position); - } - function makeChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, seenLines?: Map) { const { line: lineNumber } = getLineAndCharacterOfPosition(sourceFile, position); - // Only need to add `// @ts-ignore` for a line once. - if (seenLines && !addToSeen(seenLines, lineNumber)) { - return; + if (!seenLines || addToSeen(seenLines, lineNumber)) { + changes.insertCommentBeforeLine(sourceFile, lineNumber, position, " @ts-ignore"); } - - const lineStartPosition = getStartPositionOfLine(lineNumber, sourceFile); - const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition); - - // First try to see if we can put the '// @ts-ignore' on the previous line. - // We need to make sure that we are not in the middle of a string literal or a comment. - // If so, we do not want to separate the node from its comment if we can. - // Otherwise, add an extra new line immediately before the error span. - const insertAtLineStart = isValidSuppressLocation(sourceFile, startPosition); - - const token = getTouchingToken(sourceFile, insertAtLineStart ? startPosition : position, /*includeJsDocComment*/ false); - const clone = setStartsOnNewLine(getSynthesizedDeepClone(token), true); - addSyntheticLeadingComment(clone, SyntaxKind.SingleLineCommentTrivia, " @ts-ignore"); - changes.replaceNode(sourceFile, token, clone, { preserveLeadingWhitespace: true, prefix: insertAtLineStart ? undefined : changes.newLineCharacter }); } } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 24ff0ba281285..f7f8cf361ccf5 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -103,10 +103,11 @@ namespace ts.textChanges { enum ChangeKind { Remove, ReplaceWithSingleNode, - ReplaceWithMultipleNodes + ReplaceWithMultipleNodes, + Text, } - type Change = ReplaceWithSingleNode | ReplaceWithMultipleNodes | RemoveNode; + type Change = ReplaceWithSingleNode | ReplaceWithMultipleNodes | RemoveNode | ChangeText; interface BaseChange { readonly sourceFile: SourceFile; @@ -132,6 +133,11 @@ namespace ts.textChanges { readonly options?: InsertNodeOptions; } + interface ChangeText extends BaseChange { + readonly kind: ChangeKind.Text; + readonly text: string; + } + function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart, position: Position) { if (options.useNonAdjustedStartPosition) { return node.getStart(sourceFile); @@ -344,6 +350,23 @@ namespace ts.textChanges { this.replaceRange(sourceFile, { pos, end: pos }, createToken(modifier), { suffix: " " }); } + public insertCommentBeforeLine(sourceFile: SourceFile, lineNumber: number, position: number, commentText: string): void { + const lineStartPosition = getStartPositionOfLine(lineNumber, sourceFile); + const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition); + // First try to see if we can put the comment on the previous line. + // We need to make sure that we are not in the middle of a string literal or a comment. + // If so, we do not want to separate the node from its comment if we can. + // Otherwise, add an extra new line immediately before the error span. + const insertAtLineStart = isValidLocationToAddComment(sourceFile, startPosition); + const token = getTouchingToken(sourceFile, insertAtLineStart ? startPosition : position, /*includeJsDocComment*/ false); + const text = `${insertAtLineStart ? "" : this.newLineCharacter}${sourceFile.text.slice(lineStartPosition, startPosition)}//${commentText}${this.newLineCharacter}`; + this.insertText(sourceFile, token.getStart(sourceFile), text); + } + + private insertText(sourceFile: SourceFile, pos: number, text: string): void { + this.changes.push({ kind: ChangeKind.Text, sourceFile, range: { pos, end: pos }, text }); + } + /** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */ public insertTypeAnnotation(sourceFile: SourceFile, node: TypeAnnotatable, type: TypeNode): void { const end = (isFunctionLike(node) @@ -647,6 +670,9 @@ namespace ts.textChanges { if (change.kind === ChangeKind.Remove) { return ""; } + if (change.kind === ChangeKind.Text) { + return change.text; + } const { options = {}, range: { pos } } = change; const format = (n: Node) => getFormattedTextOfNode(n, sourceFile, pos, options, newLineCharacter, formatContext, validate); @@ -896,4 +922,8 @@ namespace ts.textChanges { } } } + + export function isValidLocationToAddComment(sourceFile: SourceFile, position: number) { + return !isInComment(sourceFile, position) && !isInString(sourceFile, position) && !isInTemplateString(sourceFile, position); + } }