Skip to content

Commit

Permalink
feat: allow reordering of array literal expressions (#1323)
Browse files Browse the repository at this point in the history
  • Loading branch information
jackofdiamond5 authored Nov 21, 2024
1 parent 74486c6 commit 412a2a5
Show file tree
Hide file tree
Showing 15 changed files with 575 additions and 216 deletions.
4 changes: 2 additions & 2 deletions packages/cli/templates/react/ReactTypeScriptFileUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export class ReactTypeScriptFileUpdate extends TypeScriptFileUpdate {
this.astTransformer.requestNewMembersInArrayLiteral(
variableAsParentCondition(this.astTransformer, ROUTES_VARIABLE_NAME),
[newRoute],
prepend,
anchorElement
anchorElement,
{ prepend }
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ export class WebComponentsTypeScriptFileUpdate extends TypeScriptFileUpdate {
this.astTransformer.requestNewMembersInArrayLiteral(
variableAsParentCondition(this.astTransformer, ROUTES_VARIABLE_NAME),
[newRoute],
prepend,
anchorElement
anchorElement,
{ prepend }
);
}

Expand Down
4 changes: 2 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
"author": "Infragistics",
"license": "MIT",
"dependencies": {
"@inquirer/prompts": "^5.4.0",
"@inquirer/prompts": "~5.4.0",
"chalk": "^2.3.2",
"glob": "^7.1.2",
"through2": "^4.0.2",
"through2": "^2.0.3",
"typescript": "~5.5.4"
},
"devDependencies": {
Expand Down
35 changes: 35 additions & 0 deletions packages/core/types/types-typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,38 @@ export interface ChangeRequest<T extends ts.Node> {
*/
node: T | ts.NodeArray<T>;
}

/**
* Options that can be applied when modifying a literal expression.
*/
export interface LiteralExpressionOptionsBase {
/**
* Whether the literal should be on multiple lines.
* @remarks This option is only applicable to {@link ts.ObjectLiteralExpression} and {@link ts.ArrayLiteralExpression}.
*/
multiline?: boolean;
}

/**
* Options that can be applied when modifying an {@link ts.ObjectLiteralExpression}.
*/
export interface ObjectLiteralExpressionEditOptions
extends LiteralExpressionOptionsBase {
/**
* Whether to override all elements of the property's initializer.
* @remarks This option is only applicable to {@link ts.PropertyAssignment} with an initializer that is {@link ts.ArrayLiteralExpression}.
* All other initializers will be overridden by default.
*/
override?: boolean;
}

/**
* Options that can be applied when modifying an {@link ts.ArrayLiteralExpression}.
*/
export interface ArrayLiteralExpressionEditOptions
extends LiteralExpressionOptionsBase {
/**
* If any elements should be added at the beginning of an {@link ts.ArrayLiteralExpression}.
*/
prepend?: boolean;
}
141 changes: 65 additions & 76 deletions packages/core/typescript/TransformerFactories.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as ts from 'typescript';
import {
ArrayLiteralExpressionEditOptions,
Identifier,
ImportDeclarationMeta,
ObjectLiteralExpressionEditOptions,
PropertyAssignment,
} from '../types';
import { SIDE_EFFECTS_IMPORT_TEMPLATE_NAME } from '../util';
Expand All @@ -26,8 +28,8 @@ import { TypeScriptExpressionCollector } from './TypeScriptExpressionCollector';
export const newMemberInObjectLiteralTransformerFactory = (
newProperty: ts.PropertyAssignment,
visitCondition: (node: ts.Node) => boolean,
multiline: boolean,
expressionCollector: TypeScriptExpressionCollector
expressionCollector: TypeScriptExpressionCollector,
options: ObjectLiteralExpressionEditOptions
): ts.TransformerFactory<ts.SourceFile> => {
return <T extends ts.Node>(context: ts.TransformationContext) => {
return (rootNode: T) => {
Expand Down Expand Up @@ -73,7 +75,8 @@ export const newMemberInObjectLiteralTransformerFactory = (
context,
node,
expressionCollector,
multiline
options?.multiline,
options?.override
);
}

Expand All @@ -91,53 +94,14 @@ export const newMemberInObjectLiteralTransformerFactory = (
};
};

/**
* Creates a {@link ts.TransformerFactory} that updates a member in a {@link ts.ObjectLiteralExpression}.
*/
export const updateForObjectLiteralMemberTransformerFactory = (
visitCondition: (node: ts.ObjectLiteralExpression) => boolean,
targetMember: PropertyAssignment
): ts.TransformerFactory<ts.SourceFile> => {
return <T extends ts.Node>(context: ts.TransformationContext) => {
return (rootNode: T) => {
const visitor = (node: ts.Node): ts.VisitResult<ts.Node> => {
if (ts.isObjectLiteralExpression(node) && visitCondition(node)) {
const newProperties = node.properties.map((property) => {
const isPropertyAssignment = ts.isPropertyAssignment(property);
if (
isPropertyAssignment &&
ts.isIdentifier(property.name) &&
property.name.text === targetMember.name
) {
return context.factory.updatePropertyAssignment(
property,
property.name,
targetMember.value
);
}
return property;
});

return context.factory.updateObjectLiteralExpression(
node,
newProperties
);
}
return ts.visitEachChild(node, visitor, context);
};
return ts.visitNode(rootNode, visitor, ts.isSourceFile);
};
};
};

/**
* Creates a {@link ts.TransformerFactory} that adds a new element to a {@link ts.ArrayLiteralExpression}.
*/
export const newMemberInArrayLiteralTransformerFactory = (
visitCondition: (node: ts.ArrayLiteralExpression) => boolean,
elements: ts.Expression[],
prepend: boolean = false,
anchorElement?: ts.StringLiteral | ts.NumericLiteral | PropertyAssignment
anchorElement?: ts.StringLiteral | ts.NumericLiteral | PropertyAssignment,
options?: ArrayLiteralExpressionEditOptions
): ts.TransformerFactory<ts.SourceFile> => {
return <T extends ts.Node>(context: ts.TransformationContext) => {
return (rootNode: T) => {
Expand Down Expand Up @@ -175,9 +139,14 @@ export const newMemberInArrayLiteralTransformerFactory = (
});
}

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

if (prepend) {
if (options?.prepend) {
return context.factory.updateArrayLiteralExpression(node, [
...elements,
...node.elements,
Expand All @@ -213,6 +182,27 @@ export const newMemberInArrayLiteralTransformerFactory = (
};
};

/**
* Creates a {@link ts.TransformerFactory} that sorts the elements in a {@link ts.ArrayLiteralExpression}.
*/
export const sortInArrayLiteralTransformerFactory = (
visitCondition: (node: ts.ArrayLiteralExpression) => boolean,
sortCondition: (a: ts.Expression, b: ts.Expression) => number
) => {
return <T extends ts.Node>(context: ts.TransformationContext) => {
return (rootNode: T) => {
const visitor = (node: ts.Node): ts.VisitResult<ts.Node> => {
if (ts.isArrayLiteralExpression(node) && visitCondition(node)) {
const elements = [...node.elements].sort(sortCondition);
return context.factory.updateArrayLiteralExpression(node, elements);
}
return ts.visitEachChild(node, visitor, context);
};
return ts.visitNode(rootNode, visitor, ts.isSourceFile);
};
};
};

/**
* Creates a {@link ts.TransformerFactory} that adds a new argument to a {@link ts.CallExpression}.
*/
Expand Down Expand Up @@ -488,29 +478,7 @@ function updatePropertyAssignmentWithIdentifier(
? newProperty.initializer
: newProperty.objectAssignmentInitializer;

const updatedProperty = ts.isPropertyAssignment(existingProperty)
? context.factory.updatePropertyAssignment(
existingProperty,
existingProperty.name,
newPropInitializer
)
: context.factory.updateShorthandPropertyAssignment(
existingProperty,
existingProperty.name,
newPropInitializer
);
const structure = Array.from(node.properties);
const targetIndex = structure.indexOf(existingProperty);
if (targetIndex > -1) {
// attempt to modify the property assignment and preserve the order
structure[targetIndex] = updatedProperty;
return context.factory.updateObjectLiteralExpression(node, structure);
}
// append the property assignment at the end
return context.factory.updateObjectLiteralExpression(node, [
...node.properties.filter((p) => p !== existingProperty),
updatedProperty,
]);
return updateProperty(node, existingProperty, newPropInitializer, context);
}

/**
Expand All @@ -520,14 +488,16 @@ function updatePropertyAssignmentWithIdentifier(
* @param context The transformation context.
* @param node The object literal expression node.
* @param multiline Whether the array literal should be multiline.
* @param override Whether to override all elements if the property's initializer is an array.
*/
function updatePropertyAssignmentWithArrayLiteral(
newProperty: ts.PropertyAssignment | ts.ShorthandPropertyAssignment,
existingProperty: ts.PropertyAssignment | ts.ShorthandPropertyAssignment,
context: ts.TransformationContext,
node: ts.ObjectLiteralExpression,
expressionCollector: TypeScriptExpressionCollector,
multiline: boolean
multiline: boolean,
override: boolean
): ts.ObjectLiteralExpression {
const existingPropInitializer = ts.isPropertyAssignment(existingProperty)
? existingProperty.initializer
Expand All @@ -543,25 +513,44 @@ function updatePropertyAssignmentWithArrayLiteral(
const newElements = ts.isArrayLiteralExpression(newPropInitializer)
? [...newPropInitializer.elements]
: [newPropInitializer];
const uniqueElements = expressionCollector.collectUniqueExpressions([
...elements,
...newElements,
]);
const uniqueElements = override
? expressionCollector.collectUniqueExpressions(newElements)
: expressionCollector.collectUniqueExpressions([
...elements,
...newElements,
]);

const valueExpression = context.factory.createArrayLiteralExpression(
uniqueElements,
multiline
);

return updateProperty(node, existingProperty, valueExpression, context);
}

/**
* Updates a {@link ts.PropertyAssignment} with a new {@link ts.Initializer}.
* @param node The object literal expression node.
* @param existingProperty The property to update.
* @param newInitializer The new initializer to set.
* @param context The transformation context.
*/
function updateProperty(
node: ts.ObjectLiteralExpression,
existingProperty: ts.PropertyAssignment | ts.ShorthandPropertyAssignment,
newInitializer: ts.Expression,
context: ts.TransformationContext
): ts.ObjectLiteralExpression {
const updatedProperty = ts.isPropertyAssignment(existingProperty)
? context.factory.updatePropertyAssignment(
existingProperty,
existingProperty.name,
valueExpression
newInitializer
)
: context.factory.updateShorthandPropertyAssignment(
existingProperty,
existingProperty.name,
valueExpression
newInitializer
);

const structure = Array.from(node.properties);
Expand Down
Loading

0 comments on commit 412a2a5

Please sign in to comment.