From d4212576580e29f594cb118cd7b7753707b0ec02 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 8 Jun 2018 15:10:50 -0700 Subject: [PATCH 1/4] Add refactor to convert named to default export and back --- src/harness/fourslash.ts | 54 ++++-- .../diagnosticInformationMap.generated.ts | 4 +- src/parser/diagnosticMessages.generated.json | 4 +- src/parser/diagnosticMessages.json | 10 +- src/parser/utilities.ts | 2 +- src/services/documentHighlights.ts | 11 +- src/services/findAllReferences.ts | 24 +++ src/services/importTracker.ts | 10 +- src/services/refactors/convertExport.ts | 168 ++++++++++++++++++ src/services/refactors/convertImport.ts | 2 +- src/services/textChanges.ts | 10 ++ src/services/tsconfig.json | 1 + src/services/utilities.ts | 12 +- tests/cases/fourslash/fourslash.ts | 8 +- .../refactorConvertExport_defaultToNamed.ts | 41 +++++ .../refactorConvertExport_namedToDefault.ts | 39 ++++ ...Export_namedToDefault_alreadyHasDefault.ts | 8 + 17 files changed, 368 insertions(+), 40 deletions(-) create mode 100644 src/services/refactors/convertExport.ts create mode 100644 tests/cases/fourslash/refactorConvertExport_defaultToNamed.ts create mode 100644 tests/cases/fourslash/refactorConvertExport_namedToDefault.ts create mode 100644 tests/cases/fourslash/refactorConvertExport_namedToDefault_alreadyHasDefault.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 8045f3a948baf..84ba9ecc2aabe 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -3036,6 +3036,10 @@ Actual: ${stringify(fullActual)}`); } } + public verifyRefactorsAvailable(names: ReadonlyArray): void { + assert.deepEqual(unique(this.getApplicableRefactors(this.getSelection()), r => r.name), names); + } + public verifyRefactor({ name, actionName, refactors }: FourSlashInterface.VerifyRefactorOptions) { const actualRefactors = this.getApplicableRefactors(this.getSelection()).filter(r => r.name === name && r.actions.some(a => a.name === actionName)); this.assertObjectsEqual(actualRefactors, refactors); @@ -3077,9 +3081,22 @@ Actual: ${stringify(fullActual)}`); this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false); } - const { renamePosition, newContent } = parseNewContent(); + let renameFilename: string | undefined; + let renamePosition: number | undefined; - this.verifyCurrentFileContent(newContent); + const newFileContents = typeof newContentWithRenameMarker === "string" ? { [this.activeFile.fileName]: newContentWithRenameMarker } : newContentWithRenameMarker; + for (const fileName in newFileContents) { + const { renamePosition: rp, newContent } = TestState.parseNewContent(newFileContents[fileName]); + if (renamePosition === undefined) { + renameFilename = fileName; + renamePosition = rp; + } + else { + ts.Debug.assert(rp === undefined); + } + this.verifyFileContent(fileName, newContent); + + } if (renamePosition === undefined) { if (editInfo.renameLocation !== undefined) { @@ -3087,22 +3104,21 @@ Actual: ${stringify(fullActual)}`); } } else { - // TODO: test editInfo.renameFilename value - assert.isDefined(editInfo.renameFilename); + this.assertObjectsEqual(editInfo.renameFilename, renameFilename); if (renamePosition !== editInfo.renameLocation) { this.raiseError(`Expected rename position of ${renamePosition}, but got ${editInfo.renameLocation}`); } } + } - function parseNewContent(): { renamePosition: number | undefined, newContent: string } { - const renamePosition = newContentWithRenameMarker.indexOf("/*RENAME*/"); - if (renamePosition === -1) { - return { renamePosition: undefined, newContent: newContentWithRenameMarker }; - } - else { - const newContent = newContentWithRenameMarker.slice(0, renamePosition) + newContentWithRenameMarker.slice(renamePosition + "/*RENAME*/".length); - return { renamePosition, newContent }; - } + private static parseNewContent(newContentWithRenameMarker: string): { readonly renamePosition: number | undefined, readonly newContent: string } { + const renamePosition = newContentWithRenameMarker.indexOf("/*RENAME*/"); + if (renamePosition === -1) { + return { renamePosition: undefined, newContent: newContentWithRenameMarker }; + } + else { + const newContent = newContentWithRenameMarker.slice(0, renamePosition) + newContentWithRenameMarker.slice(renamePosition + "/*RENAME*/".length); + return { renamePosition, newContent }; } } @@ -3806,7 +3822,7 @@ ${code} } /** Collects an array of unique outputs. */ - function unique(inputs: T[], getOutput: (t: T) => string): string[] { + function unique(inputs: ReadonlyArray, getOutput: (t: T) => string): string[] { const set = ts.createMap(); for (const input of inputs) { const out = getOutput(input); @@ -4097,6 +4113,10 @@ namespace FourSlashInterface { this.state.verifyApplicableRefactorAvailableForRange(this.negative); } + public refactorsAvailable(names: ReadonlyArray): void { + this.state.verifyRefactorsAvailable(names); + } + public refactor(options: VerifyRefactorOptions) { this.state.verifyRefactor(options); } @@ -4719,7 +4739,7 @@ namespace FourSlashInterface { refactorName: string; actionName: string; actionDescription: string; - newContent: string; + newContent: NewFileContent; } export type ExpectedCompletionEntry = string | { @@ -4781,9 +4801,11 @@ namespace FourSlashInterface { filesToSearch?: ReadonlyArray; } + export type NewFileContent = string | { readonly [filename: string]: string }; + export interface NewContentOptions { // Exactly one of these should be defined. - newFileContent?: string | { readonly [filename: string]: string }; + newFileContent?: NewFileContent; newRangeContent?: string; } diff --git a/src/parser/diagnosticInformationMap.generated.ts b/src/parser/diagnosticInformationMap.generated.ts index 81c65e998ed31..71a2a7f3b5630 100644 --- a/src/parser/diagnosticInformationMap.generated.ts +++ b/src/parser/diagnosticInformationMap.generated.ts @@ -1,5 +1,5 @@ // -// generated from './diagnosticInformationMap.generated.ts' by 'src\parser' +// generated from './diagnosticInformationMap.generated.ts' by 'src/parser' /* @internal */ namespace ts { function diag(code: number, category: DiagnosticCategory, key: string, message: string, reportsUnnecessary?: {}): DiagnosticMessage { @@ -1106,5 +1106,7 @@ namespace ts { Add_or_remove_braces_in_an_arrow_function: diag(95058, DiagnosticCategory.Message, "Add_or_remove_braces_in_an_arrow_function_95058", "Add or remove braces in an arrow function"), Add_braces_to_arrow_function: diag(95059, DiagnosticCategory.Message, "Add_braces_to_arrow_function_95059", "Add braces to arrow function"), Remove_braces_from_arrow_function: diag(95060, DiagnosticCategory.Message, "Remove_braces_from_arrow_function_95060", "Remove braces from arrow function"), + Convert_default_export_to_named_export: diag(95061, DiagnosticCategory.Message, "Convert_default_export_to_named_export_95061", "Convert default export to named export"), + Convert_named_export_to_default_export: diag(95062, DiagnosticCategory.Message, "Convert_named_export_to_default_export_95062", "Convert named export to default export"), }; } \ No newline at end of file diff --git a/src/parser/diagnosticMessages.generated.json b/src/parser/diagnosticMessages.generated.json index 7174b3a06f67d..f394a851c688f 100644 --- a/src/parser/diagnosticMessages.generated.json +++ b/src/parser/diagnosticMessages.generated.json @@ -1097,5 +1097,7 @@ "Convert_named_imports_to_namespace_import_95057" : "Convert named imports to namespace import", "Add_or_remove_braces_in_an_arrow_function_95058" : "Add or remove braces in an arrow function", "Add_braces_to_arrow_function_95059" : "Add braces to arrow function", - "Remove_braces_from_arrow_function_95060" : "Remove braces from arrow function" + "Remove_braces_from_arrow_function_95060" : "Remove braces from arrow function", + "Convert_default_export_to_named_export_95061" : "Convert default export to named export", + "Convert_named_export_to_default_export_95062" : "Convert named export to default export" } \ No newline at end of file diff --git a/src/parser/diagnosticMessages.json b/src/parser/diagnosticMessages.json index 599d81c499778..1274498a62f61 100644 --- a/src/parser/diagnosticMessages.json +++ b/src/parser/diagnosticMessages.json @@ -3636,7 +3636,7 @@ "category": "Message", "code": 6353 }, - + "Project '{0}' is up to date with .d.ts files from its dependencies": { "category": "Message", "code": 6354 @@ -4414,5 +4414,13 @@ "Remove braces from arrow function": { "category": "Message", "code": 95060 + }, + "Convert default export to named export": { + "category": "Message", + "code": 95061 + }, + "Convert named export to default export": { + "category": "Message", + "code": 95062 } } diff --git a/src/parser/utilities.ts b/src/parser/utilities.ts index 83e56d81631e5..a67bfd7c96fbf 100644 --- a/src/parser/utilities.ts +++ b/src/parser/utilities.ts @@ -5813,7 +5813,7 @@ namespace ts { // Keywords /* @internal */ - export function isModifierKind(token: SyntaxKind): boolean { + export function isModifierKind(token: SyntaxKind): token is Modifier["kind"] { switch (token) { case SyntaxKind.AbstractKeyword: case SyntaxKind.AsyncKeyword: diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index 458b12c2ada1f..6976cd2ea2001 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -187,15 +187,8 @@ namespace ts.DocumentHighlights { }); } - function getModifierOccurrences(modifier: SyntaxKind, declaration: Node): Node[] { - const modifierFlag = modifierToFlag(modifier); - return mapDefined(getNodesToSearchForModifier(declaration, modifierFlag), node => { - if (getModifierFlags(node) & modifierFlag) { - const mod = find(node.modifiers!, m => m.kind === modifier); - Debug.assert(!!mod); - return mod; - } - }); + function getModifierOccurrences(modifier: Modifier["kind"], declaration: Node): Node[] { + return mapDefined(getNodesToSearchForModifier(declaration, modifierToFlag(modifier)), node => findModifier(node, modifier)); } function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): ReadonlyArray | undefined { diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 46dd83851c197..5619a7e323c4c 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -590,6 +590,30 @@ namespace ts.FindAllReferences.Core { } } + export function eachExportReference( + sourceFiles: ReadonlyArray, + checker: TypeChecker, + cancellationToken: CancellationToken | undefined, + exportSymbol: Symbol, + exportingModuleSymbol: Symbol, + exportName: string, + isDefaultExport: boolean, + cb: (ref: Identifier) => void, + ): void { + const importTracker = createImportTracker(sourceFiles, arrayToSet(sourceFiles, f => f.fileName), checker, cancellationToken); + const { importSearches, indirectUsers } = importTracker(exportSymbol, { exportKind: isDefaultExport ? ExportKind.Default : ExportKind.Named, exportingModuleSymbol }, /*isForRename*/ false); + for (const [importLocation] of importSearches) { + cb(importLocation); + } + for (const indirectUser of indirectUsers) { + for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) { + if (isIdentifier(node) && checker.getSymbolAtLocation(node) === exportSymbol) { + cb(node); + } + } + } + } + function shouldAddSingleReference(singleRef: Identifier | StringLiteral, state: State): boolean { if (!hasMatchingMeaning(singleRef, state)) return false; if (!state.options.isForRename) return true; diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index f53b7ffad490f..4ad6b0fce1f45 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -12,7 +12,7 @@ namespace ts.FindAllReferences { export type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult; /** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */ - export function createImportTracker(sourceFiles: ReadonlyArray, sourceFilesSet: ReadonlyMap, checker: TypeChecker, cancellationToken: CancellationToken): ImportTracker { + export function createImportTracker(sourceFiles: ReadonlyArray, sourceFilesSet: ReadonlyMap, checker: TypeChecker, cancellationToken: CancellationToken | undefined): ImportTracker { const allDirectImports = getDirectImportsMap(sourceFiles, checker, cancellationToken); return (exportSymbol, exportInfo, isForRename) => { const { directImports, indirectUsers } = getImportersForExport(sourceFiles, sourceFilesSet, allDirectImports, exportInfo, checker, cancellationToken); @@ -43,7 +43,7 @@ namespace ts.FindAllReferences { allDirectImports: Map, { exportingModuleSymbol, exportKind }: ExportInfo, checker: TypeChecker, - cancellationToken: CancellationToken + cancellationToken: CancellationToken | undefined, ): { directImports: Importer[], indirectUsers: ReadonlyArray } { const markSeenDirectImport = nodeSeenTracker(); const markSeenIndirectUser = nodeSeenTracker(); @@ -80,7 +80,7 @@ namespace ts.FindAllReferences { continue; } - cancellationToken.throwIfCancellationRequested(); + if (cancellationToken) cancellationToken.throwIfCancellationRequested(); switch (direct.kind) { case SyntaxKind.CallExpression: @@ -373,11 +373,11 @@ namespace ts.FindAllReferences { } /** Returns a map from a module symbol Id to all import statements that directly reference the module. */ - function getDirectImportsMap(sourceFiles: ReadonlyArray, checker: TypeChecker, cancellationToken: CancellationToken): Map { + function getDirectImportsMap(sourceFiles: ReadonlyArray, checker: TypeChecker, cancellationToken: CancellationToken | undefined): Map { const map = createMap(); for (const sourceFile of sourceFiles) { - cancellationToken.throwIfCancellationRequested(); + if (cancellationToken) cancellationToken.throwIfCancellationRequested(); forEachImport(sourceFile, (importDecl, moduleSpecifier) => { const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); if (moduleSymbol) { diff --git a/src/services/refactors/convertExport.ts b/src/services/refactors/convertExport.ts new file mode 100644 index 0000000000000..ff808da48692b --- /dev/null +++ b/src/services/refactors/convertExport.ts @@ -0,0 +1,168 @@ +/* @internal */ +namespace ts.refactor { + const refactorName = "Convert export"; + const actionNameDefaultToNamed = "Convert default export to named export"; + const actionNameNamedToDefault = "Convert named export to default export"; + registerRefactor(refactorName, { + getAvailableActions(context): ApplicableRefactorInfo[] | undefined { + const info = getInfo(context); + if (!info) return undefined; + const description = info.wasDefault ? Diagnostics.Convert_default_export_to_named_export.message : Diagnostics.Convert_named_export_to_default_export.message; + const actionName = info.wasDefault ? actionNameDefaultToNamed : actionNameNamedToDefault; + return [{ name: refactorName, description, actions: [{ name: actionName, description }] }]; + }, + getEditsForAction(context, actionName): RefactorEditInfo { + Debug.assert(actionName === actionNameDefaultToNamed || actionName === actionNameNamedToDefault); + const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, Debug.assertDefined(getInfo(context)), t, context.cancellationToken)); + return { edits, renameFilename: undefined, renameLocation: undefined }; + }, + }); + + type ExportToConvert = (FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration) & { readonly name: Identifier }; + interface Info { + readonly exportNode: ExportToConvert; + readonly wasDefault: boolean; + } + + function getInfo(context: RefactorContext): Info | undefined { + const { file } = context; + const span = getRefactorContextSpan(context); + const token = getTokenAtPosition(file, span.start, /*includeJsDocComment*/ false); + const exportNode = getParentNodeInSpan(token, file, span); + if (!exportNode || exportNode.parent !== file) return undefined; + + const flags = getModifierFlags(exportNode); + const wasDefault = !!(flags & ModifierFlags.Default); + // If source file already has a default export, don't offer refactor. + if (!(flags & ModifierFlags.Export) || !wasDefault && file.symbol.exports!.has(InternalSymbolName.Default)) { + return undefined; + } + + switch (exportNode.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ModuleDeclaration: + return isIdentifier((exportNode as ExportToConvert).name) ? { exportNode: exportNode as ExportToConvert, wasDefault } : undefined; + default: + return undefined; + } + } + + function doChange(exportingSourceFile: SourceFile, program: Program, info: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { + changeExport(exportingSourceFile, info, changes); + changeImports(exportingSourceFile, program, info, changes, cancellationToken); + } + + function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode }: Info, changes: textChanges.ChangeTracker): void { + if (wasDefault) { + changes.deleteNode(exportingSourceFile, Debug.assertDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword))); + } + else { + changes.insertNodeAfter(exportingSourceFile, Debug.assertDefined(findModifier(exportNode, SyntaxKind.ExportKeyword)), createToken(SyntaxKind.DefaultKeyword)); + } + } + + function changeImports(exportingSourceFile: SourceFile, program: Program, { wasDefault, exportNode }: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { + const checker = program.getTypeChecker(); + const exportSymbol = Debug.assertDefined(checker.getSymbolAtLocation(exportNode.name)); + const exportName = exportNode.name.text; + FindAllReferences.Core.eachExportReference(program.getSourceFiles(), checker, cancellationToken, exportSymbol, exportingSourceFile.symbol, exportName, wasDefault, ref => { + const importingSourceFile = ref.getSourceFile(); + if (wasDefault) { + changeDefaultToNamedImport(importingSourceFile, ref, changes, exportName); + } + else { + changeNamedToDefaultImport(importingSourceFile, ref, changes); + } + }); + } + + function changeDefaultToNamedImport(importingSourceFile: SourceFile, ref: Identifier, changes: textChanges.ChangeTracker, exportName: string): void { + const { parent } = ref; + switch (parent.kind) { + case SyntaxKind.PropertyAccessExpression: + // `a.default` --> `a.foo` + changes.replaceNode(importingSourceFile, ref, createIdentifier(exportName)); + break; + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: { + const spec = parent as ImportSpecifier | ExportSpecifier; + // `default as foo` --> `foo`, `default as bar` --> `foo as bar` + changes.replaceNode(importingSourceFile, spec, makeImportSpecifier(exportName, spec.name.text)); + break; + } + case SyntaxKind.ImportClause: { + const clause = parent as ImportClause; + Debug.assert(clause.name === ref); + const spec = makeImportSpecifier(exportName, ref.text); + const { namedBindings } = clause; + if (!namedBindings) { + // `import foo from "./a";` --> `import { foo } from "./a";` + changes.replaceNode(importingSourceFile, ref, createNamedImports([spec])); + } + else if (namedBindings.kind === SyntaxKind.NamespaceImport) { + // `import foo, * as a from "./a";` --> `import * as a from ".a/"; import { foo } from "./a";` + changes.deleteNode(importingSourceFile, ref); + const quotePreference = isStringLiteral(clause.parent.moduleSpecifier) ? quotePreferenceFromString(clause.parent.moduleSpecifier, importingSourceFile) : QuotePreference.Double; + const newImport = makeImport(/*default*/ undefined, [makeImportSpecifier(exportName, ref.text)], clause.parent.moduleSpecifier, quotePreference); + changes.insertNodeAfter(importingSourceFile, clause.parent, newImport); + } + else { + // `import foo, { bar } from "./a"` --> `import { bar, foo } from "./a";` + changes.deleteNode(importingSourceFile, ref); + changes.insertNodeAtEndOfList(importingSourceFile, namedBindings.elements, spec); + } + break; + } + default: + Debug.failBadSyntaxKind(parent); + } + } + + function changeNamedToDefaultImport(importingSourceFile: SourceFile, ref: Identifier, changes: textChanges.ChangeTracker): void { + const { parent } = ref; + switch (parent.kind) { + case SyntaxKind.PropertyAccessExpression: + // `a.foo` --> `a.default` + changes.replaceNode(importingSourceFile, ref, createIdentifier("default")); + break; + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: { + const spec = parent as ImportSpecifier | ExportSpecifier; + if (spec.kind === SyntaxKind.ImportSpecifier) { + // `import { foo } from "./a";` --> `import foo from "./a";` + // `import { foo as bar } from "./a";` --> `import bar from "./a";` + const defaultImport = createIdentifier(spec.name.text); + if (spec.parent.elements.length === 1) { + changes.replaceNode(importingSourceFile, spec.parent, defaultImport); + } + else { + changes.deleteNodeInList(importingSourceFile, spec); + changes.insertNodeBefore(importingSourceFile, spec.parent, defaultImport); + } + } + else { + // `export { foo } from "./a";` --> `export { default as foo } from "./a";` + // `export { foo as bar } from "./a";` --> `export { default as bar } from "./a";` + // `export { foo as default } from "./a";` --> `export { default } from "./a";` + // (Because `export foo from "./a";` isn't valid syntax.) + changes.replaceNode(importingSourceFile, spec, makeExportSpecifier("default", spec.name.text)); + } + break; + } + default: + Debug.failBadSyntaxKind(parent); + } + + } + + function makeImportSpecifier(propertyName: string, name: string): ImportSpecifier { + return createImportSpecifier(propertyName === name ? undefined : createIdentifier(propertyName), createIdentifier(name)); + } + + function makeExportSpecifier(propertyName: string, name: string): ExportSpecifier { + return createExportSpecifier(propertyName === name ? undefined : createIdentifier(propertyName), createIdentifier(name)); + } +} diff --git a/src/services/refactors/convertImport.ts b/src/services/refactors/convertImport.ts index bdd3e7f4a9684..cfdd9baffde52 100644 --- a/src/services/refactors/convertImport.ts +++ b/src/services/refactors/convertImport.ts @@ -1,5 +1,5 @@ /* @internal */ -namespace ts.refactor.generateGetAccessorAndSetAccessor { +namespace ts.refactor { const refactorName = "Convert import"; const actionNameNamespaceToNamed = "Convert namespace import to named imports"; const actionNameNamedToNamespace = "Convert named imports to namespace import"; diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index c37969f19c245..ba0d14a4cf4b7 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -397,6 +397,9 @@ namespace ts.textChanges { else if (isParameter(before)) { return {}; } + else if (isStringLiteral(before) && isImportDeclaration(before.parent) || isNamedImports(before)) { + return { suffix: ", " }; + } return Debug.failBadSyntaxKind(before); // We haven't handled this kind of node yet -- add it } @@ -465,6 +468,10 @@ namespace ts.textChanges { this.insertNodeAt(sourceFile, endPosition, newNode, this.getInsertNodeAfterOptions(sourceFile, after)); } + public insertNodeAtEndOfList(sourceFile: SourceFile, list: NodeArray, newNode: Node): void { + this.insertNodeAt(sourceFile, list.end, newNode, { prefix: ", " }); + } + public insertNodesAfter(sourceFile: SourceFile, after: Node, newNodes: ReadonlyArray): void { const endPosition = this.insertNodeAfterWorker(sourceFile, after, first(newNodes)); this.insertNodesAt(sourceFile, endPosition, newNodes, this.getInsertNodeAfterOptions(sourceFile, after)); @@ -505,6 +512,9 @@ namespace ts.textChanges { else if (isParameter(node)) { return {}; } + else if (node.kind === SyntaxKind.ExportKeyword) { + return { prefix: " " }; + } return Debug.failBadSyntaxKind(node); // We haven't handled this kind of node yet -- add it } diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 90b77e537dafe..a631ac0d11e16 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -73,6 +73,7 @@ "codefixes/useDefaultImport.ts", "codefixes/fixAddModuleReferTypeMissingTypeof.ts", "codefixes/convertToMappedObjectType.ts", + "refactors/convertExport.ts", "refactors/convertImport.ts", "refactors/extractSymbol.ts", "refactors/generateGetAccessorAndSetAccessor.ts", diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 95c677ce2877e..3852cf0af1230 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1277,13 +1277,17 @@ namespace ts { export const enum QuotePreference { Single, Double } + export function quotePreferenceFromString(str: StringLiteral, sourceFile: SourceFile): QuotePreference { + return isStringDoubleQuoted(str, sourceFile) ? QuotePreference.Double : QuotePreference.Single; + } + export function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference { if (preferences.quotePreference) { return preferences.quotePreference === "single" ? QuotePreference.Single : QuotePreference.Double; } else { - const firstModuleSpecifier = firstOrUndefined(sourceFile.imports); - return !!firstModuleSpecifier && !isStringDoubleQuoted(firstModuleSpecifier, sourceFile) ? QuotePreference.Single : QuotePreference.Double; + const firstModuleSpecifier = sourceFile.imports && find(sourceFile.imports, isStringLiteral); + return firstModuleSpecifier ? quotePreferenceFromString(firstModuleSpecifier, sourceFile) : QuotePreference.Double; } } @@ -1379,6 +1383,10 @@ namespace ts { return textSpanContainsPosition(span, node.getStart(file)) && node.getEnd() <= textSpanEnd(span); } + + export function findModifier(node: Node, kind: Modifier["kind"]): Modifier | undefined { + return node.modifiers && find(node.modifiers, m => m.kind === kind); + } } // Display-part writer helpers diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index a104e2fde0fdc..d5313506cbfe1 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -178,7 +178,7 @@ declare namespace FourSlashInterface { isInCommentAtPosition(onlyMultiLineDiverges?: boolean): void; codeFix(options: { description: string, - newFileContent?: string | { readonly [fileName: string]: string }, + newFileContent?: NewFileContent, newRangeContent?: string, errorCode?: number, index?: number, @@ -190,6 +190,7 @@ declare namespace FourSlashInterface { applicableRefactorAvailableForRange(): void; refactorAvailable(name: string, actionName?: string): void; + refactorsAvailable(names: ReadonlyArray): void; refactor(options: { name: string; actionName: string; @@ -335,7 +336,7 @@ declare namespace FourSlashInterface { getEditsForFileRename(options: { oldPath: string; newPath: string; - newFileContents: { [fileName: string]: string }; + newFileContents: { readonly [fileName: string]: string }; }): void; moveToNewFile(options: { readonly newFileContents: { readonly [fileName: string]: string }; @@ -356,7 +357,7 @@ declare namespace FourSlashInterface { enableFormatting(): void; disableFormatting(): void; - applyRefactor(options: { refactorName: string, actionName: string, actionDescription: string, newContent: string }): void; + applyRefactor(options: { refactorName: string, actionName: string, actionDescription: string, newContent: NewFileContent }): void; } class debug { printCurrentParameterHelp(): void; @@ -568,6 +569,7 @@ declare namespace FourSlashInterface { } type ArrayOrSingle = T | ReadonlyArray; + type NewFileContent = string | { readonly [fileName: string]: string }; } declare function verifyOperationIsCancelled(f: any): void; declare var test: FourSlashInterface.test_; diff --git a/tests/cases/fourslash/refactorConvertExport_defaultToNamed.ts b/tests/cases/fourslash/refactorConvertExport_defaultToNamed.ts new file mode 100644 index 0000000000000..52d259e841dfd --- /dev/null +++ b/tests/cases/fourslash/refactorConvertExport_defaultToNamed.ts @@ -0,0 +1,41 @@ +/// + +// @Filename: /a.ts +/////*a*/export default function f() {}/*b*/ + +// @Filename: /b.ts +////import f from "./a"; +////import { default as f } from "./a"; +////import { default as g } from "./a"; +////import f, * as a from "./a"; // TODO: GH#24875 +//// +////export { default } from "./a"; +////export { default as f } from "./a"; +////export { default as i } from "./a"; +//// +////import * as a from "./a"; +////a.default(); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert export", + actionName: "Convert default export to named export", + actionDescription: "Convert default export to named export", + newContent: { + "/a.ts": +`export function f() {}`, + + "/b.ts": +`import { f } from "./a"; +import { f } from "./a"; +import { f as g } from "./a"; +import f, * as a from "./a"; // TODO: GH#24875 + +export { f as default } from "./a"; +export { f } from "./a"; +export { f as i } from "./a"; + +import * as a from "./a"; +a.f();`, +}, +}); diff --git a/tests/cases/fourslash/refactorConvertExport_namedToDefault.ts b/tests/cases/fourslash/refactorConvertExport_namedToDefault.ts new file mode 100644 index 0000000000000..4e179d3c79329 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertExport_namedToDefault.ts @@ -0,0 +1,39 @@ +/// + +// @Filename: /a.ts +/////*a*/export function f() {}/*b*/ + +// @Filename: /b.ts +////import { f } from "./a"; +////import { f as g } from "./a"; +////import { f, other } from "./a"; +//// +////export { f } from "./a"; +////export { f as i } from "./a"; +////export { f as default } from "./a"; +//// +////import * as a from "./a"; +////a.f(); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert export", + actionName: "Convert named export to default export", + actionDescription: "Convert named export to default export", + newContent: { + "/a.ts": +`export default function f() {}`, + + "/b.ts": +`import f from "./a"; +import g from "./a"; +import f, { other } from "./a"; + +export { default as f } from "./a"; +export { default as i } from "./a"; +export { default } from "./a"; + +import * as a from "./a"; +a.default();`, +}, +}); diff --git a/tests/cases/fourslash/refactorConvertExport_namedToDefault_alreadyHasDefault.ts b/tests/cases/fourslash/refactorConvertExport_namedToDefault_alreadyHasDefault.ts new file mode 100644 index 0000000000000..1e9faf5bd5ca8 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertExport_namedToDefault_alreadyHasDefault.ts @@ -0,0 +1,8 @@ +/// + +// @Filename: /a.ts +/////*a*/export function f() {}/*b*/ +////export default function g() {} + +goTo.select("a", "b"); +verify.refactorsAvailable([]); From b75f9c0c5c8f5dbc31efbe3960a60e008435bcb9 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 14 Jun 2018 11:30:38 -0700 Subject: [PATCH 2/4] Support ambient module --- src/services/refactors/convertExport.ts | 17 ++++++++----- .../refactorConvertExport_ambientModule.ts | 25 +++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 tests/cases/fourslash/refactorConvertExport_ambientModule.ts diff --git a/src/services/refactors/convertExport.ts b/src/services/refactors/convertExport.ts index ff808da48692b..6afe70ae11dba 100644 --- a/src/services/refactors/convertExport.ts +++ b/src/services/refactors/convertExport.ts @@ -22,6 +22,7 @@ namespace ts.refactor { interface Info { readonly exportNode: ExportToConvert; readonly wasDefault: boolean; + readonly exportingModuleSymbol: Symbol; } function getInfo(context: RefactorContext): Info | undefined { @@ -29,12 +30,16 @@ namespace ts.refactor { const span = getRefactorContextSpan(context); const token = getTokenAtPosition(file, span.start, /*includeJsDocComment*/ false); const exportNode = getParentNodeInSpan(token, file, span); - if (!exportNode || exportNode.parent !== file) return undefined; + if (!exportNode || (!isSourceFile(exportNode.parent) && !(isModuleBlock(exportNode.parent) && isAmbientModule(exportNode.parent.parent)))) { + return undefined; + } + + const exportingModuleSymbol = isSourceFile(exportNode.parent) ? exportNode.parent.symbol : exportNode.parent.parent.symbol; const flags = getModifierFlags(exportNode); const wasDefault = !!(flags & ModifierFlags.Default); // If source file already has a default export, don't offer refactor. - if (!(flags & ModifierFlags.Export) || !wasDefault && file.symbol.exports!.has(InternalSymbolName.Default)) { + if (!(flags & ModifierFlags.Export) || !wasDefault && exportingModuleSymbol.exports!.has(InternalSymbolName.Default)) { return undefined; } @@ -44,7 +49,7 @@ namespace ts.refactor { case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.ModuleDeclaration: - return isIdentifier((exportNode as ExportToConvert).name) ? { exportNode: exportNode as ExportToConvert, wasDefault } : undefined; + return isIdentifier((exportNode as ExportToConvert).name) ? { exportNode: exportNode as ExportToConvert, wasDefault, exportingModuleSymbol } : undefined; default: return undefined; } @@ -52,7 +57,7 @@ namespace ts.refactor { function doChange(exportingSourceFile: SourceFile, program: Program, info: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { changeExport(exportingSourceFile, info, changes); - changeImports(exportingSourceFile, program, info, changes, cancellationToken); + changeImports(program, info, changes, cancellationToken); } function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode }: Info, changes: textChanges.ChangeTracker): void { @@ -64,11 +69,11 @@ namespace ts.refactor { } } - function changeImports(exportingSourceFile: SourceFile, program: Program, { wasDefault, exportNode }: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { + function changeImports(program: Program, { wasDefault, exportNode, exportingModuleSymbol }: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { const checker = program.getTypeChecker(); const exportSymbol = Debug.assertDefined(checker.getSymbolAtLocation(exportNode.name)); const exportName = exportNode.name.text; - FindAllReferences.Core.eachExportReference(program.getSourceFiles(), checker, cancellationToken, exportSymbol, exportingSourceFile.symbol, exportName, wasDefault, ref => { + FindAllReferences.Core.eachExportReference(program.getSourceFiles(), checker, cancellationToken, exportSymbol, exportingModuleSymbol, exportName, wasDefault, ref => { const importingSourceFile = ref.getSourceFile(); if (wasDefault) { changeDefaultToNamedImport(importingSourceFile, ref, changes, exportName); diff --git a/tests/cases/fourslash/refactorConvertExport_ambientModule.ts b/tests/cases/fourslash/refactorConvertExport_ambientModule.ts new file mode 100644 index 0000000000000..a0e39c7a2514b --- /dev/null +++ b/tests/cases/fourslash/refactorConvertExport_ambientModule.ts @@ -0,0 +1,25 @@ +/// + +// @Filename: /foo.ts +////declare module "foo" { +//// /*a*/export default function foo(): void;/*b*/ +////} + +// @Filename: /b.ts +////import foo from "foo"; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert export", + actionName: "Convert default export to named export", + actionDescription: "Convert default export to named export", + newContent: { + "/foo.ts": +`declare module "foo" { + export function foo(): void; +}`, + + "/b.ts": +`import { foo } from "foo";`, +}, +}); From da4cc60b0d3677569703c35ed025d2c0289e408e Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 14 Jun 2018 16:38:08 -0700 Subject: [PATCH 3/4] Handle declaration kinds that can't be default-exported --- src/harness/fourslash.ts | 10 +++ src/services/refactors/convertExport.ts | 61 +++++++++++--- src/services/textChanges.ts | 4 + tests/cases/fourslash/fourslash.ts | 1 + .../refactorConvertExport_exportNodeKinds.ts | 79 +++++++++++++++++++ 5 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 tests/cases/fourslash/refactorConvertExport_exportNodeKinds.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 84ba9ecc2aabe..ded7d5d176fdc 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -451,6 +451,12 @@ namespace FourSlash { this.selectionEnd = end.position; } + public selectAllInFile(fileName: string) { + this.openFile(fileName); + this.goToPosition(0); + this.selectionEnd = this.activeFile.content.length; + } + public selectRange(range: Range): void { this.goToRangeStart(range); this.selectionEnd = range.end; @@ -3969,6 +3975,10 @@ namespace FourSlashInterface { this.state.select(startMarker, endMarker); } + public selectAllInFile(fileName: string) { + this.state.selectAllInFile(fileName); + } + public selectRange(range: FourSlash.Range): void { this.state.selectRange(range); } diff --git a/src/services/refactors/convertExport.ts b/src/services/refactors/convertExport.ts index 6afe70ae11dba..6c7710c0fe857 100644 --- a/src/services/refactors/convertExport.ts +++ b/src/services/refactors/convertExport.ts @@ -18,9 +18,11 @@ namespace ts.refactor { }, }); - type ExportToConvert = (FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration) & { readonly name: Identifier }; + // If a VariableStatement, will have exactly one VariableDeclaration, with an Identifier for a name. + type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement; interface Info { readonly exportNode: ExportToConvert; + readonly exportName: Identifier; // This is exportNode.name except for VariableStatement_s. readonly wasDefault: boolean; readonly exportingModuleSymbol: Symbol; } @@ -48,35 +50,72 @@ namespace ts.refactor { case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: - case SyntaxKind.ModuleDeclaration: - return isIdentifier((exportNode as ExportToConvert).name) ? { exportNode: exportNode as ExportToConvert, wasDefault, exportingModuleSymbol } : undefined; + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ModuleDeclaration: { + const node = exportNode as FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | TypeAliasDeclaration | NamespaceDeclaration; + return node.name && isIdentifier(node.name) ? { exportNode: node, exportName: node.name, wasDefault, exportingModuleSymbol } : undefined; + } + case SyntaxKind.VariableStatement: { + const vs = exportNode as VariableStatement; + // Must be `export const x = something;`. + if (!(vs.declarationList.flags & NodeFlags.Const) || vs.declarationList.declarations.length !== 1) { + return undefined; + } + const decl = first(vs.declarationList.declarations); + if (!decl.initializer) return undefined; + Debug.assert(!wasDefault); + return isIdentifier(decl.name) ? { exportNode: vs, exportName: decl.name, wasDefault, exportingModuleSymbol } : undefined; + } default: return undefined; } } function doChange(exportingSourceFile: SourceFile, program: Program, info: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { - changeExport(exportingSourceFile, info, changes); + changeExport(exportingSourceFile, info, changes, program.getTypeChecker()); changeImports(program, info, changes, cancellationToken); } - function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode }: Info, changes: textChanges.ChangeTracker): void { + function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: Info, changes: textChanges.ChangeTracker, checker: TypeChecker): void { if (wasDefault) { changes.deleteNode(exportingSourceFile, Debug.assertDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword))); } else { - changes.insertNodeAfter(exportingSourceFile, Debug.assertDefined(findModifier(exportNode, SyntaxKind.ExportKeyword)), createToken(SyntaxKind.DefaultKeyword)); + const exportKeyword = Debug.assertDefined(findModifier(exportNode, SyntaxKind.ExportKeyword)); + switch (exportNode.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + changes.insertNodeAfter(exportingSourceFile, exportKeyword, createToken(SyntaxKind.DefaultKeyword)); + break; + case SyntaxKind.VariableStatement: + // If 'x' isn't used in this file, `export const x = 0;` --> `export default 0;` + if (!FindAllReferences.Core.isSymbolReferencedInFile(exportName, checker, exportingSourceFile)) { + // We checked in `getInfo` that an initializer exists. + changes.replaceNode(exportingSourceFile, exportNode, createExportDefault(Debug.assertDefined(first(exportNode.declarationList.declarations).initializer))); + break; + } + // falls through + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ModuleDeclaration: + // `export type T = number;` -> `type T = number; export default T;` + changes.deleteModifier(exportingSourceFile, exportKeyword); + changes.insertNodeAfter(exportingSourceFile, exportNode, createExportDefault(createIdentifier(exportName.text))); + break; + default: + Debug.assertNever(exportNode); + } } } - function changeImports(program: Program, { wasDefault, exportNode, exportingModuleSymbol }: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { + function changeImports(program: Program, { wasDefault, exportName, exportingModuleSymbol }: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { const checker = program.getTypeChecker(); - const exportSymbol = Debug.assertDefined(checker.getSymbolAtLocation(exportNode.name)); - const exportName = exportNode.name.text; - FindAllReferences.Core.eachExportReference(program.getSourceFiles(), checker, cancellationToken, exportSymbol, exportingModuleSymbol, exportName, wasDefault, ref => { + const exportSymbol = Debug.assertDefined(checker.getSymbolAtLocation(exportName)); + FindAllReferences.Core.eachExportReference(program.getSourceFiles(), checker, cancellationToken, exportSymbol, exportingModuleSymbol, exportName.text, wasDefault, ref => { const importingSourceFile = ref.getSourceFile(); if (wasDefault) { - changeDefaultToNamedImport(importingSourceFile, ref, changes, exportName); + changeDefaultToNamedImport(importingSourceFile, ref, changes, exportName.text); } else { changeNamedToDefaultImport(importingSourceFile, ref, changes); diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index ba0d14a4cf4b7..0f97ca2e800d6 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -241,6 +241,10 @@ namespace ts.textChanges { return this; } + public deleteModifier(sourceFile: SourceFile, modifier: Modifier): void { + this.deleteRange(sourceFile, { pos: modifier.getStart(sourceFile), end: skipTrivia(sourceFile.text, modifier.end, /*stopAfterLineBreak*/ true) }); + } + public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}) { const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart); const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index d5313506cbfe1..78adfc9e7609d 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -139,6 +139,7 @@ declare namespace FourSlashInterface { file(name: string, content?: string, scriptKindName?: string): any; select(startMarker: string, endMarker: string): void; selectRange(range: Range): void; + selectAllInFile(fileName: string): void; } class verifyNegatable { private negative; diff --git a/tests/cases/fourslash/refactorConvertExport_exportNodeKinds.ts b/tests/cases/fourslash/refactorConvertExport_exportNodeKinds.ts new file mode 100644 index 0000000000000..4e73f8e7c9f78 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertExport_exportNodeKinds.ts @@ -0,0 +1,79 @@ +/// + +// @Filename: /fn.ts +////export function f() {} + +// @Filename: /cls.ts +////export class C {} + +// @Filename: /interface.ts +////export interface I {} + +// @Filename: /enum.ts +////export const enum E {} + +// @Filename: /namespace.ts +////export namespace N {} + +// @Filename: /type.ts +////export type T = number; + +// @Filename: /var_unused.ts +////export const x = 0; + +// @Filename: /var_unused_noInitializer.ts +////export const x; + +// @Filename: /var_used.ts +////export const x = 0; +////x; + +const tests: { [fileName: string]: string | undefined } = { + fn: `export default function f() {}`, + + cls: `export default class C {}`, + + interface: `export default interface I {}`, + + enum: +`const enum E {} +export default E; +`, + + namespace: +`namespace N {} + +export default N; +`, + + type: +`type T = number; +export default T; +`, + + var_unused: `export default 0;`, + + var_unused_noInitializer: undefined, + + var_used: +`const x = 0; +export default x; +x;`, +}; + +for (const name in tests) { + const newContent = tests[name]; + const fileName = `/${name}.ts`; + goTo.selectAllInFile(fileName); + if (newContent === undefined) { + verify.refactorsAvailable([]); + } + else { + edit.applyRefactor({ + refactorName: "Convert export", + actionName: "Convert named export to default export", + actionDescription: "Convert named export to default export", + newContent: { [fileName]: newContent }, + }); + } +} From 65b441f290d1145a2495da65844613caecb29c83 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 15 Jun 2018 09:01:23 -0700 Subject: [PATCH 4/4] Update API (#24966) --- .../baselines/reference/api/tsserverlibrary.d.ts | 15 ++++++++++++--- tests/baselines/reference/api/typescript.d.ts | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index b2ec88acf71a0..4fef8881db846 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5908,6 +5908,8 @@ declare namespace ts { Add_or_remove_braces_in_an_arrow_function: DiagnosticMessage; Add_braces_to_arrow_function: DiagnosticMessage; Remove_braces_from_arrow_function: DiagnosticMessage; + Convert_default_export_to_named_export: DiagnosticMessage; + Convert_named_export_to_default_export: DiagnosticMessage; }; } declare namespace ts { @@ -6941,7 +6943,7 @@ declare namespace ts { function isTemplateMiddleOrTemplateTail(node: Node): node is TemplateMiddle | TemplateTail; function isStringTextContainingNode(node: Node): node is StringLiteral | TemplateLiteralToken; function isGeneratedIdentifier(node: Node): node is GeneratedIdentifier; - function isModifierKind(token: SyntaxKind): boolean; + function isModifierKind(token: SyntaxKind): token is Modifier["kind"]; function isParameterPropertyModifier(kind: SyntaxKind): boolean; function isClassMemberModifier(idToken: SyntaxKind): boolean; function isModifier(node: Node): node is Modifier; @@ -10586,6 +10588,7 @@ declare namespace ts { Single = 0, Double = 1 } + function quotePreferenceFromString(str: StringLiteral, sourceFile: SourceFile): QuotePreference; function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference; function symbolNameNoDefault(symbol: Symbol): string | undefined; function symbolEscapedNameNoDefault(symbol: Symbol): __String | undefined; @@ -10610,6 +10613,7 @@ declare namespace ts { some(pred: (node: Node) => boolean): boolean; } function getParentNodeInSpan(node: Node | undefined, file: SourceFile, span: TextSpan): Node | undefined; + function findModifier(node: Node, kind: Modifier["kind"]): Modifier | undefined; function insertImport(changes: textChanges.ChangeTracker, sourceFile: SourceFile, importDecl: Statement): void; } declare namespace ts { @@ -10787,7 +10791,7 @@ declare namespace ts.FindAllReferences { } type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult; /** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */ - function createImportTracker(sourceFiles: ReadonlyArray, sourceFilesSet: ReadonlyMap, checker: TypeChecker, cancellationToken: CancellationToken): ImportTracker; + function createImportTracker(sourceFiles: ReadonlyArray, sourceFilesSet: ReadonlyMap, checker: TypeChecker, cancellationToken: CancellationToken | undefined): ImportTracker; /** Info about an exported symbol to perform recursive search on. */ interface ExportInfo { exportingModuleSymbol: Symbol; @@ -10890,6 +10894,7 @@ declare namespace ts.FindAllReferences { declare namespace ts.FindAllReferences.Core { /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken, options?: Options, sourceFilesSet?: ReadonlyMap): SymbolAndEntries[] | undefined; + function eachExportReference(sourceFiles: ReadonlyArray, checker: TypeChecker, cancellationToken: CancellationToken | undefined, exportSymbol: Symbol, exportingModuleSymbol: Symbol, exportName: string, isDefaultExport: boolean, cb: (ref: Identifier) => void): void; /** Used as a quick check for whether a symbol is used at all in a file (besides its definition). */ function isSymbolReferencedInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile): boolean; function eachSymbolReferenceInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T): T | undefined; @@ -11275,6 +11280,7 @@ declare namespace ts.textChanges { deleteRange(sourceFile: SourceFile, range: TextRange): this; /** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */ deleteNode(sourceFile: SourceFile, node: Node, options?: ConfigurableStartEnd): this; + deleteModifier(sourceFile: SourceFile, modifier: Modifier): void; deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options?: ConfigurableStartEnd): this; deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options?: ConfigurableStartEnd): void; deleteNodeInList(sourceFile: SourceFile, node: Node): this; @@ -11306,6 +11312,7 @@ declare namespace ts.textChanges { private getInsertNodeAtClassStartPrefixSuffix; insertNodeAfterComma(sourceFile: SourceFile, after: Node, newNode: Node): void; insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node): void; + insertNodeAtEndOfList(sourceFile: SourceFile, list: NodeArray, newNode: Node): void; insertNodesAfter(sourceFile: SourceFile, after: Node, newNodes: ReadonlyArray): void; private insertNodeAfterWorker; private getInsertNodeAfterOptions; @@ -11463,7 +11470,9 @@ declare namespace ts.codefix { } declare namespace ts.codefix { } -declare namespace ts.refactor.generateGetAccessorAndSetAccessor { +declare namespace ts.refactor { +} +declare namespace ts.refactor { } declare namespace ts.refactor.extractSymbol { /** diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 181f0d72d1069..9d955961d0a33 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5908,6 +5908,8 @@ declare namespace ts { Add_or_remove_braces_in_an_arrow_function: DiagnosticMessage; Add_braces_to_arrow_function: DiagnosticMessage; Remove_braces_from_arrow_function: DiagnosticMessage; + Convert_default_export_to_named_export: DiagnosticMessage; + Convert_named_export_to_default_export: DiagnosticMessage; }; } declare namespace ts { @@ -6941,7 +6943,7 @@ declare namespace ts { function isTemplateMiddleOrTemplateTail(node: Node): node is TemplateMiddle | TemplateTail; function isStringTextContainingNode(node: Node): node is StringLiteral | TemplateLiteralToken; function isGeneratedIdentifier(node: Node): node is GeneratedIdentifier; - function isModifierKind(token: SyntaxKind): boolean; + function isModifierKind(token: SyntaxKind): token is Modifier["kind"]; function isParameterPropertyModifier(kind: SyntaxKind): boolean; function isClassMemberModifier(idToken: SyntaxKind): boolean; function isModifier(node: Node): node is Modifier; @@ -10766,6 +10768,7 @@ declare namespace ts { Single = 0, Double = 1 } + function quotePreferenceFromString(str: StringLiteral, sourceFile: SourceFile): QuotePreference; function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference; function symbolNameNoDefault(symbol: Symbol): string | undefined; function symbolEscapedNameNoDefault(symbol: Symbol): __String | undefined; @@ -10790,6 +10793,7 @@ declare namespace ts { some(pred: (node: Node) => boolean): boolean; } function getParentNodeInSpan(node: Node | undefined, file: SourceFile, span: TextSpan): Node | undefined; + function findModifier(node: Node, kind: Modifier["kind"]): Modifier | undefined; function insertImport(changes: textChanges.ChangeTracker, sourceFile: SourceFile, importDecl: Statement): void; } declare namespace ts { @@ -10967,7 +10971,7 @@ declare namespace ts.FindAllReferences { } type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult; /** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */ - function createImportTracker(sourceFiles: ReadonlyArray, sourceFilesSet: ReadonlyMap, checker: TypeChecker, cancellationToken: CancellationToken): ImportTracker; + function createImportTracker(sourceFiles: ReadonlyArray, sourceFilesSet: ReadonlyMap, checker: TypeChecker, cancellationToken: CancellationToken | undefined): ImportTracker; /** Info about an exported symbol to perform recursive search on. */ interface ExportInfo { exportingModuleSymbol: Symbol; @@ -11070,6 +11074,7 @@ declare namespace ts.FindAllReferences { declare namespace ts.FindAllReferences.Core { /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken, options?: Options, sourceFilesSet?: ReadonlyMap): SymbolAndEntries[] | undefined; + function eachExportReference(sourceFiles: ReadonlyArray, checker: TypeChecker, cancellationToken: CancellationToken | undefined, exportSymbol: Symbol, exportingModuleSymbol: Symbol, exportName: string, isDefaultExport: boolean, cb: (ref: Identifier) => void): void; /** Used as a quick check for whether a symbol is used at all in a file (besides its definition). */ function isSymbolReferencedInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile): boolean; function eachSymbolReferenceInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T): T | undefined; @@ -11455,6 +11460,7 @@ declare namespace ts.textChanges { deleteRange(sourceFile: SourceFile, range: TextRange): this; /** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */ deleteNode(sourceFile: SourceFile, node: Node, options?: ConfigurableStartEnd): this; + deleteModifier(sourceFile: SourceFile, modifier: Modifier): void; deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options?: ConfigurableStartEnd): this; deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options?: ConfigurableStartEnd): void; deleteNodeInList(sourceFile: SourceFile, node: Node): this; @@ -11486,6 +11492,7 @@ declare namespace ts.textChanges { private getInsertNodeAtClassStartPrefixSuffix; insertNodeAfterComma(sourceFile: SourceFile, after: Node, newNode: Node): void; insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node): void; + insertNodeAtEndOfList(sourceFile: SourceFile, list: NodeArray, newNode: Node): void; insertNodesAfter(sourceFile: SourceFile, after: Node, newNodes: ReadonlyArray): void; private insertNodeAfterWorker; private getInsertNodeAfterOptions; @@ -11643,7 +11650,9 @@ declare namespace ts.codefix { } declare namespace ts.codefix { } -declare namespace ts.refactor.generateGetAccessorAndSetAccessor { +declare namespace ts.refactor { +} +declare namespace ts.refactor { } declare namespace ts.refactor.extractSymbol { /**