mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-07-02 14:48:32 -05:00
Fix multiline import specifier sorting (#51634)
* Fix multiline import specifier sorting * Update baselines * Switch to EmitFlag, set hasTrailingComma on original node array * Update API baseline * Update baselines
This commit is contained in:
@@ -4862,7 +4862,14 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
|
||||
}
|
||||
|
||||
function emitList(parentNode: Node | undefined, children: NodeArray<Node> | undefined, format: ListFormat, parenthesizerRule?: ParenthesizerRuleOrSelector<Node>, start?: number, count?: number) {
|
||||
emitNodeList(emit, parentNode, children, format, parenthesizerRule, start, count);
|
||||
emitNodeList(
|
||||
emit,
|
||||
parentNode,
|
||||
children,
|
||||
format | (parentNode && getEmitFlags(parentNode) & EmitFlags.MultiLine ? ListFormat.PreferNewLine : 0),
|
||||
parenthesizerRule,
|
||||
start,
|
||||
count);
|
||||
}
|
||||
|
||||
function emitExpressionList(parentNode: Node | undefined, children: NodeArray<Node> | undefined, format: ListFormat, parenthesizerRule?: ParenthesizerRuleOrSelector<Expression>, start?: number, count?: number) {
|
||||
|
||||
@@ -7490,7 +7490,7 @@ export interface EmitNode {
|
||||
helpers?: EmitHelper[]; // Emit helpers for the node
|
||||
startsOnNewLine?: boolean; // If the node should begin on a new line
|
||||
snippetElement?: SnippetElement; // Snippet element of the node
|
||||
typeNode?: TypeNode; // VariableDeclaration type
|
||||
typeNode?: TypeNode; // VariableDeclaration type
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -7520,38 +7520,39 @@ export const enum SnippetKind {
|
||||
export const enum EmitFlags {
|
||||
None = 0,
|
||||
SingleLine = 1 << 0, // The contents of this node should be emitted on a single line.
|
||||
AdviseOnEmitNode = 1 << 1, // The printer should invoke the onEmitNode callback when printing this node.
|
||||
NoSubstitution = 1 << 2, // Disables further substitution of an expression.
|
||||
CapturesThis = 1 << 3, // The function captures a lexical `this`
|
||||
NoLeadingSourceMap = 1 << 4, // Do not emit a leading source map location for this node.
|
||||
NoTrailingSourceMap = 1 << 5, // Do not emit a trailing source map location for this node.
|
||||
MultiLine = 1 << 1,
|
||||
AdviseOnEmitNode = 1 << 2, // The printer should invoke the onEmitNode callback when printing this node.
|
||||
NoSubstitution = 1 << 3, // Disables further substitution of an expression.
|
||||
CapturesThis = 1 << 4, // The function captures a lexical `this`
|
||||
NoLeadingSourceMap = 1 << 5, // Do not emit a leading source map location for this node.
|
||||
NoTrailingSourceMap = 1 << 6, // Do not emit a trailing source map location for this node.
|
||||
NoSourceMap = NoLeadingSourceMap | NoTrailingSourceMap, // Do not emit a source map location for this node.
|
||||
NoNestedSourceMaps = 1 << 6, // Do not emit source map locations for children of this node.
|
||||
NoTokenLeadingSourceMaps = 1 << 7, // Do not emit leading source map location for token nodes.
|
||||
NoTokenTrailingSourceMaps = 1 << 8, // Do not emit trailing source map location for token nodes.
|
||||
NoNestedSourceMaps = 1 << 7, // Do not emit source map locations for children of this node.
|
||||
NoTokenLeadingSourceMaps = 1 << 8, // Do not emit leading source map location for token nodes.
|
||||
NoTokenTrailingSourceMaps = 1 << 9, // Do not emit trailing source map location for token nodes.
|
||||
NoTokenSourceMaps = NoTokenLeadingSourceMaps | NoTokenTrailingSourceMaps, // Do not emit source map locations for tokens of this node.
|
||||
NoLeadingComments = 1 << 9, // Do not emit leading comments for this node.
|
||||
NoTrailingComments = 1 << 10, // Do not emit trailing comments for this node.
|
||||
NoLeadingComments = 1 << 10, // Do not emit leading comments for this node.
|
||||
NoTrailingComments = 1 << 11, // Do not emit trailing comments for this node.
|
||||
NoComments = NoLeadingComments | NoTrailingComments, // Do not emit comments for this node.
|
||||
NoNestedComments = 1 << 11,
|
||||
HelperName = 1 << 12, // The Identifier refers to an *unscoped* emit helper (one that is emitted at the top of the file)
|
||||
ExportName = 1 << 13, // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal).
|
||||
LocalName = 1 << 14, // Ensure an export prefix is not added for an identifier that points to an exported declaration.
|
||||
InternalName = 1 << 15, // The name is internal to an ES5 class body function.
|
||||
Indented = 1 << 16, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter).
|
||||
NoIndentation = 1 << 17, // Do not indent the node.
|
||||
AsyncFunctionBody = 1 << 18,
|
||||
ReuseTempVariableScope = 1 << 19, // Reuse the existing temp variable scope during emit.
|
||||
CustomPrologue = 1 << 20, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed).
|
||||
NoHoisting = 1 << 21, // Do not hoist this declaration in --module system
|
||||
HasEndOfDeclarationMarker = 1 << 22, // Declaration has an associated NotEmittedStatement to mark the end of the declaration
|
||||
Iterator = 1 << 23, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable.
|
||||
NoAsciiEscaping = 1 << 24, // When synthesizing nodes that lack an original node or textSourceNode, we want to write the text on the node with ASCII escaping substitutions.
|
||||
/** @internal */ TypeScriptClassWrapper = 1 << 25, // The node is an IIFE class wrapper created by the ts transform.
|
||||
/** @internal */ NeverApplyImportHelper = 1 << 26, // Indicates the node should never be wrapped with an import star helper (because, for example, it imports tslib itself)
|
||||
/** @internal */ IgnoreSourceNewlines = 1 << 27, // Overrides `printerOptions.preserveSourceNewlines` to print this node (and all descendants) with default whitespace.
|
||||
/** @internal */ Immutable = 1 << 28, // Indicates a node is a singleton intended to be reused in multiple locations. Any attempt to make further changes to the node will result in an error.
|
||||
/** @internal */ IndirectCall = 1 << 29, // Emit CallExpression as an indirect call: `(0, f)()`
|
||||
NoNestedComments = 1 << 12,
|
||||
HelperName = 1 << 13, // The Identifier refers to an *unscoped* emit helper (one that is emitted at the top of the file)
|
||||
ExportName = 1 << 14, // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal).
|
||||
LocalName = 1 << 15, // Ensure an export prefix is not added for an identifier that points to an exported declaration.
|
||||
InternalName = 1 << 16, // The name is internal to an ES5 class body function.
|
||||
Indented = 1 << 17, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter).
|
||||
NoIndentation = 1 << 18, // Do not indent the node.
|
||||
AsyncFunctionBody = 1 << 19,
|
||||
ReuseTempVariableScope = 1 << 20, // Reuse the existing temp variable scope during emit.
|
||||
CustomPrologue = 1 << 21, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed).
|
||||
NoHoisting = 1 << 22, // Do not hoist this declaration in --module system
|
||||
HasEndOfDeclarationMarker = 1 << 23, // Declaration has an associated NotEmittedStatement to mark the end of the declaration
|
||||
Iterator = 1 << 24, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable.
|
||||
NoAsciiEscaping = 1 << 25, // When synthesizing nodes that lack an original node or textSourceNode, we want to write the text on the node with ASCII escaping substitutions.
|
||||
/** @internal */ TypeScriptClassWrapper = 1 << 26, // The node is an IIFE class wrapper created by the ts transform.
|
||||
/** @internal */ NeverApplyImportHelper = 1 << 27, // Indicates the node should never be wrapped with an import star helper (because, for example, it imports tslib itself)
|
||||
/** @internal */ IgnoreSourceNewlines = 1 << 28, // Overrides `printerOptions.preserveSourceNewlines` to print this node (and all descendants) with default whitespace.
|
||||
/** @internal */ Immutable = 1 << 29, // Indicates a node is a singleton intended to be reused in multiple locations. Any attempt to make further changes to the node will result in an error.
|
||||
/** @internal */ IndirectCall = 1 << 30, // Emit CallExpression as an indirect call: `(0, f)()`
|
||||
}
|
||||
|
||||
export interface EmitHelperBase {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
compareValues,
|
||||
Comparison,
|
||||
createScanner,
|
||||
EmitFlags,
|
||||
emptyArray,
|
||||
ExportDeclaration,
|
||||
ExportSpecifier,
|
||||
@@ -43,7 +44,9 @@ import {
|
||||
NamespaceImport,
|
||||
OrganizeImportsMode,
|
||||
Program,
|
||||
rangeIsOnSingleLine,
|
||||
Scanner,
|
||||
setEmitFlags,
|
||||
some,
|
||||
SortedReadonlyArray,
|
||||
SourceFile,
|
||||
@@ -79,7 +82,7 @@ export function organizeImports(
|
||||
const maybeRemove = shouldRemove ? removeUnusedImports : identity;
|
||||
const maybeCoalesce = shouldCombine ? coalesceImports : identity;
|
||||
const processImportsOfSameModuleSpecifier = (importGroup: readonly ImportDeclaration[]) => {
|
||||
const processedDeclarations = maybeCoalesce(maybeRemove(importGroup, sourceFile, program));
|
||||
const processedDeclarations = maybeCoalesce(maybeRemove(importGroup, sourceFile, program), sourceFile);
|
||||
return shouldSort
|
||||
? stableSort(processedDeclarations, (s1, s2) => compareImportsOrRequireStatements(s1, s2))
|
||||
: processedDeclarations;
|
||||
@@ -296,7 +299,7 @@ function getExternalModuleName(specifier: Expression) {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function coalesceImports(importGroup: readonly ImportDeclaration[]) {
|
||||
export function coalesceImports(importGroup: readonly ImportDeclaration[], sourceFile?: SourceFile) {
|
||||
if (importGroup.length === 0) {
|
||||
return importGroup;
|
||||
}
|
||||
@@ -350,7 +353,11 @@ export function coalesceImports(importGroup: readonly ImportDeclaration[]) {
|
||||
|
||||
newImportSpecifiers.push(...getNewImportSpecifiers(namedImports));
|
||||
|
||||
const sortedImportSpecifiers = sortSpecifiers(newImportSpecifiers);
|
||||
const sortedImportSpecifiers = factory.createNodeArray(
|
||||
sortSpecifiers(newImportSpecifiers),
|
||||
(namedImports[0]?.importClause!.namedBindings as NamedImports)?.elements.hasTrailingComma
|
||||
);
|
||||
|
||||
const importDecl = defaultImports.length > 0
|
||||
? defaultImports[0]
|
||||
: namedImports[0];
|
||||
@@ -363,6 +370,14 @@ export function coalesceImports(importGroup: readonly ImportDeclaration[]) {
|
||||
? factory.createNamedImports(sortedImportSpecifiers)
|
||||
: factory.updateNamedImports(namedImports[0].importClause!.namedBindings as NamedImports, sortedImportSpecifiers); // TODO: GH#18217
|
||||
|
||||
if (sourceFile &&
|
||||
newNamedImports &&
|
||||
namedImports[0]?.importClause!.namedBindings &&
|
||||
!rangeIsOnSingleLine(namedImports[0].importClause.namedBindings, sourceFile)
|
||||
) {
|
||||
setEmitFlags(newNamedImports, EmitFlags.MultiLine);
|
||||
}
|
||||
|
||||
// Type-only imports are not allowed to mix default, namespace, and named imports in any combination.
|
||||
// We could rewrite a default import as a named import (`import { default as name }`), but we currently
|
||||
// choose not to as a stylistic preference.
|
||||
|
||||
Reference in New Issue
Block a user