Skip to content

Commit d4bb267

Browse files
committed
add insertNodeInListAfter functionality
1 parent e7e1ac9 commit d4bb267

28 files changed

Lines changed: 580 additions & 125 deletions

src/harness/unittests/textChanges.ts

Lines changed: 153 additions & 19 deletions
Large diffs are not rendered by default.

src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ namespace ts.codefix {
2727
}
2828
}
2929
const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
30-
changeTracker.insertNodeAfter(sourceFile, getOpenBrace(<ConstructorDeclaration>constructor, sourceFile), superCall, { insertTrailingNewLine: true });
30+
changeTracker.insertNodeAfter(sourceFile, getOpenBrace(<ConstructorDeclaration>constructor, sourceFile), superCall, { suffix: context.newLineCharacter });
3131
changeTracker.deleteNode(sourceFile, superCall);
3232

3333
return [{

src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace ts.codefix {
1212

1313
const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
1414
const superCall = createStatement(createCall(createSuper(), /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray));
15-
changeTracker.insertNodeAfter(sourceFile, getOpenBrace(<ConstructorDeclaration>token.parent, sourceFile), superCall, { insertTrailingNewLine: true });
15+
changeTracker.insertNodeAfter(sourceFile, getOpenBrace(<ConstructorDeclaration>token.parent, sourceFile), superCall, { suffix: context.newLineCharacter });
1616

1717
return [{
1818
description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call),

src/services/codefixes/importFixes.ts

Lines changed: 42 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -130,22 +130,22 @@ namespace ts.codefix {
130130

131131
// this is a module id -> module import declaration map
132132
const cachedImportDeclarations: (ImportDeclaration | ImportEqualsDeclaration)[][] = [];
133-
let cachedNewImportInsertPosition: number;
133+
let lastImportDeclaration: Node;
134134

135135
const currentTokenMeaning = getMeaningFromLocation(token);
136136
if (context.errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code) {
137137
const symbol = checker.getAliasedSymbol(checker.getSymbolAtLocation(token));
138138
return getCodeActionForImport(symbol, /*isDefault*/ false, /*isNamespaceImport*/ true);
139139
}
140140

141-
const allPotentialModules = checker.getAmbientModules();
141+
const candidateModules = checker.getAmbientModules();
142142
for (const otherSourceFile of allSourceFiles) {
143143
if (otherSourceFile !== sourceFile && isExternalOrCommonJsModule(otherSourceFile)) {
144-
allPotentialModules.push(otherSourceFile.symbol);
144+
candidateModules.push(otherSourceFile.symbol);
145145
}
146146
}
147147

148-
for (const moduleSymbol of allPotentialModules) {
148+
for (const moduleSymbol of candidateModules) {
149149
context.cancellationToken.throwIfCancellationRequested();
150150

151151
// check the default export
@@ -277,14 +277,12 @@ namespace ts.codefix {
277277
* If the existing import declaration already has a named import list, just
278278
* insert the identifier into that list.
279279
*/
280-
const textChange = getTextChangeForImportClause(namedImportDeclaration.importClause);
280+
const fileTextChanges = getTextChangeForImportClause(namedImportDeclaration.importClause);
281281
const moduleSpecifierWithoutQuotes = stripQuotes(namedImportDeclaration.moduleSpecifier.getText());
282282
actions.push(createCodeAction(
283283
Diagnostics.Add_0_to_existing_import_declaration_from_1,
284284
[name, moduleSpecifierWithoutQuotes],
285-
textChange.newText,
286-
textChange.span,
287-
sourceFile.fileName,
285+
fileTextChanges,
288286
"InsertingIntoExistingImport",
289287
moduleSpecifierWithoutQuotes
290288
));
@@ -302,49 +300,31 @@ namespace ts.codefix {
302300
return declaration.moduleReference.getText();
303301
}
304302

305-
function getTextChangeForImportClause(importClause: ImportClause): TextChange {
306-
const newImportText = isDefault ? `default as ${name}` : name;
303+
function getTextChangeForImportClause(importClause: ImportClause): FileTextChanges[] {
304+
//const newImportText = isDefault ? `default as ${name}` : name;
307305
const importList = <NamedImports>importClause.namedBindings;
306+
const newImportSpecifier = createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name));
308307
// case 1:
309308
// original text: import default from "module"
310309
// change to: import default, { name } from "module"
311-
if (!importList && importClause.name) {
312-
const start = importClause.name.getEnd();
313-
return {
314-
newText: `, { ${newImportText} }`,
315-
span: { start, length: 0 }
316-
};
317-
}
318-
319310
// case 2:
320311
// original text: import {} from "module"
321312
// change to: import { name } from "module"
322-
if (importList.elements.length === 0) {
323-
const start = importList.getStart();
324-
return {
325-
newText: `{ ${newImportText} }`,
326-
span: { start, length: importList.getEnd() - start }
327-
};
313+
if (!importList || importList.elements.length === 0) {
314+
const newImportClause = createImportClause(importClause.name, createNamedImports([newImportSpecifier]));
315+
return createChangeTracker().replaceNode(sourceFile, importClause, newImportClause).getChanges();
328316
}
329317

330-
// case 3:
331-
// original text: import { foo, bar } from "module"
332-
// change to: import { foo, bar, name } from "module"
333-
const insertPoint = importList.elements[importList.elements.length - 1].getEnd();
334318
/**
335319
* If the import list has one import per line, preserve that. Otherwise, insert on same line as last element
336320
* import {
337321
* foo
338322
* } from "./module";
339323
*/
340-
const startLine = getLineOfLocalPosition(sourceFile, importList.getStart());
341-
const endLine = getLineOfLocalPosition(sourceFile, importList.getEnd());
342-
const oneImportPerLine = endLine - startLine > importList.elements.length;
343-
344-
return {
345-
newText: `,${oneImportPerLine ? context.newLineCharacter : " "}${newImportText}`,
346-
span: { start: insertPoint, length: 0 }
347-
};
324+
return createChangeTracker().insertNodeInListAfter(
325+
sourceFile,
326+
importList.elements[importList.elements.length - 1],
327+
newImportSpecifier).getChanges();
348328
}
349329

350330
function getCodeActionForNamespaceImport(declaration: ImportDeclaration | ImportEqualsDeclaration): ImportCodeAction {
@@ -370,48 +350,47 @@ namespace ts.codefix {
370350
return createCodeAction(
371351
Diagnostics.Change_0_to_1,
372352
[name, `${namespacePrefix}.${name}`],
373-
`${namespacePrefix}.`,
374-
{ start: token.getStart(), length: 0 },
375-
sourceFile.fileName,
353+
createChangeTracker().replaceNode(sourceFile, token, createPropertyAccess(createIdentifier(namespacePrefix), name)).getChanges(),
376354
"CodeChange"
377355
);
378356
}
379357
}
380358

381359
function getCodeActionForNewImport(moduleSpecifier?: string): ImportCodeAction {
382-
if (!cachedNewImportInsertPosition) {
360+
if (!lastImportDeclaration) {
383361
// insert after any existing imports
384-
let lastModuleSpecifierEnd = -1;
385-
for (const moduleSpecifier of sourceFile.imports) {
386-
const end = moduleSpecifier.getEnd();
387-
if (!lastModuleSpecifierEnd || end > lastModuleSpecifierEnd) {
388-
lastModuleSpecifierEnd = end;
362+
for (let i = sourceFile.statements.length - 1; i >= 0; i--) {
363+
const statement = sourceFile.statements[i];
364+
if (statement.kind === SyntaxKind.ImportEqualsDeclaration || statement.kind === SyntaxKind.ImportDeclaration) {
365+
lastImportDeclaration = statement;
366+
break;
389367
}
390368
}
391-
cachedNewImportInsertPosition = lastModuleSpecifierEnd > 0 ? sourceFile.getLineEndOfPosition(lastModuleSpecifierEnd) : sourceFile.getStart();
392369
}
393370

394371
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
395372
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier || getModuleSpecifierForNewImport());
396-
const importStatementText = isDefault
397-
? `import ${name} from "${moduleSpecifierWithoutQuotes}"`
373+
const changeTracker = createChangeTracker();
374+
const importClause = isDefault
375+
? createImportClause(createIdentifier(name), /*namedBindings*/ undefined)
398376
: isNamespaceImport
399-
? `import * as ${name} from "${moduleSpecifierWithoutQuotes}"`
400-
: `import { ${name} } from "${moduleSpecifierWithoutQuotes}"`;
377+
? createImportClause(/*name*/ undefined, createNamespaceImport(createIdentifier(name)))
378+
: createImportClause(/*name*/ undefined, createNamedImports([createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name))]));
379+
const importDecl = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, importClause, createLiteral(moduleSpecifierWithoutQuotes));
380+
if (!lastImportDeclaration) {
381+
changeTracker.insertNodeAt(sourceFile, sourceFile.getStart(), importDecl, { suffix: `${context.newLineCharacter}${context.newLineCharacter}` });
382+
}
383+
else {
384+
changeTracker.insertNodeAfter(sourceFile, lastImportDeclaration, importDecl, { suffix: context.newLineCharacter });
385+
}
401386

402387
// if this file doesn't have any import statements, insert an import statement and then insert a new line
403388
// between the only import statement and user code. Otherwise just insert the statement because chances
404389
// are there are already a new line seperating code and import statements.
405-
const newText = cachedNewImportInsertPosition === sourceFile.getStart()
406-
? `${importStatementText};${context.newLineCharacter}${context.newLineCharacter}`
407-
: `${context.newLineCharacter}${importStatementText};`;
408-
409390
return createCodeAction(
410391
Diagnostics.Import_0_from_1,
411392
[name, `"${moduleSpecifierWithoutQuotes}"`],
412-
newText,
413-
{ start: cachedNewImportInsertPosition, length: 0 },
414-
sourceFile.fileName,
393+
changeTracker.getChanges(),
415394
"NewImport",
416395
moduleSpecifierWithoutQuotes
417396
);
@@ -576,17 +555,19 @@ namespace ts.codefix {
576555

577556
}
578557

558+
function createChangeTracker() {
559+
return textChanges.ChangeTracker.fromCodeFixContext(context);;
560+
}
561+
579562
function createCodeAction(
580563
description: DiagnosticMessage,
581564
diagnosticArgs: string[],
582-
newText: string,
583-
span: TextSpan,
584-
fileName: string,
565+
changes: FileTextChanges[],
585566
kind: ImportCodeActionKind,
586567
moduleSpecifier?: string): ImportCodeAction {
587568
return {
588569
description: formatMessage.apply(undefined, [undefined, description].concat(<any[]>diagnosticArgs)),
589-
changes: [{ fileName, textChanges: [{ newText, span }] }],
570+
changes,
590571
kind,
591572
moduleSpecifier
592573
};

src/services/codefixes/unusedIdentifierFixes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ namespace ts.codefix {
132132
else {
133133
const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1);
134134
if (previousToken && previousToken.kind === SyntaxKind.CommaToken) {
135-
const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, /*forDeleteOperation*/ true);
135+
const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, textChanges.Position.FullStart);
136136
return deleteRange({ pos: startPosition, end: namespaceImport.end });
137137
}
138138
return deleteRange(namespaceImport);

src/services/formatting/formattingScanner.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,8 @@ namespace ts.formatting {
276276
function isOnToken(): boolean {
277277
Debug.assert(scanner !== undefined);
278278

279-
const current = (lastTokenInfo && lastTokenInfo.token.kind) || scanner.getToken();
280-
const startPos = (lastTokenInfo && lastTokenInfo.token.pos) || scanner.getStartPos();
279+
const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken();
280+
const startPos = lastTokenInfo ? lastTokenInfo.token.pos : scanner.getStartPos();
281281
return startPos < endPos && current !== SyntaxKind.EndOfFileToken && !isTrivia(current);
282282
}
283283

0 commit comments

Comments
 (0)