Skip to content

Commit b2dd610

Browse files
authored
Fix ignore message indentation (#22340)
* Update baselines for user tests * Add explicit indentation * Fix #21355: Format `// @ts-ignore` added by quick fix * Extract check to a separate function * Consolidate checking for valid insert location * Code review comments * Do not return makeChange
1 parent 2fb7e64 commit b2dd610

File tree

5 files changed

+62
-49
lines changed

5 files changed

+62
-49
lines changed

src/services/codefixes/disableJsDiagnostics.ts

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,60 +9,69 @@ namespace ts.codefix {
99
registerCodeFix({
1010
errorCodes,
1111
getCodeActions(context) {
12-
const { sourceFile, program, span } = context;
12+
const { sourceFile, program, span, host, formatContext } = context;
1313

1414
if (!isInJavaScriptFile(sourceFile) || !isCheckJsEnabledForFile(sourceFile, program.getCompilerOptions())) {
1515
return undefined;
1616
}
1717

18-
const newLineCharacter = getNewLineOrDefaultFromHost(context.host, context.formatContext.options);
18+
const fixes: CodeFixAction[] = [
19+
{
20+
description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file),
21+
changes: [createFileTextChanges(sourceFile.fileName, [
22+
createTextChange(sourceFile.checkJsDirective
23+
? createTextSpanFromBounds(sourceFile.checkJsDirective.pos, sourceFile.checkJsDirective.end)
24+
: createTextSpan(0, 0), `// @ts-nocheck${getNewLineOrDefaultFromHost(host, formatContext.options)}`),
25+
])],
26+
// fixId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file.
27+
fixId: undefined,
28+
}];
1929

20-
return [{
21-
description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message),
22-
changes: [createFileTextChanges(sourceFile.fileName, [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter).change])],
23-
fixId,
24-
},
25-
{
26-
description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file),
27-
changes: [createFileTextChanges(sourceFile.fileName, [
28-
createTextChange(sourceFile.checkJsDirective ? createTextSpanFromBounds(sourceFile.checkJsDirective.pos, sourceFile.checkJsDirective.end) : createTextSpan(0, 0), `// @ts-nocheck${newLineCharacter}`),
29-
])],
30-
// fixId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file.
31-
fixId: undefined,
32-
}];
30+
if (isValidSuppressLocation(sourceFile, span.start)) {
31+
fixes.unshift({
32+
description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message),
33+
changes: textChanges.ChangeTracker.with(context, t => makeChange(t, sourceFile, span.start)),
34+
fixId,
35+
});
36+
}
37+
38+
return fixes;
3339
},
3440
fixIds: [fixId],
3541
getAllCodeActions: context => {
36-
const seenLines = createMap<true>(); // Only need to add `// @ts-ignore` for a line once.
37-
return codeFixAllWithTextChanges(context, errorCodes, (changes, err) => {
38-
if (err.start !== undefined) {
39-
const { lineNumber, change } = getIgnoreCommentLocationForLocation(err.file!, err.start, getNewLineOrDefaultFromHost(context.host, context.formatContext.options));
40-
if (addToSeen(seenLines, lineNumber)) {
41-
changes.push(change);
42-
}
42+
const seenLines = createMap<true>();
43+
return codeFixAll(context, errorCodes, (changes, diag) => {
44+
if (isValidSuppressLocation(diag.file!, diag.start!)) {
45+
makeChange(changes, diag.file!, diag.start!, seenLines);
4346
}
4447
});
4548
},
4649
});
4750

48-
function getIgnoreCommentLocationForLocation(sourceFile: SourceFile, position: number, newLineCharacter: string): { lineNumber: number, change: TextChange } {
51+
function isValidSuppressLocation(sourceFile: SourceFile, position: number) {
52+
return !isInComment(sourceFile, position) && !isInString(sourceFile, position) && !isInTemplateString(sourceFile, position);
53+
}
54+
55+
function makeChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, seenLines?: Map<true>) {
4956
const { line: lineNumber } = getLineAndCharacterOfPosition(sourceFile, position);
57+
58+
// Only need to add `// @ts-ignore` for a line once.
59+
if (seenLines && !addToSeen(seenLines, lineNumber)) {
60+
return;
61+
}
62+
5063
const lineStartPosition = getStartPositionOfLine(lineNumber, sourceFile);
5164
const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition);
5265

5366
// First try to see if we can put the '// @ts-ignore' on the previous line.
5467
// We need to make sure that we are not in the middle of a string literal or a comment.
55-
// We also want to check if the previous line holds a comment for a node on the next line
56-
// if so, we do not want to separate the node from its comment if we can.
57-
if (!isInComment(sourceFile, startPosition) && !isInString(sourceFile, startPosition) && !isInTemplateString(sourceFile, startPosition)) {
58-
const token = getTouchingToken(sourceFile, startPosition, /*includeJsDocComment*/ false);
59-
const tokenLeadingComments = getLeadingCommentRangesOfNode(token, sourceFile);
60-
if (!tokenLeadingComments || !tokenLeadingComments.length || tokenLeadingComments[0].pos >= startPosition) {
61-
return { lineNumber, change: createTextChangeFromStartLength(startPosition, 0, `// @ts-ignore${newLineCharacter}`) };
62-
}
63-
}
68+
// If so, we do not want to separate the node from its comment if we can.
69+
// Otherwise, add an extra new line immediately before the error span.
70+
const insertAtLineStart = isValidSuppressLocation(sourceFile, startPosition);
6471

65-
// If all fails, add an extra new line immediately before the error span.
66-
return { lineNumber, change: createTextChangeFromStartLength(position, 0, `${position === startPosition ? "" : newLineCharacter}// @ts-ignore${newLineCharacter}`) };
72+
const token = getTouchingToken(sourceFile, insertAtLineStart ? startPosition : position, /*includeJsDocComment*/ false);
73+
const clone = setStartsOnNewLine(getSynthesizedDeepClone(token), true);
74+
addSyntheticLeadingComment(clone, SyntaxKind.SingleLineCommentTrivia, " @ts-ignore");
75+
changes.replaceNode(sourceFile, token, clone, { preserveLeadingWhitespace: true, prefix: insertAtLineStart ? undefined : changes.newLineCharacter });
6776
}
6877
}

src/services/textChanges.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ namespace ts.textChanges {
9494
* Text of inserted node will be formatted with this delta, otherwise delta will be inferred from the new node kind
9595
*/
9696
delta?: number;
97+
/**
98+
* Do not trim leading white spaces in the edit range
99+
*/
100+
preserveLeadingWhitespace?: boolean;
97101
}
98102

99103
enum ChangeKind {
@@ -212,7 +216,7 @@ namespace ts.textChanges {
212216
}
213217

214218
/** Public for tests only. Other callers should use `ChangeTracker.with`. */
215-
constructor(private readonly newLineCharacter: string, private readonly formatContext: formatting.FormatContext) {}
219+
constructor(public readonly newLineCharacter: string, private readonly formatContext: formatting.FormatContext) {}
216220

217221
public deleteRange(sourceFile: SourceFile, range: TextRange) {
218222
this.changes.push({ kind: ChangeKind.Remove, sourceFile, range });
@@ -628,7 +632,7 @@ namespace ts.textChanges {
628632
? change.nodes.map(n => removeSuffix(format(n), newLineCharacter)).join(newLineCharacter)
629633
: format(change.node);
630634
// strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line
631-
const noIndent = (options.indentation !== undefined || getLineStartPositionForPosition(pos, sourceFile) === pos) ? text : text.replace(/^\s+/, "");
635+
const noIndent = (options.preserveLeadingWhitespace || options.indentation !== undefined || getLineStartPositionForPosition(pos, sourceFile) === pos) ? text : text.replace(/^\s+/, "");
632636
return (options.prefix || "") + noIndent + (options.suffix || "");
633637
}
634638

tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile6.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
// @Filename: a.js
88
////var x = 0;
99
////
10-
////function f(_a) {
11-
//// [|f(x());|]
12-
////}
10+
////function f(_a) {[|
11+
//// f(x());
12+
////|]}
1313

1414
// Disable checking for next line
15-
verify.rangeAfterCodeFix(`// @ts-ignore
15+
verify.rangeAfterCodeFix(` // @ts-ignore
1616
f(x());`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
1717

tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile7.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
// @Filename: a.js
88
////var x = 0;
99
////
10-
////function f(_a) {
11-
//// [|x();|]
12-
////}
10+
////function f(_a) {[|
11+
//// x();
12+
////|]}
1313

1414
// Disable checking for next line
15-
verify.rangeAfterCodeFix(`// @ts-ignore
15+
verify.rangeAfterCodeFix(`// @ts-ignore
1616
x();`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
1717

tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile8.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
// @Filename: a.js
88
////var x = 0;
99
////
10-
////function f(_a) {
10+
////function f(_a) {[|
1111
//// /** comment for f */
12-
//// [|f(x());|]
13-
////}
12+
//// f(x());
13+
////|]}
1414

1515
// Disable checking for next line
16-
verify.rangeAfterCodeFix(`f(
16+
verify.rangeAfterCodeFix(` /** comment for f */
1717
// @ts-ignore
18-
x());`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
18+
f(x());`, /*includeWhiteSpace*/ false, /*errorCode*/ undefined, /*index*/ 0);
1919

0 commit comments

Comments
 (0)