Skip to content

Commit 412a2a5

Browse files
feat: allow reordering of array literal expressions (#1323)
1 parent 74486c6 commit 412a2a5

15 files changed

+575
-216
lines changed

packages/cli/templates/react/ReactTypeScriptFileUpdate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ export class ReactTypeScriptFileUpdate extends TypeScriptFileUpdate {
5454
this.astTransformer.requestNewMembersInArrayLiteral(
5555
variableAsParentCondition(this.astTransformer, ROUTES_VARIABLE_NAME),
5656
[newRoute],
57-
prepend,
58-
anchorElement
57+
anchorElement,
58+
{ prepend }
5959
);
6060
}
6161

packages/cli/templates/webcomponents/WebComponentsTypeScriptFileUpdate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ export class WebComponentsTypeScriptFileUpdate extends TypeScriptFileUpdate {
4343
this.astTransformer.requestNewMembersInArrayLiteral(
4444
variableAsParentCondition(this.astTransformer, ROUTES_VARIABLE_NAME),
4545
[newRoute],
46-
prepend,
47-
anchorElement
46+
anchorElement,
47+
{ prepend }
4848
);
4949
}
5050

packages/core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
"author": "Infragistics",
1313
"license": "MIT",
1414
"dependencies": {
15-
"@inquirer/prompts": "^5.4.0",
15+
"@inquirer/prompts": "~5.4.0",
1616
"chalk": "^2.3.2",
1717
"glob": "^7.1.2",
18-
"through2": "^4.0.2",
18+
"through2": "^2.0.3",
1919
"typescript": "~5.5.4"
2020
},
2121
"devDependencies": {

packages/core/types/types-typescript.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,38 @@ export interface ChangeRequest<T extends ts.Node> {
8484
*/
8585
node: T | ts.NodeArray<T>;
8686
}
87+
88+
/**
89+
* Options that can be applied when modifying a literal expression.
90+
*/
91+
export interface LiteralExpressionOptionsBase {
92+
/**
93+
* Whether the literal should be on multiple lines.
94+
* @remarks This option is only applicable to {@link ts.ObjectLiteralExpression} and {@link ts.ArrayLiteralExpression}.
95+
*/
96+
multiline?: boolean;
97+
}
98+
99+
/**
100+
* Options that can be applied when modifying an {@link ts.ObjectLiteralExpression}.
101+
*/
102+
export interface ObjectLiteralExpressionEditOptions
103+
extends LiteralExpressionOptionsBase {
104+
/**
105+
* Whether to override all elements of the property's initializer.
106+
* @remarks This option is only applicable to {@link ts.PropertyAssignment} with an initializer that is {@link ts.ArrayLiteralExpression}.
107+
* All other initializers will be overridden by default.
108+
*/
109+
override?: boolean;
110+
}
111+
112+
/**
113+
* Options that can be applied when modifying an {@link ts.ArrayLiteralExpression}.
114+
*/
115+
export interface ArrayLiteralExpressionEditOptions
116+
extends LiteralExpressionOptionsBase {
117+
/**
118+
* If any elements should be added at the beginning of an {@link ts.ArrayLiteralExpression}.
119+
*/
120+
prepend?: boolean;
121+
}

packages/core/typescript/TransformerFactories.ts

Lines changed: 65 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import * as ts from 'typescript';
22
import {
3+
ArrayLiteralExpressionEditOptions,
34
Identifier,
45
ImportDeclarationMeta,
6+
ObjectLiteralExpressionEditOptions,
57
PropertyAssignment,
68
} from '../types';
79
import { SIDE_EFFECTS_IMPORT_TEMPLATE_NAME } from '../util';
@@ -26,8 +28,8 @@ import { TypeScriptExpressionCollector } from './TypeScriptExpressionCollector';
2628
export const newMemberInObjectLiteralTransformerFactory = (
2729
newProperty: ts.PropertyAssignment,
2830
visitCondition: (node: ts.Node) => boolean,
29-
multiline: boolean,
30-
expressionCollector: TypeScriptExpressionCollector
31+
expressionCollector: TypeScriptExpressionCollector,
32+
options: ObjectLiteralExpressionEditOptions
3133
): ts.TransformerFactory<ts.SourceFile> => {
3234
return <T extends ts.Node>(context: ts.TransformationContext) => {
3335
return (rootNode: T) => {
@@ -73,7 +75,8 @@ export const newMemberInObjectLiteralTransformerFactory = (
7375
context,
7476
node,
7577
expressionCollector,
76-
multiline
78+
options?.multiline,
79+
options?.override
7780
);
7881
}
7982

@@ -91,53 +94,14 @@ export const newMemberInObjectLiteralTransformerFactory = (
9194
};
9295
};
9396

94-
/**
95-
* Creates a {@link ts.TransformerFactory} that updates a member in a {@link ts.ObjectLiteralExpression}.
96-
*/
97-
export const updateForObjectLiteralMemberTransformerFactory = (
98-
visitCondition: (node: ts.ObjectLiteralExpression) => boolean,
99-
targetMember: PropertyAssignment
100-
): ts.TransformerFactory<ts.SourceFile> => {
101-
return <T extends ts.Node>(context: ts.TransformationContext) => {
102-
return (rootNode: T) => {
103-
const visitor = (node: ts.Node): ts.VisitResult<ts.Node> => {
104-
if (ts.isObjectLiteralExpression(node) && visitCondition(node)) {
105-
const newProperties = node.properties.map((property) => {
106-
const isPropertyAssignment = ts.isPropertyAssignment(property);
107-
if (
108-
isPropertyAssignment &&
109-
ts.isIdentifier(property.name) &&
110-
property.name.text === targetMember.name
111-
) {
112-
return context.factory.updatePropertyAssignment(
113-
property,
114-
property.name,
115-
targetMember.value
116-
);
117-
}
118-
return property;
119-
});
120-
121-
return context.factory.updateObjectLiteralExpression(
122-
node,
123-
newProperties
124-
);
125-
}
126-
return ts.visitEachChild(node, visitor, context);
127-
};
128-
return ts.visitNode(rootNode, visitor, ts.isSourceFile);
129-
};
130-
};
131-
};
132-
13397
/**
13498
* Creates a {@link ts.TransformerFactory} that adds a new element to a {@link ts.ArrayLiteralExpression}.
13599
*/
136100
export const newMemberInArrayLiteralTransformerFactory = (
137101
visitCondition: (node: ts.ArrayLiteralExpression) => boolean,
138102
elements: ts.Expression[],
139-
prepend: boolean = false,
140-
anchorElement?: ts.StringLiteral | ts.NumericLiteral | PropertyAssignment
103+
anchorElement?: ts.StringLiteral | ts.NumericLiteral | PropertyAssignment,
104+
options?: ArrayLiteralExpressionEditOptions
141105
): ts.TransformerFactory<ts.SourceFile> => {
142106
return <T extends ts.Node>(context: ts.TransformationContext) => {
143107
return (rootNode: T) => {
@@ -175,9 +139,14 @@ export const newMemberInArrayLiteralTransformerFactory = (
175139
});
176140
}
177141

142+
/**
143+
* TODO:
144+
* Consider extracting some of the logic to the factory that handles array literals as property initializers and reusing that here.
145+
* The anchor element should be preserved while it should also allow for overriding of the elements, if needed.
146+
*/
178147
if (anchor) {
179148
let structure!: ts.Expression[];
180-
if (prepend) {
149+
if (options?.prepend) {
181150
structure = node.elements
182151
.slice(0, node.elements.indexOf(anchor))
183152
.concat(elements)
@@ -195,7 +164,7 @@ export const newMemberInArrayLiteralTransformerFactory = (
195164
);
196165
}
197166

198-
if (prepend) {
167+
if (options?.prepend) {
199168
return context.factory.updateArrayLiteralExpression(node, [
200169
...elements,
201170
...node.elements,
@@ -213,6 +182,27 @@ export const newMemberInArrayLiteralTransformerFactory = (
213182
};
214183
};
215184

185+
/**
186+
* Creates a {@link ts.TransformerFactory} that sorts the elements in a {@link ts.ArrayLiteralExpression}.
187+
*/
188+
export const sortInArrayLiteralTransformerFactory = (
189+
visitCondition: (node: ts.ArrayLiteralExpression) => boolean,
190+
sortCondition: (a: ts.Expression, b: ts.Expression) => number
191+
) => {
192+
return <T extends ts.Node>(context: ts.TransformationContext) => {
193+
return (rootNode: T) => {
194+
const visitor = (node: ts.Node): ts.VisitResult<ts.Node> => {
195+
if (ts.isArrayLiteralExpression(node) && visitCondition(node)) {
196+
const elements = [...node.elements].sort(sortCondition);
197+
return context.factory.updateArrayLiteralExpression(node, elements);
198+
}
199+
return ts.visitEachChild(node, visitor, context);
200+
};
201+
return ts.visitNode(rootNode, visitor, ts.isSourceFile);
202+
};
203+
};
204+
};
205+
216206
/**
217207
* Creates a {@link ts.TransformerFactory} that adds a new argument to a {@link ts.CallExpression}.
218208
*/
@@ -488,29 +478,7 @@ function updatePropertyAssignmentWithIdentifier(
488478
? newProperty.initializer
489479
: newProperty.objectAssignmentInitializer;
490480

491-
const updatedProperty = ts.isPropertyAssignment(existingProperty)
492-
? context.factory.updatePropertyAssignment(
493-
existingProperty,
494-
existingProperty.name,
495-
newPropInitializer
496-
)
497-
: context.factory.updateShorthandPropertyAssignment(
498-
existingProperty,
499-
existingProperty.name,
500-
newPropInitializer
501-
);
502-
const structure = Array.from(node.properties);
503-
const targetIndex = structure.indexOf(existingProperty);
504-
if (targetIndex > -1) {
505-
// attempt to modify the property assignment and preserve the order
506-
structure[targetIndex] = updatedProperty;
507-
return context.factory.updateObjectLiteralExpression(node, structure);
508-
}
509-
// append the property assignment at the end
510-
return context.factory.updateObjectLiteralExpression(node, [
511-
...node.properties.filter((p) => p !== existingProperty),
512-
updatedProperty,
513-
]);
481+
return updateProperty(node, existingProperty, newPropInitializer, context);
514482
}
515483

516484
/**
@@ -520,14 +488,16 @@ function updatePropertyAssignmentWithIdentifier(
520488
* @param context The transformation context.
521489
* @param node The object literal expression node.
522490
* @param multiline Whether the array literal should be multiline.
491+
* @param override Whether to override all elements if the property's initializer is an array.
523492
*/
524493
function updatePropertyAssignmentWithArrayLiteral(
525494
newProperty: ts.PropertyAssignment | ts.ShorthandPropertyAssignment,
526495
existingProperty: ts.PropertyAssignment | ts.ShorthandPropertyAssignment,
527496
context: ts.TransformationContext,
528497
node: ts.ObjectLiteralExpression,
529498
expressionCollector: TypeScriptExpressionCollector,
530-
multiline: boolean
499+
multiline: boolean,
500+
override: boolean
531501
): ts.ObjectLiteralExpression {
532502
const existingPropInitializer = ts.isPropertyAssignment(existingProperty)
533503
? existingProperty.initializer
@@ -543,25 +513,44 @@ function updatePropertyAssignmentWithArrayLiteral(
543513
const newElements = ts.isArrayLiteralExpression(newPropInitializer)
544514
? [...newPropInitializer.elements]
545515
: [newPropInitializer];
546-
const uniqueElements = expressionCollector.collectUniqueExpressions([
547-
...elements,
548-
...newElements,
549-
]);
516+
const uniqueElements = override
517+
? expressionCollector.collectUniqueExpressions(newElements)
518+
: expressionCollector.collectUniqueExpressions([
519+
...elements,
520+
...newElements,
521+
]);
550522

551523
const valueExpression = context.factory.createArrayLiteralExpression(
552524
uniqueElements,
553525
multiline
554526
);
527+
528+
return updateProperty(node, existingProperty, valueExpression, context);
529+
}
530+
531+
/**
532+
* Updates a {@link ts.PropertyAssignment} with a new {@link ts.Initializer}.
533+
* @param node The object literal expression node.
534+
* @param existingProperty The property to update.
535+
* @param newInitializer The new initializer to set.
536+
* @param context The transformation context.
537+
*/
538+
function updateProperty(
539+
node: ts.ObjectLiteralExpression,
540+
existingProperty: ts.PropertyAssignment | ts.ShorthandPropertyAssignment,
541+
newInitializer: ts.Expression,
542+
context: ts.TransformationContext
543+
): ts.ObjectLiteralExpression {
555544
const updatedProperty = ts.isPropertyAssignment(existingProperty)
556545
? context.factory.updatePropertyAssignment(
557546
existingProperty,
558547
existingProperty.name,
559-
valueExpression
548+
newInitializer
560549
)
561550
: context.factory.updateShorthandPropertyAssignment(
562551
existingProperty,
563552
existingProperty.name,
564-
valueExpression
553+
newInitializer
565554
);
566555

567556
const structure = Array.from(node.properties);

0 commit comments

Comments
 (0)