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:
Andrew Branch
2022-11-28 15:35:32 -08:00
committed by GitHub
parent 0c60da9288
commit e6d7b526c8
9 changed files with 179 additions and 95 deletions

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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.